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 |
} |