PageRenderTime 54ms CodeModel.GetById 13ms app.highlight 20ms RepoModel.GetById 17ms app.codeStats 1ms

/src/socket.c

https://github.com/booo/imapfilter
C | 438 lines | 334 code | 80 blank | 24 comment | 92 complexity | 410bc3697c4516e41e8e89c1310bd539 MD5 | raw file
  1#include <stdio.h>
  2#include <unistd.h>
  3#include <string.h>
  4#include <strings.h>
  5#include <errno.h>
  6#include <netinet/in.h>
  7#include <netdb.h>
  8#include <sys/socket.h>
  9#include <sys/types.h>
 10#include <sys/time.h>
 11#include <sys/select.h>
 12
 13#include <openssl/ssl.h>
 14#include <openssl/err.h>
 15
 16#include "imapfilter.h"
 17#include "session.h"
 18
 19
 20/*
 21 * Connect to mail server.
 22 */
 23int
 24open_connection(session *ssn)
 25{
 26	struct addrinfo hints, *res, *ressave;
 27	int n, sockfd;
 28
 29	memset(&hints, 0, sizeof(struct addrinfo));
 30
 31	hints.ai_family = AF_UNSPEC;
 32	hints.ai_socktype = SOCK_STREAM;
 33
 34	n = getaddrinfo(ssn->server, ssn->port, &hints, &res);
 35
 36	if (n < 0) {
 37		error("gettaddrinfo; %s\n", gai_strerror(n));
 38		return -1;
 39	}
 40
 41	ressave = res;
 42
 43	sockfd = -1;
 44
 45	while (res) {
 46		sockfd = socket(res->ai_family, res->ai_socktype,
 47		    res->ai_protocol);
 48
 49		if (sockfd >= 0) {
 50			if (connect(sockfd, res->ai_addr, res->ai_addrlen) == 0)
 51				break;
 52
 53			sockfd = -1;
 54		}
 55		res = res->ai_next;
 56	}
 57
 58	if (ressave)
 59		freeaddrinfo(ressave);
 60
 61	if (sockfd == -1) {
 62		error("error while initiating connection to %s at port %s\n",
 63		    ssn->server, ssn->port);
 64		return -1;
 65	}
 66
 67	ssn->socket = sockfd;
 68
 69	if (ssn->sslproto) {
 70		if (open_secure_connection(ssn) == -1) {
 71			close_connection(ssn);
 72			return -1;
 73		}
 74	}
 75
 76	return ssn->socket;
 77}
 78
 79
 80/*
 81 * Initialize SSL/TLS connection.
 82 */
 83int
 84open_secure_connection(session *ssn)
 85{
 86	int r, e;
 87	SSL_CTX *ctx;
 88#if OPENSSL_VERSION_NUMBER >= 0x1000000fL
 89	const SSL_METHOD *method;
 90#else		
 91	SSL_METHOD *method;
 92#endif
 93
 94	method = NULL;
 95
 96	if (ssn->sslproto && (!strncasecmp(ssn->sslproto, "ssl3", 4) ||
 97	    !strncasecmp(ssn->sslproto, "ssl2", 4)))
 98		method = SSLv23_client_method();
 99	else
100		method = TLSv1_client_method();
101
102	if (!(ctx = SSL_CTX_new(method)))
103		goto fail;
104
105	if (!(ssn->sslconn = SSL_new(ctx)))
106		goto fail;
107
108	SSL_set_fd(ssn->sslconn, ssn->socket);
109
110	for (;;) {
111		if ((r = SSL_connect(ssn->sslconn)) > 0)
112			break;
113
114		switch (SSL_get_error(ssn->sslconn, r)) {
115		case SSL_ERROR_ZERO_RETURN:
116			error("initiating SSL connection to %s; the "
117			    "connection has been closed cleanly\n",
118			    ssn->server);
119			goto fail;
120		case SSL_ERROR_NONE:
121		case SSL_ERROR_WANT_CONNECT:
122		case SSL_ERROR_WANT_ACCEPT:
123		case SSL_ERROR_WANT_X509_LOOKUP:
124		case SSL_ERROR_WANT_READ:
125		case SSL_ERROR_WANT_WRITE:
126			break;
127		case SSL_ERROR_SYSCALL:
128			e = ERR_get_error();
129			if (e == 0 && r == 0)
130				error("initiating SSL connection to %s; EOF in "
131				    "violation of the protocol\n", ssn->server);
132			else if (e == 0 && r == -1)
133				error("initiating SSL connection to %s; %s\n",
134				    ssn->server, strerror(errno));
135			else
136				error("initiating SSL connection to %s; %s\n",
137				    ssn->server, ERR_error_string(e, NULL));
138			goto fail;
139		case SSL_ERROR_SSL:
140			error("initiating SSL connection to %s; %s\n",
141			    ssn->server, ERR_error_string(ERR_get_error(),
142			    NULL));
143			goto fail;
144		default:
145			break;
146		}
147	}
148	if (get_option_boolean("certificates") && get_cert(ssn) == -1)
149		goto fail;
150
151	SSL_CTX_free(ctx);
152
153	return 0;
154
155fail:
156	ssn->sslconn = NULL;
157	SSL_CTX_free(ctx);
158
159	return -1;
160}
161
162
163/*
164 * Disconnect from mail server.
165 */
166int
167close_connection(session *ssn)
168{
169	int r;
170
171	r = 0;
172
173	close_secure_connection(ssn);
174
175	if (ssn->socket != -1) {
176		r = close(ssn->socket);
177		ssn->socket = -1;
178
179		if (r == -1)
180			error("closing socket; %s\n", strerror(errno));
181	}
182	return r;
183}
184
185
186/*
187 * Shutdown SSL/TLS connection.
188 */
189int
190close_secure_connection(session *ssn)
191{
192
193	if (ssn->sslconn) {
194		SSL_shutdown(ssn->sslconn);
195		SSL_free(ssn->sslconn);
196		ssn->sslconn = NULL;
197	}
198
199	return 0;
200}
201
202
203/*
204 * Read data from socket.
205 */
206ssize_t
207socket_read(session *ssn, char *buf, size_t len, long timeout, int timeoutfail)
208{
209	int s;
210	ssize_t r;
211	fd_set fds;
212
213	struct timeval tv;
214
215	struct timeval *tvp;
216
217	r = 0;
218	s = 1;
219	tvp = NULL;
220
221	memset(buf, 0, len + 1);
222
223	if (timeout > 0) {
224		tv.tv_sec = timeout;
225		tv.tv_usec = 0;
226		tvp = &tv;
227	}
228
229	FD_ZERO(&fds);
230	FD_SET(ssn->socket, &fds);
231 
232	if (ssn->sslconn) {
233		if (SSL_pending(ssn->sslconn) > 0 ||
234		    ((s = select(ssn->socket + 1, &fds, NULL, NULL, tvp)) > 0 &&
235		    FD_ISSET(ssn->socket, &fds))) {
236			r = socket_secure_read(ssn, buf, len);
237
238			if (r <= 0)
239				goto fail;
240		}
241	} else {
242		if ((s = select(ssn->socket + 1, &fds, NULL, NULL, tvp)) > 0 &&
243		    FD_ISSET(ssn->socket, &fds)) {
244			r = read(ssn->socket, buf, len);
245
246			if (r == -1) {
247				error("reading data; %s\n", strerror(errno));
248				goto fail;
249			} else if (r == 0) {
250				goto fail;
251			}
252		}
253	}
254
255	if (s == -1) {
256		error("waiting to read from socket; %s\n", strerror(errno));
257		goto fail;
258	} else if (s == 0 && timeoutfail) {
259		error("timeout period expired while waiting to read data\n");
260		goto fail;
261	}
262
263	return r;
264fail:
265	close_connection(ssn);
266
267	return -1;
268
269}
270
271
272/*
273 * Read data from a TLS/SSL connection.
274 */
275ssize_t
276socket_secure_read(session *ssn, char *buf, size_t len)
277{
278	int r, e;
279
280	for (;;) {
281		if ((r = (ssize_t) SSL_read(ssn->sslconn, buf, len)) > 0)
282			break;
283
284		switch (SSL_get_error(ssn->sslconn, r)) {
285		case SSL_ERROR_ZERO_RETURN:
286			error("reading data through SSL; the connection has "
287			    "been closed cleanly\n");
288			goto fail;
289		case SSL_ERROR_NONE:
290		case SSL_ERROR_WANT_READ:
291		case SSL_ERROR_WANT_WRITE:
292		case SSL_ERROR_WANT_CONNECT:
293		case SSL_ERROR_WANT_ACCEPT:
294		case SSL_ERROR_WANT_X509_LOOKUP:
295			break;
296		case SSL_ERROR_SYSCALL:
297			e = ERR_get_error();
298			if (e == 0 && r == 0)
299				error("reading data through SSL; EOF in "
300				    "violation of the protocol\n");
301			else if (e == 0 && r == -1)
302				error("reading data through SSL; %s\n",
303				    strerror(errno));
304			else
305				error("reading data through SSL; %s\n",
306				    ERR_error_string(e, NULL));
307			goto fail;
308		case SSL_ERROR_SSL:
309			error("reading data through SSL; %s\n",
310			    ERR_error_string(ERR_get_error(), NULL));
311			goto fail;
312		default:
313			break;
314		}
315	}
316
317	return r;
318fail:
319	SSL_set_shutdown(ssn->sslconn, SSL_SENT_SHUTDOWN |
320	    SSL_RECEIVED_SHUTDOWN);
321
322	return -1;
323
324}
325
326
327/*
328 * Write data to socket.
329 */
330ssize_t
331socket_write(session *ssn, const char *buf, size_t len)
332{
333	int s;
334	ssize_t r, t;
335	fd_set fds;
336
337	r = t = 0;
338	s = 1;
339
340	FD_ZERO(&fds);
341	FD_SET(ssn->socket, &fds);
342
343	while (len) {
344		if ((s = select(ssn->socket + 1, NULL, &fds, NULL, NULL) > 0 &&
345		    FD_ISSET(ssn->socket, &fds))) {
346			if (ssn->sslconn) {
347				r = socket_secure_write(ssn, buf, len);
348
349				if (r <= 0)
350					goto fail;
351			} else {
352				r = write(ssn->socket, buf, len);
353
354				if (r == -1) {
355					error("writing data; %s\n",
356					    strerror(errno));
357					goto fail;
358				} else if (r == 0) {
359					goto fail;
360				}
361			}
362
363			if (r > 0) {
364				len -= r;
365				buf += r;
366				t += r;
367			}
368		}
369	}
370
371	if (s == -1) {
372		error("waiting to write to socket; %s\n", strerror(errno));
373		goto fail;
374	} else if (s == 0) {
375		error("timeout period expired while waiting to write data\n");
376		goto fail;
377	}
378
379	return t;
380fail:
381	close_connection(ssn);
382
383	return -1;
384}
385
386
387/*
388 * Write data to a TLS/SSL connection.
389 */
390ssize_t
391socket_secure_write(session *ssn, const char *buf, size_t len)
392{
393	int r, e;
394
395	for (;;) {
396		if ((r = (ssize_t) SSL_write(ssn->sslconn, buf, len)) > 0)
397			break;
398
399		switch (SSL_get_error(ssn->sslconn, r)) {
400		case SSL_ERROR_ZERO_RETURN:
401			error("writing data through SSL; the connection has "
402			    "been closed cleanly\n");
403			goto fail;
404		case SSL_ERROR_NONE:
405		case SSL_ERROR_WANT_READ:
406		case SSL_ERROR_WANT_WRITE:
407		case SSL_ERROR_WANT_CONNECT:
408		case SSL_ERROR_WANT_ACCEPT:
409		case SSL_ERROR_WANT_X509_LOOKUP:
410			break;
411		case SSL_ERROR_SYSCALL:
412			e = ERR_get_error();
413			if (e == 0 && r == 0)
414				error("writing data through SSL; EOF in "
415				    "violation of the protocol\n");
416			else if (e == 0 && r == -1)
417				error("writing data through SSL; %s\n",
418				    strerror(errno));
419			else
420				error("writing data through SSL; %s\n",
421				    ERR_error_string(e, NULL));
422			goto fail;
423		case SSL_ERROR_SSL:
424			error("writing data through SSL; %s\n",
425			    ERR_error_string(ERR_get_error(), NULL));
426			goto fail;
427		default:
428			break;
429		}
430	}
431
432	return r;
433fail:
434	SSL_set_shutdown(ssn->sslconn, SSL_SENT_SHUTDOWN |
435	    SSL_RECEIVED_SHUTDOWN);
436
437	return -1;
438}