/** * \file nbsmtp.c * \brief nbsmtp specific functions * * \author Fernando J. Pereda * \author Ricardo Cervera Navarro * * This is part of nbsmtp. nbsmtp is free software; * you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * nbsmtp is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with nbsmtp; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * See COPYING for details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_SSL #include #include #include #include #include #endif #include "nbsmtp.h" #include "util.h" #include "base64.h" #include "servinfo.h" #include "smtp.h" #include "fdutil.h" #include "original.h" #ifdef HAVE_SSL #include "hmac_md5.h" #endif /** * \brief nbSMTP authors */ const char *const authors[] = {"Copyright (C) 2004 Fernando J. Pereda ", "Copyright (C) 2004 Ricardo Cervera Navarro ", "Copyright (C) 2000-2001 David Rysdam",NULL}; /** * \brief Send the mail body * * \param[in] serverinfo * \return 1 in command succes and -1 in case of error */ int nbsmtp_data_body(servinfo_t *serverinfo) { int c; /* Current read data */ int last = -1; /* Last read data */ bool_t more_data = True; /* Continue bufferind/sending ? */ char buf[BUFSIZ]; /* Temporal buffer to store contents of stdin */ int i = 0; /* Index to store chars in the buffer */ while (more_data) { c = fgetc(stdin); if ( c != EOF ) { /* Convert \n -> \r\n */ if (c=='\n' && last!='\r') { buf[i++] = '\r'; } /* Convert \n. -> \n.. */ else if (c=='.' && last=='\n') { buf[i++] = '.'; } /* * Check if we reached the end of the buffer, write * current character (c) and then check bounds again */ NBSMTP_BODY_BUF_SEND(); buf[i++] = (char)c; NBSMTP_BODY_BUF_SEND(); /* Finally save the last char in order to compare */ last = c; } else { /* * End of stream, truncate buffer and write it to * the socket. Finally, break the while */ buf[i] = '\0'; fd_puts(serverinfo,buf,i); more_data = False; } } return 1; } /** * \brief Send the mail headers * * \param[in] serverinfo Pointer to a servinfo_t struct with the server information * \param[in] msg Pointer to the message * \return 1 in command success and -1 in case of error */ int nbsmtp_data(servinfo_t *serverinfo, string_t *msg) { char *local_out_buf; char local_in_buf[BUF_SIZE]; char *msg_tmp; asprintf(&local_out_buf,"%s","DATA"); if(smtp_write(serverinfo, local_out_buf)<1) { log_msg(LOG_ERR,"Error writting DATA command to the socket"); return -1; } free(local_out_buf); /* Here the server should answer 354 so we check smtp_read to return '3' */ if (smtp_read(serverinfo,local_in_buf)!=3) { log_msg(LOG_ERR,"An error ocurred after sending the DATA command"); log_msg(LOG_ERR,"Server said: '%s'",smtp_last_message()); return -1; } if (nbsmtp_header(serverinfo)<0) { return -1; } msg_tmp = (char *) strstr(msg->str, "\n\n"); msg_tmp += 2; *msg_tmp = '\0'; if ( smtp_write_data(serverinfo, msg->str, strlen(msg->str)) != 0 ) { log_msg(LOG_ERR,"Error sending mail after DATA command"); return -1; } if (nbsmtp_data_body(serverinfo)<1) { log_msg(LOG_ERR,"Error sending mail body"); return -1; } asprintf(&local_out_buf,"\r\n.\r\n"); if (fd_puts(serverinfo,local_out_buf,strlen(local_out_buf))<1) { return -1; } free(local_out_buf); /* Read last 221: Ok queued as... */ if (!smtp_okay(serverinfo)) { log_msg(LOG_ERR,"Error terminating data. Server said: '%s'.",smtp_last_message()); return -1; } return 1; } /** * \brief Send received: by header * * \param[in] serverinfo Struct with info about the connection * \return < 0 in case of error > 0 otherwise */ int nbsmtp_header(servinfo_t *serverinfo) { char local_tmp_buf[BUF_SIZE]; char *local_out_buf; #ifdef HAVE_SSL char *tls_header_buf; int cipher_bits, algo_bits; #endif arpadate(local_tmp_buf); #ifdef HAVE_SSL if (serverinfo->using_tls) { cipher_bits = SSL_get_cipher_bits(serverinfo->ssl,&algo_bits); asprintf(&tls_header_buf,"\r\n\t(using %s with cipher %s (%d/%d bits))", SSL_get_cipher_version(serverinfo->ssl), SSL_get_cipher_name(serverinfo->ssl), cipher_bits,algo_bits); } #endif asprintf(&local_out_buf,"Received: by %s (nbSMTP-%s) for uid %d%s\r\n\t%s; %s", serverinfo->domain,PACKAGE_VERSION,getuid(), #ifdef HAVE_SSL (serverinfo->using_tls ? tls_header_buf : ""), #else "", #endif serverinfo->fromaddr,local_tmp_buf); if (smtp_write(serverinfo,local_out_buf)<1) { log_msg(LOG_ERR,"Error writting Received: by header to the socket"); return -1; } free(local_out_buf); #ifdef HAVE_SSL if (serverinfo->using_tls) { free(tls_header_buf); } #endif return 1; } /** * \brief Send Recipients to the server * * \param[in] serverinfo Pointer to a servinfo_t struct with the server information * \param[in,out] rcpts Pointer to a string_t matrix with all recipients * \return 1 in command success and -1 in case of error */ int nbsmtp_rcpts(servinfo_t *serverinfo, string_t *rcpts) { char *local_out_buf; int i; bool_t validrcpts = False; for( i=0 ; inum_rcpts ; i++ ) { asprintf(&local_out_buf,"RCPT TO:<%s>", rcpts[i].str); if (smtp_write(serverinfo, local_out_buf)<1) { log_msg(LOG_ERR,"Error writting RCPT command to the socket"); return -1; } free(local_out_buf); if (!smtp_okay(serverinfo)) { log_msg(LOG_WARNING,"Recipient REJECTED [%s] -> '%s'", rcpts[i].str,smtp_last_message()); } else { log_msg(LOG_INFO,"Recipient accepted [%s]",rcpts[i].str); validrcpts = True; } str_free(&rcpts[i]); } /* We check for, at least, one valid recipient */ if (validrcpts==False) { log_msg(LOG_ERR,"No recipients were accepted by the server, exiting"); return -1; } return 1; } /** * \brief Sends EHLO SMTP command * * \param[in] serverinfo Pointer to a servinfo_t struct with the server information * \return 1 in command success, 0 in case of error */ int nbsmtp_ehlo(servinfo_t *serverinfo) { char *local_out_buf; asprintf(&local_out_buf,"EHLO %s",serverinfo->domain); if (smtp_write(serverinfo,local_out_buf)<1) { log_msg(LOG_ERR,"Error writing EHLO command to the socket"); free(local_out_buf); return 0; } free(local_out_buf); if(!smtp_okay(serverinfo)) { log_msg(LOG_ERR,"EHLO command failed"); return 0; } return 1; } /** * \brief Sends HELO SMTP command * * \param[in] serverinfo Pointer to a servinfo_t struct with the server information * \return 1 in command success, 0 in case of error */ int nbsmtp_helo(servinfo_t *serverinfo) { char *local_out_buf; asprintf(&local_out_buf,"HELO %s",serverinfo->domain); if(smtp_write(serverinfo,local_out_buf)<1) { log_msg(LOG_ERR,"Error writting HELO command to the socket"); free(local_out_buf); return 0; } free(local_out_buf); if(!smtp_okay(serverinfo)) { log_msg(LOG_ERR,"HELO command failed"); return 0; } return 1; } /** * \brief Sends AUTH command to the server * * \param[in] serverinfo A pointer to a servinfo_t struct with the needed info * \return Returns 0 in case of error and 1 in command success */ int nbsmtp_auth(servinfo_t *serverinfo) { char local_in_buf[BUF_SIZE]; char local_tmp_buf[BUF_SIZE]; char *local_out_buf; int len; int i; memset(local_tmp_buf,0,sizeof(local_tmp_buf)); if (serverinfo->auth_mech==SASL_LOGIN) { to64frombits((unsigned char*)local_tmp_buf, (const unsigned char *)serverinfo->auth_user, strlen(serverinfo->auth_user)); asprintf(&local_out_buf,"AUTH LOGIN %s",local_tmp_buf); if (smtp_write(serverinfo,local_out_buf)<1) { log_msg(LOG_ERR,"Error writting AUTH command to the socket"); return 0; } free(local_out_buf); if (smtp_read(serverinfo,local_in_buf)!=3) { log_msg(LOG_ERR,"The server rejected the authentication method"); log_msg(LOG_ERR,"Server said: '%s'",smtp_last_message()); return 0; } memset(local_tmp_buf,0,sizeof(local_tmp_buf)); to64frombits((unsigned char *)local_tmp_buf, (const unsigned char *)serverinfo->auth_pass, strlen(serverinfo->auth_pass)); asprintf(&local_out_buf,"%s",local_tmp_buf); if (smtp_write(serverinfo,local_out_buf)<1) { log_msg(LOG_ERR,"Error writting the password to the socket"); return 0; } free(local_out_buf); if (!smtp_okay(serverinfo)) { log_msg(LOG_ERR,"The password wasn't accepted"); log_msg(LOG_ERR,"Server said: '%s'",smtp_last_message()); return 0; } } else if(serverinfo->auth_mech==SASL_PLAIN) { asprintf(&local_out_buf,"^%s^%s",serverinfo->auth_user,serverinfo->auth_pass); len = strlen(local_out_buf); for ( i = len-1 ; i >= 0 ; i-- ) { if (local_out_buf[i]=='^') { local_out_buf[i]='\0'; } } to64frombits((unsigned char *)local_tmp_buf,(const unsigned char *)local_out_buf,len); free(local_out_buf); asprintf(&local_out_buf,"AUTH PLAIN %s",local_tmp_buf); if (smtp_write(serverinfo,local_out_buf)<1) { log_msg(LOG_ERR,"Error writting AUTH PLAIN command to the socket"); return 0; } free(local_out_buf); if (!smtp_okay(serverinfo)) { log_msg(LOG_ERR,"Error, the authentication failed"); log_msg(LOG_ERR,"Server said: '%s'",smtp_last_message()); return 0; } } #ifdef HAVE_SSL else if (serverinfo->auth_mech==SASL_CRAMMD5) { /* * This code has been adapted from a code by Oliver Hitz */ unsigned char challenge[BUFSIZ]; unsigned char digest[16]; unsigned char digasc[33]; char *decoded; unsigned char encoded[BUFSIZ]; unsigned char greeting[BUFSIZ]; static char hextab[] = "0123456789abcdef"; local_out_buf = (char *)strdup("AUTH CRAM-MD5"); if (smtp_write(serverinfo,local_out_buf)<1) { log_msg(LOG_ERR,"Error writting AUTH CRAM-MD5 command to the socket"); return 0; } free(local_out_buf); if (smtp_read(serverinfo,local_in_buf)!=3) { /* Server rejected the auth method */ log_msg(LOG_ERR,"The server rejected the authentication method"); log_msg(LOG_ERR,"Server said: '%s'",smtp_last_message()); return 0; } /* First get the greeting and decode the challenge */ strncpy((char *)greeting,smtp_last_message(),sizeof(greeting)); i = from64tobits((char *)challenge,(char *)greeting); /* Make sure challenge is '\0' ended, since from64tobits doesn't do it itself */ challenge[i] = '\0'; /* Perform the keyed-hashing algorithm */ hmac_md5(challenge,strlen((char *)challenge), (unsigned char *)serverinfo->auth_pass, strlen(serverinfo->auth_pass),digest); /* Standard hexadecimal conversion */ for (i = 0; i < 16; i++) { digasc[2*i] = hextab[digest[i] >> 4]; digasc[2*i+1] = hextab[digest[i] & 0xf]; } /* Always NULL-terminate digasc to avoid problems */ digasc[32] = '\0'; /* Create and encode the challenge response */ asprintf(&decoded,"%s %s",serverinfo->auth_user,digasc); to64frombits((unsigned char*)encoded,(const unsigned char*)decoded,strlen(decoded)); free(decoded); if (smtp_write(serverinfo,(char *)encoded)<1) { log_msg(LOG_ERR,"Error writting auth string to the socket"); } if (!smtp_okay(serverinfo)) { log_msg(LOG_ERR,"Error, the authentication failed"); log_msg(LOG_ERR,"Server said: '%s'",smtp_last_message()); return 0; } } #endif /* Command succeded so tell the log */ log_msg(LOG_INFO,"Authentication succeded [%s]",serverinfo->auth_user); return 1; } #ifdef HAVE_SSL /** * \brief Inits SSL stuff * * \param[in,out] serverinfo A pointer to a servinfo_t struct with the needed info * \return Returns -1 in case of error */ int nbsmtp_sslinit(servinfo_t *serverinfo) { SSL_CTX *ctx; SSL_METHOD *method; X509 *server_cert; SSL_load_error_strings(); SSLeay_add_ssl_algorithms(); method = SSLv23_client_method(); ctx = SSL_CTX_new(method); if (!ctx) { log_msg(LOG_ERR,"Couldn't initialize SSL\n"); return -1; } serverinfo->ssl = SSL_new(ctx); if (!serverinfo->ssl) { log_msg(LOG_ERR,"SSL not working"); return -1; } SSL_set_fd(serverinfo->ssl,serverinfo->sockfd); if (SSL_connect(serverinfo->ssl) < 0) { log_msg(LOG_ERR,"SSL_connect failed"); return -1; } server_cert = SSL_get_peer_certificate(serverinfo->ssl); if (!server_cert) { log_msg(LOG_ERR,"We didn't get a server certificate. Exiting"); return -1; } serverinfo->using_tls = True; X509_free(server_cert); SSL_CTX_free(ctx); return 0; } /** * \brief Cleans up SSL stuff * * \param[in,out] serverinfo A pointer to a servinfo_t struct with the needed info * \return Returns -1 in case of error */ int nbsmtp_sslexit(servinfo_t *serverinfo) { if (serverinfo->use_tls == True && serverinfo->using_tls == True) { SSL_shutdown(serverinfo->ssl); SSL_free(serverinfo->ssl); ERR_free_strings(); ERR_remove_state(0); EVP_cleanup(); #ifdef HAVE_CRYPTO_CLEANUP_ALL_EX_DATA CRYPTO_cleanup_all_ex_data(); #endif } return 0; } #endif