summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJosé Fonseca <jrfonseca@users.sourceforge.net>2003-07-03 17:37:11 +0000
committerJosé Fonseca <jrfonseca@users.sourceforge.net>2003-07-03 17:37:11 +0000
commit2867d16bc9bae9764904efbf44cd5131fca1bc9b (patch)
tree1e910b1566a40e9b02961bf5aed2958b9fa7b974
parent2b266bab3687c078061c7b8bf3676ef3a61d53a7 (diff)
Modularization of the code.
Hability to get the recipients from the message headers. Local delivery via a MDA.
-rw-r--r--Makefile.am2
-rw-r--r--TODO2
-rw-r--r--esmtp.12
-rw-r--r--esmtp.h30
-rw-r--r--lexer.l1
-rw-r--r--list.h242
-rw-r--r--local.c238
-rw-r--r--local.h28
-rw-r--r--main.c939
-rw-r--r--main.h43
-rw-r--r--message.c386
-rw-r--r--message.h64
-rw-r--r--parser.y43
-rw-r--r--rfc822.c193
-rw-r--r--rfc822.h3
-rw-r--r--smtp.c437
-rw-r--r--smtp.h59
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
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 <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");
+ }
+}
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 <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
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 <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);
}
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 <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
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 <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);
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 <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;
+}
+
+/*@}*/
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 <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