"Fossies" - the Fresh Open Source Software Archive

Member "netxms-3.8.166/src/server/core/email.cpp" (23 Feb 2021, 15410 Bytes) of package /linux/misc/netxms-3.8.166.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "email.cpp" see the Fossies "Dox" file reference documentation.

    1 /* 
    2 ** NetXMS - Network Management System
    3 ** Copyright (C) 2003-2019 Victor Kirhenshtein
    4 **
    5 ** This program is free software; you can redistribute it and/or modify
    6 ** it under the terms of the GNU General Public License as published by
    7 ** the Free Software Foundation; either version 2 of the License, or
    8 ** (at your option) any later version.
    9 **
   10 ** This program is distributed in the hope that it will be useful,
   11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
   12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   13 ** GNU General Public License for more details.
   14 **
   15 ** You should have received a copy of the GNU General Public License
   16 ** along with this program; if not, write to the Free Software
   17 ** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
   18 **
   19 ** File: email.cpp
   20 **
   21 **/
   22 
   23 #include "nxcore.h"
   24 
   25 /**
   26  * Receive buffer size
   27  */
   28 #define SMTP_BUFFER_SIZE            1024
   29 
   30 /**
   31  * Sender errors
   32  */
   33 #define SMTP_ERR_SUCCESS            0
   34 #define SMTP_ERR_BAD_SERVER_NAME    1
   35 #define SMTP_ERR_COMM_FAILURE       2
   36 #define SMTP_ERR_PROTOCOL_FAILURE   3
   37 
   38 /**
   39  * Mail sender states
   40  */
   41 #define STATE_INITIAL      0
   42 #define STATE_HELLO        1
   43 #define STATE_FROM         2
   44 #define STATE_RCPT         3
   45 #define STATE_DATA         4
   46 #define STATE_MAIL_BODY    5
   47 #define STATE_QUIT         6
   48 #define STATE_FINISHED     7
   49 #define STATE_ERROR        8
   50 
   51 /**
   52  * Mail envelope structure
   53  */
   54 struct MAIL_ENVELOPE
   55 {
   56    char rcptAddr[MAX_RCPT_ADDR_LEN];
   57    char subject[MAX_EMAIL_SUBJECT_LEN];
   58    char *text;
   59    char encoding[64];
   60    bool isHtml;
   61    bool isUtf8;
   62    int retryCount;
   63 };
   64 
   65 /**
   66  * Static data
   67  */
   68 static ObjectQueue<MAIL_ENVELOPE> s_mailerQueue(64, Ownership::False);
   69 static THREAD s_mailerThread = INVALID_THREAD_HANDLE;
   70 
   71 /**
   72  * Find end-of-line character
   73  */
   74 static char *FindEOL(char *pszBuffer, size_t len)
   75 {
   76    for(size_t i = 0; i < len; i++)
   77       if (pszBuffer[i] == '\n')
   78          return &pszBuffer[i];
   79    return nullptr;
   80 }
   81 
   82 /**
   83  * Read line from socket
   84  */
   85 static bool ReadLineFromSocket(SOCKET hSocket, char *pszBuffer, size_t *pnBufPos, char *pszLine)
   86 {
   87    char *ptr;
   88    do
   89    {
   90       ptr = FindEOL(pszBuffer, *pnBufPos);
   91       if (ptr == nullptr)
   92       {
   93          ssize_t bytes = RecvEx(hSocket, &pszBuffer[*pnBufPos], SMTP_BUFFER_SIZE - *pnBufPos, 0, 30000);
   94          if (bytes <= 0)
   95             return false;
   96          *pnBufPos += bytes;
   97       }
   98    } while(ptr == nullptr);
   99    *ptr = 0;
  100    strcpy(pszLine, pszBuffer);
  101    *pnBufPos -= (int)(ptr - pszBuffer + 1);
  102    memmove(pszBuffer, ptr + 1, *pnBufPos);
  103    return true;
  104 }
  105 
  106 /**
  107  * Read SMTP response code from socket
  108  */
  109 static int GetSMTPResponse(SOCKET hSocket, char *pszBuffer, size_t *pnBufPos)
  110 {
  111    char szLine[SMTP_BUFFER_SIZE];
  112 
  113    while(1)
  114    {
  115       if (!ReadLineFromSocket(hSocket, pszBuffer, pnBufPos, szLine))
  116          return -1;
  117       if (strlen(szLine) < 4)
  118          return -1;
  119       if (szLine[3] == ' ')
  120       {
  121          szLine[3] = 0;
  122          break;
  123       }
  124    }
  125    return atoi(szLine);
  126 }
  127 
  128 /**
  129  * Encode SMTP header
  130  */
  131 static char *EncodeHeader(const char *header, const char *encoding, const char *data, char *buffer, size_t bufferSize)
  132 {
  133    bool encode = false;
  134    for(const char *p = data; *p != 0; p++)
  135       if (*p & 0x80)
  136       {
  137          encode = true;
  138          break;
  139       }
  140    if (encode)
  141    {
  142       char *encodedData = NULL;
  143       base64_encode_alloc(data, strlen(data), &encodedData);
  144       if (encodedData != NULL)
  145       {
  146          if (header != NULL)
  147             snprintf(buffer, bufferSize, "%s: =?%s?B?%s?=\r\n", header, encoding, encodedData);
  148          else
  149             snprintf(buffer, bufferSize, "=?%s?B?%s?=", encoding, encodedData);
  150          free(encodedData);
  151       }
  152       else
  153       {
  154          // fallback
  155          if (header != NULL)
  156             snprintf(buffer, bufferSize, "%s: %s\r\n", header, data);
  157          else
  158             strlcpy(buffer, data, bufferSize);
  159       }
  160    }
  161    else
  162    {
  163       if (header != NULL)
  164          snprintf(buffer, bufferSize, "%s: %s\r\n", header, data);
  165       else
  166          strlcpy(buffer, data, bufferSize);
  167    }
  168    return buffer;
  169 }
  170 
  171 /**
  172  * Send e-mail
  173  */
  174 static UINT32 SendMail(const char *pszRcpt, const char *pszSubject, const char *pszText, const char *encoding, bool isHtml, bool isUtf8)
  175 {
  176    TCHAR smtpServer[256];
  177    char fromName[256], fromAddr[256], localHostName[256];
  178    ConfigReadStr(_T("SMTP.Server"), smtpServer, 256, _T("localhost"));
  179    ConfigReadStrA(_T("SMTP.FromAddr"), fromAddr, 256, "netxms@localhost");
  180    if (isUtf8)
  181    {
  182       ConfigReadStrUTF8(_T("SMTP.FromName"), fromName, 256, "NetXMS Server");
  183    }
  184    else
  185    {
  186       ConfigReadStrA(_T("SMTP.FromName"), fromName, 256, "NetXMS Server");
  187    }
  188    uint16_t smtpPort = static_cast<uint16_t>(ConfigReadInt(_T("SMTP.Port"), 25));
  189    ConfigReadStrA(_T("SMTP.LocalHostName"), localHostName, 256, "");
  190    if (localHostName[0] == 0)
  191    {
  192 #ifdef UNICODE
  193       WCHAR localHostNameW[256] = L"";
  194       GetLocalHostName(localHostNameW, 256, true);
  195       wchar_to_utf8(localHostNameW, -1, localHostName, 256);
  196 #else
  197       GetLocalHostName(localHostName, 256, true);
  198 #endif
  199       if (localHostName[0] == 0)
  200          strcpy(localHostName, "localhost");
  201    }
  202 
  203    // Resolve hostname
  204     InetAddress addr = InetAddress::resolveHostName(smtpServer);
  205    if (!addr.isValid() || addr.isBroadcast() || addr.isMulticast())
  206       return SMTP_ERR_BAD_SERVER_NAME;
  207 
  208    // Create socket and connect to server
  209    SOCKET hSocket = ConnectToHost(addr, smtpPort, 3000);
  210    if (hSocket == INVALID_SOCKET)
  211       return SMTP_ERR_COMM_FAILURE;
  212 
  213    char szBuffer[SMTP_BUFFER_SIZE];
  214    size_t nBufPos = 0;
  215    int iState = STATE_INITIAL;
  216    while((iState != STATE_FINISHED) && (iState != STATE_ERROR))
  217    {
  218       int iResp = GetSMTPResponse(hSocket, szBuffer, &nBufPos);
  219       DbgPrintf(8, _T("SMTP RESPONSE: %03d (state=%d)"), iResp, iState);
  220       if (iResp > 0)
  221       {
  222          switch(iState)
  223          {
  224             case STATE_INITIAL:
  225                // Server should send 220 text after connect
  226                if (iResp == 220)
  227                {
  228                   iState = STATE_HELLO;
  229                   char command[280];
  230                   snprintf(command, 280, "HELO %s\r\n", localHostName);
  231                   SendEx(hSocket, command, strlen(command), 0, nullptr);
  232                }
  233                else
  234                {
  235                   iState = STATE_ERROR;
  236                }
  237                break;
  238             case STATE_HELLO:
  239                // Server should respond with 250 text to our HELO command
  240                if (iResp == 250)
  241                {
  242                   iState = STATE_FROM;
  243                   snprintf(szBuffer, SMTP_BUFFER_SIZE, "MAIL FROM: <%s>\r\n", fromAddr);
  244                   SendEx(hSocket, szBuffer, strlen(szBuffer), 0, NULL);
  245                }
  246                else
  247                {
  248                   iState = STATE_ERROR;
  249                }
  250                break;
  251             case STATE_FROM:
  252                // Server should respond with 250 text to our MAIL FROM command
  253                if (iResp == 250)
  254                {
  255                   iState = STATE_RCPT;
  256                   snprintf(szBuffer, SMTP_BUFFER_SIZE, "RCPT TO: <%s>\r\n", pszRcpt);
  257                   SendEx(hSocket, szBuffer, strlen(szBuffer), 0, NULL);
  258                }
  259                else
  260                {
  261                   iState = STATE_ERROR;
  262                }
  263                break;
  264             case STATE_RCPT:
  265                // Server should respond with 250 text to our RCPT TO command
  266                if (iResp == 250)
  267                {
  268                   iState = STATE_DATA;
  269                   SendEx(hSocket, "DATA\r\n", 6, 0, NULL);
  270                }
  271                else
  272                {
  273                   iState = STATE_ERROR;
  274                }
  275                break;
  276             case STATE_DATA:
  277                // Server should respond with 354 text to our DATA command
  278                if (iResp == 354)
  279                {
  280                   iState = STATE_MAIL_BODY;
  281 
  282                   // Mail headers
  283                   // from
  284                   char from[512];
  285                   snprintf(szBuffer, SMTP_BUFFER_SIZE, "From: \"%s\" <%s>\r\n", EncodeHeader(NULL, encoding, fromName, from, 512), fromAddr);
  286                   SendEx(hSocket, szBuffer, strlen(szBuffer), 0, NULL);
  287                   // to
  288                   snprintf(szBuffer, SMTP_BUFFER_SIZE, "To: <%s>\r\n", pszRcpt);
  289                   SendEx(hSocket, szBuffer, strlen(szBuffer), 0, NULL);
  290                   // subject
  291                   EncodeHeader("Subject", encoding, pszSubject, szBuffer, SMTP_BUFFER_SIZE);
  292                   SendEx(hSocket, szBuffer, strlen(szBuffer), 0, NULL);
  293 
  294                   // date
  295                   time_t currentTime;
  296                   struct tm *pCurrentTM;
  297                   time(&currentTime);
  298 #ifdef HAVE_LOCALTIME_R
  299                   struct tm currentTM;
  300                   localtime_r(&currentTime, &currentTM);
  301                   pCurrentTM = &currentTM;
  302 #else
  303                   pCurrentTM = localtime(&currentTime);
  304 #endif
  305 #ifdef _WIN32
  306                   strftime(szBuffer, sizeof(szBuffer), "Date: %a, %d %b %Y %H:%M:%S ", pCurrentTM);
  307 
  308                   TIME_ZONE_INFORMATION tzi;
  309                   UINT32 tzType = GetTimeZoneInformation(&tzi);
  310                   LONG effectiveBias;
  311                   switch(tzType)
  312                   {
  313                      case TIME_ZONE_ID_STANDARD:
  314                         effectiveBias = tzi.Bias + tzi.StandardBias;
  315                         break;
  316                      case TIME_ZONE_ID_DAYLIGHT:
  317                         effectiveBias = tzi.Bias + tzi.DaylightBias;
  318                         break;
  319                      case TIME_ZONE_ID_UNKNOWN:
  320                         effectiveBias = tzi.Bias;
  321                         break;
  322                      default:       // error
  323                         effectiveBias = 0;
  324                         DbgPrintf(4, _T("GetTimeZoneInformation() call failed"));
  325                         break;
  326                   }
  327                   int offset = abs(effectiveBias);
  328                   sprintf(&szBuffer[strlen(szBuffer)], "%c%02d%02d\r\n", effectiveBias <= 0 ? '+' : '-', offset / 60, offset % 60);
  329 #else
  330                   strftime(szBuffer, sizeof(szBuffer), "Date: %a, %d %b %Y %H:%M:%S %z\r\n", pCurrentTM);
  331 #endif
  332 
  333                   SendEx(hSocket, szBuffer, strlen(szBuffer), 0, NULL);
  334                   // content-type
  335                   snprintf(szBuffer, SMTP_BUFFER_SIZE,
  336                                     "Content-Type: text/%s; charset=%s\r\n"
  337                                     "Content-Transfer-Encoding: 8bit\r\n\r\n", isHtml ? "html" : "plain", encoding);
  338                   SendEx(hSocket, szBuffer, strlen(szBuffer), 0, NULL);
  339 
  340                   // Mail body
  341                   SendEx(hSocket, pszText, strlen(pszText), 0, NULL);
  342                   SendEx(hSocket, "\r\n.\r\n", 5, 0, NULL);
  343                }
  344                else
  345                {
  346                   iState = STATE_ERROR;
  347                }
  348                break;
  349             case STATE_MAIL_BODY:
  350                // Server should respond with 250 to our mail body
  351                if (iResp == 250)
  352                {
  353                   iState = STATE_QUIT;
  354                   SendEx(hSocket, "QUIT\r\n", 6, 0, NULL);
  355                }
  356                else
  357                {
  358                   iState = STATE_ERROR;
  359                }
  360                break;
  361             case STATE_QUIT:
  362                // Server should respond with 221 text to our QUIT command
  363                if (iResp == 221)
  364                {
  365                   iState = STATE_FINISHED;
  366                }
  367                else
  368                {
  369                   iState = STATE_ERROR;
  370                }
  371                break;
  372             default:
  373                iState = STATE_ERROR;
  374                break;
  375          }
  376       }
  377       else
  378       {
  379          iState = STATE_ERROR;
  380       }
  381    }
  382 
  383    // Shutdown communication channel
  384    shutdown(hSocket, SHUT_RDWR);
  385    closesocket(hSocket);
  386 
  387    return (iState == STATE_FINISHED) ? SMTP_ERR_SUCCESS : SMTP_ERR_PROTOCOL_FAILURE;
  388 }
  389 
  390 /**
  391  * Mailer thread
  392  */
  393 static THREAD_RESULT THREAD_CALL MailerThread(void *pArg)
  394 {
  395    static const TCHAR *m_szErrorText[] =
  396    {
  397       _T("Sent successfully"),
  398       _T("Unable to resolve SMTP server name"),
  399       _T("Communication failure"),
  400       _T("SMTP conversation failure")
  401    };
  402 
  403    ThreadSetName("Mailer");
  404     DbgPrintf(1, _T("SMTP mailer thread started"));
  405    while(1)
  406    {
  407       MAIL_ENVELOPE *pEnvelope = s_mailerQueue.getOrBlock();
  408       if (pEnvelope == INVALID_POINTER_VALUE)
  409          break;
  410 
  411         nxlog_debug(6, _T("SMTP(%p): new envelope, rcpt=%hs"), pEnvelope, pEnvelope->rcptAddr);
  412 
  413       UINT32 dwResult = SendMail(pEnvelope->rcptAddr, pEnvelope->subject, pEnvelope->text, pEnvelope->encoding, pEnvelope->isHtml, pEnvelope->isUtf8);
  414       if (dwResult != SMTP_ERR_SUCCESS)
  415         {
  416             pEnvelope->retryCount--;
  417             DbgPrintf(6, _T("SMTP(%p): Failed to send e-mail, remaining retries: %d"), pEnvelope, pEnvelope->retryCount);
  418             if (pEnvelope->retryCount > 0)
  419             {
  420                 // Try posting again
  421                s_mailerQueue.put(pEnvelope);
  422             }
  423             else
  424             {
  425                 PostSystemEvent(EVENT_SMTP_FAILURE, g_dwMgmtNode, "dsmm", dwResult,
  426                              m_szErrorText[dwResult], pEnvelope->rcptAddr, pEnvelope->subject);
  427                 MemFree(pEnvelope->text);
  428                 MemFree(pEnvelope);
  429             }
  430         }
  431         else
  432         {
  433             DbgPrintf(6, _T("SMTP(%p): mail sent successfully"), pEnvelope);
  434             MemFree(pEnvelope->text);
  435             MemFree(pEnvelope);
  436         }
  437    }
  438    return THREAD_OK;
  439 }
  440 
  441 /**
  442  * Initialize mailer subsystem
  443  */
  444 void InitMailer()
  445 {
  446    s_mailerThread = ThreadCreateEx(MailerThread, 0, NULL);
  447 }
  448 
  449 /**
  450  * Shutdown mailer
  451  */
  452 void ShutdownMailer()
  453 {
  454    s_mailerQueue.clear();
  455    s_mailerQueue.put(INVALID_POINTER_VALUE);
  456    ThreadJoin(s_mailerThread);
  457 }
  458 
  459 /**
  460  * Post e-mail to queue
  461  */
  462 void NXCORE_EXPORTABLE PostMail(const TCHAR *pszRcpt, const TCHAR *pszSubject, const TCHAR *pszText, bool isHtml)
  463 {
  464    auto envelope = MemAllocStruct<MAIL_ENVELOPE>();
  465    ConfigReadStrA(_T("MailEncoding"), envelope->encoding, 64, "utf8");
  466    envelope->isUtf8 = isHtml || !stricmp(envelope->encoding, "utf-8") || !stricmp(envelope->encoding, "utf8");
  467 
  468 #ifdef UNICODE
  469     WideCharToMultiByte(envelope->isUtf8 ? CP_UTF8 : CP_ACP, envelope->isUtf8 ? 0 : WC_DEFAULTCHAR | WC_COMPOSITECHECK, pszRcpt, -1, envelope->rcptAddr, MAX_RCPT_ADDR_LEN, NULL, NULL);
  470     envelope->rcptAddr[MAX_RCPT_ADDR_LEN - 1] = 0;
  471     WideCharToMultiByte(envelope->isUtf8 ? CP_UTF8 : CP_ACP, envelope->isUtf8 ? 0 : WC_DEFAULTCHAR | WC_COMPOSITECHECK, pszSubject, -1, envelope->subject, MAX_EMAIL_SUBJECT_LEN, NULL, NULL);
  472     envelope->subject[MAX_EMAIL_SUBJECT_LEN - 1] = 0;
  473     envelope->text = envelope->isUtf8 ? UTF8StringFromWideString(pszText) : MBStringFromWideString(pszText);
  474 #else
  475     if (envelope->isUtf8)
  476     {
  477        mb_to_utf8(pszRcpt, -1, envelope->rcptAddr, MAX_RCPT_ADDR_LEN);
  478        envelope->rcptAddr[MAX_RCPT_ADDR_LEN - 1] = 0;
  479       mb_to_utf8(pszSubject, -1, envelope->subject, MAX_EMAIL_SUBJECT_LEN);
  480       envelope->subject[MAX_EMAIL_SUBJECT_LEN - 1] = 0;
  481        envelope->text = UTF8StringFromMBString(pszText);
  482     }
  483     else
  484     {
  485       nx_strncpy(envelope->rcptAddr, pszRcpt, MAX_RCPT_ADDR_LEN);
  486       nx_strncpy(envelope->subject, pszSubject, MAX_EMAIL_SUBJECT_LEN);
  487       envelope->text = strdup(pszText);
  488     }
  489 #endif
  490     envelope->retryCount = ConfigReadInt(_T("SMTP.RetryCount"), 1);
  491     envelope->isHtml = isHtml;
  492     s_mailerQueue.put(envelope);
  493 }