| 1 |
/* |
| 2 |
This file is part of libquickmail. |
| 3 |
|
| 4 |
libquickmail is free software: you can redistribute it and/or modify |
| 5 |
it under the terms of the GNU General Public License as published by |
| 6 |
the Free Software Foundation, either version 3 of the License, or |
| 7 |
(at your option) any later version. |
| 8 |
|
| 9 |
libquickmail is distributed in the hope that it will be useful, |
| 10 |
but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 12 |
GNU General Public License for more details. |
| 13 |
|
| 14 |
You should have received a copy of the GNU General Public License |
| 15 |
along with libquickmail. If not, see <http://www.gnu.org/licenses/>. |
| 16 |
*/ |
| 17 |
|
| 18 |
#include "smtpsocket.h" |
| 19 |
#include <stdlib.h> |
| 20 |
#include <ctype.h> |
| 21 |
#include <string.h> |
| 22 |
#if _MSC_VER |
| 23 |
#define va_copy(dst,src) ((dst) = (src)) |
| 24 |
#endif |
| 25 |
|
| 26 |
//////////////////////////////////////////////////////////////////////// |
| 27 |
|
| 28 |
#define DEBUG_ERROR(errmsg) |
| 29 |
static const char* ERRMSG_MEMORY_ALLOCATION_ERROR = "Memory allocation error"; |
| 30 |
|
| 31 |
//////////////////////////////////////////////////////////////////////// |
| 32 |
|
| 33 |
SOCKET socket_open (const char* smtpserver, unsigned int smtpport, char** errmsg) |
| 34 |
{ |
| 35 |
struct in_addr ipv4addr; |
| 36 |
SOCKET sock; |
| 37 |
struct sockaddr_in remote_sock_addr; |
| 38 |
static const struct linger linger_option = {-1, 2}; //linger 2 seconds when disconnecting |
| 39 |
//determine IPv4 address of SMTP server |
| 40 |
ipv4addr.s_addr = inet_addr(smtpserver); |
| 41 |
if (ipv4addr.s_addr == INADDR_NONE) { |
| 42 |
struct hostent* addr; |
| 43 |
if ((addr = gethostbyname(smtpserver)) != NULL && (addr->h_addrtype == AF_INET && addr->h_length >= 1 && ((struct in_addr*)addr->h_addr)->s_addr != 0)) |
| 44 |
memcpy(&ipv4addr, addr->h_addr, sizeof(ipv4addr)); |
| 45 |
} |
| 46 |
if (ipv4addr.s_addr == INADDR_NONE) { |
| 47 |
if (errmsg) |
| 48 |
*errmsg = "Unable to resolve SMTP server host name"; |
| 49 |
return INVALID_SOCKET; |
| 50 |
} |
| 51 |
//create socket |
| 52 |
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); |
| 53 |
if (sock == INVALID_SOCKET) { |
| 54 |
if (errmsg) |
| 55 |
*errmsg = "Error creating socket for SMTP connection"; |
| 56 |
return INVALID_SOCKET; |
| 57 |
} |
| 58 |
//connect |
| 59 |
remote_sock_addr.sin_family = AF_INET; |
| 60 |
remote_sock_addr.sin_port = htons(smtpport); |
| 61 |
remote_sock_addr.sin_addr.s_addr = ipv4addr.s_addr; |
| 62 |
if (connect(sock, (struct sockaddr*)&remote_sock_addr, sizeof(remote_sock_addr)) != 0) { |
| 63 |
if (errmsg) |
| 64 |
*errmsg = "Error connecting to SMTP server"; |
| 65 |
socket_close(sock); |
| 66 |
return INVALID_SOCKET; |
| 67 |
} |
| 68 |
//set linger option |
| 69 |
setsockopt(sock, SOL_SOCKET, SO_LINGER, (const char*)&linger_option, sizeof(linger_option)); |
| 70 |
return sock; |
| 71 |
} |
| 72 |
|
| 73 |
void socket_close (SOCKET sock) |
| 74 |
{ |
| 75 |
#ifndef _WIN32 |
| 76 |
shutdown(sock, 2); |
| 77 |
#else |
| 78 |
closesocket(sock); |
| 79 |
#endif |
| 80 |
} |
| 81 |
|
| 82 |
int socket_send (SOCKET sock, const char* buf, int len) |
| 83 |
{ |
| 84 |
int total_sent = 0; |
| 85 |
int l = 0; |
| 86 |
if (sock == 0 || !buf) |
| 87 |
return 0; |
| 88 |
if (len < 0) |
| 89 |
len = strlen(buf); |
| 90 |
while (len > 0 && (l = send(sock, buf, len, 0)) < len) { |
| 91 |
if (l == SOCKET_ERROR || l > len) |
| 92 |
return (total_sent > 0 ? total_sent : -1); |
| 93 |
total_sent += l; |
| 94 |
buf += l; |
| 95 |
len -= l; |
| 96 |
} |
| 97 |
return total_sent + l; |
| 98 |
} |
| 99 |
|
| 100 |
int socket_data_waiting (SOCKET sock, int timeoutseconds) |
| 101 |
{ |
| 102 |
fd_set rfds; |
| 103 |
struct timeval tv; |
| 104 |
if (sock == 0) |
| 105 |
return 0; |
| 106 |
//make a set with only this socket |
| 107 |
FD_ZERO(&rfds); |
| 108 |
FD_SET(sock, &rfds); |
| 109 |
//make a timeval with the supplied timeout |
| 110 |
tv.tv_sec = timeoutseconds; |
| 111 |
tv.tv_usec = 0; |
| 112 |
//check the socket |
| 113 |
return (select(1, &rfds, NULL, NULL, &tv) > 0); |
| 114 |
} |
| 115 |
|
| 116 |
char* socket_receive_smtp (SOCKET sock) |
| 117 |
{ |
| 118 |
char* buf = NULL; |
| 119 |
int bufsize = READ_BUFFER_CHUNK_SIZE; |
| 120 |
int pos = 0; |
| 121 |
int linestart; |
| 122 |
int n; |
| 123 |
if ((buf = (char*)malloc(bufsize)) == NULL) { |
| 124 |
DEBUG_ERROR(ERRMSG_MEMORY_ALLOCATION_ERROR) |
| 125 |
return NULL; |
| 126 |
} |
| 127 |
do { |
| 128 |
//insert line break if response is multiple lines |
| 129 |
if (pos > 0) { |
| 130 |
buf[pos++] = '\n'; |
| 131 |
if (pos >= bufsize) { |
| 132 |
char* newbuf; |
| 133 |
if ((newbuf = (char*)realloc(buf, bufsize + READ_BUFFER_CHUNK_SIZE)) == NULL) { |
| 134 |
free(buf); |
| 135 |
DEBUG_ERROR(ERRMSG_MEMORY_ALLOCATION_ERROR) |
| 136 |
return NULL; |
| 137 |
} |
| 138 |
buf = newbuf; |
| 139 |
bufsize += READ_BUFFER_CHUNK_SIZE; |
| 140 |
} |
| 141 |
} |
| 142 |
//add each character read until it is a line break |
| 143 |
linestart = pos; |
| 144 |
while ((n = recv(sock, buf + pos, 1, 0)) == 1) { |
| 145 |
//detect optional carriage return (CR) |
| 146 |
if (buf[pos] == '\r') |
| 147 |
if (recv(sock, buf + pos, 1, 0) < 1) |
| 148 |
return NULL; |
| 149 |
//detect line feed (LF) |
| 150 |
if (buf[pos] == '\n') |
| 151 |
break; |
| 152 |
//enlarge buffer if necessary |
| 153 |
if (++pos >= bufsize) { |
| 154 |
char* newbuf; |
| 155 |
if ((newbuf = (char*)realloc(buf, bufsize + READ_BUFFER_CHUNK_SIZE)) == NULL) { |
| 156 |
free(buf); |
| 157 |
DEBUG_ERROR(ERRMSG_MEMORY_ALLOCATION_ERROR) |
| 158 |
return NULL; |
| 159 |
} |
| 160 |
buf = newbuf; |
| 161 |
bufsize += READ_BUFFER_CHUNK_SIZE; |
| 162 |
} |
| 163 |
} |
| 164 |
//exit on error (e.g. if connection is closed) |
| 165 |
if (n < 1) |
| 166 |
return NULL; |
| 167 |
} while (!isdigit(buf[linestart]) || !isdigit(buf[linestart + 1]) || !isdigit(buf[linestart + 2]) || buf[linestart + 3] != ' '); |
| 168 |
buf[pos] = 0; |
| 169 |
return buf; |
| 170 |
} |
| 171 |
|
| 172 |
int socket_get_smtp_code (SOCKET sock, char** message) |
| 173 |
{ |
| 174 |
int code; |
| 175 |
char* buf = socket_receive_smtp(sock); |
| 176 |
if (!buf || strlen(buf) < 4 || (buf[3] != ' ' && buf[3] != '-')) { |
| 177 |
free(buf); |
| 178 |
return 999; |
| 179 |
} |
| 180 |
//get code |
| 181 |
buf[3] = 0; |
| 182 |
code = atoi(buf); |
| 183 |
//get error message (if needed) |
| 184 |
if (message /*&& code >= 400*/) |
| 185 |
*message = strdup(buf + 4); |
| 186 |
//clean up and return |
| 187 |
free(buf); |
| 188 |
return code; |
| 189 |
} |
| 190 |
|
| 191 |
int socket_smtp_command (SOCKET sock, FILE* debuglog, const char* template, ...) |
| 192 |
{ |
| 193 |
char* message; |
| 194 |
int statuscode; |
| 195 |
//send command (if one is supplied) |
| 196 |
if (template) { |
| 197 |
va_list ap; |
| 198 |
va_list aq; |
| 199 |
char* cmd; |
| 200 |
int cmdlen; |
| 201 |
va_start(ap, template); |
| 202 |
//construct command to send |
| 203 |
va_copy(aq, ap); |
| 204 |
cmdlen = vsnprintf(NULL, 0, template, aq); |
| 205 |
va_end(aq); |
| 206 |
if ((cmd = (char*)malloc(cmdlen + 3)) == NULL) { |
| 207 |
DEBUG_ERROR(ERRMSG_MEMORY_ALLOCATION_ERROR) |
| 208 |
if (debuglog) |
| 209 |
fprintf(debuglog, ERRMSG_MEMORY_ALLOCATION_ERROR); |
| 210 |
va_end(ap); |
| 211 |
return 999; |
| 212 |
} |
| 213 |
vsnprintf(cmd, cmdlen + 1, template, ap); |
| 214 |
//log command to send |
| 215 |
if (debuglog) |
| 216 |
fprintf(debuglog, "SMTP> %s\n", cmd); |
| 217 |
//append CR+LF |
| 218 |
strcpy(cmd + cmdlen, "\r\n"); |
| 219 |
cmdlen += 2; |
| 220 |
//send command |
| 221 |
statuscode = socket_send(sock, cmd, cmdlen); |
| 222 |
//clean up |
| 223 |
free(cmd); |
| 224 |
va_end(ap); |
| 225 |
if (statuscode < 0) |
| 226 |
return 999; |
| 227 |
} |
| 228 |
//receive result |
| 229 |
message = NULL; |
| 230 |
statuscode = socket_get_smtp_code(sock, &message); |
| 231 |
if (debuglog) |
| 232 |
fprintf(debuglog, "SMTP< %i %s\n", statuscode, (message ? message : "")); |
| 233 |
free(message); |
| 234 |
return statuscode; |
| 235 |
} |