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