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