1 /**
2  * \file nbsmtp.c
3  * \brief nbsmtp specific functions
4  *
5  * \author Fernando J. Pereda <ferdy@ferdyx.org>
6  * \author Ricardo Cervera Navarro <ricardo@zonasiete.org>
7  *
8  * This is part of nbsmtp. nbsmtp is free software;
9  * you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License
11  * as published by the Free Software Foundation; either version 2
12  * of the License, or (at your option) any later version.
13  *
14  * nbsmtp is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with nbsmtp; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22  *
23  * See COPYING for details.
24  */
25 #include <stdio.h>
26 #include <unistd.h>
27 #include <stdlib.h>
28 #include <sys/types.h>
29 #include <sys/socket.h>
30 #include <sys/param.h>
31 #include <netdb.h>
32 #include <netinet/in.h>
33 #include <fcntl.h>
34 #include <stdarg.h>
35 #include <ctype.h>
36 #include <string.h>
37 #include <syslog.h>
38 #include <pwd.h>
39 #ifdef HAVE_SSL
40 #include <openssl/crypto.h>
41 #include <openssl/x509.h>
42 #include <openssl/pem.h>
43 #include <openssl/ssl.h>
44 #include <openssl/err.h>
45 #endif
46 
47 #include "nbsmtp.h"
48 #include "util.h"
49 #include "base64.h"
50 #include "servinfo.h"
51 #include "smtp.h"
52 #include "fdutil.h"
53 #include "original.h"
54 #ifdef HAVE_SSL
55 #include "hmac_md5.h"
56 #endif
57 
58 /**
59  * \brief nbSMTP authors
60  */
61 const char *const authors[] = {"Copyright (C) 2004 Fernando J. Pereda <ferdy@ferdyx.org>",
62 	"Copyright (C) 2004 Ricardo Cervera Navarro <ricardo@zonasiete.org>",
63 	"Copyright (C) 2000-2001 David Rysdam",NULL};
64 
65 /**
66  * \brief Send the mail body
67  *
68  * \param[in] serverinfo
69  * \return 1 in command succes and -1 in case of error
70  */
nbsmtp_data_body(servinfo_t * serverinfo)71 int nbsmtp_data_body(servinfo_t *serverinfo)
72 {
73 	int c;				/* Current read data	*/
74 	int last = -1;			/* Last read data	*/
75 	bool_t more_data = True;	/* Continue bufferind/sending ? */
76 	char buf[BUFSIZ];		/* Temporal buffer to store contents of stdin */
77 	int i = 0;			/* Index to store chars in the buffer */
78 
79 	while (more_data)
80 	{
81 		c = fgetc(stdin);
82 
83 		if ( c != EOF )
84 		{
85 			/* Convert \n -> \r\n */
86 			if (c=='\n' && last!='\r')
87 			{
88 				buf[i++] = '\r';
89 			}
90 			/* Convert \n. -> \n.. */
91 			else if (c=='.' && last=='\n')
92 			{
93 				buf[i++] = '.';
94 			}
95 
96 
97 			/*
98 			 * Check if we reached the end of the buffer, write
99 			 * current character (c) and then check bounds again
100 			 */
101 			NBSMTP_BODY_BUF_SEND();
102 			buf[i++] = (char)c;
103 			NBSMTP_BODY_BUF_SEND();
104 
105 			/* Finally save the last char in order to compare */
106 			last = c;
107 		}
108 		else
109 		{
110 			/*
111 			 * End of stream, truncate buffer and write it to
112 			 * the socket. Finally, break the while
113 			 */
114 			buf[i] = '\0';
115 			fd_puts(serverinfo,buf,i);
116 			more_data = False;
117 		}
118 	}
119 
120 	return 1;
121 }
122 
123 /**
124  * \brief Send the mail headers
125  *
126  * \param[in] serverinfo	Pointer to a servinfo_t struct with the server information
127  * \param[in] msg		Pointer to the message
128  * \return 1 in command success and -1 in case of error
129  */
nbsmtp_data(servinfo_t * serverinfo,string_t * msg)130 int nbsmtp_data(servinfo_t *serverinfo, string_t *msg)
131 {
132 	char *local_out_buf;
133 	char local_in_buf[BUF_SIZE];
134 	char *msg_tmp;
135 
136 	asprintf(&local_out_buf,"%s","DATA");
137 
138 	if(smtp_write(serverinfo, local_out_buf)<1)
139 	{
140 		log_msg(LOG_ERR,"Error writting DATA command to the socket");
141 		return -1;
142 	}
143 
144 	free(local_out_buf);
145 
146 	/* Here the server should answer 354 so we check smtp_read to return '3' */
147 	if (smtp_read(serverinfo,local_in_buf)!=3)
148 	{
149 		log_msg(LOG_ERR,"An error ocurred after sending the DATA command");
150 		log_msg(LOG_ERR,"Server said: '%s'",smtp_last_message());
151 
152 		return -1;
153 	}
154 
155 	if (nbsmtp_header(serverinfo)<0)
156 	{
157 		return -1;
158 	}
159 
160 	msg_tmp = (char *) strstr(msg->str, "\n\n");
161 	msg_tmp += 2;
162 	*msg_tmp = '\0';
163 
164 	if ( smtp_write_data(serverinfo, msg->str, strlen(msg->str)) != 0 )
165 	{
166 		log_msg(LOG_ERR,"Error sending mail after DATA command");
167 		return -1;
168 	}
169 
170 	if (nbsmtp_data_body(serverinfo)<1)
171 	{
172 		log_msg(LOG_ERR,"Error sending mail body");
173 
174 		return -1;
175 	}
176 
177 	asprintf(&local_out_buf,"\r\n.\r\n");
178 
179 	if (fd_puts(serverinfo,local_out_buf,strlen(local_out_buf))<1)
180 	{
181 		return -1;
182 	}
183 
184 	free(local_out_buf);
185 
186 	/* Read last 221: Ok queued as... */
187 	if (!smtp_okay(serverinfo))
188 	{
189 		log_msg(LOG_ERR,"Error terminating data. Server said: '%s'.",smtp_last_message());
190 		return -1;
191 	}
192 
193 	return 1;
194 }
195 
196 /**
197  * \brief Send received: by header
198  *
199  * \param[in] serverinfo Struct with info about the connection
200  * \return < 0 in case of error > 0 otherwise
201  */
nbsmtp_header(servinfo_t * serverinfo)202 int nbsmtp_header(servinfo_t *serverinfo)
203 {
204 	char local_tmp_buf[BUF_SIZE];
205 	char *local_out_buf;
206 #ifdef HAVE_SSL
207 	char *tls_header_buf;
208 	int cipher_bits, algo_bits;
209 #endif
210 
211 	arpadate(local_tmp_buf);
212 
213 #ifdef HAVE_SSL
214 	if (serverinfo->using_tls)
215 	{
216 		cipher_bits = SSL_get_cipher_bits(serverinfo->ssl,&algo_bits);
217 		asprintf(&tls_header_buf,"\r\n\t(using %s with cipher %s (%d/%d bits))",
218 				SSL_get_cipher_version(serverinfo->ssl),
219 				SSL_get_cipher_name(serverinfo->ssl),
220 				cipher_bits,algo_bits);
221 	}
222 #endif
223 
224 	asprintf(&local_out_buf,"Received: by %s (nbSMTP-%s) for uid %d%s\r\n\t%s; %s",
225 			serverinfo->domain,PACKAGE_VERSION,getuid(),
226 #ifdef HAVE_SSL
227 			(serverinfo->using_tls ? tls_header_buf : ""),
228 #else
229 			"",
230 #endif
231 			serverinfo->fromaddr,local_tmp_buf);
232 
233 	if (smtp_write(serverinfo,local_out_buf)<1)
234 	{
235 		log_msg(LOG_ERR,"Error writting Received: by header to the socket");
236 		return -1;
237 	}
238 
239 	free(local_out_buf);
240 #ifdef HAVE_SSL
241 	if (serverinfo->using_tls)
242 	{
243 		free(tls_header_buf);
244 	}
245 #endif
246 
247 	return 1;
248 }
249 
250 /**
251  * \brief Send Recipients to the server
252  *
253  * \param[in]		serverinfo	Pointer to a servinfo_t struct with the server information
254  * \param[in,out]	rcpts		Pointer to a string_t matrix with all recipients
255  * \return 1 in command success and -1 in case of error
256  */
nbsmtp_rcpts(servinfo_t * serverinfo,string_t * rcpts)257 int nbsmtp_rcpts(servinfo_t *serverinfo, string_t *rcpts)
258 {
259 	char *local_out_buf;
260 	int i;
261 	bool_t validrcpts = False;
262 
263 	for( i=0 ; i<serverinfo->num_rcpts ; i++ )
264 	{
265 		asprintf(&local_out_buf,"RCPT TO:<%s>", rcpts[i].str);
266 
267 		if (smtp_write(serverinfo, local_out_buf)<1)
268 		{
269 			log_msg(LOG_ERR,"Error writting RCPT command to the socket");
270 			return -1;
271 		}
272 
273 		free(local_out_buf);
274 
275 		if (!smtp_okay(serverinfo))
276 		{
277 			log_msg(LOG_WARNING,"Recipient REJECTED [%s] -> '%s'",
278 					rcpts[i].str,smtp_last_message());
279 		}
280 		else
281 		{
282 			log_msg(LOG_INFO,"Recipient accepted [%s]",rcpts[i].str);
283 
284 			validrcpts = True;
285 		}
286 
287 		str_free(&rcpts[i]);
288 	}
289 
290 	/* We check for, at least, one valid recipient */
291 	if (validrcpts==False)
292 	{
293 		log_msg(LOG_ERR,"No recipients were accepted by the server, exiting");
294 		return -1;
295 	}
296 
297 	return 1;
298 }
299 
300 /**
301  * \brief Sends EHLO SMTP command
302  *
303  * \param[in] serverinfo Pointer to a servinfo_t struct with the server information
304  * \return 1 in command success, 0 in case of error
305  */
nbsmtp_ehlo(servinfo_t * serverinfo)306 int nbsmtp_ehlo(servinfo_t *serverinfo)
307 {
308 	char *local_out_buf;
309 
310 	asprintf(&local_out_buf,"EHLO %s",serverinfo->domain);
311 
312 	if (smtp_write(serverinfo,local_out_buf)<1)
313 	{
314 		log_msg(LOG_ERR,"Error writing EHLO command to the socket");
315 		free(local_out_buf);
316 		return 0;
317 	}
318 
319 	free(local_out_buf);
320 
321 	if(!smtp_okay(serverinfo))
322 	{
323 		log_msg(LOG_ERR,"EHLO command failed");
324 		return 0;
325 	}
326 
327 	return 1;
328 }
329 
330 /**
331  * \brief Sends HELO SMTP command
332  *
333  * \param[in] serverinfo Pointer to a servinfo_t struct with the server information
334  * \return 1 in command success, 0 in case of error
335  */
nbsmtp_helo(servinfo_t * serverinfo)336 int nbsmtp_helo(servinfo_t *serverinfo)
337 {
338 	char *local_out_buf;
339 
340 	asprintf(&local_out_buf,"HELO %s",serverinfo->domain);
341 
342 	if(smtp_write(serverinfo,local_out_buf)<1)
343 	{
344 		log_msg(LOG_ERR,"Error writting HELO command to the socket");
345 		free(local_out_buf);
346 		return 0;
347 	}
348 
349 	free(local_out_buf);
350 
351 	if(!smtp_okay(serverinfo))
352 	{
353 		log_msg(LOG_ERR,"HELO command failed");
354 		return 0;
355 	}
356 
357 	return 1;
358 }
359 
360 /**
361  * \brief Sends AUTH command to the server
362  *
363  * \param[in] serverinfo A pointer to a servinfo_t struct with the needed info
364  * \return Returns 0 in case of error and 1 in command success
365  */
nbsmtp_auth(servinfo_t * serverinfo)366 int nbsmtp_auth(servinfo_t *serverinfo)
367 {
368 	char local_in_buf[BUF_SIZE];
369 	char local_tmp_buf[BUF_SIZE];
370 	char *local_out_buf;
371 	int len;
372 	int i;
373 
374 	memset(local_tmp_buf,0,sizeof(local_tmp_buf));
375 
376 	if (serverinfo->auth_mech==SASL_LOGIN)
377 	{
378 		to64frombits((unsigned char*)local_tmp_buf,
379 				(const unsigned char *)serverinfo->auth_user,
380 				strlen(serverinfo->auth_user));
381 
382 		asprintf(&local_out_buf,"AUTH LOGIN %s",local_tmp_buf);
383 
384 		if (smtp_write(serverinfo,local_out_buf)<1)
385 		{
386 			log_msg(LOG_ERR,"Error writting AUTH command to the socket");
387 			return 0;
388 		}
389 
390 		free(local_out_buf);
391 
392 		if (smtp_read(serverinfo,local_in_buf)!=3)
393 		{
394 			log_msg(LOG_ERR,"The server rejected the authentication method");
395 			log_msg(LOG_ERR,"Server said: '%s'",smtp_last_message());
396 
397 			return 0;
398 		}
399 
400 		memset(local_tmp_buf,0,sizeof(local_tmp_buf));
401 
402 		to64frombits((unsigned char *)local_tmp_buf,
403 				(const unsigned char *)serverinfo->auth_pass,
404 				strlen(serverinfo->auth_pass));
405 
406 		asprintf(&local_out_buf,"%s",local_tmp_buf);
407 
408 		if (smtp_write(serverinfo,local_out_buf)<1)
409 		{
410 			log_msg(LOG_ERR,"Error writting the password to the socket");
411 			return 0;
412 		}
413 
414 		free(local_out_buf);
415 
416 		if (!smtp_okay(serverinfo))
417 		{
418 			log_msg(LOG_ERR,"The password wasn't accepted");
419 			log_msg(LOG_ERR,"Server said: '%s'",smtp_last_message());
420 
421 			return 0;
422 		}
423 	}
424 	else if(serverinfo->auth_mech==SASL_PLAIN)
425 	{
426 		asprintf(&local_out_buf,"^%s^%s",serverinfo->auth_user,serverinfo->auth_pass);
427 		len = strlen(local_out_buf);
428 
429 		for ( i = len-1 ; i >= 0 ; i-- )
430 		{
431 			if (local_out_buf[i]=='^')
432 			{
433 				local_out_buf[i]='\0';
434 			}
435 		}
436 
437 		to64frombits((unsigned char *)local_tmp_buf,(const unsigned char *)local_out_buf,len);
438 
439 		free(local_out_buf);
440 
441 		asprintf(&local_out_buf,"AUTH PLAIN %s",local_tmp_buf);
442 
443 		if (smtp_write(serverinfo,local_out_buf)<1)
444 		{
445 			log_msg(LOG_ERR,"Error writting AUTH PLAIN command to the socket");
446 			return 0;
447 		}
448 
449 		free(local_out_buf);
450 
451 		if (!smtp_okay(serverinfo))
452 		{
453 			log_msg(LOG_ERR,"Error, the authentication failed");
454 			log_msg(LOG_ERR,"Server said: '%s'",smtp_last_message());
455 
456 			return 0;
457 		}
458 	}
459 #ifdef HAVE_SSL
460 	else if (serverinfo->auth_mech==SASL_CRAMMD5)
461 	{
462 		/*
463 		 * This code has been adapted from a code by Oliver Hitz <oliver@net-track.ch>
464 		 */
465 		unsigned char challenge[BUFSIZ];
466 		unsigned char digest[16];
467 		unsigned char digasc[33];
468 		char *decoded;
469 		unsigned char encoded[BUFSIZ];
470 		unsigned char greeting[BUFSIZ];
471 		static char hextab[] = "0123456789abcdef";
472 
473 		local_out_buf = (char *)strdup("AUTH CRAM-MD5");
474 
475 		if (smtp_write(serverinfo,local_out_buf)<1)
476 		{
477 			log_msg(LOG_ERR,"Error writting AUTH CRAM-MD5 command to the socket");
478 			return 0;
479 		}
480 
481 		free(local_out_buf);
482 
483 		if (smtp_read(serverinfo,local_in_buf)!=3)
484 		{
485 			/* Server rejected the auth method */
486 			log_msg(LOG_ERR,"The server rejected the authentication method");
487 			log_msg(LOG_ERR,"Server said: '%s'",smtp_last_message());
488 
489 			return 0;
490 		}
491 
492 		/* First get the greeting and decode the challenge */
493 		strncpy((char *)greeting,smtp_last_message(),sizeof(greeting));
494 		i = from64tobits((char *)challenge,(char *)greeting);
495 
496 		/* Make sure challenge is '\0' ended, since from64tobits doesn't do it itself */
497 		challenge[i] = '\0';
498 
499 		/* Perform the keyed-hashing algorithm */
500 		hmac_md5(challenge,strlen((char *)challenge),
501 				(unsigned char *)serverinfo->auth_pass,
502 				strlen(serverinfo->auth_pass),digest);
503 
504 		/* Standard hexadecimal conversion */
505 		for (i = 0; i < 16; i++)
506 		{
507 			digasc[2*i] = hextab[digest[i] >> 4];
508 			digasc[2*i+1] = hextab[digest[i] & 0xf];
509 		}
510 
511 		/* Always NULL-terminate digasc to avoid problems */
512 		digasc[32] = '\0';
513 
514 		/* Create and encode the challenge response */
515 		asprintf(&decoded,"%s %s",serverinfo->auth_user,digasc);
516 		to64frombits((unsigned char*)encoded,(const unsigned char*)decoded,strlen(decoded));
517 		free(decoded);
518 
519 		if (smtp_write(serverinfo,(char *)encoded)<1)
520 		{
521 			log_msg(LOG_ERR,"Error writting auth string to the socket");
522 		}
523 
524 		if (!smtp_okay(serverinfo))
525 		{
526 			log_msg(LOG_ERR,"Error, the authentication failed");
527 			log_msg(LOG_ERR,"Server said: '%s'",smtp_last_message());
528 
529 			return 0;
530 		}
531 	}
532 #endif
533 
534 	/* Command succeded so tell the log */
535 	log_msg(LOG_INFO,"Authentication succeded [%s]",serverinfo->auth_user);
536 
537 	return 1;
538 }
539 
540 #ifdef HAVE_SSL
541 /**
542  * \brief Inits SSL stuff
543  *
544  * \param[in,out] serverinfo A pointer to a servinfo_t struct with the needed info
545  * \return Returns -1 in case of error
546  */
nbsmtp_sslinit(servinfo_t * serverinfo)547 int nbsmtp_sslinit(servinfo_t *serverinfo)
548 {
549 	SSL_CTX *ctx;
550 	SSL_METHOD *method;
551 	X509 *server_cert;
552 
553 	SSL_load_error_strings();
554 	SSLeay_add_ssl_algorithms();
555 	method = SSLv23_client_method();
556 	ctx = SSL_CTX_new(method);
557 
558 	if (!ctx)
559 	{
560 		log_msg(LOG_ERR,"Couldn't initialize SSL\n");
561 		return -1;
562 	}
563 
564 	serverinfo->ssl = SSL_new(ctx);
565 
566 	if (!serverinfo->ssl)
567 	{
568 		log_msg(LOG_ERR,"SSL not working");
569 		return -1;
570 	}
571 
572 	SSL_set_fd(serverinfo->ssl,serverinfo->sockfd);
573 
574 	if (SSL_connect(serverinfo->ssl) < 0)
575 	{
576 		log_msg(LOG_ERR,"SSL_connect failed");
577 		return -1;
578 	}
579 
580 	server_cert = SSL_get_peer_certificate(serverinfo->ssl);
581 
582 	if (!server_cert)
583 	{
584 		log_msg(LOG_ERR,"We didn't get a server certificate. Exiting");
585 		return -1;
586 	}
587 
588 	serverinfo->using_tls = True;
589 
590 	X509_free(server_cert);
591 	SSL_CTX_free(ctx);
592 
593 	return 0;
594 }
595 
596 /**
597  * \brief Cleans up SSL stuff
598  *
599  * \param[in,out] serverinfo A pointer to a servinfo_t struct with the needed info
600  * \return Returns -1 in case of error
601  */
nbsmtp_sslexit(servinfo_t * serverinfo)602 int nbsmtp_sslexit(servinfo_t *serverinfo)
603 {
604 	if (serverinfo->use_tls == True && serverinfo->using_tls == True)
605 	{
606 		SSL_shutdown(serverinfo->ssl);
607 		SSL_free(serverinfo->ssl);
608 		ERR_free_strings();
609 		ERR_remove_state(0);
610 		EVP_cleanup();
611 #ifdef HAVE_CRYPTO_CLEANUP_ALL_EX_DATA
612 		CRYPTO_cleanup_all_ex_data();
613 #endif
614 	}
615 
616 	return 0;
617 }
618 #endif
619