917aa385d1ae1144d96f6f29bc4621787516330e
[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 } srs_config_t;
61
62
63 /* Server {{{1
64  */
65
66 static listener_t *decoder_ptr = NULL;
67 static listener_t *encoder_ptr = NULL;
68
69 static void *srsd_starter(listener_t *server)
70 {
71     return server;
72 }
73
74
75 /* Processing {{{1
76  */
77
78 void urldecode(char *s, char *end)
79 {
80     char *p = s;
81
82     while (*p) {
83         if (*p == '%' && end - p >= 3) {
84             int h = (hexval(p[1]) << 4) | hexval(p[2]);
85
86             if (h >= 0) {
87                 *s++ = h;
88                 p += 3;
89                 continue;
90             }
91         }
92
93         *s++ = *p++;
94     }
95     *s++ = '\0';
96 }
97
98 int process_srs(client_t *srsd, void* vconfig)
99 {
100     srs_config_t* config = vconfig;
101     buffer_t *ibuf = client_input_buffer(srsd);
102     buffer_t *obuf = client_output_buffer(srsd);
103     bool decoder = (client_data(srsd) == decoder_ptr);
104     int res = client_read(srsd);
105
106     if ((res < 0 && errno != EINTR && errno != EAGAIN) || res == 0)
107         return -1;
108
109     while (ibuf->len > 4) {
110         char buf[BUFSIZ], *p, *q, *nl;
111         int err;
112
113         nl = strchr(ibuf->data + 4, '\n');
114         if (!nl) {
115             if (ibuf->len > BUFSIZ) {
116                 err("unreasonnable amount of data without a \\n");
117                 return -1;
118             }
119             if (obuf->len) {
120                 client_io_rw(srsd);
121             }
122             return 0;
123         }
124
125         if (strncmp("get ", ibuf->data, 4)) {
126             err("bad request, not starting with \"get \"");
127             return -1;
128         }
129
130         for (p = ibuf->data + 4; p < nl && isspace(*p); p++);
131         for (q = nl++; q >= p && isspace(*q); *q-- = '\0');
132
133         if (p == q) {
134             buffer_addstr(obuf, "400 empty request ???\n");
135             warn("empty request");
136             goto skip;
137         }
138
139         urldecode(p, q);
140
141         if (decoder) {
142             err = srs_reverse(config->srs, buf, ssizeof(buf), p);
143         } else {
144             err = srs_forward(config->srs, buf, ssizeof(buf), p, config->domain);
145         }
146
147         if (err == 0) {
148             buffer_addstr(obuf, "200 ");
149             buffer_addstr(obuf, buf);
150         } else {
151             switch (SRS_ERROR_TYPE(err)) {
152               case SRS_ERRTYPE_SRS:
153               case SRS_ERRTYPE_SYNTAX:
154                 buffer_addstr(obuf, "500 ");
155                 break;
156               default:
157                 buffer_addstr(obuf, "400 ");
158                 break;
159             }
160             buffer_addstr(obuf, srs_strerror(err));
161         }
162         buffer_addch(obuf, '\n');
163
164       skip:
165         buffer_consume(ibuf, nl - ibuf->data);
166     }
167     if (obuf->len) {
168         client_io_rw(srsd);
169     }
170     return 0;
171 }
172
173
174 /* config {{{1
175  */
176
177 static srs_config_t config = {
178     .srs = NULL,
179     .domain = NULL
180 };
181
182 /** overload srs_free since the lib is not properly maintained.
183  */
184 inline void srs_free(srs_t* srs)
185 {
186     int  i;
187     for (i = 0; i < srs->numsecrets; i++) {
188         memset(srs->secrets[i], 0, strlen(srs->secrets[i]));
189         free(srs->secrets[i]);
190         srs->secrets[i] = '\0';
191     }
192     if (srs->secrets) {
193         free(srs->secrets);
194     }
195     free(srs);
196 }
197
198 static void config_shutdown(void)
199 {
200     if (config.srs) {
201         srs_free(config.srs);
202         config.srs = NULL;
203     }
204 }
205
206 module_exit(config_shutdown);
207
208 static srs_t *srs_read_secrets(const char *sfile)
209 {
210     srs_t *srs;
211     char buf[BUFSIZ];
212     FILE *f;
213     int lineno = 0;
214
215     f = fopen(sfile, "r");
216     if (!f) {
217         UNIXERR("fopen");
218         return NULL;
219     }
220
221     srs = srs_new();
222
223     while (fgets(buf, sizeof(buf), f)) {
224         int n = strlen(buf);
225
226         ++lineno;
227         if (n == sizeof(buf) - 1 && buf[n - 1] != '\n') {
228             crit("%s:%d: line too long", sfile, lineno);
229             goto error;
230         }
231         m_strrtrim(buf);
232         srs_add_secret(srs, skipspaces(buf));
233     }
234
235     if (!lineno) {
236         crit("%s: empty file, no secrets", sfile);
237         goto error;
238     }
239
240     fclose(f);
241     return srs;
242
243   error:
244     fclose(f);
245     srs_free(srs);
246     return NULL;
247 }
248
249 /* administrivia {{{1
250  */
251
252 void usage(void)
253 {
254     fputs("usage: "DAEMON_NAME" [options] domain secrets\n"
255           "\n"
256           "Options:\n"
257           "    -e <port>    port to listen to for encoding requests\n"
258           "                 (default: "STR(DEFAULT_ENCODER_PORT)")\n"
259           "    -d <port>    port to listen to for decoding requests\n"
260           "                 (default: "STR(DEFAULT_DECODER_PORT)")\n"
261           "    -p <pidfile> file to write our pid to\n"
262           "    -u           unsafe mode: don't drop privilegies\n"
263           "    -f           stay in foreground\n"
264          , stderr);
265 }
266
267 /* }}}
268  */
269
270 int main(int argc, char *argv[])
271 {
272     bool unsafe  = false;
273     bool daemonize = true;
274     int port_enc = DEFAULT_ENCODER_PORT;
275     int port_dec = DEFAULT_DECODER_PORT;
276     const char *pidfile = NULL;
277
278     for (int c = 0; (c = getopt(argc, argv, "hfu" "e:d:p:")) >= 0; ) {
279         switch (c) {
280           case 'e':
281             port_enc = atoi(optarg);
282             break;
283           case 'f':
284             daemonize = false;
285             break;
286           case 'd':
287             port_dec = atoi(optarg);
288             break;
289           case 'p':
290             pidfile = optarg;
291             break;
292           case 'u':
293             unsafe = true;
294             break;
295           default:
296             usage();
297             return EXIT_FAILURE;
298         }
299     }
300
301     if (!daemonize) {
302         log_syslog = false;
303     }
304
305     if (argc - optind != 2) {
306         usage();
307         return EXIT_FAILURE;
308     }
309
310     info("%s v%s...", DAEMON_NAME, DAEMON_VERSION);
311
312     config.domain = argv[optind];
313     config.srs = srs_read_secrets(argv[optind + 1]);
314     if (!config.srs
315         || common_setup(pidfile, unsafe, RUNAS_USER, RUNAS_GROUP,
316                         daemonize) != EXIT_SUCCESS
317         || (encoder_ptr = start_listener(port_enc)) == NULL
318         || (decoder_ptr = start_listener(port_dec)) == NULL) {
319         return EXIT_FAILURE;
320     }
321     return server_loop(srsd_starter, NULL, process_srs, NULL, &config);
322 }