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/helpers/stringhelper.h>
23 #include <log4cxx/helpers/stringtokenizer.h>
24 #include <log4cxx/helpers/transcoder.h>
25 #include <log4cxx/helpers/synchronized.h>
26 #if !defined(LOG4CXX)
27 #define LOG4CXX 1
28 #endif
29 #include <log4cxx/private/log4cxx_private.h>
30
31
32
33 #include <apr_strings.h>
34 #include <vector>
35
36 using namespace log4cxx;
37 using namespace log4cxx::helpers;
38 using namespace log4cxx::net;
39 using namespace log4cxx::spi;
40
41 #if LOG4CXX_HAVE_LIBESMTP
42 #include <auth-client.h>
43 #include <libesmtp.h>
44 #endif
45
46 namespace log4cxx
47 {
48 namespace net
49 {
50 //
51 // The following two classes implement an C++ SMTP wrapper over libesmtp.
52 // The same signatures could be implemented over different SMTP implementations
53 // or libesmtp could be combined with libgmime to enable support for non-ASCII
54 // content.
55
56 #if LOG4CXX_HAVE_LIBESMTP
57 /**
58 * SMTP Session.
59 */
60 class SMTPSession
61 {
62 public:
63 /**
64 * Create new instance.
65 */
SMTPSession(const LogString & smtpHost,int smtpPort,const LogString & smtpUsername,const LogString & smtpPassword,Pool & p)66 SMTPSession(const LogString& smtpHost,
67 int smtpPort,
68 const LogString& smtpUsername,
69 const LogString& smtpPassword,
70 Pool& p) : session(0), authctx(0),
71 user(toAscii(smtpUsername, p)),
72 pwd(toAscii(smtpPassword, p))
73 {
74 auth_client_init();
75 session = smtp_create_session();
76
77 if (session == 0)
78 {
79 throw Exception("Could not initialize session.");
80 }
81
82 std::string host(toAscii(smtpHost, p));
83 host.append(1, ':');
84 host.append(p.itoa(smtpPort));
85 smtp_set_server(session, host.c_str());
86
87 authctx = auth_create_context();
88 auth_set_mechanism_flags(authctx, AUTH_PLUGIN_PLAIN, 0);
89 auth_set_interact_cb(authctx, authinteract, (void*) this);
90
91 if (*user || *pwd)
92 {
93 smtp_auth_set_context(session, authctx);
94 }
95 }
96
~SMTPSession()97 ~SMTPSession()
98 {
99 smtp_destroy_session(session);
100 auth_destroy_context(authctx);
101 }
102
send(Pool & p)103 void send(Pool& p)
104 {
105 int status = smtp_start_session(session);
106
107 if (!status)
108 {
109 size_t bufSize = 128;
110 char* buf = p.pstralloc(bufSize);
111 smtp_strerror(smtp_errno(), buf, bufSize);
112 throw Exception(buf);
113 }
114 }
115
operator smtp_session_t()116 operator smtp_session_t()
117 {
118 return session;
119 }
120
toAscii(const LogString & str,Pool & p)121 static char* toAscii(const LogString& str, Pool& p)
122 {
123 char* buf = p.pstralloc(str.length() + 1);
124 char* current = buf;
125
126 for (LogString::const_iterator iter = str.begin();
127 iter != str.end();
128 iter++)
129 {
130 unsigned int c = *iter;
131
132 if (c > 0x7F)
133 {
134 c = '?';
135 }
136
137 *current++ = c;
138 }
139
140 *current = 0;
141 return buf;
142 }
143
144 private:
145 SMTPSession(SMTPSession&);
146 SMTPSession& operator=(SMTPSession&);
147 smtp_session_t session;
148 auth_context_t authctx;
149 char* user;
150 char* pwd;
151
152 /**
153 * This method is called if the SMTP server requests authentication.
154 */
authinteract(auth_client_request_t request,char ** result,int fields,void * arg)155 static int authinteract(auth_client_request_t request, char** result, int fields,
156 void* arg)
157 {
158 SMTPSession* pThis = (SMTPSession*) arg;
159
160 for (int i = 0; i < fields; i++)
161 {
162 int flag = request[i].flags & 0x07;
163
164 if (flag == AUTH_USER)
165 {
166 result[i] = pThis->user;
167 }
168 else if (flag == AUTH_PASS)
169 {
170 result[i] = pThis->pwd;
171 }
172 }
173
174 return 1;
175 }
176
177
178 };
179
180 /**
181 * A message in an SMTP session.
182 */
183 class SMTPMessage
184 {
185 public:
SMTPMessage(SMTPSession & session,const LogString & from,const LogString & to,const LogString & cc,const LogString & bcc,const LogString & subject,const LogString msg,Pool & p)186 SMTPMessage(SMTPSession& session,
187 const LogString& from,
188 const LogString& to,
189 const LogString& cc,
190 const LogString& bcc,
191 const LogString& subject,
192 const LogString msg, Pool& p)
193 {
194 message = smtp_add_message(session);
195 body = current = toMessage(msg, p);
196 messagecbState = 0;
197 smtp_set_reverse_path(message, toAscii(from, p));
198 addRecipients(to, "To", p);
199 addRecipients(cc, "Cc", p);
200 addRecipients(bcc, "Bcc", p);
201
202 if (!subject.empty())
203 {
204 smtp_set_header(message, "Subject", toAscii(subject, p));
205 }
206
207 smtp_set_messagecb(message, messagecb, this);
208 }
~SMTPMessage()209 ~SMTPMessage()
210 {
211 }
212
213 private:
214 SMTPMessage(const SMTPMessage&);
215 SMTPMessage& operator=(const SMTPMessage&);
216 smtp_message_t message;
217 const char* body;
218 const char* current;
219 int messagecbState;
addRecipients(const LogString & addresses,const char * field,Pool & p)220 void addRecipients(const LogString& addresses, const char* field, Pool& p)
221 {
222 if (!addresses.empty())
223 {
224 char* str = p.pstrdup(toAscii(addresses, p));;
225 smtp_set_header(message, field, NULL, str);
226 char* last;
227
228 for (char* next = apr_strtok(str, ",", &last);
229 next;
230 next = apr_strtok(NULL, ",", &last))
231 {
232 smtp_add_recipient(message, next);
233 }
234 }
235 }
toAscii(const LogString & str,Pool & p)236 static const char* toAscii(const LogString& str, Pool& p)
237 {
238 return SMTPSession::toAscii(str, p);
239 }
240
241 /**
242 * Message bodies can only contain US-ASCII characters and
243 * CR and LFs can only occur together.
244 */
toMessage(const LogString & str,Pool & p)245 static const char* toMessage(const LogString& str, Pool& p)
246 {
247 //
248 // count the number of carriage returns and line feeds
249 //
250 int feedCount = 0;
251
252 for (size_t pos = str.find_first_of(LOG4CXX_STR("\n\r"));
253 pos != LogString::npos;
254 pos = str.find_first_of(LOG4CXX_STR("\n\r"), ++pos))
255 {
256 feedCount++;
257 }
258
259 //
260 // allocate sufficient space for the modified message
261 char* retval = p.pstralloc(str.length() + feedCount + 1);
262 char* current = retval;
263 char* startOfLine = current;
264
265 //
266 // iterator through message
267 //
268 for (LogString::const_iterator iter = str.begin();
269 iter != str.end();
270 iter++)
271 {
272 unsigned int c = *iter;
273
274 //
275 // replace non-ASCII characters with '?'
276 //
277 if (c > 0x7F)
278 {
279 *current++ = 0x3F; // '?'
280 }
281 else if (c == 0x0A || c == 0x0D)
282 {
283 //
284 // replace any stray CR or LF with CRLF
285 // reset start of line
286 *current++ = 0x0D;
287 *current++ = 0x0A;
288 startOfLine = current;
289 LogString::const_iterator next = iter + 1;
290
291 if (next != str.end() && (*next == 0x0A || *next == 0x0D))
292 {
293 iter++;
294 }
295 }
296 else
297 {
298 //
299 // truncate any lines to 1000 characters (including CRLF)
300 // as required by RFC.
301 if (current < startOfLine + 998)
302 {
303 *current++ = (char) c;
304 }
305 }
306 }
307
308 *current = 0;
309 return retval;
310 }
311
312 /**
313 * Callback for message.
314 */
messagecb(void ** ctx,int * len,void * arg)315 static const char* messagecb(void** ctx, int* len, void* arg)
316 {
317 *ctx = 0;
318 const char* retval = 0;
319 SMTPMessage* pThis = (SMTPMessage*) arg;
320
321 // rewind message
322 if (len == NULL)
323 {
324 pThis->current = pThis->body;
325 }
326 else
327 {
328 // we are asked for headers, but we don't have any
329 if ((pThis->messagecbState)++ == 0)
330 {
331 return NULL;
332 }
333
334 if (pThis->current)
335 {
336 *len = strlen(pThis->current);
337 }
338
339 retval = pThis->current;
340 pThis->current = 0;
341 }
342
343 return retval;
344 }
345
346 };
347 #endif
348
349 class LOG4CXX_EXPORT DefaultEvaluator :
350 public virtual spi::TriggeringEventEvaluator,
351 public virtual helpers::ObjectImpl
352 {
353 public:
354 DECLARE_LOG4CXX_OBJECT(DefaultEvaluator)
355 BEGIN_LOG4CXX_CAST_MAP()
356 LOG4CXX_CAST_ENTRY(DefaultEvaluator)
357 LOG4CXX_CAST_ENTRY(spi::TriggeringEventEvaluator)
358 END_LOG4CXX_CAST_MAP()
359
360 DefaultEvaluator();
361
362 /**
363 Is this <code>event</code> the e-mail triggering event?
364 <p>This method returns <code>true</code>, if the event level
365 has ERROR level or higher. Otherwise it returns
366 <code>false</code>.
367 */
368 virtual bool isTriggeringEvent(const spi::LoggingEventPtr& event);
369 private:
370 DefaultEvaluator(const DefaultEvaluator&);
371 DefaultEvaluator& operator=(const DefaultEvaluator&);
372 }; // class DefaultEvaluator
373
374 }
375 }
376
377 IMPLEMENT_LOG4CXX_OBJECT(DefaultEvaluator)
IMPLEMENT_LOG4CXX_OBJECT(SMTPAppender)378 IMPLEMENT_LOG4CXX_OBJECT(SMTPAppender)
379
380 DefaultEvaluator::DefaultEvaluator()
381 {
382 }
383
isTriggeringEvent(const spi::LoggingEventPtr & event)384 bool DefaultEvaluator::isTriggeringEvent(const spi::LoggingEventPtr& event)
385 {
386 return event->getLevel()->isGreaterOrEqual(Level::getError());
387 }
388
SMTPAppender()389 SMTPAppender::SMTPAppender()
390 : smtpPort(25), bufferSize(512), locationInfo(false), cb(bufferSize),
391 evaluator(new DefaultEvaluator())
392 {
393 }
394
395 /**
396 Use <code>evaluator</code> passed as parameter as the
397 TriggeringEventEvaluator for this SMTPAppender. */
SMTPAppender(spi::TriggeringEventEvaluatorPtr evaluator)398 SMTPAppender::SMTPAppender(spi::TriggeringEventEvaluatorPtr evaluator)
399 : smtpPort(25), bufferSize(512), locationInfo(false), cb(bufferSize),
400 evaluator(evaluator)
401 {
402 }
403
~SMTPAppender()404 SMTPAppender::~SMTPAppender()
405 {
406 finalize();
407 }
408
requiresLayout() const409 bool SMTPAppender::requiresLayout() const
410 {
411 return true;
412 }
413
414
getFrom() const415 LogString SMTPAppender::getFrom() const
416 {
417 return from;
418 }
419
setFrom(const LogString & newVal)420 void SMTPAppender::setFrom(const LogString& newVal)
421 {
422 from = newVal;
423 }
424
425
getSubject() const426 LogString SMTPAppender::getSubject() const
427 {
428 return subject;
429 }
430
setSubject(const LogString & newVal)431 void SMTPAppender::setSubject(const LogString& newVal)
432 {
433 subject = newVal;
434 }
435
getSMTPHost() const436 LogString SMTPAppender::getSMTPHost() const
437 {
438 return smtpHost;
439 }
440
setSMTPHost(const LogString & newVal)441 void SMTPAppender::setSMTPHost(const LogString& newVal)
442 {
443 smtpHost = newVal;
444 }
445
getSMTPPort() const446 int SMTPAppender::getSMTPPort() const
447 {
448 return smtpPort;
449 }
450
setSMTPPort(int newVal)451 void SMTPAppender::setSMTPPort(int newVal)
452 {
453 smtpPort = newVal;
454 }
455
getLocationInfo() const456 bool SMTPAppender::getLocationInfo() const
457 {
458 return locationInfo;
459 }
460
setLocationInfo(bool newVal)461 void SMTPAppender::setLocationInfo(bool newVal)
462 {
463 locationInfo = newVal;
464 }
465
getSMTPUsername() const466 LogString SMTPAppender::getSMTPUsername() const
467 {
468 return smtpUsername;
469 }
470
setSMTPUsername(const LogString & newVal)471 void SMTPAppender::setSMTPUsername(const LogString& newVal)
472 {
473 smtpUsername = newVal;
474 }
475
getSMTPPassword() const476 LogString SMTPAppender::getSMTPPassword() const
477 {
478 return smtpPassword;
479 }
480
setSMTPPassword(const LogString & newVal)481 void SMTPAppender::setSMTPPassword(const LogString& newVal)
482 {
483 smtpPassword = newVal;
484 }
485
486
487
488
489
setOption(const LogString & option,const LogString & value)490 void SMTPAppender::setOption(const LogString& option,
491 const LogString& value)
492 {
493 if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("BUFFERSIZE"), LOG4CXX_STR("buffersize")))
494 {
495 setBufferSize(OptionConverter::toInt(value, 512));
496 }
497 else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("EVALUATORCLASS"), LOG4CXX_STR("evaluatorclass")))
498 {
499 setEvaluatorClass(value);
500 }
501 else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("FROM"), LOG4CXX_STR("from")))
502 {
503 setFrom(value);
504 }
505 else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("SMTPHOST"), LOG4CXX_STR("smtphost")))
506 {
507 setSMTPHost(value);
508 }
509 else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("SMTPUSERNAME"), LOG4CXX_STR("smtpusername")))
510 {
511 setSMTPUsername(value);
512 }
513 else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("SMTPPASSWORD"), LOG4CXX_STR("smtppassword")))
514 {
515 setSMTPPassword(value);
516 }
517 else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("SUBJECT"), LOG4CXX_STR("subject")))
518 {
519 setSubject(value);
520 }
521 else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("TO"), LOG4CXX_STR("to")))
522 {
523 setTo(value);
524 }
525 else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("CC"), LOG4CXX_STR("cc")))
526 {
527 setCc(value);
528 }
529 else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("BCC"), LOG4CXX_STR("bcc")))
530 {
531 setBcc(value);
532 }
533 else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("SMTPPORT"), LOG4CXX_STR("smtpport")))
534 {
535 setSMTPPort(OptionConverter::toInt(value, 25));
536 }
537 else
538 {
539 AppenderSkeleton::setOption(option, value);
540 }
541 }
542
543
asciiCheck(const LogString & value,const LogString & field)544 bool SMTPAppender::asciiCheck(const LogString& value, const LogString& field)
545 {
546 for (LogString::const_iterator iter = value.begin();
547 iter != value.end();
548 iter++)
549 {
550 if (0x7F < (unsigned int) *iter)
551 {
552 LogLog::warn(field + LOG4CXX_STR(" contains non-ASCII character"));
553 return false;
554 }
555 }
556
557 return true;
558 }
559
560 /**
561 Activate the specified options, such as the smtp host, the
562 recipient, from, etc. */
activateOptions(Pool & p)563 void SMTPAppender::activateOptions(Pool& p)
564 {
565 bool activate = true;
566
567 if (layout == 0)
568 {
569 errorHandler->error(LOG4CXX_STR("No layout set for appender named [") + name + LOG4CXX_STR("]."));
570 activate = false;
571 }
572
573 if (evaluator == 0)
574 {
575 errorHandler->error(LOG4CXX_STR("No TriggeringEventEvaluator is set for appender [") +
576 name + LOG4CXX_STR("]."));
577 activate = false;
578 }
579
580 if (smtpHost.empty())
581 {
582 errorHandler->error(LOG4CXX_STR("No smtpHost is set for appender [") +
583 name + LOG4CXX_STR("]."));
584 activate = false;
585 }
586
587 if (to.empty() && cc.empty() && bcc.empty())
588 {
589 errorHandler->error(LOG4CXX_STR("No recipient address is set for appender [") +
590 name + LOG4CXX_STR("]."));
591 activate = false;
592 }
593
594 activate &= asciiCheck(to, LOG4CXX_STR("to"));
595 activate &= asciiCheck(cc, LOG4CXX_STR("cc"));
596 activate &= asciiCheck(bcc, LOG4CXX_STR("bcc"));
597 activate &= asciiCheck(from, LOG4CXX_STR("from"));
598
599 #if !LOG4CXX_HAVE_LIBESMTP
600 errorHandler->error(LOG4CXX_STR("log4cxx built without SMTP support."));
601 activate = false;
602 #endif
603
604 if (activate)
605 {
606 AppenderSkeleton::activateOptions(p);
607 }
608 }
609
610 /**
611 Perform SMTPAppender specific appending actions, mainly adding
612 the event to a cyclic buffer and checking if the event triggers
613 an e-mail to be sent. */
append(const spi::LoggingEventPtr & event,Pool & p)614 void SMTPAppender::append(const spi::LoggingEventPtr& event, Pool& p)
615 {
616 if (!checkEntryConditions())
617 {
618 return;
619 }
620
621 LogString ndc;
622 event->getNDC(ndc);
623 event->getThreadName();
624 // Get a copy of this thread's MDC.
625 event->getMDCCopy();
626
627 cb.add(event);
628
629 if (evaluator->isTriggeringEvent(event))
630 {
631 sendBuffer(p);
632 }
633 }
634
635 /**
636 This method determines if there is a sense in attempting to append.
637 <p>It checks whether there is a set output target and also if
638 there is a set layout. If these checks fail, then the boolean
639 value <code>false</code> is returned. */
checkEntryConditions()640 bool SMTPAppender::checkEntryConditions()
641 {
642 #if LOG4CXX_HAVE_LIBESMTP
643
644 if ((to.empty() && cc.empty() && bcc.empty()) || from.empty() || smtpHost.empty())
645 {
646 errorHandler->error(LOG4CXX_STR("Message not configured."));
647 return false;
648 }
649
650 if (evaluator == 0)
651 {
652 errorHandler->error(LOG4CXX_STR("No TriggeringEventEvaluator is set for appender [") +
653 name + LOG4CXX_STR("]."));
654 return false;
655 }
656
657
658 if (layout == 0)
659 {
660 errorHandler->error(LOG4CXX_STR("No layout set for appender named [") + name + LOG4CXX_STR("]."));
661 return false;
662 }
663
664 return true;
665 #else
666 return false;
667 #endif
668 }
669
670
671
close()672 void SMTPAppender::close()
673 {
674 this->closed = true;
675 }
676
getTo() const677 LogString SMTPAppender::getTo() const
678 {
679 return to;
680 }
681
setTo(const LogString & addressStr)682 void SMTPAppender::setTo(const LogString& addressStr)
683 {
684 to = addressStr;
685 }
686
getCc() const687 LogString SMTPAppender::getCc() const
688 {
689 return cc;
690 }
691
setCc(const LogString & addressStr)692 void SMTPAppender::setCc(const LogString& addressStr)
693 {
694 cc = addressStr;
695 }
696
getBcc() const697 LogString SMTPAppender::getBcc() const
698 {
699 return bcc;
700 }
701
setBcc(const LogString & addressStr)702 void SMTPAppender::setBcc(const LogString& addressStr)
703 {
704 bcc = addressStr;
705 }
706
707 /**
708 Send the contents of the cyclic buffer as an e-mail message.
709 */
sendBuffer(Pool & p)710 void SMTPAppender::sendBuffer(Pool& p)
711 {
712 #if LOG4CXX_HAVE_LIBESMTP
713
714 // Note: this code already owns the monitor for this
715 // appender. This frees us from needing to synchronize on 'cb'.
716 try
717 {
718 LogString sbuf;
719 layout->appendHeader(sbuf, p);
720
721 int len = cb.length();
722
723 for (int i = 0; i < len; i++)
724 {
725 LoggingEventPtr event = cb.get();
726 layout->format(sbuf, event, p);
727 }
728
729 layout->appendFooter(sbuf, p);
730
731 SMTPSession session(smtpHost, smtpPort, smtpUsername, smtpPassword, p);
732
733 SMTPMessage message(session, from, to, cc,
734 bcc, subject, sbuf, p);
735
736 session.send(p);
737
738 }
739 catch (std::exception& e)
740 {
741 LogLog::error(LOG4CXX_STR("Error occured while sending e-mail notification."), e);
742 }
743
744 #endif
745 }
746
747 /**
748 Returns value of the <b>EvaluatorClass</b> option.
749 */
getEvaluatorClass()750 LogString SMTPAppender::getEvaluatorClass()
751 {
752 return evaluator == 0 ? LogString() : evaluator->getClass().getName();
753 }
754
getEvaluator() const755 log4cxx::spi::TriggeringEventEvaluatorPtr SMTPAppender::getEvaluator() const
756 {
757 return evaluator;
758 }
759
setEvaluator(log4cxx::spi::TriggeringEventEvaluatorPtr & trigger)760 void SMTPAppender::setEvaluator(log4cxx::spi::TriggeringEventEvaluatorPtr& trigger)
761 {
762 evaluator = trigger;
763 }
764
765 /**
766 The <b>BufferSize</b> option takes a positive integer
767 representing the maximum number of logging events to collect in a
768 cyclic buffer. When the <code>BufferSize</code> is reached,
769 oldest events are deleted as new events are added to the
770 buffer. By default the size of the cyclic buffer is 512 events.
771 */
setBufferSize(int sz)772 void SMTPAppender::setBufferSize(int sz)
773 {
774 this->bufferSize = sz;
775 cb.resize(sz);
776 }
777
778 /**
779 The <b>EvaluatorClass</b> option takes a string value
780 representing the name of the class implementing the {@link
781 TriggeringEventEvaluator} interface. A corresponding object will
782 be instantiated and assigned as the triggering event evaluator
783 for the SMTPAppender.
784 */
setEvaluatorClass(const LogString & value)785 void SMTPAppender::setEvaluatorClass(const LogString& value)
786 {
787 evaluator = OptionConverter::instantiateByClassName(value,
788 TriggeringEventEvaluator::getStaticClass(), evaluator);
789 }
790
791