diff options
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | TODO | 2 | ||||
-rw-r--r-- | esmtp.1 | 2 | ||||
-rw-r--r-- | esmtp.h | 30 | ||||
-rw-r--r-- | lexer.l | 1 | ||||
-rw-r--r-- | list.h | 242 | ||||
-rw-r--r-- | local.c | 238 | ||||
-rw-r--r-- | local.h | 28 | ||||
-rw-r--r-- | main.c | 939 | ||||
-rw-r--r-- | main.h | 43 | ||||
-rw-r--r-- | message.c | 386 | ||||
-rw-r--r-- | message.h | 64 | ||||
-rw-r--r-- | parser.y | 43 | ||||
-rw-r--r-- | rfc822.c | 193 | ||||
-rw-r--r-- | rfc822.h | 3 | ||||
-rw-r--r-- | smtp.c | 437 | ||||
-rw-r--r-- | smtp.h | 59 |
17 files changed, 2047 insertions, 665 deletions
diff --git a/Makefile.am b/Makefile.am index fd59cb0..8402471 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2,7 +2,7 @@ bin_PROGRAMS = esmtp dist_man_MANS = esmtp.1 -esmtp_SOURCES = esmtp.h main.c parser.y lexer.l +esmtp_SOURCES = main.h main.c parser.y lexer.l list.h message.h message.c smtp.h smtp.c local.h local.c rfc822.c EXTRA_DIST = README.mutt sample.esmtprc @@ -1,3 +1 @@ -- handle the '-t' option - safer memory allocation -- local mail delivery @@ -213,7 +213,7 @@ headers. An alternate and obsolete form of the \fB\-f\fR flag. .TP -\fB\-t\fR (unsupported) +\fB\-t\fR Read message for recipients. To:, Cc:, and Bcc: lines will be scanned for recipient addresses. The Bcc: line will be deleted before transmission. diff --git a/esmtp.h b/esmtp.h deleted file mode 100644 index 7c61e9a..0000000 --- a/esmtp.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * esmtp.h - global declarations - */ - -typedef struct { - char *identity; - char *host; - char *user; - char *pass; - enum starttls_option starttls; /* it should default to Starttls_DISABLED */ - char *certificate_passphrase; -} identity_t; - -extern identity_t default_identity; - -typedef struct identity_list_rec identity_list_t; - -struct identity_list_rec { - identity_list_t *next; - identity_t identity; -} ; - -extern identity_list_t *identities_head, **identities_tail; - - -extern char *rcfile; - - -extern void parse_rcfile(void); - @@ -60,6 +60,7 @@ user(name)? { BEGIN(NAME); return USERNAME; } pass(word)? { BEGIN(NAME); return PASSWORD; } (start)?tls { return STARTTLS; } (certificate_)?passphrase { return CERTIFICATE_PASSPHRASE; } +mda { return MDA; } = { return MAP; } @@ -0,0 +1,242 @@ +/** + * \file list.h + * Simple doubly linked list implementation. + * + * Some of the internal functions ("__xxx") are useful when + * manipulating whole lists rather than single entries, as + * sometimes we already know the next/prev entries and we can + * generate better code by using them directly rather than + * using the generic single-entry routines. + * + * \author Copied from the linux kernel, file include/linux/list.h. + */ + +#ifndef _LIST_H +#define _LIST_H + +struct list_head { + struct list_head *next, *prev; +}; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +#define LIST_HEAD(name) \ + struct list_head name = LIST_HEAD_INIT(name) + +#define INIT_LIST_HEAD(ptr) do { \ + (ptr)->next = (ptr); (ptr)->prev = (ptr); \ +} while (0) + +/** + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_add(struct list_head *new, + struct list_head *prev, + struct list_head *next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +/** + * Add a new entry. + * + * \param new new entry to be added + * \param head list head to add it after + * + * Insert a new entry after the specified head. + * This is good for implementing stacks. + */ +static inline void list_add(struct list_head *new, struct list_head *head) +{ + __list_add(new, head, head->next); +} + +/** + * Add a new entry. + * + * \param new new entry to be added + * \param head list head to add it before + * + * Insert a new entry before the specified head. + * This is useful for implementing queues. + */ +static inline void list_add_tail(struct list_head *new, struct list_head *head) +{ + __list_add(new, head->prev, head); +} + +/** + * Delete a list entry by making the prev/next entries + * point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_del(struct list_head *prev, struct list_head *next) +{ + next->prev = prev; + prev->next = next; +} + +/** + * Deletes entry from list. + * + * \param entry the element to delete from the list. + * + * \note list_empty on entry does not return true after this, the entry is in an undefined state. + */ +static inline void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + entry->next = (void *) 0; + entry->prev = (void *) 0; +} + +/** + * Deletes entry from list and reinitialize it. + * + * \param entry the element to delete from the list. + */ +static inline void list_del_init(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + INIT_LIST_HEAD(entry); +} + +/** + * Delete from one list and add as another's head. + * + * \param list the entry to move + * \param head the head that will precede our entry + */ +static inline void list_move(struct list_head *list, struct list_head *head) +{ + __list_del(list->prev, list->next); + list_add(list, head); +} + +/** + * Delete from one list and add as another's tail. + * + * \param list the entry to move + * \param head the head that will follow our entry + */ +static inline void list_move_tail(struct list_head *list, + struct list_head *head) +{ + __list_del(list->prev, list->next); + list_add_tail(list, head); +} + +/** + * Tests whether a list is empty. + * + * \param head the list to test. + */ +static inline int list_empty(struct list_head *head) +{ + return head->next == head; +} + +static inline void __list_splice(struct list_head *list, + struct list_head *head) +{ + struct list_head *first = list->next; + struct list_head *last = list->prev; + struct list_head *at = head->next; + + first->prev = head; + head->next = first; + + last->next = at; + at->prev = last; +} + +/** + * Join two lists. + * + * \param list the new list to add. + * \param head the place to add it in the first list. + */ +static inline void list_splice(struct list_head *list, struct list_head *head) +{ + if (!list_empty(list)) + __list_splice(list, head); +} + +/** + * Join two lists and reinitialise the emptied list. + * + * \param list the new list to add. + * \param head the place to add it in the first list. + * + * The list at \p list is reinitialised + */ +static inline void list_splice_init(struct list_head *list, + struct list_head *head) +{ + if (!list_empty(list)) { + __list_splice(list, head); + INIT_LIST_HEAD(list); + } +} + +/** + * Get the struct for this entry. + * + * \param ptr the &struct list_head pointer. + * \param type the type of the struct this is embedded in. + * \param member the name of the list_struct within the struct. + */ +#define list_entry(ptr, type, member) \ + ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) + +/** + * Iterate over a list. + * + * \param pos the struct list_head to use as a loop counter. + * \param head the head for your list. + */ +#define list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); \ + pos = pos->next) +/** + * Iterate over a list backwards. + * + * \param pos the &struct list_head to use as a loop counter. + * \param head the head for your list. + */ +#define list_for_each_prev(pos, head) \ + for (pos = (head)->prev; pos != (head); \ + pos = pos->prev) + +/** + * Iterate over a list safe against removal of list entry. + * + * \param pos the &struct list_head to use as a loop counter. + * \param n another &struct list_head to use as temporary storage + * \param head the head for your list. + */ +#define list_for_each_safe(pos, n, head) \ + for (pos = (head)->next, n = pos->next; pos != (head); \ + pos = n, n = pos->next) + +/** + * Iterate over list of given type. + * + * \param pos the type * to use as a loop counter. + * \param head the head for your list. + * \param member the name of the list_struct within the struct. + */ +#define list_for_each_entry(pos, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member)) + +#endif @@ -0,0 +1,238 @@ +/** + * \file local.c + * Local delivery of mail via a MDA. + */ + + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <signal.h> +#include <pwd.h> + +#include "local.h" +#include "main.h" + + +#if 0 +char *mda = NULL; +#else +char *mda = "/usr/bin/procmail -d %T"; +#endif + +FILE *mda_fp = NULL; + + +int local_address(const char *address) +{ + return !strchr(address, '@'); +} + +/** replace ' by _ */ +static void sanitize(char *s) +{ + char *cp; + + for (cp = s; (cp = strchr (cp, '\'')); cp++) + *cp = '_'; +} + +#define xmalloc malloc +#define xstrdup strdup + +#define HAVE_SETEUID + +/** + * Pipe the message to the MDA for local delivery. + * + * Based on fetchmail's open_mda_sink(). + */ +int local_init(message_t *message) +{ +#ifdef HAVE_SETEUID + uid_t orig_uid, uid; + struct passwd *pw; +#endif /* HAVE_SETEUID */ + struct idlist *idp; + int length = 0, fromlen = 0, nameslen = 0; + char *names = NULL, *before, *after, *from = NULL; + char *user = NULL; + + if (!mda) + return 1; + + length = strlen(mda); + before = xstrdup(mda); + + /* get user addresses for %T */ + if (strstr(before, "%T")) + { + struct list_head *ptr; + + /* + * We go through this in order to be able to handle very + * long lists of users and (re)implement %s. + */ + nameslen = 0; + list_for_each(ptr, &message->local_recipients) + { + recipient_t *recipient = list_entry(ptr, recipient_t, list); + + if(recipient->address) + nameslen += (strlen(recipient->address) + 1); /* string + ' ' */ + } + + names = (char *)xmalloc(nameslen + 1); /* account for '\0' */ + names[0] = '\0'; + list_for_each(ptr, &message->local_recipients) + { + recipient_t *recipient = list_entry(ptr, recipient_t, list); + + if(recipient->address) + { + if(!user) + user = recipient->address; + strcat(names, recipient->address); + strcat(names, " "); + } + } + names[--nameslen] = '\0'; /* chop trailing space */ + + sanitize(names); + } + + /* get From address for %F */ + if (strstr(before, "%F")) + { + from = xstrdup(message->reverse_path); + + sanitize(from); + + fromlen = strlen(from); + } + + /* do we have to build an mda string? */ + if (names || from) + { + char *sp, *dp; + + /* find length of resulting mda string */ + sp = before; + while ((sp = strstr(sp, "%s"))) { + length += nameslen; /* subtract %s and add '' */ + sp += 2; + } + sp = before; + while ((sp = strstr(sp, "%T"))) { + length += nameslen; /* subtract %T and add '' */ + sp += 2; + } + sp = before; + while ((sp = strstr(sp, "%F"))) { + length += fromlen; /* subtract %F and add '' */ + sp += 2; + } + + after = xmalloc(length + 1); + + /* copy mda source string to after, while expanding %[sTF] */ + for (dp = after, sp = before; (*dp = *sp); dp++, sp++) { + if (sp[0] != '%') continue; + + /* need to expand? BTW, no here overflow, because in + ** the worst case (end of string) sp[1] == '\0' */ + if (sp[1] == 'T') { + *dp++ = '\''; + strcpy(dp, names); + dp += nameslen; + *dp++ = '\''; + sp++; /* position sp over [sT] */ + dp--; /* adjust dp */ + } else if (sp[1] == 'F') { + *dp++ = '\''; + strcpy(dp, from); + dp += fromlen; + *dp++ = '\''; + sp++; /* position sp over F */ + dp--; /* adjust dp */ + } + } + + if (names) { + free(names); + names = NULL; + } + if (from) { + free(from); + from = NULL; + } + + free(before); + + before = after; + } + +#ifdef HAVE_SETEUID + /* + * Arrange to run with user's permissions if we're root. + * This will initialize the ownership of any files the + * MDA creates properly. (The seteuid call is available + * under all BSDs and Linux) + */ + orig_uid = getuid(); + /* if `user' doesn't name a real local user, try to run as root */ + if ((pw = getpwnam(user)) == (struct passwd *)NULL) + uid = 0; + else + uid = pw->pw_uid; /* for local delivery via MDA */ + seteuid(uid); +#endif /* HAVE_SETEUID */ + + mda_fp = popen(before, "w"); + +#ifdef HAVE_SETEUID + /* this will fail quietly if we didn't start as root */ + seteuid(orig_uid); +#endif /* HAVE_SETEUID */ + + if (!mda_fp) + { + free(before); + before = NULL; + fprintf(stderr, "MDA open failed\n"); + return 0; + } + + if(verbose) + fprintf(stdout, "Connected to MDA: %s\n", before); + + free(before); + before = NULL; + return 1; +} + +int local_flush(message_t *message) +{ + char buffer[BUFSIZ]; + size_t n; + + do { + n = message_read(message, buffer, BUFSIZ); + if(fwrite(buffer, 1, n, mda_fp) != n) + return 0; + } while(n == BUFSIZ); + + return 1; +} + +void local_cleanup(void) +{ + if(mda_fp) + { + fclose(mda_fp); + mda_fp = NULL; + + if(verbose) + fprintf(stdout, "Disconnected to MDA\n"); + } +} @@ -0,0 +1,28 @@ +/** + * \file local.h + */ + +#ifndef _LOCAL_H +#define _LOCAL_H + + +#include <stdio.h> + +#include "message.h" + + +extern char *mda; +extern FILE *mda_fp; + + +/** Check whether it's a local or a remote address */ +int local_address(const char *address); + +/** Send a message locally (via a MDA) */ +int local_init(message_t *msg); + +int local_flush(message_t *msg); + +void local_cleanup(void); + +#endif @@ -1,649 +1,370 @@ -/* - * A libESMTP Example Application. - * Copyright (C) 2001,2002 Brian Stafford <brian@stafford.uklinux.net> - * - * 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 +/** + * \file main.c + * Main program. */ -/* 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 <stdio.h> #include <stdlib.h> -#include <stdarg.h> -#include <ctype.h> -#include <unistd.h> -#include <getopt.h> #include <string.h> -#include <fcntl.h> -#include <signal.h> -#include <errno.h> - -#include <auth-client.h> -#include <libesmtp.h> - -#include "esmtp.h" - -/* Identity management */ -identity_t default_identity = { - NULL, - NULL, - NULL, - NULL, - Starttls_DISABLED, - NULL -}; - -identity_list_t *identities_head = NULL, **identities_tail = &identities_head; - -/* 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 +#include <getopt.h> -const char * readlinefp_cb (void **buf, int *len, void *arg) -{ - int octets; +#include "main.h" +#include "message.h" +#include "smtp.h" +#include "local.h" - if (*buf == NULL) - *buf = malloc (BUFLEN); - if (len == NULL) - { - rewind ((FILE *) arg); - return NULL; - } +int verbose = 0; - if (fgets (*buf, BUFLEN - 2, (FILE *) arg) == NULL) - octets = 0; - else - { - char *p = strchr (*buf, '\0'); +FILE *log_fp = NULL; - if (p[-1] == '\n' && p[-2] != '\r') - { - strcpy (p - 1, "\r\n"); - p++; - } - octets = p - (char *) *buf; - } - *len = octets; - return *buf; -} - -#define SIZETICKER 1024 /* print 1 dot per this many bytes */ - -void event_cb (smtp_session_t session, int event_no, void *arg, ...) -{ - FILE *fp = arg; - va_list ap; - const char *mailbox; - smtp_message_t message; - smtp_recipient_t recipient; - const smtp_status_t *status; - static int sizeticking = 0, sizeticker; - - if (event_no != SMTP_EV_MESSAGEDATA && sizeticking) - { - fputs("\n", fp); - sizeticking = 0; - } - - va_start (ap, arg); - switch (event_no) { - case SMTP_EV_CONNECT: - fputs("Connected to MTA\n", fp); - break; - - case SMTP_EV_MAILSTATUS: - mailbox = va_arg (ap, const char *); - message = va_arg (ap, smtp_message_t); - status = smtp_reverse_path_status (message); - fprintf (fp, "From %s: %d %s", mailbox, status->code, status->text); - break; - - case SMTP_EV_RCPTSTATUS: - mailbox = va_arg (ap, const char *); - recipient = va_arg (ap, smtp_recipient_t); - status = smtp_recipient_status (recipient); - fprintf (fp, "To %s: %d %s", mailbox, status->code, status->text); - break; - - case SMTP_EV_MESSAGEDATA: - message = va_arg (ap, smtp_message_t); - if (!sizeticking) - { - fputs("Message data: ", fp); - sizeticking = 1; - sizeticker = SIZETICKER - 1; - } - sizeticker += va_arg (ap, int); - while (sizeticker >= SIZETICKER) - { - fputc('.', fp); - sizeticker -= SIZETICKER; - } - break; - - case SMTP_EV_MESSAGESENT: - message = va_arg (ap, smtp_message_t); - status = smtp_message_transfer_status (message); - fprintf (fp, "Message sent: %d %s", status->code, status->text); - break; - - case SMTP_EV_DISCONNECT: - fputs("Disconnected\n", fp); - break; - - default: - break; - } - va_end (ap); -} - -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); -} - -/* Callback to request user/password info. Not thread safe. */ -int authinteract (auth_client_request_t request, char **result, int fields, - void *arg) -{ - identity_t *identity = (identity_t *)arg; - int i; - - if(!identity) - return 0; - - for (i = 0; i < fields; i++) - { - if (request[i].flags & AUTH_USER && identity->user) - result[i] = identity->user; - else if (request[i].flags & AUTH_PASS && identity->pass) - result[i] = identity->pass; - else - return 0; - } - return 1; -} - -int tlsinteract (char *buf, int buflen, int rwflag, void *arg) -{ - identity_t *identity = (identity_t *)arg; - char *pw; - int len; - - if(!identity) - return 0; - - if (identity->certificate_passphrase) - { - pw = identity->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; - FILE *fp = NULL; - identity_t *identity = &default_identity; - char *from = NULL; - identity_list_t *p; - - /* Modes of operation. */ - enum { - ENQUEUE, /* delivery mode */ - NEWALIAS, /* initialize alias database */ - MAILQ, /* list mail queue */ - FLUSHQ, /* flush the mail queue */ - } mode; - - /* Set the default mode of operation. */ - if (strcmp(argv[0], "mailq") == 0) { - mode = MAILQ; - } else if (strcmp(argv[0], "newaliases") == 0) { - mode = NEWALIAS; - } else { - mode = ENQUEUE; - } - - /* 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 */ - rcfile = optarg; - break; - - case 'F': - /* Set full name */ - break; + int c; + int ret; + enum notify_flags notify = Notify_NOTSET; + char *from = NULL; + message_t *message; + int local, remote; + int parse_headers; + + /* Modes of operation. */ + enum { + ENQUEUE, /* delivery mode */ + NEWALIAS, /* initialize alias database */ + MAILQ, /* list mail queue */ + FLUSHQ /* flush the mail queue */ + } mode; + + identities_init(); - case 'G': - /* Relay (gateway) submission */ - break; + /* Parse the rc file. */ + parse_rcfile(); - case 'I': - /* Initialize alias database */ + /* Set the default mode of operation. */ + if (strcmp(argv[0], "mailq") == 0) { + mode = MAILQ; + } else if (strcmp(argv[0], "newaliases") == 0) { mode = NEWALIAS; - break; - - case 'L': - /* Program label */ - break; - - case 'M': - /* Define macro */ - break; + } else { + mode = ENQUEUE; + } - 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 */ - if (fp) - fclose(fp); - if ((fp = fopen(optarg, "a"))) - /* Add a protocol monitor. */ - smtp_set_monitorcb (session, monitor_cb, fp, 1); - break; - - case 'V': - /* Set original envelope id */ - break; - - case 'b': - /* Operations mode */ - c = (optarg == NULL) ? ' ' : *optarg; + 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 'm': - /* Deliver mail in the usual way */ - mode = ENQUEUE; + case 'A': + /* Use alternate sendmail/submit.cf */ + break; + + case 'B': + /* Body type */ + break; + + case 'C': + /* Select configuration file */ + rcfile = optarg; + break; + + case 'F': + /* Set full name */ + break; + + case 'G': + /* Relay (gateway) submission */ + break; + + case 'I': + /* Initialize alias database */ + mode = NEWALIAS; + 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 */ + if (log_fp) + fclose(log_fp); + log_fp = fopen(optarg, "a"); + 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 */ + mode = ENQUEUE; + break; + + case 'i': + /* Initialize the alias database */ + mode = NEWALIAS; + break; + + case 'p': + /* Print a listing of the queue(s) */ + mode = MAILQ; + 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 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 (EX_USAGE); + break; + + default: + fprintf (stderr, "Invalid operation mode %c\n", c); + exit (EX_USAGE); + 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 */ + mode = FLUSHQ; + 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: + break; + } + break; + + case 's': + /* Save From lines in headers */ + break; + + case 't': + /* Read recipients from message */ + parse_headers = 1; + break; + + case 'v': + /* Verbose */ + verbose = 1; + break; + + default: + fprintf (stderr, "Invalid option %c\n", c); + exit (EX_USAGE); + } + + switch (mode) + { + case ENQUEUE: break; + + case MAILQ: + printf ("Mail queue is empty\n"); + case NEWALIAS: + case FLUSHQ: + exit (0); + } - case 'i': - /* Initialize the alias database */ - mode = NEWALIAS; - break; + /* At least one more argument is needed. */ + if (optind > argc - 1 && !parse_headers) + { + fprintf(stderr, "Recipient names must be specified\n"); + exit (EX_USAGE); + } - case 'p': - /* Print a listing of the queue(s) */ - mode = MAILQ; - 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 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; + if(!(message = message_new())) + goto error; - 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; - p = identities_head; - while(p) + /** Parse the envelope headers */ + if(parse_headers) + { + if(!message_parse_headers(message)) { - if(!strcmp(p->identity.identity, from)) - { - identity = &p->identity; - break; - } - p = p->next; + fprintf(stderr, "Failed to parse headers\n"); + exit(EX_DATAERR); } - 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 */ - mode = FLUSHQ; - if (optarg[0] == '!') + + if(!remote && !local) { - /* Negate the meaning of pattern match */ - optarg++; + fprintf(stderr, "No recipients found\n"); + exit(EX_DATAERR); } + } - 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; + /* Set the reverse path for the mail envelope. */ + if(from && !message_set_reverse_path (message, from)) + goto error; - case 'p': - /* Persistent queue */ - if (optarg[1] == '\0') - break; - ++optarg; + /* Add remaining program arguments as message recipients. */ + while (optind < argc) { + if(!message_add_recipient(message, argv[optind++])) + goto error; + } - default: - 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 */ - /* Set the event callback. */ - smtp_set_eventcb (session, event_cb, stdout); - break; - - default: - usage (); - exit (64); - } - - switch (mode) - { - case ENQUEUE: - break; + local = !list_empty(&message->local_recipients); + remote = !list_empty(&message->remote_recipients); - case MAILQ: - printf ("Mail queue is empty\n"); - case NEWALIAS: - case FLUSHQ: - exit (0); - } - - /* 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, identity->host ? identity->host : "localhost:25"); - - /* Set the SMTP Starttls extension. */ - smtp_starttls_enable (session, identity->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, identity); - - /* 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, identity); - - /* 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) + if(remote && !local) + ret = smtp_send(message); + else if(!remote && local) { - ret = 0; - } + if(!local_init(message)) + goto error; + + if(!local_flush(message)) + goto error; + + local_cleanup(); + + 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); + if(!local_init(message)) + goto error; - ret = 70; - } - } + ret = smtp_send(message); + + if(ferror(mda_fp)) + goto error; - /* Free resources consumed by the program. */ - if (fp) - { - fputc('\n', fp); - fclose(fp); - } + if(!message_eof(message) && !local_flush(message)) + goto error; + + local_cleanup(); + } + + message_free(message); - smtp_destroy_session (session); - auth_destroy_context (authctx); - auth_client_exit (); + return ret; - exit (ret); +error: + perror(NULL); + exit(255); } @@ -0,0 +1,43 @@ +/** + * \file main.h + * Global declarations. + */ + +#ifndef _MAIN_H +#define _MAIN_H + + +#include <stdio.h> + +/** + * Error codes as specified in sendmail's sysexits.h + */ +enum { + EX_OK = 0, /**< successful termination */ + EX_USAGE = 64, /**< command line usage error */ + EX_DATAERR = 65, /**< data format error */ + EX_NOINPUT = 66, /**< cannot open input */ + EX_NOUSER = 67, /**< addressee unknown */ + EX_NOHOST = 68, /**< host name unknown */ + EX_UNAVAILABLE = 69, /**< service unavailable */ + EX_SOFTWARE = 70, /**< internal software error */ + EX_OSERR = 71, /**< system error (e.g., can't fork) */ + EX_OSFILE = 72, /**< critical OS file missing */ + EX_CANTCREAT = 73, /**< can't create (user) output file */ + EX_IOERR = 74, /**< input/output error */ + EX_TEMPFAIL = 75, /**< temp failure; user is invited to retry */ + EX_PROTOCOL = 76, /**< remote error in protocol */ + EX_NOPERM = 77, /**< permission denied */ + EX_CONFIG = 78 /**< configuration error */ +}; + +extern FILE *log_fp; + +extern int verbose; + +extern char *rcfile; + + +extern void parse_rcfile(void); + +#endif diff --git a/message.c b/message.c new file mode 100644 index 0000000..0454192 --- /dev/null +++ b/message.c @@ -0,0 +1,386 @@ +/** + * \file message.c + * Simple message handling. + * + * \author José Fonseca + */ + + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "message.h" +#include "local.h" +#include "rfc822.h" + + +message_t *message_new(void) +{ + message_t *message; + + if(!(message = (message_t *)malloc(sizeof(message_t)))) + return NULL; + + memset(message, 0, sizeof(message_t)); + + INIT_LIST_HEAD(&message->remote_recipients); + INIT_LIST_HEAD(&message->local_recipients); + + message->notify = Notify_NOTSET; + + return message; +} + +void message_free(message_t *message) +{ + struct list_head *ptr, *tmp; + recipient_t *recipient; + + if(message->reverse_path) + free(message->reverse_path); + + if(!list_empty(&message->remote_recipients)) + list_for_each_safe(ptr, tmp, &message->remote_recipients) + { + recipient = list_entry(ptr, recipient_t, list); + list_del(ptr); + + if(recipient->address) + free(recipient->address); + + free(ptr); + } + + if(!list_empty(&message->local_recipients)) + list_for_each_safe(ptr, tmp, &message->local_recipients) + { + recipient = list_entry(ptr, recipient_t, list); + list_del(ptr); + + if(recipient->address) + free(recipient->address); + + free(ptr); + } + + if(message->fp) + fclose(message->fp); + + free(message); +} + +int message_set_reverse_path(message_t *message, const char *address) +{ + if(message->reverse_path) + free(message->reverse_path); + + if(!(message->reverse_path = strdup(address))) + return 0; + + return 1; +} + +int message_add_recipient(message_t *message, const char *address) +{ + recipient_t *recipient; + + if(!(recipient = (recipient_t *)malloc(sizeof(recipient_t)))) + return 0; + + if(!(recipient->address = strdup(address))) + { + free(recipient); + return 0; + } + + if(local_address(address)) + list_add(&recipient->list, &message->local_recipients); + else + list_add(&recipient->list, &message->remote_recipients); + + return 1; +} + +static int message_buffer_alloc(message_t *message) +{ + char *buffer; + size_t buffer_size; + + if(!message->buffer_size) + buffer_size = BUFSIZ; + else + buffer_size = message->buffer_size << 1; + + if(!(buffer = (char *)realloc(message->buffer, buffer_size))) + return 0; + + message->buffer = buffer; + message->buffer_size = buffer_size; + + return 1; +} + +static char *message_buffer_readline(message_t *message) +{ + FILE *fp = message->fp ? message->fp : stdin; + size_t ret = message->buffer_stop, n; + + while(1) + { + if(message->buffer_stop >= message->buffer_size - 1 && !message_buffer_alloc(message)) + return NULL; + + if(!fgets(message->buffer + message->buffer_stop, message->buffer_size - message->buffer_stop, fp)) + return NULL; + + n = strlen(message->buffer + message->buffer_stop); + message->buffer_stop += n; + + if(*(message->buffer + message->buffer_stop - 1) == '\n') + return message->buffer + ret; + } +} + +static void message_buffer_fill(message_t *message) +{ + FILE *fp = message->fp ? message->fp : stdin; + + message->buffer_stop += fread(message->buffer, 1, message->buffer_size - message->buffer_stop, fp); +} + +static size_t message_buffer_flush(message_t *message, char *ptr, size_t size) +{ + size_t count, n; + char *p, *q; + + p = message->buffer + message->buffer_start; + count = 0; + while(count < size && message->buffer_start < message->buffer_stop) + { + q = memchr(p, '\n', message->buffer_stop - message->buffer_start); + + if(q) + /* read up to the newline */ + n = q - p; + else + /* read up to the end of the buffer */ + n = message->buffer_stop - message->buffer_start; + + if(n) + { + if(n > (size - count)) + n = size - count; + + memcpy(ptr, p, n); + + p += n; + message->buffer_start += n; + ptr += n; + count += n; + + message->buffer_r = *(p - 1) == '\r'; + } + + if(count == size) + return count; + + if(q) + { + if(!message->buffer_r) + { + *ptr++ = '\r'; + count++; + + if(count == size) + { + message->buffer_r = 1; + return count; + } + } + else + message->buffer_r = 0; + + *ptr++ = *p++; /* '\n' */ + message->buffer_start++; + count++; + } + } + + if(message->buffer_start == message->buffer_stop) + message->buffer_start = message->buffer_stop = 0; + + return count; +} + +size_t message_read(message_t *message, char *ptr, size_t size) +{ + size_t count = 0, n; + + if(!message->buffer && !message_buffer_alloc(message)) + return 0; + + n = message_buffer_flush(message, ptr, size); + count += n; + ptr += n; + + while(count != size) + { + message_buffer_fill(message); + + if(!(n = message_buffer_flush(message, ptr, size - count))) + break; + count += n; + ptr += n; + + }; + + return count; +} + +void message_rewind(message_t *message) +{ + FILE *fp = message->fp ? message->fp : stdin; + + message->buffer_start = message->buffer_stop = 0; + message->buffer_r = 0; + + rewind(fp); +} + +int message_eof(message_t *message) +{ + FILE *fp = message->fp ? message->fp : stdin; + + if(message->buffer_start != message->buffer_stop) + return 0; + + return feof(fp); +} + +static int message_parse_header(message_t *message, size_t start, size_t stop) +{ + const char *address; + char *header, *next, c; + + header = message->buffer + start; + next = message->buffer + stop; + + c = *next; + *next = '\0'; + + if(!strncasecmp("From: ", header, 6)) + { + if((address = next_address(header))) + if(!message_set_reverse_path(message, address)) + return 0; + } + else if(!strncasecmp("To: ", header, 4) || + !strncasecmp("Cc: ", header, 4) || + !strncasecmp("Bcc: ", header, 5)) + { + address = next_address(header); + while(address) + { + if(!message_add_recipient(message, address)) + return 0; + address = next_address(NULL); + } + + } + + *next = c; + + if(!strncasecmp("Bcc: ", header, 5)) + { + size_t n = message->buffer_stop - stop; + + memcpy(header, next, n); + + message->buffer_stop = start + n; + } + + return 1; +} + +int message_parse_headers(message_t *message) +{ + FILE *fp = message->fp ? message->fp : stdin; + char *line, *header; + size_t start, stop; + + assert(!message->buffer); + + if(!message_buffer_alloc(message)) + return 0; + + start = 0; + while((line = message_buffer_readline(message))) + { + if(line[0] == ' ' || line[0] == '\t') + { + /* append line */ + } + else + { + stop = line - message->buffer; + if(stop && !message_parse_header(message, start, stop)) + return 0; + + start = stop; + + if(line[0] == '\n') + return 1; + } + } + + return 0; +} + +#ifdef TEST +int local_address(const char *address) +{ + return !strchr(address, '@'); +} + +int main(int argc, char *argv[]) +{ + message_t *message = message_new(); + const size_t len = 8192; + size_t n; + char buf[len]; + unsigned i; + FILE *fpin, *fpout; + struct list_head *ptr; + int ret; + + fpin = fopen("test.in", "r"); + fpout = fopen("test.out", "w"); + message->fp = fpin; + ret = message_parse_headers(message); + do { + n = message_read(message, buf, len); + fwrite(buf, 1, n, fpout); + } while(n == len); + + printf("%d %s\n", ret, message->reverse_path); + list_for_each(ptr, &message->local_recipients) + { + recipient_t *recipient = list_entry(ptr, recipient_t, list); + + if(recipient->address) + printf("%s\n",recipient->address); + } + list_for_each(ptr, &message->remote_recipients) + { + recipient_t *recipient = list_entry(ptr, recipient_t, list); + + if(recipient->address) + printf("%s\n",recipient->address); + } + + message_free(message); +} + +#endif diff --git a/message.h b/message.h new file mode 100644 index 0000000..5977be6 --- /dev/null +++ b/message.h @@ -0,0 +1,64 @@ +/** + * \file message.h + * Simple message handling. + * + * \author José Fonseca + */ + +#ifndef _MESSAGE_H +#define _MESSAGE_H + + +#include <libesmtp.h> + +#include "list.h" + + +/** + * Item of the recipient list. + */ +typedef struct { + struct list_head list; + char *address; +} recipient_t; + +/** + * A message. + */ +typedef struct { + char *reverse_path; /**< reverse path for the mail envelope */ + struct list_head remote_recipients; /**< remote recipients */ + struct list_head local_recipients; /**< local recipients */ + + enum notify_flags notify; /**< libESMTP notificiation flags */ + + /** \name buffering */ + /*@{*/ + char *buffer; + size_t buffer_size; + size_t buffer_start, buffer_stop; + int buffer_r; /**< whether the last character was a '\r' */ + /*@}*/ + + FILE *fp; /**< message file pointer */ +} message_t; + +/** Create a new message. */ +message_t *message_new(void); + +/** Free the resources associated with a message. */ +void message_free(message_t *m); + +int message_set_reverse_path(message_t *message, const char *address); + +int message_add_recipient(message_t *message, const char *address); + +int message_parse_headers(message_t *message); + +size_t message_read(message_t *message, char *ptr, size_t size); + +void message_rewind(message_t *message); + +int message_eof(message_t *message); + +#endif @@ -1,11 +1,11 @@ %{ -/* - * parser.y -- parser for the rcfile +/** + * \file parser.y + * Parser for the rcfile. + * + * \author Adapted from fetchmail's rcfile_y.y by José Fonseca */ -/* - * Adapted from fetchmail's rcfile_y.y by José Fonseca - */ #include <errno.h> #include <stdio.h> @@ -14,7 +14,10 @@ #include <libesmtp.h> -#include "esmtp.h" +#include "main.h" +#include "smtp.h" +#include "local.h" + /* parser reads these */ char *rcfile = NULL; /* path name of dot file */ @@ -22,7 +25,7 @@ char *rcfile = NULL; /* path name of dot file */ /* parser sets these */ int yydebug; /* in case we didn't generate with -- debug */ -static identity_t *identity = &default_identity; +static identity_t *identity = NULL; /* using Bison, this arranges that yydebug messages will show actual tokens */ extern char * yytext; @@ -36,7 +39,7 @@ void yyerror (const char *s); char *sval; } -%token IDENTITY HOSTNAME USERNAME PASSWORD STARTTLS CERTIFICATE_PASSPHRASE +%token IDENTITY HOSTNAME USERNAME PASSWORD STARTTLS CERTIFICATE_PASSPHRASE MDA %token MAP @@ -64,17 +67,9 @@ map : /* empty */ identity : IDENTITY map STRING { - identity_list_t *item; - - item = malloc(sizeof(identity_list_t)); - memset(item, 0, sizeof(identity_list_t)); - - *identities_tail = item; - identities_tail = &item->next; - identity = &item->identity; - - identity->identity = strdup($3); - identity->starttls = Starttls_DISABLED; + identity = identity_new(); + identity_add(identity); + identity->address = strdup($3); } ; @@ -90,6 +85,7 @@ statement : HOSTNAME map STRING { identity->host = strdup($3); } | STARTTLS map ENABLED { identity->starttls = Starttls_ENABLED; } | STARTTLS map REQUIRED { identity->starttls = Starttls_REQUIRED; } | CERTIFICATE_PASSPHRASE map STRING { identity->certificate_passphrase = strdup($3); } + | MDA map STRING { mda = strdup($3); } ; %% @@ -102,8 +98,9 @@ extern FILE *yyin; void yyerror (const char *s) /* report a syntax error */ { - fprintf(stderr, "%s:%d: %s at %s\n", rcfile, lineno, s, - (yytext && yytext[0]) ? yytext : "end of input"); + fprintf(stderr, "%s:%d: %s at %s\n", rcfile, lineno, s, + (yytext && yytext[0]) ? yytext : "end of input"); + exit(EX_CONFIG); } #define RCFILE ".esmtprc" @@ -126,7 +123,9 @@ void parse_rcfile (void) strcat(rcfile, "/"); strcat(rcfile, RCFILE); } - + + identity = default_identity; + /* Open the configuration file and feed it to the lexer. */ if (!(yyin = fopen(rcfile, "r"))) { diff --git a/rfc822.c b/rfc822.c new file mode 100644 index 0000000..5f1cac8 --- /dev/null +++ b/rfc822.c @@ -0,0 +1,193 @@ +/** + * \file rfc822.c + * Code for slicing and dicing RFC822 mail headers. + * + * How to parse RFC822 headers in C. This is not a fully conformant + * implementation of RFC822 or RFC2822, but it has been in production use in a + * widely-deployed MTA (fetcmail) since 1996 without complaints. Really + * perverse combinations of quoting and commenting could break it. + * + * \author Eric S. Raymond <esr@thyrsus.com>, 1997. This source code example + * is part of fetchmail and the Unix Cookbook, and are released under the MIT + * license. + */ + + +#include <stdio.h> +#include <ctype.h> +#include <string.h> +#include <stdlib.h> + + +#ifndef TRUE +#define TRUE 1 +#define FALSE 0 +#endif + +#define HEADER_END(p) ((p)[0] == '\n' && ((p)[1] != ' ' && (p)[1] != '\t')) + + +/** + * Parse addresses in succession out of a specified RFC822 header. + * + * \param hdr header to be parsed, NUL to continue previous \p hdr. + */ +char *next_address(const char *hdr) +{ + static unsigned char address[BUFSIZ]; + static int tp; + static const unsigned char *hp; + static int state, oldstate; + int parendepth = 0; + +#define START_HDR 0 /* before header colon */ +#define SKIP_JUNK 1 /* skip whitespace, \n, and junk */ +#define BARE_ADDRESS 2 /* collecting address without delimiters */ +#define INSIDE_DQUOTE 3 /* inside double quotes */ +#define INSIDE_PARENS 4 /* inside parentheses */ +#define INSIDE_BRACKETS 5 /* inside bracketed address */ +#define ENDIT_ALL 6 /* after last address */ + +#define NEXTTP() ((tp < sizeof(address)-1) ? tp++ : tp) + + if (hdr) + { + hp = hdr; + state = START_HDR; + tp = 0; + } + + for (; *hp; hp++) + { + + if (state == ENDIT_ALL) /* after last address */ + return(NULL); + else if (HEADER_END(hp)) + { + state = ENDIT_ALL; + if (tp) + { + while (isspace(address[--tp])) + continue; + address[++tp] = '\0'; + tp = 0; + return (address); + } + return((unsigned char *)NULL); + } + else if (*hp == '\\') /* handle RFC822 escaping */ + { + if (state != INSIDE_PARENS) + { + address[NEXTTP()] = *hp++; /* take the escape */ + address[NEXTTP()] = *hp; /* take following unsigned char */ + } + } + else switch (state) + { + case START_HDR: /* before header colon */ + if (*hp == ':') + state = SKIP_JUNK; + break; + + case SKIP_JUNK: /* looking for address start */ + if (*hp == '"') /* quoted string */ + { + oldstate = SKIP_JUNK; + state = INSIDE_DQUOTE; + address[NEXTTP()] = *hp; + } + else if (*hp == '(') /* address comment -- ignore */ + { + parendepth = 1; + oldstate = SKIP_JUNK; + state = INSIDE_PARENS; + } + else if (*hp == '<') /* begin <address> */ + { + state = INSIDE_BRACKETS; + tp = 0; + } + else if (*hp != ',' && !isspace(*hp)) + { + --hp; + state = BARE_ADDRESS; + } + break; + + case BARE_ADDRESS: /* collecting address without delimiters */ + if (*hp == ',') /* end of address */ + { + if (tp) + { + address[NEXTTP()] = '\0'; + state = SKIP_JUNK; + tp = 0; + return(address); + } + } + else if (*hp == '(') /* beginning of comment */ + { + parendepth = 1; + oldstate = BARE_ADDRESS; + state = INSIDE_PARENS; + } + else if (*hp == '<') /* beginning of real address */ + { + state = INSIDE_BRACKETS; + tp = 0; + } + else if (*hp == '"') /* quoted word, copy verbatim */ + { + oldstate = state; + state = INSIDE_DQUOTE; + address[NEXTTP()] = *hp; + } + else if (!isspace(*hp)) /* just take it, ignoring whitespace */ + address[NEXTTP()] = *hp; + break; + + case INSIDE_DQUOTE: /* we're in a quoted string, copy verbatim */ + if (*hp != '"') + address[NEXTTP()] = *hp; + else + { + address[NEXTTP()] = *hp; + state = oldstate; + } + break; + + case INSIDE_PARENS: /* we're in a parenthesized comment, ignore */ + if (*hp == '(') + ++parendepth; + else if (*hp == ')') + --parendepth; + if (parendepth == 0) + state = oldstate; + break; + + case INSIDE_BRACKETS: /* possible <>-enclosed address */ + if (*hp == '>') /* end of address */ + { + address[NEXTTP()] = '\0'; + state = SKIP_JUNK; + ++hp; + tp = 0; + return(address); + } + else if (*hp == '<') /* nested <> */ + tp = 0; + else if (*hp == '"') /* quoted address */ + { + address[NEXTTP()] = *hp; + oldstate = INSIDE_BRACKETS; + state = INSIDE_DQUOTE; + } + else /* just copy address */ + address[NEXTTP()] = *hp; + break; + } + } + + return(NULL); +} diff --git a/rfc822.h b/rfc822.h new file mode 100644 index 0000000..1266530 --- /dev/null +++ b/rfc822.h @@ -0,0 +1,3 @@ + + +const char *next_address(char *header); @@ -0,0 +1,437 @@ +/** + * \file smtp.c + * Send a message via libESMTP. + * + * \author Adapted from the libESMTP's mail-file example by José Fonseca. + */ + + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <signal.h> +#include <errno.h> + +#include <auth-client.h> +#include <libesmtp.h> + +#include "smtp.h" +#include "local.h" +#include "main.h" + + +/** + * \name Identity management + */ +/*@{*/ + +identity_t *default_identity = NULL; + +static LIST_HEAD(identities); + +identity_t *identity_new(void) +{ + identity_t *identity; + + if(!(identity = (identity_t *)malloc(sizeof(identity_t)))) + return NULL; + + memset(identity, 0, sizeof(identity_t)); + + identity->starttls = Starttls_DISABLED; + + return identity; +} + +void identity_free(identity_t *identity) +{ + if(identity->address) + free(identity->address); + + if(identity->host) + free(identity->host); + + if(identity->user) + free(identity->user); + + if(identity->pass) + free(identity->pass); + + if(identity->certificate_passphrase) + free(identity->certificate_passphrase); + + free(identity); +} + +void identity_add(identity_t *identity) +{ + list_add(&identity->list, &identities); +} + +identity_t *identity_lookup(const char *address) +{ + if(address) + { + struct list_head *ptr; + + list_for_each(ptr, &identities) + { + identity_t *identity; + + identity = list_entry(ptr, identity_t, list); + if(!strcmp(identity->address, address)) + return identity; + } + } + + return default_identity; +} + +int identities_init(void) +{ + if(!(default_identity = identity_new())) + return 0; + + return 1; +} + +void identities_cleanup(void) +{ + if(default_identity) + identity_free(default_identity); + + if(!list_empty(&identities)) + { + struct list_head *ptr, *tmp; + + list_for_each_safe(ptr, tmp, &identities) + { + identity_t *identity; + + identity = list_entry(ptr, identity_t, list); + list_del(ptr); + + identity_free(identity); + } + + } +} + +/*@}*/ + + +/** + * \name libESMTP interface + */ +/*@{*/ + +/** + * 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. + */ +static const char * message_cb (void **buf, int *len, void *arg) +{ + message_t *message = (message_t *)arg; + int octets; + + if (len == NULL) + assert(*buf == NULL); + + if (*buf == NULL) + *buf = malloc (BUFSIZ); + + if (len == NULL) + { + message_rewind(message); + return NULL; + } + + *len = message_read(message, *buf, BUFSIZ); + + /** hook for the MDA */ + if(mda_fp) + fwrite(*buf, 1, *len, mda_fp); + + return *buf; +} + +#define SIZETICKER 1024 /**< print 1 dot per this many bytes */ + +static void event_cb (smtp_session_t session, int event_no, void *arg, ...) +{ + FILE *fp = arg; + va_list ap; + const char *mailbox; + smtp_message_t message; + smtp_recipient_t recipient; + const smtp_status_t *status; + static int sizeticking = 0, sizeticker; + + if (event_no != SMTP_EV_MESSAGEDATA && sizeticking) + { + fputs("\n", fp); + sizeticking = 0; + } + + va_start (ap, arg); + switch (event_no) { + case SMTP_EV_CONNECT: + fputs("Connected to MTA\n", fp); + break; + + case SMTP_EV_MAILSTATUS: + mailbox = va_arg (ap, const char *); + message = va_arg (ap, smtp_message_t); + status = smtp_reverse_path_status (message); + fprintf (fp, "From %s: %d %s", mailbox, status->code, status->text); + break; + + case SMTP_EV_RCPTSTATUS: + mailbox = va_arg (ap, const char *); + recipient = va_arg (ap, smtp_recipient_t); + status = smtp_recipient_status (recipient); + fprintf (fp, "To %s: %d %s", mailbox, status->code, status->text); + break; + + case SMTP_EV_MESSAGEDATA: + message = va_arg (ap, smtp_message_t); + if (!sizeticking) + { + fputs("Message data: ", fp); + sizeticking = 1; + sizeticker = SIZETICKER - 1; + } + sizeticker += va_arg (ap, int); + while (sizeticker >= SIZETICKER) + { + fputc('.', fp); + sizeticker -= SIZETICKER; + } + break; + + case SMTP_EV_MESSAGESENT: + message = va_arg (ap, smtp_message_t); + status = smtp_message_transfer_status (message); + fprintf (fp, "Message sent: %d %s", status->code, status->text); + break; + + case SMTP_EV_DISCONNECT: + fputs("Disconnected to MTA\n", fp); + break; + + default: + break; + } + va_end (ap); +} + +static 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); +} + +/** + * Callback to request user/password info. + * + * Not thread safe. + */ +static int authinteract (auth_client_request_t request, char **result, int fields, + void *arg) +{ + identity_t *identity = (identity_t *)arg; + int i; + + if(!identity) + return 0; + + for (i = 0; i < fields; i++) + { + if (request[i].flags & AUTH_USER && identity->user) + result[i] = identity->user; + else if (request[i].flags & AUTH_PASS && identity->pass) + result[i] = identity->pass; + else + return 0; + } + return 1; +} + +static int tlsinteract (char *buf, int buflen, int rwflag, void *arg) +{ + identity_t *identity = (identity_t *)arg; + char *pw; + int len; + + if(!identity) + return 0; + + if (identity->certificate_passphrase) + { + pw = identity->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 smtp_send(message_t *msg) +{ + 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 ret; + identity_t *identity; + struct list_head *ptr; + + /* This program sends only one message at a time. Create an SMTP + * session. + */ + auth_client_init (); + session = smtp_create_session (); + + /* Add a protocol monitor. */ + if(log_fp) + smtp_set_monitorcb (session, monitor_cb, log_fp, 1); + + /* Lookup the identity */ + identity = identity_lookup(msg->reverse_path); + assert(identity); + + /* Set the event callback. */ + if(verbose) + smtp_set_eventcb (session, event_cb, stdout); + + /* 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, identity->host ? identity->host : "localhost:25"); + + /* Set the SMTP Starttls extension. */ + smtp_starttls_enable (session, identity->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, identity); + + /* 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, identity); + + /* 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, msg->reverse_path); + + /* Open the message file and set the callback to read it. */ + smtp_set_messagecb (message, message_cb, msg); + + /* Add remaining program arguments as message recipients. */ + list_for_each(ptr, &msg->remote_recipients) + { + recipient_t *entry = list_entry(ptr, recipient_t, list); + + if(entry->address) + { + recipient = smtp_add_recipient (message, entry->address); + + /* Recipient options set here */ + if (msg->notify != Notify_NOTSET) + smtp_dsn_set_notify (recipient, msg->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 = EX_UNAVAILABLE; + } + else + { + /* Report on the success or otherwise of the mail transfer. */ + status = smtp_message_transfer_status (message); + if (status->code / 100 == 2) + ret = EX_OK; + 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 = EX_SOFTWARE; + } + } + + if (log_fp) + fputc('\n', log_fp); + + smtp_destroy_session (session); + auth_destroy_context (authctx); + auth_client_exit (); + + return ret; +} + +/*@}*/ @@ -0,0 +1,59 @@ +/** + * \file smtp.h + */ + +#ifndef _SMTP_H +#define _SMTP_H + + +#include <libesmtp.h> + +#include "list.h" +#include "message.h" + + +/** + * \name Identity management + */ +/*@{*/ + +/** + * Identity. + */ +typedef struct { + struct list_head list; + char *address; + char *host; + char *user; + char *pass; + enum starttls_option starttls; /**< it should default to Starttls_DISABLED */ + char *certificate_passphrase; +} identity_t; + +/** + * Default identity. + */ +extern identity_t *default_identity; + +/** Create a new identity */ +identity_t *identity_new(void); + +/** Add a new identity */ +void identity_add(identity_t *identity); + +/** Lookup a identity */ +identity_t *identity_lookup(const char *address); + +/** Initialize the identities resources */ +int identities_init(void); + +/** Cleanup the resources associated with the identities */ +void identities_cleanup(void); + +/*@}*/ + + +/** Send a message via a SMTP server */ +int smtp_send(message_t *msg); + +#endif |