/* * A libESMTP Example Application. * Copyright (C) 2001,2002 Brian Stafford * * 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 2 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* This program attemps to mimic the sendmail program behavior using libESMTP. * * Adapted from the libESMTP's mail-file example by José Fonseca. */ #define _XOPEN_SOURCE 500 #include #include #include #include #include #include #include #include #include #include #include char *from = NULL; char *host = NULL; char *user = NULL; char *pass = NULL; enum starttls_option starttls = Starttls_DISABLED; char *certificate_passphrase = NULL; extern void parse_rcfile(void); /* Callback function to read the message from a file. Since libESMTP does not * provide callbacks which translate line endings, one must be provided by the * application. * * The message is read a line at a time and the newlines converted to \r\n. * Unfortunately, RFC 822 states that bare \n and \r are acceptable in messages * and that individually they do not constitute a line termination. This * requirement cannot be reconciled with storing messages with Unix line * terminations. RFC 2822 rescues this situation slightly by prohibiting lone * \r and \n in messages. * * The following code cannot therefore work correctly in all situations. * Furthermore it is very inefficient since it must search for the \n. */ #define BUFLEN 8192 const char * readlinefp_cb (void **buf, int *len, void *arg) { int octets; if (*buf == NULL) *buf = malloc (BUFLEN); if (len == NULL) { rewind ((FILE *) arg); return NULL; } if (fgets (*buf, BUFLEN - 2, (FILE *) arg) == NULL) octets = 0; else { char *p = strchr (*buf, '\0'); if (p[-1] == '\n' && p[-2] != '\r') { strcpy (p - 1, "\r\n"); p++; } octets = p - (char *) *buf; } *len = octets; return *buf; } void monitor_cb (const char *buf, int buflen, int writing, void *arg) { FILE *fp = arg; if (writing == SMTP_CB_HEADERS) { fputs ("H: ", fp); fwrite (buf, 1, buflen, fp); return; } fputs (writing ? "C: " : "S: ", fp); fwrite (buf, 1, buflen, fp); if (buf[buflen - 1] != '\n') putc ('\n', fp); } void usage (void) { fputs ("usage: esmtp [options] mailbox [mailbox ...]\n", stderr); } void version (void) { char buf[32]; smtp_version (buf, sizeof buf, 0); printf ("libESMTP version %s\n", buf); } /* Callback to request user/password info. Not thread safe. */ int authinteract (auth_client_request_t request, char **result, int fields, void *arg) { int i; for (i = 0; i < fields; i++) { if (request[i].flags & AUTH_USER && user) result[i] = user; else if (request[i].flags & AUTH_PASS && pass) result[i] = pass; else return 0; } return 1; } int tlsinteract (char *buf, int buflen, int rwflag, void *arg) { char *pw; int len; if (certificate_passphrase) { pw = certificate_passphrase; len = strlen (pw); if (len + 1 > buflen) return 0; strcpy (buf, pw); return len; } else return 0; } /* Callback to print the recipient status. */ void print_recipient_status (smtp_recipient_t recipient, const char *mailbox, void *arg) { const smtp_status_t *status; status = smtp_recipient_status (recipient); fprintf (stderr, "%s: %d %s\n", mailbox, status->code, status->text); } int main (int argc, char **argv) { smtp_session_t session; smtp_message_t message; smtp_recipient_t recipient; auth_context_t authctx; const smtp_status_t *status; struct sigaction sa; int c; int ret; enum notify_flags notify = Notify_NOTSET; /* Parse the rc file. */ parse_rcfile(); /* This program sends only one message at a time. Create an SMTP session. */ auth_client_init (); session = smtp_create_session (); while ((c = getopt (argc, argv, "A:B:b:C:cd:e:F:f:Gh:IiL:M:mN:nO:o:p:q:R:r:sTtV:vX:")) != EOF) switch (c) { case 'A': /* Use alternate sendmail/submit.cf */ break; case 'B': /* Body type */ break; case 'C': /* Select configuration file */ break; case 'F': /* Set full name */ break; case 'G': /* Relay (gateway) submission */ break; case 'I': /* Initialize alias database */ break; case 'L': /* Program label */ break; case 'M': /* Define macro */ break; case 'N': /* Delivery status notifications */ if (strcmp (optarg, "never") == 0) notify = Notify_NEVER; else { if (strstr (optarg, "failure")) notify |= Notify_FAILURE; if (strstr (optarg, "delay")) notify |= Notify_DELAY; if (strstr (optarg, "success")) notify |= Notify_SUCCESS; } break; case 'R': /* What to return */ break; case 'T': /* Set timeout interval */ break; case 'X': /* Traffic log file */ break; case 'V': /* Set original envelope id */ break; case 'b': /* Operations mode */ c = (optarg == NULL) ? ' ' : *optarg; switch (c) { case 'm': /* Deliver mail in the usual way */ break; case 'i': /* Initialize the alias database */ break; case 'a': /* Go into ARPANET mode */ case 'd': /* Run as a daemon */ case 'D': /* Run as a daemon in foreground */ case 'h': /* Print the persistent host status database */ case 'H': /* Purge expired entries from the persistent host * status database */ case 'p': /* Print a listing of the queue(s) */ case 'P': /* Print number of entries in the queue(s) */ case 's': /* Use the SMTP protocol as described in RFC821 * on standard input and output */ case 't': /* Run in address test mode */ case 'v': /* Verify names only */ fprintf (stderr, "Unsupported operation mode %c\n", c); exit (64); break; default: fprintf (stderr, "Invalid operation mode %c\n", c); exit (64); break; } break; case 'c': /* Connect to non-local mailers */ break; case 'd': /* Debugging */ break; case 'e': /* Error message disposition */ break; case 'f': /* From address */ case 'r': /* Obsolete -f flag */ from = optarg; break; case 'h': /* Hop count */ break; case 'i': /* Don't let dot stop me */ break; case 'm': /* Send to me too */ break; case 'n': /* don't alias */ break; case 'o': /* Set option */ break; case 'p': /* Set protocol */ break; case 'q': /* Run queue files at intervals */ if (optarg[0] == '!') { /* Negate the meaning of pattern match */ optarg++; } switch (optarg[0]) { case 'G': /* Limit by queue group name */ break; case 'I': /* Limit by ID */ break; case 'R': /* Limit by recipient */ break; case 'S': /* Limit by sender */ break; case 'f': /* Foreground queue run */ break; case 'p': /* Persistent queue */ if (optarg[1] == '\0') break; ++optarg; default: fprintf (stderr, "Invalid -q value\n"); exit (64); break; } break; case 's': /* Save From lines in headers */ break; case 't': /* Read recipients from message */ fprintf (stderr, "Unsupported option 't'\n"); exit (64); break; case 'v': /* Verbose */ smtp_set_monitorcb (session, monitor_cb, stdout, 1); break; default: usage (); exit (64); } /* At least one more argument is needed. */ if (optind > argc - 1) { usage (); exit (64); } /* NB. libESMTP sets timeouts as it progresses through the protocol. In * addition the remote server might close its socket on a timeout. * Consequently libESMTP may sometimes try to write to a socket with no * reader. Ignore SIGPIPE, then the program doesn't get killed if/when * this happens. */ sa.sa_handler = SIG_IGN; sigemptyset (&sa.sa_mask); sa.sa_flags = 0; sigaction (SIGPIPE, &sa, NULL); /* Set the host running the SMTP server. LibESMTP has a default port * number of 587, however this is not widely deployed so the port is * specified as 25 along with the default MTA host. */ smtp_set_server (session, host ? host : "localhost:25"); /* Set the SMTP Starttls extension. */ smtp_starttls_enable (session, starttls); /* Do what's needed at application level to use authentication. */ authctx = auth_create_context (); auth_set_mechanism_flags (authctx, AUTH_PLUGIN_PLAIN, 0); auth_set_interact_cb (authctx, authinteract, NULL); /* Use our callback for X.509 certificate passwords. If STARTTLS is not in * use or disabled in configure, the following is harmless. */ smtp_starttls_set_password_cb (tlsinteract, NULL); /* Now tell libESMTP it can use the SMTP AUTH extension. */ smtp_auth_set_context (session, authctx); /* At present it can't handle one recipient only out of many failing. Make * libESMTP require all specified recipients to succeed before transferring * a message. */ smtp_option_require_all_recipients (session, 1); /* Add a message to the SMTP session. */ message = smtp_add_message (session); /* Set the reverse path for the mail envelope. (NULL is ok) */ smtp_set_reverse_path (message, from); /* Open the message file and set the callback to read it. */ #if 0 smtp_set_message_fp (message, stdin); #else smtp_set_messagecb (message, readlinefp_cb, stdin); #endif /* Add remaining program arguments as message recipients. */ while (optind < argc) { recipient = smtp_add_recipient (message, argv[optind++]); /* Recipient options set here */ if (notify != Notify_NOTSET) smtp_dsn_set_notify (recipient, notify); } /* Initiate a connection to the SMTP server and transfer the message. */ if (!smtp_start_session (session)) { char buf[128]; fprintf (stderr, "SMTP server problem %s\n", smtp_strerror (smtp_errno (), buf, sizeof buf)); ret = 69; } else { /* Report on the success or otherwise of the mail transfer. */ status = smtp_message_transfer_status (message); if (status->code / 100 == 2) { ret = 0; } else { /* Report on the failure of the mail transfer. */ status = smtp_message_transfer_status (message); fprintf (stderr, "%d %s\n", status->code, status->text); smtp_enumerate_recipients (message, print_recipient_status, NULL); ret = 70; } } /* Free resources consumed by the program. */ smtp_destroy_session (session); auth_destroy_context (authctx); auth_client_exit (); exit (ret); }