/src/logging-log4cxx/src/main/cpp/smtpappender.cpp
Line | Count | Source |
1 | | /* |
2 | | * Licensed to the Apache Software Foundation (ASF) under one or more |
3 | | * contributor license agreements. See the NOTICE file distributed with |
4 | | * this work for additional information regarding copyright ownership. |
5 | | * The ASF licenses this file to You under the Apache License, Version 2.0 |
6 | | * (the "License"); you may not use this file except in compliance with |
7 | | * the License. You may obtain a copy of the License at |
8 | | * |
9 | | * http://www.apache.org/licenses/LICENSE-2.0 |
10 | | * |
11 | | * Unless required by applicable law or agreed to in writing, software |
12 | | * distributed under the License is distributed on an "AS IS" BASIS, |
13 | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
14 | | * See the License for the specific language governing permissions and |
15 | | * limitations under the License. |
16 | | */ |
17 | | #include <log4cxx/net/smtpappender.h> |
18 | | #include <log4cxx/level.h> |
19 | | #include <log4cxx/helpers/loglog.h> |
20 | | #include <log4cxx/helpers/optionconverter.h> |
21 | | #include <log4cxx/spi/loggingevent.h> |
22 | | #include <log4cxx/private/string_c11.h> |
23 | | #include <log4cxx/helpers/stringhelper.h> |
24 | | #include <log4cxx/helpers/stringtokenizer.h> |
25 | | #include <log4cxx/helpers/transcoder.h> |
26 | | #include <log4cxx/helpers/loader.h> |
27 | | #if !defined(LOG4CXX) |
28 | | #define LOG4CXX 1 |
29 | | #endif |
30 | | #include <log4cxx/private/log4cxx_private.h> |
31 | | #include <log4cxx/private/appenderskeleton_priv.h> |
32 | | |
33 | | |
34 | | #include <apr_strings.h> |
35 | | #include <vector> |
36 | | |
37 | | using namespace LOG4CXX_NS; |
38 | | using namespace LOG4CXX_NS::helpers; |
39 | | using namespace LOG4CXX_NS::net; |
40 | | using namespace LOG4CXX_NS::spi; |
41 | | |
42 | | #if LOG4CXX_HAVE_LIBESMTP |
43 | | #include <auth-client.h> |
44 | | #include <libesmtp.h> |
45 | | #endif |
46 | | |
47 | | namespace |
48 | | { |
49 | | // RFC 5322 §2.1 defines header fields as CRLF-terminated lines, so an embedded |
50 | | // CR or LF in a configured Subject/From/To/Cc/Bcc value would split it across |
51 | | // header boundaries on the wire — a caller who controls a configured field |
52 | | // (e.g. through ${...} property substitution from an environment variable) |
53 | | // could inject arbitrary additional headers such as Bcc. The library already |
54 | | // owns SMTP wire-format sanitization (see SMTPSession::toAscii, which silently |
55 | | // rewrites non-ASCII to '?'); strip CR/LF in the public setters so the same |
56 | | // boundary is enforced regardless of how the value reaches the appender. |
57 | | LogString stripSmtpControl(const LogString& value, const logchar* field) |
58 | 0 | { |
59 | 0 | if (value.find_first_of(LOG4CXX_STR("\r\n")) == LogString::npos) |
60 | 0 | { |
61 | 0 | return value; |
62 | 0 | } |
63 | 0 | LogString warning(LOG4CXX_STR("SMTPAppender ")); |
64 | 0 | warning.append(field); |
65 | 0 | warning.append(LOG4CXX_STR(" contains CR or LF; stripping to prevent SMTP header injection.")); |
66 | 0 | LogLog::warn(warning); |
67 | 0 | LogString out; |
68 | 0 | out.reserve(value.size()); |
69 | 0 | for (auto ch : value) |
70 | 0 | { |
71 | 0 | if (ch != 0x0D && ch != 0x0A) |
72 | 0 | { |
73 | 0 | out.append(1, ch); |
74 | 0 | } |
75 | 0 | } |
76 | 0 | return out; |
77 | 0 | } |
78 | | |
79 | | } // namespace |
80 | | |
81 | | namespace LOG4CXX_NS |
82 | | { |
83 | | namespace net |
84 | | { |
85 | | // |
86 | | // The following two classes implement an C++ SMTP wrapper over libesmtp. |
87 | | // The same signatures could be implemented over different SMTP implementations |
88 | | // or libesmtp could be combined with libgmime to enable support for non-ASCII |
89 | | // content. |
90 | | |
91 | | #if LOG4CXX_HAVE_LIBESMTP |
92 | | /** |
93 | | * SMTP Session. |
94 | | */ |
95 | | class SMTPSession |
96 | | { |
97 | | public: |
98 | | /** |
99 | | * Create new instance. |
100 | | */ |
101 | | SMTPSession(const LogString& smtpHost, |
102 | | int smtpPort, |
103 | | const LogString& smtpUsername, |
104 | | const LogString& smtpPassword, |
105 | | bool allowPlainTextAuth, |
106 | | Pool& p |
107 | | ) |
108 | | : user{toAscii(smtpUsername, p)} |
109 | | , pwd{toAscii(smtpPassword, p)} |
110 | | { |
111 | | auth_client_init(); |
112 | | session = smtp_create_session(); |
113 | | if (session == 0) |
114 | | { |
115 | | throw Exception("Could not initialize session."); |
116 | | } |
117 | | |
118 | | std::string host(toAscii(smtpHost, p)); |
119 | | host.append(1, ':'); |
120 | | host.append(p.itoa(smtpPort)); |
121 | | smtp_set_server(session, host.c_str()); |
122 | | smtp_set_monitorcb(session, monitor_cb, (void*)this, 1); |
123 | | smtp_set_eventcb(session, event_cb, (void*)this); |
124 | | |
125 | | authctx = auth_create_context(); |
126 | | auth_set_mechanism_flags(authctx, AUTH_PLUGIN_PLAIN, 0); |
127 | | auth_set_interact_cb(authctx, authinteract, (void*) this); |
128 | | |
129 | | if (*user || *pwd) |
130 | | { |
131 | | // Secure by default: never send AUTH credentials over an |
132 | | // unencrypted connection. Require STARTTLS before |
133 | | // authenticating unless the operator explicitly opted in |
134 | | // to plain-text authentication via the |
135 | | // AllowPlainTextAuthentication option. |
136 | | if (!allowPlainTextAuth && !smtp_starttls_enable(session, Starttls_REQUIRED)) |
137 | | { |
138 | | // The destructor does not run when a constructor throws |
139 | | smtp_destroy_session(session); |
140 | | auth_destroy_context(authctx); |
141 | | throw Exception("SMTPAppender: STARTTLS is unavailable in this libESMTP build;" |
142 | | " refusing to send SMTP credentials in clear text." |
143 | | " Set AllowPlainTextAuthentication=true to override."); |
144 | | } |
145 | | smtp_auth_set_context(session, authctx); |
146 | | } |
147 | | } |
148 | | |
149 | | ~SMTPSession() |
150 | | { |
151 | | smtp_destroy_session(session); |
152 | | auth_destroy_context(authctx); |
153 | | } |
154 | | |
155 | | void send() |
156 | | { |
157 | | int status = smtp_start_session(session); |
158 | | |
159 | | if (!status) |
160 | | { |
161 | | static const size_t smtp_msgSize = 128; |
162 | | char smtp_msg[smtp_msgSize]; |
163 | | smtp_strerror(smtp_errno(), smtp_msg, smtp_msgSize); |
164 | | char msg[2 * smtp_msgSize]; |
165 | | snprintf(msg, sizeof (msg), "%s (sessionState %d isActive? %d tlsStarted? %d)" |
166 | | , smtp_msg, sessionState, isActive, tlsStarted); |
167 | | throw Exception(msg); |
168 | | } |
169 | | else if (incorrectAuthentication) |
170 | | throw Exception("Incorrect authentication data"); |
171 | | else if (relayDenied) |
172 | | throw Exception("Relay Denied"); |
173 | | else if (!certificateProblem.empty()) |
174 | | throw Exception(("X509 error: " + certificateProblem).c_str()); |
175 | | } |
176 | | |
177 | | operator smtp_session_t() |
178 | | { |
179 | | return session; |
180 | | } |
181 | | |
182 | | static char* toAscii(const LogString& str, Pool& p) |
183 | | { |
184 | | char* buf = p.pstralloc(str.length() + 1); |
185 | | char* current = buf; |
186 | | |
187 | | for (unsigned int c : str) |
188 | | { |
189 | | if (c > 0x7F) |
190 | | { |
191 | | c = '?'; |
192 | | } |
193 | | |
194 | | *current++ = c; |
195 | | } |
196 | | |
197 | | *current = 0; |
198 | | return buf; |
199 | | } |
200 | | |
201 | | private: |
202 | | SMTPSession(SMTPSession&); |
203 | | SMTPSession& operator=(SMTPSession&); |
204 | | smtp_session_t session{0}; |
205 | | auth_context_t authctx{0}; |
206 | | char* user; |
207 | | char* pwd; |
208 | | int sessionState{0}; |
209 | | bool isActive{false}; |
210 | | bool tlsStarted{false}; |
211 | | bool incorrectAuthentication{false}; |
212 | | bool relayDenied{false}; |
213 | | std::string certificateProblem; |
214 | | |
215 | | /** |
216 | | * This method is called if the SMTP server requests authentication. |
217 | | */ |
218 | | static int authinteract(auth_client_request_t request, char** result, int fields, |
219 | | void* arg) |
220 | | { |
221 | | auto pThis = static_cast<SMTPSession*>(arg); |
222 | | |
223 | | for (int i = 0; i < fields; i++) |
224 | | { |
225 | | int flag = request[i].flags & 0x07; |
226 | | |
227 | | if (flag == AUTH_USER) |
228 | | { |
229 | | result[i] = pThis->user; |
230 | | } |
231 | | else if (flag == AUTH_PASS) |
232 | | { |
233 | | result[i] = pThis->pwd; |
234 | | } |
235 | | } |
236 | | |
237 | | return 1; |
238 | | } |
239 | | |
240 | | static void monitor_cb(const char *buf, int buflen, int writing, void *arg) |
241 | | { |
242 | | auto pThis = static_cast<SMTPSession*>(arg); |
243 | | if (writing) |
244 | | pThis->isActive = true; |
245 | | else if (auto smtp_response = atoi(buf)) |
246 | | { |
247 | | if (535 == smtp_response) |
248 | | pThis->incorrectAuthentication = true; |
249 | | if (550 == smtp_response) |
250 | | pThis->relayDenied = true; |
251 | | } |
252 | | |
253 | | if (LogLog::isDebugEnabled()) |
254 | | { |
255 | | while (0 < buflen && std::isspace(buf[buflen - 1])) |
256 | | --buflen; |
257 | | std::string data(buf, buflen); |
258 | | LOG4CXX_DECODE_CHAR(lsData, data); |
259 | | LogString type = writing ? LOG4CXX_STR("send") : LOG4CXX_STR("recv"); |
260 | | LogLog::debug(LOG4CXX_STR("SMTP ") + type + LOG4CXX_STR(" [") + lsData + LOG4CXX_STR("]")); |
261 | | } |
262 | | } |
263 | | |
264 | | static void event_cb (smtp_session_t session /* unused */, int event_no, void *arg,...) |
265 | | { |
266 | | auto pThis = static_cast<SMTPSession*>(arg); |
267 | | va_list alist; |
268 | | va_start(alist, arg); |
269 | | switch (event_no) |
270 | | { |
271 | | case SMTP_EV_CONNECT: |
272 | | case SMTP_EV_MAILSTATUS: |
273 | | case SMTP_EV_RCPTSTATUS: |
274 | | case SMTP_EV_MESSAGEDATA: |
275 | | case SMTP_EV_MESSAGESENT: |
276 | | case SMTP_EV_DISCONNECT: |
277 | | pThis->sessionState = event_no; |
278 | | break; |
279 | | case SMTP_EV_WEAK_CIPHER: |
280 | | { |
281 | | auto bitsRequired = va_arg(alist, long); |
282 | | pThis->certificateProblem = "weak cipher"; |
283 | | if (auto ok = va_arg(alist, int*)) |
284 | | *ok = 1; // Accept the problem |
285 | | break; |
286 | | } |
287 | | case SMTP_EV_STARTTLS_OK: |
288 | | pThis->tlsStarted = true; |
289 | | break; |
290 | | case SMTP_EV_INVALID_PEER_CERTIFICATE: |
291 | | pThis->certificateProblem = get_X509_error(va_arg(alist, long)); |
292 | | if (auto ok = va_arg(alist, int*)) |
293 | | *ok = 1; // Accept the problem |
294 | | break; |
295 | | case SMTP_EV_NO_PEER_CERTIFICATE: |
296 | | pThis->certificateProblem = "no peer certificate"; |
297 | | if (auto ok = va_arg(alist, int*)) |
298 | | *ok = 1; |
299 | | break; |
300 | | case SMTP_EV_WRONG_PEER_CERTIFICATE: |
301 | | pThis->certificateProblem = "wrong peer certificate"; |
302 | | if (auto ok = va_arg(alist, int*)) |
303 | | *ok = 1; // Accept the problem |
304 | | break; |
305 | | case SMTP_EV_NO_CLIENT_CERTIFICATE: |
306 | | pThis->certificateProblem = "no client certificate"; |
307 | | if (auto ok = va_arg(alist, int*)) |
308 | | *ok = 1; // Accept the problem |
309 | | break; |
310 | | } |
311 | | va_end(alist); |
312 | | } |
313 | | |
314 | | static std::string get_X509_error(long verifyResult) |
315 | | { |
316 | | switch (verifyResult) |
317 | | { |
318 | | case 2: return "unable to get issuer cert"; |
319 | | case 3: return "unable to get crl"; |
320 | | case 4: return "unable to decrypt cert signature"; |
321 | | case 5: return "unable to decrypt crl signature"; |
322 | | case 6: return "unable to decode issuer public key"; |
323 | | case 7: return "cert signature failure"; |
324 | | case 8: return "crl signature failure"; |
325 | | case 9: return "cert not yet valid"; |
326 | | case 10: return "cert has expired"; |
327 | | case 11: return "crl not yet valid"; |
328 | | case 12: return "crl has expired"; |
329 | | case 13: return "error in cert not before field"; |
330 | | case 14: return "error in cert not after field"; |
331 | | case 15: return "error in crl last update field"; |
332 | | case 16: return "error in crl next update field"; |
333 | | case 17: return "out of mem"; |
334 | | case 18: return "depth zero self signed cert"; |
335 | | case 19: return "self signed cert in chain"; |
336 | | case 20: return "unable to get issuer cert locally"; |
337 | | case 21: return "unable to verify leaf signature"; |
338 | | case 22: return "cert chain too long"; |
339 | | case 23: return "cert revoked"; |
340 | | case 24: return "invalid ca"; |
341 | | case 25: return "path length exceeded"; |
342 | | case 26: return "invalid purpose"; |
343 | | case 27: return "cert untrusted"; |
344 | | case 28: return "cert rejected"; |
345 | | case 29: return "subject issuer mismatch"; |
346 | | case 30: return "akid skid mismatch"; |
347 | | case 31: return "akid issuer serial mismatch"; |
348 | | case 32: return "keyusage no certsign"; |
349 | | case 33: return "unable to get crl issuer"; |
350 | | case 34: return "unhandled critical extension"; |
351 | | case 35: return "keyusage no crl sign"; |
352 | | case 36: return "unhandled critical crl extension"; |
353 | | case 37: return "invalid non ca"; |
354 | | case 38: return "proxy path length exceeded"; |
355 | | case 39: return "keyusage no digital signature"; |
356 | | case 40: return "proxy certificates not allowed"; |
357 | | case 41: return "invalid extension"; |
358 | | case 42: return "invalid policy extension"; |
359 | | case 43: return "no explicit policy"; |
360 | | case 44: return "different crl scope"; |
361 | | case 45: return "unsupported extension feature"; |
362 | | case 46: return "unnested resource"; |
363 | | case 47: return "permitted violation"; |
364 | | case 48: return "excluded violation"; |
365 | | case 49: return "subtree minmax"; |
366 | | case 51: return "unsupported constraint type"; |
367 | | case 52: return "unsupported constraint syntax"; |
368 | | case 53: return "unsupported name syntax"; |
369 | | case 54: return "crl path validation error"; |
370 | | case 50: return "application verification"; |
371 | | } |
372 | | return "unknown"; |
373 | | } |
374 | | }; |
375 | | |
376 | | /** |
377 | | * A message in an SMTP session. |
378 | | */ |
379 | | class SMTPMessage |
380 | | { |
381 | | public: |
382 | | SMTPMessage(SMTPSession& session, |
383 | | const LogString& from, |
384 | | const LogString& to, |
385 | | const LogString& cc, |
386 | | const LogString& bcc, |
387 | | const LogString& subject, |
388 | | const LogString msg, Pool& p) |
389 | | { |
390 | | message = smtp_add_message(session); |
391 | | body = current = toMessage(msg, p, current_len); |
392 | | messagecbState = 0; |
393 | | smtp_set_reverse_path(message, toAscii(from, p)); |
394 | | addRecipients(to, "To", p); |
395 | | addRecipients(cc, "Cc", p); |
396 | | addRecipients(bcc, "Bcc", p); |
397 | | |
398 | | if (!subject.empty()) |
399 | | { |
400 | | smtp_set_header(message, "Subject", toAscii(subject, p)); |
401 | | } |
402 | | |
403 | | smtp_set_messagecb(message, messagecb, this); |
404 | | } |
405 | | ~SMTPMessage() |
406 | | { |
407 | | } |
408 | | |
409 | | private: |
410 | | SMTPMessage(const SMTPMessage&); |
411 | | SMTPMessage& operator=(const SMTPMessage&); |
412 | | smtp_message_t message; |
413 | | const char* body; |
414 | | const char* current; |
415 | | size_t current_len; |
416 | | int messagecbState; |
417 | | void addRecipients(const LogString& addresses, const char* field, Pool& p) |
418 | | { |
419 | | if (!addresses.empty()) |
420 | | { |
421 | | char* str = p.pstrdup(toAscii(addresses, p));; |
422 | | smtp_set_header(message, field, NULL, str); |
423 | | char* last; |
424 | | |
425 | | for (char* next = apr_strtok(str, ",", &last); |
426 | | next; |
427 | | next = apr_strtok(NULL, ",", &last)) |
428 | | { |
429 | | smtp_add_recipient(message, next); |
430 | | } |
431 | | } |
432 | | } |
433 | | static const char* toAscii(const LogString& str, Pool& p) |
434 | | { |
435 | | return SMTPSession::toAscii(str, p); |
436 | | } |
437 | | |
438 | | /** |
439 | | * Message bodies can only contain US-ASCII characters and |
440 | | * CR and LFs can only occur together. |
441 | | * On return \c lenOut holds the length of the converted body. |
442 | | */ |
443 | | static const char* toMessage(const LogString& str, Pool& p, size_t& lenOut) |
444 | | { |
445 | | // |
446 | | // count the number of carriage returns and line feeds |
447 | | // |
448 | | int feedCount = 0; |
449 | | |
450 | | for (size_t pos = str.find_first_of(LOG4CXX_STR("\n\r")); |
451 | | pos != LogString::npos; |
452 | | pos = str.find_first_of(LOG4CXX_STR("\n\r"), ++pos)) |
453 | | { |
454 | | feedCount++; |
455 | | } |
456 | | |
457 | | // |
458 | | // allocate sufficient space for the modified message |
459 | | char* retval = p.pstralloc(str.length() + feedCount + 1); |
460 | | char* current = retval; |
461 | | char* startOfLine = current; |
462 | | unsigned int ignoreChar = 0; |
463 | | |
464 | | // |
465 | | // iterator through message |
466 | | // |
467 | | for (unsigned int c : str) |
468 | | { |
469 | | // |
470 | | // replace non-ASCII characters and embedded NULs with '?' |
471 | | // (a NUL octet must never act as a body terminator) |
472 | | // |
473 | | if (c > 0x7F || c == 0) |
474 | | { |
475 | | *current++ = 0x3F; // '?' |
476 | | } |
477 | | else if (c == 0x0A || c == 0x0D) |
478 | | { |
479 | | // |
480 | | // replace any stray CR or LF with CRLF |
481 | | // reset start of line |
482 | | if (c == ignoreChar && current == startOfLine) |
483 | | ignoreChar = 0; |
484 | | else |
485 | | { |
486 | | *current++ = 0x0D; |
487 | | *current++ = 0x0A; |
488 | | startOfLine = current; |
489 | | ignoreChar = (c == 0x0A ? 0x0D : 0x0A); |
490 | | } |
491 | | } |
492 | | else |
493 | | { |
494 | | // |
495 | | // truncate any lines to 1000 characters (including CRLF) |
496 | | // as required by RFC. |
497 | | if (current < startOfLine + 998) |
498 | | { |
499 | | *current++ = (char) c; |
500 | | } |
501 | | } |
502 | | } |
503 | | |
504 | | *current = 0; |
505 | | lenOut = current - retval; |
506 | | return retval; |
507 | | } |
508 | | |
509 | | /** |
510 | | * Callback for message. |
511 | | */ |
512 | | static const char* messagecb(void** ctx, int* len, void* arg) |
513 | | { |
514 | | *ctx = 0; |
515 | | const char* retval = 0; |
516 | | SMTPMessage* pThis = (SMTPMessage*) arg; |
517 | | |
518 | | // rewind message |
519 | | if (len == NULL) |
520 | | { |
521 | | pThis->current = pThis->body; |
522 | | } |
523 | | else |
524 | | { |
525 | | // we are asked for headers, but we don't have any |
526 | | if ((pThis->messagecbState)++ == 0) |
527 | | { |
528 | | return NULL; |
529 | | } |
530 | | |
531 | | if (pThis->current) |
532 | | { |
533 | | // Use the stored post-conversion length: strnlen_s over the |
534 | | // pre-conversion length truncates at an embedded NUL and |
535 | | // undercounts the CRLF-expanded body, silently dropping the |
536 | | // newest content from the alert email. |
537 | | *len = static_cast<int>(pThis->current_len); |
538 | | } |
539 | | |
540 | | retval = pThis->current; |
541 | | pThis->current = 0; |
542 | | } |
543 | | |
544 | | return retval; |
545 | | } |
546 | | |
547 | | }; |
548 | | #endif |
549 | | |
550 | | class LOG4CXX_EXPORT DefaultEvaluator |
551 | | #if LOG4CXX_ABI_VERSION <= 15 |
552 | | : public virtual spi::TriggeringEventEvaluator |
553 | | , public virtual helpers::Object |
554 | | #else |
555 | | : public spi::TriggeringEventEvaluator |
556 | | #endif |
557 | | { |
558 | | public: |
559 | | DECLARE_LOG4CXX_OBJECT(DefaultEvaluator) |
560 | 0 | BEGIN_LOG4CXX_CAST_MAP() |
561 | 0 | LOG4CXX_CAST_ENTRY(DefaultEvaluator) |
562 | 0 | LOG4CXX_CAST_ENTRY(spi::TriggeringEventEvaluator) |
563 | 0 | END_LOG4CXX_CAST_MAP() |
564 | | |
565 | | DefaultEvaluator(); |
566 | | |
567 | | /** |
568 | | Is this <code>event</code> the e-mail triggering event? |
569 | | <p>This method returns <code>true</code>, if the event level |
570 | | has ERROR level or higher. Otherwise it returns |
571 | | <code>false</code>. |
572 | | */ |
573 | | bool isTriggeringEvent(const spi::LoggingEventPtr& event) override; |
574 | | private: |
575 | | DefaultEvaluator(const DefaultEvaluator&); |
576 | | DefaultEvaluator& operator=(const DefaultEvaluator&); |
577 | | }; // class DefaultEvaluator |
578 | | |
579 | | } |
580 | | } |
581 | | |
582 | | IMPLEMENT_LOG4CXX_OBJECT(DefaultEvaluator) |
583 | | IMPLEMENT_LOG4CXX_OBJECT(SMTPAppender) |
584 | | |
585 | | struct SMTPAppender::SMTPPriv : public AppenderSkeletonPrivate |
586 | | { |
587 | | SMTPPriv() : |
588 | 0 | AppenderSkeletonPrivate(), |
589 | 0 | smtpPort(25), |
590 | 0 | bufferSize(512), |
591 | 0 | locationInfo(false), |
592 | 0 | cb(bufferSize), |
593 | 0 | evaluator(new DefaultEvaluator()) {} |
594 | | |
595 | | SMTPPriv(spi::TriggeringEventEvaluatorPtr evaluator) : |
596 | 0 | AppenderSkeletonPrivate(), |
597 | 0 | smtpPort(25), |
598 | 0 | bufferSize(512), |
599 | 0 | locationInfo(false), |
600 | 0 | cb(bufferSize), |
601 | 0 | evaluator(evaluator) {} |
602 | | |
603 | | LogString to; |
604 | | LogString cc; |
605 | | LogString bcc; |
606 | | LogString from; |
607 | | LogString subject; |
608 | | LogString smtpHost; |
609 | | LogString smtpUsername; |
610 | | LogString smtpPassword; |
611 | | int smtpPort; |
612 | | int bufferSize; // 512 |
613 | | bool locationInfo; |
614 | | helpers::CyclicBuffer cb; |
615 | | spi::TriggeringEventEvaluatorPtr evaluator; |
616 | | // Whether AUTH credentials may be sent without STARTTLS (see setOption) |
617 | | bool allowPlainTextAuth{false}; |
618 | | }; |
619 | | |
620 | 0 | #define _priv static_cast<SMTPPriv*>(m_priv.get()) |
621 | | |
622 | | DefaultEvaluator::DefaultEvaluator() |
623 | 0 | { |
624 | 0 | } Unexecuted instantiation: log4cxx::net::DefaultEvaluator::DefaultEvaluator() Unexecuted instantiation: log4cxx::net::DefaultEvaluator::DefaultEvaluator() |
625 | | |
626 | | bool DefaultEvaluator::isTriggeringEvent(const spi::LoggingEventPtr& event) |
627 | 0 | { |
628 | 0 | return event->getLevel()->isGreaterOrEqual(Level::getError()); |
629 | 0 | } |
630 | | |
631 | | SMTPAppender::SMTPAppender() |
632 | 0 | : AppenderSkeleton (std::make_unique<SMTPPriv>()) |
633 | 0 | { |
634 | 0 | } Unexecuted instantiation: log4cxx::net::SMTPAppender::SMTPAppender() Unexecuted instantiation: log4cxx::net::SMTPAppender::SMTPAppender() |
635 | | |
636 | | /** |
637 | | Use <code>evaluator</code> passed as parameter as the |
638 | | TriggeringEventEvaluator for this SMTPAppender. */ |
639 | | SMTPAppender::SMTPAppender(spi::TriggeringEventEvaluatorPtr evaluator) |
640 | 0 | : AppenderSkeleton (std::make_unique<SMTPPriv>(evaluator)) |
641 | 0 | { |
642 | 0 | } Unexecuted instantiation: log4cxx::net::SMTPAppender::SMTPAppender(std::__1::shared_ptr<log4cxx::spi::TriggeringEventEvaluator>) Unexecuted instantiation: log4cxx::net::SMTPAppender::SMTPAppender(std::__1::shared_ptr<log4cxx::spi::TriggeringEventEvaluator>) |
643 | | |
644 | | SMTPAppender::~SMTPAppender() |
645 | 0 | { |
646 | 0 | _priv->setClosed(); |
647 | 0 | } |
648 | | |
649 | | bool SMTPAppender::requiresLayout() const |
650 | 0 | { |
651 | 0 | return true; |
652 | 0 | } |
653 | | |
654 | | LogString SMTPAppender::getFrom() const |
655 | 0 | { |
656 | 0 | return _priv->from; |
657 | 0 | } |
658 | | |
659 | | void SMTPAppender::setFrom(const LogString& newVal) |
660 | 0 | { |
661 | 0 | _priv->from = stripSmtpControl(newVal, LOG4CXX_STR("From")); |
662 | 0 | } |
663 | | |
664 | | |
665 | | LogString SMTPAppender::getSubject() const |
666 | 0 | { |
667 | 0 | return _priv->subject; |
668 | 0 | } |
669 | | |
670 | | void SMTPAppender::setSubject(const LogString& newVal) |
671 | 0 | { |
672 | 0 | _priv->subject = stripSmtpControl(newVal, LOG4CXX_STR("Subject")); |
673 | 0 | } |
674 | | |
675 | | LogString SMTPAppender::getSMTPHost() const |
676 | 0 | { |
677 | 0 | return _priv->smtpHost; |
678 | 0 | } |
679 | | |
680 | | void SMTPAppender::setSMTPHost(const LogString& newVal) |
681 | 0 | { |
682 | 0 | _priv->smtpHost = newVal; |
683 | 0 | } |
684 | | |
685 | | int SMTPAppender::getSMTPPort() const |
686 | 0 | { |
687 | 0 | return _priv->smtpPort; |
688 | 0 | } |
689 | | |
690 | | void SMTPAppender::setSMTPPort(int newVal) |
691 | 0 | { |
692 | 0 | _priv->smtpPort = newVal; |
693 | 0 | } |
694 | | |
695 | | bool SMTPAppender::getLocationInfo() const |
696 | 0 | { |
697 | 0 | return _priv->locationInfo; |
698 | 0 | } |
699 | | |
700 | | void SMTPAppender::setLocationInfo(bool newVal) |
701 | 0 | { |
702 | 0 | _priv->locationInfo = newVal; |
703 | 0 | } |
704 | | |
705 | | LogString SMTPAppender::getSMTPUsername() const |
706 | 0 | { |
707 | 0 | return _priv->smtpUsername; |
708 | 0 | } |
709 | | |
710 | | void SMTPAppender::setSMTPUsername(const LogString& newVal) |
711 | 0 | { |
712 | 0 | _priv->smtpUsername = newVal; |
713 | 0 | } |
714 | | |
715 | | LogString SMTPAppender::getSMTPPassword() const |
716 | 0 | { |
717 | 0 | return _priv->smtpPassword; |
718 | 0 | } |
719 | | |
720 | | void SMTPAppender::setSMTPPassword(const LogString& newVal) |
721 | 0 | { |
722 | 0 | _priv->smtpPassword = newVal; |
723 | 0 | } |
724 | | |
725 | | |
726 | | |
727 | | |
728 | | |
729 | | void SMTPAppender::setOption(const LogString& option, |
730 | | const LogString& value) |
731 | 0 | { |
732 | 0 | if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("BUFFERSIZE"), LOG4CXX_STR("buffersize"))) |
733 | 0 | { |
734 | 0 | setBufferSize(OptionConverter::toInt(value, 512)); |
735 | 0 | } |
736 | 0 | else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("EVALUATORCLASS"), LOG4CXX_STR("evaluatorclass"))) |
737 | 0 | { |
738 | 0 | setEvaluatorClass(value); |
739 | 0 | } |
740 | 0 | else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("FROM"), LOG4CXX_STR("from"))) |
741 | 0 | { |
742 | 0 | setFrom(value); |
743 | 0 | } |
744 | 0 | else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("SMTPHOST"), LOG4CXX_STR("smtphost"))) |
745 | 0 | { |
746 | 0 | setSMTPHost(value); |
747 | 0 | } |
748 | 0 | else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("SMTPUSERNAME"), LOG4CXX_STR("smtpusername"))) |
749 | 0 | { |
750 | 0 | setSMTPUsername(value); |
751 | 0 | } |
752 | 0 | else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("SMTPPASSWORD"), LOG4CXX_STR("smtppassword"))) |
753 | 0 | { |
754 | 0 | setSMTPPassword(value); |
755 | 0 | } |
756 | 0 | else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("SUBJECT"), LOG4CXX_STR("subject"))) |
757 | 0 | { |
758 | 0 | setSubject(value); |
759 | 0 | } |
760 | 0 | else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("TO"), LOG4CXX_STR("to"))) |
761 | 0 | { |
762 | 0 | setTo(value); |
763 | 0 | } |
764 | 0 | else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("CC"), LOG4CXX_STR("cc"))) |
765 | 0 | { |
766 | 0 | setCc(value); |
767 | 0 | } |
768 | 0 | else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("BCC"), LOG4CXX_STR("bcc"))) |
769 | 0 | { |
770 | 0 | setBcc(value); |
771 | 0 | } |
772 | 0 | else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("SMTPPORT"), LOG4CXX_STR("smtpport"))) |
773 | 0 | { |
774 | 0 | setSMTPPort(OptionConverter::toInt(value, 25)); |
775 | 0 | } |
776 | 0 | else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("ALLOWPLAINTEXTAUTHENTICATION"), LOG4CXX_STR("allowplaintextauthentication"))) |
777 | 0 | { |
778 | | // Explicit opt-out from the STARTTLS-before-AUTH requirement; |
779 | | // only for servers that cannot offer TLS on a trusted network. |
780 | 0 | _priv->allowPlainTextAuth = OptionConverter::toBoolean(value, false); |
781 | 0 | } |
782 | 0 | else |
783 | 0 | { |
784 | 0 | AppenderSkeleton::setOption(option, value); |
785 | 0 | } |
786 | 0 | } |
787 | | |
788 | | |
789 | | bool SMTPAppender::asciiCheck(const LogString& value, const LogString& field) |
790 | 0 | { |
791 | 0 | for (unsigned int item : value) |
792 | 0 | { |
793 | 0 | if (0x7F < item) |
794 | 0 | { |
795 | 0 | LogLog::warn(field + LOG4CXX_STR(" contains non-ASCII character")); |
796 | 0 | return false; |
797 | 0 | } |
798 | 0 | } |
799 | | |
800 | 0 | return true; |
801 | 0 | } |
802 | | |
803 | | /** |
804 | | Activate the specified options, such as the smtp host, the |
805 | | recipient, from, etc. */ |
806 | | void SMTPAppender::activateOptions( LOG4CXX_ACTIVATE_OPTIONS_FORMAL_PARAMETERS ) |
807 | 0 | { |
808 | 0 | if (_priv->layout == 0) |
809 | 0 | { |
810 | 0 | _priv->errorHandler->error(LOG4CXX_STR("No layout set for appender named [") + _priv->name + LOG4CXX_STR("].")); |
811 | 0 | } |
812 | |
|
813 | 0 | if (_priv->evaluator == 0) |
814 | 0 | { |
815 | 0 | _priv->errorHandler->error(LOG4CXX_STR("No TriggeringEventEvaluator is set for appender [") + |
816 | 0 | _priv->name + LOG4CXX_STR("].")); |
817 | 0 | } |
818 | |
|
819 | 0 | if (_priv->smtpHost.empty()) |
820 | 0 | { |
821 | 0 | _priv->errorHandler->error(LOG4CXX_STR("No smtpHost is set for appender [") + |
822 | 0 | _priv->name + LOG4CXX_STR("].")); |
823 | 0 | } |
824 | |
|
825 | 0 | if (_priv->to.empty() && _priv->cc.empty() && _priv->bcc.empty()) |
826 | 0 | { |
827 | 0 | _priv->errorHandler->error(LOG4CXX_STR("No recipient address is set for appender [") + |
828 | 0 | _priv->name + LOG4CXX_STR("].")); |
829 | 0 | } |
830 | |
|
831 | 0 | asciiCheck(_priv->to, LOG4CXX_STR("to")); |
832 | 0 | asciiCheck(_priv->cc, LOG4CXX_STR("cc")); |
833 | 0 | asciiCheck(_priv->bcc, LOG4CXX_STR("bcc")); |
834 | 0 | asciiCheck(_priv->from, LOG4CXX_STR("from")); |
835 | |
|
836 | 0 | #if !LOG4CXX_HAVE_LIBESMTP |
837 | 0 | _priv->errorHandler->error(LOG4CXX_STR("log4cxx built without SMTP support.")); |
838 | 0 | #endif |
839 | 0 | } |
840 | | |
841 | | /** |
842 | | Perform SMTPAppender specific appending actions, mainly adding |
843 | | the event to a cyclic buffer and checking if the event triggers |
844 | | an e-mail to be sent. */ |
845 | | void SMTPAppender::append( LOG4CXX_APPEND_FORMAL_PARAMETERS ) |
846 | 0 | { |
847 | 0 | if (!checkEntryConditions()) |
848 | 0 | { |
849 | 0 | return; |
850 | 0 | } |
851 | | |
852 | | // Get a copy of this thread's diagnostic context |
853 | 0 | event->LoadDC(); |
854 | |
|
855 | 0 | _priv->cb.add(event); |
856 | |
|
857 | 0 | if (_priv->evaluator->isTriggeringEvent(event)) |
858 | 0 | { |
859 | 0 | Pool p; |
860 | 0 | sendBuffer(p); |
861 | 0 | } |
862 | 0 | } |
863 | | |
864 | | /** |
865 | | This method determines if there is a sense in attempting to append. |
866 | | <p>It checks whether there is a set output target and also if |
867 | | there is a set layout. If these checks fail, then the boolean |
868 | | value <code>false</code> is returned. */ |
869 | | bool SMTPAppender::checkEntryConditions() |
870 | 0 | { |
871 | | #if LOG4CXX_HAVE_LIBESMTP |
872 | | |
873 | | if ((_priv->to.empty() && _priv->cc.empty() && _priv->bcc.empty()) || _priv->from.empty() || _priv->smtpHost.empty()) |
874 | | { |
875 | | _priv->errorHandler->error(LOG4CXX_STR("Message not configured.")); |
876 | | return false; |
877 | | } |
878 | | |
879 | | if (_priv->evaluator == 0) |
880 | | { |
881 | | _priv->errorHandler->error(LOG4CXX_STR("No TriggeringEventEvaluator is set for appender [") + |
882 | | _priv->name + LOG4CXX_STR("].")); |
883 | | return false; |
884 | | } |
885 | | |
886 | | |
887 | | if (_priv->layout == 0) |
888 | | { |
889 | | _priv->errorHandler->error(LOG4CXX_STR("No layout set for appender named [") + _priv->name + LOG4CXX_STR("].")); |
890 | | return false; |
891 | | } |
892 | | |
893 | | return true; |
894 | | #else |
895 | 0 | return false; |
896 | 0 | #endif |
897 | 0 | } |
898 | | |
899 | | |
900 | | |
901 | | void SMTPAppender::close() |
902 | 0 | { |
903 | 0 | _priv->setClosed(); |
904 | 0 | } |
905 | | |
906 | | LogString SMTPAppender::getTo() const |
907 | 0 | { |
908 | 0 | return _priv->to; |
909 | 0 | } |
910 | | |
911 | | void SMTPAppender::setTo(const LogString& addressStr) |
912 | 0 | { |
913 | 0 | _priv->to = stripSmtpControl(addressStr, LOG4CXX_STR("To")); |
914 | 0 | } |
915 | | |
916 | | LogString SMTPAppender::getCc() const |
917 | 0 | { |
918 | 0 | return _priv->cc; |
919 | 0 | } |
920 | | |
921 | | void SMTPAppender::setCc(const LogString& addressStr) |
922 | 0 | { |
923 | 0 | _priv->cc = stripSmtpControl(addressStr, LOG4CXX_STR("Cc")); |
924 | 0 | } |
925 | | |
926 | | LogString SMTPAppender::getBcc() const |
927 | 0 | { |
928 | 0 | return _priv->bcc; |
929 | 0 | } |
930 | | |
931 | | void SMTPAppender::setBcc(const LogString& addressStr) |
932 | 0 | { |
933 | 0 | _priv->bcc = stripSmtpControl(addressStr, LOG4CXX_STR("Bcc")); |
934 | 0 | } |
935 | | |
936 | | /** |
937 | | Send the contents of the cyclic buffer as an e-mail message. |
938 | | */ |
939 | | void SMTPAppender::sendBuffer(Pool& p) |
940 | 0 | { |
941 | | #if LOG4CXX_HAVE_LIBESMTP |
942 | | |
943 | | // This thread owns the mutex for this appender, hence no need to synchronize on 'cb'. |
944 | | try |
945 | | { |
946 | | LogString sbuf; |
947 | | _priv->layout->appendHeader(sbuf); |
948 | | |
949 | | int len = _priv->cb.length(); |
950 | | |
951 | | for (int i = 0; i < len; i++) |
952 | | { |
953 | | LoggingEventPtr event = _priv->cb.get(); |
954 | | _priv->layout->format(sbuf, event); |
955 | | } |
956 | | |
957 | | _priv->layout->appendFooter(sbuf); |
958 | | |
959 | | SMTPSession session(_priv->smtpHost, _priv->smtpPort, _priv->smtpUsername, _priv->smtpPassword, _priv->allowPlainTextAuth, p); |
960 | | |
961 | | SMTPMessage message(session, _priv->from, _priv->to, _priv->cc, |
962 | | _priv->bcc, _priv->subject, sbuf, p); |
963 | | |
964 | | session.send(); |
965 | | |
966 | | } |
967 | | catch (std::exception& e) |
968 | | { |
969 | | _priv->errorHandler->error(LOG4CXX_STR("Error occured while sending e-mail to [") + _priv->smtpHost + LOG4CXX_STR("]."), e, 0); |
970 | | } |
971 | | |
972 | | #endif |
973 | 0 | } |
974 | | |
975 | | /** |
976 | | Returns value of the <b>EvaluatorClass</b> option. |
977 | | */ |
978 | | LogString SMTPAppender::getEvaluatorClass() |
979 | 0 | { |
980 | 0 | return _priv->evaluator == 0 ? LogString() : _priv->evaluator->getClass().getName(); |
981 | 0 | } |
982 | | |
983 | | LOG4CXX_NS::spi::TriggeringEventEvaluatorPtr SMTPAppender::getEvaluator() const |
984 | 0 | { |
985 | 0 | return _priv->evaluator; |
986 | 0 | } |
987 | | |
988 | | void SMTPAppender::setEvaluator(LOG4CXX_NS::spi::TriggeringEventEvaluatorPtr& trigger) |
989 | 0 | { |
990 | 0 | _priv->evaluator = trigger; |
991 | 0 | } |
992 | | |
993 | | /** |
994 | | The <b>BufferSize</b> option takes a positive integer |
995 | | representing the maximum number of logging events to collect in a |
996 | | cyclic buffer. When the <code>BufferSize</code> is reached, |
997 | | oldest events are deleted as new events are added to the |
998 | | buffer. By default the size of the cyclic buffer is 512 events. |
999 | | */ |
1000 | | void SMTPAppender::setBufferSize(int sz) |
1001 | 0 | { |
1002 | 0 | if (sz < 1) |
1003 | 0 | { |
1004 | 0 | sz = 1; |
1005 | 0 | } |
1006 | |
|
1007 | 0 | _priv->bufferSize = sz; |
1008 | 0 | _priv->cb.resize(sz); |
1009 | 0 | } |
1010 | | |
1011 | | /** |
1012 | | The <b>EvaluatorClass</b> option takes a string value |
1013 | | representing the name of the class implementing the {@link |
1014 | | TriggeringEventEvaluator} interface. A corresponding object will |
1015 | | be instantiated and assigned as the triggering event evaluator |
1016 | | for the SMTPAppender. |
1017 | | */ |
1018 | | void SMTPAppender::setEvaluatorClass(const LogString& value) |
1019 | 0 | { |
1020 | 0 | ObjectPtr obj = ObjectPtr(Loader::loadClass(value).newInstance()); |
1021 | 0 | _priv->evaluator = LOG4CXX_NS::cast<TriggeringEventEvaluator>(obj); |
1022 | 0 | } |
1023 | | |
1024 | | int SMTPAppender::getBufferSize() const |
1025 | 0 | { |
1026 | 0 | return _priv->bufferSize; |
1027 | 0 | } |