Plug the postfix policy query parsing again.
[apps/pfixtools.git] / policy.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  */
35
36 #include "common.h"
37 #include "policy.h"
38 #include "buffer.h"
39 #include "tokens.h"
40
41 enum smtp_state {
42     SMTP_UNKNOWN,
43     SMTP_CONNECT,
44     SMTP_EHLO,
45     SMTP_HELO = SMTP_EHLO,
46     SMTP_MAIL,
47     SMTP_RCPT,
48     SMTP_DATA,
49     SMTP_END_OF_MESSAGE,
50     SMTP_VRFY,
51     SMTP_ETRN,
52 };
53
54 /* \see http://www.postfix.org/SMTPD_POLICY_README.html */
55 typedef struct query_t {
56     unsigned state : 4;
57     unsigned esmtp : 1;
58
59     const char *helo_name;
60     const char *queue_id;
61     const char *sender;
62     const char *recipient;
63     const char *recipient_count;
64     const char *client_address;
65     const char *client_name;
66     const char *rclient_name;
67     const char *instance;
68
69     /* postfix 2.2+ */
70     const char *sasl_method;
71     const char *sasl_username;
72     const char *sasl_sender;
73     const char *size;
74     const char *ccert_subject;
75     const char *ccert_issuer;
76     const char *ccsert_fingerprint;
77
78     /* postfix 2.3+ */
79     const char *encryption_protocol;
80     const char *encryption_cipher;
81     const char *encryption_keysize;
82     const char *etrn_domain;
83
84     buffer_t data;
85 } query_t;
86
87 static query_t *query_init(query_t *rq) {
88     memset(rq, 0, offsetof(query_t, data));
89     buffer_init(&rq->data);
90     return rq;
91 }
92 static void query_wipe(query_t *rq) {
93     buffer_wipe(&rq->data);
94 }
95
96 static int xwrite(int fd, const char *s, size_t l)
97 {
98     while (l > 0) {
99         int nb = write(fd, s, l);
100         if (nb < 0) {
101             if (errno == EINTR || errno == EAGAIN)
102                 continue;
103             return -1;
104         }
105         l -= nb;
106     }
107     return 0;
108 }
109
110 static int postfix_parsejob(query_t *query)
111 {
112 #define PARSE_CHECK(expr, error, ...)                                        \
113     do {                                                                     \
114         if (!(expr)) {                                                       \
115             syslog(LOG_ERR, error, ##__VA_ARGS__);                           \
116             return -1;                                                       \
117         }                                                                    \
118     } while (0)
119
120     char *p = vskipspaces(query->data.data);
121
122     memset(query, 0, offsetof(query_t, data));
123     while (p[0] != '\r' || p[1] != '\n') {
124         char *k, *v;
125         int klen, vlen, vtk;
126
127         while (isblank(*p))
128             p++;
129         p = strchr(k = p, '=');
130         PARSE_CHECK(p, "could not find '=' in line");
131         for (klen = p - k; klen && isblank(k[klen]); klen--);
132         p += 1; /* skip = */
133
134         while (isblank(*p))
135             p++;
136         p = strstr(v = p, "\r\n");
137         PARSE_CHECK(p, "could not find final \\r\\n in line");
138         for (vlen = p - v; vlen && isblank(v[vlen]); vlen--);
139         p += 2; /* skip \r\n */
140
141         vtk = tokenize(v, vlen);
142         switch (tokenize(k, klen)) {
143 #define CASE(up, low)  case PTK_##up: query->low = v; v[vlen] = '\0'; break;
144             CASE(HELO_NAME,           helo_name);
145             CASE(QUEUE_ID,            queue_id);
146             CASE(SENDER,              sender);
147             CASE(RECIPIENT,           recipient);
148             CASE(RECIPIENT_COUNT,     recipient_count);
149             CASE(CLIENT_ADDRESS,      client_address);
150             CASE(CLIENT_NAME,         client_name);
151             CASE(RCLIENT_NAME,        rclient_name);
152             CASE(INSTANCE,            instance);
153             CASE(SASL_METHOD,         sasl_method);
154             CASE(SASL_USERNAME,       sasl_username);
155             CASE(SASL_SENDER,         sasl_sender);
156             CASE(SIZE,                size);
157             CASE(CCERT_SUBJECT,       ccert_subject);
158             CASE(CCERT_ISSUER,        ccert_issuer);
159             CASE(CCSERT_FINGERPRINT,  ccsert_fingerprint);
160             CASE(ENCRYPTION_PROTOCOL, encryption_protocol);
161             CASE(ENCRYPTION_CIPHER,   encryption_cipher);
162             CASE(ENCRYPTION_KEYSIZE,  encryption_keysize);
163             CASE(ETRN_DOMAIN,         etrn_domain);
164 #undef CASE
165
166           case PTK_REQUEST:
167             PARSE_CHECK(vtk == PTK_SMTPD_ACCESS_POLICY,
168                         "unexpected `request' value: %.*s", vlen, v);
169             break;
170
171           case PTK_PROTOCOL_NAME:
172             PARSE_CHECK(vtk == PTK_SMTP || vtk == PTK_ESMTP,
173                         "unexpected `protocol_name' value: %.*s", vlen, v);
174             query->esmtp = vtk == PTK_ESMTP;
175             break;
176
177           case PTK_PROTOCOL_STATE:
178             switch (vtk) {
179 #define CASE(name)  case PTK_##name: query->state = SMTP_##name; break;
180                 CASE(CONNECT);
181                 CASE(EHLO);
182                 CASE(HELO);
183                 CASE(MAIL);
184                 CASE(RCPT);
185                 CASE(DATA);
186                 CASE(END_OF_MESSAGE);
187                 CASE(VRFY);
188                 CASE(ETRN);
189               default:
190                 PARSE_CHECK(false, "unexpected `protocol_state` value: %.*s",
191                             vlen, v);
192 #undef CASE
193             }
194             break;
195
196           default:
197             syslog(LOG_WARNING, "unexpected key, skipped: %.*s", klen, k);
198             break;
199         }
200     }
201
202     return query->state == SMTP_UNKNOWN ? -1 : 0;
203 #undef PARSE_CHECK
204 }
205
206 void *policy_run(int fd, void *data)
207 {
208     query_t q;
209     query_init(&q);
210
211     for (;;) {
212         int nb = buffer_read(&q.data, fd, -1);
213         const char *eoq;
214
215         if (nb < 0) {
216             if (errno == EAGAIN || errno == EINTR)
217                 continue;
218             UNIXERR("read");
219             break;
220         }
221         if (nb == 0) {
222             if (q.data.len)
223                 syslog(LOG_ERR, "unexpected end of data");
224             break;
225         }
226
227         eoq = strstr(q.data.data + MAX(0, q.data.len - 3), "\r\n\r\n");
228         if (!eoq)
229             continue;
230
231         if (postfix_parsejob(&q) < 0)
232             break;
233
234         buffer_consume(&q.data, eoq + strlen("\r\n\r\n") - q.data.data);
235         if (xwrite(fd, "DUNNO\r\n", strlen("DUNNO\r\n"))) {
236             UNIXERR("write");
237             break;
238         }
239     }
240
241     query_wipe(&q);
242     close(fd);
243     return NULL;
244 }