diff options
author | Sergey Poznyakoff <gray@gnu.org.ua> | 2007-11-19 13:41:07 +0000 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org.ua> | 2007-11-19 13:41:07 +0000 |
commit | 719964de49d9a50b63b006bcf64e632c2503327a (patch) | |
tree | d1210d7baaec5a7fcc05067108b6dd7d24cd9e31 | |
parent | b1a3e134c1e3127cf3f31cdfa24d784858c9873d (diff) | |
download | mailfromd-719964de49d9a50b63b006bcf64e632c2503327a.tar.gz mailfromd-719964de49d9a50b63b006bcf64e632c2503327a.tar.bz2 |
* smap: New directory.
* smap/smap.c: New file. A general-purpose socket map for MeTA1
(for future use).
git-svn-id: file:///svnroot/mailfromd/trunk@1528 7a8a7f39-df28-0410-adc6-e0d955640f24
-rw-r--r-- | ChangeLog | 4 | ||||
-rw-r--r-- | smap/smap.c | 907 |
2 files changed, 911 insertions, 0 deletions
@@ -1,5 +1,9 @@ 2007-11-19 Sergey Poznyakoff <gray@gnu.org.ua> + * smap: New directory. + * smap/smap.c: New file. A general-purpose socket map for MeTA1 + (for future use). + * src/mu_dbm.c [WITH_GDBM]: do not bail out if the database does not exist. * src/mailfromd.h: Add argp.h diff --git a/smap/smap.c b/smap/smap.c new file mode 100644 index 00000000..f22cf27b --- /dev/null +++ b/smap/smap.c @@ -0,0 +1,907 @@ +/* This file is part of smap. + Copyright (C) 2006, 2007 Sergey Poznyakoff + + 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, 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/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <sys/types.h> +#include <sys/un.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <signal.h> +#include <syslog.h> +#include <grp.h> +#include <pwd.h> +#include <ctype.h> +#include <sysexits.h> /* FIXME */ +#include <mailutils/mailutils.h> +#define obstack_chunk_alloc malloc +#define obstack_chunk_free free +#include <obstack.h> + +/* FIXME */ +#include <inttypes.h> +const char *mu_umaxtostr (unsigned slot, uintmax_t val); + +#define DEFAULT_URL "tcp://127.0.0.1:" +int reuse_smap_address = 1; +char *smap_url_string; +char *run_as_user; +char *syslog_tag; +char *negative_reply = "Nie znam takiego"; +char *positive_reply = "Swoj chlop"; + +struct daemon_param daemon_param = { + MODE_DAEMON, /* Start in daemon mode */ + 20, /* Default maximum number of children */ + 0, /* No standard port */ + 600, /* Idle timeout */ + 0, /* No transcript by default */ + NULL /* No PID file by default */ +}; + +typedef union +{ + struct sockaddr sa; + struct sockaddr_in s_in; + struct sockaddr_un s_un; +} all_addr_t; + +enum macro_id { + MACRO_KEY, + MACRO_AUTH_NAME, + MACRO_AUTH_PASSWD, + MACRO_AUTH_UID, + MACRO_AUTH_GID, + MACRO_AUTH_GECOS, + MACRO_AUTH_DIR, + MACRO_AUTH_SHELL, + MACRO_AUTH_MAILBOX, + MACRO_AUTH_QUOTA +}; + +struct macro_tab +{ + char *name; + size_t len; + enum macro_id id; +}; + +static struct macro_tab macro_tab[] = { +#define S(s,m) { s, sizeof (s) - 1, m } + { "key", 3, MACRO_KEY }, + S(MU_AUTH_NAME, MACRO_AUTH_NAME), + S(MU_AUTH_PASSWD, MACRO_AUTH_PASSWD), + S(MU_AUTH_UID, MACRO_AUTH_UID), + S(MU_AUTH_GID, MACRO_AUTH_GID), + S(MU_AUTH_GECOS, MACRO_AUTH_GECOS), + S(MU_AUTH_DIR, MACRO_AUTH_DIR), + S(MU_AUTH_SHELL, MACRO_AUTH_SHELL), + S(MU_AUTH_MAILBOX, MACRO_AUTH_MAILBOX), + S(MU_AUTH_QUOTA, MACRO_AUTH_QUOTA), + { NULL } +#undef S +#undef concat +}; + +static struct obstack expand_stk; + +static const char * +expand_macro (const char *str, const char *key, struct mu_auth_data *auth, + int delim, const char **endp) +{ + const char *p; + size_t len; + struct macro_tab *macro; + const char *ret = NULL; + + for (p = str; *p != delim && isascii (*p) && isalpha (*p); p++) + ; + len = p - str; + for (macro = macro_tab; macro->name; macro++) + { + if (len == macro->len && memcmp (macro->name, str, len) == 0) + { + switch (macro->id) + { + case MACRO_KEY: + ret = key; + break; + + case MACRO_AUTH_NAME: + ret = auth ? auth->name : ""; + break; + + case MACRO_AUTH_PASSWD: + ret = auth ? auth->passwd : ""; + break; + + case MACRO_AUTH_UID: + ret = auth ? mu_umaxtostr (1, auth->uid) : "-1"; + break; + + case MACRO_AUTH_GID: + ret = auth ? mu_umaxtostr (1, auth->gid) : "-1"; + break; + + case MACRO_AUTH_GECOS: + ret = auth ? auth->gecos : ""; + break; + + case MACRO_AUTH_DIR: + ret = auth ? auth->dir : ""; + break; + + case MACRO_AUTH_SHELL: + ret = auth ? auth->shell : ""; + break; + + case MACRO_AUTH_MAILBOX: + ret = auth ? auth->mailbox : ""; + break; + + case MACRO_AUTH_QUOTA: + ret = auth ? mu_umaxtostr (1, auth->quota) : "NONE"; + break; + } + break; + } + } + if (ret) + *endp = *p ? p + 1 : p; + return ret; +} + +static const char * +expand_reply_text (const char *arg, const char *key, struct mu_auth_data *auth) +{ + const char *p; + const char *str; + + obstack_init (&expand_stk); + for (p = arg; *p; ) + { + if (*p == '$') + { + if (p[1] == '$') + { + obstack_1grow (&expand_stk, '$'); + p += 2; + } + else if (p[1] == '{') + { + str = expand_macro (p + 2, key, auth, '}', &p); + if (str) + obstack_grow (&expand_stk, str, strlen (str)); + else + { + obstack_grow (&expand_stk, p, 2); + p += 2; + } + } + else + { + str = expand_macro (p + 1, key, auth, 0, &p); + if (str) + obstack_grow (&expand_stk, str, strlen (str)); + else + { + obstack_grow (&expand_stk, p, 1); + p++; + } + } + } + else + { + obstack_1grow (&expand_stk, *p); + p++; + } + } + obstack_1grow (&expand_stk, 0); + return obstack_finish (&expand_stk); +} + +static int +smap_open_internal (int *pfd, mu_url_t url, const char *urlstr) +{ + int fd; + int rc; + int t = 1; + char buffer[64]; + all_addr_t addr; + int addrsize; + mode_t saved_umask; + + rc = mu_url_get_scheme (url, buffer, sizeof buffer, NULL); + if (rc) + { + mu_error (_("%s: cannot get scheme from URL: %s"), + urlstr, mu_strerror(rc)); + return EX_CONFIG; + } + + memset (&addr, 0, sizeof addr); + if (strcmp (buffer, "file") == 0 || strcmp (buffer, "socket") == 0) + { + size_t size; + + rc = mu_url_get_path (url, NULL, 0, &size); + if (rc) + { + mu_error (_("%s: cannot get path: %s"), urlstr, mu_strerror(rc)); + return EX_CONFIG; + } + + if (size > sizeof addr.s_un.sun_path - 1) + { + mu_error (_("%s: file name too long"), urlstr); + return EX_TEMPFAIL; + } + mu_url_get_path (url, addr.s_un.sun_path, sizeof addr.s_un.sun_path, + NULL); + + fd = socket (PF_UNIX, SOCK_STREAM, 0); + if (fd < 0) + { + mu_error ("socket: %s", mu_strerror (errno)); + return EX_TEMPFAIL; + } + + addr.s_un.sun_family = AF_UNIX; + addrsize = sizeof addr.s_un; + + if (reuse_smap_address) + { + struct stat st; + if (stat (addr.s_un.sun_path, &st)) + { + if (errno != ENOENT) + { + mu_error (_("file %s exists but cannot be stat'd"), + addr.s_un.sun_path); + return EX_TEMPFAIL; + } + } + else if (!S_ISSOCK (st.st_mode)) + { + mu_error (_("file %s is not a socket"), + addr.s_un.sun_path); + return EX_TEMPFAIL; + } + else + unlink (addr.s_un.sun_path); + } + + } + else if (strcmp (buffer, "tcp") == 0) + { + size_t size; + long n; + struct hostent *hp; + char *path = NULL; + short port = 0; + + rc = mu_url_get_port (url, &n); + if (rc) + { + mu_error (_("%s: cannot get port: %s"), urlstr, mu_strerror(rc)); + return EX_CONFIG; + } + + if (n == 0 || (port = n) != n) + { + mu_error (_("Port out of range: %ld"), n); + return EX_CONFIG; + } + + rc = mu_url_get_host (url, NULL, 0, &size); + if (rc) + { + mu_error (_("%s: cannot get host: %s"), urlstr, mu_strerror(rc)); + return EX_CONFIG; + } + path = malloc (size + 1); + if (!path) + { + mu_error (_("Not enough memory")); + return EX_TEMPFAIL; + } + mu_url_get_host (url, path, size + 1, NULL); + + fd = socket (PF_INET, SOCK_STREAM, 0); + if (fd < 0) + { + mu_error ("socket: %s", mu_strerror (errno)); + return EX_TEMPFAIL; + } + + addr.s_in.sin_family = AF_INET; + if (hp = gethostbyname (path)) + { + char **ap; + int count = 0; + + addr.s_in.sin_addr.s_addr = *(unsigned long*) hp->h_addr_list[0]; + + for (ap = hp->h_addr_list; *ap; ap++) + count++; + if (count > 1) + mu_error (_("warning: %s has several IP addresses, using %s"), + path, inet_ntoa (addr.s_in.sin_addr)); + } + else if (inet_aton (path, &addr.s_in.sin_addr) == 0) + { + mu_error ("invalid IP address: %s", path); + return EX_TEMPFAIL; + } + addr.s_in.sin_port = htons (port); + addrsize = sizeof addr.s_in; + + if (reuse_smap_address) + setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &t, sizeof(t)); + } + else + { + mu_error (_("%s: invalid scheme"), urlstr); + return EX_CONFIG; + } + + saved_umask = umask (0117); + if (bind (fd, &addr.sa, addrsize) == -1) + { + mu_error ("bind: %s", strerror (errno)); + close (fd); + return EXIT_FAILURE; + } + umask (saved_umask); + *pfd = fd; + return 0; +} + +int +smap_open (int *pfd, char *urlstr) +{ + mu_url_t url = NULL; + int rc; + + rc = mu_url_create (&url, urlstr); + if (rc) + { + mu_error (_("%s: cannot create URL: %s"), + urlstr, mu_strerror (rc)); + return EX_CONFIG; + } + rc = mu_url_parse (url); + if (rc) + { + mu_error (_("%s: error parsing URL: %s"), + urlstr, mu_strerror(rc)); + return EX_CONFIG; + } + + rc = smap_open_internal (pfd, url, urlstr); + mu_url_destroy (&url); + return rc; +} + +size_t children; +static int need_cleanup = 0; + +void +process_cleanup () +{ + pid_t pid; + int status; + + if (need_cleanup) + { + need_cleanup = 0; + while ( (pid = waitpid (-1, &status, WNOHANG)) > 0) + --children; + } +} + +RETSIGTYPE +smap_sigchld (int signo) +{ + need_cleanup = 1; +#ifndef HAVE_SIGACTION + signal (signo, smap_sigchld); +#endif +} + +void +log_connection (all_addr_t *addr, socklen_t addrlen) +{ + switch (addr->sa.sa_family) + { + case PF_UNIX: + syslog (LOG_INFO, _("connect from socket")); + break; + + case PF_INET: + syslog (LOG_INFO, _("connect from %s"), inet_ntoa (addr->s_in.sin_addr)); + } +} + +int +smap_daemon (char *urlstr) +{ + int rc; + int listenfd, connfd; + all_addr_t addr; + socklen_t addrlen; + pid_t pid; + + if (daemon_param.mode == MODE_DAEMON) + { + if (daemon (0, 0) < 0) + { + mu_error (_("Failed to become a daemon")); + return EX_UNAVAILABLE; + } + } + + rc = smap_open (&listenfd, urlstr); + if (rc) + return rc; + + if (listen (listenfd, 128) == -1) + { + mu_error ("listen: %s", strerror (errno)); + close (listenfd); + return EX_UNAVAILABLE; + } + +#ifdef HAVE_SIGACTION + { + struct sigaction act; + act.sa_handler = lmtp_sigchld; + sigemptyset (&act.sa_mask); + act.sa_flags = 0; + sigaction (SIGCHLD, &act, NULL); + } +#else + signal (SIGCHLD, smap_sigchld); +#endif + + for (;;) + { + process_cleanup (); + if (children > daemon_param.maxchildren) + { + mu_error (_("too many children (%lu)"), + (unsigned long) children); + pause (); + continue; + } + addrlen = sizeof addr; + connfd = accept (listenfd, (struct sockaddr *)&addr, &addrlen); + if (connfd == -1) + { + if (errno == EINTR) + continue; + mu_error ("accept: %s", strerror (errno)); + continue; + /*exit (EXIT_FAILURE);*/ + } + + log_connection (&addr, addrlen); + + pid = fork (); + if (pid == -1) + syslog (LOG_ERR, "fork: %s", strerror (errno)); + else if (pid == 0) /* Child. */ + { + int status; + + close (listenfd); + status = smap_loop (fdopen (connfd, "r"), fdopen (connfd, "w")); + exit (status); + } + else + { + ++children; + } + close (connfd); + } +} + + +const char *program_version = "smap (" PACKAGE_STRING ")"; +static char doc[] = "Simple remote map for MeTA1"; +static char args_doc[] = ""; + +#define OPTION_GECOS 256 +#define OPTION_POSITIVE_REPLY 257 +#define OPTION_NEGATIVE_REPLY 258 +#define OPTION_NEGATIVE_OK 259 + +static struct argp_option options[] = +{ + { "url", 'u', "URL", 0, + N_("Set URL to listen on"), 0 }, + { "user", 'U', "NAME", 0, + N_("Run as user NAME"), 0 }, + { "gecos", OPTION_GECOS, NULL, 0, + N_("Return gecos"), 0 }, + { "log-tag", 'L', N_("STRING"), 0, + N_("Set syslog tag"), 0 }, + { "positive-reply", OPTION_POSITIVE_REPLY, N_("STRING"), 0, + N_("Set positive reply text"), 0 }, + { "negative-reply", OPTION_NEGATIVE_REPLY, N_("STRING"), 0, + N_("Set negative reply text"), 0 }, + { NULL } +}; + +static error_t parse_opt (int key, char *arg, struct argp_state *state); + +static struct argp argp = { + options, + parse_opt, + args_doc, + doc, + NULL, + NULL, NULL +}; + +static const char *argp_capa[] = { + "daemon", + "auth", + "common", + "license", + "logging", + NULL +}; + +static error_t +parse_opt (int key, char *arg, struct argp_state *state) +{ + switch (key) + { + case ARGP_KEY_INIT: + state->child_inputs[0] = state->input; + break; + + case OPTION_NEGATIVE_REPLY: + negative_reply = arg; + break; + + case OPTION_POSITIVE_REPLY: + positive_reply = arg; + break; + + case 'L': + syslog_tag = arg; + break; + + case 'U': + run_as_user = arg; + break; + + case 'u': + smap_url_string = arg; + break; + + case OPTION_GECOS: + positive_reply = "${gecos}"; + break; + + default: + return ARGP_ERR_UNKNOWN; + + case ARGP_KEY_ERROR: + exit (EX_USAGE); + } + return 0; +} + +int +read_delim (FILE *fp, int delim, char *buf, size_t bufsize) +{ + int c; + int len = 0; + + bufsize--; + while (len < bufsize && ((c = fgetc (fp))) != delim) + { + if (c == EOF) + { + if (len) + { + if (daemon_param.transcript && len) + syslog (LOG_INFO, "recv: %.*s:", len, buf); + mu_error ("unexpected end of file in input"); + exit (1); + } + return 0; + } + buf[len++] = c; + } + buf[len] = 0; + if (daemon_param.transcript) + syslog (LOG_INFO, "recv: %s:", buf); + return len; +} + +void +smap_reply (FILE *fp, const char *code, const char *result) +{ + size_t len = strlen (code); + if (result) + len += 1 + strlen (result); + if (result) + { + if (daemon_param.transcript) + syslog (LOG_INFO, "send: %lu:%s %s,", + (unsigned long) len, code, result); + fprintf (fp, "%lu:%s %s,", (unsigned long) len, code, result); + } + else + { + if (daemon_param.transcript) + syslog (LOG_INFO, "send: %lu:%s,", + (unsigned long) len, code); + fprintf (fp, "%s:%s,", (unsigned long) len, code); + } +} + +int +smap_loop (FILE *in, FILE *out) +{ + char buf[80]; + size_t len; + char *key, *p; + struct mu_auth_data *auth; + + setvbuf (in, NULL, _IONBF, 0); + setvbuf (out, NULL, _IONBF, 0); + + /* Read input: */ + while (read_delim (in, ':', buf, sizeof buf)) + { + len = strtoul (buf, &p, 10); + if (*p != 0) + { + mu_error ("protocol error: expected packet length, but found %s", buf); + exit (1); + } + + if (len > sizeof buf - 1) + { + mu_error ("protocol error: packet length too big"); + exit (1); + } + + if (fread (buf, 1, len, in) != len) + { + mu_error ("protocol error: short read"); + exit (1); + } + + buf[len] = 0; + + if (daemon_param.transcript) + syslog (LOG_INFO, "recv: %s,", buf); + + if (getc (in) != ',') + { + mu_error ("protocol error: missing terminating comma"); + exit (1); + } + buf[len] = 0; + + key = strchr (buf, ' '); + if (!key) + { + mu_error ("protocol error: missing map name"); + exit (1); + } + key++; + + len = strlen (key); + if (key[len-1] == '*') + key[len-1] = 0; + + auth = mu_get_auth_by_name (key); + + /* Reply: */ + if (!auth) + { + smap_reply (out, "NOTFOUND", + expand_reply_text (negative_reply, key, NULL)); + } + else + { + smap_reply (out, "OK", expand_reply_text (positive_reply, key, auth)); + mu_auth_data_free (auth); + } + } + /* Cleanup and exit */ + fclose (in); + fclose (out); + return 0; +} + +int +switch_to_privs (uid_t uid, gid_t gid) +{ + int rc = 0; + gid_t emptygidset; + + if (uid == 0) + return 1; + + /* Reset group permissions */ + if (geteuid() == 0 && setgroups(1, &gid)) + { + mu_error (_("setgroups(1, %lu) failed: %s"), + (unsigned long) gid, + mu_strerror (errno)); + rc = 1; + } + + /* Switch to the user's gid. On some OSes the effective gid must + be reset first */ + +#if defined(HAVE_SETEGID) + if ((rc = setegid (gid)) < 0) + mu_error (_("setegid(%lu) failed: %s"), + (unsigned long) gid, mu_strerror (errno)); +#elif defined(HAVE_SETREGID) + if ((rc = setregid (gid, gid)) < 0) + mu_error(_("setregid(%lu,%lu) failed: %s"), + (unsigned long) gid, (unsigned long) gid, + mu_strerror (errno)); +#elif defined(HAVE_SETRESGID) + if ((rc = setresgid (gid, gid, gid)) < 0) + mu_error (_("setresgid(%lu,%lu,%lu) failed: %s"), + (unsigned long) gid, + (unsigned long) gid, + (unsigned long) gid, + mu_strerror (errno)); +#endif + + if (rc == 0 && gid != 0) + { + if ((rc = setgid (gid)) < 0 && getegid () != gid) + mu_error (_("setgid(%lu) failed: %s"), + (unsigned long) gid, mu_strerror (errno)); + if (rc == 0 && getegid() != gid) + { + mu_error (_("Cannot set effective gid to %lu"), + (unsigned long) gid); + rc = 1; + } + } + + /* Now reset uid */ + if (rc == 0 && uid != 0) + { + uid_t euid; + + if (setuid (uid) + || geteuid () != uid + || (getuid () != uid + && (geteuid () == 0 || getuid () == 0))) + { +#if defined(HAVE_SETREUID) + if (geteuid () != uid) + { + if (setreuid (uid, -1) < 0) + { + mu_error (_("setreuid(%lu,-1) failed"), + (unsigned long) uid, + mu_strerror (errno)); + rc = 1; + } + if (setuid (uid) < 0) + { + mu_error (_("second setuid(%lu) failed"), + (unsigned long) uid, + mu_strerror (errno)); + rc = 1; + } + } + else +#endif + { + mu_error (_("setuid(%lu) failed"), + (unsigned long) uid, + mu_strerror (errno)); + rc = 1; + } + } + + euid = geteuid (); + if (uid != 0 && setuid (0) == 0) + { + mu_error (_("seteuid(0) succeeded when it should not")); + rc = 1; + } + else if (uid != euid && setuid (euid) == 0) + { + mu_error (_("Cannot drop non-root setuid privileges")); + rc = 1; + } + } + return rc; +} + +void +priv_setup () +{ + if (getuid () == 0 && run_as_user) + { + struct passwd *pw = getpwnam (run_as_user); + if (!pw) + { + mu_error (_("No such user: %s"), run_as_user); + exit (EX_SOFTWARE); + } + if (pw && switch_to_privs (pw->pw_uid, pw->pw_gid)) + exit (EX_SOFTWARE); + } +} + +int +main (int argc, char **argv) +{ + int arg_index; + struct group *gr; + + MU_AUTH_REGISTER_ALL_MODULES (); + mu_argp_init (program_version, "<" PACKAGE_BUGREPORT ">"); + syslog_tag = strrchr (argv[0], '/'); + if (syslog_tag) + syslog_tag++; + else + syslog_tag = argv[0]; + /* Parse command line */ + mu_argp_parse (&argp, &argc, &argv, 0, argp_capa, &arg_index, &daemon_param); + if (!smap_url_string && !daemon_param.port && daemon_param.mode != MODE_INTERACTIVE) + { + mu_error ("either --url or --port must be given"); + exit (EX_CONFIG); + } + + openlog (syslog_tag, LOG_PID, log_facility); + mu_error_set_print (mu_syslog_error_printer); + + priv_setup (); + + if (smap_url_string) + return smap_daemon (smap_url_string); + else if (daemon_param.mode == MODE_INTERACTIVE) + return smap_loop (stdin, stdout); + else + { + const char *pstr = mu_umaxtostr (0, daemon_param.port); + char *urls = malloc (sizeof (DEFAULT_URL) + strlen (pstr)); + if (!urls) + { + mu_error (_("Not enough memory")); + return EX_TEMPFAIL; + } + strcpy (urls, DEFAULT_URL); + strcat (urls, pstr); + return smap_daemon (urls); + } +} |