Cleanup source structure.
[apps/pfixtools.git] / pfix-srsd / main-srsd.c
diff --git a/pfix-srsd/main-srsd.c b/pfix-srsd/main-srsd.c
new file mode 100644 (file)
index 0000000..a8e6ce9
--- /dev/null
@@ -0,0 +1,325 @@
+/******************************************************************************/
+/*          pfixtools: a collection of postfix related tools                  */
+/*          ~~~~~~~~~                                                         */
+/*  ________________________________________________________________________  */
+/*                                                                            */
+/*  Redistribution and use in source and binary forms, with or without        */
+/*  modification, are permitted provided that the following conditions        */
+/*  are met:                                                                  */
+/*                                                                            */
+/*  1. Redistributions of source code must retain the above copyright         */
+/*     notice, this list of conditions and the following disclaimer.          */
+/*  2. Redistributions in binary form must reproduce the above copyright      */
+/*     notice, this list of conditions and the following disclaimer in the    */
+/*     documentation and/or other materials provided with the distribution.   */
+/*  3. The names of its contributors may not be used to endorse or promote    */
+/*     products derived from this software without specific prior written     */
+/*     permission.                                                            */
+/*                                                                            */
+/*  THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND   */
+/*  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE     */
+/*  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR        */
+/*  PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS    */
+/*  BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR    */
+/*  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF      */
+/*  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS  */
+/*  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN   */
+/*  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)   */
+/*  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF    */
+/*  THE POSSIBILITY OF SUCH DAMAGE.                                           */
+/******************************************************************************/
+
+/*
+ * Copyright © 2005-2007 Pierre Habouzit
+ * Copyright © 2008 Florent Bruneau
+ */
+
+#include "common.h"
+
+#include <srs2.h>
+
+#include "epoll.h"
+#include "mem.h"
+#include "buffer.h"
+#include "server.h"
+
+#define DAEMON_NAME             "pfix-srsd"
+#define DEFAULT_ENCODER_PORT    10001
+#define DEFAULT_DECODER_PORT    10002
+#define RUNAS_USER              "nobody"
+#define RUNAS_GROUP             "nogroup"
+
+DECLARE_MAIN
+
+typedef struct srs_config_t {
+    srs_t* srs;
+    const char* domain;
+} srs_config_t;
+
+
+/* Server {{{1
+ */
+
+static const char* const decoder_ptr = "decoder";
+static const char* const encoder_ptr = "encoder";
+
+static void *srsd_new_decoder(void)
+{
+    return (void*)decoder_ptr;
+}
+
+static void *srsd_new_encoder(void)
+{
+    return (void*)encoder_ptr;
+}
+
+static void *srsd_starter(server_t *server)
+{
+    return server->data;
+}
+
+int start_listener(int port, bool decoder)
+{
+    return start_server(port, decoder ? srsd_new_decoder : srsd_new_encoder, NULL);
+}
+
+
+/* Processing {{{1
+ */
+
+void urldecode(char *s, char *end)
+{
+    char *p = s;
+
+    while (*p) {
+        if (*p == '%' && end - p >= 3) {
+            int h = (hexval(p[1]) << 4) | hexval(p[2]);
+
+            if (h >= 0) {
+                *s++ = h;
+                p += 3;
+                continue;
+            }
+        }
+
+        *s++ = *p++;
+    }
+    *s++ = '\0';
+}
+
+int process_srs(server_t *srsd, void* vconfig)
+{
+    srs_config_t* config = vconfig;
+    int res = buffer_read(&srsd->ibuf, srsd->fd, -1);
+
+    if ((res < 0 && errno != EINTR && errno != EAGAIN) || res == 0)
+        return -1;
+
+    while (srsd->ibuf.len > 4) {
+        char buf[BUFSIZ], *p, *q, *nl;
+        int err;
+
+        nl = strchr(srsd->ibuf.data + 4, '\n');
+        if (!nl) {
+            if (srsd->ibuf.len > BUFSIZ) {
+                syslog(LOG_ERR, "unreasonnable amount of data without a \\n");
+                return -1;
+            }
+            if (srsd->obuf.len) {
+              epoll_modify(srsd->fd, EPOLLIN | EPOLLOUT, srsd);
+            }
+            return 0;
+        }
+
+        if (strncmp("get ", srsd->ibuf.data, 4)) {
+            syslog(LOG_ERR, "bad request, not starting with \"get \"");
+            return -1;
+        }
+
+        for (p = srsd->ibuf.data + 4; p < nl && isspace(*p); p++);
+        for (q = nl++; q >= p && isspace(*q); *q-- = '\0');
+
+        if (p == q) {
+            buffer_addstr(&srsd->obuf, "400 empty request ???\n");
+            syslog(LOG_WARNING, "empty request");
+            goto skip;
+        }
+
+        urldecode(p, q);
+
+        if (srsd->data == (void*)decoder_ptr) {
+            err = srs_reverse(config->srs, buf, ssizeof(buf), p);
+        } else {
+            err = srs_forward(config->srs, buf, ssizeof(buf), p, config->domain);
+        }
+
+        if (err == 0) {
+            buffer_addstr(&srsd->obuf, "200 ");
+            buffer_addstr(&srsd->obuf, buf);
+        } else {
+            switch (SRS_ERROR_TYPE(err)) {
+              case SRS_ERRTYPE_SRS:
+              case SRS_ERRTYPE_SYNTAX:
+                buffer_addstr(&srsd->obuf, "500 ");
+                break;
+              default:
+                buffer_addstr(&srsd->obuf, "400 ");
+                break;
+            }
+            buffer_addstr(&srsd->obuf, srs_strerror(err));
+        }
+        buffer_addch(&srsd->obuf, '\n');
+
+      skip:
+        buffer_consume(&srsd->ibuf, nl - srsd->ibuf.data);
+    }
+    if (srsd->obuf.len) {
+      epoll_modify(srsd->fd, EPOLLIN | EPOLLOUT, srsd);
+    }
+    return 0;
+}
+
+
+/* config {{{1
+ */
+
+static srs_config_t config = {
+    .srs = NULL,
+    .domain = NULL
+};
+
+/** overload srs_free since the lib is not properly maintained.
+ */
+inline void srs_free(srs_t* srs)
+{
+    int  i;
+    for (i = 0; i < srs->numsecrets; i++) {
+        memset(srs->secrets[i], 0, strlen(srs->secrets[i]));
+        free(srs->secrets[i]);
+        srs->secrets[i] = '\0';
+    }
+    if (srs->secrets) {
+        free(srs->secrets);
+    }
+    free(srs);
+}
+
+static void config_shutdown(void)
+{
+    if (config.srs) {
+        srs_free(config.srs);
+        config.srs = NULL;
+    }
+}
+
+module_exit(config_shutdown);
+
+static srs_t *srs_read_secrets(const char *sfile)
+{
+    srs_t *srs;
+    char buf[BUFSIZ];
+    FILE *f;
+    int lineno = 0;
+
+    f = fopen(sfile, "r");
+    if (!f) {
+        UNIXERR("fopen");
+        return NULL;
+    }
+
+    srs = srs_new();
+
+    while (fgets(buf, sizeof(buf), f)) {
+        int n = strlen(buf);
+
+        ++lineno;
+        if (n == sizeof(buf) - 1 && buf[n - 1] != '\n') {
+            syslog(LOG_CRIT, "%s:%d: line too long", sfile, lineno);
+            goto error;
+        }
+        m_strrtrim(buf);
+        srs_add_secret(srs, skipspaces(buf));
+    }
+
+    if (!lineno) {
+        syslog(LOG_CRIT, "%s: empty file, no secrets", sfile);
+        goto error;
+    }
+
+    fclose(f);
+    return srs;
+
+  error:
+    fclose(f);
+    srs_free(srs);
+    return NULL;
+}
+
+/* administrivia {{{1
+ */
+
+void usage(void)
+{
+    fputs("usage: "DAEMON_NAME" [options] domain secrets\n"
+          "\n"
+          "Options:\n"
+          "    -e <port>    port to listen to for encoding requests\n"
+          "                 (default: "STR(DEFAULT_ENCODER_PORT)")\n"
+          "    -d <port>    port to listen to for decoding requests\n"
+          "                 (default: "STR(DEFAULT_DECODER_PORT)")\n"
+          "    -p <pidfile> file to write our pid to\n"
+          "    -u           unsafe mode: don't drop privilegies\n"
+          "    -f           stay in foreground\n"
+         , stderr);
+}
+
+/* }}}
+ */
+
+int main(int argc, char *argv[])
+{
+    bool unsafe  = false;
+    bool daemonize = true;
+    int port_enc = DEFAULT_ENCODER_PORT;
+    int port_dec = DEFAULT_DECODER_PORT;
+    const char *pidfile = NULL;
+
+    for (int c = 0; (c = getopt(argc, argv, "hfu" "e:d:p:")) >= 0; ) {
+        switch (c) {
+          case 'e':
+            port_enc = atoi(optarg);
+            break;
+          case 'f':
+            daemonize = false;
+            break;
+          case 'd':
+            port_dec = atoi(optarg);
+            break;
+          case 'p':
+            pidfile = optarg;
+            break;
+          case 'u':
+            unsafe = true;
+            break;
+          default:
+            usage();
+            return EXIT_FAILURE;
+        }
+    }
+
+    if (argc - optind != 2) {
+        usage();
+        return EXIT_FAILURE;
+    }
+
+    config.domain = argv[optind];
+    config.srs = srs_read_secrets(argv[optind + 1]);
+    if (!config.srs
+        || common_setup(pidfile, unsafe, RUNAS_USER, RUNAS_GROUP,
+                        daemonize) != EXIT_SUCCESS
+        || start_listener(port_enc, false) < 0
+        || start_listener(port_dec, true) < 0) {
+        return EXIT_FAILURE;
+    }
+    return server_loop(srsd_starter, NULL, process_srs, &config);
+}