| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465 | 
							- /* PostSRSd - Sender Rewriting Scheme daemon for Postfix
 -  * Copyright (c) 2012 Timo Röhling <timo.roehling@gmx.de>
 -  *
 -  * 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, see <http://www.gnu.org/licenses/>.
 -  */
 - 
 - /* This program uses the libsrs2 library. The relevant source
 -  * files have been added to this distribution. */
 - 
 - #include "srs2.h"
 - #include <sys/types.h>
 - #include <sys/socket.h>
 - #include <netdb.h>
 - #include <errno.h>
 - #include <unistd.h>
 - #include <fcntl.h>
 - #include <pwd.h>
 - #include <string.h>
 - #include <poll.h>
 - #include <wait.h>
 - #include <syslog.h>
 - 
 - #ifndef VERSION
 - #define VERSION "1.0"
 - #endif
 - 
 - static char *self = NULL;
 - 
 - static int bind_service (const char *service, int family)
 - {
 -   struct addrinfo *addr, *it;
 -   struct addrinfo hints;
 -   int err, sock, flags;
 -   static const int one = 1;
 - 
 -   memset (&hints, 0, sizeof(hints));
 -   hints.ai_family = family;
 -   hints.ai_socktype = SOCK_STREAM;
 -   hints.ai_flags = AI_ADDRCONFIG | AI_V4MAPPED;
 -   err = getaddrinfo(NULL, service, &hints, &addr);
 -   if (err != 0) {
 -     fprintf(stderr, "%s: bind_service(%s): %s\n", self, service, gai_strerror(err));
 -     return -1;
 -   }
 -   sock = -1;
 -   for (it = addr; it; it = it->ai_next) {
 -     sock = socket(it->ai_family, it->ai_socktype, it->ai_protocol);
 -     if (sock < 0) goto fail;
 -     if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) goto fail;
 -     if (bind(sock, it->ai_addr, it->ai_addrlen) < 0) goto fail;
 -     if (listen(sock, 10) < 0) goto fail;
 -     flags = fcntl (sock, F_GETFL, 0);
 -     if (flags < 0) goto fail;
 -     if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) goto fail;
 -     break;
 -   fail:
 -     err = errno;
 -     if (sock >= 0) close (sock);
 -     sock = -1;
 -   }
 -   freeaddrinfo (addr);
 -   if (sock < 0)
 -     fprintf (stderr, "%s: bind_service(%s): %s\n", self, service, strerror(err));
 -   return sock;
 - }
 - 
 - static int is_hexdigit (char c)
 - {
 -   return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
 - }
 - 
 - static char hex2num (char c)
 - {
 -   if (c >= '0' && c <= '9') return c - '0';
 -   if (c >= 'a' && c <= 'f') return c - 'a' + 10;
 -   if (c >= 'A' && c <= 'F') return c - 'A' + 10;
 -   return 0;
 - }
 - 
 - static char num2hex (char c)
 - {
 -   if (c < 10) return '0' + c;
 -   return 'a' + c - 10;
 - }
 - 
 - static char hex2char (const char *s)
 - {
 -   return 16 * hex2num(s[0]) + hex2num(s[1]);
 - }
 - 
 - static void char2hex (char c, char *buf)
 - {
 -   buf[0] = num2hex((c >> 4) & 0xf);
 -   buf[1] = num2hex((c     ) & 0xf);
 - }
 - 
 - static char* url_decode (char *buf, size_t len, const char *input)
 - {
 -   char *output = buf;
 -   if (!input || !output || len == 0) return NULL;
 - 
 -   while (*input && --len) {
 -     if (*input == '%' && is_hexdigit(input[1]) && is_hexdigit(input[2])) {
 -       *output++ = hex2char(++input);
 -       input += 2;
 -     } else {
 -       *output++ = *input++;
 -     }
 -   }
 -   *output = 0;
 -   return buf;
 - }
 - 
 - static char* url_encode (char* buf, size_t len, const char *input)
 - {
 -   char *output = buf;
 -   if (!input || !output || len == 0) return NULL;
 -   while (*input && --len) {
 -     if (!isascii(*input) || !isgraph(*input) || *input == '%') {
 -       if (len <= 2) break;
 -       *output++ = '%';
 -       char2hex(*input++, output);
 -       output += 2; len -= 2;
 -     } else {
 -       *output++ = *input++;
 -     }
 -   }
 -   *output = 0;
 -   return buf;
 - }
 - 
 - static void handle_forward (srs_t *srs, FILE *fp, const char *address, const char *domain, const char **excludes)
 - {
 -   int result;
 -   size_t addrlen;
 -   char value[1024];
 -   char outputbuf[1024], *output;
 - 
 -   addrlen = strlen(address);
 -   for(; *excludes; excludes++) {
 -     size_t len;
 -     len = strlen(*excludes);
 -     if (len >= addrlen) continue;
 -     if (strcasecmp(*excludes, &address[addrlen - len]) == 0 && (**excludes == '.' || address[addrlen - len - 1] == '@')) {
 -       fputs ("500 Domain excluded from SRS\n", fp);
 -       fflush (fp);
 -       return;
 -     }
 -   }
 -   result = srs_forward(srs, value, sizeof(value), address, domain);
 -   if (result == SRS_SUCCESS) {
 -     output = url_encode(outputbuf, sizeof(outputbuf), value);
 -     fprintf (fp, "200 %s\n", output);
 -     if (strcmp(address, value) != 0) syslog (LOG_MAIL | LOG_INFO, "srs_forward: <%s> rewritten as <%s>", address, value);
 -   } else {
 -     fprintf (fp, "500 %s\n", srs_strerror(result));
 -     if (result != SRS_ENOTREWRITTEN)
 -       syslog (LOG_MAIL | LOG_INFO, "srs_forward: <%s> not rewritten: %s", address, srs_strerror(result));
 -   }
 -   fflush (fp);
 - }
 - 
 - static void handle_reverse (srs_t *srs, FILE *fp, const char *address, const char *domain, const char **excludes)
 - {
 -   int result;
 -   char value[1024];
 -   char outputbuf[1024], *output;
 -   result = srs_reverse(srs, value, sizeof(value), address);
 -   if (result == SRS_SUCCESS) {
 -     output = url_encode(outputbuf, sizeof(outputbuf), value);
 -     fprintf (fp, "200 %s\n", output);
 -     syslog (LOG_MAIL | LOG_INFO, "srs_reverse: <%s> rewritten as <%s>", address, value); 
 -   } else {
 -     fprintf (fp, "500 %s\n", srs_strerror(result));
 -     if (result != SRS_ENOTREWRITTEN && result != SRS_ENOTSRSADDRESS)
 -       syslog (LOG_MAIL | LOG_INFO, "srs_reverse: <%s> not rewritten: %s", address, srs_strerror(result));
 -   }
 -   fflush (fp);
 - }
 - 
 - static void show_help ()
 - {
 -   fprintf (stdout,
 -     "Sender Rewriting Scheme implementation for Postfix.\n\n"
 -     "Implements two TCP lookup tables to rewrite mail addresses\n"
 -     "as needed. The forward SRS is for sender envelope addresses\n"
 -     "to prevent SPF-related bounces. The reverse SRS is for\n"
 -     "recipient envelope addresses so that bounced mails can be\n"
 -     "routed back to their original sender.\n"
 -     "\n"
 -     "Usage: %s -s<file> -d<domain> [other options]\n"
 -     "Options:\n"
 -     "   -s<file>       read secrets from file (required)\n"
 -     "   -d<domain>     set domain name for rewrite (required)\n"
 -     "   -f<port>       set port for the forward SRS lookup (default: 10001)\n"
 -     "   -r<port>       set port for the reverse SRS lookup (default: 10002)\n"
 -     "   -p<pidfile>    write process ID to pidfile (default: none)\n"
 -     "   -c<dir>        chroot to <dir> (default: none)\n"
 -     "   -u<user>       switch user id after port bind (default: none)\n"
 -     "   -t<seconds>    timeout for idle client connections (default: 1800)\n"
 -     "   -X<domain>     exclude additional domain from address rewriting\n"
 -     "   -D             fork into background\n"
 -     "   -4             force IPv4 socket (default: any)\n"
 -     "   -6             force IPv6 socket (default: any)\n"
 -     "   -h             show this help\n"
 -     "   -v             show version\n"
 -     "\n",
 -     self
 -   );
 - }
 - 
 - typedef void(*handle_t)(srs_t*, FILE*, const char*, const char*, const char**);
 - 
 - int main (int argc, char **argv)
 - {
 -   int opt, timeout = 1800, family = AF_UNSPEC;
 -   int daemonize = FALSE;
 -   char *forward_service = NULL, *reverse_service = NULL,
 -        *user = NULL, *domain = NULL, *chroot_dir = NULL;
 -   int forward_sock, reverse_sock;
 -   char *secret_file = NULL, *pid_file = NULL;
 -   FILE *pf = NULL, *sf = NULL;
 -   struct passwd *pwd = NULL;
 -   char secretbuf[1024], *secret = NULL;
 -   char *tmp;
 -   srs_t *srs;
 -   struct pollfd fds[3];
 -   const char **excludes;
 -   size_t s1 = 0, s2 = 1;
 -   handle_t handler[2] = { handle_forward, handle_reverse };
 - 
 -   excludes = (const char**)calloc(1, sizeof(char*));
 -   tmp = strrchr(argv[0], '/');
 -   if (tmp) self = strdup(tmp + 1); else self = strdup(argv[0]);
 - 
 -   while ((opt = getopt(argc, argv, "46d:f:r:s:u:t:p:c:X::Dhv")) != -1) {
 -     switch (opt) {
 -       case '?':
 -         return EXIT_FAILURE;
 -       case '4':
 -         family = AF_INET;
 -         break;
 -       case '6':
 -         family = AF_INET6;
 -         break;
 -       case 'd':
 -         domain = strdup(optarg);
 -         break;
 -       case 'f':
 -         forward_service = strdup(optarg);
 -         break;
 -       case 'r':
 -         reverse_service = strdup(optarg);
 -         break;
 -       case 't':
 -         timeout = atoi(optarg);
 -         break;
 -       case 's':
 -         secret_file = strdup(optarg);
 -         break;
 -       case 'p':
 -         pid_file = strdup(optarg);
 -         break;
 -       case 'u':
 -         user = strdup(optarg);
 -         break;
 -       case 'c':
 -         chroot_dir = strdup(optarg);
 -         break;
 -       case 'D':
 -         daemonize = TRUE;
 -         break;
 -       case 'h':
 -         show_help();
 -         return EXIT_SUCCESS;
 -       case 'X':
 -         if (optarg != NULL) {
 -           tmp = strtok(optarg, ",; \t\r\n");
 -           while (tmp) {
 -             if (s1 + 1 >= s2) {
 -               s2 *= 2;
 -               excludes = (const char **)realloc(excludes, s2 * sizeof(char*));
 -               if (excludes == NULL) {
 -                 fprintf (stderr, "%s: Out of memory\n\n", self);
 -                 return EXIT_FAILURE;
 -               }
 -             }
 -             excludes[s1++] = strdup(tmp);
 -             tmp = strtok(NULL, ",; \t\r\n");
 -           }
 -           excludes[s1] = NULL;
 -         }
 -         break;
 -       case 'v':
 -         fprintf (stdout, "%s\n", VERSION);
 -         return EXIT_SUCCESS;
 -     }
 -   }
 -   if (domain == NULL) {
 -     fprintf (stderr, "%s: You must set a home domain (-d)\n", self);
 -     show_help();
 -     return EXIT_FAILURE;
 -   }
 - 
 -   /* The stuff we do first may not be possible from within chroot or without privileges */
 - 
 -   /* Open pid file for writing (the actual process ID is filled in later) */
 -   if (pid_file) {
 -     pf = fopen (pid_file, "w");
 -     if (pf == NULL) {
 -       fprintf (stderr, "%s: Cannot write PID: %s\n\n", self, pid_file);
 -       return EXIT_FAILURE;
 -     }
 -   }
 -   /* Read secret. The default installation makes this root accessible only. */
 -   if (secret_file != NULL) {
 -     size_t len;
 -     sf = fopen(secret_file, "rb");
 -     if (sf == NULL) {
 -       fprintf (stderr, "%s: Cannot open file with secret: %s\n", self, secret_file);
 -       return EXIT_FAILURE;
 -     }
 -   } else {
 -     fprintf (stderr, "%s: You must set a secret (-s)\n", self);
 -     show_help();
 -     return EXIT_FAILURE;
 -   }
 -   /* Bind ports. May require privileges if the config specifies ports below 1024 */
 -   if (forward_service != NULL) {
 -     forward_sock = bind_service(forward_service, family);
 -     free (forward_service);
 -   } else {
 -     forward_sock = bind_service("10001", family);
 -   }
 -   if (forward_sock < 0) return EXIT_FAILURE;
 -   if (reverse_service != NULL) {
 -     reverse_sock = bind_service(reverse_service, family);
 -     free (reverse_service);
 -   } else {
 -     reverse_sock = bind_service("10002", family);
 -   }
 -   if (reverse_sock < 0) return EXIT_FAILURE;
 - 
 -   /* Open syslog now (NDELAY), because it may no longer reachable from chroot */
 -   openlog (self, LOG_PID | LOG_NDELAY, LOG_MAIL);
 -   /* We also have to lookup the uid of the unprivileged user for the same reason. */
 -   if (user) {
 -     errno = 0;
 -     pwd = getpwnam(user);
 -     if (pwd == NULL) {
 -       if (errno != 0)
 -         fprintf (stderr, "%s: Failed to lookup user: %s\n", self, strerror(errno));
 -       else
 -         fprintf (stderr, "%s: No such user: %s\n", self, user);
 -       return EXIT_FAILURE;
 -     }
 -   }
 -   /* Now we can chroot, which again requires root privileges */
 -   if (chroot_dir) {
 -     if (chdir(chroot_dir) < 0) {
 -       fprintf (stderr, "%s: Cannot change to chroot: %s\n", self, strerror(errno));
 -       return EXIT_FAILURE;
 -     }
 -     if (chroot(chroot_dir) < 0) {
 -       fprintf (stderr, "%s: Failed to enable chroot: %s\n", self, strerror(errno));
 -       return EXIT_FAILURE;
 -     }
 -   }
 -   /* Finally, we revert to the unprivileged user */
 -   if (pwd) {
 -     if (setuid(pwd->pw_uid) < 0) {
 -       fprintf (stderr, "%s: Failed to switch user id: %s\n", self, strerror(errno));
 -       return EXIT_FAILURE;
 -     }
 -   }
 -   /* Standard double fork technique to disavow all knowledge about the controlling terminal */
 -   if (daemonize) {
 -     close(0); close(1); close(2);
 -     if (fork() != 0) return EXIT_SUCCESS;
 -     setsid();
 -     if (fork() != 0) return EXIT_SUCCESS;
 -   }
 -   /* Make note of our actual process ID */
 -   if (pf) {
 -     fprintf (pf, "%d", (int)getpid());
 -     fclose (pf);
 -   }
 - 
 -   srs = srs_new();
 -   while ((secret = fgets(secretbuf, sizeof(secretbuf), sf))) {
 -     secret = strtok(secret, "\r\n");
 -     if (secret)
 -       srs_add_secret (srs, secret);
 -   }
 -   fclose (sf);
 -   srs_set_separator (srs, '+');
 - 
 -   fds[0].fd = forward_sock;
 -   fds[0].events = POLLIN;
 -   fds[1].fd = reverse_sock;
 -   fds[1].events = POLLIN;
 - 
 -   while(TRUE) {
 -     int i, conn;
 -     FILE *fp;
 -     char linebuf[1024], *line;
 -     char keybuf[1024], *key;
 - 
 -     if (poll(fds, 2, 1000) < 0) {
 -       if (daemonize)
 -         syslog (LOG_MAIL | LOG_ERR, "Poll failure: %s", strerror(errno));
 -       else
 -         fprintf (stderr, "%s: Poll failure: %s\n", self, strerror(errno));
 -       return EXIT_FAILURE;
 -     }
 -     for (i = 0; i < 2; ++i) {
 -       if (fds[i].revents) {
 -         conn = accept(fds[i].fd, NULL, NULL);
 -         if (conn < 0) continue;
 -         if (fork() == 0) {
 -           fp = fdopen(conn, "r+");
 -           if (fp == NULL) exit(EXIT_FAILURE);
 -           fds[2].fd = conn;
 -           fds[2].events = POLLIN;
 -           if (poll(&fds[2], 1, timeout * 1000) <= 0) return EXIT_FAILURE;
 -           line = fgets(linebuf, sizeof(linebuf), fp);
 -           while (line) {
 -             char* token;
 -             token = strtok(line, " \r\n");
 -             if (token == NULL || strcmp(token, "get") != 0) {
 -               fprintf (fp, "500 Invalid request\n");
 -               return EXIT_FAILURE;
 -             }
 -             token = strtok(NULL, "\r\n");
 -             if (!token) {
 -               fprintf (fp, "500 Invalid request\n");
 -               return EXIT_FAILURE;
 -             }
 -             key = url_decode(keybuf, sizeof(keybuf), token);
 -             if (!key) break;
 -             handler[i](srs, fp, key, domain, excludes);
 -             if (poll(&fds[2], 1, timeout * 1000) <= 0) break;
 -             line = fgets(linebuf, sizeof(linebuf), fp);
 -           }
 -           fclose (fp);
 -           return EXIT_SUCCESS;
 -         }
 -         close (conn);
 -       }
 -     }
 -     waitpid(-1, NULL, WNOHANG);
 -   }
 -   return EXIT_SUCCESS;
 - }
 
 
  |