smtp.cpp

来自「Shorthand是一个强大的脚本语言」· C++ 代码 · 共 409 行

CPP
409
字号
/////////////////////////////////////////////////////////////////////////////
// $Header: /shorthand/src/smtp.cpp 2     8/28/02 6:27a Arm $
//---------------------------------------------------------------------------
// This file is part of "libAndrix" library - a collection of classes
// and functions developed by Andrei Remenchuk.
//---------------------------------------------------------------------------
// While you may own complete copyright on the project with which you have
// received this file, the author reserves the right to use code contained
// in this very file for any purposes, including publishing and usage in
// any free or commercial software.
//
// You may re-distribute this file or re-use it in your own free or
// commercial software provided that this text is included in the file.
// If you change this file you must include clear notice stating that
// you changed this file and the date of change.
//
// This statement doesn't apply to other files that are part of the same
// package unless otherwise noted.
//---------------------------------------------------------------------------
// (c) 1998-2002 Andrei Remenchuk <andrei@remenchuk.com>
//---------------------------------------------------------------------------
// smtp.cpp - SMTP client classes and functions
/////////////////////////////////////////////////////////////////////////////
#ifdef WIN32
#include <winsock.h>
#define SOCK_ERROR WSAGetLastError()
#else
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h> 
#include <netdb.h>
#include <errno.h>
#define SOCK_ERROR errno
#endif

#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <malloc.h>
#include <limits.h>

#include "utils.h"
#include "smtp.h"
#include "except.h"

#pragma hdrstop


#if defined( DEBUG ) || defined( SMTP_TEST )
#define IFD(x) x
#else
#define IFD(x) 
#endif

#define debugf TRACE


CSMTPSession::CSMTPSession(const char* host, int port)
{
    disconnect();
    memset(this, 0, sizeof(CSMTPSession));
    this->host = strdup(host);
    this->port = port;
    socketTimeout = 30;
}

CSMTPSession::~CSMTPSession() 
{
#ifdef WIN32
    if (sock != 0) closesocket(sock);
    if (needWSACleanup) WSACleanup();
#else
    if (sock != 0) close(sock);
#endif
    if (host != NULL) free(host);
    if (errorText != NULL) free(errorText);
    if (localHostName != NULL) free(localHostName);
}


void CSMTPSession::setContentType(const char* contentType) 
{
    this->contentType = contentType;
}


const char* CSMTPSession::getErrorText() 
{ 
    return errorText != NULL ? errorText : ""; 
}


int CSMTPSession::connect()
{
#ifdef WIN32
    char buf[80];
    gethostname(buf, sizeof(buf)-1);
    if (WSAGetLastError() == WSANOTINITIALISED)
    {
        WSADATA wsa;
        memset(&wsa, 0, sizeof(WSADATA));
        if (WSAStartup(0x0101, &wsa) == SOCKET_ERROR)
            return wsError("WinSock initialization failed (error %d)", WSAGetLastError());
        needWSACleanup = true;
    }
    else
    {
        IFD(debugf("SMTP: WSA already initialized\n"));
    }
#endif

    struct hostent* hp;
    unsigned int ip; 

    if (isalpha(host[0])) {   /* server address is a name */ 
        hp = gethostbyname(host); 
    } 
    else  { /* Convert nnn.nnn address to a usable one */ 
        ip = inet_addr(host); 
        hp = gethostbyaddr((char *)&ip,4,AF_INET); 
    } 
    if (hp == NULL)
        return wsError("Cannot resolve address \"%s\" (error %d)", host, SOCK_ERROR);

    struct sockaddr_in address;
    memset(&address, 0, sizeof(address));
    memcpy(&(address.sin_addr), hp->h_addr, hp->h_length);
    address.sin_family = hp->h_addrtype;
    address.sin_port = htons(port);

    sock = ::socket(AF_INET, SOCK_STREAM, 0);
    if (sock == -1) 
        return wsError("couldn't create socket (error %d)", SOCK_ERROR);
    
    if (::connect(sock, (sockaddr*) &address, sizeof(address)) != 0)
        return wsError("Connection to %s:%d failed (error %d)", host, port, SOCK_ERROR);
    
    if (localHostName == NULL)
    {
        localHostName = (char*) malloc(80);
        if (gethostname(localHostName, sizeof(localHostName)-1) != 0)
            sprintf(localHostName, "localhost");
    }
    return status = 0;
}


int CSMTPSession::disconnect()
{
    if (sock != 0) 
    {
#ifdef WIN32
        closesocket(sock);
#else
	close(sock);
#endif
        sock = 0;
    }
    return 0;
}

/* 
   Wait for file descriptor FD to be readable, MAXTIME being the
   timeout in seconds.  If WRITEP is non-zero, checks for FD being
   writable instead.

   Returns 1 if FD is accessible, 0 for timeout and -1 for error in
   select(). 
*/
int select_fd (int fd, int maxtime, int writep)
{
    fd_set fds, exceptfds;
    struct timeval timeout;
    FD_ZERO (&fds);
    FD_SET (fd, &fds);
    FD_ZERO (&exceptfds);
    FD_SET (fd, &exceptfds);
    timeout.tv_sec = maxtime;
    timeout.tv_usec = 0;
    return ::select (fd + 1, writep ? NULL : &fds, writep ? &fds : NULL,
	        &exceptfds, &timeout);
}

void CSMTPSession::setError(const char* format, va_list args)
{
     int width = vawidth(format, &args)+1;
     if (width > errorCapacity) {
         errorCapacity = width;
         if (errorText == NULL) errorText = (char*) malloc(errorCapacity);
         else errorText = (char*) realloc(errorText, errorCapacity);
     }
     vsprintf(errorText, format, args);
     //dispatch_log_f(LOG_SMTP_CLIENT, 0, "SMTP: %s", errorText);
}


void CSMTPSession::setError(const char* format, ...)
{
     va_list args;
     va_start(args,format);
     setError(format, args);
}

int CSMTPSession::wsError(const char* format, ...)
{
     va_list args;
     va_start(args, format);
     setError(format, args);
     status = SOCK_ERROR;
     if (status == 0) status = -1;
     return status;
}


int CSMTPSession::post(const char* format, ...)
{
     va_list args;
     va_start(args,format);
     int width = vawidth(format, &args);
     char* data = (char*) alloca(width+1);
     vsprintf(data, format, args);

     int rc = select_fd(sock, socketTimeout, 1);
     if (rc == -1) 
         return wsError("select() failed (error %d)", SOCK_ERROR);
     else if (rc == 0) 
         return wsError("connection timed out");

     if (send(sock, data, strlen(data), 0) == -1)
         return wsError("send() failed (error %d)", SOCK_ERROR);

     flushInData();
     //dispatch_log_f(LOG_SMTP_CLIENT, 3, "SMTP:OUT> %s", data);
     return status = 0;
}


const char* CSMTPSession::getInData() { 
    return (inData != NULL) ? inData : ""; 
}


void CSMTPSession::ensureInCapacity(int n)
{
    if (inCapacity < n) 
    {
        inCapacity = n;
        if (inData == NULL) inData = (char*) malloc(inCapacity);
        else inData = (char*) realloc(inData, inCapacity);
    }
}

/**
 * Retrieves respose from SMTP server.
 * 
 * return value:
 *    non-zero SMTP response code if response was successfully receieved.
 *    zero if error occured or response is invalid.
 */
int CSMTPSession::consume()
{
    responseCode = 0;
    flushInData(); 
    int bufferSize = 1024;
    while(true)
    {
         int rc = select_fd(sock, socketTimeout, 0);
         if (rc == -1) {
             setError("select() failed (error %d)", SOCK_ERROR);
             return 0;
         } else if (rc == 0) {
             setError("connection timed out (error %d)", SOCK_ERROR);
             return 0;
         }

         ensureInCapacity(inLength + bufferSize + 1);
         int count = recv(sock, inData + inLength, bufferSize, 0);
         if (count > 0) 
         {
             inData[inLength+count] = '\0';
             char* lf = strchr(inData, '\n');
             inLength += count;
             if (lf != NULL) break;
         }
     }
     //dispatch_log_f(LOG_SMTP_CLIENT, 3, "SMTP:IN> %s", inData);

     char* endp = NULL;
     long code = strtol(inData, &endp, 10);
     if (code == 0 || code == LONG_MAX || code == LONG_MIN)
     {
         status = -1401;
         setError("Unrecognized response from SMTP server: %s\n", inData);
         return 0;

     }
     return responseCode = code;
}


void CSMTPSession::flushInData()
{
    inLength = 0;
    if (inData != NULL) *inData = '\0';
}

int CSMTPSession::rejectResponse()
{
    setError("Unrecognized response from SMTP server: %s", getInData());
    return status = -1402;
}

int CSMTPSession::smtpError()
{
    if (responseCode != 0) 
    {
        const char* e = getInData();
        //while(*e != '\0' && strchr("0123456789 ", *e) != NULL) e++;
        setError("SMTP Error: %s", e);
        return responseCode;
    }
    else
    {
        return status;
    }
}

int CSMTPSession::sendMessage(const char* from, const char* to, const char* subject, const char* body)
{
    int rc;
    rc = connect(); if (rc) return rc;

    IFD(debugf("smtp.connect(%s) succeeded\n", host));

    consume();
    if (responseCode != 220) return smtpError();

    post("HELO %s\r\n", localHostName);
    if (status) return status;
    consume();
    if (responseCode != 250) return smtpError();

    post("MAIL FROM: %s\r\n", from);
    if (status) return status;
    consume();
    if (responseCode != 250) return smtpError();

    post("RCPT TO: %s\r\n", to);
    if (status) return status;
    consume();
    if (responseCode != 250 && responseCode != 251) return smtpError();

    post("DATA\r\n");
    if (status) return status;
    consume();
    if (responseCode != 354) return smtpError();

    string agent = "ShortHand";
    //agent.printf("%s %d.%d.%d.%d", APP_TITLE, CFG().version(0), CFG().version(1), CFG().version(2), CFG().version(3));

    post("From: %s\r\nTo: %s\r\nX-Mailer: %s\r\nSubject: %s\r\n", from, to, agent.cstr(), subject);
    if (status) return status;

    if (contentType && *contentType)
    {
        post("Content-Type: %s\r\n", contentType);
        if (status) return status;
    }

    post("\r\n");
    if (status) return status;

    post("%s", body);
    if (status) return status;
    post("\r\n.\r\n");
    if (status) return status;
    
    consume();
    if (responseCode != 250) return smtpError();

    post("QUIT\r\n");
    consume();

    return 0;
}


#ifdef SMTP_TEST

int main(char** argv) 
{
    const char* host = "smtp-server.nyc.rr.com";
    const char* from = "smtptest@remenchuk.com";
    const char* to   = "remena@agsltd.com";
    int port = 25;

    CSMTPSession smtp(host, port);

    if (smtp.sendMessage(from, to, "Test", "This is a test\r\n\r\nBye") != 0)
    {
        printf("smtp failed:\n%s\n", smtp.getErrorText());
    }
    smtp.disconnect();
    return 0;
}

#endif // SMTP_TEST

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?