style, mostly. Also fix return case now that we parse everything :)
[apps/pfixtools.git] / postfix.c
1 /******************************************************************************/
2 /*          postlicyd: a postfix policy daemon with a lot of features         */
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 <errno.h>
37 #include <limits.h>
38 #include <stdbool.h>
39 #include <syslog.h>
40 #include <unistd.h>
41
42 #include "job.h"
43 #include "postfix.h"
44 #include "buffer.h"
45 #include "tokens.h"
46
47 #define ishspace(c)  ((c) == ' ' || (c) == '\t')
48
49 struct jpriv_t {
50     buffer_t ibuf;
51     buffer_t obuf;
52     query_t query;
53 };
54
55 static jpriv_t *postfix_jpriv_init(jpriv_t *jp)
56 {
57     buffer_init(&jp->ibuf);
58     buffer_init(&jp->obuf);
59     query_init(&jp->query);
60     return jp;
61 }
62 static void postfix_jpriv_wipe(jpriv_t *jp)
63 {
64     query_wipe(&jp->query);
65     buffer_wipe(&jp->ibuf);
66     buffer_wipe(&jp->obuf);
67 }
68 DO_NEW(jpriv_t, postfix_jpriv);
69 DO_DELETE(jpriv_t, postfix_jpriv);
70
71 static void postfix_stop(job_t *job)
72 {
73     postfix_jpriv_delete(&job->jdata);
74 }
75
76 static int postfix_parsejob(query_t *query)
77 {
78 #define PARSE_CHECK(expr, error, ...)                                        \
79     do {                                                                     \
80         if (!(expr)) {                                                       \
81             syslog(LOG_ERR, error, ##__VA_ARGS__);                           \
82             return -1;                                                       \
83         }                                                                    \
84     } while (0)
85
86     char *p = vskipspaces(query->data.data);
87
88     while (*p) {
89         char *k, *v;
90         int klen, vlen, vtk;
91
92         while (ishspace(*p))
93             p++;
94         p = strchr(k = p, '=');
95         PARSE_CHECK(p, "could not find '=' in line");
96         for (klen = p - k; klen && ishspace(k[klen]); klen--);
97         p += 1; /* skip = */
98
99         while (ishspace(*p))
100             p++;
101         p = strstr(v = p, "\r\n");
102         PARSE_CHECK(p, "could not find final \\r\\n in line");
103         for (vlen = p - v; vlen && ishspace(v[vlen]); vlen--);
104         p += 2; /* skip \r\n */
105
106         vtk = tokenize(v, vlen);
107         switch (tokenize(k, klen)) {
108 #define CASE(up, low)  case PTK_##up: query->low = v; v[vlen] = '\0'; break;
109             CASE(HELO_NAME,           helo_name);
110             CASE(QUEUE_ID,            queue_id);
111             CASE(SENDER,              sender);
112             CASE(RECIPIENT,           recipient);
113             CASE(RECIPIENT_COUNT,     recipient_count);
114             CASE(CLIENT_ADDRESS,      client_address);
115             CASE(CLIENT_NAME,         client_name);
116             CASE(RCLIENT_NAME,        rclient_name);
117             CASE(INSTANCE,            instance);
118             CASE(SASL_METHOD,         sasl_method);
119             CASE(SASL_USERNAME,       sasl_username);
120             CASE(SASL_SENDER,         sasl_sender);
121             CASE(SIZE,                size);
122             CASE(CCERT_SUBJECT,       ccert_subject);
123             CASE(CCERT_ISSUER,        ccert_issuer);
124             CASE(CCSERT_FINGERPRINT,  ccsert_fingerprint);
125             CASE(ENCRYPTION_PROTOCOL, encryption_protocol);
126             CASE(ENCRYPTION_CIPHER,   encryption_cipher);
127             CASE(ENCRYPTION_KEYSIZE,  encryption_keysize);
128             CASE(ETRN_DOMAIN,         etrn_domain);
129 #undef CASE
130
131           case PTK_REQUEST:
132             PARSE_CHECK(vtk == PTK_SMTPD_ACCESS_POLICY,
133                         "unexpected `request' value: %.*s", vlen, v);
134             break;
135
136           case PTK_PROTOCOL_NAME:
137             PARSE_CHECK(vtk == PTK_SMTP || vtk == PTK_ESMTP,
138                         "unexpected `protocol_name' value: %.*s", vlen, v);
139             query->esmtp = vtk == PTK_ESMTP;
140             break;
141
142           case PTK_PROTOCOL_STATE:
143             switch (vtk) {
144 #define CASE(name)  case PTK_##name: query->state = SMTP_##name; break;
145                 CASE(CONNECT);
146                 CASE(EHLO);
147                 CASE(HELO);
148                 CASE(MAIL);
149                 CASE(RCPT);
150                 CASE(DATA);
151                 CASE(END_OF_MESSAGE);
152                 CASE(VRFY);
153                 CASE(ETRN);
154               default:
155                 PARSE_CHECK(false, "unexpected `protocol_state` value: %.*s",
156                             vlen, v);
157 #undef CASE
158             }
159             break;
160
161           default:
162             return -1;
163         }
164     }
165
166     return query->state == SMTP_UNKNOWN ? -1 : 0;
167
168 #undef PARSE_CHECK
169 }
170
171 static void postfix_process(job_t *job)
172 {
173     int nb;
174     const char *p;
175
176     switch (job->mode) {
177       case JOB_LISTEN:
178         if ((job = job_accept(job, JOB_READ))) {
179             job->jdata   = postfix_jpriv_new();
180             job->process = &postfix_process;
181             job->stop    = &postfix_stop;
182         }
183         return;
184
185       case JOB_WRITE:
186         nb = write(job->fd, job->jdata->obuf.data, job->jdata->obuf.len);
187         if (nb < 0) {
188             if ((job->error = errno != EINTR && errno != EAGAIN)) {
189                 syslog(LOG_ERR, "unexpected problem on the socket: %m");
190             }
191             return;
192         }
193
194         buffer_consume(&job->jdata->obuf, nb);
195         if (job->jdata->obuf.len)
196             return;
197
198         job_update_mode(job, JOB_READ);
199
200         /* fall through */
201
202       case JOB_READ:
203         nb = buffer_read(&job->jdata->ibuf, job->fd, -1);
204         if (nb < 0) {
205             if ((job->error = errno != EINTR && errno != EAGAIN)) {
206                 syslog(LOG_ERR, "unexpected problem on the socket: %m");
207             }
208             return;
209         }
210         if (nb == 0) {
211             syslog(LOG_ERR, "unexpected eof");
212             job->error = true;
213             return;
214         }
215
216         p = strstr(skipspaces(job->jdata->ibuf.data), "\r\n\r\n");
217         if (!p) {
218             if (job->jdata->ibuf.len > SHRT_MAX) {
219                 syslog(LOG_ERR, "too much data without CRLFCRLF");
220                 job->error = true;
221             }
222             return;
223         }
224         p += 4;
225
226         query_reset(&job->jdata->query);
227         buffer_add(&job->jdata->query.data, job->jdata->ibuf.data,
228                    p - 2 - job->jdata->ibuf.data);
229         buffer_consume(&job->jdata->query.data, p - job->jdata->ibuf.data);
230
231         if (postfix_parsejob(&job->jdata->query) < 0) {
232             job->error = true;
233             return;
234         }
235
236         job_update_mode(job, JOB_IDLE);
237
238         /* TODO: run the scenario */
239         return;
240
241       default:
242         job->error = true;
243         return;
244     }
245 }