/* * msmtp.c * * This file is part of msmtp, an SMTP client. * * Copyright (C) 2000, 2003, 2004, 2005, 2006, 2007 * Martin Lambers * * This program 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 3 of the License, or * (at your option) any later version. * * This program 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 this program. If not, see . */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include extern char *optarg; extern int optind; #include #include #ifdef ENABLE_NLS # include #endif #ifdef HAVE_SYSLOG # include #endif #ifdef W32_NATIVE #include #include #include #include #elif defined DJGPP #include #include #include #include #else /* UNIX */ #include #include #endif #include "getpass.h" #include "gettext.h" #include "xalloc.h" #include "xvasprintf.h" #include "conf.h" #include "list.h" #include "net.h" #include "netrc.h" #include "smtp.h" #include "tools.h" #ifdef HAVE_TLS # include "tls.h" #endif /* HAVE_TLS */ /* Default file names. */ #ifdef W32_NATIVE #define SYSCONFFILE "msmtprc.txt" #define USERCONFFILE "msmtprc.txt" #define NETRCFILE "netrc.txt" #elif defined (DJGPP) #define SYSCONFFILE "msmtprc" #define USERCONFFILE "_msmtprc" #define NETRCFILE "_netrc" #else /* UNIX */ #define SYSCONFFILE "msmtprc" #define USERCONFFILE ".msmtprc" #define NETRCFILE ".netrc" #endif /* The name of this program */ const char *prgname; /* * Die if memory allocation fails */ void xalloc_die(void) { fprintf(stderr, "%s: FATAL: %s", prgname, strerror(ENOMEM)); exit(EX_OSERR); } /* * Translate error codes from net.h, tls.h or smtp.h * to error codes from sysexits.h */ int exitcode_net(int net_error_code) { switch (net_error_code) { case NET_EHOSTNOTFOUND: return EX_NOHOST; case NET_ESOCKET: return EX_OSERR; case NET_ECONNECT: return EX_TEMPFAIL; case NET_EIO: return EX_IOERR; case NET_ELIBFAILED: default: return EX_SOFTWARE; } } #ifdef HAVE_TLS int exitcode_tls(int tls_error_code) { switch (tls_error_code) { case TLS_EIO: return EX_IOERR; case TLS_EFILE: return EX_NOINPUT; case TLS_EHANDSHAKE: return EX_PROTOCOL; case TLS_ECERT: /* did not find anything better... */ return EX_UNAVAILABLE; case TLS_ELIBFAILED: case TLS_ESEED: default: return EX_SOFTWARE; } } #endif /* HAVE_TLS */ int exitcode_smtp(int smtp_error_code) { switch (smtp_error_code) { case SMTP_EIO: return EX_IOERR; case SMTP_EPROTO: return EX_PROTOCOL; case SMTP_EINVAL: return EX_DATAERR; case SMTP_EAUTHFAIL: return EX_NOPERM; case SMTP_EINSECURE: case SMTP_EUNAVAIL: return EX_UNAVAILABLE; case SMTP_ELIBFAILED: default: return EX_SOFTWARE; } } /* * Return the name of a sysexits.h exitcode */ const char *exitcode_to_string(int exitcode) { switch (exitcode) { case EX_OK: return "EX_OK"; case EX_USAGE: return "EX_USAGE"; case EX_DATAERR: return "EX_DATAERR"; case EX_NOINPUT: return "EX_NOINPUT"; case EX_NOUSER: return "EX_NOUSER"; case EX_NOHOST: return "EX_NOHOST"; case EX_UNAVAILABLE: return "EX_UNAVAILABLE"; case EX_SOFTWARE: return "EX_SOFTWARE"; case EX_OSERR: return "EX_OSERR"; case EX_OSFILE: return "EX_OSFILE"; case EX_CANTCREAT: return "EX_CANTCREAT"; case EX_IOERR: return "EX_IOERR"; case EX_TEMPFAIL: return "EX_TEMPFAIL"; case EX_PROTOCOL: return "EX_PROTOCOL"; case EX_NOPERM: return "EX_NOPERM"; case EX_CONFIG: return "EX_CONFIG"; default: return "BUG:UNKNOWN"; } } /* * msmtp_sanitize_string() * * Replaces all control characters in the string with a question mark */ char *msmtp_sanitize_string(char *str) { char *p = str; while (*p != '\0') { if (iscntrl((unsigned char)*p)) { *p = '?'; } p++; } return str; } /* * msmtp_password_callback() * * This function will be called by smtp_auth() to get a password if none was * given. It tries to read a password from .netrc, and if that fails reads a * password with getpass(). * It must return NULL on failure or a password in an allocated buffer. */ char *msmtp_password_callback(const char *hostname, const char *user) { char *homedir; char *netrc_filename; netrc_entry *netrc_hostlist; netrc_entry *netrc_host; char *prompt; char *gpw; char *password = NULL; homedir = get_homedir(); netrc_filename = get_filename(homedir, NETRCFILE); free(homedir); if ((netrc_hostlist = parse_netrc(netrc_filename))) { if ((netrc_host = search_netrc(netrc_hostlist, hostname, user))) { password = xstrdup(netrc_host->password); } free_netrc_entry_list(netrc_hostlist); } free(netrc_filename); if (!password) { prompt = xasprintf(_("password for %s at %s: "), user, hostname); gpw = getpass(prompt); free(prompt); if (gpw) { password = xstrdup(gpw); } } return password; } /* * msmtp_print_tls_cert_info() * * Prints information about a TLS certificate. */ #ifdef HAVE_TLS /* Convert the given time into a string. */ void msmtp_time_to_string(time_t *t, char *buf, size_t bufsize) { #ifdef ENABLE_NLS (void)strftime(buf, bufsize, "%c", localtime(t)); #else char *p; (void)snprintf(buf, bufsize, "%s", ctime(t)); if ((p = strchr(buf, '\n'))) { *p = '\0'; } #endif } void msmtp_print_tls_cert_info(tls_cert_info_t *tci) { const char *info_fieldname[6] = { N_("Common Name"), N_("Organization"), N_("Organizational unit"), N_("Locality"), N_("State or Province"), N_("Country") }; char hex[] = "0123456789ABCDEF"; char sha1_fingerprint_string[60]; char md5_fingerprint_string[48]; char timebuf[128]; /* should be long enough for every locale */ char *tmp; int i; for (i = 0; i < 20; i++) { sha1_fingerprint_string[3 * i] = hex[(tci->sha1_fingerprint[i] & 0xf0) >> 4]; sha1_fingerprint_string[3 * i + 1] = hex[tci->sha1_fingerprint[i] & 0x0f]; sha1_fingerprint_string[3 * i + 2] = ':'; } sha1_fingerprint_string[59] = '\0'; for (i = 0; i < 16; i++) { md5_fingerprint_string[3 * i] = hex[(tci->md5_fingerprint[i] & 0xf0) >> 4]; md5_fingerprint_string[3 * i + 1] = hex[tci->md5_fingerprint[i] & 0x0f]; md5_fingerprint_string[3 * i + 2] = ':'; } md5_fingerprint_string[47] = '\0'; printf(_("TLS certificate information:\n")); printf(" %s:\n", _("Owner")); for (i = 0; i < 6; i++) { if (tci->owner_info[i]) { tmp = xstrdup(tci->owner_info[i]); printf(" %s: %s\n", gettext(info_fieldname[i]), msmtp_sanitize_string(tmp)); free(tmp); } } printf(" %s:\n", _("Issuer")); for (i = 0; i < 6; i++) { if (tci->issuer_info[i]) { tmp = xstrdup(tci->issuer_info[i]); printf(" %s: %s\n", gettext(info_fieldname[i]), msmtp_sanitize_string(tmp)); free(tmp); } } printf(" %s:\n", _("Validity")); msmtp_time_to_string(&tci->activation_time, timebuf, sizeof(timebuf)); printf(" %s: %s\n", _("Activation time"), timebuf); msmtp_time_to_string(&tci->expiration_time, timebuf, sizeof(timebuf)); printf(" %s: %s\n", _("Expiration time"), timebuf); printf(" %s:\n", _("Fingerprints")); printf(" SHA1: %s\n", sha1_fingerprint_string); printf(" MD5: %s\n", md5_fingerprint_string); } #endif /* * msmtp_endsession() * * Quit an SMTP session and close the connection. * QUIT is only sent when the flag 'quit' is set. */ void msmtp_endsession(smtp_server_t *srv, int quit) { char *tmp_errstr; if (quit) { tmp_errstr = NULL; (void)smtp_quit(srv, &tmp_errstr); free(tmp_errstr); } smtp_close(srv); } /* * msmtp_rmqs() * * Sends an ETRN request to the SMTP server specified in the account 'acc'. * If an error occured, '*errstr' points to an allocated string that describes * the error or is NULL, and '*msg' may contain the offending message from the * SMTP server (or be NULL). */ int msmtp_rmqs(account_t *acc, int debug, const char *rmqs_argument, list_t **msg, char **errstr) { smtp_server_t srv; int e; #ifdef HAVE_TLS tls_cert_info_t *tci = NULL; #endif /* HAVE_TLS */ *errstr = NULL; *msg = NULL; /* create a new smtp_server_t */ srv = smtp_new(debug ? stdout : NULL, acc->protocol); /* connect */ if ((e = smtp_connect(&srv, acc->host, acc->port, acc->timeout, NULL, NULL, errstr)) != NET_EOK) { return exitcode_net(e); } /* prepare tls */ #ifdef HAVE_TLS if (acc->tls) { if ((e = smtp_tls_init(&srv, acc->tls_key_file, acc->tls_cert_file, acc->tls_trust_file, acc->tls_force_sslv3, errstr)) != TLS_EOK) { return exitcode_tls(e); } } #endif /* HAVE_TLS */ /* start tls for ssmtp servers */ #ifdef HAVE_TLS if (acc->tls && acc->tls_nostarttls) { if (debug) { tci = tls_cert_info_new(); } if ((e = smtp_tls(&srv, acc->host, acc->tls_nocertcheck, tci, errstr)) != TLS_EOK) { if (debug) { tls_cert_info_free(tci); } msmtp_endsession(&srv, 0); return exitcode_tls(e); } if (debug) { msmtp_print_tls_cert_info(tci); tls_cert_info_free(tci); } } #endif /* HAVE_TLS */ /* get greeting */ if ((e = smtp_get_greeting(&srv, msg, NULL, errstr)) != SMTP_EOK) { msmtp_endsession(&srv, 0); return exitcode_smtp(e); } /* initialize session */ if ((e = smtp_init(&srv, acc->domain, msg, errstr)) != SMTP_EOK) { msmtp_endsession(&srv, 0); return exitcode_smtp(e); } /* start tls for starttls servers */ #ifdef HAVE_TLS if (acc->tls && !acc->tls_nostarttls) { if (!(srv.cap.flags & SMTP_CAP_STARTTLS)) { *errstr = xasprintf(_("the server does not support TLS " "via the STARTTLS command")); msmtp_endsession(&srv, 1); return EX_UNAVAILABLE; } if ((e = smtp_tls_starttls(&srv, msg, errstr)) != SMTP_EOK) { msmtp_endsession(&srv, 0); return exitcode_smtp(e); } if (debug) { tci = tls_cert_info_new(); } if ((e = smtp_tls(&srv, acc->host, acc->tls_nocertcheck, tci, errstr)) != TLS_EOK) { if (debug) { tls_cert_info_free(tci); } msmtp_endsession(&srv, 0); return exitcode_tls(e); } if (debug) { msmtp_print_tls_cert_info(tci); tls_cert_info_free(tci); } /* initialize again */ if ((e = smtp_init(&srv, acc->domain, msg, errstr)) != SMTP_EOK) { msmtp_endsession(&srv, 0); return exitcode_smtp(e); } } #endif /* HAVE_TLS */ if (!(srv.cap.flags & SMTP_CAP_ETRN)) { *errstr = xasprintf(_("the server does not support " "Remote Message Queue Starting")); msmtp_endsession(&srv, 1); return EX_UNAVAILABLE; } /* authenticate */ if (acc->auth_mech) { if (!(srv.cap.flags & SMTP_CAP_AUTH)) { *errstr = xasprintf( _("the server does not support authentication")); msmtp_endsession(&srv, 1); return EX_UNAVAILABLE; } if ((e = smtp_auth(&srv, acc->host, acc->username, acc->password, acc->ntlmdomain, acc->auth_mech, msmtp_password_callback, msg, errstr)) != SMTP_EOK) { msmtp_endsession(&srv, 0); return exitcode_smtp(e); } } /* send the ETRN request */ if ((e = smtp_etrn(&srv, rmqs_argument, msg, errstr)) != SMTP_EOK) { msmtp_endsession(&srv, 0); return exitcode_smtp(e); } /* end session */ msmtp_endsession(&srv, 1); return EX_OK; } /* * msmtp_serverinfo() * * Prints information about the SMTP server specified in the account 'acc'. * If an error occured, '*errstr' points to an allocated string that describes * the error or is NULL, and '*msg' may contain the offending message from the * SMTP server (or be NULL). */ int msmtp_serverinfo(account_t *acc, int debug, list_t **msg, char **errstr) { smtp_server_t srv; char *server_canonical_name = NULL; char *server_address = NULL; char *server_greeting = NULL; int e; #ifdef HAVE_TLS tls_cert_info_t *tci = NULL; #endif /* HAVE_TLS */ *errstr = NULL; *msg = NULL; /* create a new smtp_server_t */ srv = smtp_new(debug ? stdout : NULL, acc->protocol); /* connect */ if ((e = smtp_connect(&srv, acc->host, acc->port, acc->timeout, &server_canonical_name, &server_address, errstr)) != NET_EOK) { e = exitcode_net(e); goto error_exit; } /* prepare tls */ #ifdef HAVE_TLS if (acc->tls) { tci = tls_cert_info_new(); if ((e = smtp_tls_init(&srv, acc->tls_key_file, acc->tls_cert_file, acc->tls_trust_file, acc->tls_force_sslv3, errstr)) != TLS_EOK) { e = exitcode_tls(e); goto error_exit; } } #endif /* HAVE_TLS */ /* start tls for ssmtp servers */ #ifdef HAVE_TLS if (acc->tls && acc->tls_nostarttls) { if ((e = smtp_tls(&srv, acc->host, acc->tls_nocertcheck, tci, errstr)) != TLS_EOK) { msmtp_endsession(&srv, 0); e = exitcode_tls(e); goto error_exit; } } #endif /* HAVE_TLS */ /* get greeting */ if ((e = smtp_get_greeting(&srv, msg, &server_greeting, errstr)) != SMTP_EOK) { msmtp_endsession(&srv, 0); e = exitcode_smtp(e); goto error_exit; } /* initialize session */ if ((e = smtp_init(&srv, acc->domain, msg, errstr)) != SMTP_EOK) { msmtp_endsession(&srv, 0); e = exitcode_smtp(e); goto error_exit; } /* start tls for starttls servers */ #ifdef HAVE_TLS if (acc->tls && !acc->tls_nostarttls) { if (!(srv.cap.flags & SMTP_CAP_STARTTLS)) { *errstr = xasprintf(_("the server does not support TLS " "via the STARTTLS command")); msmtp_endsession(&srv, 1); e = EX_UNAVAILABLE; goto error_exit; } if ((e = smtp_tls_starttls(&srv, msg, errstr)) != SMTP_EOK) { msmtp_endsession(&srv, 0); e = exitcode_smtp(e); goto error_exit; } if ((e = smtp_tls(&srv, acc->host, acc->tls_nocertcheck, tci, errstr)) != TLS_EOK) { msmtp_endsession(&srv, 0); e = exitcode_tls(e); goto error_exit; } /* initialize again */ if ((e = smtp_init(&srv, acc->domain, msg, errstr)) != SMTP_EOK) { msmtp_endsession(&srv, 0); e = exitcode_smtp(e); goto error_exit; } } #endif /* HAVE_TLS */ /* end session */ msmtp_endsession(&srv, 1); /* print results */ if (server_canonical_name && server_address) { printf(_("%s server at %s (%s [%s]), port %d:\n"), acc->protocol == SMTP_PROTO_SMTP ? "SMTP" : "LMTP", acc->host, server_canonical_name, server_address, acc->port); } else if (server_canonical_name) { printf(_("%s server at %s (%s), port %d:\n"), acc->protocol == SMTP_PROTO_SMTP ? "SMTP" : "LMTP", acc->host, server_canonical_name, acc->port); } else if (server_address) { printf(_("%s server at %s ([%s]), port %d:\n"), acc->protocol == SMTP_PROTO_SMTP ? "SMTP" : "LMTP", acc->host, server_address, acc->port); } else { printf(_("%s server at %s, port %d:\n"), acc->protocol == SMTP_PROTO_SMTP ? "SMTP" : "LMTP", acc->host, acc->port); } if (*server_greeting != '\0') { printf(" %s\n", msmtp_sanitize_string(server_greeting)); } #ifdef HAVE_TLS if (acc->tls) { msmtp_print_tls_cert_info(tci); } #endif /* HAVE_TLS */ #ifdef HAVE_TLS if (srv.cap.flags == 0 && !(acc->tls && !acc->tls_nostarttls)) #else /* not HAVE_TLS */ if (srv.cap.flags == 0) #endif /* not HAVE_TLS */ { printf(_("No special capabilities.\n")); } else { printf(_("Capabilities:\n")); if (srv.cap.flags & SMTP_CAP_SIZE) { printf(" SIZE %ld:\n %s", srv.cap.size, _("Maximum message size is ")); if (srv.cap.size == 0) { printf(_("unlimited\n")); } else { printf(_("%ld bytes"), srv.cap.size); if (srv.cap.size > 1024 * 1024) { printf(_(" = %.2f MB"), (float)srv.cap.size / (1024.0 * 1024.0)); } else if (srv.cap.size > 1024) { printf(_(" = %.2f KB"), (float)srv.cap.size / 1024.0); } printf("\n"); } } if (srv.cap.flags & SMTP_CAP_PIPELINING) { printf(" PIPELINING:\n %s\n", _("Support for command " "grouping for faster transmission")); } if (srv.cap.flags & SMTP_CAP_ETRN) { printf(" ETRN:\n %s\n", _("Support for RMQS " "(Remote Message Queue Starting)")); } if (srv.cap.flags & SMTP_CAP_DSN) { printf(" DSN:\n %s\n", _("Support for " "Delivery Status Notifications")); } #ifdef HAVE_TLS if ((acc->tls && !acc->tls_nostarttls) || (srv.cap.flags & SMTP_CAP_STARTTLS)) #else /* not HAVE_TLS */ if (srv.cap.flags & SMTP_CAP_STARTTLS) #endif /* not HAVE_TLS */ { printf(" STARTTLS:\n %s\n", _("Support for " "TLS encryption via the STARTTLS command")); } if (srv.cap.flags & SMTP_CAP_AUTH) { printf(" AUTH:\n %s\n ", _("Supported authentication methods:")); if (srv.cap.flags & SMTP_CAP_AUTH_PLAIN) { printf("PLAIN "); } if (srv.cap.flags & SMTP_CAP_AUTH_CRAM_MD5) { printf("CRAM-MD5 "); } if (srv.cap.flags & SMTP_CAP_AUTH_DIGEST_MD5) { printf("DIGEST-MD5 "); } if (srv.cap.flags & SMTP_CAP_AUTH_GSSAPI) { printf("GSSAPI "); } if (srv.cap.flags & SMTP_CAP_AUTH_EXTERNAL) { printf("EXTERNAL "); } if (srv.cap.flags & SMTP_CAP_AUTH_LOGIN) { printf("LOGIN "); } if (srv.cap.flags & SMTP_CAP_AUTH_NTLM) { printf("NTLM "); } printf("\n"); } #ifdef HAVE_TLS if ((srv.cap.flags & SMTP_CAP_STARTTLS) && !acc->tls) #else /* not HAVE_TLS */ if (srv.cap.flags & SMTP_CAP_STARTTLS) #endif /* not HAVE_TLS */ { printf(_("This server might advertise more or other " "capabilities when TLS is active.\n")); } } e = EX_OK; error_exit: free(server_canonical_name); free(server_address); #ifdef HAVE_TLS if (tci) { tls_cert_info_free(tci); } #endif /* HAVE_TLS */ free(server_greeting); return e; } /* * msmtp_read_recipients() * * Copies the headers of the mail from 'mailf' to a temporary file '*tmpfile', * including the blank line that separates the header from the body of the mail. * Extracts all recipients from the To, Cc, and Bcc headers and adds them * to 'recipients'. * This function rewinds '*tmpfile' after writing the headers to it. * * See RFC2822, section 3 for the format of these headers. * * Return codes: EX_OK, EX_IOERR */ #define STATE_LINESTART_FRESH 0 /* a new line started; the previous line was not a recipient header */ #define STATE_LINESTART_AFTER_RCPTHDR 1 /* a new line started; the previous line was a recipient header */ #define STATE_OTHER_HDR 2 /* a header we don't care about */ #define STATE_TO 3 /* we saw "^T" */ #define STATE_CC 4 /* we saw "^C" */ #define STATE_BCC1 5 /* we saw "^B" */ #define STATE_BCC2 6 /* we saw "^Bc" */ #define STATE_RCPTHDR_ALMOST 7 /* we saw "^To", "^Cc" or "^Bcc" */ #define STATE_RCPTHDR_DEFAULT 8 /* in_rcpt_hdr and in_rcpt state our position */ #define STATE_RCPTHDR_DQUOTE 9 /* duoble quotes */ #define STATE_RCPTHDR_BRACKETS_START 10 /* entering <...> */ #define STATE_RCPTHDR_IN_BRACKETS 11 /* an address inside <> */ #define STATE_RCPTHDR_PARENTH_START 12 /* entering (...) */ #define STATE_RCPTHDR_IN_PARENTH 13 /* a comment inside () */ #define STATE_RCPTHDR_IN_ADDRESS 14 /* a bare address */ #define STATE_RCPTHDR_BACKQUOTE 15 /* we saw a '\\' */ #define STATE_HEADERS_END 16 /* we saw "^$", the blank line between headers and body */ int msmtp_read_recipients(FILE *mailf, list_t *recipients, FILE **tmpfile, char **errstr) { int c; int state = STATE_LINESTART_FRESH; int oldstate = STATE_LINESTART_FRESH; int backquote_savestate = STATE_LINESTART_FRESH; int parentheses_depth = 0; int parentheses_savestate = STATE_LINESTART_FRESH; int folded_rcpthdr_savestate = STATE_LINESTART_FRESH; char *current_recipient = NULL; size_t current_recipient_len = 0; int forget_current_recipient = 0; int finish_current_recipient = 0; size_t bufsize = 0; /* The buffer that is filled with the current recipient grows by * 'bufsize_step' if the remaining space becomes too small. This value must * be at least 2. Wasted characters are at most (bufsize_step - 1). A value * of 10 means low wasted space and a low number of realloc()s per * recipient. */ const size_t bufsize_step = 10; if (!(*tmpfile = tempfile(PACKAGE_NAME))) { *errstr = xasprintf(_("cannot create temporary file: %s"), strerror(errno)); goto error_exit; } for (;;) { c = fgetc(mailf); /* Convert CRLF to LF. According to RFC 2822, CRs may only occur in a * mail when they are followed by LF, so just ignoring CRs is ok. */ if (c == '\r') { continue; } oldstate = state; if (c == EOF) { state = STATE_HEADERS_END; if (current_recipient) finish_current_recipient = 1; } else { switch (state) { case STATE_LINESTART_FRESH: parentheses_depth = 0; if (c == 't' || c == 'T') state = STATE_TO; else if (c == 'c' || c == 'C') state = STATE_CC; else if (c == 'b' || c == 'B') state = STATE_BCC1; else if (c == '\n') state = STATE_HEADERS_END; else state = STATE_OTHER_HDR; break; case STATE_LINESTART_AFTER_RCPTHDR: if (c != ' ' && c != '\t' && current_recipient) finish_current_recipient = 1; if (c == ' ' || c == '\t') state = folded_rcpthdr_savestate; else if (c == 't' || c == 'T') state = STATE_TO; else if (c == 'c' || c == 'C') state = STATE_CC; else if (c == 'b' || c == 'B') state = STATE_BCC1; else if (c == '\n') state = STATE_HEADERS_END; else state = STATE_OTHER_HDR; break; case STATE_OTHER_HDR: if (c == '\n') state = STATE_LINESTART_FRESH; break; case STATE_TO: if (c == 'o' || c == 'O') state = STATE_RCPTHDR_ALMOST; else if (c == '\n') state = STATE_LINESTART_FRESH; else state = STATE_OTHER_HDR; break; case STATE_CC: if (c == 'c' || c == 'C') state = STATE_RCPTHDR_ALMOST; else if (c == '\n') state = STATE_LINESTART_FRESH; else state = STATE_OTHER_HDR; break; case STATE_BCC1: if (c == 'c' || c == 'C') state = STATE_BCC2; else if (c == '\n') state = STATE_LINESTART_FRESH; else state = STATE_OTHER_HDR; break; case STATE_BCC2: if (c == 'c' || c == 'C') state = STATE_RCPTHDR_ALMOST; else if (c == '\n') state = STATE_LINESTART_FRESH; else state = STATE_OTHER_HDR; break; case STATE_RCPTHDR_ALMOST: if (c == ':') state = STATE_RCPTHDR_DEFAULT; else if (c == '\n') state = STATE_LINESTART_FRESH; else state = STATE_OTHER_HDR; break; case STATE_RCPTHDR_DEFAULT: if (c == '\n') { if (current_recipient) finish_current_recipient = 1; folded_rcpthdr_savestate = state; state = STATE_LINESTART_AFTER_RCPTHDR; } else if (c == '\\') { backquote_savestate = state; state = STATE_RCPTHDR_BACKQUOTE; } else if (c == '(') { parentheses_savestate = state; state = STATE_RCPTHDR_PARENTH_START; } else if (c == '"') { if (current_recipient) forget_current_recipient = 1; state = STATE_RCPTHDR_DQUOTE; } else if (c == '<') { if (current_recipient) forget_current_recipient = 1; state = STATE_RCPTHDR_BRACKETS_START; } else if (c == ' ' || c == '\t') ; /* keep state */ else if (c == ':') { if (current_recipient) forget_current_recipient = 1; } else if (c == ';' || c == ',') { if (current_recipient) finish_current_recipient = 1; } else { if (current_recipient) forget_current_recipient = 1; state = STATE_RCPTHDR_IN_ADDRESS; } break; case STATE_RCPTHDR_DQUOTE: if (c == '\n') { folded_rcpthdr_savestate = state; state = STATE_LINESTART_AFTER_RCPTHDR; } else if (c == '\\') { backquote_savestate = state; state = STATE_RCPTHDR_BACKQUOTE; } else if (c == '"') state = STATE_RCPTHDR_DEFAULT; break; case STATE_RCPTHDR_BRACKETS_START: if (c == '\n') { folded_rcpthdr_savestate = state; state = STATE_LINESTART_AFTER_RCPTHDR; } else if (c == '(') { parentheses_savestate = state; state = STATE_RCPTHDR_PARENTH_START; } else if (c == '>') state = STATE_RCPTHDR_DEFAULT; else state = STATE_RCPTHDR_IN_BRACKETS; break; case STATE_RCPTHDR_IN_BRACKETS: if (c == '\n') { folded_rcpthdr_savestate = state; state = STATE_LINESTART_AFTER_RCPTHDR; } else if (c == '\\') { backquote_savestate = state; state = STATE_RCPTHDR_BACKQUOTE; } else if (c == '(') { parentheses_savestate = state; state = STATE_RCPTHDR_PARENTH_START; } else if (c == '>') { finish_current_recipient = 1; state = STATE_RCPTHDR_DEFAULT; } break; case STATE_RCPTHDR_PARENTH_START: if (c == '\n') { folded_rcpthdr_savestate = state; state = STATE_LINESTART_AFTER_RCPTHDR; } else if (c == ')') state = parentheses_savestate; else { parentheses_depth++; state = STATE_RCPTHDR_IN_PARENTH; } break; case STATE_RCPTHDR_IN_PARENTH: if (c == '\n') { folded_rcpthdr_savestate = state; state = STATE_LINESTART_AFTER_RCPTHDR; } else if (c == '\\') { backquote_savestate = state; state = STATE_RCPTHDR_BACKQUOTE; } else if (c == '(') state = STATE_RCPTHDR_PARENTH_START; else if (c == ')') { parentheses_depth--; if (parentheses_depth == 0) state = parentheses_savestate; } break; case STATE_RCPTHDR_IN_ADDRESS: if (c == '\n') { folded_rcpthdr_savestate = STATE_RCPTHDR_DEFAULT; state = STATE_LINESTART_AFTER_RCPTHDR; } else if (c == '\\') { backquote_savestate = state; state = STATE_RCPTHDR_BACKQUOTE; } else if (c == '"') { forget_current_recipient = 1; state = STATE_RCPTHDR_DQUOTE; } else if (c == '(') { parentheses_savestate = state; state = STATE_RCPTHDR_PARENTH_START; } else if (c == '<') { forget_current_recipient = 1; state = STATE_RCPTHDR_BRACKETS_START; } else if (c == ' ' || c == '\t') state = STATE_RCPTHDR_DEFAULT; else if (c == ':') { forget_current_recipient = 1; state = STATE_RCPTHDR_DEFAULT; } else if (c == ',' || c == ';') { finish_current_recipient = 1; state = STATE_RCPTHDR_DEFAULT; } break; case STATE_RCPTHDR_BACKQUOTE: if (c == '\n') { folded_rcpthdr_savestate = STATE_RCPTHDR_DEFAULT; state = STATE_LINESTART_AFTER_RCPTHDR; } else state = backquote_savestate; break; } } if (c != EOF && fputc(c, *tmpfile) == EOF) { *errstr = xasprintf(_("cannot write mail headers to temporary " "file: output error")); goto error_exit; } if (forget_current_recipient) { /* this was just junk */ free(current_recipient); current_recipient = NULL; current_recipient_len = 0; bufsize = 0; forget_current_recipient = 0; } if (finish_current_recipient) { /* The current recipient just ended. Add it to the list */ current_recipient[current_recipient_len] = '\0'; list_insert(recipients, current_recipient); recipients = recipients->next; /* Reset for the next recipient */ current_recipient = NULL; current_recipient_len = 0; bufsize = 0; finish_current_recipient = 0; } if ((state == STATE_RCPTHDR_IN_ADDRESS || state == STATE_RCPTHDR_IN_BRACKETS) && oldstate != STATE_RCPTHDR_PARENTH_START && oldstate != STATE_RCPTHDR_IN_PARENTH && oldstate != STATE_LINESTART_AFTER_RCPTHDR) { /* Add this character to the current recipient */ current_recipient_len++; if (bufsize < current_recipient_len + 1) { bufsize += bufsize_step; current_recipient = xrealloc(current_recipient, bufsize * sizeof(char)); } /* sanitize characters */ if (!iscntrl((unsigned char)c) && !isspace((unsigned char)c)) { current_recipient[current_recipient_len - 1] = (char)c; } else { current_recipient[current_recipient_len - 1] = '_'; } } if (state == STATE_HEADERS_END) { break; } } if (ferror(mailf)) { *errstr = xasprintf(_("input error while reading the mail")); goto error_exit; } if (fseek(*tmpfile, 0L, SEEK_SET) != 0) { *errstr = xasprintf(_("cannot rewind temporary file: %s"), strerror(errno)); goto error_exit; } return EX_OK; error_exit: if (*tmpfile) { (void)fclose(*tmpfile); *tmpfile = NULL; } free(current_recipient); return EX_IOERR; } /* * msmtp_sendmail() * * Sends a mail. Returns a value from sysexits.h. * If 'read_recipients' is true, recipients are extracted from the To, Cc, * and Bcc headers *in addition* to the recipients in 'recipients'. * If an error occured, '*errstr' points to an allocated string that describes * the error or is NULL, and '*msg' may contain the offending message from the * SMTP server (or be NULL). * In case of success, 'mailsize' contains the number of bytes of the mail * transferred to the SMTP server. In case of failure, its contents are * undefined. */ int msmtp_sendmail(account_t *acc, list_t *recipients, int read_recipients, FILE *f, int debug, long *mailsize, list_t **lmtp_errstrs, list_t **lmtp_error_msgs, list_t **msg, char **errstr) { smtp_server_t srv; FILE *tmpfile = NULL; int e; #ifdef HAVE_TLS tls_cert_info_t *tci = NULL; #endif /* HAVE_TLS */ *errstr = NULL; *msg = NULL; *lmtp_errstrs = NULL; *lmtp_error_msgs = NULL; /* Read recipients from the mail as soon as possible. Important for * error reporting/logging. */ if (read_recipients) { if ((e = msmtp_read_recipients(f, list_last(recipients), &tmpfile, errstr)) != EX_OK) { goto error_exit; } if (list_is_empty(recipients)) { *errstr = xasprintf(_("no recipients found")); e = EX_DATAERR; goto error_exit; } } /* create a new smtp_server_t */ srv = smtp_new(debug ? stdout : NULL, acc->protocol); /* prepare tls */ #ifdef HAVE_TLS if (acc->tls) { if ((e = smtp_tls_init(&srv, acc->tls_key_file, acc->tls_cert_file, acc->tls_trust_file, acc->tls_force_sslv3, errstr)) != TLS_EOK) { e = exitcode_tls(e); goto error_exit; } } #endif /* HAVE_TLS */ /* connect */ if ((e = smtp_connect(&srv, acc->host, acc->port, acc->timeout, NULL, NULL, errstr)) != NET_EOK) { e = exitcode_net(e); goto error_exit; } /* start tls for ssmtp servers */ #ifdef HAVE_TLS if (acc->tls && acc->tls_nostarttls) { if (debug) { tci = tls_cert_info_new(); } if ((e = smtp_tls(&srv, acc->host, acc->tls_nocertcheck, tci, errstr)) != TLS_EOK) { if (debug) { tls_cert_info_free(tci); } msmtp_endsession(&srv, 0); e = exitcode_tls(e); goto error_exit; } if (debug) { msmtp_print_tls_cert_info(tci); tls_cert_info_free(tci); } } #endif /* HAVE_TLS */ /* get greeting */ if ((e = smtp_get_greeting(&srv, msg, NULL, errstr)) != SMTP_EOK) { msmtp_endsession(&srv, 0); e = exitcode_smtp(e); goto error_exit; } /* initialize session */ if ((e = smtp_init(&srv, acc->domain, msg, errstr)) != SMTP_EOK) { msmtp_endsession(&srv, 0); e = exitcode_smtp(e); goto error_exit; } /* start tls for starttls servers */ #ifdef HAVE_TLS if (acc->tls && !acc->tls_nostarttls) { if (!(srv.cap.flags & SMTP_CAP_STARTTLS)) { *errstr = xasprintf(_("the server does not support TLS " "via the STARTTLS command")); msmtp_endsession(&srv, 1); e = EX_UNAVAILABLE; goto error_exit; } if ((e = smtp_tls_starttls(&srv, msg, errstr)) != SMTP_EOK) { msmtp_endsession(&srv, 0); e = exitcode_smtp(e); goto error_exit; } if (debug) { tci = tls_cert_info_new(); } if ((e = smtp_tls(&srv, acc->host, acc->tls_nocertcheck, tci, errstr)) != TLS_EOK) { if (debug) { tls_cert_info_free(tci); } msmtp_endsession(&srv, 0); e = exitcode_tls(e); goto error_exit; } if (debug) { msmtp_print_tls_cert_info(tci); tls_cert_info_free(tci); } /* initialize again */ if ((e = smtp_init(&srv, acc->domain, msg, errstr)) != SMTP_EOK) { msmtp_endsession(&srv, 0); e = exitcode_smtp(e); goto error_exit; } } #endif /* HAVE_TLS */ /* test for needed features */ if ((acc->dsn_return || acc->dsn_notify) && !(srv.cap.flags & SMTP_CAP_DSN)) { *errstr = xasprintf(_("the server does not support DSN")); msmtp_endsession(&srv, 1); e = EX_UNAVAILABLE; goto error_exit; } /* authenticate */ if (acc->auth_mech) { if (!(srv.cap.flags & SMTP_CAP_AUTH)) { *errstr = xasprintf( _("the server does not support authentication")); msmtp_endsession(&srv, 1); e = EX_UNAVAILABLE; goto error_exit; } if ((e = smtp_auth(&srv, acc->host, acc->username, acc->password, acc->ntlmdomain, acc->auth_mech, msmtp_password_callback, msg, errstr)) != SMTP_EOK) { msmtp_endsession(&srv, 0); e = exitcode_smtp(e); goto error_exit; } } /* send the envelope */ if ((e = smtp_send_envelope(&srv, acc->from, recipients, acc->dsn_notify, acc->dsn_return, msg, errstr)) != SMTP_EOK) { msmtp_endsession(&srv, 0); e = exitcode_smtp(e); goto error_exit; } /* send header and body */ *mailsize = 0; if (read_recipients) { /* first the headers from the temp file */ if ((e = smtp_send_mail(&srv, tmpfile, acc->keepbcc, mailsize, errstr)) != SMTP_EOK) { msmtp_endsession(&srv, 0); e = exitcode_smtp(e); goto error_exit; } (void)fclose(tmpfile); tmpfile = NULL; /* then the body from the original file */ if ((e = smtp_send_mail(&srv, f, 1, mailsize, errstr)) != SMTP_EOK) { msmtp_endsession(&srv, 0); e = exitcode_smtp(e); goto error_exit; } } else { if ((e = smtp_send_mail(&srv, f, acc->keepbcc, mailsize, errstr)) != SMTP_EOK) { msmtp_endsession(&srv, 0); e = exitcode_smtp(e); goto error_exit; } } /* end the mail */ if (acc->protocol == SMTP_PROTO_SMTP) { e = smtp_end_mail(&srv, msg, errstr); } else { e = smtp_end_mail_lmtp(&srv, recipients, lmtp_errstrs, lmtp_error_msgs, errstr); } if (e != SMTP_EOK) { msmtp_endsession(&srv, 0); e = exitcode_smtp(e); goto error_exit; } /* end session */ msmtp_endsession(&srv, 1); e = EX_OK; error_exit: if (tmpfile) { (void)fclose(tmpfile); } return e; } /* * print_error() * * Print an error message */ /* make gcc print format warnings for this function */ #ifdef __GNUC__ void print_error(const char *format, ...) __attribute__ ((format (printf, 1, 2))); #endif void print_error(const char *format, ...) { va_list args; fprintf(stderr, "%s: ", prgname); va_start(args, format); vfprintf(stderr, format, args); va_end(args); fprintf(stderr, "\n"); } /* * msmtp_get_log_info() * * Gather log information for syslog or logfile and put it in a string: * - host=%s * - tls=on|off * - auth=on|off * - user=%s (only if auth == on and username != NULL) * - from=%s * - recipients=%s,%s,... * - mailsize=%s (only if exitcode == EX_OK) * - smtpstatus=%s (only if exitcode != EX_OK and a smtp msg is available) * - smtpmsg='%s' (only if exitcode != EX_OK and a smtp msg is available) * - errormsg='%s' (only if exitcode != EX_OK and an error msg is available) * - exitcode=%s * 'exitcode' must be one of the sysexits.h exitcodes. * This function cannot fail. */ char *msmtp_get_log_info(account_t *acc, list_t *recipients, long mailsize, list_t *errmsg, char *errstr, int exitcode) { int i; size_t s; list_t *l; char *line; int n; char *p; char *tmp; /* temporary strings: */ char *mailsize_str = NULL; const char *exitcode_str; char *smtpstatus_str = NULL; char *smtperrmsg_str = NULL; /* gather information */ line = NULL; /* mailsize */ if (exitcode == EX_OK) { mailsize_str = xasprintf("%ld", mailsize); } /* exitcode */ exitcode_str = exitcode_to_string(exitcode); /* smtp status and smtp error message */ if (exitcode != EX_OK && errmsg) { smtpstatus_str = xasprintf("%d", smtp_msg_status(errmsg)); l = errmsg; s = 0; while (!list_is_empty(l)) { l = l->next; s += strlen(l->data) + 2; } s += 1; smtperrmsg_str = xmalloc(s * sizeof(char)); smtperrmsg_str[0] = '\''; i = 1; l = errmsg; while (!list_is_empty(l)) { l = l->next; p = msmtp_sanitize_string(l->data); while (*p != '\0') { /* hide single quotes to make the info easy to parse */ smtperrmsg_str[i] = (*p == '\'') ? '?' : *p; p++; i++; } smtperrmsg_str[i++] = '\\'; smtperrmsg_str[i++] = 'n'; } i -= 2; smtperrmsg_str[i++] = '\''; smtperrmsg_str[i++] = '\0'; } /* calculate the length of the log line */ s = 0; /* "host=%s " */ s += 5 + strlen(acc->host) + 1; /* "tls=on|off " */ s += 4 + (acc->tls ? 2 : 3) + 1; /* "auth=on|off " */ s += 5 + (acc->auth_mech ? 2 : 3) + 1; /* "user=%s " */ if (acc->auth_mech && acc->username) { s += 5 + strlen(acc->username) + 1; } /* "from=%s " */ s += 5 + strlen(acc->from) + 1; /* "recipients=%s,%s,... " */ s += 11; l = recipients; while (!list_is_empty(l)) { l = l->next; s += strlen(l->data) + 1; } /* "mailsize=%s " */ if (exitcode == EX_OK) { s += 9 + strlen(mailsize_str) + 1; } /* "smtpstatus=%s smtpmsg=%s " */ if (exitcode != EX_OK && errmsg) { s += 11 + strlen(smtpstatus_str) + 1 + 8 + strlen(smtperrmsg_str) + 1; } /* "errormsg='%s' */ if (exitcode != EX_OK && errstr[0] != '\0') { s += 10 + strlen(errstr) + 2; } /* "exitcode=%s" */ s += 9 + strlen(exitcode_str); /* '\0' */ s++; line = xmalloc(s * sizeof(char)); /* build the log line */ p = line; n = snprintf(p, s, "host=%s tls=%s auth=%s ", acc->host, (acc->tls ? "on" : "off"), (acc->auth_mech ? "on" : "off")); s -= n; p += n; if (acc->auth_mech && acc->username) { n = snprintf(p, s, "user=%s ", acc->username); s -= n; p += n; } n = snprintf(p, s, "from=%s recipients=", acc->from); s -= n; p += n; l = recipients; while (!list_is_empty(l)) { l = l->next; n = snprintf(p, s, "%s,", (char *)(l->data)); s -= n; p += n; } /* delete the last ',' */ *(p - 1) = ' '; if (exitcode == EX_OK) { n = snprintf(p, s, "mailsize=%s ", mailsize_str); s -= n; p += n; } if (exitcode != EX_OK && errmsg) { n = snprintf(p, s, "smtpstatus=%s smtpmsg=%s ", smtpstatus_str, smtperrmsg_str); s -= n; p += n; } if (exitcode != EX_OK && errstr[0] != '\0') { /* hide single quotes to make the info easy to parse */ tmp = errstr; while (*tmp) { if (*tmp == '\'') { *tmp = '?'; } tmp++; } n = snprintf(p, s, "errormsg='%s' ", msmtp_sanitize_string(errstr)); s -= n; p += n; } (void)snprintf(p, s, "exitcode=%s", exitcode_str); free(mailsize_str); free(smtpstatus_str); free(smtperrmsg_str); return line; } /* * msmtp_log_to_file() * * Append a log entry to 'acc->logfile' with the following information: * - date/time * - the log line as delivered by msmtp_get_log_info */ void msmtp_log_to_file(const char *logfile, const char *loginfo) { FILE *f; time_t t; struct tm *tm; char *failure_reason; char time_str[64]; int e; /* get time */ if ((t = time(NULL)) < 0) { failure_reason = xasprintf(_("cannot get system time: %s"), strerror(errno)); goto log_failure; } if (!(tm = localtime(&t))) { failure_reason = xstrdup(_("cannot convert UTC time to local time")); goto log_failure; } (void)strftime(time_str, sizeof(time_str), "%b %d %H:%M:%S", tm); /* write log to file */ if (strcmp(logfile, "-") == 0) { f = stdout; } else { if (!(f = fopen(logfile, "a"))) { failure_reason = xasprintf(_("cannot open: %s"), strerror(errno)); goto log_failure; } if ((e = lock_file(f, TOOLS_LOCK_WRITE, 10)) != 0) { if (e == 1) { failure_reason = xasprintf( _("cannot lock (tried for %d seconds): %s"), 10, strerror(errno)); } else { failure_reason = xasprintf(_("cannot lock: %s"), strerror(errno)); } goto log_failure; } } if ((fputs(time_str, f) == EOF) || (fputc(' ', f) == EOF) || (fputs(loginfo, f) == EOF) || (fputc('\n', f) == EOF)) { failure_reason = xstrdup(_("output error")); goto log_failure; } if (f != stdout && fclose(f) != 0) { failure_reason = xstrdup(strerror(errno)); goto log_failure; } return; /* error exit target */ log_failure: print_error(_("cannot log to %s: %s"), logfile, failure_reason); free(failure_reason); if (loginfo) { print_error(_("log info was: %s"), loginfo); } } /* * msmtp_log_to_syslog() * * Log the information delivered by msmtp_get_log_info() to syslog * the facility_str must be one of "LOG_MAIL", "LOG_USER", "LOG_LOCAL0", ... * "LOG_LOCAL7" * If 'error' is set, LOG_ERR is used, else LOG_INFO is used. */ #ifdef HAVE_SYSLOG void msmtp_log_to_syslog(const char *facility_str, const char *loginfo, int error) { int facility; if (facility_str[4] == 'M') { facility = LOG_MAIL; } else if (facility_str[4] == 'U') { facility = LOG_USER; } else if (facility_str[9] == '0') { facility = LOG_LOCAL0; } else if (facility_str[9] == '1') { facility = LOG_LOCAL1; } else if (facility_str[9] == '2') { facility = LOG_LOCAL2; } else if (facility_str[9] == '3') { facility = LOG_LOCAL3; } else if (facility_str[9] == '4') { facility = LOG_LOCAL4; } else if (facility_str[9] == '5') { facility = LOG_LOCAL5; } else if (facility_str[9] == '6') { facility = LOG_LOCAL6; } else { facility = LOG_LOCAL7; } openlog(PACKAGE_NAME, 0, facility); syslog(error ? LOG_ERR : LOG_INFO, "%s", loginfo); closelog(); } #endif /* HAVE_SYSLOG */ /* * msmtp_construct_env_from() * * Build an envelope from address for the current user. * If maildomain is not NULL and not the empty string, it will be the domain * part of the address. Otherwise, the address won't have a domain part. */ char *msmtp_construct_env_from(const char *maildomain) { char *envelope_from; size_t len; envelope_from = get_username(); if (maildomain && *maildomain != '\0') { len = strlen(envelope_from); envelope_from = xrealloc(envelope_from, ((len + 1 + strlen(maildomain) + 1) * sizeof(char))); envelope_from[len] = '@'; strcpy(envelope_from + len + 1, maildomain); } return envelope_from; } /* * msmtp_print_version() * * Print --version information */ void msmtp_print_version(void) { char *sysconfdir; char *sysconffile; char *homedir; char *userconffile; printf(_("%s version %s\n"), PACKAGE_NAME, VERSION); /* TLS/SSL support */ printf(_("TLS/SSL library: %s\n"), #ifdef HAVE_LIBGNUTLS "GnuTLS" #elif defined (HAVE_OPENSSL) "OpenSSL" #else _("none") #endif ); /* Authentication support */ printf(_("Authentication library: %s\n" "Supported authentication methods:\n"), #ifdef HAVE_LIBGSASL "GNU SASL" #else _("built-in") #endif /* HAVE_LIBGSASL */ ); if (smtp_client_supports_authmech("PLAIN")) { printf("plain "); } if (smtp_client_supports_authmech("CRAM-MD5")) { printf("cram-md5 "); } if (smtp_client_supports_authmech("DIGEST-MD5")) { printf("digest-md5 "); } if (smtp_client_supports_authmech("GSSAPI")) { printf("gssapi "); } if (smtp_client_supports_authmech("EXTERNAL")) { printf("external "); } if (smtp_client_supports_authmech("LOGIN")) { printf("login "); } if (smtp_client_supports_authmech("NTLM")) { printf("ntlm "); } printf("\n"); /* Internationalized Domain Names support */ printf(_("IDN support: ")); #ifdef HAVE_LIBIDN printf(_("enabled")); #else printf(_("disabled")); #endif printf("\n"); /* Native language support */ printf(_("NLS: ")); #ifdef ENABLE_NLS printf(_("enabled")); printf(_(", LOCALEDIR is %s"), LOCALEDIR); #else printf(_("disabled")); #endif printf("\n"); sysconfdir = get_sysconfdir(); sysconffile = get_filename(sysconfdir, SYSCONFFILE); printf(_("System configuration file name: %s\n"), sysconffile); free(sysconffile); free(sysconfdir); homedir = get_homedir(); userconffile = get_filename(homedir, USERCONFFILE); printf(_("User configuration file name: %s\n"), userconffile); free(userconffile); free(homedir); printf("\n"); printf(_("Copyright (C) 2007 Martin Lambers and others.\n" "This is free software. You may redistribute copies of " "it under the terms of\n" "the GNU General Public License " ".\n" "There is NO WARRANTY, to the extent permitted by law.\n")); } /* * msmtp_print_help() * * Print --help information */ void msmtp_print_help(void) { printf(_("USAGE:\n\n" "Sendmail mode (default):\n" " %s [option...] [--] recipient...\n" " %s [option...] -t [--] [recipient...]\n" " Read a mail from standard input and transmit it to an SMTP " "or LMTP server.\n" "Server information mode:\n" " %s [option...] --serverinfo\n" " Print information about a server.\n" "Remote Message Queue Starting mode:\n" " %s [option...] --rmqs=host|@domain|#queue\n" " Send a Remote Message Queue Starting request to a server.\n" "\nOPTIONS:\n\n" "General options:\n" " --version Print version.\n" " --help Print help.\n" " -P, --pretend Print configuration info and " "exit.\n" " -d, --debug Print debugging information.\n" "Changing the mode of operation:\n" " -S, --serverinfo Print information about the " "server.\n" " --rmqs=host|@domain|#queue Send a Remote Message Queue " "Starting request.\n" "Configuration options:\n" " -C, --file=filename Set configuration file.\n" " -a, --account=id Use the given account instead of " "the account\n" " named \"default\"; its settings " "may be changed\n" " with command line options.\n" " --host=hostname Set the server, use only command " "line settings;\n" " do not use any configuration file " "data.\n" " --port=number Set port number.\n" " --timeout=(off|seconds) Set/unset network timeout in " "seconds.\n" " --protocol=(smtp|lmtp) Use the given sub protocol.\n" " --domain=string Set the argument of EHLO or LHLO " "command.\n" " --auth[=(on|off|method)] Enable/disable authentication and " "optionally\n" " choose the method.\n" " --user=[username] Set/unset user name for " "authentication.\n" " --tls[=(on|off)] Enable/disable TLS encryption.\n" " --tls-starttls[=(on|off)] Enable/disable STARTTLS for TLS.\n" " --tls-trust-file=[file] Set/unset trust file for TLS.\n" " --tls-key-file=[file] Set/unset private key file for " "TLS.\n" " --tls-cert-file=[file] Set/unset private cert file for " "TLS.\n" " --tls-certcheck[=(on|off)] Enable/disable server certificate " "checks for TLS.\n" " --tls-force-sslv3[=(on|off)] Enable/disable restriction to " "SSLv3.\n" "Options specific to sendmail mode:\n" " --auto-from[=(on|off)] Enable/disable automatic " "envelope-from addresses.\n" " -f, --from=address Set envelope from address.\n" " --maildomain=[domain] Set the domain for automatic " "envelope from\n" " addresses.\n" " -N, --dsn-notify=(off|cond) Set/unset DSN conditions.\n" " -R, --dsn-return=(off|ret) Set/unset DSN amount.\n" " --keepbcc[=(on|off)] Enable/disable preservation of the " "Bcc header.\n" " -X, --logfile=[file] Set/unset log file.\n" " --syslog[=(on|off|facility)] Enable/disable/configure syslog " "logging.\n" " -t, --read-recipients Read additional recipients from " "the mail.\n" " -- End of options.\n" "Accepted but ignored: -A, -B, -bm, -F, -G, -h, -i, -L, -m, -n, " "-O, -o, -v\n" "\nReport bugs to <%s>.\n"), prgname, prgname, prgname, prgname, PACKAGE_BUGREPORT); } /* * msmtp_cmdline() * * Process the command line */ typedef struct { /* the configuration */ int print_version; int print_help; int print_conf; int debug; int pretend; int read_recipients; /* mode of operation */ int sendmail; int serverinfo; int rmqs; char *rmqs_argument; /* account information from the command line */ account_t *cmdline_account; const char *account_id; char *user_conffile; /* the list of recipients */ list_t *recipients; } msmtp_cmdline_conf_t; /* long options without a corresponding short option */ #define LONGONLYOPT_VERSION 0 #define LONGONLYOPT_HELP 1 #define LONGONLYOPT_HOST 2 #define LONGONLYOPT_PORT 3 #define LONGONLYOPT_TIMEOUT 4 #define LONGONLYOPT_AUTH 5 #define LONGONLYOPT_USER 6 #define LONGONLYOPT_TLS 7 #define LONGONLYOPT_TLS_STARTTLS 8 #define LONGONLYOPT_TLS_TRUST_FILE 9 #define LONGONLYOPT_TLS_KEY_FILE 10 #define LONGONLYOPT_TLS_CERT_FILE 11 #define LONGONLYOPT_TLS_CERTCHECK 12 #define LONGONLYOPT_TLS_FORCE_SSLV3 13 #define LONGONLYOPT_PROTOCOL 14 #define LONGONLYOPT_DOMAIN 15 #define LONGONLYOPT_KEEPBCC 16 #define LONGONLYOPT_RMQS 17 #define LONGONLYOPT_SYSLOG 18 #define LONGONLYOPT_MAILDOMAIN 19 #define LONGONLYOPT_AUTO_FROM 20 int msmtp_cmdline(msmtp_cmdline_conf_t *conf, int argc, char *argv[]) { struct option options[] = { { "version", no_argument, 0, LONGONLYOPT_VERSION }, { "help", no_argument, 0, LONGONLYOPT_HELP }, { "pretend", no_argument, 0, 'P' }, /* accept an optional argument for sendmail compatibility: */ { "debug", optional_argument, 0, 'd' }, { "serverinfo", no_argument, 0, 'S' }, { "rmqs", required_argument, 0, LONGONLYOPT_RMQS }, { "file", required_argument, 0, 'C' }, { "account", required_argument, 0, 'a' }, { "host", required_argument, 0, LONGONLYOPT_HOST }, { "port", required_argument, 0, LONGONLYOPT_PORT }, { "timeout", required_argument, 0, LONGONLYOPT_TIMEOUT}, /* for compatibility with versions <= 1.4.1: */ { "connect-timeout", required_argument, 0, LONGONLYOPT_TIMEOUT}, { "auto-from", optional_argument, 0, LONGONLYOPT_AUTO_FROM }, { "from", required_argument, 0, 'f' }, { "maildomain", required_argument, 0, LONGONLYOPT_MAILDOMAIN }, { "auth", optional_argument, 0, LONGONLYOPT_AUTH }, { "user", required_argument, 0, LONGONLYOPT_USER }, { "tls", optional_argument, 0, LONGONLYOPT_TLS }, { "tls-starttls", optional_argument, 0, LONGONLYOPT_TLS_STARTTLS }, { "tls-trust-file", required_argument, 0, LONGONLYOPT_TLS_TRUST_FILE }, { "tls-key-file", required_argument, 0, LONGONLYOPT_TLS_KEY_FILE }, { "tls-cert-file", required_argument, 0, LONGONLYOPT_TLS_CERT_FILE }, { "tls-certcheck", optional_argument, 0, LONGONLYOPT_TLS_CERTCHECK }, { "tls-force-sslv3", optional_argument, 0, LONGONLYOPT_TLS_FORCE_SSLV3 }, { "dsn-notify", required_argument, 0, 'N' }, { "dsn-return", required_argument, 0, 'R' }, { "protocol", required_argument, 0, LONGONLYOPT_PROTOCOL }, { "domain", required_argument, 0, LONGONLYOPT_DOMAIN }, { "keepbcc", optional_argument, 0, LONGONLYOPT_KEEPBCC }, { "logfile", required_argument, 0, 'X' }, { "syslog", optional_argument, 0, LONGONLYOPT_SYSLOG }, { "read-recipients", no_argument, 0, 't' }, { 0, 0, 0, 0 } }; int rcptc; char **rcptv; int error_code; int c; int i; list_t *lp; /* the program name */ prgname = get_prgname(argv[0]); /* the configuration */ conf->print_version = 0; conf->print_help = 0; conf->print_conf = 0; conf->debug = 0; conf->pretend = 0; conf->read_recipients = 0; /* mode of operation */ conf->sendmail = 1; conf->serverinfo = 0; conf->rmqs = 0; conf->rmqs_argument = NULL; /* account information from the command line */ conf->cmdline_account = account_new(NULL, NULL); conf->account_id = NULL; conf->user_conffile = NULL; /* the recipients */ conf->recipients = NULL; /* process the command line */ error_code = 0; for (;;) { c = getopt_long(argc, argv, "Pd::SC:a:f:N:R:X:tA:B:b:F:Gh:iL:mnO:o:v", options, NULL); if (c == -1) { break; } switch(c) { case LONGONLYOPT_VERSION: conf->print_version = 1; conf->sendmail = 0; conf->serverinfo = 0; break; case LONGONLYOPT_HELP: conf->print_help = 1; conf->sendmail = 0; conf->serverinfo = 0; break; case 'P': conf->print_conf = 1; conf->pretend = 1; break; case 'd': conf->print_conf = 1; conf->debug = 1; /* only care about the optional argument if it's "0.1", which is * the only argument that's documented for sendmail: it prints * version information */ if (optarg && strcmp(optarg, "0.1") == 0) { conf->print_version = 1; } break; case 'S': if (conf->rmqs) { print_error(_("cannot use both --serverinfo and --rmqs")); error_code = 1; } else { conf->serverinfo = 1; conf->sendmail = 0; conf->rmqs = 0; } break; case LONGONLYOPT_RMQS: if (conf->serverinfo) { print_error(_("cannot use both --serverinfo and --rmqs")); error_code = 1; } else { conf->rmqs = 1; conf->rmqs_argument = optarg; conf->sendmail = 0; conf->serverinfo = 0; } break; case 'C': free(conf->user_conffile); conf->user_conffile = xstrdup(optarg); break; case 'a': if (conf->cmdline_account->host) { print_error(_("cannot use both --host and --account")); error_code = 1; } else { conf->account_id = optarg; } break; case LONGONLYOPT_HOST: if (conf->account_id) { print_error(_("cannot use both --host and --account")); error_code = 1; } else { free(conf->cmdline_account->host); conf->cmdline_account->host = xstrdup(optarg); conf->cmdline_account->mask |= ACC_HOST; } break; case LONGONLYOPT_PORT: conf->cmdline_account->port = get_pos_int(optarg); if (conf->cmdline_account->port < 1 || conf->cmdline_account->port > 65535) { print_error(_("invalid argument %s for %s"), optarg, "--port"); error_code = 1; } conf->cmdline_account->mask |= ACC_PORT; break; case LONGONLYOPT_TIMEOUT: if (is_off(optarg)) { conf->cmdline_account->timeout = 0; } else { conf->cmdline_account->timeout = get_pos_int(optarg); if (conf->cmdline_account->timeout < 1) { print_error(_("invalid argument %s for %s"), optarg, "--timeout"); error_code = 1; } } conf->cmdline_account->mask |= ACC_TIMEOUT; break; case LONGONLYOPT_AUTO_FROM: if (!optarg || is_on(optarg)) { conf->cmdline_account->auto_from = 1; } else if (is_off(optarg)) { conf->cmdline_account->auto_from = 0; } else { print_error(_("invalid argument %s for %s"), optarg, "--auto-from"); error_code = 1; } conf->cmdline_account->mask |= ACC_AUTO_FROM; break; case 'f': free(conf->cmdline_account->from); conf->cmdline_account->from = xstrdup(optarg); conf->cmdline_account->mask |= ACC_FROM; break; case LONGONLYOPT_MAILDOMAIN: free(conf->cmdline_account->maildomain); conf->cmdline_account->maildomain = (*optarg == '\0') ? NULL : xstrdup(optarg); conf->cmdline_account->mask |= ACC_MAILDOMAIN; break; case LONGONLYOPT_AUTH: free(conf->cmdline_account->auth_mech); if (!optarg || is_on(optarg)) { conf->cmdline_account->auth_mech = xstrdup(""); } else if (is_off(optarg)) { conf->cmdline_account->auth_mech = NULL; } else if (check_auth_arg(optarg) == 0) { conf->cmdline_account->auth_mech = xstrdup(optarg); } else { conf->cmdline_account->auth_mech = NULL; print_error(_("invalid argument %s for %s"), optarg, "--auth"); error_code = 1; } conf->cmdline_account->mask |= ACC_AUTH_MECH; break; case LONGONLYOPT_USER: free(conf->cmdline_account->username); conf->cmdline_account->username = (*optarg == '\0') ? NULL : xstrdup(optarg); conf->cmdline_account->mask |= ACC_USERNAME; break; case LONGONLYOPT_TLS: if (!optarg || is_on(optarg)) { conf->cmdline_account->tls = 1; } else if (is_off(optarg)) { conf->cmdline_account->tls = 0; } else { print_error(_("invalid argument %s for %s"), optarg, "--tls"); error_code = 1; } conf->cmdline_account->mask |= ACC_TLS; break; case LONGONLYOPT_TLS_STARTTLS: if (!optarg || is_on(optarg)) { conf->cmdline_account->tls_nostarttls = 0; } else if (is_off(optarg)) { conf->cmdline_account->tls_nostarttls = 1; } else { print_error(_("invalid argument %s for %s"), optarg, "--tls-starttls"); error_code = 1; } conf->cmdline_account->mask |= ACC_TLS_NOSTARTTLS; break; case LONGONLYOPT_TLS_TRUST_FILE: free(conf->cmdline_account->tls_trust_file); if (*optarg) { conf->cmdline_account->tls_trust_file = expand_tilde(optarg); } else { conf->cmdline_account->tls_trust_file = NULL; } conf->cmdline_account->mask |= ACC_TLS_TRUST_FILE; break; case LONGONLYOPT_TLS_KEY_FILE: free(conf->cmdline_account->tls_key_file); if (*optarg) { conf->cmdline_account->tls_key_file = expand_tilde(optarg); } else { conf->cmdline_account->tls_key_file = NULL; } conf->cmdline_account->mask |= ACC_TLS_KEY_FILE; break; case LONGONLYOPT_TLS_CERT_FILE: free(conf->cmdline_account->tls_cert_file); if (*optarg) { conf->cmdline_account->tls_cert_file = expand_tilde(optarg); } else { conf->cmdline_account->tls_cert_file = NULL; } conf->cmdline_account->mask |= ACC_TLS_CERT_FILE; break; case LONGONLYOPT_TLS_CERTCHECK: if (!optarg || is_on(optarg)) { conf->cmdline_account->tls_nocertcheck = 0; } else if (is_off(optarg)) { conf->cmdline_account->tls_nocertcheck = 1; } else { print_error(_("invalid argument %s for %s"), optarg, "--tls-certcheck"); error_code = 1; } conf->cmdline_account->mask |= ACC_TLS_NOCERTCHECK; break; case LONGONLYOPT_TLS_FORCE_SSLV3: if (!optarg || is_on(optarg)) { conf->cmdline_account->tls_force_sslv3 = 1; } else if (is_off(optarg)) { conf->cmdline_account->tls_force_sslv3 = 0; } else { print_error(_("invalid argument %s for %s"), optarg, "--tls-force-sslv3"); error_code = 1; } conf->cmdline_account->mask |= ACC_TLS_FORCE_SSLV3; break; case 'N': free(conf->cmdline_account->dsn_notify); if (is_off(optarg)) { conf->cmdline_account->dsn_notify = NULL; } else if (check_dsn_notify_arg(optarg) == 0) { conf->cmdline_account->dsn_notify = xstrdup(optarg); } else { print_error(_("invalid argument %s for %s"), optarg, "--dsn-notify"); error_code = 1; } conf->cmdline_account->mask |= ACC_DSN_NOTIFY; break; case 'R': /* be compatible to both sendmail and the dsn_notify command */ free(conf->cmdline_account->dsn_return); if (is_off(optarg)) { conf->cmdline_account->dsn_return = NULL; } else if (strcmp(optarg, "hdrs") == 0 || strcmp(optarg, "headers") == 0) { conf->cmdline_account->dsn_return = xstrdup("HDRS"); } else if (strcmp(optarg, "full") == 0) { conf->cmdline_account->dsn_return = xstrdup("FULL"); } else { print_error(_("invalid argument %s for %s"), optarg, "--dsn-return"); error_code = 1; } conf->cmdline_account->mask |= ACC_DSN_RETURN; break; case LONGONLYOPT_PROTOCOL: conf->cmdline_account->mask |= ACC_PROTOCOL; if (strcmp(optarg, "smtp") == 0) { conf->cmdline_account->protocol = SMTP_PROTO_SMTP; } else if (strcmp(optarg, "lmtp") == 0) { conf->cmdline_account->protocol = SMTP_PROTO_LMTP; } else { print_error(_("invalid argument %s for %s"), optarg, "--protocol"); error_code = 1; } break; case LONGONLYOPT_DOMAIN: free(conf->cmdline_account->domain); conf->cmdline_account->domain = xstrdup(optarg); conf->cmdline_account->mask |= ACC_DOMAIN; break; case LONGONLYOPT_KEEPBCC: if (!optarg || is_on(optarg)) { conf->cmdline_account->keepbcc = 1; } else if (is_off(optarg)) { conf->cmdline_account->keepbcc = 0; } else { print_error(_("invalid argument %s for %s"), optarg, "--keepbcc"); error_code = 1; } conf->cmdline_account->mask |= ACC_KEEPBCC; break; case 'X': free(conf->cmdline_account->logfile); if (*optarg) { conf->cmdline_account->logfile = expand_tilde(optarg); } else { conf->cmdline_account->logfile = NULL; } conf->cmdline_account->mask |= ACC_LOGFILE; break; case LONGONLYOPT_SYSLOG: free(conf->cmdline_account->syslog); if (!optarg || is_on(optarg)) { conf->cmdline_account->syslog = get_default_syslog_facility(); } else if (is_off(optarg)) { conf->cmdline_account->syslog = NULL; } else { if (check_syslog_arg(optarg) != 0) { print_error(_("invalid argument %s for %s"), optarg, "--syslog"); error_code = 1; } else { conf->cmdline_account->syslog = xstrdup(optarg); } } conf->cmdline_account->mask |= ACC_SYSLOG; break; case 't': conf->read_recipients = 1; break; case 'b': /* only m makes sense */ if (strcmp(optarg, "m") != 0) { print_error(_("unsupported operation mode b%s"), optarg); error_code = 1; } break; case 'A': case 'B': case 'F': case 'G': case 'h': case 'i': case 'L': case 'm': case 'n': case 'O': case 'o': case 'v': break; /* unknown option */ default: error_code = 1; break; } if (error_code) { break; } } if (error_code) { return EX_USAGE; } /* the list of recipients */ rcptc = argc - optind; rcptv = &(argv[optind]); conf->recipients = list_new(); lp = conf->recipients; for (i = 0; i < rcptc; i++) { list_insert(lp, xstrdup(rcptv[i])); lp = lp->next; } return EX_OK; } /* * msmtp_get_conffile_accounts() * Read the system and user configuration files and merge the data */ int msmtp_get_conffile_accounts(list_t **account_list, int print_info, const char *user_conffile, char **loaded_system_conffile, char **loaded_user_conffile) { char *errstr; char *system_confdir; char *system_conffile; char *homedir; char *real_user_conffile; list_t *system_account_list; list_t *user_account_list; list_t *lps; list_t *lpu; int e; *loaded_system_conffile = NULL; *loaded_user_conffile = NULL; /* Read the system configuration file. * It is not an error if system_conffile cannot be opened, * but it is an error is the file content is invalid */ system_confdir = get_sysconfdir(); system_conffile = get_filename(system_confdir, SYSCONFFILE); free(system_confdir); if ((e = get_conf(system_conffile, 0, &system_account_list, &errstr)) != CONF_EOK) { if (e == CONF_ECANTOPEN) { if (print_info) { printf(_("ignoring system configuration file %s: %s\n"), system_conffile, msmtp_sanitize_string(errstr)); } } else { print_error("%s: %s", system_conffile, msmtp_sanitize_string(errstr)); return (e == CONF_EIO) ? EX_IOERR : EX_CONFIG; } } else { if (print_info) { printf(_("loaded system configuration file %s\n"), system_conffile); } *loaded_system_conffile = xstrdup(system_conffile); } free(system_conffile); /* Read the user configuration file. * It is not an error if user_conffile cannot be opened (unless it was * chosen with -C/--file), but it is an error is the file content is * invalid */ if (user_conffile) { real_user_conffile = xstrdup(user_conffile); } else { homedir = get_homedir(); real_user_conffile = get_filename(homedir, USERCONFFILE); free(homedir); } if ((e = get_conf(real_user_conffile, 1, &user_account_list, &errstr)) != CONF_EOK) { if (e == CONF_ECANTOPEN) { /* If the configuration file was set with -C/--file, it is an * error if we cannot open it */ if (user_conffile) { print_error("%s: %s", real_user_conffile, msmtp_sanitize_string(errstr)); return EX_IOERR; } /* otherwise, we can ignore it */ if (print_info) { printf(_("ignoring user configuration file %s: %s\n"), real_user_conffile, msmtp_sanitize_string(errstr)); } } else { print_error("%s: %s", real_user_conffile, msmtp_sanitize_string(errstr)); return (e == CONF_EIO) ? EX_IOERR : EX_CONFIG; } } else { if (print_info) { printf(_("loaded user configuration file %s\n"), real_user_conffile); } *loaded_user_conffile = xstrdup(real_user_conffile); } free(real_user_conffile); /* Merge system_account_list and user_account_list into account_list. * If an account exist in both files, only the one from the user conffile is * kept. It is important that the order of accounts is maintained, so that * --from can choose the *first* account with a matching envelope from * address. */ if (*loaded_system_conffile && *loaded_user_conffile) { lpu = user_account_list; lps = system_account_list; while (!list_is_empty(lps)) { lps = lps->next; if (!find_account(user_account_list, ((account_t *)lps->data)->id)) { list_insert(lpu, account_copy(lps->data)); lpu = lpu->next; } } *account_list = user_account_list; list_xfree(system_account_list, account_free); } else if (*loaded_system_conffile) { *account_list = system_account_list; } else if (*loaded_user_conffile) { *account_list = user_account_list; } else { *account_list = list_new(); } return EX_OK; } /* * msmtp_print_conf * * Print configuration information, for example for --pretend */ void msmtp_print_conf(msmtp_cmdline_conf_t conf, account_t *account) { if (account->id && account->conffile) { printf(_("using account %s from %s\n"), account->id, account->conffile); } printf("host = %s\n" "port = %d\n", account->host, account->port); printf("timeout = "); if (account->timeout <= 0) { printf(_("off\n")); } else { if (account->timeout > 1) { printf(_("%d seconds\n"), account->timeout); } else { printf(_("1 second\n")); } } printf("protocol = %s\n" "domain = %s\n", account->protocol == SMTP_PROTO_SMTP ? "smtp" : "lmtp", account->domain); printf("auth = "); if (!account->auth_mech) { printf(_("none\n")); } else if (account->auth_mech[0] == '\0') { printf(_("choose\n")); } else { printf("%s\n", account->auth_mech); } printf("user = %s\n" "password = %s\n" "ntlmdomain = %s\n" "tls = %s\n" "tls_starttls = %s\n" "tls_trust_file = %s\n" "tls_key_file = %s\n" "tls_cert_file = %s\n" "tls_certcheck = %s\n" "tls_force_sslv3 = %s\n", account->username ? account->username : _("(not set)"), account->password ? "*" : _("(not set)"), account->ntlmdomain ? account->ntlmdomain : _("(not set)"), account->tls ? _("on") : _("off"), account->tls_nostarttls ? _("off") : _("on"), account->tls_trust_file ? account->tls_trust_file : _("(not set)"), account->tls_key_file ? account->tls_key_file : _("(not set)"), account->tls_cert_file ? account->tls_cert_file : _("(not set)"), account->tls_nocertcheck ? _("off") : _("on"), account->tls_force_sslv3 ? _("on") : _("off")); if (conf.sendmail) { printf("auto_from = %s\n" "maildomain = %s\n" "from = %s\n" "dsn_notify = %s\n" "dsn_return = %s\n" "keepbcc = %s\n" "logfile = %s\n" "syslog = %s\n", account->auto_from ? _("on") : _("off"), account->maildomain ? account->maildomain : _("(not set)"), account->from ? account->from : _("(not set)"), account->dsn_notify ? account->dsn_notify : _("(not set)"), account->dsn_return ? account->dsn_return : _("(not set)"), account->keepbcc ? _("on") : _("off"), account->logfile ? account->logfile : _("(not set)"), account->syslog ? account->syslog : _("(not set)")); if (conf.read_recipients) { printf(_("reading recipients from the command line " "and the mail\n")); } else { printf(_("reading recipients from the command line\n")); } } if (conf.rmqs) { printf("RMQS argument = %s\n", conf.rmqs_argument); } } /* * The main function. * It returns values from sysexits.h (like sendmail does). */ int main(int argc, char *argv[]) { msmtp_cmdline_conf_t conf; /* account information from the configuration file(s) */ list_t *account_list = NULL; char *loaded_system_conffile = NULL; char *loaded_user_conffile = NULL; /* the account data that will be used */ account_t *account = NULL; /* error handling */ char *errstr; list_t *errmsg; int error_code; int e; list_t *lp; /* misc */ #ifdef HAVE_TLS int tls_lib_initialized = 0; #endif int net_lib_initialized = 0; /* the size of a sent mail */ long mailsize; /* special LMTP error info */ list_t *lmtp_errstrs; list_t *lmtp_error_msgs; list_t *lp_lmtp_errstrs; list_t *lp_lmtp_error_msgs; /* log information */ char *log_info; /* needed to get the default port */ struct servent *se; /* Avoid the side effects of text mode interpretations on DOS systems. */ #ifdef W32_NATIVE _setmode(_fileno(stdin), _O_BINARY); _fmode = _O_BINARY; #elif defined DJGPP setmode(fileno(stdin), O_BINARY); _fmode = O_BINARY; #endif errstr = NULL; errmsg = NULL; /* internationalization with gettext */ #ifdef ENABLE_NLS setlocale(LC_ALL, ""); bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); #endif /* the command line */ if ((error_code = msmtp_cmdline(&conf, argc, argv)) != EX_OK) { goto exit; } if (conf.print_version) { msmtp_print_version(); } if (conf.print_help) { msmtp_print_help(); } if (!conf.sendmail && !conf.serverinfo && !conf.rmqs && !conf.print_conf) { error_code = EX_OK; goto exit; } /* check the list of recipients */ if (conf.sendmail && list_is_empty(conf.recipients) && !conf.read_recipients && !conf.pretend) { print_error(_("no recipients given")); error_code = EX_USAGE; goto exit; } if ((conf.serverinfo || conf.rmqs) && !list_is_empty(conf.recipients)) { print_error(_("too many arguments")); error_code = EX_USAGE; goto exit; } /* get the account to be used, either from the conffile(s) or from the * command line */ if (!conf.cmdline_account->host) { if ((error_code = msmtp_get_conffile_accounts(&account_list, (conf.pretend || conf.debug), conf.user_conffile, &loaded_system_conffile, &loaded_user_conffile)) != EX_OK) { goto exit; } if (!conf.account_id) { if (conf.cmdline_account->from) { /* No account was chosen, but the envelope from address is * given. Choose the right account with this address. */ account = account_copy(find_account_by_envelope_from( account_list, conf.cmdline_account->from)); } if (!account) { /* No envelope from address or no matching account. * Use default. */ conf.account_id = "default"; } } if (!account && !(account = account_copy(find_account(account_list, conf.account_id)))) { if (loaded_system_conffile && loaded_user_conffile) { print_error(_("account %s not found in %s and %s"), conf.account_id, loaded_system_conffile, loaded_user_conffile); } else if (loaded_system_conffile) { print_error(_("account %s not found in %s"), conf.account_id, loaded_system_conffile); } else if (loaded_user_conffile) { print_error(_("account %s not found in %s"), conf.account_id, loaded_user_conffile); } else /* no conffile was read */ { print_error(_("account %s not found: " "no configuration file available"), conf.account_id); } error_code = EX_CONFIG; goto exit; } override_account(account, conf.cmdline_account); } else { account = account_copy(conf.cmdline_account); } /* OK, we're using the settings in 'account'. Complete them and check * them. */ if (account->port == 0) { if (account->protocol == SMTP_PROTO_SMTP) { if (account->tls && account->tls_nostarttls) { se = getservbyname("ssmtp", NULL); account->port = se ? ntohs(se->s_port) : 465; } else { se = getservbyname("smtp", NULL); account->port = se ? ntohs(se->s_port) : 25; } } else /* LMTP. Has no default port as of 2006-06-17. */ { se = getservbyname("lmtp", NULL); if (se) { account->port = ntohs(se->s_port); } } } if (conf.sendmail && account->auto_from) { free(account->from); account->from = msmtp_construct_env_from(account->maildomain); } if (check_account(account, conf.sendmail, &errstr) != CONF_EOK) { if (account->id && account->conffile) { print_error(_("account %s from %s: %s"), account->id, account->conffile, msmtp_sanitize_string(errstr)); } else { print_error("%s", msmtp_sanitize_string(errstr)); } error_code = EX_CONFIG; goto exit; } /* print configuration */ if (conf.print_conf) { msmtp_print_conf(conf, account); } /* stop if there's nothing to do */ if (conf.pretend || (!conf.sendmail && !conf.serverinfo && !conf.rmqs)) { error_code = EX_OK; goto exit; } /* initialize libraries */ #ifndef HAVE_SYSLOG if (conf.sendmail && account->syslog) { print_error(_("this platform does not support syslog logging")); error_code = EX_UNAVAILABLE; goto exit; } #endif /* not HAVE_SYSLOG */ if ((conf.sendmail || conf.rmqs) /* serverinfo does not use auth */ && account->auth_mech && (strcmp(account->auth_mech, "") != 0) && !smtp_client_supports_authmech(account->auth_mech)) { print_error(_("support for authentication method %s " "is not compiled in"), account->auth_mech); error_code = EX_UNAVAILABLE; goto exit; } if ((e = net_lib_init(&errstr)) != NET_EOK) { print_error(_("cannot initialize networking: %s"), msmtp_sanitize_string(errstr)); error_code = EX_SOFTWARE; goto exit; } net_lib_initialized = 1; if (account->tls) { #ifdef HAVE_TLS if ((e = tls_lib_init(&errstr)) != TLS_EOK) { print_error(_("cannot initialize TLS library: %s"), msmtp_sanitize_string(errstr)); error_code = EX_SOFTWARE; goto exit; } tls_lib_initialized = 1; #else /* not HAVE_TLS */ print_error(_("support for TLS is not compiled in")); error_code = EX_UNAVAILABLE; goto exit; #endif /* not HAVE_TLS */ } /* do the work */ if (conf.sendmail) { if ((error_code = msmtp_sendmail(account, conf.recipients, conf.read_recipients, stdin, conf.debug, &mailsize, &lmtp_errstrs, &lmtp_error_msgs, &errmsg, &errstr)) != EX_OK) { if (account->protocol == SMTP_PROTO_LMTP && lmtp_errstrs) { lp_lmtp_errstrs = lmtp_errstrs; lp_lmtp_error_msgs = lmtp_error_msgs; while (!list_is_empty(lp_lmtp_errstrs)) { lp_lmtp_errstrs = lp_lmtp_errstrs->next; lp_lmtp_error_msgs = lp_lmtp_error_msgs->next; if (lp_lmtp_errstrs->data) { print_error("%s", msmtp_sanitize_string( lp_lmtp_errstrs->data)); if ((lp = lp_lmtp_error_msgs->data)) { while (!list_is_empty(lp)) { lp = lp->next; print_error(_("LMTP server message: %s"), msmtp_sanitize_string(lp->data)); } list_xfree(lp_lmtp_error_msgs->data, free); } } } list_xfree(lmtp_errstrs, free); list_free(lmtp_error_msgs); if (account->id && account->conffile) { print_error(_("could not send mail to all recipients " "(account %s from %s)"), account->id, account->conffile); } else { print_error(_("could not send mail to all recipients")); } } else { if (errstr) { print_error("%s", msmtp_sanitize_string(errstr)); } if (errmsg) { lp = errmsg; while (!list_is_empty(lp)) { lp = lp->next; print_error(_("server message: %s"), msmtp_sanitize_string(lp->data)); } } if (account->id && account->conffile) { print_error(_("could not send mail (account %s from %s)"), account->id, account->conffile); } else { print_error(_("could not send mail")); } } } if (account->logfile || account->syslog) { if (account->protocol == SMTP_PROTO_LMTP && lmtp_errstrs) { /* errstr is NULL; print short info to it */ errstr = xasprintf( _("delivery to one or more recipients failed")); /* we know that errmsg is NULL. that's ok. */ } log_info = msmtp_get_log_info(account, conf.recipients, mailsize, errmsg, errstr, error_code); if (account->logfile) { msmtp_log_to_file(account->logfile, log_info); } #ifdef HAVE_SYSLOG if (account->syslog) { msmtp_log_to_syslog(account->syslog, log_info, (error_code != EX_OK)); } #endif free(log_info); } } else if (conf.serverinfo) { if ((error_code = msmtp_serverinfo(account, conf.debug, &errmsg, &errstr)) != EX_OK) { if (errstr) { print_error("%s", msmtp_sanitize_string(errstr)); } if (errmsg) { lp = errmsg; while (!list_is_empty(lp)) { lp = lp->next; print_error(_("server message: %s"), msmtp_sanitize_string(lp->data)); } } } } else /* rmqs */ { if ((error_code = msmtp_rmqs(account, conf.debug, conf.rmqs_argument, &errmsg, &errstr)) != EX_OK) { if (errstr) { print_error("%s", msmtp_sanitize_string(errstr)); } if (errmsg) { lp = errmsg; while (!list_is_empty(lp)) { lp = lp->next; print_error(_("server message: %s"), msmtp_sanitize_string(lp->data)); } } } } exit: /* clean up */ free(loaded_system_conffile); free(loaded_user_conffile); #ifdef HAVE_TLS if (tls_lib_initialized) { tls_lib_deinit(); } #endif /* HAVE_TLS */ if (net_lib_initialized) { net_lib_deinit(); } if (account_list) { list_xfree(account_list, account_free); } account_free(conf.cmdline_account); account_free(account); if (conf.recipients) { list_xfree(conf.recipients, free); } free(errstr); if (errmsg) { list_xfree(errmsg, free); } return error_code; }