bc04e85b41d188eec8ff0a3f5f6430fbee84e276
[apps/pfixtools.git] / main-postlicyd.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 REGENTS AND CONTRIBUTORS ``AS IS'' AND   */
20 /*  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE     */
21 /*  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR        */
22 /*  PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS    */
23 /*  BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR    */
24 /*  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF      */
25 /*  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS  */
26 /*  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN   */
27 /*  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)   */
28 /*  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF    */
29 /*  THE POSSIBILITY OF SUCH DAMAGE.                                           */
30 /******************************************************************************/
31
32 /*
33  * Copyright © 2006-2007 Pierre Habouzit
34  * Copyright © 2008 Florent Bruneau
35  */
36
37 #include <getopt.h>
38
39 #include "buffer.h"
40 #include "common.h"
41 #include "epoll.h"
42 #include "tokens.h"
43 #include "server.h"
44
45 #define DAEMON_NAME             "postlicyd"
46 #define DEFAULT_PORT            10000
47 #define RUNAS_USER              "nobody"
48 #define RUNAS_GROUP             "nogroup"
49
50 enum smtp_state {
51     SMTP_UNKNOWN,
52     SMTP_CONNECT,
53     SMTP_EHLO,
54     SMTP_HELO = SMTP_EHLO,
55     SMTP_MAIL,
56     SMTP_RCPT,
57     SMTP_DATA,
58     SMTP_END_OF_MESSAGE,
59     SMTP_VRFY,
60     SMTP_ETRN,
61 };
62
63 /* \see http://www.postfix.org/SMTPD_POLICY_README.html */
64 typedef struct query_t {
65     unsigned state : 4;
66     unsigned esmtp : 1;
67
68     const char *helo_name;
69     const char *queue_id;
70     const char *sender;
71     const char *recipient;
72     const char *recipient_count;
73     const char *client_address;
74     const char *client_name;
75     const char *reverse_client_name;
76     const char *instance;
77
78     /* postfix 2.2+ */
79     const char *sasl_method;
80     const char *sasl_username;
81     const char *sasl_sender;
82     const char *size;
83     const char *ccert_subject;
84     const char *ccert_issuer;
85     const char *ccsert_fingerprint;
86
87     /* postfix 2.3+ */
88     const char *encryption_protocol;
89     const char *encryption_cipher;
90     const char *encryption_keysize;
91     const char *etrn_domain;
92
93     const char *eoq;
94 } query_t;
95
96 static query_t *query_new(void)
97 {
98     return p_new(query_t, 1);
99 }
100
101 static void query_delete(query_t **query)
102 {
103     if (*query) {
104         p_delete(query);
105     }
106 }
107
108 static void *query_starter(server_t* server)
109 {
110     return query_new();
111 }
112
113 static int postfix_parsejob(query_t *query, char *p)
114 {
115 #define PARSE_CHECK(expr, error, ...)                                        \
116     do {                                                                     \
117         if (!(expr)) {                                                       \
118             syslog(LOG_ERR, error, ##__VA_ARGS__);                           \
119             return -1;                                                       \
120         }                                                                    \
121     } while (0)
122
123     p_clear(query, 1);
124     while (*p != '\n') {
125         char *k, *v;
126         int klen, vlen, vtk;
127
128         while (isblank(*p))
129             p++;
130         p = strchr(k = p, '=');
131         PARSE_CHECK(p, "could not find '=' in line");
132         for (klen = p - k; klen && isblank(k[klen]); klen--);
133         p += 1; /* skip = */
134
135         while (isblank(*p))
136             p++;
137         p = strchr(v = p, '\n');
138         PARSE_CHECK(p, "could not find final \\n in line");
139         for (vlen = p - v; vlen && isblank(v[vlen]); vlen--);
140         p += 1; /* skip \n */
141
142         vtk = tokenize(v, vlen);
143         switch (tokenize(k, klen)) {
144 #define CASE(up, low)  case PTK_##up: query->low = v; v[vlen] = '\0'; break;
145             CASE(HELO_NAME,           helo_name);
146             CASE(QUEUE_ID,            queue_id);
147             CASE(SENDER,              sender);
148             CASE(RECIPIENT,           recipient);
149             CASE(RECIPIENT_COUNT,     recipient_count);
150             CASE(CLIENT_ADDRESS,      client_address);
151             CASE(CLIENT_NAME,         client_name);
152             CASE(REVERSE_CLIENT_NAME, reverse_client_name);
153             CASE(INSTANCE,            instance);
154             CASE(SASL_METHOD,         sasl_method);
155             CASE(SASL_USERNAME,       sasl_username);
156             CASE(SASL_SENDER,         sasl_sender);
157             CASE(SIZE,                size);
158             CASE(CCERT_SUBJECT,       ccert_subject);
159             CASE(CCERT_ISSUER,        ccert_issuer);
160             CASE(CCSERT_FINGERPRINT,  ccsert_fingerprint);
161             CASE(ENCRYPTION_PROTOCOL, encryption_protocol);
162             CASE(ENCRYPTION_CIPHER,   encryption_cipher);
163             CASE(ENCRYPTION_KEYSIZE,  encryption_keysize);
164             CASE(ETRN_DOMAIN,         etrn_domain);
165 #undef CASE
166
167           case PTK_REQUEST:
168             PARSE_CHECK(vtk == PTK_SMTPD_ACCESS_POLICY,
169                         "unexpected `request' value: %.*s", vlen, v);
170             break;
171
172           case PTK_PROTOCOL_NAME:
173             PARSE_CHECK(vtk == PTK_SMTP || vtk == PTK_ESMTP,
174                         "unexpected `protocol_name' value: %.*s", vlen, v);
175             query->esmtp = vtk == PTK_ESMTP;
176             break;
177
178           case PTK_PROTOCOL_STATE:
179             switch (vtk) {
180 #define CASE(name)  case PTK_##name: query->state = SMTP_##name; break;
181                 CASE(CONNECT);
182                 CASE(EHLO);
183                 CASE(HELO);
184                 CASE(MAIL);
185                 CASE(RCPT);
186                 CASE(DATA);
187                 CASE(END_OF_MESSAGE);
188                 CASE(VRFY);
189                 CASE(ETRN);
190               default:
191                 PARSE_CHECK(false, "unexpected `protocol_state` value: %.*s",
192                             vlen, v);
193 #undef CASE
194             }
195             break;
196
197           default:
198             syslog(LOG_WARNING, "unexpected key, skipped: %.*s", klen, k);
199             break;
200         }
201     }
202
203     return query->state == SMTP_UNKNOWN ? -1 : 0;
204 #undef PARSE_CHECK
205 }
206
207 __attribute__((format(printf,2,0)))
208 static void policy_answer(server_t *pcy, const char *fmt, ...)
209 {
210     va_list args;
211     va_start(args, fmt);
212     buffer_addvf(&pcy->obuf, fmt, args);
213     va_end(args);
214     buffer_addstr(&pcy->obuf, "\n\n");
215     buffer_consume(&pcy->ibuf, ((query_t*)(pcy->data))->eoq - pcy->ibuf.data);
216     epoll_modify(pcy->fd, EPOLLIN | EPOLLOUT, pcy);
217 }
218
219 static void policy_process(server_t *pcy)
220 {
221     policy_answer(pcy, "DUNNO");
222 }
223
224 static int policy_run(server_t *pcy, void* config)
225 {
226     ssize_t search_offs = MAX(0, pcy->ibuf.len - 1);
227     int nb = buffer_read(&pcy->ibuf, pcy->fd, -1);
228     const char *eoq;
229
230     if (nb < 0) {
231         if (errno == EAGAIN || errno == EINTR)
232             return 0;
233         UNIXERR("read");
234         return -1;
235     }
236     if (nb == 0) {
237         if (pcy->ibuf.len)
238             syslog(LOG_ERR, "unexpected end of data");
239         return -1;
240     }
241
242     if (!(eoq = strstr(pcy->ibuf.data + search_offs, "\n\n")))
243         return 0;
244
245     if (postfix_parsejob(pcy->data, pcy->ibuf.data) < 0)
246         return -1;
247     ((query_t*)pcy->data)->eoq = eoq + strlen("\n\n");
248     epoll_modify(pcy->fd, 0, pcy);
249     policy_process(pcy);
250     return 0;
251 }
252
253 int start_listener(int port)
254 {
255     return start_server(port, NULL, NULL);
256 }
257
258 /* administrivia {{{ */
259
260 static int main_initialize(void)
261 {
262     openlog("postlicyd", LOG_PID, LOG_MAIL);
263     signal(SIGPIPE, SIG_IGN);
264     signal(SIGINT,  &common_sighandler);
265     signal(SIGTERM, &common_sighandler);
266     signal(SIGHUP,  &common_sighandler);
267     signal(SIGSEGV, &common_sighandler);
268     syslog(LOG_INFO, "Starting...");
269     return 0;
270 }
271
272 static void main_shutdown(void)
273 {
274     closelog();
275 }
276
277 module_init(main_initialize);
278 module_exit(main_shutdown);
279
280 void usage(void)
281 {
282     fputs("usage: "DAEMON_NAME" [options] config\n"
283           "\n"
284           "Options:\n"
285           "    -l <port>    port to listen to\n"
286           "    -p <pidfile> file to write our pid to\n"
287           "    -f           stay in foreground\n"
288          , stderr);
289 }
290
291 /* }}} */
292
293 int main(int argc, char *argv[])
294 {
295     const char *pidfile = NULL;
296     bool daemonize = true;
297     int port = DEFAULT_PORT;
298
299     for (int c = 0; (c = getopt(argc, argv, "hf" "l:p:")) >= 0; ) {
300         switch (c) {
301           case 'p':
302             pidfile = optarg;
303             break;
304           case 'l':
305             port = atoi(optarg);
306             break;
307           case 'f':
308             daemonize = false;
309             break;
310           default:
311             usage();
312             return EXIT_FAILURE;
313         }
314     }
315
316     if (argc - optind != 1) {
317         usage();
318         return EXIT_FAILURE;
319     }
320
321     if (pidfile_open(pidfile) < 0) {
322         syslog(LOG_CRIT, "unable to write pidfile %s", pidfile);
323         return EXIT_FAILURE;
324     }
325
326     if (drop_privileges(RUNAS_USER, RUNAS_GROUP) < 0) {
327         syslog(LOG_CRIT, "unable to drop privileges");
328         return EXIT_FAILURE;
329     }
330
331     if (daemonize && daemon_detach() < 0) {
332         syslog(LOG_CRIT, "unable to fork");
333         return EXIT_FAILURE;
334     }
335
336     pidfile_refresh();
337
338     if (start_listener(port) < 0)
339         return EXIT_FAILURE;
340
341     (void)server_loop(query_starter, (delete_client_t)query_delete,
342                       policy_run, NULL);
343
344     syslog(LOG_INFO, "Stopping...");
345     return EXIT_SUCCESS;
346 }