- Change so that the child uses poll(2) on all the fds rather than letting the fetch-* stuff poll and periodically checking the parent fd.
- Make delivery children be handled in the main loop (fdm.c) along with the fetch children. This means that different accounts will not block each other when delivering. I tried allowing multiple simultaneous deliveries for each account but it doesn't work very well, particularly regarding errors.
Nicholas Marriott
17 years ago
0 | 16 March 2007 | |
1 | ||
2 | * Change so that the child uses poll(2) on all the fds rather than letting | |
3 | the fetch-* stuff poll and periodically checking the parent fd. | |
4 | * Make delivery children be handled in the main loop (fdm.c) along with | |
5 | the fetch children. This means that different accounts will not block each | |
6 | other when delivering. I tried allowing multiple simultaneous deliveries | |
7 | for each account but it doesn't work very well, particularly regarding | |
8 | errors. | |
9 | ||
0 | 10 | 15 March 2007 |
1 | 11 | |
2 | 12 | * Seriously reduce -v output in favour of -vv and -vvv. |
16 | 16 | deliver-keep.c deliver-maildir.c deliver-mbox.c deliver-write.c \ |
17 | 17 | deliver-append.c deliver-rewrite.c match-regexp.c match-command.c \ |
18 | 18 | match-tagged.c match-size.c match-string.c match-matched.c match-age.c \ |
19 | match-unmatched.c match-attachment.c child.c parent.c privsep.c attach.c \ | |
19 | match-unmatched.c match-attachment.c child.c privsep.c attach.c \ | |
20 | 20 | cleanup.c imap-common.c fetch-imappipe.c deliver-remove-header.c \ |
21 | 21 | deliver-stdout.c deliver-append-string.c strb.c deliver-add-header.c \ |
22 | deliver-exec.c \ | |
22 | deliver-exec.c child-fetch.c parent-fetch.c child-deliver.c \ | |
23 | parent-deliver.c \ | |
23 | 24 | parse.y lex.l |
24 | 25 | |
25 | 26 | LEX= lex |
29 | 29 | - look at .netrc |
30 | 30 | -- move lbuf stuff into fetch_*_data |
31 | 31 | -- split fetch into next and get(line) and alloc mail outside |
32 | -- introduce eg fetch_pop3_state structs and make fetch reentrant | |
33 | -- rework IO_NOWAIT and FETCH_NOWAIT | |
32 | -- rework IO_NOWAIT | |
34 | 33 | -- tidy child.c |
35 | 34 | -- audit for uses of special "off" variables where m->size would do |
36 | 35 | - is there any reason struct mail shouldn't point to a struct account? |
36 | - coalesce multiple IMAP deletions into a single call |
0 | /* $Id$ */ | |
1 | ||
2 | /* | |
3 | * Copyright (c) 2006 Nicholas Marriott <nicm@users.sourceforge.net> | |
4 | * | |
5 | * Permission to use, copy, modify, and distribute this software for any | |
6 | * purpose with or without fee is hereby granted, provided that the above | |
7 | * copyright notice and this permission notice appear in all copies. | |
8 | * | |
9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
13 | * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER | |
14 | * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING | |
15 | * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
16 | */ | |
17 | ||
18 | #include <sys/types.h> | |
19 | ||
20 | #include <string.h> | |
21 | #include <unistd.h> | |
22 | ||
23 | #include "fdm.h" | |
24 | #include "match.h" | |
25 | ||
26 | int child_deliver(struct child *child, struct io *io); | |
27 | ||
28 | int | |
29 | child_deliver(struct child *child, struct io *io) | |
30 | { | |
31 | struct child_deliver_data *data = child->data; | |
32 | struct account *a = data->account; | |
33 | struct mail *m = data->mail; | |
34 | struct msg msg; | |
35 | int error = 0; | |
36 | ||
37 | #ifndef NO_SETPROCTITLE | |
38 | setproctitle("%s[%lu]", data->name, (u_long) geteuid()); | |
39 | #endif | |
40 | ||
41 | /* refresh user and home and fix tags */ | |
42 | fill_info(NULL); | |
43 | update_tags(&m->tags); | |
44 | log_debug2("%s: user is: %s, home is: %s", a->name, conf.info.user, | |
45 | conf.info.home); | |
46 | ||
47 | /* call the hook */ | |
48 | data->hook(0, a, &msg, data, &msg.data.error); | |
49 | ||
50 | /* inform parent we're done */ | |
51 | msg.type = MSG_DONE; | |
52 | if (privsep_send(io, &msg, m->tags, STRB_SIZE(m->tags)) != 0) | |
53 | fatalx("deliver: privsep_send error"); | |
54 | if (privsep_recv(io, &msg, NULL, 0) != 0) | |
55 | fatalx("deliver: privsep_recv error"); | |
56 | if (msg.type != MSG_EXIT) | |
57 | fatalx("deliver: unexpected message"); | |
58 | ||
59 | return (error); | |
60 | } | |
61 | ||
62 | void | |
63 | child_deliver_action_hook(pid_t pid, struct account *a, struct msg *msg, | |
64 | struct child_deliver_data *data, int *result) | |
65 | { | |
66 | struct action *t = data->action; | |
67 | struct deliver_ctx *dctx = data->dctx; | |
68 | struct mail *m = data->mail; | |
69 | struct mail *md = &dctx->wr_mail; | |
70 | ||
71 | /* check if this is the parent */ | |
72 | if (pid != 0) { | |
73 | /* use new mail if necessary */ | |
74 | if (t->deliver->type != DELIVER_WRBACK) { | |
75 | xfree(dctx); | |
76 | return; | |
77 | } | |
78 | ||
79 | if (*result != DELIVER_SUCCESS) { | |
80 | mail_destroy(md); | |
81 | ||
82 | xfree(dctx); | |
83 | return; | |
84 | } | |
85 | ||
86 | mail_close(md); | |
87 | mail_receive(m, msg); | |
88 | ||
89 | xfree(dctx); | |
90 | return; | |
91 | } | |
92 | ||
93 | /* this is the child. do the delivery */ | |
94 | *result = t->deliver->deliver(dctx, t); | |
95 | if (t->deliver->type != DELIVER_WRBACK || *result != DELIVER_SUCCESS) | |
96 | return; | |
97 | ||
98 | mail_send(md, msg); | |
99 | log_debug2("%s: using new mail, size %zu", a->name, md->size); | |
100 | } | |
101 | ||
102 | void | |
103 | child_deliver_cmd_hook(pid_t pid, struct account *a, unused struct msg *msg, | |
104 | struct child_deliver_data *data, int *result) | |
105 | { | |
106 | struct match_ctx *mctx = data->mctx; | |
107 | struct mail *m = mctx->mail; | |
108 | struct match_command_data *cmddata = data->cmddata; | |
109 | int flags, status, found = 0; | |
110 | char *s, *cause, *lbuf, *out, *err, tag[24]; | |
111 | size_t llen; | |
112 | struct cmd *cmd = NULL; | |
113 | struct rmlist rml; | |
114 | u_int i; | |
115 | ||
116 | /* if this is the parent, do nothing */ | |
117 | if (pid != 0) { | |
118 | xfree(mctx); | |
119 | return; | |
120 | } | |
121 | ||
122 | /* sort out the command */ | |
123 | s = replacepath(&cmddata->cmd, m->tags, m, &m->rml); | |
124 | if (s == NULL || *s == '\0') { | |
125 | log_warnx("%s: empty command", a->name); | |
126 | goto error; | |
127 | } | |
128 | ||
129 | log_debug2("%s: %s: started (ret=%d re=%s)", a->name, s, cmddata->ret, | |
130 | cmddata->re.str == NULL ? "none" : cmddata->re.str); | |
131 | flags = CMD_ONCE; | |
132 | if (cmddata->pipe) | |
133 | flags |= CMD_IN; | |
134 | if (cmddata->re.str != NULL) | |
135 | flags |= CMD_OUT; | |
136 | cmd = cmd_start(s, flags, conf.timeout, m->data, m->size, &cause); | |
137 | if (cmd == NULL) { | |
138 | log_warnx("%s: %s: %s", a->name, s, cause); | |
139 | goto error; | |
140 | } | |
141 | ||
142 | llen = IO_LINESIZE; | |
143 | lbuf = xmalloc(llen); | |
144 | ||
145 | do { | |
146 | status = cmd_poll(cmd, &out, &err, &lbuf, &llen, &cause); | |
147 | if (status > 0) { | |
148 | log_warnx("%s: %s: %s", a->name, s, cause); | |
149 | goto error; | |
150 | } | |
151 | if (status < 0) | |
152 | break; | |
153 | if (err != NULL) | |
154 | log_warnx("%s: %s: %s", a->name, s, err); | |
155 | if (out == NULL) | |
156 | continue; | |
157 | log_debug3("%s: %s: out: %s", a->name, s, out); | |
158 | if (found) /* XXX stop early? */ | |
159 | continue; | |
160 | ||
161 | found = re_string(&cmddata->re, out, &rml, &cause); | |
162 | if (found == -1) { | |
163 | log_warnx("%s: %s", a->name, cause); | |
164 | goto error; | |
165 | } | |
166 | if (found != 1) | |
167 | continue; | |
168 | /* save the matches */ | |
169 | if (!rml.valid) | |
170 | continue; | |
171 | for (i = 0; i < NPMATCH; i++) { | |
172 | if (!rml.list[i].valid) | |
173 | break; | |
174 | xsnprintf(tag, sizeof tag, "command%u", i); | |
175 | add_tag(&m->tags, tag, "%.*s", (int) (rml.list[i].eo - | |
176 | rml.list[i].so), out + rml.list[i].so); | |
177 | } | |
178 | } while (status >= 0); | |
179 | status = -1 - status; | |
180 | ||
181 | log_debug2("%s: %s: returned %d, found %d", a->name, s, status, found); | |
182 | ||
183 | cmd_free(cmd); | |
184 | xfree(s); | |
185 | xfree(lbuf); | |
186 | ||
187 | status = cmddata->ret == status; | |
188 | if (cmddata->ret != -1 && cmddata->re.str != NULL) | |
189 | *result = (found && status) ? MATCH_TRUE : MATCH_FALSE; | |
190 | else if (cmddata->ret != -1 && cmddata->re.str == NULL) | |
191 | *result = status ? MATCH_TRUE : MATCH_FALSE; | |
192 | else if (cmddata->ret == -1 && cmddata->re.str != NULL) | |
193 | *result = found ? MATCH_TRUE : MATCH_FALSE; | |
194 | else | |
195 | *result = MATCH_ERROR; | |
196 | return; | |
197 | ||
198 | error: | |
199 | if (cause != NULL) | |
200 | xfree(cause); | |
201 | if (cmd != NULL) | |
202 | cmd_free(cmd); | |
203 | if (s != NULL) | |
204 | xfree(s); | |
205 | if (lbuf != NULL) | |
206 | xfree(lbuf); | |
207 | *result = MATCH_ERROR; | |
208 | } |
0 | /* $Id$ */ | |
1 | ||
2 | /* | |
3 | * Copyright (c) 2006 Nicholas Marriott <nicm@users.sourceforge.net> | |
4 | * | |
5 | * Permission to use, copy, modify, and distribute this software for any | |
6 | * purpose with or without fee is hereby granted, provided that the above | |
7 | * copyright notice and this permission notice appear in all copies. | |
8 | * | |
9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
13 | * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER | |
14 | * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING | |
15 | * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
16 | */ | |
17 | ||
18 | #include <sys/types.h> | |
19 | #include <sys/stat.h> | |
20 | #include <sys/time.h> | |
21 | #include <sys/wait.h> | |
22 | ||
23 | #include <fcntl.h> | |
24 | #include <fnmatch.h> | |
25 | #include <limits.h> | |
26 | #include <string.h> | |
27 | #include <time.h> | |
28 | #include <unistd.h> | |
29 | ||
30 | #include "fdm.h" | |
31 | #include "deliver.h" | |
32 | #include "fetch.h" | |
33 | #include "match.h" | |
34 | ||
35 | int poll_account(struct io *, struct account *); | |
36 | int fetch_account(struct io *, struct account *, struct ios *, double); | |
37 | ||
38 | int fetch_flush(struct account *, struct io *, int *, int *, int *, | |
39 | const char **); | |
40 | int fetch_poll(struct account *a, int, struct ios *, struct io **); | |
41 | int fetch_transform(struct account *, struct mail *); | |
42 | int fetch_rule(struct match_ctx *, const char **); | |
43 | ||
44 | int run_match(struct account *, const char **); | |
45 | int run_deliver(struct account *, struct io *, int *, const char **); | |
46 | int run_done(struct account *, int *, int *, const char **); | |
47 | ||
48 | void flush_queue(struct match_queue *); | |
49 | u_int queue_length(struct match_queue *); | |
50 | ||
51 | struct strings *get_users(struct match_ctx *, struct rule *, struct action *, | |
52 | int *); | |
53 | ||
54 | int do_expr(struct rule *, struct match_ctx *); | |
55 | int do_deliver(struct rule *, struct match_ctx *); | |
56 | int do_rules(struct match_ctx *, struct rules *, const char **); | |
57 | ||
58 | int start_action(struct io *, struct deliver_ctx *); | |
59 | int finish_action(struct deliver_ctx *, struct msg *, void *, size_t); | |
60 | ||
61 | /* XXX wrap in struct (match_state?) and pass, with kept/dropped etc */ | |
62 | struct match_queue matchq; | |
63 | struct match_queue deliverq; | |
64 | struct match_queue doneq; | |
65 | ||
66 | int | |
67 | child_fetch(struct child *child, struct io *io) | |
68 | { | |
69 | struct child_fetch_data *data = child->data; | |
70 | enum fdmop op = data->op; | |
71 | struct account *a = data->account; | |
72 | struct msg msg; | |
73 | int error = 1; | |
74 | double tim; | |
75 | struct ios ios; | |
76 | ||
77 | #ifdef DEBUG | |
78 | xmalloc_clear(); | |
79 | COUNTFDS(a->name); | |
80 | #endif | |
81 | ||
82 | io->flags |= IO_NOWAIT; | |
83 | log_debug2("%s: started, pid %ld", a->name, (long) getpid()); | |
84 | ||
85 | #ifndef NO_SETPROCTITLE | |
86 | setproctitle("child: %s", a->name); | |
87 | #endif | |
88 | ||
89 | if (op == FDMOP_POLL && a->fetch->poll == NULL) { | |
90 | log_info("%s: polling not supported", a->name); | |
91 | goto out; | |
92 | } else if (op == FDMOP_FETCH && a->fetch->fetch == NULL) { | |
93 | log_info("%s: fetching not supported", a->name); | |
94 | goto out; | |
95 | } | |
96 | tim = get_time(); | |
97 | ||
98 | /* start fetch */ | |
99 | ARRAY_INIT(&ios); | |
100 | ARRAY_ADD(&ios, io, struct io *); | |
101 | if (a->fetch->start != NULL && | |
102 | a->fetch->start(a, &ios) != FETCH_SUCCESS) { | |
103 | log_warnx("%s: start error. aborting", a->name); | |
104 | goto out; | |
105 | } | |
106 | ||
107 | /* process fetch */ | |
108 | log_debug2("%s: started processing", a->name); | |
109 | switch (op) { | |
110 | case FDMOP_POLL: | |
111 | error = poll_account(io, a); | |
112 | break; | |
113 | case FDMOP_FETCH: | |
114 | error = fetch_account(io, a, &ios, tim); | |
115 | break; | |
116 | default: | |
117 | fatalx("child: unexpected command"); | |
118 | } | |
119 | log_debug2("%s: finished processing. exiting", a->name); | |
120 | ||
121 | out: | |
122 | /* finish fetch */ | |
123 | if (a->fetch->finish != NULL && a->fetch->finish(a) != FETCH_SUCCESS) | |
124 | error = 1; | |
125 | ||
126 | io->flags &= ~IO_NOWAIT; | |
127 | memset(&msg, 0, sizeof msg); | |
128 | ||
129 | msg.type = MSG_EXIT; | |
130 | log_debug3("%s: sending exit message to parent", a->name); | |
131 | if (privsep_send(io, &msg, NULL, 0) != 0) | |
132 | fatalx("child: privsep_send error"); | |
133 | log_debug3("%s: waiting for exit message from parent", a->name); | |
134 | if (privsep_recv(io, &msg, NULL, 0) != 0) | |
135 | fatalx("child: privsep_recv error"); | |
136 | if (msg.type != MSG_EXIT) | |
137 | fatalx("child: unexpected message"); | |
138 | ||
139 | io_close(io); | |
140 | io_free(io); | |
141 | ||
142 | #ifdef DEBUG | |
143 | COUNTFDS(a->name); | |
144 | xmalloc_report(a->name); | |
145 | #endif | |
146 | ||
147 | return (error); | |
148 | } | |
149 | ||
150 | int | |
151 | poll_account(unused struct io *io, struct account *a) | |
152 | { | |
153 | u_int n; | |
154 | ||
155 | log_debug2("%s: polling", a->name); | |
156 | ||
157 | if (a->fetch->poll(a, &n) == FETCH_ERROR) { | |
158 | log_warnx("%s: polling error. aborted", a->name); | |
159 | return (1); | |
160 | } | |
161 | ||
162 | log_info("%s: %u messages found", a->name, n); | |
163 | ||
164 | return (0); | |
165 | } | |
166 | ||
167 | int | |
168 | run_match(struct account *a, const char **cause) | |
169 | { | |
170 | struct match_ctx *mctx; | |
171 | ||
172 | if (TAILQ_EMPTY(&matchq)) | |
173 | return (0); | |
174 | ||
175 | mctx = TAILQ_FIRST(&matchq); | |
176 | log_debug3("%s: running match queue", a->name); | |
177 | ||
178 | switch (fetch_rule(mctx, cause)) { | |
179 | case FETCH_ERROR: | |
180 | return (1); | |
181 | case FETCH_AGAIN: | |
182 | /* delivering mail, queue for delivery */ | |
183 | log_debug3("%s: adding to deliver queue", a->name); | |
184 | TAILQ_REMOVE(&matchq, mctx, match_entry); | |
185 | TAILQ_INSERT_TAIL(&deliverq, mctx, deliver_entry); | |
186 | break; | |
187 | case FETCH_COMPLETE: | |
188 | /* finished with mail, queue on done queue */ | |
189 | log_debug3("%s: adding to done queue", a->name); | |
190 | TAILQ_REMOVE(&matchq, mctx, match_entry); | |
191 | TAILQ_INSERT_TAIL(&doneq, mctx, done_entry); | |
192 | break; | |
193 | } | |
194 | ||
195 | return (0); | |
196 | } | |
197 | ||
198 | int | |
199 | run_deliver(struct account *a, struct io *io, int *blocked, const char **cause) | |
200 | { | |
201 | struct match_ctx *mctx; | |
202 | struct deliver_ctx *dctx; | |
203 | struct msg msg; | |
204 | void *buf; | |
205 | size_t len; | |
206 | ||
207 | *blocked = 0; | |
208 | if (TAILQ_EMPTY(&deliverq)) | |
209 | return (0); | |
210 | ||
211 | mctx = TAILQ_FIRST(&deliverq); | |
212 | if (TAILQ_EMPTY(&mctx->dqueue)) { | |
213 | /* delivery done. return to match queue */ | |
214 | log_debug3("%s: returning to match queue", a->name); | |
215 | TAILQ_REMOVE(&deliverq, mctx, deliver_entry); | |
216 | TAILQ_INSERT_TAIL(&matchq, mctx, match_entry); | |
217 | return (0); | |
218 | } | |
219 | ||
220 | /* start the first action */ | |
221 | log_debug3("%s: running deliver queue", a->name); | |
222 | dctx = TAILQ_FIRST(&mctx->dqueue); | |
223 | ||
224 | if (dctx->blocked) { | |
225 | /* check for reply from parent and finish */ | |
226 | if (!privsep_check(io)) { | |
227 | *blocked = 1; | |
228 | return (0); | |
229 | } | |
230 | ||
231 | if (privsep_recv(io, &msg, &buf, &len) != 0) | |
232 | fatalx("child: privsep_recv error"); | |
233 | if (msg.type != MSG_DONE) | |
234 | fatalx("child: unexpected message"); | |
235 | ||
236 | if (finish_action(dctx, &msg, buf, len) != 0) { | |
237 | *cause = "delivery"; | |
238 | return (1); | |
239 | } | |
240 | ||
241 | goto remove; | |
242 | } | |
243 | ||
244 | if (start_action(mctx->io, dctx) != 0) { | |
245 | *cause = "delivery"; | |
246 | return (1); | |
247 | } | |
248 | if (dctx->blocked) { | |
249 | *blocked = 1; | |
250 | return (0); | |
251 | } | |
252 | ||
253 | remove: | |
254 | TAILQ_REMOVE(&mctx->dqueue, dctx, entry); | |
255 | xfree(dctx); | |
256 | return (0); | |
257 | } | |
258 | ||
259 | int | |
260 | run_done(struct account *a, int *dropped, int *kept, const char **cause) | |
261 | { | |
262 | struct match_ctx *mctx; | |
263 | struct mail *m; | |
264 | int error = 0; | |
265 | const char *type; | |
266 | ||
267 | if (TAILQ_EMPTY(&doneq)) | |
268 | return (0); | |
269 | ||
270 | mctx = TAILQ_FIRST(&doneq); | |
271 | m = mctx->mail; | |
272 | log_debug3("%s: running done queue", a->name); | |
273 | ||
274 | TAILQ_REMOVE(&doneq, mctx, done_entry); | |
275 | ARRAY_FREE(&mctx->stack); | |
276 | xfree(mctx); | |
277 | ||
278 | if (mctx->account->fetch->done != NULL) { | |
279 | switch (mctx->mail->decision) { | |
280 | case DECISION_DROP: | |
281 | type = "deleting"; | |
282 | (*dropped)++; | |
283 | break; | |
284 | case DECISION_KEEP: | |
285 | type = "keeping"; | |
286 | (*kept)++; | |
287 | break; | |
288 | default: | |
289 | fatalx("invalid decision"); | |
290 | } | |
291 | log_debug("%s: %s message %u", a->name, type, m->idx); | |
292 | ||
293 | if (a->fetch->done(a, m) != FETCH_SUCCESS) { | |
294 | *cause = type; | |
295 | error = 1; | |
296 | } | |
297 | } | |
298 | ||
299 | mail_destroy(m); | |
300 | xfree(m); | |
301 | ||
302 | return (error); | |
303 | } | |
304 | ||
305 | void | |
306 | flush_queue(struct match_queue *mq) | |
307 | { | |
308 | struct match_ctx *mctx; | |
309 | struct mail *m; | |
310 | ||
311 | while (!TAILQ_EMPTY(mq)) { | |
312 | mctx = TAILQ_FIRST(mq); | |
313 | m = mctx->mail; | |
314 | ||
315 | TAILQ_REMOVE(mq, mctx, done_entry); | |
316 | ARRAY_FREE(&mctx->stack); | |
317 | xfree(mctx); | |
318 | ||
319 | mail_destroy(m); | |
320 | xfree(m); | |
321 | } | |
322 | } | |
323 | ||
324 | u_int | |
325 | queue_length(struct match_queue *mq) | |
326 | { | |
327 | struct match_ctx *mctx; | |
328 | u_int n; | |
329 | ||
330 | n = 0; | |
331 | TAILQ_FOREACH(mctx, mq, entry) | |
332 | n++; | |
333 | ||
334 | return (n); | |
335 | } | |
336 | ||
337 | int | |
338 | fetch_poll(struct account *a, int blocked, struct ios *ios, struct io **rio) | |
339 | { | |
340 | int timeout; | |
341 | char *cause; | |
342 | ||
343 | if (TAILQ_EMPTY(&matchq) && (blocked || TAILQ_EMPTY(&deliverq))) | |
344 | timeout = conf.timeout; | |
345 | else | |
346 | timeout = 0; | |
347 | ||
348 | log_debug3("%s: polling %u fds, timeout=%d", a->name, ARRAY_LENGTH(ios), | |
349 | timeout); | |
350 | switch (io_polln(ios->list, ARRAY_LENGTH(ios), rio, timeout, &cause)) { | |
351 | case 0: | |
352 | log_warnx("%s: connection unexpectedly closed", a->name); | |
353 | return (1); | |
354 | case -1: | |
355 | if (errno == EAGAIN) | |
356 | return (0); | |
357 | log_warnx("%s: %s", a->name, cause); | |
358 | xfree(cause); | |
359 | return (1); | |
360 | } | |
361 | ||
362 | return (0); | |
363 | } | |
364 | ||
365 | int | |
366 | fetch_flush(struct account *a, struct io *pio, int *blocked, int *dropped, | |
367 | int *kept, const char **cause) | |
368 | { | |
369 | while (!TAILQ_EMPTY(&matchq) || !TAILQ_EMPTY(&deliverq)) { | |
370 | if (run_match(a, cause) != 0) | |
371 | return (1); | |
372 | if (run_deliver(a, pio, blocked, cause) != 0) | |
373 | return (1); | |
374 | ||
375 | log_debug3("%s: queue lengths: match %u, deliver %u, done %u; " | |
376 | "blocked=%d", a->name, queue_length(&matchq), | |
377 | queue_length(&deliverq), queue_length(&doneq), *blocked); | |
378 | ||
379 | if (!TAILQ_EMPTY(&deliverq) && *blocked) { | |
380 | pio->flags &= ~IO_NOWAIT; | |
381 | if (!TAILQ_EMPTY(&matchq)) | |
382 | pio->flags |= IO_NOWAIT; | |
383 | switch (io_poll(pio, NULL)) { | |
384 | case 0: | |
385 | fatalx("child: parent socket closed"); | |
386 | case -1: | |
387 | if (errno == EAGAIN) | |
388 | break; | |
389 | fatalx("child: parent socket error"); | |
390 | } | |
391 | } | |
392 | ||
393 | if (run_done(a, dropped, kept, cause) != 0) | |
394 | return (1); | |
395 | } | |
396 | ||
397 | while (!TAILQ_EMPTY(&doneq)) { | |
398 | if (run_done(a, dropped, kept, cause) != 0) | |
399 | return (1); | |
400 | } | |
401 | ||
402 | return (0); | |
403 | } | |
404 | ||
405 | int | |
406 | fetch_account(struct io *pio, struct account *a, struct ios *ios, double tim) | |
407 | { | |
408 | struct mail *m; | |
409 | u_int n, dropped, kept; | |
410 | int error, blocked; | |
411 | const char *cause = NULL; | |
412 | struct match_ctx *mctx; | |
413 | struct io *rio; | |
414 | ||
415 | log_debug2("%s: fetching", a->name); | |
416 | ||
417 | TAILQ_INIT(&matchq); | |
418 | TAILQ_INIT(&deliverq); | |
419 | TAILQ_INIT(&doneq); | |
420 | ||
421 | n = dropped = kept = 0; | |
422 | m = NULL; | |
423 | for (;;) { | |
424 | m = xcalloc(1, sizeof *m); | |
425 | m->body = -1; | |
426 | m->decision = DECISION_DROP; | |
427 | m->done = 0; | |
428 | m->idx = ++a->idx; | |
429 | ||
430 | /* fetch a message */ | |
431 | error = FETCH_AGAIN; | |
432 | rio = NULL; | |
433 | while (error == FETCH_AGAIN) { | |
434 | log_debug3("%s: queue lengths: match %u, deliver %u, " | |
435 | "done %u; blocked=%d", a->name, | |
436 | queue_length(&matchq), queue_length(&deliverq), | |
437 | queue_length(&doneq), blocked); | |
438 | ||
439 | if (rio != pio) { | |
440 | error = a->fetch->fetch(a, m); | |
441 | switch (error) { | |
442 | case FETCH_ERROR: | |
443 | if (rio == pio) | |
444 | fatalx("child: lost parent"); | |
445 | cause = "fetching"; | |
446 | goto out; | |
447 | case FETCH_COMPLETE: | |
448 | goto out; | |
449 | } | |
450 | } | |
451 | if (error == FETCH_AGAIN) { | |
452 | if (fetch_poll(a, blocked, ios, &rio) != 0) | |
453 | goto out; | |
454 | } | |
455 | ||
456 | if (run_match(a, &cause) != 0) | |
457 | goto out; | |
458 | if (run_deliver(a, pio, &blocked, &cause) != 0) | |
459 | goto out; | |
460 | } | |
461 | ||
462 | if (error != FETCH_OVERSIZE && error != FETCH_EMPTY) { | |
463 | trim_from(m); | |
464 | if (m->size == 0) | |
465 | error = FETCH_EMPTY; | |
466 | } | |
467 | ||
468 | switch (error) { | |
469 | case FETCH_EMPTY: | |
470 | log_warnx("%s: empty message", a->name); | |
471 | cause = "fetching"; | |
472 | goto out; | |
473 | case FETCH_OVERSIZE: | |
474 | log_warnx("%s: message too big: %zu bytes (limit %zu)", | |
475 | a->name, m->size, conf.max_size); | |
476 | if (conf.del_big) | |
477 | break; | |
478 | cause = "fetching"; | |
479 | goto out; | |
480 | } | |
481 | ||
482 | log_debug("%s: got message %u: size %zu, body %zd", a->name, | |
483 | m->idx, m->size, m->body); | |
484 | fetch_transform(a, m); | |
485 | ||
486 | /* construct mctx */ | |
487 | mctx = xcalloc(1, sizeof *mctx); | |
488 | mctx->io = pio; | |
489 | mctx->account = a; | |
490 | mctx->mail = m; | |
491 | ARRAY_INIT(&mctx->stack); | |
492 | mctx->rule = TAILQ_FIRST(&conf.rules); | |
493 | mctx->matched = mctx->stopped = 0; | |
494 | TAILQ_INIT(&mctx->dqueue); | |
495 | m = NULL; /* clear m to avoid double-free if out later */ | |
496 | ||
497 | /* and queue it */ | |
498 | log_debug3("%s: adding to match queue", a->name); | |
499 | TAILQ_INSERT_TAIL(&matchq, mctx, match_entry); | |
500 | ||
501 | /* finish up a done mail */ | |
502 | if (run_done(a, &dropped, &kept, &cause) != 0) | |
503 | goto out; | |
504 | ||
505 | if (conf.purge_after == 0 || a->fetch->purge == NULL) | |
506 | continue; | |
507 | ||
508 | n++; | |
509 | if (n >= conf.purge_after) { | |
510 | log_debug("%s: got %u mails, purging", a->name, n); | |
511 | ||
512 | /* | |
513 | * Must empty queues before purge to make sure things | |
514 | * like POP3 indexing don't get ballsed up. | |
515 | */ | |
516 | if (fetch_flush(a, pio, &blocked, &dropped, &kept, | |
517 | &cause) != 0) | |
518 | goto out; | |
519 | ||
520 | ARRAY_FREE(ios); | |
521 | ARRAY_ADD(ios, pio, struct io *); | |
522 | if (a->fetch->purge(a, ios) != FETCH_SUCCESS) { | |
523 | cause = "purging"; | |
524 | goto out; | |
525 | } | |
526 | ||
527 | n = 0; | |
528 | } | |
529 | } | |
530 | ||
531 | out: | |
532 | if (m != NULL) { | |
533 | mail_destroy(m); | |
534 | xfree(m); | |
535 | } | |
536 | ||
537 | if (cause == NULL) | |
538 | fetch_flush(a, pio, &blocked, &dropped, &kept, &cause); | |
539 | if (cause != NULL) { | |
540 | flush_queue(&matchq); | |
541 | flush_queue(&deliverq); | |
542 | flush_queue(&doneq); | |
543 | ||
544 | log_warnx("%s: %s error. aborted", a->name, cause); | |
545 | } | |
546 | ||
547 | tim = get_time() - tim; | |
548 | n = dropped + kept; | |
549 | if (n > 0) { | |
550 | log_info("%s: %u messages processed (%u kept) in %.3f seconds " | |
551 | "(average %.3f)", a->name, n, kept, tim, tim / n); | |
552 | } else { | |
553 | log_info("%s: %u messages processed in %.3f seconds", | |
554 | a->name, n, tim); | |
555 | } | |
556 | ||
557 | return (cause != NULL); | |
558 | } | |
559 | ||
560 | int | |
561 | fetch_transform(struct account *a, struct mail *m) | |
562 | { | |
563 | char *hdr, rtm[64], *rnm; | |
564 | u_int lines; | |
565 | size_t len; | |
566 | int error; | |
567 | ||
568 | hdr = find_header(m, "message-id", &len, 1); | |
569 | if (hdr == NULL || len == 0 || len > INT_MAX) | |
570 | log_debug2("%s: message-id not found", a->name); | |
571 | else { | |
572 | log_debug2("%s: message-id is: %.*s", a->name, (int) len, hdr); | |
573 | add_tag(&m->tags, "message_id", "%.*s", (int) len, hdr); | |
574 | } | |
575 | ||
576 | /* | |
577 | * Insert received header. | |
578 | * | |
579 | * No header line must exceed 998 bytes. Limiting the user-supplied | |
580 | * stuff to 900 bytes gives plenty of space for the other stuff, and if | |
581 | * it gets truncated, who cares? | |
582 | */ | |
583 | if (!conf.no_received) { | |
584 | error = 1; | |
585 | if (rfc822_time(time(NULL), rtm, sizeof rtm) != NULL) { | |
586 | rnm = conf.info.fqdn; | |
587 | if (rnm == NULL) | |
588 | rnm = conf.info.host; | |
589 | ||
590 | error = insert_header(m, "received", | |
591 | "Received: by %.450s (%s " BUILD ", " | |
592 | "account \"%.450s\");\n\t%s", | |
593 | rnm, __progname, a->name, rtm); | |
594 | } | |
595 | if (error != 0) | |
596 | log_debug3("%s: couldn't add received header", a->name); | |
597 | } | |
598 | ||
599 | /* fill wrapped line list */ | |
600 | lines = fill_wrapped(m); | |
601 | log_debug2("%s: found %u wrapped lines", a->name, lines); | |
602 | ||
603 | return (FETCH_SUCCESS); | |
604 | } | |
605 | ||
606 | int | |
607 | fetch_rule(struct match_ctx *mctx, const char **cause) | |
608 | { | |
609 | struct account *a = mctx->account; | |
610 | struct strings *aa; | |
611 | struct mail *m = mctx->mail; | |
612 | struct rule *r = mctx->rule; | |
613 | u_int i; | |
614 | int error; | |
615 | char *tkey, *tvalue; | |
616 | ||
617 | /* matching finished */ | |
618 | if (m->done) { | |
619 | if (conf.keep_all || a->keep) | |
620 | m->decision = DECISION_KEEP; | |
621 | return (FETCH_COMPLETE); | |
622 | } | |
623 | ||
624 | /* end of ruleset reached */ | |
625 | if (r == NULL) { | |
626 | switch (conf.impl_act) { | |
627 | case DECISION_NONE: | |
628 | log_warnx("%s: reached end of ruleset. no " | |
629 | "unmatched-mail option; keeping mail", a->name); | |
630 | m->decision = DECISION_KEEP; | |
631 | break; | |
632 | case DECISION_KEEP: | |
633 | log_debug2("%s: reached end of ruleset. keeping mail", | |
634 | a->name); | |
635 | m->decision = DECISION_KEEP; | |
636 | break; | |
637 | case DECISION_DROP: | |
638 | log_debug2("%s: reached end of ruleset. dropping mail", | |
639 | a->name); | |
640 | m->decision = DECISION_DROP; | |
641 | break; | |
642 | } | |
643 | m->done = 1; | |
644 | return (FETCH_SUCCESS); | |
645 | } | |
646 | ||
647 | mctx->rule = TAILQ_NEXT(mctx->rule, entry); | |
648 | while (mctx->rule == NULL) { | |
649 | if (ARRAY_EMPTY(&mctx->stack)) | |
650 | break; | |
651 | mctx->rule = ARRAY_LAST(&mctx->stack, struct rule *); | |
652 | mctx->rule = TAILQ_NEXT(mctx->rule, entry); | |
653 | ARRAY_TRUNC(&mctx->stack, 1, struct rule *); | |
654 | } | |
655 | ||
656 | aa = r->accounts; | |
657 | if (!ARRAY_EMPTY(aa)) { | |
658 | for (i = 0; i < ARRAY_LENGTH(aa); i++) { | |
659 | if (name_match(ARRAY_ITEM(aa, i, char *), a->name)) | |
660 | break; | |
661 | } | |
662 | if (i == ARRAY_LENGTH(aa)) | |
663 | return (FETCH_SUCCESS); | |
664 | } | |
665 | ||
666 | /* match all the regexps */ | |
667 | switch (r->type) { | |
668 | case RULE_EXPRESSION: | |
669 | /* combine wrapped lines */ | |
670 | set_wrapped(m, ' '); | |
671 | ||
672 | /* perform the expression */ | |
673 | if ((error = do_expr(r, mctx)) == -1) { | |
674 | *cause = "matching"; | |
675 | return (FETCH_ERROR); | |
676 | } | |
677 | ||
678 | /* continue if no match */ | |
679 | if (!error) | |
680 | return (FETCH_SUCCESS); | |
681 | break; | |
682 | case RULE_ALL: | |
683 | break; | |
684 | } | |
685 | ||
686 | /* reset wrapped lines */ | |
687 | set_wrapped(m, '\n'); | |
688 | ||
689 | /* report rule number */ | |
690 | if (TAILQ_EMPTY(&r->rules)) | |
691 | log_debug2("%s: matched to rule %u", a->name, r->idx); | |
692 | else | |
693 | log_debug2("%s: matched to rule %u (nested)", a->name, r->idx); | |
694 | ||
695 | /* deal with nested rules */ | |
696 | if (!TAILQ_EMPTY(&r->rules)) { | |
697 | log_debug2("%s: entering nested rules", a->name); | |
698 | ARRAY_ADD(&mctx->stack, r, struct rule *); | |
699 | mctx->rule = TAILQ_FIRST(&r->rules); | |
700 | return (FETCH_SUCCESS); | |
701 | } | |
702 | ||
703 | /* tag mail if needed */ | |
704 | if (r->key.str != NULL) { | |
705 | tkey = replacestr(&r->key, m->tags, m, &m->rml); | |
706 | tvalue = replacestr(&r->value, m->tags, m, &m->rml); | |
707 | ||
708 | if (tkey != NULL && *tkey != '\0' && tvalue != NULL) { | |
709 | log_debug2("%s: tagging message: %s (%s)", | |
710 | a->name, tkey, tvalue); | |
711 | add_tag(&m->tags, tkey, "%s", tvalue); | |
712 | } | |
713 | ||
714 | if (tkey != NULL) | |
715 | xfree(tkey); | |
716 | if (tvalue != NULL) | |
717 | xfree(tvalue); | |
718 | } | |
719 | ||
720 | /* if this rule is marked as stop, mark the mail as done */ | |
721 | if (r->stop) | |
722 | m->done = 1; | |
723 | ||
724 | /* handle delivery */ | |
725 | if (r->actions != NULL) { | |
726 | log_debug2("%s: delivering message", a->name); | |
727 | mctx->matched = 1; | |
728 | if (do_deliver(r, mctx) != 0) { | |
729 | *cause = "delivery"; | |
730 | return (FETCH_ERROR); | |
731 | } | |
732 | return (FETCH_AGAIN); | |
733 | } | |
734 | ||
735 | return (FETCH_SUCCESS); | |
736 | } | |
737 | ||
738 | int | |
739 | do_expr(struct rule *r, struct match_ctx *mctx) | |
740 | { | |
741 | int fres, cres; | |
742 | struct expritem *ei; | |
743 | char desc[DESCBUFSIZE]; | |
744 | ||
745 | fres = 0; | |
746 | TAILQ_FOREACH(ei, r->expr, entry) { | |
747 | cres = ei->match->match(mctx, ei); | |
748 | if (cres == MATCH_ERROR) | |
749 | return (-1); | |
750 | cres = cres == MATCH_TRUE; | |
751 | if (ei->inverted) | |
752 | cres = !cres; | |
753 | switch (ei->op) { | |
754 | case OP_NONE: | |
755 | case OP_OR: | |
756 | fres = fres || cres; | |
757 | break; | |
758 | case OP_AND: | |
759 | fres = fres && cres; | |
760 | break; | |
761 | } | |
762 | ||
763 | ei->match->desc(ei, desc, sizeof desc); | |
764 | log_debug2("%s: tried %s%s, got %d", mctx->account->name, | |
765 | ei->inverted ? "not " : "", desc, cres); | |
766 | } | |
767 | ||
768 | return (fres); | |
769 | } | |
770 | ||
771 | int | |
772 | do_deliver(struct rule *r, struct match_ctx *mctx) | |
773 | { | |
774 | struct account *a = mctx->account; | |
775 | struct mail *m = mctx->mail; | |
776 | struct action *t; | |
777 | struct actions *ta; | |
778 | u_int i, j, k; | |
779 | char *s; | |
780 | struct replstr *rs; | |
781 | struct deliver_ctx *dctx; | |
782 | struct strings *users; | |
783 | int should_free; | |
784 | ||
785 | for (i = 0; i < ARRAY_LENGTH(r->actions); i++) { | |
786 | rs = &ARRAY_ITEM(r->actions, i, struct replstr); | |
787 | s = replacestr(rs, m->tags, m, &m->rml); | |
788 | ||
789 | log_debug2("%s: looking for actions matching: %s", a->name, s); | |
790 | ta = match_actions(s); | |
791 | if (ARRAY_EMPTY(ta)) | |
792 | goto empty; | |
793 | xfree(s); | |
794 | ||
795 | log_debug2("%s: found %u actions", a->name, ARRAY_LENGTH(ta)); | |
796 | for (j = 0; j < ARRAY_LENGTH(ta); j++) { | |
797 | t = ARRAY_ITEM(ta, j, struct action *); | |
798 | users = get_users(mctx, r, t, &should_free); | |
799 | ||
800 | for (k = 0; k < ARRAY_LENGTH(users); k++) { | |
801 | dctx = xmalloc(sizeof *dctx); | |
802 | dctx->action = t; | |
803 | dctx->account = a; | |
804 | dctx->rule = r; | |
805 | dctx->mail = m; | |
806 | dctx->uid = ARRAY_ITEM(users, i, uid_t); | |
807 | dctx->blocked = 0; | |
808 | ||
809 | TAILQ_INSERT_TAIL(&mctx->dqueue, dctx, entry); | |
810 | } | |
811 | ||
812 | if (should_free) | |
813 | ARRAY_FREEALL(users); | |
814 | } | |
815 | ||
816 | ARRAY_FREEALL(ta); | |
817 | } | |
818 | ||
819 | return (0); | |
820 | ||
821 | empty: | |
822 | xfree(s); | |
823 | ARRAY_FREEALL(ta); | |
824 | log_warnx("%s: no actions matching: %s (%s)", a->name, s, rs->str); | |
825 | return (1); | |
826 | } | |
827 | ||
828 | struct strings * | |
829 | get_users(struct match_ctx *mctx, struct rule *r, struct action *t, | |
830 | int *should_free) | |
831 | { | |
832 | struct account *a = mctx->account; | |
833 | struct mail *m = mctx->mail; | |
834 | struct strings *users; | |
835 | ||
836 | *should_free = 0; | |
837 | users = NULL; | |
838 | if (r->find_uid) { /* rule comes first */ | |
839 | *should_free = 1; | |
840 | users = find_users(m); | |
841 | } else if (r->users != NULL) { | |
842 | *should_free = 0; | |
843 | users = r->users; | |
844 | } else if (t->find_uid) { /* then action */ | |
845 | *should_free = 1; | |
846 | users = find_users(m); | |
847 | } else if (t->users != NULL) { | |
848 | *should_free = 0; | |
849 | users = t->users; | |
850 | } else if (a->find_uid) { /* then account */ | |
851 | *should_free = 1; | |
852 | users = find_users(m); | |
853 | } else if (a->users != NULL) { | |
854 | *should_free = 0; | |
855 | users = a->users; | |
856 | } | |
857 | if (users == NULL) { | |
858 | *should_free = 1; | |
859 | users = xmalloc(sizeof *users); | |
860 | ARRAY_INIT(users); | |
861 | ARRAY_ADD(users, conf.def_user, uid_t); | |
862 | } | |
863 | ||
864 | return (users); | |
865 | } | |
866 | ||
867 | int | |
868 | start_action(struct io *io, struct deliver_ctx *dctx) | |
869 | { | |
870 | struct account *a = dctx->account; | |
871 | struct action *t = dctx->action; | |
872 | struct mail *m = dctx->mail; | |
873 | struct msg msg; | |
874 | ||
875 | if (t->deliver->deliver == NULL) | |
876 | return (0); | |
877 | ||
878 | log_debug2("%s: running action %s as user %lu", a->name, t->name, | |
879 | (u_long) dctx->uid); | |
880 | add_tag(&m->tags, "action", "%s", t->name); | |
881 | ||
882 | /* just deliver now for in-child delivery */ | |
883 | if (t->deliver->type == DELIVER_INCHILD) { | |
884 | dctx->blocked = 0; | |
885 | if (t->deliver->deliver(dctx, t) != DELIVER_SUCCESS) | |
886 | return (1); | |
887 | return (0); | |
888 | } | |
889 | ||
890 | memset(&msg, 0, sizeof msg); | |
891 | msg.type = MSG_ACTION; | |
892 | ||
893 | msg.data.account = a; | |
894 | msg.data.action = t; | |
895 | msg.data.uid = dctx->uid; | |
896 | ||
897 | mail_send(m, &msg); | |
898 | ||
899 | log_debug3("%s: sending action to parent", a->name); | |
900 | if (privsep_send(io, &msg, m->tags, STRB_SIZE(m->tags)) != 0) | |
901 | fatalx("child: privsep_send error"); | |
902 | dctx->blocked = 1; | |
903 | ||
904 | return (0); | |
905 | } | |
906 | ||
907 | int | |
908 | finish_action(struct deliver_ctx *dctx, struct msg *msg, void *buf, size_t len) | |
909 | { | |
910 | struct account *a = dctx->account; | |
911 | struct action *t = dctx->action; | |
912 | struct mail *m = dctx->mail; | |
913 | u_int lines; | |
914 | ||
915 | if (buf == NULL || len == 0) | |
916 | fatalx("child: bad tags"); | |
917 | strb_destroy(&m->tags); | |
918 | m->tags = buf; | |
919 | update_tags(&m->tags); | |
920 | ||
921 | if (msg->data.error != 0) | |
922 | return (1); | |
923 | ||
924 | if (t->deliver->type != DELIVER_WRBACK) | |
925 | return (0); | |
926 | ||
927 | mail_receive(m, msg); | |
928 | log_debug2("%s: received modified mail: size %zu, body %zd", a->name, | |
929 | m->size, m->body); | |
930 | ||
931 | /* trim from line */ | |
932 | trim_from(m); | |
933 | ||
934 | /* and recreate the wrapped array */ | |
935 | lines = fill_wrapped(m); | |
936 | log_debug2("%s: found %u wrapped lines", a->name, lines); | |
937 | ||
938 | return (0); | |
939 | } |
16 | 16 | */ |
17 | 17 | |
18 | 18 | #include <sys/types.h> |
19 | #include <sys/stat.h> | |
20 | #include <sys/time.h> | |
21 | #include <sys/wait.h> | |
19 | #include <sys/socket.h> | |
22 | 20 | |
23 | #include <fcntl.h> | |
24 | #include <fnmatch.h> | |
25 | #include <limits.h> | |
26 | #include <string.h> | |
27 | #include <time.h> | |
28 | 21 | #include <unistd.h> |
29 | 22 | |
30 | 23 | #include "fdm.h" |
31 | #include "deliver.h" | |
32 | #include "fetch.h" | |
33 | #include "match.h" | |
34 | ||
35 | int poll_account(struct io *, struct account *); | |
36 | int fetch_account(struct io *, struct account *, double); | |
37 | int fetch_transform(struct account *, struct mail *); | |
38 | ||
39 | int run_done(struct match_queue *, int *, int *, const char **); | |
40 | void flush_done(struct match_queue *); | |
41 | int run_active(struct match_queue *, struct match_queue *, const char **); | |
42 | void flush_active(struct match_queue *); | |
43 | ||
44 | int fetch_rule(struct match_ctx *, const char **); | |
45 | ||
46 | int do_expr(struct rule *, struct match_ctx *); | |
47 | int do_deliver(struct rule *, struct match_ctx *); | |
48 | int do_action(struct rule *, struct match_ctx *, struct action *); | |
49 | int do_rules(struct match_ctx *, struct rules *, const char **); | |
50 | 24 | |
51 | 25 | void child_sighandler(int); |
52 | 26 | |
98 | 72 | _exit(status); |
99 | 73 | } |
100 | 74 | |
101 | int | |
102 | do_child(int fd, enum fdmop op, struct account *a) | |
75 | struct child * | |
76 | child_start(struct children *children, uid_t uid, int (*start)(struct child *, | |
77 | struct io *), int (*msg)(struct child *, struct msg *, void *, size_t), | |
78 | void *data) | |
103 | 79 | { |
80 | struct child *child, *childp; | |
81 | int fds[2], n; | |
82 | u_int i; | |
104 | 83 | struct io *io; |
105 | struct msg msg; | |
106 | int error = 1; | |
107 | double tim; | |
108 | 84 | |
109 | #ifdef DEBUG | |
110 | xmalloc_clear(); | |
111 | COUNTFDS(a->name); | |
85 | if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, fds) != 0) | |
86 | fatal("socketpair"); | |
87 | ||
88 | child = xcalloc(1, sizeof *child); | |
89 | child->io = io_create(fds[0], NULL, IO_CRLF, INFTIM); | |
90 | child->data = data; | |
91 | child->msg = msg; | |
92 | ||
93 | if ((child->pid = child_fork()) == 0) { | |
94 | for (i = 0; i < ARRAY_LENGTH(children); i++) { | |
95 | childp = ARRAY_ITEM(children, i, struct child *); | |
96 | io_close(childp->io); | |
97 | io_free(childp->io); | |
98 | xfree(childp); | |
99 | } | |
100 | io_close(child->io); | |
101 | io_free(child->io); | |
102 | ||
103 | if (geteuid() == 0) { | |
104 | if (dropto(uid) != 0) | |
105 | fatal("dropto"); | |
106 | } | |
107 | ||
108 | io = io_create(fds[1], NULL, IO_LF, INFTIM); | |
109 | n = start(child, io); | |
110 | #ifdef PROFILE | |
111 | /* | |
112 | * We want to use _exit rather than exit in the child process, | |
113 | * and it doesn't run atexit handlers, so run _mcleanup | |
114 | * manually to force the profiler to dump its statistics. | |
115 | * | |
116 | * This is icky but it works, and is only for profiling. | |
117 | */ | |
118 | extern void _mcleanup(void); | |
119 | _mcleanup(); | |
112 | 120 | #endif |
121 | child_exit(n); | |
122 | } | |
123 | close(fds[1]); | |
113 | 124 | |
114 | io = io_create(fd, NULL, IO_LF, INFTIM); | |
115 | log_debug2("%s: started, pid %ld", a->name, (long) getpid()); | |
116 | ||
117 | if (geteuid() != 0) { | |
118 | log_debug2("%s: not root. not dropping privileges", a->name); | |
119 | } else { | |
120 | log_debug2("%s: changing to user %lu", a->name, | |
121 | (u_long) conf.child_uid); | |
122 | if (dropto(conf.child_uid) != 0) | |
123 | fatal("dropto"); | |
124 | } | |
125 | #ifndef NO_SETPROCTITLE | |
126 | setproctitle("child: %s", a->name); | |
127 | #endif | |
128 | ||
129 | if (op == FDMOP_POLL && a->fetch->poll == NULL) { | |
130 | log_info("%s: polling not supported", a->name); | |
131 | goto out; | |
132 | } else if (op == FDMOP_FETCH && a->fetch->fetch == NULL) { | |
133 | log_info("%s: fetching not supported", a->name); | |
134 | goto out; | |
135 | } | |
136 | tim = get_time(); | |
137 | ||
138 | /* start fetch */ | |
139 | if (a->fetch->start != NULL && a->fetch->start(a) != FETCH_SUCCESS) { | |
140 | log_warnx("%s: start error. aborting", a->name); | |
141 | goto out; | |
142 | } | |
143 | ||
144 | /* process fetch */ | |
145 | log_debug2("%s: started processing", a->name); | |
146 | switch (op) { | |
147 | case FDMOP_POLL: | |
148 | error = poll_account(io, a); | |
149 | break; | |
150 | case FDMOP_FETCH: | |
151 | error = fetch_account(io, a, tim); | |
152 | break; | |
153 | default: | |
154 | fatalx("child: unexpected command"); | |
155 | } | |
156 | log_debug2("%s: finished processing. exiting", a->name); | |
157 | ||
158 | out: | |
159 | /* finish fetch */ | |
160 | if (a->fetch->finish != NULL && a->fetch->finish(a) != FETCH_SUCCESS) | |
161 | error = 1; | |
162 | ||
163 | memset(&msg, 0, sizeof msg); | |
164 | msg.type = MSG_EXIT; | |
165 | log_debug3("%s: sending exit message to parent", a->name); | |
166 | if (privsep_send(io, &msg, NULL, 0) != 0) | |
167 | fatalx("child: privsep_send error"); | |
168 | log_debug3("%s: waiting for exit message from parent", a->name); | |
169 | if (privsep_recv(io, &msg, NULL, 0) != 0) | |
170 | fatalx("child: privsep_recv error"); | |
171 | if (msg.type != MSG_EXIT) | |
172 | fatalx("child: unexpected message"); | |
173 | ||
174 | io_close(io); | |
175 | io_free(io); | |
176 | ||
177 | #ifdef DEBUG | |
178 | COUNTFDS(a->name); | |
179 | xmalloc_report(a->name); | |
180 | #endif | |
181 | ||
182 | return (error); | |
125 | ARRAY_ADD(children, child, struct child *); | |
126 | return (child); | |
183 | 127 | } |
184 | 128 | |
185 | int | |
186 | poll_account(unused struct io *io, struct account *a) | |
187 | { | |
188 | u_int n; | |
189 | 129 | |
190 | log_debug2("%s: polling", a->name); | |
191 | ||
192 | if (a->fetch->poll(a, &n) == FETCH_ERROR) { | |
193 | log_warnx("%s: polling error. aborted", a->name); | |
194 | return (1); | |
195 | } | |
196 | ||
197 | log_info("%s: %u messages found", a->name, n); | |
198 | ||
199 | return (0); | |
200 | } | |
201 | ||
202 | int | |
203 | run_done(struct match_queue *dq, int *dropped, int *kept, const char **cause) | |
204 | { | |
205 | struct match_ctx *mctx; | |
206 | struct account *a; | |
207 | struct mail *m; | |
208 | int error = 0; | |
209 | const char *type; | |
210 | ||
211 | if (TAILQ_EMPTY(dq)) | |
212 | return (0); | |
213 | ||
214 | mctx = TAILQ_FIRST(dq); | |
215 | a = mctx->account; | |
216 | m = mctx->mail; | |
217 | log_debug3("%s: running done queue", a->name); | |
218 | ||
219 | TAILQ_REMOVE(dq, mctx, entry); | |
220 | ARRAY_FREE(&mctx->stack); | |
221 | xfree(mctx); | |
222 | ||
223 | if (mctx->account->fetch->done != NULL) { | |
224 | switch (mctx->mail->decision) { | |
225 | case DECISION_DROP: | |
226 | type = "deleting"; | |
227 | (*dropped)++; | |
228 | break; | |
229 | case DECISION_KEEP: | |
230 | type = "keeping"; | |
231 | (*kept)++; | |
232 | break; | |
233 | default: | |
234 | fatalx("invalid decision"); | |
235 | } | |
236 | log_debug2("%s: %s message", a->name, type); | |
237 | ||
238 | if (a->fetch->done(a, m) != FETCH_SUCCESS) { | |
239 | *cause = type; | |
240 | error = 1; | |
241 | } | |
242 | } | |
243 | ||
244 | mail_destroy(m); | |
245 | xfree(m); | |
246 | ||
247 | return (error); | |
248 | } | |
249 | ||
250 | void | |
251 | flush_done(struct match_queue *dq) | |
252 | { | |
253 | struct match_ctx *mctx; | |
254 | struct mail *m; | |
255 | ||
256 | while (!TAILQ_EMPTY(dq)) { | |
257 | mctx = TAILQ_FIRST(dq); | |
258 | m = mctx->mail; | |
259 | ||
260 | TAILQ_REMOVE(dq, mctx, entry); | |
261 | ARRAY_FREE(&mctx->stack); | |
262 | xfree(mctx); | |
263 | ||
264 | mail_destroy(m); | |
265 | xfree(m); | |
266 | } | |
267 | } | |
268 | ||
269 | int | |
270 | run_active(struct match_queue *aq, struct match_queue *dq, const char **cause) | |
271 | { | |
272 | struct match_ctx *mctx; | |
273 | struct account *a; | |
274 | ||
275 | if (TAILQ_EMPTY(aq)) | |
276 | return (0); | |
277 | ||
278 | mctx = TAILQ_FIRST(aq); | |
279 | a = mctx->account; | |
280 | log_debug3("%s: running active queue", a->name); | |
281 | ||
282 | switch (fetch_rule(mctx, cause)) { | |
283 | case FETCH_ERROR: | |
284 | return (1); | |
285 | case FETCH_COMPLETE: | |
286 | TAILQ_REMOVE(aq, mctx, entry); | |
287 | TAILQ_INSERT_TAIL(dq, mctx, entry); | |
288 | break; | |
289 | } | |
290 | ||
291 | return (0); | |
292 | } | |
293 | ||
294 | void | |
295 | flush_active(struct match_queue *aq) | |
296 | { | |
297 | struct match_ctx *mctx; | |
298 | struct mail *m; | |
299 | ||
300 | while (!TAILQ_EMPTY(aq)) { | |
301 | mctx = TAILQ_FIRST(aq); | |
302 | m = mctx->mail; | |
303 | ||
304 | TAILQ_REMOVE(aq, mctx, entry); | |
305 | ARRAY_FREE(&mctx->stack); | |
306 | xfree(mctx); | |
307 | ||
308 | mail_destroy(m); | |
309 | xfree(m); | |
310 | } | |
311 | } | |
312 | ||
313 | int | |
314 | fetch_account(struct io *io, struct account *a, double tim) | |
315 | { | |
316 | struct mail *m; | |
317 | u_int n, dropped, kept; | |
318 | int error; | |
319 | const char *cause = NULL; | |
320 | struct match_queue activeq; | |
321 | struct match_queue doneq; | |
322 | struct match_ctx *mctx; | |
323 | ||
324 | log_debug2("%s: fetching", a->name); | |
325 | ||
326 | TAILQ_INIT(&activeq); | |
327 | TAILQ_INIT(&doneq); | |
328 | ||
329 | n = dropped = kept = 0; | |
330 | for (;;) { | |
331 | m = xcalloc(1, sizeof *m); | |
332 | m->body = -1; | |
333 | m->decision = DECISION_DROP; | |
334 | ||
335 | /* fetch a message */ | |
336 | error = FETCH_AGAIN; | |
337 | while (error == FETCH_AGAIN) { | |
338 | if (TAILQ_EMPTY(&activeq)) { | |
339 | log_debug3("%s: queue empty", a->name); | |
340 | error = a->fetch->fetch(a, m, 0); | |
341 | } else { | |
342 | log_debug3("%s: queue non-empty", a->name); | |
343 | error = a->fetch->fetch(a, m, FETCH_NOWAIT); | |
344 | } | |
345 | switch (error) { | |
346 | case FETCH_ERROR: | |
347 | cause = "fetching"; | |
348 | goto out; | |
349 | case FETCH_COMPLETE: | |
350 | goto out; | |
351 | } | |
352 | ||
353 | if (run_active(&activeq, &doneq, &cause) != 0) | |
354 | goto out; | |
355 | } | |
356 | ||
357 | if (error != FETCH_OVERSIZE && error != FETCH_EMPTY) { | |
358 | trim_from(m); | |
359 | if (m->size == 0) | |
360 | error = FETCH_EMPTY; | |
361 | } | |
362 | ||
363 | switch (error) { | |
364 | case FETCH_EMPTY: | |
365 | log_warnx("%s: empty message", a->name); | |
366 | cause = "fetching"; | |
367 | goto out; | |
368 | case FETCH_OVERSIZE: | |
369 | log_warnx("%s: message too big: %zu bytes (limit %zu)", | |
370 | a->name, m->size, conf.max_size); | |
371 | if (conf.del_big) | |
372 | break; | |
373 | cause = "fetching"; | |
374 | goto out; | |
375 | } | |
376 | ||
377 | log_debug("%s: got message: size %zu, body %zd", a->name, | |
378 | m->size, m->body); | |
379 | fetch_transform(a, m); | |
380 | ||
381 | /* construct mctx */ | |
382 | mctx = xcalloc(1, sizeof *mctx); | |
383 | mctx->io = io; | |
384 | mctx->account = a; | |
385 | mctx->mail = m; | |
386 | ARRAY_INIT(&mctx->stack); | |
387 | mctx->rule = TAILQ_FIRST(&conf.rules); | |
388 | mctx->matched = mctx->stopped = 0; | |
389 | m = NULL; /* clear m to avoid double-free if out later */ | |
390 | ||
391 | /* and queue it */ | |
392 | TAILQ_INSERT_TAIL(&activeq, mctx, entry); | |
393 | ||
394 | /* finish up a done mail */ | |
395 | if (run_done(&doneq, &dropped, &kept, &cause) != 0) | |
396 | goto out; | |
397 | ||
398 | if (conf.purge_after > 0 && a->fetch->purge != NULL) { | |
399 | n++; | |
400 | if (n >= conf.purge_after) { | |
401 | log_debug("%s: %u mails, purging", a->name, n); | |
402 | ||
403 | /* | |
404 | * Must empty queues before purge to make sure | |
405 | * eg POP3 indexing doesn't get ballsed up. | |
406 | */ | |
407 | while (!TAILQ_EMPTY(&activeq)) { | |
408 | if (run_active(&activeq, &doneq, | |
409 | &cause) != 0) | |
410 | break; | |
411 | } | |
412 | while (!TAILQ_EMPTY(&doneq)) { | |
413 | if (run_done(&doneq, &dropped, &kept, | |
414 | &cause) != 0) | |
415 | break; | |
416 | } | |
417 | ||
418 | if (a->fetch->purge(a) != FETCH_SUCCESS) { | |
419 | cause = "purging"; | |
420 | goto out; | |
421 | } | |
422 | n = 0; | |
423 | } | |
424 | } | |
425 | } | |
426 | ||
427 | out: | |
428 | if (m != NULL) { | |
429 | mail_destroy(m); | |
430 | xfree(m); | |
431 | } | |
432 | if (cause == NULL) { | |
433 | while (!TAILQ_EMPTY(&activeq)) { | |
434 | if (run_active(&activeq, &doneq, &cause) != 0) | |
435 | break; | |
436 | } | |
437 | while (!TAILQ_EMPTY(&doneq)) { | |
438 | if (run_done(&doneq, &dropped, &kept, &cause) != 0) | |
439 | break; | |
440 | } | |
441 | } | |
442 | if (cause != NULL) { | |
443 | flush_active(&activeq); | |
444 | flush_done(&doneq); | |
445 | } | |
446 | if (cause != NULL) | |
447 | log_warnx("%s: %s error. aborted", a->name, cause); | |
448 | ||
449 | tim = get_time() - tim; | |
450 | n = dropped + kept; | |
451 | if (n > 0) { | |
452 | log_info("%s: %u messages processed (%u kept) in %.3f seconds " | |
453 | "(average %.3f)", a->name, n, kept, tim, tim / n); | |
454 | } else { | |
455 | log_info("%s: %u messages processed in %.3f seconds", | |
456 | a->name, n, tim); | |
457 | } | |
458 | ||
459 | return (cause != NULL); | |
460 | } | |
461 | ||
462 | int | |
463 | fetch_transform(struct account *a, struct mail *m) | |
464 | { | |
465 | char *hdr, rtm[64], *rnm; | |
466 | u_int lines; | |
467 | size_t len; | |
468 | int error; | |
469 | ||
470 | hdr = find_header(m, "message-id", &len, 1); | |
471 | if (hdr == NULL || len == 0 || len > INT_MAX) | |
472 | log_debug2("%s: message-id not found", a->name); | |
473 | else { | |
474 | log_debug2("%s: message-id is: %.*s", a->name, (int) len, hdr); | |
475 | add_tag(&m->tags, "message_id", "%.*s", (int) len, hdr); | |
476 | } | |
477 | ||
478 | /* | |
479 | * Insert received header. | |
480 | * | |
481 | * No header line must exceed 998 bytes. Limiting the user-supplied | |
482 | * stuff to 900 bytes gives plenty of space for the other stuff, and if | |
483 | * it gets truncated, who cares? | |
484 | */ | |
485 | if (!conf.no_received) { | |
486 | error = 1; | |
487 | if (rfc822_time(time(NULL), rtm, sizeof rtm) != NULL) { | |
488 | rnm = conf.info.fqdn; | |
489 | if (rnm == NULL) | |
490 | rnm = conf.info.host; | |
491 | ||
492 | error = insert_header(m, "received", | |
493 | "Received: by %.450s (%s " BUILD ", " | |
494 | "account \"%.450s\");\n\t%s", | |
495 | rnm, __progname, a->name, rtm); | |
496 | } | |
497 | if (error != 0) | |
498 | log_debug3("%s: couldn't add received header", a->name); | |
499 | } | |
500 | ||
501 | /* fill wrapped line list */ | |
502 | lines = fill_wrapped(m); | |
503 | log_debug2("%s: found %u wrapped lines", a->name, lines); | |
504 | ||
505 | return (FETCH_SUCCESS); | |
506 | } | |
507 | ||
508 | int | |
509 | fetch_rule(struct match_ctx *mctx, const char **cause) | |
510 | { | |
511 | struct account *a = mctx->account; | |
512 | struct strings *aa; | |
513 | struct mail *m = mctx->mail; | |
514 | struct rule *r = mctx->rule; | |
515 | u_int i; | |
516 | int error; | |
517 | char *tkey, *tvalue; | |
518 | ||
519 | if (r == NULL) { | |
520 | switch (conf.impl_act) { | |
521 | case DECISION_NONE: | |
522 | log_warnx("%s: reached end of ruleset. no " | |
523 | "unmatched-mail option; keeping mail", a->name); | |
524 | m->decision = DECISION_KEEP; | |
525 | break; | |
526 | case DECISION_KEEP: | |
527 | log_debug2("%s: reached end of ruleset. keeping mail", | |
528 | a->name); | |
529 | m->decision = DECISION_KEEP; | |
530 | break; | |
531 | case DECISION_DROP: | |
532 | log_debug2("%s: reached end of ruleset. dropping mail", | |
533 | a->name); | |
534 | m->decision = DECISION_DROP; | |
535 | break; | |
536 | } | |
537 | goto done; | |
538 | } | |
539 | ||
540 | mctx->rule = TAILQ_NEXT(mctx->rule, entry); | |
541 | while (mctx->rule == NULL) { | |
542 | if (ARRAY_EMPTY(&mctx->stack)) | |
543 | break; | |
544 | mctx->rule = ARRAY_LAST(&mctx->stack, struct rule *); | |
545 | mctx->rule = TAILQ_NEXT(mctx->rule, entry); | |
546 | ARRAY_TRUNC(&mctx->stack, 1, struct rule *); | |
547 | } | |
548 | ||
549 | aa = r->accounts; | |
550 | if (!ARRAY_EMPTY(aa)) { | |
551 | for (i = 0; i < ARRAY_LENGTH(aa); i++) { | |
552 | if (name_match(ARRAY_ITEM(aa, i, char *), a->name)) | |
553 | break; | |
554 | } | |
555 | if (i == ARRAY_LENGTH(aa)) | |
556 | return (FETCH_SUCCESS); | |
557 | } | |
558 | ||
559 | /* match all the regexps */ | |
560 | switch (r->type) { | |
561 | case RULE_EXPRESSION: | |
562 | /* combine wrapped lines */ | |
563 | set_wrapped(m, ' '); | |
564 | ||
565 | /* perform the expression */ | |
566 | if ((error = do_expr(r, mctx)) == -1) { | |
567 | *cause = "matching"; | |
568 | return (FETCH_ERROR); | |
569 | } | |
570 | ||
571 | /* continue if no match */ | |
572 | if (!error) | |
573 | return (FETCH_SUCCESS); | |
574 | break; | |
575 | case RULE_ALL: | |
576 | break; | |
577 | } | |
578 | ||
579 | /* reset wrapped lines */ | |
580 | set_wrapped(m, '\n'); | |
581 | ||
582 | /* report rule number */ | |
583 | if (TAILQ_EMPTY(&r->rules)) | |
584 | log_debug2("%s: matched to rule %u", a->name, r->idx); | |
585 | else | |
586 | log_debug2("%s: matched to rule %u (nested)", a->name, r->idx); | |
587 | ||
588 | /* tag mail if needed */ | |
589 | if (r->key.str != NULL) { | |
590 | tkey = replacestr(&r->key, m->tags, m, &m->rml); | |
591 | tvalue = replacestr(&r->value, m->tags, m, &m->rml); | |
592 | ||
593 | if (tkey != NULL && *tkey != '\0' && tvalue != NULL) { | |
594 | log_debug2("%s: tagging message: %s (%s)", | |
595 | a->name, tkey, tvalue); | |
596 | add_tag(&m->tags, tkey, "%s", tvalue); | |
597 | } | |
598 | ||
599 | if (tkey != NULL) | |
600 | xfree(tkey); | |
601 | if (tvalue != NULL) | |
602 | xfree(tvalue); | |
603 | } | |
604 | ||
605 | /* handle delivery */ | |
606 | if (r->actions != NULL) { | |
607 | log_debug2("%s: delivering message", a->name); | |
608 | mctx->matched = 1; | |
609 | if (do_deliver(r, mctx) != 0) { | |
610 | *cause = "delivery"; | |
611 | return (FETCH_ERROR); | |
612 | } | |
613 | } | |
614 | ||
615 | /* deal with nested rules */ | |
616 | if (!TAILQ_EMPTY(&r->rules)) { | |
617 | log_debug2("%s: entering nested rules", a->name); | |
618 | ARRAY_ADD(&mctx->stack, r, struct rule *); | |
619 | mctx->rule = TAILQ_FIRST(&r->rules); | |
620 | return (FETCH_SUCCESS); | |
621 | } | |
622 | ||
623 | /* if this rule is marked as stop, stop checking now */ | |
624 | if (r->stop) | |
625 | goto done; | |
626 | return (FETCH_SUCCESS); | |
627 | ||
628 | done: | |
629 | if (conf.keep_all || a->keep) | |
630 | m->decision = DECISION_KEEP; | |
631 | return (FETCH_COMPLETE); | |
632 | } | |
633 | ||
634 | int | |
635 | do_expr(struct rule *r, struct match_ctx *mctx) | |
636 | { | |
637 | int fres, cres; | |
638 | struct expritem *ei; | |
639 | char desc[DESCBUFSIZE]; | |
640 | ||
641 | fres = 0; | |
642 | TAILQ_FOREACH(ei, r->expr, entry) { | |
643 | cres = ei->match->match(mctx, ei); | |
644 | if (cres == MATCH_ERROR) | |
645 | return (-1); | |
646 | cres = cres == MATCH_TRUE; | |
647 | if (ei->inverted) | |
648 | cres = !cres; | |
649 | switch (ei->op) { | |
650 | case OP_NONE: | |
651 | case OP_OR: | |
652 | fres = fres || cres; | |
653 | break; | |
654 | case OP_AND: | |
655 | fres = fres && cres; | |
656 | break; | |
657 | } | |
658 | ||
659 | ei->match->desc(ei, desc, sizeof desc); | |
660 | log_debug2("%s: tried %s%s, got %d", mctx->account->name, | |
661 | ei->inverted ? "not " : "", desc, cres); | |
662 | } | |
663 | ||
664 | return (fres); | |
665 | } | |
666 | ||
667 | int | |
668 | do_deliver(struct rule *r, struct match_ctx *mctx) | |
669 | { | |
670 | struct account *a = mctx->account; | |
671 | struct mail *m = mctx->mail; | |
672 | struct action *t; | |
673 | struct actions *ta; | |
674 | u_int i, j; | |
675 | char *s; | |
676 | struct replstr *rs; | |
677 | ||
678 | if (r->actions == NULL) | |
679 | return (0); | |
680 | ||
681 | for (i = 0; i < ARRAY_LENGTH(r->actions); i++) { | |
682 | rs = &ARRAY_ITEM(r->actions, i, struct replstr); | |
683 | s = replacestr(rs, m->tags, m, &m->rml); | |
684 | ||
685 | log_debug2("%s: looking for actions matching: %s", a->name, s); | |
686 | ta = match_actions(s); | |
687 | if (ARRAY_EMPTY(ta)) { | |
688 | log_warnx("%s: no actions matching: %s (was %s)", | |
689 | a->name, s, rs->str); | |
690 | xfree(s); | |
691 | ARRAY_FREEALL(ta); | |
692 | return (1); | |
693 | } | |
694 | xfree(s); | |
695 | ||
696 | log_debug2("%s: found %u actions", a->name, ARRAY_LENGTH(ta)); | |
697 | for (j = 0; j < ARRAY_LENGTH(ta); j++) { | |
698 | t = ARRAY_ITEM(ta, j, struct action *); | |
699 | log_debug2("%s: action %s", a->name, t->name); | |
700 | if (do_action(r, mctx, t) != 0) { | |
701 | ARRAY_FREEALL(ta); | |
702 | return (1); | |
703 | } | |
704 | } | |
705 | ||
706 | ARRAY_FREEALL(ta); | |
707 | } | |
708 | ||
709 | return (0); | |
710 | } | |
711 | ||
712 | int | |
713 | do_action(struct rule *r, struct match_ctx *mctx, struct action *t) | |
714 | { | |
715 | struct account *a = mctx->account; | |
716 | struct mail *m = mctx->mail; | |
717 | struct msg msg; | |
718 | struct deliver_ctx dctx; | |
719 | u_int i, l; | |
720 | int find; | |
721 | struct strings *users; | |
722 | void *buf; | |
723 | size_t len; | |
724 | ||
725 | if (t->deliver->deliver == NULL) | |
726 | return (0); | |
727 | add_tag(&m->tags, "action", "%s", t->name); | |
728 | ||
729 | /* just deliver now for in-child delivery */ | |
730 | if (t->deliver->type == DELIVER_INCHILD) { | |
731 | memset(&dctx, 0, sizeof dctx); | |
732 | dctx.account = a; | |
733 | dctx.mail = m; | |
734 | ||
735 | if (t->deliver->deliver(&dctx, t) != DELIVER_SUCCESS) | |
736 | return (1); | |
737 | return (0); | |
738 | } | |
739 | ||
740 | /* figure out the users to use */ | |
741 | find = 0; | |
742 | users = NULL; | |
743 | if (r->find_uid) { /* rule comes first */ | |
744 | find = 1; | |
745 | users = find_users(m); | |
746 | } else if (r->users != NULL) { | |
747 | find = 0; | |
748 | users = r->users; | |
749 | } else if (t->find_uid) { /* then action */ | |
750 | find = 1; | |
751 | users = find_users(m); | |
752 | } else if (t->users != NULL) { | |
753 | find = 0; | |
754 | users = t->users; | |
755 | } else if (a->find_uid) { /* then account */ | |
756 | find = 1; | |
757 | users = find_users(m); | |
758 | } else if (a->users != NULL) { | |
759 | find = 0; | |
760 | users = a->users; | |
761 | } | |
762 | if (users == NULL) { | |
763 | find = 1; | |
764 | users = xmalloc(sizeof *users); | |
765 | ARRAY_INIT(users); | |
766 | ARRAY_ADD(users, conf.def_user, uid_t); | |
767 | } | |
768 | ||
769 | for (i = 0; i < ARRAY_LENGTH(users); i++) { | |
770 | memset(&msg, 0, sizeof msg); | |
771 | msg.type = MSG_ACTION; | |
772 | msg.data.account = a; | |
773 | msg.data.action = t; | |
774 | msg.data.uid = ARRAY_ITEM(users, i, uid_t); | |
775 | ||
776 | mail_send(m, &msg); | |
777 | ||
778 | if (privsep_send(mctx->io, &msg, m->tags, | |
779 | STRB_SIZE(m->tags)) != 0) | |
780 | fatalx("child: privsep_send error"); | |
781 | ||
782 | if (privsep_recv(mctx->io, &msg, &buf, &len) != 0) | |
783 | fatalx("child: privsep_recv error"); | |
784 | if (msg.type != MSG_DONE) | |
785 | fatalx("child: unexpected message"); | |
786 | ||
787 | if (buf == NULL || len == 0) | |
788 | fatalx("child: bad tags"); | |
789 | strb_destroy(&m->tags); | |
790 | m->tags = buf; | |
791 | update_tags(&m->tags); | |
792 | ||
793 | if (msg.data.error != 0) { | |
794 | ARRAY_FREEALL(users); | |
795 | return (1); | |
796 | } | |
797 | ||
798 | if (t->deliver->type != DELIVER_WRBACK) { | |
799 | /* check everything that should be is the same */ | |
800 | if (m->size != msg.data.mail.size || | |
801 | m->body != msg.data.mail.body) | |
802 | fatalx("child: corrupted message"); | |
803 | continue; | |
804 | } | |
805 | ||
806 | mail_receive(m, &msg); | |
807 | log_debug2("%s: received modified mail: size %zu, body %zd", | |
808 | a->name, m->size, m->body); | |
809 | ||
810 | /* trim from line */ | |
811 | trim_from(m); | |
812 | ||
813 | /* and recreate the wrapped array */ | |
814 | l = fill_wrapped(m); | |
815 | log_debug2("%s: found %u wrapped lines", a->name, l); | |
816 | } | |
817 | ||
818 | if (find) | |
819 | ARRAY_FREEALL(users); | |
820 | ||
821 | return (0); | |
822 | } |
74 | 74 | int exists, fd = -1, fd2; |
75 | 75 | int res = DELIVER_FAILURE; |
76 | 76 | gzFile gzf = NULL; |
77 | u_int n; | |
77 | 78 | |
78 | 79 | path = replacepath(&data->path, m->tags, m, &m->rml); |
79 | 80 | if (path == NULL || *path == '\0') { |
105 | 106 | goto out; |
106 | 107 | } |
107 | 108 | |
109 | n = 0; | |
108 | 110 | do { |
109 | 111 | fd = openlock(path, conf.lock_types, O_CREAT|O_WRONLY|O_APPEND, |
110 | 112 | FILEMODE); |
111 | 113 | if (fd < 0) { |
112 | 114 | if (errno == EAGAIN) { |
113 | log_warnx("%s: %s: couldn't obtain lock. " | |
114 | "sleeping", a->name, path); | |
115 | sleep(LOCKSLEEPTIME); | |
115 | usleep(LOCKSLEEPTIME); | |
116 | n++; | |
117 | if (n >= LOCKRETRIES) { | |
118 | log_warnx("%s: %s: couldn't obtain lock" | |
119 | " in %.2f seconds", a->name, path, | |
120 | (LOCKSLEEPTIME * n) / 1000000.0); | |
121 | goto out; | |
122 | } | |
116 | 123 | } else { |
117 | 124 | log_warn("%s: %s: open", a->name, path); |
118 | 125 | goto out; |
24 | 24 | |
25 | 25 | /* Deliver context. */ |
26 | 26 | struct deliver_ctx { |
27 | struct action *action; | |
28 | struct rule *rule; | |
29 | ||
27 | 30 | struct account *account; |
28 | 31 | struct mail *mail; |
29 | 32 | |
33 | uid_t uid; | |
34 | ||
30 | 35 | struct mail wr_mail; |
36 | ||
37 | int blocked; /* blocked waiting for parent */ | |
38 | TAILQ_ENTRY(deliver_ctx) entry; | |
31 | 39 | }; |
40 | TAILQ_HEAD(deliver_queue, deliver_ctx); | |
32 | 41 | |
33 | 42 | /* Delivery types. */ |
34 | 43 | enum delivertype { |
263 | 263 | int |
264 | 264 | main(int argc, char **argv) |
265 | 265 | { |
266 | int opt, fds[2], lockfd, status, res; | |
266 | int opt, lockfd, status, res; | |
267 | 267 | u_int i; |
268 | 268 | enum fdmop op = FDMOP_NONE; |
269 | 269 | const char *errstr, *proxy = NULL, *s; |
275 | 275 | time_t tt; |
276 | 276 | struct account *a; |
277 | 277 | pid_t pid; |
278 | struct children children; | |
278 | struct children children, dead_children; | |
279 | 279 | struct child *child; |
280 | void *buf; | |
281 | size_t len; | |
280 | 282 | struct io **ios, *io; |
281 | 283 | double tim; |
282 | 284 | struct sigaction act; |
283 | 285 | struct msg msg; |
284 | 286 | size_t off; |
285 | 287 | struct macro *macro; |
288 | struct child_fetch_data *cfd; | |
286 | 289 | #ifdef DEBUG |
287 | 290 | struct rule *r; |
288 | 291 | struct action *t; |
668 | 671 | |
669 | 672 | /* start the children and build the array */ |
670 | 673 | ARRAY_INIT(&children); |
674 | ARRAY_INIT(&dead_children); | |
675 | ||
671 | 676 | child = NULL; |
672 | 677 | TAILQ_FOREACH(a, &conf.accounts, entry) { |
673 | 678 | if (!use_account(a, NULL)) |
674 | 679 | continue; |
675 | 680 | |
676 | if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, fds) != 0) | |
677 | fatal("socketpair"); | |
678 | if ((pid = child_fork()) == 0) { | |
679 | for (i = 0; i < ARRAY_LENGTH(&children); i++) { | |
680 | child = ARRAY_ITEM(&children, i,struct child *); | |
681 | io_close(child->io); | |
682 | io_free(child->io); | |
683 | xfree(child); | |
684 | } | |
685 | close(fds[0]); | |
686 | ||
687 | res = do_child(fds[1], op, a); | |
688 | #ifdef PROFILE | |
689 | /* | |
690 | * We want to use _exit rather than exit in the child | |
691 | * process, and it doesn't run atexit handlers, so run | |
692 | * _mcleanup manually to force the profiler to dump its | |
693 | * statistics. | |
694 | * | |
695 | * This is icky but it works, and is only for profiling. | |
696 | */ | |
697 | extern void _mcleanup(void); | |
698 | _mcleanup(); | |
699 | #endif | |
700 | child_exit(res); | |
701 | } | |
702 | ||
703 | log_debug2("parent: child %ld (%s) started", (long) pid, | |
681 | cfd = xmalloc(sizeof *cfd); | |
682 | cfd->account = a; | |
683 | cfd->op = op; | |
684 | cfd->children = &children; | |
685 | child = child_start(&children, conf.child_uid, child_fetch, | |
686 | parent_fetch, cfd); | |
687 | ||
688 | log_debug2("parent: child %ld (%s) started", (long) child->pid, | |
704 | 689 | a->name); |
705 | ||
706 | child = xcalloc(1, sizeof *child); | |
707 | ARRAY_ADD(&children, child, struct child *); | |
708 | close(fds[1]); | |
709 | child->io = io_create(fds[0], NULL, IO_CRLF, INFTIM); | |
710 | child->pid = pid; | |
711 | child->account = a; | |
712 | 690 | } |
713 | 691 | |
714 | 692 | if (ARRAY_EMPTY(&children)) { |
724 | 702 | tim = get_time(); |
725 | 703 | |
726 | 704 | res = 0; |
727 | ios = xcalloc(ARRAY_LENGTH(&children), sizeof (struct io *)); | |
705 | ios = NULL; | |
728 | 706 | while (!ARRAY_EMPTY(&children)) { |
729 | 707 | if (sigint || sigterm) |
730 | 708 | break; |
731 | 709 | |
732 | 710 | /* fill the io list */ |
711 | ios = xrealloc(ios, ARRAY_LENGTH(&children), sizeof **ios); | |
733 | 712 | for (i = 0; i < ARRAY_LENGTH(&children); i++) { |
734 | 713 | child = ARRAY_ITEM(&children, i, struct child *); |
735 | 714 | ios[i] = child->io; |
755 | 734 | break; |
756 | 735 | |
757 | 736 | /* and handle them if necessary */ |
758 | if (do_parent(child) == 0) | |
737 | if (privsep_recv(child->io, &msg, &buf, &len) != 0) | |
738 | fatalx("parent: privsep_recv error"); | |
739 | if (child->msg(child, &msg, buf, len) == 0) | |
759 | 740 | continue; |
760 | 741 | |
761 | 742 | /* child has said it is ready to exit, tell it to */ |
769 | 750 | fatal("waitpid"); |
770 | 751 | if (WIFSIGNALED(status)) { |
771 | 752 | res = 1; |
772 | log_debug2("parent: child %ld (%s) done, got" | |
773 | "signal %d", (long) child->pid, | |
774 | child->account->name, WTERMSIG(status)); | |
753 | log_debug2("parent: child %ld got signal %d", | |
754 | (long) child->pid, WTERMSIG(status)); | |
775 | 755 | } else if (!WIFEXITED(status)) { |
776 | 756 | res = 1; |
777 | log_debug2("parent: child %ld (%s) done, didn't" | |
778 | "exit normally", (long) child->pid, | |
779 | child->account->name); | |
757 | log_debug2("parent: child %ld didn't exit" | |
758 | "normally", (long) child->pid); | |
780 | 759 | } else { |
781 | 760 | if (WEXITSTATUS(status) != 0) |
782 | 761 | res = 1; |
783 | log_debug2("parent: child %ld (%s) done, " | |
784 | "returned %d", (long) child->pid, | |
785 | child->account->name, WEXITSTATUS(status)); | |
762 | log_debug2("parent: child %ld returned %d", | |
763 | (long) child->pid, WEXITSTATUS(status)); | |
786 | 764 | } |
787 | 765 | |
766 | io_close(child->io); | |
767 | ||
788 | 768 | ARRAY_REMOVE(&children, i, struct child *); |
789 | ||
790 | io_close(child->io); | |
791 | io_free(child->io); | |
792 | xfree(child); | |
769 | ARRAY_ADD(&dead_children, child, struct child *); | |
793 | 770 | } |
794 | 771 | } |
795 | 772 | xfree(ios); |
773 | ||
774 | /* free the dead children */ | |
775 | for (i = 0; i < ARRAY_LENGTH(&dead_children); i++) { | |
776 | child = ARRAY_ITEM(&dead_children, i, struct child *); | |
777 | io_free(child->io); | |
778 | if (child->data != NULL) | |
779 | xfree(child->data); | |
780 | xfree(child); | |
781 | } | |
782 | ARRAY_FREE(&dead_children); | |
796 | 783 | |
797 | 784 | if (sigint || sigterm) { |
798 | 785 | act.sa_handler = SIG_IGN; |
44 | 44 | #define MAXMAILSIZE INT_MAX |
45 | 45 | #define DEFMAILSIZE (1 * 1024 * 1024 * 1024) /* 1 GB */ |
46 | 46 | #define DEFTIMEOUT (900 * 1000) |
47 | #define LOCKSLEEPTIME 2 | |
47 | #define LOCKSLEEPTIME 10000 | |
48 | #define LOCKRETRIES 1000 | |
48 | 49 | #define MAXNAMESIZE 64 |
49 | 50 | #define DEFUMASK (S_IRWXG|S_IRWXO) |
50 | 51 | #define FILEMODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) |
351 | 352 | |
352 | 353 | /* A single mail. */ |
353 | 354 | struct mail { |
355 | u_int idx; | |
356 | ||
354 | 357 | struct strb *tags; |
355 | 358 | |
356 | 359 | struct shm shm; |
370 | 373 | |
371 | 374 | ssize_t body; /* offset of body */ |
372 | 375 | |
376 | /* XXX move below into special struct and just cp it in mail_*? */ | |
373 | 377 | struct rmlist rml; /* regexp matches */ |
378 | ||
379 | int done; /* mail is finished with */ | |
374 | 380 | enum decision decision; /* final deliver decision */ |
375 | 381 | |
376 | 382 | void (*auxfree)(void *); |
392 | 398 | TAILQ_HEAD(, attach) children; |
393 | 399 | |
394 | 400 | TAILQ_ENTRY(attach) entry; |
401 | }; | |
402 | ||
403 | /* Privsep message types. */ | |
404 | enum msgtype { | |
405 | MSG_ACTION, | |
406 | MSG_EXIT, | |
407 | MSG_DONE, | |
408 | MSG_COMMAND | |
409 | }; | |
410 | ||
411 | /* Privsep message data. */ | |
412 | struct msgdata { | |
413 | int error; | |
414 | struct mail mail; | |
415 | ||
416 | /* these only work so long as they aren't moved in either process */ | |
417 | struct account *account; | |
418 | struct action *action; | |
419 | struct match_command_data *cmddata; | |
420 | ||
421 | uid_t uid; | |
422 | }; | |
423 | ||
424 | /* Privsep message. */ | |
425 | struct msg { | |
426 | enum msgtype type; | |
427 | size_t size; | |
428 | ||
429 | struct msgdata data; | |
395 | 430 | }; |
396 | 431 | |
397 | 432 | /* A single child. */ |
398 | 433 | struct child { |
399 | 434 | pid_t pid; |
400 | 435 | struct io *io; |
401 | struct account *account; | |
436 | ||
437 | void *data; | |
438 | int (*msg)(struct child *, struct msg *, void *, size_t); | |
402 | 439 | }; |
403 | 440 | |
404 | 441 | /* List of children. */ |
405 | 442 | ARRAY_DECL(children, struct child *); |
406 | 443 | |
444 | /* Fetch child data. */ | |
445 | struct child_fetch_data { | |
446 | struct account *account; | |
447 | enum fdmop op; | |
448 | struct children *children; | |
449 | }; | |
450 | ||
451 | /* Deliver child data. */ | |
452 | struct child_deliver_data { | |
453 | void (*hook)(int, struct account *, struct msg *, | |
454 | struct child_deliver_data *, int *); | |
455 | struct child *child; /* the source of the request */ | |
456 | u_int msgid; | |
457 | const char *name; | |
458 | struct account *account; | |
459 | struct action *action; | |
460 | struct deliver_ctx *dctx; | |
461 | struct mail *mail; | |
462 | struct match_ctx *mctx; | |
463 | struct match_command_data *cmddata; | |
464 | }; | |
465 | ||
407 | 466 | /* Account entry. */ |
408 | 467 | struct account { |
468 | u_int idx; | |
469 | ||
409 | 470 | char name[MAXNAMESIZE]; |
410 | 471 | |
411 | 472 | struct strings *users; |
615 | 676 | int timeout; |
616 | 677 | const char *eol; |
617 | 678 | }; |
679 | ARRAY_DECL(ios, struct io *); | |
618 | 680 | |
619 | 681 | /* Command flags. */ |
620 | 682 | #define CMD_IN 0x1 |
631 | 693 | struct io *io_in; |
632 | 694 | struct io *io_out; |
633 | 695 | struct io *io_err; |
634 | }; | |
635 | ||
636 | /* Privsep message types. */ | |
637 | enum msgtype { | |
638 | MSG_ACTION, | |
639 | MSG_EXIT, | |
640 | MSG_DONE, | |
641 | MSG_COMMAND | |
642 | }; | |
643 | ||
644 | /* Privsep message data. */ | |
645 | struct msgdata { | |
646 | int error; | |
647 | struct mail mail; | |
648 | ||
649 | /* these only work so long as they aren't moved in either process */ | |
650 | struct account *account; | |
651 | struct action *action; | |
652 | struct match_command_data *cmddata; | |
653 | ||
654 | uid_t uid; | |
655 | }; | |
656 | ||
657 | /* Privsep message. */ | |
658 | struct msg { | |
659 | u_int n; | |
660 | ||
661 | enum msgtype type; | |
662 | size_t size; | |
663 | ||
664 | struct msgdata data; | |
665 | 696 | }; |
666 | 697 | |
667 | 698 | /* Comparison operators. */ |
762 | 793 | /* child.c */ |
763 | 794 | int child_fork(void); |
764 | 795 | __dead void child_exit(int); |
765 | int do_child(int, enum fdmop, struct account *); | |
766 | ||
767 | /* parent.c */ | |
768 | int do_parent(struct child *); | |
796 | struct child *child_start(struct children *, uid_t, | |
797 | int (*)(struct child *, struct io *), | |
798 | int (*)(struct child *, struct msg *, void *, | |
799 | size_t), void *); | |
800 | ||
801 | /* child-fetch.c */ | |
802 | int child_fetch(struct child *, struct io *); | |
803 | ||
804 | /* child-deliver.c */ | |
805 | int child_deliver(struct child *, struct io *); | |
806 | void child_deliver_action_hook(int, struct account *, | |
807 | struct msg *, struct child_deliver_data *, int *); | |
808 | void child_deliver_cmd_hook(int, struct account *, | |
809 | struct msg *, struct child_deliver_data *, int *); | |
810 | ||
811 | /* parent-fetch.c */ | |
812 | int parent_fetch(struct child *, struct msg *, void *, | |
813 | size_t); | |
814 | ||
815 | /* parent-deliver.c */ | |
816 | int parent_deliver(struct child *, struct msg *, void *, | |
817 | size_t); | |
769 | 818 | |
770 | 819 | /* connect.c */ |
771 | 820 | struct proxy *getproxy(const char *); |
22 | 22 | #include "fdm.h" |
23 | 23 | #include "fetch.h" |
24 | 24 | |
25 | int fetch_imap_start(struct account *); | |
25 | int fetch_imap_start(struct account *, struct ios *); | |
26 | 26 | int fetch_imap_finish(struct account *); |
27 | 27 | void fetch_imap_desc(struct account *, char *, size_t); |
28 | 28 | |
56 | 56 | } |
57 | 57 | |
58 | 58 | int |
59 | fetch_imap_getln(struct account *a, int type, char **line, int flags) | |
59 | fetch_imap_getln(struct account *a, int type, char **line, int flag) | |
60 | 60 | { |
61 | 61 | struct fetch_imap_data *data = a->data; |
62 | 62 | char **lbuf = &data->lbuf; |
64 | 64 | char *cause; |
65 | 65 | int tag; |
66 | 66 | |
67 | if (flags & FETCH_NOWAIT) | |
68 | data->io->flags |= IO_NOWAIT; | |
69 | else | |
70 | data->io->flags &= ~IO_NOWAIT; | |
71 | ||
72 | ||
73 | 67 | restart: |
74 | switch (io_pollline2(data->io, line, lbuf, llen, &cause)) { | |
75 | case 0: | |
76 | log_warnx("%s: connection unexpectedly closed", a->name); | |
77 | return (-1); | |
78 | case -1: | |
79 | if (errno == EAGAIN) | |
68 | if (flag) { | |
69 | *line = io_readline2(data->io, &data->lbuf, &data->llen); | |
70 | if (*line == NULL) | |
80 | 71 | return (1); |
81 | log_warnx("%s: %s", a->name, cause); | |
82 | xfree(cause); | |
83 | return (-1); | |
72 | } else { | |
73 | switch (io_pollline2(data->io, line, lbuf, llen, &cause)) { | |
74 | case 0: | |
75 | log_warnx("%s: connection unexpectedly closed",a->name); | |
76 | return (-1); | |
77 | case -1: | |
78 | log_warnx("%s: %s", a->name, cause); | |
79 | xfree(cause); | |
80 | return (-1); | |
81 | } | |
84 | 82 | } |
85 | 83 | |
86 | 84 | if (type == IMAP_RAW) |
123 | 121 | } |
124 | 122 | |
125 | 123 | int |
126 | fetch_imap_start(struct account *a) | |
124 | fetch_imap_start(struct account *a, struct ios *ios) | |
127 | 125 | { |
128 | 126 | struct fetch_imap_data *data = a->data; |
129 | 127 | char *cause; |
140 | 138 | } |
141 | 139 | if (conf.debug > 3 && !conf.syslog) |
142 | 140 | data->io->dup_fd = STDOUT_FILENO; |
141 | ARRAY_ADD(ios, data->io, struct io *); | |
143 | 142 | |
144 | 143 | data->getln = fetch_imap_getln; |
145 | 144 | data->putln = fetch_imap_putln; |
27 | 27 | void fetch_imappipe_desc(struct account *, char *, size_t); |
28 | 28 | |
29 | 29 | int printflike2 fetch_imappipe_putln(struct account *, const char *, ...); |
30 | int fetch_imappipe_getln(struct account *, int, char **, int); | |
30 | int fetch_imappipe_getln(struct account *, int, char **); | |
31 | 31 | void fetch_imappipe_flush(struct account *); |
32 | 32 | |
33 | 33 | struct fetch fetch_imappipe = { |
56 | 56 | } |
57 | 57 | |
58 | 58 | int |
59 | fetch_imappipe_getln(struct account *a, int type, char **line, int flags) | |
59 | fetch_imappipe_getln(struct account *a, int type, char **line) | |
60 | 60 | { |
61 | 61 | struct fetch_imap_data *data = a->data; |
62 | 62 | char **lbuf = &data->lbuf; |
63 | 63 | size_t *llen = &data->llen; |
64 | 64 | char *out, *err, *cause; |
65 | 65 | int tag; |
66 | ||
67 | if (flags & FETCH_NOWAIT) | |
68 | data->cmd->timeout = 0; | |
69 | else | |
70 | data->cmd->timeout = conf.timeout; | |
71 | 66 | |
72 | 67 | restart: |
73 | 68 | switch (cmd_poll(data->cmd, &out, &err, lbuf, llen, &cause)) { |
84 | 79 | |
85 | 80 | if (err != NULL) |
86 | 81 | log_warnx("%s: %s: %s", a->name, data->pipecmd, err); |
87 | if (out == NULL) { | |
88 | if (flags & FETCH_NOWAIT) | |
89 | return (1); | |
82 | if (out == NULL) | |
90 | 83 | goto restart; |
91 | } | |
92 | 84 | *line = out; |
93 | 85 | |
94 | 86 | if (type == IMAP_RAW) |
26 | 26 | #include "fdm.h" |
27 | 27 | #include "fetch.h" |
28 | 28 | |
29 | int fetch_pop3_start(struct account *); | |
29 | int fetch_pop3_start(struct account *, struct ios *); | |
30 | 30 | int fetch_pop3_finish(struct account *); |
31 | 31 | int fetch_pop3_poll(struct account *, u_int *); |
32 | int fetch_pop3_fetch(struct account *, struct mail *, int); | |
33 | int fetch_pop3_purge(struct account *); | |
32 | int fetch_pop3_fetch(struct account *, struct mail *); | |
33 | int fetch_pop3_purge(struct account *, struct ios *); | |
34 | 34 | int fetch_pop3_done(struct account *, struct mail *); |
35 | 35 | void fetch_pop3_desc(struct account *, char *, size_t); |
36 | 36 | |
37 | 37 | void fetch_pop3_free(void *); |
38 | 38 | |
39 | int fetch_pop3_connect(struct account *); | |
39 | int fetch_pop3_connect(struct account *, struct ios *); | |
40 | 40 | int fetch_pop3_disconnect(struct account *); |
41 | 41 | |
42 | int fetch_pop3_line(struct account *, char **, int); | |
42 | int fetch_pop3_line(struct account *, char **); | |
43 | 43 | int fetch_pop3_okay(char *); |
44 | 44 | char *fetch_pop3_check(struct account *); |
45 | 45 | |
64 | 64 | } |
65 | 65 | |
66 | 66 | int |
67 | fetch_pop3_line(struct account *a, char **line, int flags) | |
67 | fetch_pop3_line(struct account *a, char **line) | |
68 | 68 | { |
69 | 69 | struct fetch_pop3_data *data = a->data; |
70 | 70 | struct io *io = data->io; |
71 | 71 | char *cause; |
72 | 72 | |
73 | if (flags & FETCH_NOWAIT) | |
74 | io->flags |= IO_NOWAIT; | |
75 | else | |
76 | io->flags &= ~IO_NOWAIT; | |
77 | 73 | switch (io_pollline2(io, line, &data->lbuf, &data->llen, &cause)) { |
78 | 74 | case 0: |
79 | 75 | log_warnx("%s: connection unexpectedly closed", a->name); |
80 | return (-1); | |
76 | return (1); | |
81 | 77 | case -1: |
82 | 78 | if (errno == EAGAIN) |
83 | 79 | return (1); |
84 | 80 | log_warnx("%s: %s", a->name, cause); |
85 | 81 | xfree(cause); |
86 | return (-1); | |
82 | return (1); | |
87 | 83 | } |
88 | 84 | |
89 | 85 | return (0); |
102 | 98 | { |
103 | 99 | char *line; |
104 | 100 | |
105 | if (fetch_pop3_line(a, &line, 0) != 0) | |
101 | if (fetch_pop3_line(a, &line) != 0) | |
106 | 102 | return (NULL); |
107 | 103 | |
108 | 104 | if (!fetch_pop3_okay(line)) { |
114 | 110 | } |
115 | 111 | |
116 | 112 | int |
117 | fetch_pop3_start(struct account *a) | |
113 | fetch_pop3_start(struct account *a, struct ios *ios) | |
118 | 114 | { |
119 | 115 | struct fetch_pop3_data *data = a->data; |
120 | 116 | |
123 | 119 | data->llen = IO_LINESIZE; |
124 | 120 | data->lbuf = xmalloc(data->llen); |
125 | 121 | |
126 | return (fetch_pop3_connect(a)); | |
122 | data->state = POP3_START; | |
123 | ||
124 | return (fetch_pop3_connect(a, ios)); | |
127 | 125 | } |
128 | 126 | |
129 | 127 | int |
148 | 146 | } |
149 | 147 | |
150 | 148 | int |
151 | fetch_pop3_connect(struct account *a) | |
149 | fetch_pop3_connect(struct account *a, struct ios *ios) | |
152 | 150 | { |
153 | 151 | struct fetch_pop3_data *data = a->data; |
154 | 152 | char *line, *cause; |
162 | 160 | } |
163 | 161 | if (conf.debug > 3 && !conf.syslog) |
164 | 162 | data->io->dup_fd = STDOUT_FILENO; |
163 | ARRAY_ADD(ios, data->io, struct io *); | |
165 | 164 | |
166 | 165 | if (fetch_pop3_check(a) == NULL) |
167 | 166 | return (FETCH_ERROR); |
224 | 223 | } |
225 | 224 | |
226 | 225 | int |
227 | fetch_pop3_fetch(struct account *a, struct mail *m, int flags) | |
226 | fetch_pop3_fetch(struct account *a, struct mail *m) | |
228 | 227 | { |
229 | 228 | struct fetch_pop3_data *data = a->data; |
230 | 229 | struct fetch_pop3_mail *aux; |
231 | 230 | char *line, *uid; |
232 | 231 | size_t len; |
233 | 232 | u_int n, i; |
234 | int error; | |
235 | 233 | |
236 | 234 | restart: |
235 | line = NULL; | |
236 | if (data->state != POP3_START) { | |
237 | line = io_readline2(data->io, &data->lbuf, &data->llen); | |
238 | if (line == NULL) | |
239 | return (FETCH_AGAIN); | |
240 | } | |
241 | ||
237 | 242 | switch (data->state) { |
238 | case POP3_LISTDONE: | |
239 | case POP3_UIDLDONE: | |
240 | case POP3_RETRDONE: | |
241 | case POP3_LINE: | |
242 | error = fetch_pop3_line(a, &line, flags); | |
243 | switch (error) { | |
244 | case -1: | |
245 | return (FETCH_ERROR); | |
246 | case 1: | |
247 | return (FETCH_AGAIN); | |
248 | } | |
249 | default: | |
250 | break; | |
251 | } | |
252 | ||
253 | switch (data->state) { | |
254 | case POP3_LIST: | |
243 | case POP3_START: | |
255 | 244 | data->cur++; |
256 | 245 | if (data->cur > data->num) |
257 | 246 | return (FETCH_COMPLETE); |
258 | 247 | io_writeline(data->io, "LIST %u", data->cur); |
259 | data->state = POP3_LISTDONE; | |
248 | data->state = POP3_LIST; | |
260 | 249 | break; |
261 | case POP3_LISTDONE: | |
250 | case POP3_LIST: | |
262 | 251 | if (!fetch_pop3_okay(line)) |
263 | 252 | goto bad; |
264 | 253 | if (sscanf(line, "+OK %*u %zu", &data->size) != 1) { |
271 | 260 | m->size = data->size; |
272 | 261 | return (FETCH_OVERSIZE); |
273 | 262 | } |
263 | io_writeline(data->io, "UIDL %u", data->cur); | |
274 | 264 | data->state = POP3_UIDL; |
275 | 265 | break; |
276 | 266 | case POP3_UIDL: |
277 | io_writeline(data->io, "UIDL %u", data->cur); | |
278 | data->state = POP3_UIDLDONE; | |
279 | break; | |
280 | case POP3_UIDLDONE: | |
281 | 267 | if (!fetch_pop3_okay(line)) |
282 | 268 | goto bad; |
283 | 269 | if (sscanf(line, "+OK %u ", &n) != 1) |
308 | 294 | break; |
309 | 295 | } |
310 | 296 | } |
297 | io_writeline(data->io, "RETR %u", data->cur); | |
311 | 298 | data->state = POP3_RETR; |
312 | 299 | break; |
313 | 300 | case POP3_RETR: |
314 | io_writeline(data->io, "RETR %u", data->cur); | |
315 | data->state = POP3_RETRDONE; | |
316 | break; | |
317 | case POP3_RETRDONE: | |
318 | 301 | if (!fetch_pop3_okay(line)) |
319 | 302 | goto bad; |
320 | 303 | mail_open(m, IO_ROUND(data->size)); |
370 | 353 | goto restart; |
371 | 354 | |
372 | 355 | complete: |
373 | data->state = POP3_LIST; | |
356 | data->state = POP3_START; | |
374 | 357 | |
375 | 358 | add_tag(&m->tags, "lines", "%u", data->lines); |
376 | 359 | if (data->bodylines == -1) { |
390 | 373 | |
391 | 374 | if (data->flushing) |
392 | 375 | return (FETCH_OVERSIZE); |
376 | ||
393 | 377 | return (FETCH_SUCCESS); |
394 | 378 | |
395 | 379 | bad: |
398 | 382 | } |
399 | 383 | |
400 | 384 | int |
401 | fetch_pop3_purge(struct account *a) | |
385 | fetch_pop3_purge(struct account *a, struct ios *ios) | |
402 | 386 | { |
403 | 387 | if (fetch_pop3_disconnect(a) != 0) |
404 | 388 | return (FETCH_ERROR); |
405 | return (fetch_pop3_connect(a)); | |
389 | return (fetch_pop3_connect(a, ios)); | |
406 | 390 | } |
407 | 391 | |
408 | 392 | int |
27 | 27 | #include "fdm.h" |
28 | 28 | #include "fetch.h" |
29 | 29 | |
30 | int fetch_stdin_start(struct account *); | |
30 | int fetch_stdin_start(struct account *, struct ios *); | |
31 | 31 | int fetch_stdin_finish(struct account *); |
32 | int fetch_stdin_fetch(struct account *, struct mail *, int); | |
32 | int fetch_stdin_fetch(struct account *, struct mail *); | |
33 | 33 | int fetch_stdin_done(struct account *, struct mail *); |
34 | 34 | void fetch_stdin_desc(struct account *, char *, size_t); |
35 | 35 | |
45 | 45 | }; |
46 | 46 | |
47 | 47 | int |
48 | fetch_stdin_start(struct account *a) | |
48 | fetch_stdin_start(struct account *a, struct ios *ios) | |
49 | 49 | { |
50 | 50 | struct fetch_stdin_data *data = a->data; |
51 | 51 | |
67 | 67 | data->io = io_create(STDIN_FILENO, NULL, IO_LF, conf.timeout); |
68 | 68 | if (conf.debug > 3 && !conf.syslog) |
69 | 69 | data->io->dup_fd = STDOUT_FILENO; |
70 | ARRAY_ADD(ios, data->io, struct io *); | |
70 | 71 | |
71 | 72 | data->complete = 0; |
72 | 73 | |
108 | 109 | } |
109 | 110 | |
110 | 111 | int |
111 | fetch_stdin_fetch(struct account *a, struct mail *m, int flags) | |
112 | fetch_stdin_fetch(struct account *a, struct mail *m) | |
112 | 113 | { |
113 | 114 | struct fetch_stdin_data *data = a->data; |
114 | 115 | int error; |
123 | 124 | m->size = 0; |
124 | 125 | } |
125 | 126 | |
126 | if (flags & FETCH_NOWAIT) | |
127 | data->io->flags |= IO_NOWAIT; | |
128 | else | |
129 | data->io->flags &= ~IO_NOWAIT; | |
130 | ||
131 | 127 | restart: |
128 | /* | |
129 | * There can only ever be one mail on stdin, so the normal reentrancy | |
130 | * becomes irrelevent. Which is good since we need to detect when the | |
131 | * fd is closed. | |
132 | */ | |
132 | 133 | error = io_pollline2(data->io, &line, &data->lbuf, &data->llen, &cause); |
133 | 134 | switch (error) { |
134 | 135 | case 0: |
141 | 142 | xfree(cause); |
142 | 143 | return (FETCH_ERROR); |
143 | 144 | } |
144 | ||
145 | ||
145 | 146 | len = strlen(line); |
146 | 147 | if (len == 0 && m->body == -1) { |
147 | 148 | m->body = m->size + 1; |
26 | 26 | #define FETCH_COMPLETE 4 |
27 | 27 | #define FETCH_AGAIN 5 |
28 | 28 | |
29 | /* Fetch flags. */ | |
30 | #define FETCH_NOWAIT 0x1 | |
31 | ||
32 | 29 | /* Fetch functions. */ |
33 | 30 | struct fetch { |
34 | 31 | #define FETCHPORT_NORMAL 0 |
35 | 32 | #define FETCHPORT_SSL 1 |
36 | 33 | const char *ports[2]; /* normal port, ssl port */ |
37 | 34 | |
38 | int (*start)(struct account *); | |
35 | int (*start)(struct account *, struct ios *); | |
39 | 36 | int (*poll)(struct account *, u_int *); |
40 | int (*fetch)(struct account *, struct mail *, int); | |
41 | int (*purge)(struct account *); | |
37 | int (*fetch)(struct account *, struct mail *); | |
38 | int (*purge)(struct account *, struct ios *); | |
42 | 39 | int (*done)(struct account *, struct mail *); |
43 | 40 | int (*finish)(struct account *); |
44 | 41 | void (*desc)(struct account *, char *, size_t); |
106 | 103 | struct strings kept; |
107 | 104 | |
108 | 105 | enum { |
106 | POP3_START, | |
109 | 107 | POP3_LIST, |
110 | POP3_LISTDONE, | |
111 | 108 | POP3_UIDL, |
112 | POP3_UIDLDONE, | |
113 | 109 | POP3_RETR, |
114 | POP3_RETRDONE, | |
115 | 110 | POP3_LINE |
116 | 111 | } state; |
117 | 112 | int flushing; |
161 | 156 | ARRAY_DECL(, u_int) kept; |
162 | 157 | |
163 | 158 | enum { |
164 | IMAP_UID, | |
165 | IMAP_UIDDONE1, | |
166 | IMAP_UIDDONE2, | |
159 | IMAP_START, | |
160 | IMAP_UID1, | |
161 | IMAP_UID2, | |
167 | 162 | IMAP_FETCH, |
168 | IMAP_FETCHDONE, | |
169 | 163 | IMAP_LINE, |
170 | IMAP_LINEDONE1, | |
171 | IMAP_LINEDONE2, | |
164 | IMAP_END1, | |
165 | IMAP_END2 | |
172 | 166 | } state; |
173 | 167 | int flushing; |
174 | 168 | int bodylines; |
219 | 213 | void imap_abort(struct account *); |
220 | 214 | int imap_uid(struct account *); |
221 | 215 | int imap_poll(struct account *, u_int *); |
222 | int imap_fetch(struct account *, struct mail *, int); | |
223 | int imap_purge(struct account *); | |
216 | int imap_fetch(struct account *, struct mail *); | |
217 | int imap_purge(struct account *, struct ios *); | |
224 | 218 | int imap_done(struct account *, struct mail *); |
225 | 219 | |
226 | 220 | #endif |
208 | 208 | } |
209 | 209 | |
210 | 210 | int |
211 | imap_fetch(struct account *a, struct mail *m, int flags) | |
211 | imap_fetch(struct account *a, struct mail *m) | |
212 | 212 | { |
213 | 213 | struct fetch_imap_data *data = a->data; |
214 | 214 | struct fetch_imap_mail *aux = m->auxdata; |
220 | 220 | restart: |
221 | 221 | type = -1; |
222 | 222 | switch (data->state) { |
223 | case IMAP_UIDDONE1: | |
224 | case IMAP_FETCHDONE: | |
223 | case IMAP_UID1: | |
224 | case IMAP_FETCH: | |
225 | 225 | type = IMAP_UNTAGGED; |
226 | 226 | break; |
227 | case IMAP_UIDDONE2: | |
228 | case IMAP_LINEDONE2: | |
227 | case IMAP_UID2: | |
228 | case IMAP_END2: | |
229 | 229 | type = IMAP_TAGGED; |
230 | 230 | break; |
231 | 231 | case IMAP_LINE: |
232 | case IMAP_LINEDONE1: | |
232 | case IMAP_END1: | |
233 | 233 | type = IMAP_RAW; |
234 | 234 | break; |
235 | 235 | default: |
236 | 236 | break; |
237 | 237 | } |
238 | 238 | if (type != -1) { |
239 | error = data->getln(a, type, &line, flags); | |
239 | error = data->getln(a, type, &line, 1); | |
240 | 240 | switch (error) { |
241 | 241 | case -1: |
242 | 242 | return (FETCH_ERROR); |
246 | 246 | } |
247 | 247 | |
248 | 248 | switch (data->state) { |
249 | case IMAP_UID: | |
249 | case IMAP_START: | |
250 | 250 | data->cur++; |
251 | 251 | if (data->cur > data->num) |
252 | 252 | return (FETCH_COMPLETE); |
255 | 255 | if (data->putln(a, |
256 | 256 | "%u FETCH %u UID", ++data->tag, data->cur) != 0) |
257 | 257 | return (FETCH_ERROR); |
258 | data->state = IMAP_UIDDONE1; | |
259 | break; | |
260 | case IMAP_UIDDONE1: | |
258 | data->state = IMAP_UID1; | |
259 | break; | |
260 | case IMAP_UID1: | |
261 | 261 | if (sscanf(line, "* %u FETCH (UID %u)", &n, &data->uid) != 2) { |
262 | 262 | log_warnx("%s: invalid response: %s", a->name, line); |
263 | 263 | return (FETCH_ERROR); |
266 | 266 | log_warnx("%s: bad message index: %s", a->name, line); |
267 | 267 | return (FETCH_ERROR); |
268 | 268 | } |
269 | data->state = IMAP_UIDDONE2; | |
270 | break; | |
271 | case IMAP_UIDDONE2: | |
269 | data->state = IMAP_UID2; | |
270 | break; | |
271 | case IMAP_UID2: | |
272 | 272 | if (!imap_okay(a, line)) |
273 | 273 | return (FETCH_ERROR); |
274 | 274 | |
275 | 275 | for (i = 0; i < ARRAY_LENGTH(&data->kept); i++) { |
276 | 276 | if (ARRAY_ITEM(&data->kept, i, u_int) == data->uid) { |
277 | 277 | /* had this message before and kept, so skip */ |
278 | data->state = IMAP_UID; | |
278 | data->state = IMAP_START; | |
279 | 279 | break; |
280 | 280 | } |
281 | 281 | } |
282 | data->state = IMAP_FETCH; | |
283 | break; | |
284 | case IMAP_FETCH: | |
285 | 282 | if (data->putln(a, |
286 | 283 | "%u FETCH %u BODY[]", ++data->tag, data->cur) != 0) |
287 | 284 | return (FETCH_ERROR); |
288 | data->state = IMAP_FETCHDONE; | |
289 | break; | |
290 | case IMAP_FETCHDONE: | |
285 | data->state = IMAP_FETCH; | |
286 | break; | |
287 | case IMAP_FETCH: | |
291 | 288 | if (sscanf(line, "* %u FETCH (", &n) != 1) { |
292 | 289 | log_warnx("%s: invalid response: %s", a->name, line); |
293 | 290 | return (FETCH_ERROR); |
351 | 348 | data->bodylines++; |
352 | 349 | m->size += len + 1; |
353 | 350 | if (m->size + data->lines >= data->size) |
354 | data->state = IMAP_LINEDONE1; | |
355 | break; | |
356 | case IMAP_LINEDONE1: | |
351 | data->state = IMAP_END1; | |
352 | break; | |
353 | case IMAP_END1: | |
357 | 354 | if (strcmp(line, ")") != 0) { |
358 | 355 | log_warnx("%s: invalid response: %s", a->name, line); |
359 | 356 | return (FETCH_ERROR); |
360 | 357 | } |
361 | data->state = IMAP_LINEDONE2; | |
362 | break; | |
363 | case IMAP_LINEDONE2: | |
358 | data->state = IMAP_END2; | |
359 | break; | |
360 | case IMAP_END2: | |
364 | 361 | if (!imap_okay(a, line)) |
365 | 362 | return (FETCH_ERROR); |
366 | 363 | goto complete; |
369 | 366 | goto restart; |
370 | 367 | |
371 | 368 | complete: |
372 | data->state = IMAP_UID; | |
369 | data->state = IMAP_START; | |
373 | 370 | |
374 | 371 | if (m->size + data->lines != data->size) { |
375 | 372 | log_warnx("%s: received too much data", a->name); |
415 | 412 | } |
416 | 413 | |
417 | 414 | int |
418 | imap_purge(struct account *a) | |
415 | imap_purge(struct account *a, struct ios *ios) | |
419 | 416 | { |
420 | 417 | struct fetch_imap_data *data = a->data; |
421 | 418 | char *line; |
430 | 427 | if (imap_select(a) != 0) |
431 | 428 | return (FETCH_ERROR); |
432 | 429 | |
433 | return (FETCH_SUCCESS); | |
434 | } | |
430 | ARRAY_ADD(ios, data->io, struct io *); | |
431 | return (FETCH_SUCCESS); | |
432 | } |
698 | 698 | int |
699 | 699 | io_flush(struct io *io, char **cause) |
700 | 700 | { |
701 | int flags; | |
702 | ||
703 | flags = io->flags; | |
704 | io->flags &= ~IO_NOWAIT; | |
705 | ||
701 | 706 | while (io->wsize > 0) { |
702 | if (io_poll(io, cause) != 1) | |
707 | if (io_poll(io, cause) != 1) { | |
708 | io->flags = flags; | |
703 | 709 | return (-1); |
704 | } | |
705 | ||
710 | } | |
711 | } | |
712 | ||
713 | io->flags = flags; | |
706 | 714 | return (0); |
707 | 715 | } |
708 | 716 | |
710 | 718 | int |
711 | 719 | io_wait(struct io *io, size_t len, char **cause) |
712 | 720 | { |
721 | int flags; | |
722 | ||
723 | flags = io->flags; | |
724 | io->flags &= ~IO_NOWAIT; | |
725 | ||
713 | 726 | while (io->rsize < len) { |
714 | if (io_poll(io, cause) != 1) | |
727 | if (io_poll(io, cause) != 1) { | |
728 | io->flags = flags; | |
715 | 729 | return (-1); |
716 | } | |
717 | ||
730 | } | |
731 | } | |
732 | ||
733 | io->flags = flags; | |
718 | 734 | return (0); |
719 | 735 | } |
36 | 36 | void |
37 | 37 | mail_open(struct mail *m, size_t size) |
38 | 38 | { |
39 | memset(m, 0, sizeof m); | |
40 | ||
41 | 39 | m->size = size; |
42 | 40 | m->space = m->size; |
43 | 41 | m->body = -1; |
68 | 66 | mail_receive(struct mail *m, struct msg *msg) |
69 | 67 | { |
70 | 68 | struct mail *mm = &msg->data.mail; |
69 | ||
70 | mm->done = m->done; | |
71 | mm->idx = m->idx; | |
71 | 72 | |
72 | 73 | mm->tags = m->tags; |
73 | 74 | m->tags = NULL; |
45 | 45 | * We are called as the child so to change uid this needs to be done |
46 | 46 | * largely in the parent. |
47 | 47 | */ |
48 | memset(&msg, 0, sizeof msg); | |
48 | 49 | msg.type = MSG_COMMAND; |
49 | 50 | msg.data.account = a; |
50 | 51 | msg.data.cmddata = data; |
18 | 18 | #ifndef MATCH_H |
19 | 19 | #define MATCH_H |
20 | 20 | |
21 | #include "deliver.h" | |
22 | ||
21 | 23 | /* Match return codes. */ |
22 | 24 | #define MATCH_FALSE 0 |
23 | 25 | #define MATCH_TRUE 1 |
25 | 27 | |
26 | 28 | /* Match context. */ |
27 | 29 | struct match_ctx { |
28 | struct io *io; | |
29 | struct account *account; | |
30 | struct mail *mail; | |
30 | struct io *io; | |
31 | struct account *account; | |
32 | struct mail *mail; | |
31 | 33 | |
32 | int matched; | |
33 | int stopped; | |
34 | int matched; | |
35 | int stopped; | |
34 | 36 | |
35 | struct rule *rule; | |
36 | ARRAY_DECL(, struct rule *) stack; | |
37 | ||
38 | TAILQ_ENTRY(match_ctx) entry; | |
37 | struct rule *rule; | |
38 | ARRAY_DECL(, struct rule *) stack; | |
39 | ||
40 | struct deliver_queue dqueue; | |
41 | ||
42 | TAILQ_ENTRY(match_ctx) entry; | |
43 | #define match_entry entry | |
44 | #define deliver_entry entry | |
45 | #define done_entry entry | |
39 | 46 | }; |
40 | 47 | TAILQ_HEAD(match_queue, match_ctx); |
41 | 48 |
0 | /* $Id$ */ | |
1 | ||
2 | /* | |
3 | * Copyright (c) 2006 Nicholas Marriott <nicm@users.sourceforge.net> | |
4 | * | |
5 | * Permission to use, copy, modify, and distribute this software for any | |
6 | * purpose with or without fee is hereby granted, provided that the above | |
7 | * copyright notice and this permission notice appear in all copies. | |
8 | * | |
9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
13 | * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER | |
14 | * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING | |
15 | * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
16 | */ | |
17 | ||
18 | #include <sys/types.h> | |
19 | #include <sys/socket.h> | |
20 | #include <sys/wait.h> | |
21 | ||
22 | #include <fcntl.h> | |
23 | #include <paths.h> | |
24 | #include <string.h> | |
25 | #include <unistd.h> | |
26 | ||
27 | #include "fdm.h" | |
28 | #include "deliver.h" | |
29 | #include "match.h" | |
30 | ||
31 | int | |
32 | parent_deliver(struct child *child, struct msg *msg, void *buf, size_t len) | |
33 | { | |
34 | struct child_deliver_data *data = child->data; | |
35 | struct account *a = data->account; | |
36 | struct mail *m = data->mail; | |
37 | ||
38 | log_debug2("parent_deliver: got message type %d from child %ld", | |
39 | msg->type, (long) child->pid); | |
40 | ||
41 | switch (msg->type) { | |
42 | case MSG_DONE: | |
43 | break; | |
44 | default: | |
45 | fatalx("parent_deliver: unexpected message"); | |
46 | } | |
47 | ||
48 | if (buf == NULL || len == 0) | |
49 | fatalx("parent_deliver: bad tags"); | |
50 | strb_destroy(&m->tags); | |
51 | m->tags = buf; | |
52 | ||
53 | /* call the hook */ | |
54 | data->hook(1, a, msg, data, &msg->data.error); | |
55 | ||
56 | msg->type = MSG_DONE; | |
57 | ||
58 | mail_send(m, msg); | |
59 | ||
60 | child = data->child; | |
61 | if (privsep_send(child->io, msg, m->tags, STRB_SIZE(m->tags)) != 0) | |
62 | fatalx("parent_deliver: privsep_send error"); | |
63 | ||
64 | mail_close(m); | |
65 | xfree(m); | |
66 | ||
67 | return (1); | |
68 | } |
0 | /* $Id$ */ | |
1 | ||
2 | /* | |
3 | * Copyright (c) 2006 Nicholas Marriott <nicm@users.sourceforge.net> | |
4 | * | |
5 | * Permission to use, copy, modify, and distribute this software for any | |
6 | * purpose with or without fee is hereby granted, provided that the above | |
7 | * copyright notice and this permission notice appear in all copies. | |
8 | * | |
9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
13 | * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER | |
14 | * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING | |
15 | * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
16 | */ | |
17 | ||
18 | #include <sys/types.h> | |
19 | #include <sys/socket.h> | |
20 | #include <sys/wait.h> | |
21 | ||
22 | #include <fcntl.h> | |
23 | #include <paths.h> | |
24 | #include <string.h> | |
25 | #include <unistd.h> | |
26 | ||
27 | #include "fdm.h" | |
28 | #include "deliver.h" | |
29 | #include "match.h" | |
30 | ||
31 | void parent_fetch_action(struct child *, struct children *, | |
32 | struct deliver_ctx *, struct msg *); | |
33 | void parent_fetch_cmd(struct child *, struct children *, struct match_ctx *, | |
34 | struct msg *); | |
35 | ||
36 | int | |
37 | parent_fetch(struct child *child, struct msg *msg, void *buf, size_t len) | |
38 | { | |
39 | struct child_fetch_data *data = child->data; | |
40 | struct children *children = data->children; | |
41 | struct deliver_ctx *dctx; | |
42 | struct match_ctx *mctx; | |
43 | struct mail *m; | |
44 | ||
45 | log_debug2("parent_fetch: got message type %d from child %ld", | |
46 | msg->type, (long) child->pid); | |
47 | ||
48 | switch (msg->type) { | |
49 | case MSG_ACTION: | |
50 | if (buf == NULL || len == 0) | |
51 | fatalx("parent_fetch: bad tags"); | |
52 | m = xcalloc(1, sizeof *m); | |
53 | mail_receive(m, msg); | |
54 | m->tags = buf; | |
55 | ||
56 | dctx = xcalloc(1, sizeof *dctx); | |
57 | dctx->account = msg->data.account; | |
58 | dctx->mail = m; | |
59 | ||
60 | parent_fetch_action(child, children, dctx, msg); | |
61 | break; | |
62 | case MSG_COMMAND: | |
63 | if (buf == NULL || len == 0) | |
64 | fatalx("parent_fetch: bad tags"); | |
65 | m = xcalloc(1, sizeof *m); | |
66 | mail_receive(m, msg); | |
67 | m->tags = buf; | |
68 | ||
69 | mctx = xcalloc(1, sizeof *mctx); | |
70 | mctx->account = msg->data.account; | |
71 | mctx->mail = m; | |
72 | ||
73 | parent_fetch_cmd(child, children, mctx, msg); | |
74 | break; | |
75 | case MSG_DONE: | |
76 | fatalx("parent_fetch: unexpected message"); | |
77 | case MSG_EXIT: | |
78 | return (1); | |
79 | } | |
80 | ||
81 | return (0); | |
82 | } | |
83 | ||
84 | void | |
85 | parent_fetch_action(struct child *child, struct children *children, | |
86 | struct deliver_ctx *dctx, struct msg *msg) | |
87 | { | |
88 | struct action *t = msg->data.action; | |
89 | uid_t uid = msg->data.uid; | |
90 | struct mail *m = dctx->mail; | |
91 | struct mail *md = &dctx->wr_mail; | |
92 | struct child_deliver_data *data; | |
93 | ||
94 | memset(md, 0, sizeof *md); | |
95 | /* | |
96 | * If writing back, open a new mail now and set its ownership so it | |
97 | * can be accessed by the child. | |
98 | */ | |
99 | if (t->deliver->type == DELIVER_WRBACK) { | |
100 | mail_open(md, IO_BLOCKSIZE); | |
101 | if (geteuid() == 0 && | |
102 | fchown(md->shm.fd, conf.child_uid, conf.child_gid) != 0) | |
103 | fatal("fchown"); | |
104 | md->decision = m->decision; | |
105 | } | |
106 | ||
107 | data = xmalloc(sizeof *data); | |
108 | data->child = child; | |
109 | data->account = dctx->account; | |
110 | data->hook = child_deliver_action_hook; | |
111 | data->action = t; | |
112 | data->dctx = dctx; | |
113 | data->mail = m; | |
114 | data->name = "deliver"; | |
115 | child = child_start(children, uid, child_deliver, parent_deliver, data); | |
116 | log_debug3("parent: deliver child %ld started", (long) child->pid); | |
117 | } | |
118 | ||
119 | void | |
120 | parent_fetch_cmd(struct child *child, struct children *children, | |
121 | struct match_ctx *mctx, struct msg *msg) | |
122 | { | |
123 | uid_t uid = msg->data.uid; | |
124 | struct mail *m = mctx->mail; | |
125 | struct child_deliver_data *data; | |
126 | ||
127 | data = xmalloc(sizeof *data); | |
128 | data->child = child; | |
129 | data->account = mctx->account; | |
130 | data->hook = child_deliver_cmd_hook; | |
131 | data->mctx = mctx; | |
132 | data->cmddata = msg->data.cmddata; | |
133 | data->mail = m; | |
134 | data->name = "command"; | |
135 | child = child_start(children, uid, child_deliver, parent_deliver, data); | |
136 | log_debug2("parent: command child %ld started", (long) child->pid); | |
137 | } |
0 | /* $Id$ */ | |
1 | ||
2 | /* | |
3 | * Copyright (c) 2006 Nicholas Marriott <nicm@users.sourceforge.net> | |
4 | * | |
5 | * Permission to use, copy, modify, and distribute this software for any | |
6 | * purpose with or without fee is hereby granted, provided that the above | |
7 | * copyright notice and this permission notice appear in all copies. | |
8 | * | |
9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
13 | * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER | |
14 | * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING | |
15 | * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
16 | */ | |
17 | ||
18 | #include <sys/types.h> | |
19 | #include <sys/socket.h> | |
20 | #include <sys/wait.h> | |
21 | ||
22 | #include <fcntl.h> | |
23 | #include <paths.h> | |
24 | #include <string.h> | |
25 | #include <unistd.h> | |
26 | ||
27 | #include "fdm.h" | |
28 | #include "deliver.h" | |
29 | #include "match.h" | |
30 | ||
31 | void parent_get(struct mail *, struct msg *, void *, struct deliver_ctx *, | |
32 | struct match_ctx *); | |
33 | void parent_done(struct io *, struct mail *, struct msg *, int); | |
34 | int parent_child(struct account *, struct mail *, const char *, uid_t, | |
35 | int (*)(pid_t, struct account *, struct msg *, void *, int *), | |
36 | void *, int *); | |
37 | ||
38 | struct parent_action_data { | |
39 | struct action *action; | |
40 | struct deliver_ctx *dctx; | |
41 | struct mail *mail; | |
42 | }; | |
43 | ||
44 | int parent_action(struct action *, struct deliver_ctx *, uid_t); | |
45 | int parent_action_hook(int, struct account *, struct msg *, void *, int *); | |
46 | ||
47 | struct parent_cmd_data { | |
48 | struct match_ctx *mctx; | |
49 | struct match_command_data *data; | |
50 | struct mail *mail; | |
51 | }; | |
52 | ||
53 | int parent_cmd(struct match_ctx *, struct match_command_data *, uid_t); | |
54 | int parent_cmd_hook(int, struct account *, struct msg *, void *, int *); | |
55 | ||
56 | void | |
57 | parent_get(struct mail *m, struct msg *msg, void *buf, struct deliver_ctx *dctx, | |
58 | struct match_ctx *mctx) | |
59 | { | |
60 | mail_receive(m, msg); | |
61 | m->tags = buf; | |
62 | ||
63 | if (dctx != NULL) { | |
64 | memset(dctx, 0, sizeof dctx); | |
65 | dctx->account = msg->data.account; | |
66 | dctx->mail = m; | |
67 | } | |
68 | ||
69 | if (mctx != NULL) { | |
70 | memset(mctx, 0, sizeof mctx); | |
71 | mctx->account = msg->data.account; | |
72 | mctx->mail = m; | |
73 | } | |
74 | } | |
75 | ||
76 | void | |
77 | parent_done(struct io *io, struct mail *m, struct msg *msg, int error) | |
78 | { | |
79 | memset(msg, 0, sizeof msg); | |
80 | ||
81 | msg->type = MSG_DONE; | |
82 | msg->data.error = error; | |
83 | ||
84 | mail_send(m, msg); | |
85 | ||
86 | if (privsep_send(io, msg, m->tags, STRB_SIZE(m->tags)) != 0) | |
87 | fatalx("parent: privsep_send error"); | |
88 | ||
89 | mail_close(m); | |
90 | } | |
91 | ||
92 | int | |
93 | parent_child(struct account *a, struct mail *m, const char *name, uid_t uid, | |
94 | int (*hook)(pid_t, struct account *, struct msg *, void *, int *), | |
95 | void *hookdata, int *result) | |
96 | { | |
97 | struct io *io; | |
98 | struct msg msg; | |
99 | int status, error = 0, fds[2]; | |
100 | pid_t pid; | |
101 | void *buf; | |
102 | size_t len; | |
103 | ||
104 | if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, fds) != 0) | |
105 | fatal("socketpair"); | |
106 | pid = child_fork(); | |
107 | if (pid != 0) { | |
108 | /* create privsep io */ | |
109 | close(fds[1]); | |
110 | io = io_create(fds[0], NULL, IO_LF, INFTIM); | |
111 | ||
112 | /* parent process. wait for child */ | |
113 | log_debug2("%s: forked. child pid is %ld", a->name, (long) pid); | |
114 | ||
115 | do { | |
116 | if (privsep_recv(io, &msg, &buf, &len) != 0) | |
117 | fatalx("parent2: privsep_recv error"); | |
118 | log_debug2("parent2: got message type %d", msg.type); | |
119 | ||
120 | switch (msg.type) { | |
121 | case MSG_DONE: | |
122 | break; | |
123 | default: | |
124 | fatalx("parent2: unexpected message"); | |
125 | } | |
126 | } while (msg.type != MSG_DONE); | |
127 | *result = msg.data.error; | |
128 | ||
129 | if (buf == NULL || len == 0) | |
130 | fatalx("parent2: bad tags"); | |
131 | strb_destroy(&m->tags); | |
132 | m->tags = buf; | |
133 | ||
134 | /* call the hook */ | |
135 | if (hook(pid, a, &msg, hookdata, result) != 0) | |
136 | error = 1; | |
137 | ||
138 | /* free the io */ | |
139 | io_close(io); | |
140 | io_free(io); | |
141 | ||
142 | if (waitpid(pid, &status, 0) == -1) | |
143 | fatal("waitpid"); | |
144 | if (WIFSIGNALED(status)) { | |
145 | log_warnx("%s: child got signal: %d", a->name, | |
146 | WTERMSIG(status)); | |
147 | return (1); | |
148 | } | |
149 | if (!WIFEXITED(status)) { | |
150 | log_warnx("%s: child didn't exit normally", a->name); | |
151 | return (1); | |
152 | } | |
153 | status = WEXITSTATUS(status); | |
154 | if (status != 0) { | |
155 | log_warnx("%s: child returned %d", a->name, status); | |
156 | return (1); | |
157 | } | |
158 | ||
159 | return (error); | |
160 | } | |
161 | ||
162 | #ifdef DEBUG | |
163 | xmalloc_clear(); | |
164 | COUNTFDS(a->name); | |
165 | #endif | |
166 | ||
167 | /* create privsep io */ | |
168 | close(fds[0]); | |
169 | io = io_create(fds[1], NULL, IO_LF, INFTIM); | |
170 | ||
171 | /* child process. change user and group */ | |
172 | if (geteuid() == 0) { | |
173 | if (dropto(uid) != 0) { | |
174 | log_warnx("%s: can't drop privileges", a->name); | |
175 | child_exit(1); | |
176 | } | |
177 | } else { | |
178 | log_debug2("%s: not root. using current user", a->name); | |
179 | uid = geteuid(); | |
180 | } | |
181 | #ifndef NO_SETPROCTITLE | |
182 | setproctitle("%s[%lu]", name, (u_long) uid); | |
183 | #endif | |
184 | ||
185 | /* refresh user and home and fix tags */ | |
186 | fill_info(NULL); | |
187 | update_tags(&m->tags); | |
188 | log_debug2("%s: user is: %s, home is: %s", a->name, conf.info.user, | |
189 | conf.info.home); | |
190 | ||
191 | /* call the hook */ | |
192 | if (hook(pid, a, &msg, hookdata, result) != 0) | |
193 | error = 1; | |
194 | ||
195 | /* inform parent we're done */ | |
196 | msg.type = MSG_DONE; | |
197 | msg.data.error = *result; | |
198 | if (privsep_send(io, &msg, m->tags, STRB_SIZE(m->tags)) != 0) | |
199 | fatalx("deliver: privsep_send error"); | |
200 | ||
201 | /* free the io */ | |
202 | io_close(io); | |
203 | io_free(io); | |
204 | ||
205 | #ifdef DEBUG | |
206 | COUNTFDS(a->name); | |
207 | xmalloc_report(a->name); | |
208 | #endif | |
209 | ||
210 | child_exit(error); | |
211 | fatalx("child_exit: failed"); | |
212 | } | |
213 | ||
214 | int | |
215 | do_parent(struct child *child) | |
216 | { | |
217 | struct msg msg; | |
218 | struct deliver_ctx dctx; | |
219 | struct match_ctx mctx; | |
220 | struct mail m; | |
221 | int error; | |
222 | void *buf; | |
223 | size_t len; | |
224 | ||
225 | memset(&m, 0, sizeof m); | |
226 | ||
227 | if (privsep_recv(child->io, &msg, &buf, &len) != 0) | |
228 | fatalx("parent: privsep_recv error"); | |
229 | log_debug2("parent: got message type %d from child %ld (%s)", msg.type, | |
230 | (long) child->pid, child->account->name); | |
231 | ||
232 | switch (msg.type) { | |
233 | case MSG_ACTION: | |
234 | if (buf == NULL || len == 0) | |
235 | fatalx("parent: bad tags"); | |
236 | parent_get(&m, &msg, buf, &dctx, NULL); | |
237 | error = parent_action(msg.data.action, &dctx, msg.data.uid); | |
238 | parent_done(child->io, &m, &msg, error); | |
239 | break; | |
240 | case MSG_COMMAND: | |
241 | if (buf == NULL || len == 0) | |
242 | fatalx("parent: bad tags"); | |
243 | parent_get(&m, &msg, buf, NULL, &mctx); | |
244 | error = parent_cmd(&mctx, msg.data.cmddata, msg.data.uid); | |
245 | parent_done(child->io, &m, &msg, error); | |
246 | break; | |
247 | case MSG_DONE: | |
248 | fatalx("parent: unexpected message"); | |
249 | case MSG_EXIT: | |
250 | return (1); | |
251 | } | |
252 | ||
253 | return (0); | |
254 | } | |
255 | ||
256 | int | |
257 | parent_action(struct action *t, struct deliver_ctx *dctx, uid_t uid) | |
258 | { | |
259 | struct account *a = dctx->account; | |
260 | struct mail *m = dctx->mail; | |
261 | struct mail *md = &dctx->wr_mail; | |
262 | struct parent_action_data ad; | |
263 | int result; | |
264 | ||
265 | ad.action = t; | |
266 | ad.dctx = dctx; | |
267 | ad.mail = m; | |
268 | ||
269 | memset(md, 0, sizeof *md); | |
270 | /* | |
271 | * If writing back, open a new mail now and set its ownership so it | |
272 | * can be accessed by the child. | |
273 | */ | |
274 | if (t->deliver->type == DELIVER_WRBACK) { | |
275 | mail_open(md, IO_BLOCKSIZE); | |
276 | if (geteuid() == 0 && | |
277 | fchown(md->shm.fd, conf.child_uid, conf.child_gid) != 0) | |
278 | fatal("fchown"); | |
279 | md->decision = m->decision; | |
280 | } | |
281 | ||
282 | if (parent_child(a, | |
283 | m, "deliver", uid, parent_action_hook, &ad, &result) != 0) | |
284 | return (DELIVER_FAILURE); | |
285 | return (result); | |
286 | } | |
287 | ||
288 | int | |
289 | parent_action_hook(pid_t pid, struct account *a, struct msg *msg, | |
290 | void *hookdata, int *result) | |
291 | { | |
292 | struct parent_action_data *ad = hookdata; | |
293 | struct action *t = ad->action; | |
294 | struct deliver_ctx *dctx = ad->dctx; | |
295 | struct mail *m = ad->mail; | |
296 | struct mail *md = &dctx->wr_mail; | |
297 | ||
298 | /* check if this is the parent */ | |
299 | if (pid != 0) { | |
300 | /* use new mail if necessary */ | |
301 | if (t->deliver->type != DELIVER_WRBACK) | |
302 | return (0); | |
303 | ||
304 | if (*result != DELIVER_SUCCESS) { | |
305 | mail_destroy(md); | |
306 | return (0); | |
307 | } | |
308 | ||
309 | mail_close(md); | |
310 | mail_receive(m, msg); | |
311 | log_debug2("%s: got new mail from delivery: size %zu, body %zd", | |
312 | a->name, m->size, m->body); | |
313 | ||
314 | return (0); | |
315 | } | |
316 | ||
317 | /* this is the child. do the delivery */ | |
318 | *result = t->deliver->deliver(dctx, t); | |
319 | if (t->deliver->type != DELIVER_WRBACK || *result != DELIVER_SUCCESS) | |
320 | return (0); | |
321 | ||
322 | mail_send(md, msg); | |
323 | log_debug2("%s: using new mail, size %zu", a->name, md->size); | |
324 | ||
325 | return (0); | |
326 | } | |
327 | ||
328 | int | |
329 | parent_cmd(struct match_ctx *mctx, struct match_command_data *data, | |
330 | uid_t uid) | |
331 | { | |
332 | struct account *a = mctx->account; | |
333 | struct mail *m = mctx->mail; | |
334 | struct parent_cmd_data cd; | |
335 | int result; | |
336 | ||
337 | cd.mctx = mctx; | |
338 | cd.data = data; | |
339 | ||
340 | if (parent_child(a, | |
341 | m, "command", uid, parent_cmd_hook, &cd, &result) != 0) | |
342 | return (MATCH_ERROR); | |
343 | return (result); | |
344 | } | |
345 | ||
346 | int | |
347 | parent_cmd_hook(pid_t pid, struct account *a, unused struct msg *msg, | |
348 | void *hookdata, int *result) | |
349 | { | |
350 | struct parent_cmd_data *cd = hookdata; | |
351 | struct match_ctx *mctx = cd->mctx; | |
352 | struct mail *m = mctx->mail; | |
353 | struct match_command_data *data = cd->data; | |
354 | int flags, status, found = 0; | |
355 | char *s, *cause, *lbuf, *out, *err, tag[24]; | |
356 | size_t llen; | |
357 | struct cmd *cmd = NULL; | |
358 | struct rmlist rml; | |
359 | u_int i; | |
360 | ||
361 | /* if this is the parent, do nothing */ | |
362 | if (pid != 0) | |
363 | return (0); | |
364 | ||
365 | /* sort out the command */ | |
366 | s = replacepath(&data->cmd, m->tags, m, &m->rml); | |
367 | if (s == NULL || *s == '\0') { | |
368 | log_warnx("%s: empty command", a->name); | |
369 | goto error; | |
370 | } | |
371 | ||
372 | log_debug2("%s: %s: started (ret=%d re=%s)", a->name, | |
373 | s, data->ret, data->re.str == NULL ? "none" : data->re.str); | |
374 | flags = CMD_ONCE; | |
375 | if (data->pipe) | |
376 | flags |= CMD_IN; | |
377 | if (data->re.str != NULL) | |
378 | flags |= CMD_OUT; | |
379 | cmd = cmd_start(s, flags, conf.timeout, m->data, m->size, &cause); | |
380 | if (cmd == NULL) { | |
381 | log_warnx("%s: %s: %s", a->name, s, cause); | |
382 | goto error; | |
383 | } | |
384 | ||
385 | llen = IO_LINESIZE; | |
386 | lbuf = xmalloc(llen); | |
387 | ||
388 | do { | |
389 | status = cmd_poll(cmd, &out, &err, &lbuf, &llen, &cause); | |
390 | if (status > 0) { | |
391 | log_warnx("%s: %s: %s", a->name, s, cause); | |
392 | goto error; | |
393 | } | |
394 | if (status < 0) | |
395 | break; | |
396 | if (err != NULL) | |
397 | log_warnx("%s: %s: %s", a->name, s, err); | |
398 | if (out == NULL) | |
399 | continue; | |
400 | log_debug3("%s: %s: out: %s", a->name, s, out); | |
401 | if (found) /* XXX stop early? */ | |
402 | continue; | |
403 | ||
404 | found = re_string(&data->re, out, &rml, &cause); | |
405 | if (found == -1) { | |
406 | log_warnx("%s: %s", a->name, cause); | |
407 | goto error; | |
408 | } | |
409 | if (found != 1) | |
410 | continue; | |
411 | /* save the matches */ | |
412 | if (!rml.valid) | |
413 | continue; | |
414 | for (i = 0; i < NPMATCH; i++) { | |
415 | if (!rml.list[i].valid) | |
416 | break; | |
417 | xsnprintf(tag, sizeof tag, "command%u", i); | |
418 | add_tag(&m->tags, tag, "%.*s", (int) (rml.list[i].eo - | |
419 | rml.list[i].so), out + rml.list[i].so); | |
420 | } | |
421 | } while (status >= 0); | |
422 | status = -1 - status; | |
423 | ||
424 | log_debug2("%s: %s: returned %d, found %d", a->name, s, status, found); | |
425 | ||
426 | cmd_free(cmd); | |
427 | xfree(s); | |
428 | xfree(lbuf); | |
429 | ||
430 | status = data->ret == status; | |
431 | if (data->ret != -1 && data->re.str != NULL) | |
432 | *result = (found && status) ? MATCH_TRUE : MATCH_FALSE; | |
433 | else if (data->ret != -1 && data->re.str == NULL) | |
434 | *result = status ? MATCH_TRUE : MATCH_FALSE; | |
435 | else if (data->ret == -1 && data->re.str != NULL) | |
436 | *result = found ? MATCH_TRUE : MATCH_FALSE; | |
437 | else | |
438 | *result = MATCH_ERROR; | |
439 | ||
440 | return (0); | |
441 | ||
442 | error: | |
443 | if (cause != NULL) | |
444 | xfree(cause); | |
445 | if (cmd != NULL) | |
446 | cmd_free(cmd); | |
447 | if (s != NULL) | |
448 | xfree(s); | |
449 | if (lbuf != NULL) | |
450 | xfree(lbuf); | |
451 | ||
452 | return (1); | |
453 | } |
22 | 22 | int |
23 | 23 | privsep_send(struct io *io, struct msg *msg, void *buf, size_t len) |
24 | 24 | { |
25 | char *cause; | |
26 | ||
25 | 27 | if (buf != NULL && len > 0) |
26 | 28 | msg->size = len; |
27 | 29 | else |
28 | 30 | msg->size = 0; |
29 | 31 | |
30 | 32 | io_write(io, msg, sizeof *msg); |
31 | if (io_flush(io, NULL) != 0) | |
33 | if (io_flush(io, &cause) != 0) | |
32 | 34 | return (1); |
33 | 35 | |
34 | 36 | if (buf != NULL && len > 0) { |
35 | 37 | io_write(io, buf, len); |
36 | if (io_flush(io, NULL) != 0) | |
38 | if (io_flush(io, &cause) != 0) | |
37 | 39 | return (1); |
38 | 40 | } |
39 | 41 |