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