pfix-srsd: add a -I option
[apps/pfixtools.git] / pfix-srsd / main-srsd.c
1 /******************************************************************************/
2 /*          pfixtools: a collection of postfix related tools                  */
3 /*          ~~~~~~~~~                                                         */
4 /*  ________________________________________________________________________  */
5 /*                                                                            */
6 /*  Redistribution and use in source and binary forms, with or without        */
7 /*  modification, are permitted provided that the following conditions        */
8 /*  are met:                                                                  */
9 /*                                                                            */
10 /*  1. Redistributions of source code must retain the above copyright         */
11 /*     notice, this list of conditions and the following disclaimer.          */
12 /*  2. Redistributions in binary form must reproduce the above copyright      */
13 /*     notice, this list of conditions and the following disclaimer in the    */
14 /*     documentation and/or other materials provided with the distribution.   */
15 /*  3. The names of its contributors may not be used to endorse or promote    */
16 /*     products derived from this software without specific prior written     */
17 /*     permission.                                                            */
18 /*                                                                            */
19 /*  THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS   */
20 /*  OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED         */
21 /*  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE    */
22 /*  DISCLAIMED.  IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY         */
23 /*  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL        */
24 /*  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS   */
25 /*  OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)     */
26 /*  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,       */
27 /*  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN  */
28 /*  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE           */
29 /*  POSSIBILITY OF SUCH DAMAGE.                                               */
30 /*                                                                            */
31 /*   Copyright (c) 2006-2008 the Authors                                      */
32 /*   see AUTHORS and source files for details                                 */
33 /******************************************************************************/
34
35 /*
36  * Copyright © 2005-2007 Pierre Habouzit
37  * Copyright © 2008 Florent Bruneau
38  */
39
40 #include "common.h"
41
42 #include <srs2.h>
43
44 #include "mem.h"
45 #include "buffer.h"
46 #include "server.h"
47
48 #define DAEMON_NAME             "pfix-srsd"
49 #define DAEMON_VERSION          "0.5"
50 #define DEFAULT_ENCODER_PORT    10001
51 #define DEFAULT_DECODER_PORT    10002
52 #define RUNAS_USER              "nobody"
53 #define RUNAS_GROUP             "nogroup"
54
55 DECLARE_MAIN
56
57 typedef struct srs_config_t {
58     srs_t* srs;
59     const char* domain;
60     int domainlen;
61     int ignore_ext;
62 } srs_config_t;
63
64
65 /* Server {{{1
66  */
67
68 static listener_t *decoder_ptr = NULL;
69 static listener_t *encoder_ptr = NULL;
70
71 static void *srsd_starter(listener_t *server)
72 {
73     return server;
74 }
75
76
77 /* Processing {{{1
78  */
79
80 char *urldecode(char *s, char *end)
81 {
82     char *p = s;
83
84     while (*p) {
85         if (*p == '%' && end - p >= 3) {
86             int h = (hexval(p[1]) << 4) | hexval(p[2]);
87
88             if (h >= 0) {
89                 *s++ = h;
90                 p += 3;
91                 continue;
92             }
93         }
94
95         *s++ = *p++;
96     }
97     *s = '\0';
98     return s;
99 }
100
101 int process_srs(client_t *srsd, void* vconfig)
102 {
103     srs_config_t* config = vconfig;
104     buffer_t *ibuf = client_input_buffer(srsd);
105     buffer_t *obuf = client_output_buffer(srsd);
106     bool decoder = (client_data(srsd) == decoder_ptr);
107     int res = client_read(srsd);
108
109     if ((res < 0 && errno != EINTR && errno != EAGAIN) || res == 0)
110         return -1;
111
112     while (ibuf->len > 4) {
113         char buf[BUFSIZ], *p, *q, *nl;
114         int err;
115
116         nl = strchr(ibuf->data + 4, '\n');
117         if (!nl) {
118             if (ibuf->len > BUFSIZ) {
119                 err("unreasonnable amount of data without a \\n");
120                 return -1;
121             }
122             if (obuf->len) {
123                 client_io_rw(srsd);
124             }
125             return 0;
126         }
127
128         if (strncmp("get ", ibuf->data, 4)) {
129             err("bad request, not starting with \"get \"");
130             return -1;
131         }
132
133         for (p = ibuf->data + 4; p < nl && isspace(*p); p++);
134         for (q = nl++; q >= p && isspace(*q); *q-- = '\0');
135
136         if (p == q) {
137             buffer_addstr(obuf, "400 empty request ???\n");
138             warn("empty request");
139             goto skip;
140         }
141
142         q = urldecode(p, q);
143
144         if (decoder) {
145             if (config->ignore_ext) {
146                 int dlen = config->domainlen;
147
148                 if (q - p <= dlen || q[-1 - dlen] != '@' ||
149                     memcmp(q - dlen, config->domain, dlen))
150                 {
151                     buffer_addstr(obuf, "200 ");
152                     buffer_add(obuf, p, q - p);
153                     buffer_addch(obuf, '\n');
154                     goto skip;
155                 }
156             }
157             err = srs_reverse(config->srs, buf, ssizeof(buf), p);
158         } else {
159             err = srs_forward(config->srs, buf, ssizeof(buf), p, config->domain);
160         }
161
162         if (err == 0) {
163             buffer_addstr(obuf, "200 ");
164             buffer_addstr(obuf, buf);
165         } else {
166             switch (SRS_ERROR_TYPE(err)) {
167               case SRS_ERRTYPE_SRS:
168               case SRS_ERRTYPE_SYNTAX:
169                 buffer_addstr(obuf, "500 ");
170                 break;
171               default:
172                 buffer_addstr(obuf, "400 ");
173                 break;
174             }
175             buffer_addstr(obuf, srs_strerror(err));
176         }
177         buffer_addch(obuf, '\n');
178
179       skip:
180         buffer_consume(ibuf, nl - ibuf->data);
181     }
182     if (obuf->len) {
183         client_io_rw(srsd);
184     }
185     return 0;
186 }
187
188
189 /* config {{{1
190  */
191
192 static srs_config_t config = {
193     .srs = NULL,
194     .domain = NULL
195 };
196
197 /** overload srs_free since the lib is not properly maintained.
198  */
199 inline void srs_free(srs_t* srs)
200 {
201     int  i;
202     for (i = 0; i < srs->numsecrets; i++) {
203         memset(srs->secrets[i], 0, strlen(srs->secrets[i]));
204         free(srs->secrets[i]);
205         srs->secrets[i] = '\0';
206     }
207     if (srs->secrets) {
208         free(srs->secrets);
209     }
210     free(srs);
211 }
212
213 static void config_shutdown(void)
214 {
215     if (config.srs) {
216         srs_free(config.srs);
217         config.srs = NULL;
218     }
219 }
220
221 module_exit(config_shutdown);
222
223 static srs_t *srs_read_secrets(const char *sfile)
224 {
225     srs_t *srs;
226     char buf[BUFSIZ];
227     FILE *f;
228     int lineno = 0;
229
230     f = fopen(sfile, "r");
231     if (!f) {
232         UNIXERR("fopen");
233         return NULL;
234     }
235
236     srs = srs_new();
237
238     while (fgets(buf, sizeof(buf), f)) {
239         int n = strlen(buf);
240
241         ++lineno;
242         if (n == sizeof(buf) - 1 && buf[n - 1] != '\n') {
243             crit("%s:%d: line too long", sfile, lineno);
244             goto error;
245         }
246         m_strrtrim(buf);
247         srs_add_secret(srs, skipspaces(buf));
248     }
249
250     if (!lineno) {
251         crit("%s: empty file, no secrets", sfile);
252         goto error;
253     }
254
255     fclose(f);
256     return srs;
257
258   error:
259     fclose(f);
260     srs_free(srs);
261     return NULL;
262 }
263
264 /* administrivia {{{1
265  */
266
267 void usage(void)
268 {
269     fputs("usage: "DAEMON_NAME" [options] domain secrets\n"
270           "\n"
271           "Options:\n"
272           "    -e <port>    port to listen to for encoding requests\n"
273           "                 (default: "STR(DEFAULT_ENCODER_PORT)")\n"
274           "    -d <port>    port to listen to for decoding requests\n"
275           "                 (default: "STR(DEFAULT_DECODER_PORT)")\n"
276           "    -p <pidfile> file to write our pid to\n"
277           "    -u           unsafe mode: don't drop privilegies\n"
278           "    -I           do not touch mails outside of \"domain\" in decoding mode\n"
279           "    -f           stay in foreground\n"
280          , stderr);
281 }
282
283 /* }}}
284  */
285
286 int main(int argc, char *argv[])
287 {
288     bool unsafe  = false;
289     bool daemonize = true;
290     int port_enc = DEFAULT_ENCODER_PORT;
291     int port_dec = DEFAULT_DECODER_PORT;
292     const char *pidfile = NULL;
293
294     for (int c = 0; (c = getopt(argc, argv, "hfuI" "e:d:p:")) >= 0; ) {
295         switch (c) {
296           case 'e':
297             port_enc = atoi(optarg);
298             break;
299           case 'f':
300             daemonize = false;
301             break;
302           case 'd':
303             port_dec = atoi(optarg);
304             break;
305           case 'p':
306             pidfile = optarg;
307             break;
308           case 'u':
309             unsafe = true;
310             break;
311           case 'I':
312             config.ignore_ext = true;
313             break;
314           default:
315             usage();
316             return EXIT_FAILURE;
317         }
318     }
319
320     if (!daemonize) {
321         log_syslog = false;
322     }
323
324     if (argc - optind != 2) {
325         usage();
326         return EXIT_FAILURE;
327     }
328
329     info("%s v%s...", DAEMON_NAME, DAEMON_VERSION);
330
331     config.domain = argv[optind];
332     config.domainlen = strlen(config.domain);
333     config.srs = srs_read_secrets(argv[optind + 1]);
334     if (!config.srs
335         || common_setup(pidfile, unsafe, RUNAS_USER, RUNAS_GROUP,
336                         daemonize) != EXIT_SUCCESS
337         || (encoder_ptr = start_listener(port_enc)) == NULL
338         || (decoder_ptr = start_listener(port_dec)) == NULL) {
339         return EXIT_FAILURE;
340     }
341     return server_loop(srsd_starter, NULL, process_srs, NULL, &config);
342 }