1 /* $OpenBSD: enqueue.c,v 1.26 2009/11/13 20:34:51 chl Exp $ */ 2 3 /* 4 * Copyright (c) 2005 Henning Brauer <henning@bulabula.org> 5 * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> 6 * 7 * Permission to use, copy, modify, and distribute this software for any 8 * purpose with or without fee is hereby granted, provided that the above 9 * copyright notice and this permission notice appear in all copies. 10 * 11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 16 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 17 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 */ 19 20 #include <sys/param.h> 21 #include <sys/queue.h> 22 #include <sys/socket.h> 23 #include <sys/tree.h> 24 #include <sys/types.h> 25 26 #include <ctype.h> 27 #include <err.h> 28 #include <errno.h> 29 #include <event.h> 30 #include <netdb.h> 31 #include <pwd.h> 32 #include <signal.h> 33 #include <stdarg.h> 34 #include <stdio.h> 35 #include <stdlib.h> 36 #include <string.h> 37 #include <time.h> 38 #include <unistd.h> 39 40 #include "smtpd.h" 41 #include "client.h" 42 43 extern struct imsgbuf *ibuf; 44 45 void usage(void); 46 void sighdlr(int); 47 int main(int, char *[]); 48 void build_from(char *, struct passwd *); 49 int parse_message(FILE *, int, int, struct buf *); 50 void parse_addr(char *, size_t, int); 51 void parse_addr_terminal(int); 52 char *qualify_addr(char *); 53 void rcpt_add(char *); 54 int open_connection(void); 55 56 enum headerfields { 57 HDR_NONE, 58 HDR_FROM, 59 HDR_TO, 60 HDR_CC, 61 HDR_BCC, 62 HDR_SUBJECT, 63 HDR_DATE, 64 HDR_MSGID 65 }; 66 67 struct { 68 char *word; 69 enum headerfields type; 70 } keywords[] = { 71 { "From:", HDR_FROM }, 72 { "To:", HDR_TO }, 73 { "Cc:", HDR_CC }, 74 { "Bcc:", HDR_BCC }, 75 { "Subject:", HDR_SUBJECT }, 76 { "Date:", HDR_DATE }, 77 { "Message-Id:", HDR_MSGID } 78 }; 79 80 #define SMTP_LINELEN 1000 81 #define SMTP_TIMEOUT 120 82 #define TIMEOUTMSG "Timeout\n" 83 84 #define WSP(c) (c == ' ' || c == '\t') 85 86 int verbose = 0; 87 char host[MAXHOSTNAMELEN]; 88 char *user = NULL; 89 time_t timestamp; 90 91 struct { 92 int fd; 93 char *from; 94 char *fromname; 95 char **rcpts; 96 int rcpt_cnt; 97 char *data; 98 size_t len; 99 int saw_date; 100 int saw_msgid; 101 int saw_from; 102 } msg; 103 104 struct { 105 u_int quote; 106 u_int comment; 107 u_int esc; 108 u_int brackets; 109 size_t wpos; 110 char buf[SMTP_LINELEN]; 111 } pstate; 112 113 void 114 sighdlr(int sig) 115 { 116 if (sig == SIGALRM) { 117 write(STDERR_FILENO, TIMEOUTMSG, sizeof(TIMEOUTMSG)); 118 _exit (2); 119 } 120 } 121 122 int 123 enqueue(int argc, char *argv[]) 124 { 125 int i, ch, tflag = 0, noheader, ret; 126 char *fake_from = NULL; 127 struct passwd *pw; 128 struct smtp_client *sp; 129 struct buf *body; 130 131 bzero(&msg, sizeof(msg)); 132 time(×tamp); 133 134 while ((ch = getopt(argc, argv, 135 "A:B:b:E::e:F:f:iJ::L:mo:p:qtvx")) != -1) { 136 switch (ch) { 137 case 'f': 138 fake_from = optarg; 139 break; 140 case 'F': 141 msg.fromname = optarg; 142 break; 143 case 't': 144 tflag = 1; 145 break; 146 case 'v': 147 verbose = 1; 148 break; 149 /* all remaining: ignored, sendmail compat */ 150 case 'A': 151 case 'B': 152 case 'b': 153 case 'E': 154 case 'e': 155 case 'i': 156 case 'L': 157 case 'm': 158 case 'o': 159 case 'p': 160 case 'x': 161 break; 162 case 'q': 163 /* XXX: implement "process all now" */ 164 return (0); 165 default: 166 usage(); 167 } 168 } 169 170 argc -= optind; 171 argv += optind; 172 173 if (gethostname(host, sizeof(host)) == -1) 174 err(1, "gethostname"); 175 if ((pw = getpwuid(getuid())) == NULL) 176 user = "anonymous"; 177 if (pw != NULL && (user = strdup(pw->pw_name)) == NULL) 178 err(1, "strdup"); 179 180 build_from(fake_from, pw); 181 182 while(argc > 0) { 183 rcpt_add(argv[0]); 184 argv++; 185 argc--; 186 } 187 188 signal(SIGALRM, sighdlr); 189 alarm(SMTP_TIMEOUT); 190 191 msg.fd = open_connection(); 192 193 /* init session */ 194 if ((sp = client_init(msg.fd, "localhost")) == NULL) 195 err(1, "client_init failed"); 196 if (verbose) 197 client_verbose(sp, stdout); 198 199 /* parse message */ 200 if ((body = buf_dynamic(0, SIZE_T_MAX)) < 0) 201 err(1, "buf_dynamic failed"); 202 noheader = parse_message(stdin, fake_from == NULL, tflag, body); 203 204 /* set envelope from */ 205 if (client_sender(sp, "%s", msg.from) < 0) 206 err(1, "client_sender failed"); 207 208 /* add recipients */ 209 if (msg.rcpt_cnt == 0) 210 errx(1, "no recipients"); 211 for (i = 0; i < msg.rcpt_cnt; i++) 212 if (client_rcpt(sp, "%s", msg.rcpts[i]) < 0) 213 err(1, "client_rcpt failed"); 214 215 /* add From */ 216 if (!msg.saw_from) { 217 if (msg.fromname != NULL) { 218 if (client_data_printf(sp, 219 "From: %s <%s>\n", msg.fromname, msg.from) < 0) 220 err(1, "client_data_printf failed"); 221 } else 222 if (client_data_printf(sp, 223 "From: %s\n", msg.from) < 0) 224 err(1, "client_data_printf failed"); 225 } 226 227 /* add Date */ 228 if (!msg.saw_date) 229 if (client_data_printf(sp, 230 "Date: %s\n", time_to_text(timestamp)) < 0) 231 err(1, "client_data_printf failed"); 232 233 /* add Message-Id */ 234 if (!msg.saw_msgid) 235 if (client_data_printf(sp, 236 "Message-Id: <%llu.enqueue@%s>\n", 237 generate_uid(), host) < 0) 238 err(1, "client_data_printf failed"); 239 240 /* add separating newline */ 241 if (noheader) 242 if (client_data_printf(sp, "\n") < 0) 243 err(1, "client_data_printf failed"); 244 245 if (client_data_printf(sp, "%.*s", buf_size(body), body->buf) < 0) 246 err(1, "client_data_printf failed"); 247 buf_free(body); 248 249 /* run the protocol engine */ 250 for (;;) { 251 while ((ret = client_read(sp)) == CLIENT_WANT_READ) 252 ; 253 if (ret == CLIENT_ERROR) 254 errx(1, "read error: %s", client_strerror(sp)); 255 if (ret == CLIENT_RCPT_FAIL) 256 errx(1, "recipient refused: %s", client_reply(sp)); 257 if (ret == CLIENT_DONE) 258 break; 259 while ((ret = client_write(sp)) == CLIENT_WANT_WRITE) 260 ; 261 if (ret == CLIENT_ERROR) 262 errx(1, "write error: %s", client_strerror(sp)); 263 } 264 265 client_close(sp); 266 267 close(msg.fd); 268 exit (0); 269 } 270 271 void 272 build_from(char *fake_from, struct passwd *pw) 273 { 274 char *p; 275 276 if (fake_from == NULL) 277 msg.from = qualify_addr(user); 278 else { 279 if (fake_from[0] == '<') { 280 if (fake_from[strlen(fake_from) - 1] != '>') 281 errx(1, "leading < but no trailing >"); 282 fake_from[strlen(fake_from) - 1] = 0; 283 if ((p = malloc(strlen(fake_from))) == NULL) 284 err(1, "malloc"); 285 strlcpy(p, fake_from + 1, strlen(fake_from)); 286 287 msg.from = qualify_addr(p); 288 free(p); 289 } else 290 msg.from = qualify_addr(fake_from); 291 } 292 293 if (msg.fromname == NULL && fake_from == NULL && pw != NULL) { 294 int len, apos; 295 296 len = strcspn(pw->pw_gecos, ","); 297 if ((p = memchr(pw->pw_gecos, '&', len))) { 298 apos = p - pw->pw_gecos; 299 if (asprintf(&msg.fromname, "%.*s%s%.*s", 300 apos, pw->pw_gecos, 301 pw->pw_name, 302 len - apos - 1, p + 1) == -1) 303 err(1, NULL); 304 msg.fromname[apos] = toupper(msg.fromname[apos]); 305 } else { 306 if (asprintf(&msg.fromname, "%.*s", len, 307 pw->pw_gecos) == -1) 308 err(1, NULL); 309 } 310 } 311 } 312 313 int 314 parse_message(FILE *fin, int get_from, int tflag, struct buf *body) 315 { 316 char *buf; 317 size_t len; 318 u_int i, cur = HDR_NONE; 319 u_int header_seen = 0, header_done = 0; 320 321 bzero(&pstate, sizeof(pstate)); 322 for (;;) { 323 buf = fgetln(fin, &len); 324 if (buf == NULL && ferror(fin)) 325 err(1, "fgetln"); 326 if (buf == NULL && feof(fin)) 327 break; 328 329 /* account for \r\n linebreaks */ 330 if (len >= 2 && buf[len - 2] == '\r' && buf[len - 1] == '\n') 331 buf[--len - 1] = '\n'; 332 333 if (len == 1 && buf[0] == '\n') /* end of header */ 334 header_done = 1; 335 336 if (buf == NULL || len < 1) 337 err(1, "fgetln weird"); 338 339 if (!WSP(buf[0])) { /* whitespace -> continuation */ 340 if (cur == HDR_FROM) 341 parse_addr_terminal(1); 342 if (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC) 343 parse_addr_terminal(0); 344 cur = HDR_NONE; 345 } 346 347 for (i = 0; !header_done && cur == HDR_NONE && 348 i < nitems(keywords); i++) 349 if (len > strlen(keywords[i].word) && 350 !strncasecmp(buf, keywords[i].word, 351 strlen(keywords[i].word))) 352 cur = keywords[i].type; 353 354 if (cur != HDR_NONE) 355 header_seen = 1; 356 357 if (cur != HDR_BCC) { 358 if (buf_add(body, buf, len) < 0) 359 err(1, "buf_add failed"); 360 if (buf[len - 1] != '\n' && buf_add(body, "\n", 1) < 0) 361 err(1, "buf_add failed"); 362 } 363 364 /* 365 * using From: as envelope sender is not sendmail compatible, 366 * but I really want it that way - maybe needs a knob 367 */ 368 if (cur == HDR_FROM) { 369 msg.saw_from++; 370 if (get_from) 371 parse_addr(buf, len, 1); 372 } 373 374 if (tflag && (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC)) 375 parse_addr(buf, len, 0); 376 377 if (cur == HDR_DATE) 378 msg.saw_date++; 379 if (cur == HDR_MSGID) 380 msg.saw_msgid++; 381 } 382 383 return (!header_seen); 384 } 385 386 void 387 parse_addr(char *s, size_t len, int is_from) 388 { 389 size_t pos = 0; 390 int terminal = 0; 391 392 /* unless this is a continuation... */ 393 if (!WSP(s[pos]) && s[pos] != ',' && s[pos] != ';') { 394 /* ... skip over everything before the ':' */ 395 for (; pos < len && s[pos] != ':'; pos++) 396 ; /* nothing */ 397 /* ... and check & reset parser state */ 398 parse_addr_terminal(is_from); 399 } 400 401 /* skip over ':' ',' ';' and whitespace */ 402 for (; pos < len && !pstate.quote && (WSP(s[pos]) || s[pos] == ':' || 403 s[pos] == ',' || s[pos] == ';'); pos++) 404 ; /* nothing */ 405 406 for (; pos < len; pos++) { 407 if (!pstate.esc && !pstate.quote && s[pos] == '(') 408 pstate.comment++; 409 if (!pstate.comment && !pstate.esc && s[pos] == '"') 410 pstate.quote = !pstate.quote; 411 412 if (!pstate.comment && !pstate.quote && !pstate.esc) { 413 if (s[pos] == ':') { /* group */ 414 for(pos++; pos < len && WSP(s[pos]); pos++) 415 ; /* nothing */ 416 pstate.wpos = 0; 417 } 418 if (s[pos] == '\n' || s[pos] == '\r') 419 break; 420 if (s[pos] == ',' || s[pos] == ';') { 421 terminal = 1; 422 break; 423 } 424 if (s[pos] == '<') { 425 pstate.brackets = 1; 426 pstate.wpos = 0; 427 } 428 if (pstate.brackets && s[pos] == '>') 429 terminal = 1; 430 } 431 432 if (!pstate.comment && !terminal && (!(!(pstate.quote || 433 pstate.esc) && (s[pos] == '<' || WSP(s[pos]))))) { 434 if (pstate.wpos >= sizeof(pstate.buf)) 435 errx(1, "address exceeds buffer size"); 436 pstate.buf[pstate.wpos++] = s[pos]; 437 } 438 439 if (!pstate.quote && pstate.comment && s[pos] == ')') 440 pstate.comment--; 441 442 if (!pstate.esc && !pstate.comment && !pstate.quote && 443 s[pos] == '\\') 444 pstate.esc = 1; 445 else 446 pstate.esc = 0; 447 } 448 449 if (terminal) 450 parse_addr_terminal(is_from); 451 452 for (; pos < len && (s[pos] == '\r' || s[pos] == '\n'); pos++) 453 ; /* nothing */ 454 455 if (pos < len) 456 parse_addr(s + pos, len - pos, is_from); 457 } 458 459 void 460 parse_addr_terminal(int is_from) 461 { 462 if (pstate.comment || pstate.quote || pstate.esc) 463 errx(1, "syntax error in address"); 464 if (pstate.wpos) { 465 if (pstate.wpos >= sizeof(pstate.buf)) 466 errx(1, "address exceeds buffer size"); 467 pstate.buf[pstate.wpos] = '\0'; 468 if (is_from) 469 msg.from = qualify_addr(pstate.buf); 470 else 471 rcpt_add(pstate.buf); 472 pstate.wpos = 0; 473 } 474 } 475 476 char * 477 qualify_addr(char *in) 478 { 479 char *out; 480 481 if (strchr(in, '@') == NULL) { 482 if (asprintf(&out, "%s@%s", in, host) == -1) 483 err(1, "qualify asprintf"); 484 } else 485 if ((out = strdup(in)) == NULL) 486 err(1, "qualify strdup"); 487 488 return (out); 489 } 490 491 void 492 rcpt_add(char *addr) 493 { 494 void *nrcpts; 495 496 if ((nrcpts = realloc(msg.rcpts, 497 sizeof(char *) * (msg.rcpt_cnt + 1))) == NULL) 498 err(1, "rcpt_add realloc"); 499 msg.rcpts = nrcpts; 500 msg.rcpts[msg.rcpt_cnt++] = qualify_addr(addr); 501 } 502 503 int 504 open_connection(void) 505 { 506 struct imsg imsg; 507 int fd; 508 int n; 509 510 imsg_compose(ibuf, IMSG_SMTP_ENQUEUE, 0, 0, -1, NULL, 0); 511 512 while (ibuf->w.queued) 513 if (msgbuf_write(&ibuf->w) < 0) 514 err(1, "write error"); 515 516 while (1) { 517 if ((n = imsg_read(ibuf)) == -1) 518 errx(1, "imsg_read error"); 519 if (n == 0) 520 errx(1, "pipe closed"); 521 522 if ((n = imsg_get(ibuf, &imsg)) == -1) 523 errx(1, "imsg_get error"); 524 if (n == 0) 525 continue; 526 527 switch (imsg.hdr.type) { 528 case IMSG_CTL_OK: 529 break; 530 case IMSG_CTL_FAIL: 531 errx(1, "server disallowed submission request"); 532 default: 533 errx(1, "unexpected imsg reply type"); 534 } 535 536 fd = imsg.fd; 537 imsg_free(&imsg); 538 539 break; 540 } 541 542 return fd; 543 } 544 545 int 546 enqueue_offline(int argc, char *argv[]) 547 { 548 char path[MAXPATHLEN]; 549 FILE *fp; 550 int i, fd, ch; 551 552 if (! bsnprintf(path, sizeof(path), "%s%s/%d.XXXXXXXXXX", PATH_SPOOL, 553 PATH_OFFLINE, time(NULL))) 554 err(1, "snprintf"); 555 556 if ((fd = mkstemp(path)) == -1 || (fp = fdopen(fd, "w+")) == NULL) { 557 warn("cannot create temporary file %s", path); 558 if (fd != -1) 559 unlink(path); 560 exit(1); 561 } 562 563 for (i = 1; i < argc; i++) { 564 if (strchr(argv[i], '|') != NULL) { 565 warnx("%s contains illegal character", argv[i]); 566 unlink(path); 567 exit(1); 568 } 569 fprintf(fp, "%s%s", i == 1 ? "" : "|", argv[i]); 570 } 571 572 fprintf(fp, "\n"); 573 574 while ((ch = fgetc(stdin)) != EOF) 575 if (fputc(ch, fp) == EOF) { 576 warn("write error"); 577 unlink(path); 578 exit(1); 579 } 580 581 if (ferror(stdin)) { 582 warn("read error"); 583 unlink(path); 584 exit(1); 585 } 586 587 fclose(fp); 588 589 return (0); 590 } 591