/* This file is part of libquickmail. libquickmail is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. libquickmail is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with libquickmail. If not, see . */ #include "smtpsocket.h" #include #include #include #if _MSC_VER #define va_copy(dst,src) ((dst) = (src)) #endif //////////////////////////////////////////////////////////////////////// #define DEBUG_ERROR(errmsg) static const char* ERRMSG_MEMORY_ALLOCATION_ERROR = "Memory allocation error"; //////////////////////////////////////////////////////////////////////// SOCKET socket_open (const char* smtpserver, unsigned int smtpport, char** errmsg) { struct in_addr ipv4addr; SOCKET sock; struct sockaddr_in remote_sock_addr; static const struct linger linger_option = {-1, 2}; //linger 2 seconds when disconnecting //determine IPv4 address of SMTP server ipv4addr.s_addr = inet_addr(smtpserver); if (ipv4addr.s_addr == INADDR_NONE) { struct hostent* addr; if ((addr = gethostbyname(smtpserver)) != NULL && (addr->h_addrtype == AF_INET && addr->h_length >= 1 && ((struct in_addr*)addr->h_addr)->s_addr != 0)) memcpy(&ipv4addr, addr->h_addr, sizeof(ipv4addr)); } if (ipv4addr.s_addr == INADDR_NONE) { if (errmsg) *errmsg = "Unable to resolve SMTP server host name"; return INVALID_SOCKET; } //create socket sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sock == INVALID_SOCKET) { if (errmsg) *errmsg = "Error creating socket for SMTP connection"; return INVALID_SOCKET; } //connect remote_sock_addr.sin_family = AF_INET; remote_sock_addr.sin_port = htons(smtpport); remote_sock_addr.sin_addr.s_addr = ipv4addr.s_addr; if (connect(sock, (struct sockaddr*)&remote_sock_addr, sizeof(remote_sock_addr)) != 0) { if (errmsg) *errmsg = "Error connecting to SMTP server"; socket_close(sock); return INVALID_SOCKET; } //set linger option setsockopt(sock, SOL_SOCKET, SO_LINGER, (const char*)&linger_option, sizeof(linger_option)); return sock; } void socket_close (SOCKET sock) { #ifndef _WIN32 shutdown(sock, 2); #else closesocket(sock); #endif } int socket_send (SOCKET sock, const char* buf, int len) { int total_sent = 0; int l = 0; if (sock == 0 || !buf) return 0; if (len < 0) len = strlen(buf); while (len > 0 && (l = send(sock, buf, len, 0)) < len) { if (l == SOCKET_ERROR || l > len) return (total_sent > 0 ? total_sent : -1); total_sent += l; buf += l; len -= l; } return total_sent + l; } int socket_data_waiting (SOCKET sock, int timeoutseconds) { fd_set rfds; struct timeval tv; if (sock == 0) return 0; //make a set with only this socket FD_ZERO(&rfds); FD_SET(sock, &rfds); //make a timeval with the supplied timeout tv.tv_sec = timeoutseconds; tv.tv_usec = 0; //check the socket return (select(1, &rfds, NULL, NULL, &tv) > 0); } char* socket_receive_smtp (SOCKET sock) { char* buf = NULL; int bufsize = READ_BUFFER_CHUNK_SIZE; int pos = 0; int linestart; int n; if ((buf = (char*)malloc(bufsize)) == NULL) { DEBUG_ERROR(ERRMSG_MEMORY_ALLOCATION_ERROR) return NULL; } do { //insert line break if response is multiple lines if (pos > 0) { buf[pos++] = '\n'; if (pos >= bufsize) { char* newbuf; if ((newbuf = (char*)realloc(buf, bufsize + READ_BUFFER_CHUNK_SIZE)) == NULL) { free(buf); DEBUG_ERROR(ERRMSG_MEMORY_ALLOCATION_ERROR) return NULL; } buf = newbuf; bufsize += READ_BUFFER_CHUNK_SIZE; } } //add each character read until it is a line break linestart = pos; while ((n = recv(sock, buf + pos, 1, 0)) == 1) { //detect optional carriage return (CR) if (buf[pos] == '\r') if (recv(sock, buf + pos, 1, 0) < 1) return NULL; //detect line feed (LF) if (buf[pos] == '\n') break; //enlarge buffer if necessary if (++pos >= bufsize) { char* newbuf; if ((newbuf = (char*)realloc(buf, bufsize + READ_BUFFER_CHUNK_SIZE)) == NULL) { free(buf); DEBUG_ERROR(ERRMSG_MEMORY_ALLOCATION_ERROR) return NULL; } buf = newbuf; bufsize += READ_BUFFER_CHUNK_SIZE; } } //exit on error (e.g. if connection is closed) if (n < 1) return NULL; } while (!isdigit(buf[linestart]) || !isdigit(buf[linestart + 1]) || !isdigit(buf[linestart + 2]) || buf[linestart + 3] != ' '); buf[pos] = 0; return buf; } int socket_get_smtp_code (SOCKET sock, char** message) { int code; char* buf = socket_receive_smtp(sock); if (!buf || strlen(buf) < 4 || (buf[3] != ' ' && buf[3] != '-')) { free(buf); return 999; } //get code buf[3] = 0; code = atoi(buf); //get error message (if needed) if (message /*&& code >= 400*/) *message = strdup(buf + 4); //clean up and return free(buf); return code; } int socket_smtp_command (SOCKET sock, FILE* debuglog, const char* template, ...) { char* message; int statuscode; //send command (if one is supplied) if (template) { va_list ap; va_list aq; char* cmd; int cmdlen; va_start(ap, template); //construct command to send va_copy(aq, ap); cmdlen = vsnprintf(NULL, 0, template, aq); va_end(aq); if ((cmd = (char*)malloc(cmdlen + 3)) == NULL) { DEBUG_ERROR(ERRMSG_MEMORY_ALLOCATION_ERROR) if (debuglog) fprintf(debuglog, ERRMSG_MEMORY_ALLOCATION_ERROR); va_end(ap); return 999; } vsnprintf(cmd, cmdlen + 1, template, ap); //log command to send if (debuglog) fprintf(debuglog, "SMTP> %s\n", cmd); //append CR+LF strcpy(cmd + cmdlen, "\r\n"); cmdlen += 2; //send command statuscode = socket_send(sock, cmd, cmdlen); //clean up free(cmd); va_end(ap); if (statuscode < 0) return 999; } //receive result message = NULL; statuscode = socket_get_smtp_code(sock, &message); if (debuglog) fprintf(debuglog, "SMTP< %i %s\n", statuscode, (message ? message : "")); free(message); return statuscode; }