GCC Code Coverage Report | |||||||||||||||||||||
|
|||||||||||||||||||||
Line | Branch | Exec | Source |
1 |
/* $OpenBSD: enqueue.c,v 1.113 2016/07/03 14:30:33 gilles Exp $ */ |
||
2 |
|||
3 |
/* |
||
4 |
* Copyright (c) 2005 Henning Brauer <henning@bulabula.org> |
||
5 |
* Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> |
||
6 |
* Copyright (c) 2012 Gilles Chehade <gilles@poolp.org> |
||
7 |
* |
||
8 |
* Permission to use, copy, modify, and distribute this software for any |
||
9 |
* purpose with or without fee is hereby granted, provided that the above |
||
10 |
* copyright notice and this permission notice appear in all copies. |
||
11 |
* |
||
12 |
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
||
13 |
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
||
14 |
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
||
15 |
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
||
16 |
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER |
||
17 |
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING |
||
18 |
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
||
19 |
*/ |
||
20 |
|||
21 |
#include <sys/types.h> |
||
22 |
#include <sys/queue.h> |
||
23 |
#include <sys/socket.h> |
||
24 |
#include <sys/tree.h> |
||
25 |
#include <sys/stat.h> |
||
26 |
|||
27 |
#include <ctype.h> |
||
28 |
#include <err.h> |
||
29 |
#include <errno.h> |
||
30 |
#include <event.h> |
||
31 |
#include <grp.h> |
||
32 |
#include <imsg.h> |
||
33 |
#include <inttypes.h> |
||
34 |
#include <pwd.h> |
||
35 |
#include <stdarg.h> |
||
36 |
#include <stdio.h> |
||
37 |
#include <stdlib.h> |
||
38 |
#include <string.h> |
||
39 |
#include <time.h> |
||
40 |
#include <unistd.h> |
||
41 |
#include <limits.h> |
||
42 |
|||
43 |
#include "smtpd.h" |
||
44 |
|||
45 |
extern struct imsgbuf *ibuf; |
||
46 |
|||
47 |
void usage(void); |
||
48 |
static void build_from(char *, struct passwd *); |
||
49 |
static int parse_message(FILE *, int, int, FILE *); |
||
50 |
static void parse_addr(char *, size_t, int); |
||
51 |
static void parse_addr_terminal(int); |
||
52 |
static char *qualify_addr(char *); |
||
53 |
static void rcpt_add(char *); |
||
54 |
static int open_connection(void); |
||
55 |
static int get_responses(FILE *, int); |
||
56 |
static int send_line(FILE *, int, char *, ...); |
||
57 |
static int enqueue_offline(int, char *[], FILE *, FILE *); |
||
58 |
static int savedeadletter(struct passwd *, FILE *); |
||
59 |
|||
60 |
extern int srv_connected(void); |
||
61 |
|||
62 |
enum headerfields { |
||
63 |
HDR_NONE, |
||
64 |
HDR_FROM, |
||
65 |
HDR_TO, |
||
66 |
HDR_CC, |
||
67 |
HDR_BCC, |
||
68 |
HDR_SUBJECT, |
||
69 |
HDR_DATE, |
||
70 |
HDR_MSGID, |
||
71 |
HDR_MIME_VERSION, |
||
72 |
HDR_CONTENT_TYPE, |
||
73 |
HDR_CONTENT_DISPOSITION, |
||
74 |
HDR_CONTENT_TRANSFER_ENCODING, |
||
75 |
HDR_USER_AGENT |
||
76 |
}; |
||
77 |
|||
78 |
struct { |
||
79 |
char *word; |
||
80 |
enum headerfields type; |
||
81 |
} keywords[] = { |
||
82 |
{ "From:", HDR_FROM }, |
||
83 |
{ "To:", HDR_TO }, |
||
84 |
{ "Cc:", HDR_CC }, |
||
85 |
{ "Bcc:", HDR_BCC }, |
||
86 |
{ "Subject:", HDR_SUBJECT }, |
||
87 |
{ "Date:", HDR_DATE }, |
||
88 |
{ "Message-Id:", HDR_MSGID }, |
||
89 |
{ "MIME-Version:", HDR_MIME_VERSION }, |
||
90 |
{ "Content-Type:", HDR_CONTENT_TYPE }, |
||
91 |
{ "Content-Disposition:", HDR_CONTENT_DISPOSITION }, |
||
92 |
{ "Content-Transfer-Encoding:", HDR_CONTENT_TRANSFER_ENCODING }, |
||
93 |
{ "User-Agent:", HDR_USER_AGENT }, |
||
94 |
}; |
||
95 |
|||
96 |
#define LINESPLIT 990 |
||
97 |
#define SMTP_LINELEN 1000 |
||
98 |
#define TIMEOUTMSG "Timeout\n" |
||
99 |
|||
100 |
#define WSP(c) (c == ' ' || c == '\t') |
||
101 |
|||
102 |
int verbose = 0; |
||
103 |
static char host[HOST_NAME_MAX+1]; |
||
104 |
char *user = NULL; |
||
105 |
time_t timestamp; |
||
106 |
|||
107 |
struct { |
||
108 |
int fd; |
||
109 |
char *from; |
||
110 |
char *fromname; |
||
111 |
char **rcpts; |
||
112 |
char *dsn_notify; |
||
113 |
char *dsn_ret; |
||
114 |
char *dsn_envid; |
||
115 |
int rcpt_cnt; |
||
116 |
int need_linesplit; |
||
117 |
int saw_date; |
||
118 |
int saw_msgid; |
||
119 |
int saw_from; |
||
120 |
int saw_mime_version; |
||
121 |
int saw_content_type; |
||
122 |
int saw_content_disposition; |
||
123 |
int saw_content_transfer_encoding; |
||
124 |
int saw_user_agent; |
||
125 |
int noheader; |
||
126 |
} msg; |
||
127 |
|||
128 |
struct { |
||
129 |
uint quote; |
||
130 |
uint comment; |
||
131 |
uint esc; |
||
132 |
uint brackets; |
||
133 |
size_t wpos; |
||
134 |
char buf[SMTP_LINELEN]; |
||
135 |
} pstate; |
||
136 |
|||
137 |
static void |
||
138 |
qp_encoded_write(FILE *fp, char *buf, size_t len) |
||
139 |
{ |
||
140 |
while (len) { |
||
141 |
if (*buf == '=') |
||
142 |
fprintf(fp, "=3D"); |
||
143 |
else if (*buf == ' ' || *buf == '\t') { |
||
144 |
char *p = buf; |
||
145 |
|||
146 |
while (*p != '\n') { |
||
147 |
if (*p != ' ' && *p != '\t') |
||
148 |
break; |
||
149 |
p++; |
||
150 |
} |
||
151 |
if (*p == '\n') |
||
152 |
fprintf(fp, "=%2X", *buf & 0xff); |
||
153 |
else |
||
154 |
fprintf(fp, "%c", *buf & 0xff); |
||
155 |
} |
||
156 |
else if (!isprint((unsigned char)*buf) && *buf != '\n') |
||
157 |
fprintf(fp, "=%2X", *buf & 0xff); |
||
158 |
else |
||
159 |
fprintf(fp, "%c", *buf); |
||
160 |
buf++; |
||
161 |
len--; |
||
162 |
} |
||
163 |
} |
||
164 |
|||
165 |
int |
||
166 |
enqueue(int argc, char *argv[], FILE *ofp) |
||
167 |
{ |
||
168 |
int i, ch, tflag = 0; |
||
169 |
4 |
char *fake_from = NULL, *buf = NULL; |
|
170 |
struct passwd *pw; |
||
171 |
FILE *fp = NULL, *fout; |
||
172 |
2 |
size_t sz = 0, envid_sz = 0; |
|
173 |
ssize_t len; |
||
174 |
int fd; |
||
175 |
2 |
char sfn[] = "/tmp/smtpd.XXXXXXXXXX"; |
|
176 |
char *line; |
||
177 |
int dotted; |
||
178 |
int inheaders = 1; |
||
179 |
int save_argc; |
||
180 |
char **save_argv; |
||
181 |
int no_getlogin = 0; |
||
182 |
|||
183 |
2 |
memset(&msg, 0, sizeof(msg)); |
|
184 |
2 |
time(×tamp); |
|
185 |
|||
186 |
save_argc = argc; |
||
187 |
save_argv = argv; |
||
188 |
|||
189 |
✓✓ | 14 |
while ((ch = getopt(argc, argv, |
190 |
6 |
"A:B:b:E::e:F:f:iJ::L:mN:o:p:qr:R:StvV:x")) != -1) { |
|
191 |
✗✗✗✗ ✗✗✗✗ ✗✗✓✗ ✗✗✗✗ ✗✓✗✗ ✗✗ |
6 |
switch (ch) { |
192 |
case 'f': |
||
193 |
fake_from = optarg; |
||
194 |
break; |
||
195 |
case 'F': |
||
196 |
msg.fromname = optarg; |
||
197 |
break; |
||
198 |
case 'N': |
||
199 |
msg.dsn_notify = optarg; |
||
200 |
break; |
||
201 |
case 'r': |
||
202 |
fake_from = optarg; |
||
203 |
break; |
||
204 |
case 'R': |
||
205 |
msg.dsn_ret = optarg; |
||
206 |
break; |
||
207 |
case 'S': |
||
208 |
no_getlogin = 1; |
||
209 |
break; |
||
210 |
case 't': |
||
211 |
tflag = 1; |
||
212 |
2 |
break; |
|
213 |
case 'v': |
||
214 |
verbose = 1; |
||
215 |
break; |
||
216 |
case 'V': |
||
217 |
msg.dsn_envid = optarg; |
||
218 |
break; |
||
219 |
/* all remaining: ignored, sendmail compat */ |
||
220 |
case 'A': |
||
221 |
case 'B': |
||
222 |
case 'b': |
||
223 |
case 'E': |
||
224 |
case 'e': |
||
225 |
case 'i': |
||
226 |
case 'L': |
||
227 |
case 'm': |
||
228 |
case 'o': |
||
229 |
case 'p': |
||
230 |
case 'x': |
||
231 |
break; |
||
232 |
case 'q': |
||
233 |
/* XXX: implement "process all now" */ |
||
234 |
return (EX_SOFTWARE); |
||
235 |
default: |
||
236 |
usage(); |
||
237 |
} |
||
238 |
} |
||
239 |
|||
240 |
2 |
argc -= optind; |
|
241 |
2 |
argv += optind; |
|
242 |
|||
243 |
✗✓ | 2 |
if (getmailname(host, sizeof(host)) == -1) |
244 |
errx(EX_NOHOST, "getmailname"); |
||
245 |
✗✓ | 2 |
if (no_getlogin) { |
246 |
if ((pw = getpwuid(getuid())) == NULL) |
||
247 |
user = "anonymous"; |
||
248 |
if (pw != NULL) |
||
249 |
user = xstrdup(pw->pw_name, "enqueue"); |
||
250 |
} |
||
251 |
else { |
||
252 |
2 |
uid_t ruid = getuid(); |
|
253 |
|||
254 |
✓✗✓✗ |
4 |
if ((user = getlogin()) != NULL && *user != '\0') { |
255 |
✓✗✗✗ |
2 |
if ((pw = getpwnam(user)) == NULL || |
256 |
✗✓ | 2 |
(ruid != 0 && ruid != pw->pw_uid)) |
257 |
pw = getpwuid(ruid); |
||
258 |
} else if ((pw = getpwuid(ruid)) == NULL) { |
||
259 |
user = "anonymous"; |
||
260 |
} |
||
261 |
2 |
user = xstrdup(pw ? pw->pw_name : user, "enqueue"); |
|
262 |
} |
||
263 |
|||
264 |
2 |
build_from(fake_from, pw); |
|
265 |
|||
266 |
✗✓ | 4 |
while (argc > 0) { |
267 |
rcpt_add(argv[0]); |
||
268 |
argv++; |
||
269 |
argc--; |
||
270 |
} |
||
271 |
|||
272 |
✓✗✗✓ |
4 |
if ((fd = mkstemp(sfn)) == -1 || |
273 |
2 |
(fp = fdopen(fd, "w+")) == NULL) { |
|
274 |
int saved_errno = errno; |
||
275 |
if (fd != -1) { |
||
276 |
unlink(sfn); |
||
277 |
close(fd); |
||
278 |
} |
||
279 |
errc(EX_UNAVAILABLE, saved_errno, "mkstemp"); |
||
280 |
} |
||
281 |
2 |
unlink(sfn); |
|
282 |
2 |
msg.noheader = parse_message(stdin, fake_from == NULL, tflag, fp); |
|
283 |
|||
284 |
✗✓ | 2 |
if (msg.rcpt_cnt == 0) |
285 |
errx(EX_SOFTWARE, "no recipients"); |
||
286 |
|||
287 |
/* init session */ |
||
288 |
2 |
rewind(fp); |
|
289 |
|||
290 |
/* check if working in offline mode */ |
||
291 |
/* If the server is not running, enqueue the message offline */ |
||
292 |
|||
293 |
✗✓ | 2 |
if (!srv_connected()) { |
294 |
if (pledge("stdio flock rpath cpath wpath", NULL) == -1) |
||
295 |
err(1, "pledge"); |
||
296 |
|||
297 |
return (enqueue_offline(save_argc, save_argv, fp, ofp)); |
||
298 |
} |
||
299 |
|||
300 |
✗✓ | 2 |
if ((msg.fd = open_connection()) == -1) |
301 |
errx(EX_UNAVAILABLE, "server too busy"); |
||
302 |
|||
303 |
✗✓ | 2 |
if (pledge("stdio wpath cpath flock rpath", NULL) == -1) |
304 |
err(1, "pledge"); |
||
305 |
|||
306 |
2 |
fout = fdopen(msg.fd, "a+"); |
|
307 |
✗✓ | 2 |
if (fout == NULL) |
308 |
err(EX_UNAVAILABLE, "fdopen"); |
||
309 |
|||
310 |
/* |
||
311 |
* We need to call get_responses after every command because we don't |
||
312 |
* support PIPELINING on the server-side yet. |
||
313 |
*/ |
||
314 |
|||
315 |
/* banner */ |
||
316 |
✓✗ | 2 |
if (!get_responses(fout, 1)) |
317 |
goto fail; |
||
318 |
|||
319 |
✓✗ | 2 |
if (!send_line(fout, verbose, "EHLO localhost\n")) |
320 |
goto fail; |
||
321 |
✓✗ | 2 |
if (!get_responses(fout, 1)) |
322 |
goto fail; |
||
323 |
|||
324 |
✗✓ | 2 |
if (msg.dsn_envid != NULL) |
325 |
envid_sz = strlen(msg.dsn_envid); |
||
326 |
|||
327 |
✓✗ | 4 |
if (!send_line(fout, verbose, "MAIL FROM:<%s> %s%s %s%s\n", |
328 |
2 |
msg.from, |
|
329 |
2 |
msg.dsn_ret ? "RET=" : "", |
|
330 |
2 |
msg.dsn_ret ? msg.dsn_ret : "", |
|
331 |
2 |
envid_sz ? "ENVID=" : "", |
|
332 |
2 |
envid_sz ? msg.dsn_envid : "")) |
|
333 |
goto fail; |
||
334 |
✓✗ | 2 |
if (!get_responses(fout, 1)) |
335 |
goto fail; |
||
336 |
|||
337 |
✓✓ | 8 |
for (i = 0; i < msg.rcpt_cnt; i++) { |
338 |
✓✗ | 2 |
if (!send_line(fout, verbose, "RCPT TO:<%s> %s%s\n", |
339 |
2 |
msg.rcpts[i], |
|
340 |
2 |
msg.dsn_notify ? "NOTIFY=" : "", |
|
341 |
2 |
msg.dsn_notify ? msg.dsn_notify : "")) |
|
342 |
goto fail; |
||
343 |
✓✗ | 2 |
if (!get_responses(fout, 1)) |
344 |
goto fail; |
||
345 |
} |
||
346 |
|||
347 |
✓✗ | 2 |
if (!send_line(fout, verbose, "DATA\n")) |
348 |
goto fail; |
||
349 |
✓✗ | 2 |
if (!get_responses(fout, 1)) |
350 |
goto fail; |
||
351 |
|||
352 |
/* add From */ |
||
353 |
✓✗✓✗ |
4 |
if (!msg.saw_from && !send_line(fout, 0, "From: %s%s<%s>\n", |
354 |
2 |
msg.fromname ? msg.fromname : "", msg.fromname ? " " : "", |
|
355 |
2 |
msg.from)) |
|
356 |
goto fail; |
||
357 |
|||
358 |
/* add Date */ |
||
359 |
✓✗✓✗ |
4 |
if (!msg.saw_date && !send_line(fout, 0, "Date: %s\n", |
360 |
2 |
time_to_text(timestamp))) |
|
361 |
goto fail; |
||
362 |
|||
363 |
✗✓ | 2 |
if (msg.need_linesplit) { |
364 |
/* we will always need to mime encode for long lines */ |
||
365 |
if (!msg.saw_mime_version && !send_line(fout, 0, |
||
366 |
"MIME-Version: 1.0\n")) |
||
367 |
goto fail; |
||
368 |
if (!msg.saw_content_type && !send_line(fout, 0, |
||
369 |
"Content-Type: text/plain; charset=unknown-8bit\n")) |
||
370 |
goto fail; |
||
371 |
if (!msg.saw_content_disposition && !send_line(fout, 0, |
||
372 |
"Content-Disposition: inline\n")) |
||
373 |
goto fail; |
||
374 |
if (!msg.saw_content_transfer_encoding && !send_line(fout, 0, |
||
375 |
"Content-Transfer-Encoding: quoted-printable\n")) |
||
376 |
goto fail; |
||
377 |
} |
||
378 |
|||
379 |
/* add separating newline */ |
||
380 |
✗✓ | 2 |
if (msg.noheader) { |
381 |
if (!send_line(fout, 0, "\n")) |
||
382 |
goto fail; |
||
383 |
inheaders = 0; |
||
384 |
} |
||
385 |
|||
386 |
for (;;) { |
||
387 |
✓✓ | 13055 |
if ((len = getline(&buf, &sz, fp)) == -1) { |
388 |
✓✗✗✓ ✗✗ |
4 |
if (feof(fp)) |
389 |
break; |
||
390 |
else |
||
391 |
err(EX_UNAVAILABLE, "getline"); |
||
392 |
} |
||
393 |
|||
394 |
/* newlines have been normalized on first parsing */ |
||
395 |
✗✓ | 13053 |
if (buf[len-1] != '\n') |
396 |
errx(EX_SOFTWARE, "expect EOL"); |
||
397 |
|||
398 |
dotted = 0; |
||
399 |
✗✓ | 13053 |
if (buf[0] == '.') { |
400 |
if (fputc('.', fout) == EOF) |
||
401 |
goto fail; |
||
402 |
dotted = 1; |
||
403 |
} |
||
404 |
|||
405 |
13053 |
line = buf; |
|
406 |
|||
407 |
✓✓ | 13053 |
if (inheaders) { |
408 |
✗✓ | 6 |
if (strncasecmp("from ", line, 5) == 0) |
409 |
continue; |
||
410 |
✗✓ | 6 |
if (strncasecmp("return-path: ", line, 13) == 0) |
411 |
continue; |
||
412 |
} |
||
413 |
|||
414 |
✓✗ | 26106 |
if (msg.saw_content_transfer_encoding || msg.noheader || |
415 |
13053 |
inheaders || !msg.need_linesplit) { |
|
416 |
✓✗ | 13053 |
if (!send_line(fout, 0, "%.*s", (int)len, line)) |
417 |
goto fail; |
||
418 |
✓✓✓✓ |
13059 |
if (inheaders && buf[0] == '\n') |
419 |
2 |
inheaders = 0; |
|
420 |
continue; |
||
421 |
} |
||
422 |
|||
423 |
/* we don't have a content transfer encoding, use our default */ |
||
424 |
do { |
||
425 |
if (len < LINESPLIT) { |
||
426 |
qp_encoded_write(fout, line, len); |
||
427 |
break; |
||
428 |
} |
||
429 |
else { |
||
430 |
qp_encoded_write(fout, line, |
||
431 |
LINESPLIT - 2 - dotted); |
||
432 |
if (!send_line(fout, 0, "=\n")) |
||
433 |
goto fail; |
||
434 |
line += LINESPLIT - 2 - dotted; |
||
435 |
len -= LINESPLIT - 2 - dotted; |
||
436 |
} |
||
437 |
} while (len); |
||
438 |
} |
||
439 |
2 |
free(buf); |
|
440 |
✓✗ | 2 |
if (!send_line(fout, verbose, ".\n")) |
441 |
goto fail; |
||
442 |
✓✗ | 2 |
if (!get_responses(fout, 1)) |
443 |
goto fail; |
||
444 |
|||
445 |
✓✗ | 2 |
if (!send_line(fout, verbose, "QUIT\n")) |
446 |
goto fail; |
||
447 |
✓✗ | 2 |
if (!get_responses(fout, 1)) |
448 |
goto fail; |
||
449 |
|||
450 |
fclose(fp); |
||
451 |
fclose(fout); |
||
452 |
|||
453 |
exit(EX_OK); |
||
454 |
|||
455 |
fail: |
||
456 |
if (pw) |
||
457 |
savedeadletter(pw, fp); |
||
458 |
exit(EX_SOFTWARE); |
||
459 |
} |
||
460 |
|||
461 |
static int |
||
462 |
get_responses(FILE *fin, int n) |
||
463 |
{ |
||
464 |
28 |
char *buf = NULL; |
|
465 |
14 |
size_t sz = 0; |
|
466 |
ssize_t len; |
||
467 |
int e, ret = 0; |
||
468 |
|||
469 |
14 |
fflush(fin); |
|
470 |
✓✗✗✓ |
42 |
if ((e = ferror(fin))) { |
471 |
warnx("ferror: %d", e); |
||
472 |
goto err; |
||
473 |
} |
||
474 |
|||
475 |
✓✓ | 50 |
while (n) { |
476 |
✗✓ | 22 |
if ((len = getline(&buf, &sz, fin)) == -1) { |
477 |
if (ferror(fin)) { |
||
478 |
warn("getline"); |
||
479 |
goto err; |
||
480 |
} else if (feof(fin)) |
||
481 |
break; |
||
482 |
else |
||
483 |
err(EX_UNAVAILABLE, "getline"); |
||
484 |
} |
||
485 |
|||
486 |
/* account for \r\n linebreaks */ |
||
487 |
✓✗✓✗ ✓✗ |
66 |
if (len >= 2 && buf[len - 2] == '\r' && buf[len - 1] == '\n') |
488 |
22 |
buf[--len - 1] = '\n'; |
|
489 |
|||
490 |
✗✓ | 22 |
if (len < 4) { |
491 |
warnx("bad response"); |
||
492 |
goto err; |
||
493 |
} |
||
494 |
|||
495 |
✗✓ | 22 |
if (verbose) |
496 |
printf("<<< %.*s", (int)len, buf); |
||
497 |
|||
498 |
✓✓ | 22 |
if (buf[3] == '-') |
499 |
continue; |
||
500 |
✓✓✗✓ |
16 |
if (buf[0] != '2' && buf[0] != '3') { |
501 |
warnx("command failed: %.*s", (int)len, buf); |
||
502 |
goto err; |
||
503 |
} |
||
504 |
14 |
n--; |
|
505 |
} |
||
506 |
|||
507 |
14 |
ret = 1; |
|
508 |
err: |
||
509 |
14 |
free(buf); |
|
510 |
14 |
return ret; |
|
511 |
14 |
} |
|
512 |
|||
513 |
static int |
||
514 |
send_line(FILE *fp, int v, char *fmt, ...) |
||
515 |
{ |
||
516 |
int ret = 0; |
||
517 |
52244 |
va_list ap; |
|
518 |
|||
519 |
26122 |
va_start(ap, fmt); |
|
520 |
✓✗ | 26122 |
if (vfprintf(fp, fmt, ap) >= 0) |
521 |
26122 |
ret = 1; |
|
522 |
26122 |
va_end(ap); |
|
523 |
|||
524 |
✗✓ | 26122 |
if (ret && v) { |
525 |
printf(">>> "); |
||
526 |
va_start(ap, fmt); |
||
527 |
vprintf(fmt, ap); |
||
528 |
va_end(ap); |
||
529 |
} |
||
530 |
|||
531 |
26122 |
return (ret); |
|
532 |
26122 |
} |
|
533 |
|||
534 |
static void |
||
535 |
build_from(char *fake_from, struct passwd *pw) |
||
536 |
{ |
||
537 |
char *p; |
||
538 |
|||
539 |
✓✗ | 4 |
if (fake_from == NULL) |
540 |
2 |
msg.from = qualify_addr(user); |
|
541 |
else { |
||
542 |
if (fake_from[0] == '<') { |
||
543 |
if (fake_from[strlen(fake_from) - 1] != '>') |
||
544 |
errx(1, "leading < but no trailing >"); |
||
545 |
fake_from[strlen(fake_from) - 1] = 0; |
||
546 |
p = xstrdup(fake_from + 1, "build_from"); |
||
547 |
|||
548 |
msg.from = qualify_addr(p); |
||
549 |
free(p); |
||
550 |
} else |
||
551 |
msg.from = qualify_addr(fake_from); |
||
552 |
} |
||
553 |
|||
554 |
✓✗ | 2 |
if (msg.fromname == NULL && fake_from == NULL && pw != NULL) { |
555 |
int len, apos; |
||
556 |
|||
557 |
2 |
len = strcspn(pw->pw_gecos, ","); |
|
558 |
✓✗ | 2 |
if ((p = memchr(pw->pw_gecos, '&', len))) { |
559 |
2 |
apos = p - pw->pw_gecos; |
|
560 |
✗✓ | 4 |
if (asprintf(&msg.fromname, "%.*s%s%.*s", |
561 |
apos, pw->pw_gecos, |
||
562 |
2 |
pw->pw_name, |
|
563 |
4 |
len - apos - 1, p + 1) == -1) |
|
564 |
err(1, NULL); |
||
565 |
2 |
msg.fromname[apos] = toupper((unsigned char)msg.fromname[apos]); |
|
566 |
2 |
} else { |
|
567 |
if (asprintf(&msg.fromname, "%.*s", len, |
||
568 |
pw->pw_gecos) == -1) |
||
569 |
err(1, NULL); |
||
570 |
} |
||
571 |
2 |
} |
|
572 |
2 |
} |
|
573 |
|||
574 |
static int |
||
575 |
parse_message(FILE *fin, int get_from, int tflag, FILE *fout) |
||
576 |
{ |
||
577 |
4 |
char *buf = NULL; |
|
578 |
2 |
size_t sz = 0; |
|
579 |
ssize_t len; |
||
580 |
uint i, cur = HDR_NONE; |
||
581 |
uint header_seen = 0, header_done = 0; |
||
582 |
|||
583 |
2 |
memset(&pstate, 0, sizeof(pstate)); |
|
584 |
2 |
for (;;) { |
|
585 |
✓✓ | 13055 |
if ((len = getline(&buf, &sz, fin)) == -1) { |
586 |
✓✗✗✓ ✗✗ |
4 |
if (feof(fin)) |
587 |
break; |
||
588 |
else |
||
589 |
err(EX_UNAVAILABLE, "getline"); |
||
590 |
} |
||
591 |
|||
592 |
/* account for \r\n linebreaks */ |
||
593 |
✓✓✗✓ ✗✗ |
25945 |
if (len >= 2 && buf[len - 2] == '\r' && buf[len - 1] == '\n') |
594 |
buf[--len - 1] = '\n'; |
||
595 |
|||
596 |
✓✓✓✗ |
13214 |
if (len == 1 && buf[0] == '\n') /* end of header */ |
597 |
161 |
header_done = 1; |
|
598 |
|||
599 |
✓✓✓✗ |
26104 |
if (!WSP(buf[0])) { /* whitespace -> continuation */ |
600 |
✗✓ | 13051 |
if (cur == HDR_FROM) |
601 |
parse_addr_terminal(1); |
||
602 |
✓✓ | 13051 |
if (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC) |
603 |
2 |
parse_addr_terminal(0); |
|
604 |
cur = HDR_NONE; |
||
605 |
13051 |
} |
|
606 |
|||
607 |
/* not really exact, if we are still in headers */ |
||
608 |
✗✓ | 13053 |
if (len + (buf[len - 1] == '\n' ? 0 : 1) >= LINESPLIT) |
609 |
msg.need_linesplit = 1; |
||
610 |
|||
611 |
✓✓✓✗ |
26148 |
for (i = 0; !header_done && cur == HDR_NONE && |
612 |
28 |
i < nitems(keywords); i++) |
|
613 |
✓✗✓✓ |
28 |
if ((size_t)len > strlen(keywords[i].word) && |
614 |
28 |
!strncasecmp(buf, keywords[i].word, |
|
615 |
14 |
strlen(keywords[i].word))) |
|
616 |
4 |
cur = keywords[i].type; |
|
617 |
|||
618 |
✓✓ | 13053 |
if (cur != HDR_NONE) |
619 |
4 |
header_seen = 1; |
|
620 |
|||
621 |
✓✗ | 13053 |
if (cur != HDR_BCC) { |
622 |
✗✓ | 13053 |
if (!send_line(fout, 0, "%.*s", (int)len, buf)) |
623 |
err(1, "write error"); |
||
624 |
✗✓ | 13053 |
if (buf[len - 1] != '\n') { |
625 |
if (fputc('\n', fout) == EOF) |
||
626 |
err(1, "write error"); |
||
627 |
} |
||
628 |
} |
||
629 |
|||
630 |
/* |
||
631 |
* using From: as envelope sender is not sendmail compatible, |
||
632 |
* but I really want it that way - maybe needs a knob |
||
633 |
*/ |
||
634 |
✗✓ | 13053 |
if (cur == HDR_FROM) { |
635 |
msg.saw_from++; |
||
636 |
if (get_from) |
||
637 |
parse_addr(buf, len, 1); |
||
638 |
} |
||
639 |
|||
640 |
✓✗✓✓ |
26106 |
if (tflag && (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC)) |
641 |
2 |
parse_addr(buf, len, 0); |
|
642 |
|||
643 |
✗✓ | 13053 |
if (cur == HDR_DATE) |
644 |
msg.saw_date++; |
||
645 |
✗✓ | 13053 |
if (cur == HDR_MSGID) |
646 |
msg.saw_msgid++; |
||
647 |
✗✓ | 13053 |
if (cur == HDR_MIME_VERSION) |
648 |
msg.saw_mime_version = 1; |
||
649 |
✗✓ | 13053 |
if (cur == HDR_CONTENT_TYPE) |
650 |
msg.saw_content_type = 1; |
||
651 |
✗✓ | 13053 |
if (cur == HDR_CONTENT_DISPOSITION) |
652 |
msg.saw_content_disposition = 1; |
||
653 |
✗✓ | 13053 |
if (cur == HDR_CONTENT_TRANSFER_ENCODING) |
654 |
msg.saw_content_transfer_encoding = 1; |
||
655 |
✓✗ | 13053 |
if (cur == HDR_USER_AGENT) |
656 |
msg.saw_user_agent = 1; |
||
657 |
} |
||
658 |
|||
659 |
2 |
free(buf); |
|
660 |
4 |
return (!header_seen); |
|
661 |
2 |
} |
|
662 |
|||
663 |
static void |
||
664 |
parse_addr(char *s, size_t len, int is_from) |
||
665 |
{ |
||
666 |
size_t pos = 0; |
||
667 |
int terminal = 0; |
||
668 |
|||
669 |
/* unless this is a continuation... */ |
||
670 |
✓✗✓✗ ✓✗✓✗ |
10 |
if (!WSP(s[pos]) && s[pos] != ',' && s[pos] != ';') { |
671 |
/* ... skip over everything before the ':' */ |
||
672 |
✓✗✓✓ |
16 |
for (; pos < len && s[pos] != ':'; pos++) |
673 |
; /* nothing */ |
||
674 |
/* ... and check & reset parser state */ |
||
675 |
2 |
parse_addr_terminal(is_from); |
|
676 |
2 |
} |
|
677 |
|||
678 |
/* skip over ':' ',' ';' and whitespace */ |
||
679 |
✓✗✓✓ ✓✗✓✓ ✗✓ |
26 |
for (; pos < len && !pstate.quote && (WSP(s[pos]) || s[pos] == ':' || |
680 |
✓✗ | 8 |
s[pos] == ',' || s[pos] == ';'); pos++) |
681 |
; /* nothing */ |
||
682 |
|||
683 |
✓✗ | 18 |
for (; pos < len; pos++) { |
684 |
✓✗✗✓ |
20 |
if (!pstate.esc && !pstate.quote && s[pos] == '(') |
685 |
pstate.comment++; |
||
686 |
✓✗✗✓ |
20 |
if (!pstate.comment && !pstate.esc && s[pos] == '"') |
687 |
pstate.quote = !pstate.quote; |
||
688 |
|||
689 |
✓✗ | 10 |
if (!pstate.comment && !pstate.quote && !pstate.esc) { |
690 |
✗✓ | 10 |
if (s[pos] == ':') { /* group */ |
691 |
for (pos++; pos < len && WSP(s[pos]); pos++) |
||
692 |
; /* nothing */ |
||
693 |
pstate.wpos = 0; |
||
694 |
} |
||
695 |
✓✓✓✗ |
18 |
if (s[pos] == '\n' || s[pos] == '\r') |
696 |
break; |
||
697 |
✓✗✗✓ |
16 |
if (s[pos] == ',' || s[pos] == ';') { |
698 |
terminal = 1; |
||
699 |
break; |
||
700 |
} |
||
701 |
✗✓ | 8 |
if (s[pos] == '<') { |
702 |
pstate.brackets = 1; |
||
703 |
pstate.wpos = 0; |
||
704 |
} |
||
705 |
✗✓✗✗ |
8 |
if (pstate.brackets && s[pos] == '>') |
706 |
terminal = 1; |
||
707 |
} |
||
708 |
|||
709 |
✓✗✓✗ ✓✗ |
32 |
if (!pstate.comment && !terminal && (!(!(pstate.quote || |
710 |
✓✗✓✗ |
32 |
pstate.esc) && (s[pos] == '<' || WSP(s[pos]))))) { |
711 |
✗✓ | 8 |
if (pstate.wpos >= sizeof(pstate.buf)) |
712 |
errx(1, "address exceeds buffer size"); |
||
713 |
8 |
pstate.buf[pstate.wpos++] = s[pos]; |
|
714 |
8 |
} |
|
715 |
|||
716 |
✗✓✗✗ |
8 |
if (!pstate.quote && pstate.comment && s[pos] == ')') |
717 |
pstate.comment--; |
||
718 |
|||
719 |
✓✗ | 16 |
if (!pstate.esc && !pstate.comment && !pstate.quote && |
720 |
8 |
s[pos] == '\\') |
|
721 |
pstate.esc = 1; |
||
722 |
else |
||
723 |
pstate.esc = 0; |
||
724 |
} |
||
725 |
|||
726 |
✗✓ | 2 |
if (terminal) |
727 |
parse_addr_terminal(is_from); |
||
728 |
|||
729 |
✓✓✓✗ ✓✗ |
10 |
for (; pos < len && (s[pos] == '\r' || s[pos] == '\n'); pos++) |
730 |
; /* nothing */ |
||
731 |
|||
732 |
✗✓ | 2 |
if (pos < len) |
733 |
parse_addr(s + pos, len - pos, is_from); |
||
734 |
2 |
} |
|
735 |
|||
736 |
static void |
||
737 |
parse_addr_terminal(int is_from) |
||
738 |
{ |
||
739 |
✗✓ | 8 |
if (pstate.comment || pstate.quote || pstate.esc) |
740 |
errx(1, "syntax error in address"); |
||
741 |
✓✓ | 4 |
if (pstate.wpos) { |
742 |
✗✓ | 2 |
if (pstate.wpos >= sizeof(pstate.buf)) |
743 |
errx(1, "address exceeds buffer size"); |
||
744 |
2 |
pstate.buf[pstate.wpos] = '\0'; |
|
745 |
✗✓ | 2 |
if (is_from) |
746 |
msg.from = qualify_addr(pstate.buf); |
||
747 |
else |
||
748 |
2 |
rcpt_add(pstate.buf); |
|
749 |
2 |
pstate.wpos = 0; |
|
750 |
2 |
} |
|
751 |
4 |
} |
|
752 |
|||
753 |
static char * |
||
754 |
qualify_addr(char *in) |
||
755 |
{ |
||
756 |
8 |
char *out; |
|
757 |
|||
758 |
✓✗✓✗ |
8 |
if (strlen(in) > 0 && strchr(in, '@') == NULL) { |
759 |
✗✓ | 4 |
if (asprintf(&out, "%s@%s", in, host) == -1) |
760 |
err(1, "qualify asprintf"); |
||
761 |
} else |
||
762 |
out = xstrdup(in, "qualify_addr"); |
||
763 |
|||
764 |
8 |
return (out); |
|
765 |
4 |
} |
|
766 |
|||
767 |
static void |
||
768 |
rcpt_add(char *addr) |
||
769 |
{ |
||
770 |
void *nrcpts; |
||
771 |
char *p; |
||
772 |
int n; |
||
773 |
|||
774 |
n = 1; |
||
775 |
p = addr; |
||
776 |
✗✓ | 6 |
while ((p = strchr(p, ',')) != NULL) { |
777 |
n++; |
||
778 |
p++; |
||
779 |
} |
||
780 |
|||
781 |
✗✓ | 6 |
if ((nrcpts = reallocarray(msg.rcpts, |
782 |
4 |
msg.rcpt_cnt + n, sizeof(char *))) == NULL) |
|
783 |
err(1, "rcpt_add realloc"); |
||
784 |
2 |
msg.rcpts = nrcpts; |
|
785 |
|||
786 |
✓✗ | 4 |
while (n--) { |
787 |
✗✓ | 2 |
if ((p = strchr(addr, ',')) != NULL) |
788 |
*p++ = '\0'; |
||
789 |
2 |
msg.rcpts[msg.rcpt_cnt++] = qualify_addr(addr); |
|
790 |
✗✓ | 2 |
if (p == NULL) |
791 |
break; |
||
792 |
addr = p; |
||
793 |
} |
||
794 |
2 |
} |
|
795 |
|||
796 |
static int |
||
797 |
open_connection(void) |
||
798 |
{ |
||
799 |
4 |
struct imsg imsg; |
|
800 |
int fd; |
||
801 |
int n; |
||
802 |
|||
803 |
2 |
imsg_compose(ibuf, IMSG_CTL_SMTP_SESSION, IMSG_VERSION, 0, -1, NULL, 0); |
|
804 |
|||
805 |
✓✓ | 6 |
while (ibuf->w.queued) |
806 |
✓✗✗✗ |
2 |
if (msgbuf_write(&ibuf->w) <= 0 && errno != EAGAIN) |
807 |
err(1, "write error"); |
||
808 |
|||
809 |
while (1) { |
||
810 |
✗✓✗✗ |
2 |
if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) |
811 |
errx(1, "imsg_read error"); |
||
812 |
✗✓ | 2 |
if (n == 0) |
813 |
errx(1, "pipe closed"); |
||
814 |
|||
815 |
✗✓ | 2 |
if ((n = imsg_get(ibuf, &imsg)) == -1) |
816 |
errx(1, "imsg_get error"); |
||
817 |
✗✓ | 2 |
if (n == 0) |
818 |
continue; |
||
819 |
|||
820 |
✗✗✓ | 2 |
switch (imsg.hdr.type) { |
821 |
case IMSG_CTL_OK: |
||
822 |
break; |
||
823 |
case IMSG_CTL_FAIL: |
||
824 |
errx(1, "server disallowed submission request"); |
||
825 |
default: |
||
826 |
errx(1, "unexpected imsg reply type"); |
||
827 |
} |
||
828 |
|||
829 |
2 |
fd = imsg.fd; |
|
830 |
2 |
imsg_free(&imsg); |
|
831 |
|||
832 |
break; |
||
833 |
} |
||
834 |
|||
835 |
2 |
return fd; |
|
836 |
2 |
} |
|
837 |
|||
838 |
static int |
||
839 |
enqueue_offline(int argc, char *argv[], FILE *ifile, FILE *ofile) |
||
840 |
{ |
||
841 |
int i, ch; |
||
842 |
|||
843 |
for (i = 1; i < argc; i++) { |
||
844 |
if (strchr(argv[i], '|') != NULL) { |
||
845 |
warnx("%s contains illegal character", argv[i]); |
||
846 |
ftruncate(fileno(ofile), 0); |
||
847 |
exit(EX_SOFTWARE); |
||
848 |
} |
||
849 |
if (fprintf(ofile, "%s%s", i == 1 ? "" : "|", argv[i]) < 0) |
||
850 |
goto write_error; |
||
851 |
} |
||
852 |
|||
853 |
if (fputc('\n', ofile) == EOF) |
||
854 |
goto write_error; |
||
855 |
|||
856 |
while ((ch = fgetc(ifile)) != EOF) { |
||
857 |
if (fputc(ch, ofile) == EOF) |
||
858 |
goto write_error; |
||
859 |
} |
||
860 |
|||
861 |
if (ferror(ifile)) { |
||
862 |
warn("read error"); |
||
863 |
ftruncate(fileno(ofile), 0); |
||
864 |
exit(EX_UNAVAILABLE); |
||
865 |
} |
||
866 |
|||
867 |
if (fclose(ofile) == EOF) |
||
868 |
goto write_error; |
||
869 |
|||
870 |
return (EX_TEMPFAIL); |
||
871 |
write_error: |
||
872 |
warn("write error"); |
||
873 |
ftruncate(fileno(ofile), 0); |
||
874 |
exit(EX_UNAVAILABLE); |
||
875 |
} |
||
876 |
|||
877 |
static int |
||
878 |
savedeadletter(struct passwd *pw, FILE *in) |
||
879 |
{ |
||
880 |
char buffer[PATH_MAX]; |
||
881 |
FILE *fp; |
||
882 |
char *buf = NULL; |
||
883 |
size_t sz = 0; |
||
884 |
ssize_t len; |
||
885 |
|||
886 |
(void)snprintf(buffer, sizeof buffer, "%s/dead.letter", pw->pw_dir); |
||
887 |
|||
888 |
if (fseek(in, 0, SEEK_SET) != 0) |
||
889 |
return 0; |
||
890 |
|||
891 |
if ((fp = fopen(buffer, "w")) == NULL) |
||
892 |
return 0; |
||
893 |
|||
894 |
/* add From */ |
||
895 |
if (!msg.saw_from) |
||
896 |
fprintf(fp, "From: %s%s<%s>\n", |
||
897 |
msg.fromname ? msg.fromname : "", |
||
898 |
msg.fromname ? " " : "", |
||
899 |
msg.from); |
||
900 |
|||
901 |
/* add Date */ |
||
902 |
if (!msg.saw_date) |
||
903 |
fprintf(fp, "Date: %s\n", time_to_text(timestamp)); |
||
904 |
|||
905 |
if (msg.need_linesplit) { |
||
906 |
/* we will always need to mime encode for long lines */ |
||
907 |
if (!msg.saw_mime_version) |
||
908 |
fprintf(fp, "MIME-Version: 1.0\n"); |
||
909 |
if (!msg.saw_content_type) |
||
910 |
fprintf(fp, "Content-Type: text/plain; " |
||
911 |
"charset=unknown-8bit\n"); |
||
912 |
if (!msg.saw_content_disposition) |
||
913 |
fprintf(fp, "Content-Disposition: inline\n"); |
||
914 |
if (!msg.saw_content_transfer_encoding) |
||
915 |
fprintf(fp, "Content-Transfer-Encoding: " |
||
916 |
"quoted-printable\n"); |
||
917 |
} |
||
918 |
|||
919 |
/* add separating newline */ |
||
920 |
if (msg.noheader) |
||
921 |
fprintf(fp, "\n"); |
||
922 |
|||
923 |
while ((len = getline(&buf, &sz, in)) != -1) { |
||
924 |
if (buf[len - 1] == '\n') |
||
925 |
buf[len - 1] = '\0'; |
||
926 |
fprintf(fp, "%s\n", buf); |
||
927 |
} |
||
928 |
|||
929 |
free(buf); |
||
930 |
fprintf(fp, "\n"); |
||
931 |
fclose(fp); |
||
932 |
return 1; |
||
933 |
} |
Generated by: GCOVR (Version 3.3) |