aboutsummaryrefslogtreecommitdiffstats
path: root/smtpd/makemap.c
diff options
context:
space:
mode:
Diffstat (limited to 'smtpd/makemap.c')
-rw-r--r--smtpd/makemap.c521
1 files changed, 521 insertions, 0 deletions
diff --git a/smtpd/makemap.c b/smtpd/makemap.c
new file mode 100644
index 00000000..10e3f555
--- /dev/null
+++ b/smtpd/makemap.c
@@ -0,0 +1,521 @@
+/* $OpenBSD: makemap.c,v 1.73 2020/02/24 16:16:07 millert Exp $ */
+
+/*
+ * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org>
+ * Copyright (c) 2008-2009 Jacek Masiulaniec <jacekm@dobremiasto.net>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "includes.h"
+
+#ifdef HAVE_SYS_FILE_H
+#include <sys/file.h> /* Needed for flock */
+#endif
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/tree.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+
+#include <ctype.h>
+#ifdef HAVE_DB_H
+#include <db.h>
+#elif defined(HAVE_DB1_DB_H)
+#include <db1/db.h>
+#elif defined(HAVE_DB_185_H)
+#include <db_185.h>
+#endif
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <imsg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <limits.h>
+#ifdef HAVE_UTIL_H
+#include <util.h>
+#endif
+#ifdef HAVE_LIBUTIL_H
+#include <libutil.h>
+#endif
+
+#include "smtpd.h"
+#include "log.h"
+
+#define PATH_ALIASES SMTPD_CONFDIR "/aliases"
+
+static void usage(void);
+static int parse_map(DB *, int *, char *);
+static int parse_entry(DB *, int *, char *, size_t, size_t);
+static int parse_mapentry(DB *, int *, char *, size_t, size_t);
+static int parse_setentry(DB *, int *, char *, size_t, size_t);
+static int make_plain(DBT *, char *);
+static int make_aliases(DBT *, char *);
+static char *conf_aliases(char *);
+static int dump_db(const char *, DBTYPE);
+
+char *source;
+static int mode;
+
+enum output_type {
+ T_PLAIN,
+ T_ALIASES,
+ T_SET
+} type;
+
+/*
+ * Stub functions so that makemap compiles using minimum object files.
+ */
+int
+fork_proc_backend(const char *backend, const char *conf, const char *procname)
+{
+ return (-1);
+}
+
+int
+makemap(int prog_mode, int argc, char *argv[])
+{
+ struct stat sb;
+ char dbname[PATH_MAX];
+ DB *db;
+ const char *opts;
+ char *conf, *oflag = NULL;
+ int ch, dbputs = 0, Uflag = 0;
+ DBTYPE dbtype = DB_HASH;
+ char *p;
+ gid_t gid;
+ int fd = -1;
+
+ gid = getgid();
+ if (setresgid(gid, gid, gid) == -1)
+ err(1, "setresgid");
+
+ if ((env = config_default()) == NULL)
+ err(1, NULL);
+
+ log_init(1, LOG_MAIL);
+
+ mode = prog_mode;
+ conf = CONF_FILE;
+ type = T_PLAIN;
+ opts = "b:C:d:ho:O:t:U";
+ if (mode == P_NEWALIASES)
+ opts = "f:h";
+
+ while ((ch = getopt(argc, argv, opts)) != -1) {
+ switch (ch) {
+ case 'b':
+ if (optarg && strcmp(optarg, "i") == 0)
+ mode = P_NEWALIASES;
+ break;
+ case 'C':
+ break; /* for compatibility */
+ case 'd':
+ if (strcmp(optarg, "hash") == 0)
+ dbtype = DB_HASH;
+ else if (strcmp(optarg, "btree") == 0)
+ dbtype = DB_BTREE;
+ else
+ errx(1, "unsupported DB type '%s'", optarg);
+ break;
+ case 'f':
+ conf = optarg;
+ break;
+ case 'o':
+ oflag = optarg;
+ break;
+ case 'O':
+ if (strncmp(optarg, "AliasFile=", 10) != 0)
+ break;
+ type = T_ALIASES;
+ p = strchr(optarg, '=');
+ source = ++p;
+ break;
+ case 't':
+ if (strcmp(optarg, "aliases") == 0)
+ type = T_ALIASES;
+ else if (strcmp(optarg, "set") == 0)
+ type = T_SET;
+ else
+ errx(1, "unsupported type '%s'", optarg);
+ break;
+ case 'U':
+ Uflag = 1;
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* sendmail-compat makemap ... re-execute using proper interface */
+ if (argc == 2) {
+ if (oflag)
+ usage();
+
+ p = strstr(argv[1], ".db");
+ if (p == NULL || strcmp(p, ".db") != 0) {
+ if (!bsnprintf(dbname, sizeof dbname, "%s.db",
+ argv[1]))
+ errx(1, "database name too long");
+ }
+ else {
+ if (strlcpy(dbname, argv[1], sizeof dbname)
+ >= sizeof dbname)
+ errx(1, "database name too long");
+ }
+
+ execl(PATH_MAKEMAP, "makemap", "-d", argv[0], "-o", dbname,
+ "-", (char *)NULL);
+ err(1, "execl");
+ }
+
+ if (mode == P_NEWALIASES) {
+ if (geteuid())
+ errx(1, "need root privileges");
+ if (argc != 0)
+ usage();
+ type = T_ALIASES;
+ if (source == NULL)
+ source = conf_aliases(conf);
+ } else {
+ if (argc != 1)
+ usage();
+ source = argv[0];
+ }
+
+ if (Uflag)
+ return dump_db(source, dbtype);
+
+ if (oflag == NULL && asprintf(&oflag, "%s.db", source) == -1)
+ err(1, "asprintf");
+
+ if (strcmp(source, "-") != 0)
+ if (stat(source, &sb) == -1)
+ err(1, "stat: %s", source);
+
+ if (!bsnprintf(dbname, sizeof(dbname), "%s.XXXXXXXXXXX", oflag))
+ errx(1, "path too long");
+ if ((fd = mkstemp(dbname)) == -1)
+ err(1, "mkstemp");
+
+ db = dbopen(dbname, O_TRUNC|O_RDWR, 0644, dbtype, NULL);
+ if (db == NULL) {
+ warn("dbopen: %s", dbname);
+ goto bad;
+ }
+
+ if (strcmp(source, "-") != 0)
+ if (fchmod(db->fd(db), sb.st_mode) == -1 ||
+ fchown(db->fd(db), sb.st_uid, sb.st_gid) == -1) {
+ warn("couldn't carry ownership and perms to %s",
+ dbname);
+ goto bad;
+ }
+
+ if (!parse_map(db, &dbputs, source))
+ goto bad;
+
+ if (db->close(db) == -1) {
+ warn("dbclose: %s", dbname);
+ goto bad;
+ }
+
+ /* force to disk before renaming over an existing file */
+ if (fsync(fd) == -1) {
+ warn("fsync: %s", dbname);
+ goto bad;
+ }
+ if (close(fd) == -1) {
+ fd = -1;
+ warn("close: %s", dbname);
+ goto bad;
+ }
+ fd = -1;
+
+ if (rename(dbname, oflag) == -1) {
+ warn("rename");
+ goto bad;
+ }
+
+ if (mode == P_NEWALIASES)
+ printf("%s: %d aliases\n", source, dbputs);
+ else if (dbputs == 0)
+ warnx("warning: empty map created: %s", oflag);
+
+ return 0;
+bad:
+ if (fd != -1)
+ close(fd);
+ unlink(dbname);
+ return 1;
+}
+
+static int
+parse_map(DB *db, int *dbputs, char *filename)
+{
+ FILE *fp;
+ char *line;
+ size_t len;
+ size_t lineno = 0;
+
+ if (strcmp(filename, "-") == 0)
+ fp = fdopen(0, "r");
+ else
+ fp = fopen(filename, "r");
+ if (fp == NULL) {
+ warn("%s", filename);
+ return 0;
+ }
+
+ if (!isatty(fileno(fp)) && flock(fileno(fp), LOCK_SH|LOCK_NB) == -1) {
+ if (errno == EWOULDBLOCK)
+ warnx("%s is locked", filename);
+ else
+ warn("%s: flock", filename);
+ fclose(fp);
+ return 0;
+ }
+
+ while ((line = fparseln(fp, &len, &lineno,
+ NULL, FPARSELN_UNESCCOMM)) != NULL) {
+ if (!parse_entry(db, dbputs, line, len, lineno)) {
+ free(line);
+ fclose(fp);
+ return 0;
+ }
+ free(line);
+ }
+
+ fclose(fp);
+ return 1;
+}
+
+static int
+parse_entry(DB *db, int *dbputs, char *line, size_t len, size_t lineno)
+{
+ switch (type) {
+ case T_PLAIN:
+ case T_ALIASES:
+ return parse_mapentry(db, dbputs, line, len, lineno);
+ case T_SET:
+ return parse_setentry(db, dbputs, line, len, lineno);
+ }
+ return 0;
+}
+
+static int
+parse_mapentry(DB *db, int *dbputs, char *line, size_t len, size_t lineno)
+{
+ DBT key;
+ DBT val;
+ char *keyp;
+ char *valp;
+
+ keyp = line;
+ while (isspace((unsigned char)*keyp))
+ keyp++;
+ if (*keyp == '\0')
+ return 1;
+
+ valp = keyp;
+ strsep(&valp, " \t:");
+ if (valp == NULL || valp == keyp)
+ goto bad;
+ while (*valp == ':' || isspace((unsigned char)*valp))
+ valp++;
+ if (*valp == '\0')
+ goto bad;
+
+ /* Check for dups. */
+ key.data = keyp;
+ key.size = strlen(keyp) + 1;
+
+ xlowercase(key.data, key.data, strlen(key.data) + 1);
+ if (db->get(db, &key, &val, 0) == 0) {
+ warnx("%s:%zd: duplicate entry for %s", source, lineno, keyp);
+ return 0;
+ }
+
+ if (type == T_PLAIN) {
+ if (!make_plain(&val, valp))
+ goto bad;
+ }
+ else if (type == T_ALIASES) {
+ if (!make_aliases(&val, valp))
+ goto bad;
+ }
+
+ if (db->put(db, &key, &val, 0) == -1) {
+ warn("dbput");
+ return 0;
+ }
+
+ (*dbputs)++;
+
+ free(val.data);
+
+ return 1;
+
+bad:
+ warnx("%s:%zd: invalid entry", source, lineno);
+ return 0;
+}
+
+static int
+parse_setentry(DB *db, int *dbputs, char *line, size_t len, size_t lineno)
+{
+ DBT key;
+ DBT val;
+ char *keyp;
+
+ keyp = line;
+ while (isspace((unsigned char)*keyp))
+ keyp++;
+ if (*keyp == '\0')
+ return 1;
+
+ val.data = "<set>";
+ val.size = strlen(val.data) + 1;
+
+ /* Check for dups. */
+ key.data = keyp;
+ key.size = strlen(keyp) + 1;
+ xlowercase(key.data, key.data, strlen(key.data) + 1);
+ if (db->get(db, &key, &val, 0) == 0) {
+ warnx("%s:%zd: duplicate entry for %s", source, lineno, keyp);
+ return 0;
+ }
+
+ if (db->put(db, &key, &val, 0) == -1) {
+ warn("dbput");
+ return 0;
+ }
+
+ (*dbputs)++;
+
+ return 1;
+}
+
+static int
+make_plain(DBT *val, char *text)
+{
+ val->data = xstrdup(text);
+ val->size = strlen(text) + 1;
+
+ return (val->size);
+}
+
+static int
+make_aliases(DBT *val, char *text)
+{
+ struct expandnode xn;
+ char *subrcpt;
+ char *origtext;
+
+ val->data = NULL;
+ val->size = 0;
+
+ origtext = xstrdup(text);
+
+ while ((subrcpt = strsep(&text, ",")) != NULL) {
+ /* subrcpt: strip initial and trailing whitespace. */
+ subrcpt = strip(subrcpt);
+ if (*subrcpt == '\0')
+ goto error;
+
+ if (!text_to_expandnode(&xn, subrcpt))
+ goto error;
+ }
+
+ val->data = origtext;
+ val->size = strlen(origtext) + 1;
+ return (val->size);
+
+error:
+ free(origtext);
+
+ return 0;
+}
+
+static char *
+conf_aliases(char *cfgpath)
+{
+ struct table *table;
+ char *path;
+ char *p;
+
+ if (parse_config(env, cfgpath, 0))
+ exit(1);
+
+ table = table_find(env, "aliases");
+ if (table == NULL)
+ return (PATH_ALIASES);
+
+ path = xstrdup(table->t_config);
+ p = strstr(path, ".db");
+ if (p == NULL || strcmp(p, ".db") != 0) {
+ return (path);
+ }
+ *p = '\0';
+ return (path);
+}
+
+static int
+dump_db(const char *dbname, DBTYPE dbtype)
+{
+ DB *db;
+ DBT key, val;
+ char *keystr, *valstr;
+ int r;
+
+ db = dbopen(dbname, O_RDONLY, 0644, dbtype, NULL);
+ if (db == NULL)
+ err(1, "dbopen: %s", dbname);
+
+ for (r = db->seq(db, &key, &val, R_FIRST); r == 0;
+ r = db->seq(db, &key, &val, R_NEXT)) {
+ keystr = key.data;
+ valstr = val.data;
+ if (keystr[key.size - 1] == '\0')
+ key.size--;
+ if (valstr[val.size - 1] == '\0')
+ val.size--;
+ printf("%.*s\t%.*s\n", (int)key.size, keystr,
+ (int)val.size, valstr);
+ }
+ if (r == -1)
+ err(1, "db->seq: %s", dbname);
+
+ if (db->close(db) == -1)
+ err(1, "dbclose: %s", dbname);
+
+ return 0;
+}
+
+static void
+usage(void)
+{
+ if (mode == P_NEWALIASES)
+ fprintf(stderr, "usage: newaliases [-f file]\n");
+ else
+ fprintf(stderr, "usage: makemap [-U] [-d dbtype] [-o dbfile] "
+ "[-t type] file\n");
+ exit(1);
+}