pfix-srsd: add a -I option
[apps/pfixtools.git] / postlicyd / query.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 © 2007 Pierre Habouzit
37  * Copyright © 2008 Florent Bruneau
38  */
39
40 #include "query.h"
41 #include "policy_tokens.h"
42 #include "str.h"
43
44 const static_str_t smtp_state_names[SMTP_count] = {
45   { "CONNECT", 7 },
46   { "HELO", 4 },
47   { "MAIL", 4 },
48   { "RCPT", 4 },
49   { "DATA", 4 },
50   { "END-OF-MESSAGE", 14 },
51   { "VRFY", 4 },
52   { "ETRN", 4 },
53 };
54
55 static const static_str_t static_ESMTP = { "ESMTP", 5 };
56 static const static_str_t static_SMTP  = { "SMTP",  4 };
57
58 bool query_parse(query_t *query, char *p)
59 {
60 #define PARSE_CHECK(expr, error, ...)                                        \
61     do {                                                                     \
62         if (!(expr)) {                                                       \
63             err(error, ##__VA_ARGS__);                                       \
64             return false;                                                    \
65         }                                                                    \
66     } while (0)
67
68     p_clear(query, 1);
69     query->state = SMTP_UNKNOWN;
70     while (*p != '\n') {
71         char *k, *v;
72         int klen, vlen, vtk;
73
74         while (isblank(*p))
75             p++;
76         p = strchr(k = p, '=');
77         PARSE_CHECK(p, "could not find '=' in line");
78         for (klen = p - k; klen && isblank(k[klen]); klen--);
79         p += 1; /* skip = */
80
81         while (isblank(*p))
82             p++;
83         p = strchr(v = p, '\n');
84         PARSE_CHECK(p, "could not find final \\n in line");
85         for (vlen = p - v; vlen && isblank(v[vlen]); vlen--);
86         p += 1; /* skip \n */
87
88         vtk = policy_tokenize(v, vlen);
89         switch (policy_tokenize(k, klen)) {
90 #define CASE(up, low)  case PTK_##up: query->low.str = v; query->low.len = vlen; v[vlen] = '\0';  break;
91             CASE(HELO_NAME,           helo_name);
92             CASE(QUEUE_ID,            queue_id);
93             CASE(RECIPIENT_COUNT,     recipient_count);
94             CASE(CLIENT_ADDRESS,      client_address);
95             CASE(CLIENT_NAME,         client_name);
96             CASE(REVERSE_CLIENT_NAME, reverse_client_name);
97             CASE(INSTANCE,            instance);
98             CASE(SASL_METHOD,         sasl_method);
99             CASE(SASL_USERNAME,       sasl_username);
100             CASE(SASL_SENDER,         sasl_sender);
101             CASE(SIZE,                size);
102             CASE(CCERT_SUBJECT,       ccert_subject);
103             CASE(CCERT_ISSUER,        ccert_issuer);
104             CASE(CCERT_FINGERPRINT,   ccert_fingerprint);
105             CASE(ENCRYPTION_PROTOCOL, encryption_protocol);
106             CASE(ENCRYPTION_CIPHER,   encryption_cipher);
107             CASE(ENCRYPTION_KEYSIZE,  encryption_keysize);
108             CASE(ETRN_DOMAIN,         etrn_domain);
109             CASE(STRESS,              stress);
110 #undef CASE
111
112           case PTK_SENDER:
113             query->sender.str = v;
114             query->sender.len = vlen;
115             v[vlen] = '\0';
116             query->sender_domain.str = memchr(query->sender.str, '@', vlen);
117             if (query->sender_domain.str != NULL) {
118                 ++query->sender_domain.str;
119                 query->sender_domain.len = query->sender.len
120                                          - (query->sender_domain.str - query->sender.str);
121             }
122             break;
123
124           case PTK_RECIPIENT:
125             query->recipient.str = v;
126             query->recipient.len = vlen;
127             v[vlen] = '\0';
128             query->recipient_domain.str = memchr(query->recipient.str, '@', vlen);
129             if (query->recipient_domain.str != NULL) {
130                 ++query->recipient_domain.str;
131                 query->recipient_domain.len = query->recipient.len
132                                          - (query->recipient_domain.str - query->recipient.str);
133
134             }
135             break;
136
137           case PTK_REQUEST:
138             PARSE_CHECK(vtk == PTK_SMTPD_ACCESS_POLICY,
139                         "unexpected `request' value: %.*s", vlen, v);
140             break;
141
142           case PTK_PROTOCOL_NAME:
143             PARSE_CHECK(vtk == PTK_SMTP || vtk == PTK_ESMTP,
144                         "unexpected `protocol_name' value: %.*s", vlen, v);
145             query->esmtp = vtk == PTK_ESMTP;
146             break;
147
148           case PTK_PROTOCOL_STATE:
149             switch (vtk) {
150 #define CASE(name)  case PTK_##name: query->state = SMTP_##name; break;
151                 CASE(CONNECT);
152                 CASE(EHLO);
153                 CASE(HELO);
154                 CASE(MAIL);
155                 CASE(RCPT);
156                 CASE(DATA);
157                 CASE(END_OF_MESSAGE);
158                 CASE(VRFY);
159                 CASE(ETRN);
160               default:
161                 PARSE_CHECK(false, "unexpected `protocol_state` value: %.*s",
162                             vlen, v);
163 #undef CASE
164             }
165             break;
166
167           default:
168             warn("unexpected key, skipped: %.*s", klen, k);
169             continue;
170         }
171     }
172
173     return query->state != SMTP_UNKNOWN;
174 #undef PARSE_CHECK
175 }
176
177 const static_str_t *query_field_for_id(const query_t *query, postlicyd_token id)
178 {
179     switch (id) {
180 #define CASE(Up, Low)                                                          \
181       case PTK_ ## Up: return &query->Low;
182       CASE(HELO_NAME, helo_name)
183       CASE(QUEUE_ID, queue_id)
184       CASE(SENDER, sender)
185       CASE(SENDER_DOMAIN, sender_domain)
186       CASE(RECIPIENT, recipient)
187       CASE(RECIPIENT_DOMAIN, recipient_domain)
188       CASE(RECIPIENT_COUNT, recipient_count)
189       CASE(CLIENT_ADDRESS, client_address)
190       CASE(CLIENT_NAME, client_name)
191       CASE(REVERSE_CLIENT_NAME, reverse_client_name)
192       CASE(INSTANCE, instance)
193       CASE(SASL_METHOD, sasl_method)
194       CASE(SASL_USERNAME, sasl_username)
195       CASE(SASL_SENDER, sasl_sender)
196       CASE(SIZE, size)
197       CASE(CCERT_SUBJECT, ccert_subject)
198       CASE(CCERT_ISSUER, ccert_issuer)
199       CASE(CCERT_FINGERPRINT, ccert_fingerprint)
200       CASE(ENCRYPTION_PROTOCOL, encryption_protocol)
201       CASE(ENCRYPTION_CIPHER, encryption_cipher)
202       CASE(ENCRYPTION_KEYSIZE, encryption_keysize)
203       CASE(ETRN_DOMAIN, etrn_domain)
204       CASE(STRESS, stress)
205 #undef CASE
206       case PTK_PROTOCOL_NAME:
207         return query->esmtp ? &static_ESMTP : &static_SMTP;
208
209       case PTK_PROTOCOL_STATE:
210         return &smtp_state_names[query->state];
211
212       default: return NULL;
213     }
214 }
215
216 const static_str_t *query_field_for_name(const query_t *query, const char *name)
217 {
218     postlicyd_token id = policy_tokenize(name, strlen(name));
219     if (id == PTK_UNKNOWN) {
220         warn("unknown query field %s", name);
221         return NULL;
222     }
223     return query_field_for_id(query, id);
224 }
225
226 ssize_t query_format(char *dest, size_t len, const char *fmt, const query_t *query)
227 {
228     size_t written = 0;
229     size_t pos = 0;
230
231 #define WRITE(Src, Len)                                                        \
232     do {                                                                       \
233         size_t __len     = (Len);                                              \
234         if (written < len) {                                                   \
235             size_t __to_write = MIN(len - written - 1, __len);                 \
236             memcpy(dest + written, (Src), __to_write);                         \
237             written += __to_write;                                             \
238         }                                                                      \
239         pos += __len;                                                          \
240     } while (0)
241     while (*fmt != '\0') {
242         const char *next_format = strchr(fmt, '$');
243         while (next_format != NULL && next_format[1] != '{') {
244             next_format = strchr(next_format + 1, '$');
245         }
246         if (next_format == NULL) {
247             next_format = fmt + m_strlen(fmt);
248         }
249         WRITE(fmt, next_format - fmt);
250         fmt = next_format;
251         if (*fmt != '\0') {
252             fmt += 2;
253             next_format = strchr(fmt, '}');
254             if (next_format == NULL) {
255                 return -1;
256             }
257
258             postlicyd_token tok = policy_tokenize(fmt, next_format - fmt);
259             if (tok == PTK_UNKNOWN) {
260                 warn("unknown field name \"%.*s\"", (int)(next_format - fmt), fmt);
261             }
262             const static_str_t *field = query == NULL ? NULL
263                                                       : query_field_for_id(query, tok);
264             if (field == NULL) {
265                 WRITE("(null)", 6);
266             } else {
267                 WRITE(field->str, field->len);
268             }
269             fmt = next_format + 1;
270         }
271     }
272
273     if (written > 0 && len > 0) {
274         dest[written] = '\0';
275     }
276     return pos;
277 }
278
279 bool query_format_buffer(buffer_t *buf, const char *fmt, const query_t *query)
280 {
281     buffer_ensure(buf, m_strlen(fmt) + 64);
282
283     ssize_t size = array_free_space(*buf);
284     ssize_t format_size = query_format(array_end(*buf),
285                                        size, fmt, query);
286     if (format_size == -1) {
287         return false;
288     } else if (format_size > size) {
289         buffer_ensure(buf, format_size + 1);
290         query_format(array_end(*buf),
291                      array_free_space(*buf),
292                      fmt, query);
293         array_len(*buf) += format_size;
294     } else {
295         array_len(*buf) += format_size;
296     }
297     return true;
298 }