>From 5d1f51faf9ffdb25fb30266d4a89ed98decbfdad Mon Sep 17 00:00:00 2001
From: Scott Shumate <sshumate@austin.rr.com>
Date: Tue, 27 Sep 2011 13:12:29 -0500
Subject: [PATCH] Added aliases support.

---
 doc/msmtp.1     |   38 ++++++++
 doc/msmtp.texi  |   35 +++++++
 src/Makefile.am |    3 +-
 src/aliases.c   |  282 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/aliases.h   |   50 ++++++++++
 src/conf.c      |   21 ++++
 src/conf.h      |    2 +
 src/msmtp.c     |   38 +++++++-
 8 files changed, 466 insertions(+), 3 deletions(-)
 create mode 100644 src/aliases.c
 create mode 100644 src/aliases.h

diff --git a/doc/msmtp.1 b/doc/msmtp.1
index fd3713f..ef02734 100644
--- a/doc/msmtp.1
+++ b/doc/msmtp.1
@@ -167,6 +167,9 @@ Resent-Cc, and Resent-Bcc headers in the first block of Resent- headers are
 used instead.
 .IP "\-\-read\-envelope\-from"
 Read the envelope from address from the From header of the mail.
+.IP "\-\-aliases=[\fIfile\fP]"
+Replace local recipients with addresses in the aliases file.  See the
+\fBaliases\fP command below.
 .IP "\-\-"
 This marks the end of options. All following arguments will be treated as
 recipient addresses, even if they start with a `\-'.
@@ -482,6 +485,23 @@ recipient addresses, size of the mail as transferred to the server (only if the
 delivery succeeded), SMTP status code and SMTP error message (only in case of
 failure and only if available), error message (only in case of failure and only
 if available), exit code (from sysexits.h; EX_OK indicates success).
+.br
+.IP "aliases [\fIfile\fP]"
+Replace local recipients with addresses in the aliases file.  The aliases file
+is a plain text file containing mappings between a local address and a list of
+domain addresses.  A local address is defined as one without an '@' character
+and a domain address is one with an '@' character.  The mappings are of the
+form:
+.br
+    local: someone@isp.com, person@domain.org
+.br
+Multiple domain addresses are separated with commas.  Comments start with '#'
+and continue to the end of the line.
+.br
+The local address 'default' has special significance and is matched if the
+local address is not found in the aliases file.  If no 'default' alias is
+found, then the local address is left as is.
+.br
 .SH EXAMPLES
 .br
 .B Configuration file
@@ -594,6 +614,24 @@ the following Mutt configuration lines:
 Define a default account, and put the following in your ~/.mailrc:
 .br
 .B set sendmail="/path/to/msmtp"
+
+.PP
+.B Aliases file
+.PP
+# Example aliases file
+
+# Send root to Joe and Jane
+.br
+root: joe_smith@isp.com, jane_chang@isp.com
+
+# Send cron to Mark
+.br
+cron: mark_jones@isp.com
+
+# Send everything else to admin
+.br
+default: admin@domain.org
+
 .SH FILES
 .IP "SYSCONFDIR/msmtprc"
 System configuration file. Use
diff --git a/doc/msmtp.texi b/doc/msmtp.texi
index e946622..56f60b4 100644
--- a/doc/msmtp.texi
+++ b/doc/msmtp.texi
@@ -411,6 +411,22 @@ This command enables or disables syslog logging. The facility can be one of
 @samp{LOG_USER}, @samp{LOG_MAIL}, @samp{LOG_LOCAL0}, @dots{}, @samp{LOG_LOCAL7}.
 The default facility is @samp{LOG_USER}. Syslog logging is disabled by default.
 @xref{Logging}.
+@anchor{aliases}
+@item aliases [@var{file}]
+@cmindex aliases
+Replace local recipients with addresses in the aliases file.  The aliases file
+is a plain text file containing mappings between a local address and a list of
+domain addresses.  A local address is defined as one without an '@@' character
+and a domain address is one with an '@@' character.  The mappings are of the
+form:
+
+local: someone@@isp.com, person@@domain.org
+
+Multiple domain addresses are separated with commas.  Comments start with '#'
+and continue to the end of the line.
+The local address 'default' has special significance and is matched if the
+local address is not found in the aliases file.  If no 'default' alias is
+found, then the local address is left as is.
 @end table
 
 
@@ -642,6 +658,9 @@ used instead.
 @itemx --read-envelope-from
 @opindex --read-envelope-from
 Read the envelope from address from the From header of the mail.
+@itemx --aliases=[@var{file}]
+@opindex --aliases
+Replace local recipients with addresses in the aliases file. @xref{aliases}.
 @itemx --
 This marks the end of options. All following arguments will be treated as
 recipient addresses, even if they start with a '-'.
@@ -1074,6 +1093,7 @@ Request the delivery of the messages in the given queue.
 * A user configuration file::
 * Using msmtp with Mutt::
 * Using msmtp with mail::
+* Aliases file::
 @end menu
 
 @node A system wide configuration file
@@ -1203,5 +1223,20 @@ set sendmail="/path/to/msmtp"
 You need to define a default account, because mail does not allow extra options
 to the msmtp command line.
 
+@node Aliases file
+@section Aliases file
+
+@example
+# Example aliases file
+
+# Send root to Joe and Jane
+root: joe_smith@@isp.com, jane_chang@@isp.com
+
+# Send cron to Mark
+cron: mark_jones@@isp.com
+
+# Send everything else to admin
+default: admin@@domain.org
+@end example
 
 @bye
diff --git a/src/Makefile.am b/src/Makefile.am
index 119e07c..0516c19 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -11,7 +11,8 @@ msmtp_SOURCES = \
 	stream.c stream.h \
 	tools.c tools.h \
 	xalloc.c xalloc.h \
-	gettext.h
+	gettext.h \
+	aliases.c aliases.h
 
 if HAVE_TLS
 msmtp_SOURCES += tls.c tls.h
diff --git a/src/aliases.c b/src/aliases.c
new file mode 100644
index 0000000..c0b8a89
--- /dev/null
+++ b/src/aliases.c
@@ -0,0 +1,282 @@
+/*
+ * aliases.c
+ *
+ * This file is part of msmtp, an SMTP client.
+ *
+ * Copyright (C) 2011
+ * Scott Shumate <sshumate@austin.rr.com>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define MAXTOKS 32
+#define MAXLINE 512
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include "gettext.h"
+#define _(string) gettext(string)
+
+#include "aliases.h"
+#include "list.h"
+#include "tools.h"
+#include "xalloc.h"
+
+typedef struct alias
+{
+    char *alias_str;
+    list_t *addr_list;
+} alias_t;
+
+static char *trim(char *str)
+{
+    char *end;
+
+    while (isspace(*str))
+        str++;
+
+    end = str + strlen(str) - 1;
+    while (end > str && isspace(*end))
+        end--;
+
+    *(++end) = '\0';
+
+    return str;
+}
+
+static int split(char *str, char delim, char *tokv[])
+{
+    int tokc = 0;
+    char *loc;
+
+    while (tokc < MAXTOKS && (loc = strchr(str, delim)) != NULL)
+    {
+        *loc = '\0';
+        *tokv++ = trim(str);
+        tokc++;
+        str = loc + 1;
+    }
+
+    if (tokc < MAXTOKS)
+    {
+        *tokv++ = trim(str);
+        tokc++;
+    }
+
+    return tokc;
+}
+
+static int is_alias(const char *str)
+{
+    return (*str != '\0' && strchr(str, '@') == NULL);
+}
+
+static int is_address(const char *str)
+{
+    return (*str != '\0' && strchr(str, '@') != NULL);
+}
+
+static alias_t *alias_find(const char *alias_str, list_t *alias_list)
+{
+    alias_t *entry;
+
+    while (!list_is_empty(alias_list))
+    {
+        entry = alias_list->next->data;
+        if (strcmp(alias_str, entry->alias_str) == 0)
+            return entry;
+        alias_list = alias_list->next;
+    }
+
+    return NULL;
+}
+
+static int aliases_read(FILE *f, list_t *alias_list, char **errstr)
+{
+    char line[MAXLINE];
+    char *tokv[MAXTOKS];
+    int tokc;
+    int i;
+    int lnum = 1;
+    alias_t *entry;
+    list_t *addr_list;
+    list_t *alias_itr = alias_list;
+
+    while (fgets(line, MAXLINE, f) != NULL)
+    {
+        /* Check line length */
+        if (strlen(line) == MAXLINE - 1)
+        {
+            *errstr = xasprintf(_("line %d: longer than %d characters"),
+                    lnum, MAXLINE - 1);
+            return ALIASES_EPARSE;
+        }
+
+        /* Split off comments */
+        tokc = split(line, '#', tokv);
+
+        /* Split on the colon delimiter */
+        tokc = split(tokv[0], ':', tokv);
+
+        /* If line is not empty */
+        if (tokc != 1 || *tokv[0] != '\0')
+        {
+            /* Expect a single delimiter */
+            if (tokc != 2)
+            {
+                *errstr = xasprintf(_("line %d: single ':' delimiter expected"),
+                        lnum);
+                return ALIASES_EPARSE;
+            }
+
+            /* Check for a valid alias */
+            if (!is_alias(tokv[0]))
+            {
+                *errstr = xasprintf(_("line %d: invalid alias '%s'"),
+                        lnum, tokv[0]);
+                return ALIASES_EPARSE;
+            }
+
+            if (alias_find(tokv[0], alias_list))
+            {
+                *errstr = xasprintf(_("line %d: duplicate alias '%s'"),
+                        lnum, tokv[0]);
+                return ALIASES_EPARSE;
+            }
+
+            entry = xmalloc(sizeof(alias_t));
+            entry->alias_str = xstrdup(tokv[0]);
+            entry->addr_list = list_new();
+            addr_list = entry->addr_list;
+
+            list_insert(alias_itr, entry);
+            alias_itr = alias_itr->next;
+
+            /* Add addresses to the list*/
+            tokc = split(tokv[1], ',', tokv);
+            for (i = 0; i < tokc; i++)
+            {
+                if (!is_address(tokv[i]))
+                {
+                    *errstr = xasprintf(_("line %d: invalid address '%s'"),
+                            lnum, tokv[i]);
+                    return ALIASES_EPARSE;
+                }
+
+                list_insert(addr_list, xstrdup(tokv[i]));
+                addr_list = addr_list->next;
+            }
+        }
+        lnum++;
+    }
+    if (ferror(f))
+    {
+        *errstr = xasprintf(_("input error"));
+        return ALIASES_EIO;
+    }
+
+    return ALIASES_EOK;
+}
+
+static void alias_free(void *ptr)
+{
+    alias_t *entry = ptr;
+  
+    if (entry)
+    { 
+        list_xfree(entry->addr_list, free);
+        free(entry->alias_str);
+        free(entry);
+    }
+}
+
+int aliases_replace(const char *aliases, list_t *recipient_list, char **errstr)
+{
+    FILE *f;
+    int e;
+    list_t *alias_list;
+    list_t *addr_list;
+    alias_t *entry;
+    list_t *rec_itr;
+
+    /* Make sure there is at least one alias */
+    for (rec_itr = recipient_list;
+         !list_is_empty(rec_itr);
+         rec_itr = rec_itr->next)
+    {
+        if (is_alias(rec_itr->next->data))
+            break;
+    }
+    if (list_is_empty(rec_itr))
+        return ALIASES_EOK;
+
+    /* Open and read the alias file */
+    if (!(f = fopen(aliases, "r")))
+    {
+        *errstr = xasprintf("%s", strerror(errno));
+        return ALIASES_ECANTOPEN;
+    }
+
+    alias_list = list_new();
+
+    if ((e = aliases_read(f, alias_list, errstr))
+            != ALIASES_EOK)
+    {
+        fclose(f);
+        list_xfree(alias_list, alias_free);
+        return e;
+    }
+
+    fclose(f);
+
+    /* Process all aliases in the recipient list */
+    for (rec_itr = recipient_list;
+         !list_is_empty(rec_itr);
+         rec_itr = rec_itr->next)
+    {
+        if (is_alias(rec_itr->next->data))
+        {
+            entry = alias_find(rec_itr->next->data, alias_list);
+            if (entry == NULL)
+            {
+                entry = alias_find("default", alias_list);
+            }
+            if (entry != NULL)
+            {
+                list_xremove(rec_itr, free);
+                addr_list = entry->addr_list;
+                if (!list_is_empty(addr_list))
+                {
+                    list_insert(rec_itr, xstrdup(addr_list->next->data));
+                    addr_list = addr_list->next;
+                    while (!list_is_empty(addr_list))
+                    {
+                        list_insert(rec_itr, xstrdup(addr_list->next->data));
+                        rec_itr = rec_itr->next;
+                        addr_list = addr_list->next;
+                    }
+                }
+            }
+        }
+    }
+
+    list_xfree(alias_list, alias_free);
+
+    return ALIASES_EOK;
+}
+
diff --git a/src/aliases.h b/src/aliases.h
new file mode 100644
index 0000000..7c14649
--- /dev/null
+++ b/src/aliases.h
@@ -0,0 +1,50 @@
+/*
+ * aliases.h
+ *
+ * This file is part of msmtp, an SMTP client.
+ *
+ * Copyright (C) 2011
+ * Scott Shumate <sshumate@austin.rr.com>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef ALIASES_H
+#define ALIASES_H
+
+#include "list.h"
+
+/*
+ * If a function with an 'errstr' argument returns a value != ALIASES_EOK,
+ * '*errstr' either points to an allocates string containing an error
+ * description or is NULL.
+ * If such a function returns ALIASES_EOK, 'errstr' will not be changed.
+ */
+#define ALIASES_EOK        0       /* no error */
+#define ALIASES_ECANTOPEN  1       /* Cannot open file */
+#define ALIASES_EIO        2       /* Input/output error */
+#define ALIASES_EPARSE     3       /* Parse error */
+#define ALIASES_EINSECURE  4       /* Insecure permissions */
+
+/*
+ * aliases()
+ *
+ * Read 'aliases' and replace all recipients matching an alias with
+ * its list of addresses.
+ * Used error codes: ALIASES_EOK, ALIASES_ECANTOPEN, ALIASES_EIO,
+ * ALIASES_EPARSE, ALIASES_EINSECURE
+ */
+int aliases_replace(const char *aliases, list_t *recipient_list, char **errstr);
+
+#endif
diff --git a/src/conf.c b/src/conf.c
index e3430b5..1964485 100644
--- a/src/conf.c
+++ b/src/conf.c
@@ -90,6 +90,7 @@ account_t *account_new(const char *conffile, const char *id)
     a->tls_priorities = NULL;
     a->logfile = NULL;
     a->syslog = NULL;
+    a->aliases = NULL;
     return a;
 }
 
@@ -160,6 +161,7 @@ account_t *account_copy(account_t *acc)
             acc->tls_priorities ? xstrdup(acc->tls_priorities) : NULL;
         a->logfile = acc->logfile ? xstrdup(acc->logfile) : NULL;
         a->syslog = acc->syslog ? xstrdup(acc->syslog) : NULL;
+        a->aliases = acc->aliases ? xstrdup(acc->aliases) : NULL;
     }
     return a;
 }
@@ -198,6 +200,7 @@ void account_free(void *a)
         free(p->dsn_notify);
         free(p->logfile);
         free(p->syslog);
+        free(p->aliases);
         free(p);
     }
 }
@@ -648,6 +651,11 @@ void override_account(account_t *acc1, account_t *acc2)
         free(acc1->syslog);
         acc1->syslog = acc2->syslog ? xstrdup(acc2->syslog) : NULL;
     }
+    if (acc2->mask & ACC_ALIASES)
+    {
+        free(acc1->aliases);
+        acc1->aliases = acc2->aliases ? xstrdup(acc2->aliases) : NULL;
+    }
     acc1->mask |= acc2->mask;
 }
 
@@ -1616,6 +1624,19 @@ int read_conffile(const char *conffile, FILE *f, list_t **acc_list,
                 acc->syslog = xstrdup(arg);
             }
         }
+        else if (strcmp(cmd, "aliases") == 0)
+        {
+            acc->mask |= ACC_ALIASES;
+            free(acc->aliases);
+            if (*arg == '\0')
+            {
+                acc->aliases = NULL;
+            }
+            else
+            {
+                acc->aliases = xstrdup(arg);
+            }
+        }
         else if (strcmp(cmd, "tls_nocertcheck") == 0)
         {
             /* compatibility with 1.2.x */
diff --git a/src/conf.h b/src/conf.h
index 8e508c4..60cc80e 100644
--- a/src/conf.h
+++ b/src/conf.h
@@ -72,6 +72,7 @@
 #define ACC_TLS_PRIORITIES              (1 << 26)
 #define ACC_LOGFILE                     (1 << 27)
 #define ACC_SYSLOG                      (1 << 28)
+#define ACC_ALIASES                     (1 << 29)
 
 typedef struct
 {
@@ -118,6 +119,7 @@ typedef struct
     /* logging */
     char *logfile;              /* NULL or logfile */
     char *syslog;               /* NULL or syslog facility */
+    char *aliases;              /* NULL or aliases file */
 } account_t;
 
 
diff --git a/src/msmtp.c b/src/msmtp.c
index 03c8ced..e6afff4 100644
--- a/src/msmtp.c
+++ b/src/msmtp.c
@@ -69,6 +69,7 @@ extern int optind;
 #include "netrc.h"
 #include "smtp.h"
 #include "tools.h"
+#include "aliases.h"
 #ifdef HAVE_TLS
 # include "tls.h"
 #endif /* HAVE_TLS */
@@ -2408,6 +2409,9 @@ void msmtp_print_help(void)
                 "the mail.\n"
             "  --read-envelope-from         Read envelope from address from "
                 "the mail.\n"
+            "  --aliases=[file]             Replace local recipients with "
+                "addresses\n"
+            "                               in the aliases file.\n"
             "  --                           End of options.\n"
             "Accepted but ignored: -A, -B, -bm, -F, -G, -h, -i, -L, -m, -n, "
                 "-O, -o, -v\n"
@@ -2473,6 +2477,7 @@ typedef struct
 #define LONGONLYOPT_MAILDOMAIN                  24
 #define LONGONLYOPT_AUTO_FROM                   25
 #define LONGONLYOPT_READ_ENVELOPE_FROM          26
+#define LONGONLYOPT_ALIASES                     27
 
 int msmtp_cmdline(msmtp_cmdline_conf_t *conf, int argc, char *argv[])
 {
@@ -2528,6 +2533,7 @@ int msmtp_cmdline(msmtp_cmdline_conf_t *conf, int argc, char *argv[])
         { "keepbcc",               optional_argument, 0, LONGONLYOPT_KEEPBCC },
         { "logfile",               required_argument, 0, 'X' },
         { "syslog",                optional_argument, 0, LONGONLYOPT_SYSLOG },
+        { "aliases",               required_argument, 0, LONGONLYOPT_ALIASES },
         { "read-recipients",       no_argument,       0, 't' },
         { "read-envelope-from",    no_argument,       0,
             LONGONLYOPT_READ_ENVELOPE_FROM },
@@ -3094,6 +3100,19 @@ int msmtp_cmdline(msmtp_cmdline_conf_t *conf, int argc, char *argv[])
                 conf->cmdline_account->mask |= ACC_SYSLOG;
                 break;
 
+            case LONGONLYOPT_ALIASES:
+                free(conf->cmdline_account->aliases);
+                if (*optarg)
+                {
+                    conf->cmdline_account->aliases = xstrdup(optarg);
+                }
+                else
+                {
+                    conf->cmdline_account->aliases = NULL;
+                }
+                conf->cmdline_account->mask |= ACC_ALIASES;
+                break;
+
             case 't':
                 conf->read_recipients = 1;
                 break;
@@ -3428,7 +3447,8 @@ void msmtp_print_conf(msmtp_cmdline_conf_t conf, account_t *account)
                 "dsn_return            = %s\n"
                 "keepbcc               = %s\n"
                 "logfile               = %s\n"
-                "syslog                = %s\n",
+                "syslog                = %s\n"
+                "aliases               = %s\n",
                 account->auto_from ? _("on") : _("off"),
                 account->maildomain ? account->maildomain : _("(not set)"),
                 account->from ? account->from : conf.read_envelope_from
@@ -3437,7 +3457,8 @@ void msmtp_print_conf(msmtp_cmdline_conf_t conf, account_t *account)
                 account->dsn_return ? account->dsn_return : _("(not set)"),
                 account->keepbcc ? _("on") : _("off"),
                 account->logfile ? account->logfile : _("(not set)"),
-                account->syslog ? account->syslog : _("(not set)"));
+                account->syslog ? account->syslog : _("(not set)"),
+                account->aliases ? account->aliases : _("(not set)"));
         if (conf.read_recipients)
         {
             printf(_("reading recipients from the command line "
@@ -3763,6 +3784,19 @@ int main(int argc, char *argv[])
         msmtp_print_conf(conf, account);
     }
 
+    /* replace aliases */
+    if (conf.sendmail && account->aliases)
+    {
+        if ((e = aliases_replace(account->aliases, conf.recipients,
+                         &errstr)) != ALIASES_EOK)
+        {
+            print_error(_("error in aliases file: %s"),
+                    msmtp_sanitize_string(errstr));
+            error_code = EX_CONFIG;
+            goto exit;
+        }
+    }
+
     /* stop if there's nothing to do */
     if (conf.pretend || (!conf.sendmail && !conf.serverinfo && !conf.rmqs))
     {
-- 
1.7.2

