/* smtp_open() -- Open connection to a remote SMTP listener */ int smtp_open(char *host, int port) { #ifdef INET6 struct addrinfo hints, *ai0, *ai; char servname[NI_MAXSERV]; int s; #else struct sockaddr_in name; struct hostent *hent; int i, s, namelen; #endif #ifdef HAVE_SSL int err; char buf[(BUF_SZ + 1)]; /* Init SSL stuff */ SSL_CTX *ctx; SSL_METHOD *meth; X509 *server_cert; SSL_load_error_strings(); SSLeay_add_ssl_algorithms(); meth=SSLv23_client_method(); ctx = SSL_CTX_new(meth); if(!ctx) { log_event(LOG_ERR, "No SSL support initiated\n"); return(-1); } if(use_cert == True) { if(SSL_CTX_use_certificate_chain_file(ctx, tls_cert) <= 0) { perror("Use certfile"); return(-1); } if(SSL_CTX_use_PrivateKey_file(ctx, tls_cert, SSL_FILETYPE_PEM) <= 0) { perror("Use PrivateKey"); return(-1); } if(!SSL_CTX_check_private_key(ctx)) { log_event(LOG_ERR, "Private key does not match the certificate public key\n"); return(-1); } } #endif #ifdef INET6 memset(&hints, 0, sizeof(hints)); hints.ai_family = p_family; hints.ai_socktype = SOCK_STREAM; snprintf(servname, sizeof(servname), "%d", port); /* Check we can reach the host */ if (getaddrinfo(host, servname, &hints, &ai0)) { log_event(LOG_ERR, "Unable to locate %s", host); return(-1); } for (ai = ai0; ai; ai = ai->ai_next) { /* Create a socket for the connection */ s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (s < 0) { continue; } if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0) { s = -1; continue; } break; } if(s < 0) { log_event (LOG_ERR, "Unable to connect to \"%s\" port %d.\n", host, port); return(-1); } #else /* Check we can reach the host */ if((hent = gethostbyname(host)) == (struct hostent *)NULL) { log_event(LOG_ERR, "Unable to locate %s", host); return(-1); } if(hent->h_length > sizeof(hent->h_addr)) { log_event(LOG_ERR, "Buffer overflow in gethostbyname()"); return(-1); } /* Create a socket for the connection */ if((s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { log_event(LOG_ERR, "Unable to create a socket"); return(-1); } for (i = 0; ; ++i) { if (!hent->h_addr_list[i]) { log_event(LOG_ERR, "Unable to connect to %s:%d", host, port); return(-1); } /* This SHOULD already be in Network Byte Order from gethostbyname() */ name.sin_addr.s_addr = ((struct in_addr *)(hent->h_addr_list[i]))->s_addr; name.sin_family = hent->h_addrtype; name.sin_port = htons(port); namelen = sizeof(struct sockaddr_in); if(connect(s, (struct sockaddr *)&name, namelen) < 0) continue; break; } #endif #ifdef HAVE_SSL if(use_tls == True) { log_event(LOG_INFO, "Creating SSL connection to host"); if (use_starttls == True) { use_tls=False; /* need to write plain text for a while */ if (smtp_okay(s, buf)) { smtp_write(s, "EHLO %s", hostname); if (smtp_okay(s, buf)) { smtp_write(s, "STARTTLS"); /* assume STARTTLS regardless */ if (!smtp_okay(s, buf)) { log_event(LOG_ERR, "STARTTLS not working"); return(-1); } } else { log_event(LOG_ERR, "Invalid response: %s (%s)", buf, hostname); } } else { log_event(LOG_ERR, "Invalid response SMTP Server (STARTTLS)"); return(-1); } use_tls=True; /* now continue as normal for SSL */ } ssl = SSL_new(ctx); if(!ssl) { log_event(LOG_ERR, "SSL not working"); return(-1); } SSL_set_fd(ssl, s); err = SSL_connect(ssl); if(err < 0) { perror("SSL_connect"); return(-1); } if(log_level > 0 || 1) { log_event(LOG_INFO, "SSL connection using %s", SSL_get_cipher(ssl)); } server_cert = SSL_get_peer_certificate(ssl); if(!server_cert) { return(-1); } X509_free(server_cert); /* TODO: Check server cert if changed! */ } #endif return(s); }
/* ssmtp() -- send the message (exactly one) from stdin to the mailhub SMTP port */ int ssmtp(char *argv[]) { char b[(BUF_SZ + 2)], *buf = b+1, *p, *q; #ifdef MD5AUTH char challenge[(BUF_SZ + 1)]; #endif struct passwd *pw; int i, sock; uid_t uid; bool_t minus_v_save, leadingdot, linestart = True; int timeout = 0; int bufsize = sizeof(b)-1; b[0] = '.'; outbytes = 0; ht = &headers; uid = getuid(); if((pw = getpwuid(uid)) == (struct passwd *)NULL) { die("Could not find password entry for UID %d", uid); } get_arpadate(arpadate); if(read_config() == False) { log_event(LOG_INFO, "%s not found", config_file); } if((p = strtok(pw->pw_gecos, ";,"))) { if((gecos = strdup(p)) == (char *)NULL) { die("ssmtp() -- strdup() failed"); } } revaliases(pw); /* revaliases() may have defined this */ if(uad == (char *)NULL) { uad = append_domain(pw->pw_name); } rt = &rcpt_list; header_parse(stdin); #if 1 /* With FromLineOverride=YES set, try to recover sane MAIL FROM address */ uad = append_domain(uad); #endif from = from_format(uad, override_from); /* Now to the delivery of the message */ (void)signal(SIGALRM, (void(*)())handler); /* Catch SIGALRM */ (void)alarm((unsigned) MAXWAIT); /* Set initial timer */ if(setjmp(TimeoutJmpBuf) != 0) { /* Then the timer has gone off and we bail out */ die("Connection lost in middle of processing"); } if((sock = smtp_open(mailhost, port)) == -1) { die("Cannot open %s:%d", mailhost, port); } else if (use_starttls == False) /* no initial response after STARTTLS */ { if(smtp_okay(sock, buf) == False) die("Invalid response SMTP server"); } /* If user supplied username and password, then try ELHO */ if(auth_user) { outbytes += smtp_write(sock, "EHLO %s", hostname); } else { outbytes += smtp_write(sock, "HELO %s", hostname); } (void)alarm((unsigned) MEDWAIT); if(smtp_okay(sock, buf) == False) { die("%s (%s)", buf, hostname); } /* Try to log in if username was supplied */ if(auth_user) { #ifdef MD5AUTH if(auth_pass == (char *)NULL) { auth_pass = strdup(""); } if(auth_method && strcasecmp(auth_method, "cram-md5") == 0) { outbytes += smtp_write(sock, "AUTH CRAM-MD5"); (void)alarm((unsigned) MEDWAIT); if(smtp_read(sock, buf) != 3) { die("Server rejected AUTH CRAM-MD5 (%s)", buf); } strncpy(challenge, strchr(buf,' ') + 1, sizeof(challenge)); memset(buf, 0, bufsize); crammd5(challenge, auth_user, auth_pass, buf); } else { #endif memset(buf, 0, bufsize); to64frombits(buf, auth_user, strlen(auth_user)); if (use_oldauth) { outbytes += smtp_write(sock, "AUTH LOGIN %s", buf); } else { outbytes += smtp_write(sock, "AUTH LOGIN"); (void)alarm((unsigned) MEDWAIT); if(smtp_read(sock, buf) != 3) { die("Server didn't like our AUTH LOGIN (%s)", buf); } /* we assume server asked us for Username */ memset(buf, 0, bufsize); to64frombits(buf, auth_user, strlen(auth_user)); outbytes += smtp_write(sock, buf); } (void)alarm((unsigned) MEDWAIT); if(smtp_read(sock, buf) != 3) { die("Server didn't accept AUTH LOGIN (%s)", buf); } memset(buf, 0, bufsize); to64frombits(buf, auth_pass, strlen(auth_pass)); #ifdef MD5AUTH } #endif /* We do NOT want the password output to STDERR * even base64 encoded.*/ minus_v_save = minus_v; minus_v = False; outbytes += smtp_write(sock, "%s", buf); minus_v = minus_v_save; (void)alarm((unsigned) MEDWAIT); if(smtp_okay(sock, buf) == False) { die("Authorization failed (%s)", buf); } } /* Send "MAIL FROM:" line */ outbytes += smtp_write(sock, "MAIL FROM:<%s>", uad); (void)alarm((unsigned) MEDWAIT); if(smtp_okay(sock, buf) == 0) { die("%s", buf); } /* Send all the To: adresses */ /* Either we're using the -t option, or we're using the arguments */ if(minus_t) { if(rcpt_list.next == (rcpt_t *)NULL) { die("No recipients specified although -t option used"); } rt = &rcpt_list; while(rt->next) { p = rcpt_remap(rt->string); outbytes += smtp_write(sock, "RCPT TO:<%s>", p); (void)alarm((unsigned)MEDWAIT); if(smtp_okay(sock, buf) == 0) { die("RCPT TO:<%s> (%s)", p, buf); } rt = rt->next; } } else { for(i = 1; (argv[i] != NULL); i++) { p = strtok(argv[i], ","); while(p) { /* RFC822 Address -> "foo@bar" */ q = rcpt_remap(addr_parse(p)); outbytes += smtp_write(sock, "RCPT TO:<%s>", q); (void)alarm((unsigned) MEDWAIT); if(smtp_okay(sock, buf) == 0) { die("RCPT TO:<%s> (%s)", q, buf); } p = strtok(NULL, ","); } } } /* Send DATA */ outbytes += smtp_write(sock, "DATA"); (void)alarm((unsigned) MEDWAIT); if(smtp_read(sock, buf) != 3) { /* Oops, we were expecting "354 send your data" */ die("%s", buf); } outbytes += smtp_write(sock, "Received: by %s (sSMTP sendmail emulation); %s", hostname, arpadate); if(have_from == False) { outbytes += smtp_write(sock, "From: %s", from); } if(have_date == False) { outbytes += smtp_write(sock, "Date: %s", arpadate); } #ifdef HASTO_OPTION if(have_to == False) { outbytes += smtp_write(sock, "To: postmaster"); } #endif ht = &headers; while(ht->next) { outbytes += smtp_write(sock, "%s", ht->string); ht = ht->next; } (void)alarm((unsigned) MEDWAIT); /* End of headers, start body */ outbytes += smtp_write(sock, ""); /*prevent blocking on pipes, we really shouldnt be using stdio functions like fgets in the first place */ fcntl(STDIN_FILENO,F_SETFL,O_NONBLOCK); while(!feof(stdin)) { if (!fgets(buf, bufsize, stdin)) { /* if nothing was received, then no transmission * over smtp should be done */ sleep(1); /* don't hang forever when reading from stdin */ if (++timeout >= MEDWAIT) { log_event(LOG_ERR, "killed: timeout on stdin while reading body -- message saved to dead.letter."); die("Timeout on stdin while reading body"); } continue; } /* Trim off \n, double leading .'s */ leadingdot = standardise(buf, &linestart); if (linestart || feof(stdin)) { linestart = True; outbytes += smtp_write(sock, "%s", leadingdot ? b : buf); } else { if (log_level > 0) { log_event(LOG_INFO, "Sent a very long line in chunks"); } if (leadingdot) { outbytes += fd_puts(sock, b, sizeof(b)); } else { outbytes += fd_puts(sock, buf, bufsize); } } (void)alarm((unsigned) MEDWAIT); } if(!linestart) { smtp_write(sock, ""); } /* End of body */ outbytes += smtp_write(sock, "."); (void)alarm((unsigned) MAXWAIT); if(smtp_okay(sock, buf) == 0) { die("%s", buf); } /* Close connection */ (void)signal(SIGALRM, SIG_IGN); outbytes += smtp_write(sock, "QUIT"); (void)smtp_okay(sock, buf); (void)close(sock); log_event(LOG_INFO, "Sent mail for %s (%s) uid=%d username=%s outbytes=%d", from_strip(uad), buf, uid, pw->pw_name, outbytes); return(0); }
/* smtp_open() -- Open connection to a remote SMTP listener */ int smtp_open(char *host, int port) { int fd_flags; char *ip; #ifdef INET6 struct addrinfo hints, *ai0, *ai; char servname[NI_MAXSERV]; int s; #else struct sockaddr_in name; struct hostent *hent; int s, namelen; #endif #ifdef HAVE_SSL int err; char buf[(BUF_SZ + 1)]; /* Init SSL stuff */ SSL_CTX *ctx; SSL_METHOD *meth; X509 *server_cert; SSL_load_error_strings(); SSLeay_add_ssl_algorithms(); meth=SSLv23_client_method(); ctx = SSL_CTX_new(meth); if(!ctx) { log_event(LOG_ERR, "No SSL support initiated\n"); return(-1); } if(use_cert == True) { if(SSL_CTX_use_certificate_chain_file(ctx, tls_cert) <= 0) { perror("Use certfile"); return(-1); } if(SSL_CTX_use_PrivateKey_file(ctx, tls_cert, SSL_FILETYPE_PEM) <= 0) { perror("Use PrivateKey"); return(-1); } if(!SSL_CTX_check_private_key(ctx)) { log_event(LOG_ERR, "Private key does not match the certificate public key\n"); return(-1); } } #endif #ifdef INET6 memset(&hints, 0, sizeof(hints)); hints.ai_family = p_family; hints.ai_socktype = SOCK_STREAM; snprintf(servname, sizeof(servname), "%d", port); /* Check we can reach the host */ if (getaddrinfo(host, servname, &hints, &ai0)) { log_event(LOG_ERR, "Unable to locate %s", host); return(-1); } for (ai = ai0; ai; ai = ai->ai_next) { /* Create a socket for the connection */ s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (s < 0) { continue; } #ifdef CONNECT_TIMEOUT fd_flags = fcntl(s, F_GETFL, 0); if (fcntl(s, F_SETFL, fd_flags | O_NONBLOCK) != 0) { s = -1; continue; } while (1) { if (connect(s, ai->ai_addr, ai->ai_addrlen) == 0) { goto out_of_loop; } switch(errno) { case EALREADY: case EINPROGRESS: { int poll_res; poll_res = ssmtp_poll(s, POLLOUT, connect_timeout); if (poll_res == SSMTP_POLL_SUCCESS) { /* try again */ continue; } s = -1; goto out_of_loop; } case EINTR: continue; default: s = -1; goto out_of_loop; } } #else if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0) { s = -1; continue; } #endif break; } out_of_loop: if(s < 0) { log_event (LOG_ERR, "Unable to connect to \"%s\" port %d.\n", host, port); return(-1); } #else /* Check we can reach the host */ if((hent = gethostbyname(host)) == (struct hostent *)NULL) { log_event(LOG_ERR, "Unable to locate %s", host); return(-1); } if(hent->h_length > sizeof(hent->h_addr)) { log_event(LOG_ERR, "Buffer overflow in gethostbyname()"); return(-1); } /* Create a socket for the connection */ if((s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { log_event(LOG_ERR, "Unable to create a socket"); return(-1); } /* This SHOULD already be in Network Byte Order from gethostbyname() */ name.sin_addr.s_addr = ((struct in_addr *)(hent->h_addr))->s_addr; name.sin_family = hent->h_addrtype; name.sin_port = htons(port); ip = inet_ntoa(name.sin_addr); if (ip) { server_ip = strdup(ip); } namelen = sizeof(struct sockaddr_in); #ifdef CONNECT_TIMEOUT fd_flags = fcntl(s, F_GETFL, 0); if (fcntl(s, F_SETFL, fd_flags | O_NONBLOCK) != 0) { log_event(LOG_ERR, "fcntl(, O_NONBLOCK) failed"); return(-1); } while (1) { if(connect(s, (struct sockaddr *)&name, namelen) == 0) { goto out_of_loop; } switch(errno) { case EALREADY: case EINPROGRESS: { int poll_res; poll_res = ssmtp_poll(s, POLLOUT, connect_timeout); if (poll_res == SSMTP_POLL_SUCCESS) { /* try again */ continue; } s = -1; log_event(LOG_ERR, "connect(%s:%d) timed out", host, port); goto out_of_loop; } case EINTR: continue; default: s = -1; log_event(LOG_ERR, "poll() failed on connect"); goto out_of_loop; } } out_of_loop: if (s < 0) { log_event(LOG_ERR, "Unable to connect to %s:%d", host, port); return(-1); } #else if(connect(s, (struct sockaddr *)&name, namelen) < 0) { log_event(LOG_ERR, "Unable to connect to %s:%d", host, port); return(-1); } #endif #endif #ifdef HAVE_SSL if(use_tls == True) { log_event(LOG_INFO, "Creating SSL connection to host"); if (use_starttls == True) { use_tls=False; /* need to write plain text for a while */ if (smtp_okay(s, buf)) { smtp_write(s, "EHLO %s", hostname); if (smtp_okay(s, buf)) { smtp_write(s, "STARTTLS"); /* assume STARTTLS regardless */ if (!smtp_okay(s, buf)) { log_event(LOG_ERR, "STARTTLS not working"); return(-1); } } else { log_event(LOG_ERR, "Invalid response: %s from %s", buf, hostname); } } else { log_event(LOG_ERR, "Invalid response SMTP Server (STARTTLS) from %s", hostname); return(-1); } use_tls=True; /* now continue as normal for SSL */ } ssl = SSL_new(ctx); if(!ssl) { log_event(LOG_ERR, "SSL not working"); return(-1); } SSL_set_fd(ssl, s); err = SSL_connect(ssl); if(err < 0) { perror("SSL_connect"); return(-1); } if(log_level > 0 || 1) { log_event(LOG_INFO, "SSL connection using %s", SSL_get_cipher(ssl)); } server_cert = SSL_get_peer_certificate(ssl); if(!server_cert) { return(-1); } X509_free(server_cert); /* TODO: Check server cert if changed! */ } #endif return(s); }