From 2867d16bc9bae9764904efbf44cd5131fca1bc9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Fonseca?= Date: Thu, 3 Jul 2003 17:37:11 +0000 Subject: Modularization of the code. Hability to get the recipients from the message headers. Local delivery via a MDA. --- Makefile.am | 2 +- TODO | 2 - esmtp.1 | 2 +- esmtp.h | 30 -- lexer.l | 1 + list.h | 242 ++++++++++++++++ local.c | 238 +++++++++++++++ local.h | 28 ++ main.c | 939 +++++++++++++++++++++--------------------------------------- main.h | 43 +++ message.c | 386 +++++++++++++++++++++++++ message.h | 64 +++++ parser.y | 43 ++- rfc822.c | 193 +++++++++++++ rfc822.h | 3 + smtp.c | 437 ++++++++++++++++++++++++++++ smtp.h | 59 ++++ 17 files changed, 2047 insertions(+), 665 deletions(-) delete mode 100644 esmtp.h create mode 100644 list.h create mode 100644 local.c create mode 100644 local.h create mode 100644 main.h create mode 100644 message.c create mode 100644 message.h create mode 100644 rfc822.c create mode 100644 rfc822.h create mode 100644 smtp.c create mode 100644 smtp.h 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 diff --git a/TODO b/TODO index eceb350..7d844d1 100644 --- a/TODO +++ b/TODO @@ -1,3 +1 @@ -- handle the '-t' option - safer memory allocation -- local mail delivery diff --git a/esmtp.1 b/esmtp.1 index 995dd35..7af3d3b 100644 --- a/esmtp.1 +++ b/esmtp.1 @@ -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); - diff --git a/lexer.l b/lexer.l index 530e622..a0d2c94 100644 --- a/lexer.l +++ b/lexer.l @@ -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; } diff --git a/list.h b/list.h new file mode 100644 index 0000000..524b1cd --- /dev/null +++ b/list.h @@ -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 diff --git a/local.c b/local.c new file mode 100644 index 0000000..da66ddf --- /dev/null +++ b/local.c @@ -0,0 +1,238 @@ +/** + * \file local.c + * Local delivery of mail via a MDA. + */ + + +#include +#include +#include +#include +#include + +#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"); + } +} diff --git a/local.h b/local.h new file mode 100644 index 0000000..7ce218d --- /dev/null +++ b/local.h @@ -0,0 +1,28 @@ +/** + * \file local.h + */ + +#ifndef _LOCAL_H +#define _LOCAL_H + + +#include + +#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 diff --git a/main.c b/main.c index 18672d1..eddda54 100644 --- a/main.c +++ b/main.c @@ -1,649 +1,370 @@ -/* - * 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 +/** + * \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 #include -#include -#include -#include -#include #include -#include -#include -#include - -#include -#include - -#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 -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); } diff --git a/main.h b/main.h new file mode 100644 index 0000000..5533d54 --- /dev/null +++ b/main.h @@ -0,0 +1,43 @@ +/** + * \file main.h + * Global declarations. + */ + +#ifndef _MAIN_H +#define _MAIN_H + + +#include + +/** + * 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 +#include +#include +#include + +#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 + +#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 diff --git a/parser.y b/parser.y index aebdd75..f142e75 100644 --- a/parser.y +++ b/parser.y @@ -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 #include @@ -14,7 +14,10 @@ #include -#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 , 1997. This source code example + * is part of fetchmail and the Unix Cookbook, and are released under the MIT + * license. + */ + + +#include +#include +#include +#include + + +#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
*/ + { + 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); diff --git a/smtp.c b/smtp.c new file mode 100644 index 0000000..7f472e1 --- /dev/null +++ b/smtp.c @@ -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 +#include +#include +#include +#include +#include +#include + +#include +#include + +#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; +} + +/*@}*/ diff --git a/smtp.h b/smtp.h new file mode 100644 index 0000000..da18bc6 --- /dev/null +++ b/smtp.h @@ -0,0 +1,59 @@ +/** + * \file smtp.h + */ + +#ifndef _SMTP_H +#define _SMTP_H + + +#include + +#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 -- cgit v1.2.3