/* * PHP Sendmail for Windows. * * This file is rewriten specificly for PHPFI. Some functionality * has been removed (MIME and file attachments). This code was * modified from code based on code writen by Jarle Aase. * * This class is based on the original code by Jarle Aase, see bellow: * wSendmail.cpp It has been striped of some functionality to match * the requirements of phpfi. * * Very simple SMTP Send-mail program for sending command-line level * emails and CGI-BIN form response for the Windows platform. * * The complete wSendmail package with source code can be located * from http://www.jgaa.com * */ #include "php.h" /*php specific */ #include #include #include #include "time.h" #include #include #include #include #include "sendmail.h" #include "php_ini.h" /* extern int _daylight; extern long _timezone; */ /*enum { DO_CONNECT = WM_USER +1 }; */ static char *days[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; static char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; #ifndef THREAD_SAFE char Buffer[MAIL_BUFFER_SIZE]; /* socket related data */ SOCKET sc; WSADATA Data; struct hostent *adr; SOCKADDR_IN sock_in; int WinsockStarted; /* values set by the constructor */ char *AppName; char MailHost[HOST_NAME_LEN]; char LocalHost[HOST_NAME_LEN]; #endif char seps[] = " ,\t\n"; char *php_mailer = "PHP 4.0 WIN32"; char *get_header(char *h, char *headers); /* Error messages */ static char *ErrorMessages[] = { {"Success"}, {"Bad arguments from form"}, {"Unable to open temporary mailfile for read"}, {"Failed to Start Sockets"}, {"Failed to Resolve Host"}, {"Failed to obtain socket handle"}, {"Failed to Connect"}, {"Failed to Send"}, {"Failed to Receive"}, {"Server Error"}, {"Failed to resolve the host IP name"}, {"Out of memory"}, {"Unknown error"}, {"Bad Message Contents"}, {"Bad Message Subject"}, {"Bad Message destination"}, {"Bad Message Return Path"}, {"Bad Mail Host"}, {"Bad Message File"}, {"PHP Internal error: php.ini sendmail from variable not set!"} }; /********************************************************************* // Name: TSendMail // Input: 1) host: Name of the mail host where the SMTP server resides // max accepted length of name = 256 // 2) appname: Name of the application to use in the X-mailer // field of the message. if NULL is given the application // name is used as given by the GetCommandLine() function // max accespted length of name = 100 // Output: 1) error: Returns the error code if something went wrong or // SUCCESS otherwise. // // See SendText() for additional args! //********************************************************************/ int TSendMail(char *host, int *error, char *headers, char *Subject, char *mailTo, char *data) { int ret; char *RPath = NULL; WinsockStarted = FALSE; if (host == NULL) { *error = BAD_MAIL_HOST; return BAD_MAIL_HOST; } else if (strlen(host) >= HOST_NAME_LEN) { *error = BAD_MAIL_HOST; return BAD_MAIL_HOST; } else { strcpy(MailHost, host); } if (INI_STR("sendmail_from")){ RPath = estrdup(INI_STR("sendmail_from")); } else { return 19; } /* attempt to connect with mail host */ *error = MailConnect(); if (*error != 0) { if(RPath)efree(RPath); return *error; } else { ret = SendText(RPath, Subject, mailTo, data, headers); TSMClose(); if (ret != SUCCESS) { *error = ret; } if(RPath)efree(RPath); return ret; } } //******************************************************************** // Name: TSendMail::~TSendMail // Input: // Output: // Description: DESTRUCTOR // Author/Date: jcar 20/9/96 // History: //********************************************************************/ void TSMClose() { Post("QUIT\r\n"); Ack(); /* to guarantee that the cleanup is not made twice and compomise the rest of the application if sockets are used elesewhere */ shutdown(sc, 0); closesocket(sc); } /********************************************************************* // Name: char *GetSMErrorText // Input: Error index returned by the menber functions // Output: pointer to a string containing the error description // Description: // Author/Date: jcar 20/9/96 // History: //*******************************************************************/ char *GetSMErrorText(int index) { if ((index > MAX_ERROR_INDEX) || (index < MIN_ERROR_INDEX)) return (ErrorMessages[UNKNOWN_ERROR]); else return (ErrorMessages[index]); } /********************************************************************* // Name: TSendText // Input: 1) RPath: return path of the message // Is used to fill the "Return-Path" and the // "X-Sender" fields of the message. // 2) Subject: Subject field of the message. If NULL is given // the subject is set to "No Subject" // 3) mailTo: Destination address // 4) data: Null terminated string containing the data to be send. // Output: Error code or SUCCESS // Description: // Author/Date: jcar 20/9/96 // History: //*******************************************************************/ int SendText(char *RPath, char *Subject, char *mailTo, char *data, char *headers) { int res, i; char *p; char *tempMailTo, *token, *pos1, *pos2; /* check for NULL parameters */ if (data == NULL) return (BAD_MSG_CONTENTS); if (mailTo == NULL) return (BAD_MSG_DESTINATION); if (RPath == NULL) return (BAD_MSG_RPATH); /* simple checks for the mailto address */ /* have ampersand ? */ if (strchr(mailTo, '@') == NULL) return (BAD_MSG_DESTINATION); sprintf(Buffer, "HELO %s\r\n", LocalHost); /* in the beggining of the dialog */ /* attempt reconnect if the first Post fail */ if ((res = Post(Buffer)) != SUCCESS) { MailConnect(); if ((res = Post(Buffer)) != SUCCESS) return (res); } if ((res = Ack()) != SUCCESS) return (res); sprintf(Buffer, "MAIL FROM:<%s>\r\n", RPath); if ((res = Post(Buffer)) != SUCCESS) return (res); if ((res = Ack()) != SUCCESS) return (res); tempMailTo = estrdup(mailTo); /* Send mail to all rcpt's */ token = strtok(tempMailTo, ","); while(token != NULL) { sprintf(Buffer, "RCPT TO:<%s>\r\n", token); if ((res = Post(Buffer)) != SUCCESS) return (res); if ((res = Ack()) != SUCCESS) return (res); token = strtok(NULL, ","); } /* Send mail to all Cc rcpt's */ efree(tempMailTo); if (headers && (pos1 = strstr(headers, "Cc:"))) { pos2 = strstr(pos1, "\r\n"); tempMailTo = estrndup(pos1, pos2-pos1); token = strtok(tempMailTo, ","); while(token != NULL) { sprintf(Buffer, "RCPT TO:<%s>\r\n", token); if ((res = Post(Buffer)) != SUCCESS) return (res); if ((res = Ack()) != SUCCESS) return (res); token = strtok(NULL, ","); } efree(tempMailTo); } if ((res = Post("DATA\r\n")) != SUCCESS) return (res); if ((res = Ack()) != SUCCESS) return (res); /* send message header */ if (Subject == NULL) res = PostHeader(RPath, "No Subject", mailTo, headers); else res = PostHeader(RPath, Subject, mailTo, headers); if (res != SUCCESS) return (res); /* send message contents in 1024 chunks */ if (strlen(data) <= 1024) { if ((res = Post(data)) != SUCCESS) return (res); } else { p = data; while (1) { if (*p == '\0') break; if (strlen(p) >= 1024) i = 1024; else i = strlen(p); /* put next chunk in buffer */ strncpy(Buffer, p, i); Buffer[i] = '\0'; p += i; /* send chunk */ if ((res = Post(Buffer)) != SUCCESS) return (res); } } /*send termination dot */ if ((res = Post("\r\n.\r\n")) != SUCCESS) return (res); if ((res = Ack()) != SUCCESS) return (res); return (SUCCESS); } /********************************************************************* // Name: PostHeader // Input: 1) return path // 2) Subject // 3) destination address // 4) DoMime flag // Output: Error code or Success // Description: // Author/Date: jcar 20/9/96 // History: //********************************************************************/ int PostHeader(char *RPath, char *Subject, char *mailTo, char *xheaders) { /* Print message header according to RFC 822 */ /* Return-path, Received, Date, From, Subject, Sender, To, cc */ time_t tNow = time(NULL); struct tm *tm = localtime(&tNow); int zoneh = abs(_timezone); int zonem, res; char *p; p = Buffer; zoneh /= (60 * 60); zonem = (abs(_timezone) / 60) - (zoneh * 60); if(!xheaders || !strstr(xheaders, "Date:")){ p += sprintf(p, "Date: %s, %02d %s %04d %02d:%02d:%02d %s%02d%02d\r\n", days[tm->tm_wday], tm->tm_mday, months[tm->tm_mon], tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec, (_timezone > 0) ? "+" : (_timezone < 0) ? "-" : "", zoneh, zonem); } if(!xheaders || !strstr(xheaders, "From:")){ p += sprintf(p, "From: %s\r\n", RPath); } p += sprintf(p, "Subject: %s\r\n", Subject); if(!xheaders || !strstr(xheaders, "To:")){ p += sprintf(p, "To: %s\r\n", mailTo); } if(xheaders){ p += sprintf(p, "%s\r\n", xheaders); } if ((res = Post(Buffer)) != SUCCESS) return (res); if ((res = Post("\r\n")) != SUCCESS) return (res); return (SUCCESS); } /********************************************************************* // Name: MailConnect // Input: None // Output: None // Description: Connect to the mail host and receive the welcome message. // Author/Date: jcar 20/9/96 // History: //********************************************************************/ int MailConnect() { int res; short portnum; /* Create Socket */ if ((sc = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) return (FAILED_TO_OBTAIN_SOCKET_HANDLE); /* Get our own host name */ if (gethostname(LocalHost, HOST_NAME_LEN)) return (FAILED_TO_GET_HOSTNAME); /* Resolve the servers IP */ /* if (!isdigit(MailHost[0])||!gethostbyname(MailHost)) { return (FAILED_TO_RESOLVE_HOST); } */ portnum = (short) INI_INT("sendmail_port"); if (!portnum) { portnum = 25; } /* Connect to server */ sock_in.sin_family = AF_INET; sock_in.sin_port = htons(portnum); sock_in.sin_addr.S_un.S_addr = GetAddr(MailHost); if (connect(sc, (LPSOCKADDR) & sock_in, sizeof(sock_in))) return (FAILED_TO_CONNECT); /* receive Server welcome message */ res = Ack(); return (res); } /********************************************************************* // Name: Post // Input: // Output: // Description: // Author/Date: jcar 20/9/96 // History: //********************************************************************/ int Post(LPCSTR msg) { int len = strlen(msg); int slen; int index = 0; while (len > 0) { if ((slen = send(sc, msg + index, len, 0)) < 1) return (FAILED_TO_SEND); len -= slen; index += slen; } return (SUCCESS); } /********************************************************************* // Name: Ack // Input: // Output: // Description: // Get the response from the server. We only want to know if the // last command was successful. // Author/Date: jcar 20/9/96 // History: //********************************************************************/ int Ack() { static char *buf; int rlen; int Index = 0; int Received = 0; if (!buf) if ((buf = (char *) malloc(1024 * 4)) == NULL) return (OUT_OF_MEMORY); again: if ((rlen = recv(sc, buf + Index, ((1024 * 4) - 1) - Received, 0)) < 1) return (FAILED_TO_RECEIVE); Received += rlen; buf[Received] = 0; /*err_msg fprintf(stderr,"Received: (%d bytes) %s", rlen, buf + Index); */ /* Check for newline */ Index += rlen; if ((buf[Received - 4] == ' ' && buf[Received - 3] == '-') || (buf[Received - 2] != '\r') || (buf[Received - 1] != '\n')) /* err_msg fprintf(stderr,"Incomplete server message. Awaiting CRLF\n"); */ goto again; /* Incomplete data. Line must be terminated by CRLF And not contain a space followed by a '-' */ if (buf[0] > '3') return (SMTP_SERVER_ERROR); return (SUCCESS); } /********************************************************************* // Name: unsigned long GetAddr (LPSTR szHost) // Input: // Output: // Description: Given a string, it will return an IP address. // - first it tries to convert the string directly // - if that fails, it tries o resolve it as a hostname // // WARNING: gethostbyname() is a blocking function // Author/Date: jcar 20/9/96 // History: //********************************************************************/ unsigned long GetAddr(LPSTR szHost) { LPHOSTENT lpstHost; u_long lAddr = INADDR_ANY; /* check that we have a string */ if (*szHost) { /* check for a dotted-IP address string */ lAddr = inet_addr(szHost); /* If not an address, then try to resolve it as a hostname */ if ((lAddr == INADDR_NONE) && (strcmp(szHost, "255.255.255.255"))) { lpstHost = gethostbyname(szHost); if (lpstHost) { /* success */ lAddr = *((u_long FAR *) (lpstHost->h_addr)); } else { lAddr = INADDR_ANY; /* failure */ } } } return (lAddr); } /* end GetAddr() */