1 /* $OpenBSD: enqueue.c,v 1.47 2011/08/29 21:43:08 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 <event.h> 29 #include <imsg.h> 30 #include <pwd.h> 31 #include <signal.h> 32 #include <stdio.h> 33 #include <stdlib.h> 34 #include <string.h> 35 #include <time.h> 36 #include <unistd.h> 37 38 #include "smtpd.h" 39 #include "client.h" 40 41 extern struct imsgbuf *ibuf; 42 43 void usage(void); 44 static void sighdlr(int); 45 static void build_from(char *, struct passwd *); 46 static int parse_message(FILE *, int, int, FILE *); 47 static void parse_addr(char *, size_t, int); 48 static void parse_addr_terminal(int); 49 static char *qualify_addr(char *); 50 static void rcpt_add(char *); 51 static int open_connection(void); 52 static void enqueue_event(int, short, void *); 53 54 enum headerfields { 55 HDR_NONE, 56 HDR_FROM, 57 HDR_TO, 58 HDR_CC, 59 HDR_BCC, 60 HDR_SUBJECT, 61 HDR_DATE, 62 HDR_MSGID 63 }; 64 65 struct { 66 char *word; 67 enum headerfields type; 68 } keywords[] = { 69 { "From:", HDR_FROM }, 70 { "To:", HDR_TO }, 71 { "Cc:", HDR_CC }, 72 { "Bcc:", HDR_BCC }, 73 { "Subject:", HDR_SUBJECT }, 74 { "Date:", HDR_DATE }, 75 { "Message-Id:", HDR_MSGID } 76 }; 77 78 #define SMTP_LINELEN 1000 79 #define TIMEOUTMSG "Timeout\n" 80 81 #define WSP(c) (c == ' ' || c == '\t') 82 83 int verbose = 0; 84 char host[MAXHOSTNAMELEN]; 85 char *user = NULL; 86 time_t timestamp; 87 88 struct { 89 int fd; 90 char *from; 91 char *fromname; 92 char **rcpts; 93 int rcpt_cnt; 94 char *data; 95 size_t len; 96 int saw_date; 97 int saw_msgid; 98 int saw_from; 99 100 struct smtp_client *pcb; 101 struct event ev; 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 static 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; 126 char *fake_from = NULL; 127 struct passwd *pw; 128 FILE *fp; 129 130 bzero(&msg, sizeof(msg)); 131 time(×tamp); 132 133 while ((ch = getopt(argc, argv, 134 "A:B:b:E::e:F:f:iJ::L:mo:p:qtvx")) != -1) { 135 switch (ch) { 136 case 'f': 137 fake_from = optarg; 138 break; 139 case 'F': 140 msg.fromname = optarg; 141 break; 142 case 't': 143 tflag = 1; 144 break; 145 case 'v': 146 verbose = 1; 147 break; 148 /* all remaining: ignored, sendmail compat */ 149 case 'A': 150 case 'B': 151 case 'b': 152 case 'E': 153 case 'e': 154 case 'i': 155 case 'L': 156 case 'm': 157 case 'o': 158 case 'p': 159 case 'x': 160 break; 161 case 'q': 162 /* XXX: implement "process all now" */ 163 return (0); 164 default: 165 usage(); 166 } 167 } 168 169 argc -= optind; 170 argv += optind; 171 172 if (gethostname(host, sizeof(host)) == -1) 173 err(1, "gethostname"); 174 if ((pw = getpwuid(getuid())) == NULL) 175 user = "anonymous"; 176 if (pw != NULL && (user = strdup(pw->pw_name)) == NULL) 177 err(1, "strdup"); 178 179 build_from(fake_from, pw); 180 181 while(argc > 0) { 182 rcpt_add(argv[0]); 183 argv++; 184 argc--; 185 } 186 187 signal(SIGALRM, sighdlr); 188 alarm(300); 189 190 fp = tmpfile(); 191 if (fp == NULL) 192 err(1, "tmpfile"); 193 noheader = parse_message(stdin, fake_from == NULL, tflag, fp); 194 195 if ((msg.fd = open_connection()) == -1) 196 errx(1, "server too busy"); 197 198 /* init session */ 199 rewind(fp); 200 msg.pcb = client_init(msg.fd, fp, "localhost", verbose); 201 202 /* set envelope from */ 203 client_sender(msg.pcb, "%s", msg.from); 204 205 /* add recipients */ 206 if (msg.rcpt_cnt == 0) 207 errx(1, "no recipients"); 208 for (i = 0; i < msg.rcpt_cnt; i++) 209 client_rcpt(msg.pcb, "%s", msg.rcpts[i]); 210 211 /* add From */ 212 if (!msg.saw_from) 213 client_printf(msg.pcb, "From: %s%s<%s>\n", 214 msg.fromname ? msg.fromname : "", 215 msg.fromname ? " " : "", 216 msg.from); 217 218 /* add Date */ 219 if (!msg.saw_date) 220 client_printf(msg.pcb, "Date: %s\n", time_to_text(timestamp)); 221 222 /* add Message-Id */ 223 if (!msg.saw_msgid) 224 client_printf(msg.pcb, "Message-Id: <%llu.enqueue@%s>\n", 225 generate_uid(), host); 226 227 /* add separating newline */ 228 if (noheader) 229 client_printf(msg.pcb, "\n"); 230 231 alarm(0); 232 event_init(); 233 session_socket_blockmode(msg.fd, BM_NONBLOCK); 234 event_set(&msg.ev, msg.fd, EV_READ|EV_WRITE, enqueue_event, NULL); 235 event_add(&msg.ev, &msg.pcb->timeout); 236 237 if (event_dispatch() < 0) 238 err(1, "event_dispatch"); 239 240 client_close(msg.pcb); 241 fclose(fp); 242 exit(0); 243 } 244 245 static void 246 enqueue_event(int fd, short event, void *p) 247 { 248 if (event & EV_TIMEOUT) 249 errx(1, "timeout"); 250 251 switch (client_talk(msg.pcb, event & EV_WRITE)) { 252 case CLIENT_WANT_WRITE: 253 goto rw; 254 case CLIENT_STOP_WRITE: 255 goto ro; 256 case CLIENT_RCPT_FAIL: 257 errx(1, "%s", msg.pcb->reply); 258 case CLIENT_DONE: 259 break; 260 default: 261 errx(1, "enqueue_event: unexpected code"); 262 } 263 264 if (msg.pcb->status[0] != '2') 265 errx(1, "%s", msg.pcb->status); 266 267 event_loopexit(NULL); 268 return; 269 270 ro: 271 event_set(&msg.ev, msg.fd, EV_READ, enqueue_event, NULL); 272 event_add(&msg.ev, &msg.pcb->timeout); 273 return; 274 275 rw: 276 event_set(&msg.ev, msg.fd, EV_READ|EV_WRITE, enqueue_event, NULL); 277 event_add(&msg.ev, &msg.pcb->timeout); 278 } 279 280 static void 281 build_from(char *fake_from, struct passwd *pw) 282 { 283 char *p; 284 285 if (fake_from == NULL) 286 msg.from = qualify_addr(user); 287 else { 288 if (fake_from[0] == '<') { 289 if (fake_from[strlen(fake_from) - 1] != '>') 290 errx(1, "leading < but no trailing >"); 291 fake_from[strlen(fake_from) - 1] = 0; 292 if ((p = malloc(strlen(fake_from))) == NULL) 293 err(1, "malloc"); 294 strlcpy(p, fake_from + 1, strlen(fake_from)); 295 296 msg.from = qualify_addr(p); 297 free(p); 298 } else 299 msg.from = qualify_addr(fake_from); 300 } 301 302 if (msg.fromname == NULL && fake_from == NULL && pw != NULL) { 303 int len, apos; 304 305 len = strcspn(pw->pw_gecos, ","); 306 if ((p = memchr(pw->pw_gecos, '&', len))) { 307 apos = p - pw->pw_gecos; 308 if (asprintf(&msg.fromname, "%.*s%s%.*s", 309 apos, pw->pw_gecos, 310 pw->pw_name, 311 len - apos - 1, p + 1) == -1) 312 err(1, NULL); 313 msg.fromname[apos] = toupper(msg.fromname[apos]); 314 } else { 315 if (asprintf(&msg.fromname, "%.*s", len, 316 pw->pw_gecos) == -1) 317 err(1, NULL); 318 } 319 } 320 } 321 322 static int 323 parse_message(FILE *fin, int get_from, int tflag, FILE *fout) 324 { 325 char *buf; 326 size_t len; 327 u_int i, cur = HDR_NONE; 328 u_int header_seen = 0, header_done = 0; 329 330 bzero(&pstate, sizeof(pstate)); 331 for (;;) { 332 buf = fgetln(fin, &len); 333 if (buf == NULL && ferror(fin)) 334 err(1, "fgetln"); 335 if (buf == NULL && feof(fin)) 336 break; 337 if (buf == NULL || len < 1) 338 err(1, "fgetln weird"); 339 340 /* account for \r\n linebreaks */ 341 if (len >= 2 && buf[len - 2] == '\r' && buf[len - 1] == '\n') 342 buf[--len - 1] = '\n'; 343 344 if (len == 1 && buf[0] == '\n') /* end of header */ 345 header_done = 1; 346 347 if (!WSP(buf[0])) { /* whitespace -> continuation */ 348 if (cur == HDR_FROM) 349 parse_addr_terminal(1); 350 if (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC) 351 parse_addr_terminal(0); 352 cur = HDR_NONE; 353 } 354 355 for (i = 0; !header_done && cur == HDR_NONE && 356 i < nitems(keywords); i++) 357 if (len > strlen(keywords[i].word) && 358 !strncasecmp(buf, keywords[i].word, 359 strlen(keywords[i].word))) 360 cur = keywords[i].type; 361 362 if (cur != HDR_NONE) 363 header_seen = 1; 364 365 if (cur != HDR_BCC) { 366 fprintf(fout, "%.*s", (int)len, buf); 367 if (buf[len - 1] != '\n') 368 fputc('\n', fout); 369 if (ferror(fout)) 370 err(1, "write error"); 371 } 372 373 /* 374 * using From: as envelope sender is not sendmail compatible, 375 * but I really want it that way - maybe needs a knob 376 */ 377 if (cur == HDR_FROM) { 378 msg.saw_from++; 379 if (get_from) 380 parse_addr(buf, len, 1); 381 } 382 383 if (tflag && (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC)) 384 parse_addr(buf, len, 0); 385 386 if (cur == HDR_DATE) 387 msg.saw_date++; 388 if (cur == HDR_MSGID) 389 msg.saw_msgid++; 390 } 391 392 return (!header_seen); 393 } 394 395 static void 396 parse_addr(char *s, size_t len, int is_from) 397 { 398 size_t pos = 0; 399 int terminal = 0; 400 401 /* unless this is a continuation... */ 402 if (!WSP(s[pos]) && s[pos] != ',' && s[pos] != ';') { 403 /* ... skip over everything before the ':' */ 404 for (; pos < len && s[pos] != ':'; pos++) 405 ; /* nothing */ 406 /* ... and check & reset parser state */ 407 parse_addr_terminal(is_from); 408 } 409 410 /* skip over ':' ',' ';' and whitespace */ 411 for (; pos < len && !pstate.quote && (WSP(s[pos]) || s[pos] == ':' || 412 s[pos] == ',' || s[pos] == ';'); pos++) 413 ; /* nothing */ 414 415 for (; pos < len; pos++) { 416 if (!pstate.esc && !pstate.quote && s[pos] == '(') 417 pstate.comment++; 418 if (!pstate.comment && !pstate.esc && s[pos] == '"') 419 pstate.quote = !pstate.quote; 420 421 if (!pstate.comment && !pstate.quote && !pstate.esc) { 422 if (s[pos] == ':') { /* group */ 423 for(pos++; pos < len && WSP(s[pos]); pos++) 424 ; /* nothing */ 425 pstate.wpos = 0; 426 } 427 if (s[pos] == '\n' || s[pos] == '\r') 428 break; 429 if (s[pos] == ',' || s[pos] == ';') { 430 terminal = 1; 431 break; 432 } 433 if (s[pos] == '<') { 434 pstate.brackets = 1; 435 pstate.wpos = 0; 436 } 437 if (pstate.brackets && s[pos] == '>') 438 terminal = 1; 439 } 440 441 if (!pstate.comment && !terminal && (!(!(pstate.quote || 442 pstate.esc) && (s[pos] == '<' || WSP(s[pos]))))) { 443 if (pstate.wpos >= sizeof(pstate.buf)) 444 errx(1, "address exceeds buffer size"); 445 pstate.buf[pstate.wpos++] = s[pos]; 446 } 447 448 if (!pstate.quote && pstate.comment && s[pos] == ')') 449 pstate.comment--; 450 451 if (!pstate.esc && !pstate.comment && !pstate.quote && 452 s[pos] == '\\') 453 pstate.esc = 1; 454 else 455 pstate.esc = 0; 456 } 457 458 if (terminal) 459 parse_addr_terminal(is_from); 460 461 for (; pos < len && (s[pos] == '\r' || s[pos] == '\n'); pos++) 462 ; /* nothing */ 463 464 if (pos < len) 465 parse_addr(s + pos, len - pos, is_from); 466 } 467 468 static void 469 parse_addr_terminal(int is_from) 470 { 471 if (pstate.comment || pstate.quote || pstate.esc) 472 errx(1, "syntax error in address"); 473 if (pstate.wpos) { 474 if (pstate.wpos >= sizeof(pstate.buf)) 475 errx(1, "address exceeds buffer size"); 476 pstate.buf[pstate.wpos] = '\0'; 477 if (is_from) 478 msg.from = qualify_addr(pstate.buf); 479 else 480 rcpt_add(pstate.buf); 481 pstate.wpos = 0; 482 } 483 } 484 485 static char * 486 qualify_addr(char *in) 487 { 488 char *out; 489 490 if (strlen(in) > 0 && strchr(in, '@') == NULL) { 491 if (asprintf(&out, "%s@%s", in, host) == -1) 492 err(1, "qualify asprintf"); 493 } else 494 if ((out = strdup(in)) == NULL) 495 err(1, "qualify strdup"); 496 497 return (out); 498 } 499 500 static void 501 rcpt_add(char *addr) 502 { 503 void *nrcpts; 504 505 if ((nrcpts = realloc(msg.rcpts, 506 sizeof(char *) * (msg.rcpt_cnt + 1))) == NULL) 507 err(1, "rcpt_add realloc"); 508 msg.rcpts = nrcpts; 509 msg.rcpts[msg.rcpt_cnt++] = qualify_addr(addr); 510 } 511 512 static int 513 open_connection(void) 514 { 515 struct imsg imsg; 516 int fd; 517 int n; 518 519 imsg_compose(ibuf, IMSG_SMTP_ENQUEUE, 0, 0, -1, NULL, 0); 520 521 while (ibuf->w.queued) 522 if (msgbuf_write(&ibuf->w) < 0) 523 err(1, "write error"); 524 525 while (1) { 526 if ((n = imsg_read(ibuf)) == -1) 527 errx(1, "imsg_read error"); 528 if (n == 0) 529 errx(1, "pipe closed"); 530 531 if ((n = imsg_get(ibuf, &imsg)) == -1) 532 errx(1, "imsg_get error"); 533 if (n == 0) 534 continue; 535 536 switch (imsg.hdr.type) { 537 case IMSG_CTL_OK: 538 break; 539 case IMSG_CTL_FAIL: 540 errx(1, "server disallowed submission request"); 541 default: 542 errx(1, "unexpected imsg reply type"); 543 } 544 545 fd = imsg.fd; 546 imsg_free(&imsg); 547 548 break; 549 } 550 551 return fd; 552 } 553 554 int 555 enqueue_offline(int argc, char *argv[]) 556 { 557 char path[MAXPATHLEN]; 558 FILE *fp; 559 int i, fd, ch; 560 561 if (! bsnprintf(path, sizeof(path), "%s%s/%lld.XXXXXXXXXX", PATH_SPOOL, 562 PATH_OFFLINE, (long long int) time(NULL))) 563 err(1, "snprintf"); 564 565 if ((fd = mkstemp(path)) == -1 || (fp = fdopen(fd, "w+")) == NULL) { 566 warn("cannot create temporary file %s", path); 567 if (fd != -1) 568 unlink(path); 569 exit(1); 570 } 571 572 for (i = 1; i < argc; i++) { 573 if (strchr(argv[i], '|') != NULL) { 574 warnx("%s contains illegal character", argv[i]); 575 unlink(path); 576 exit(1); 577 } 578 fprintf(fp, "%s%s", i == 1 ? "" : "|", argv[i]); 579 } 580 581 fprintf(fp, "\n"); 582 583 while ((ch = fgetc(stdin)) != EOF) 584 if (fputc(ch, fp) == EOF) { 585 warn("write error"); 586 unlink(path); 587 exit(1); 588 } 589 590 if (ferror(stdin)) { 591 warn("read error"); 592 unlink(path); 593 exit(1); 594 } 595 596 fclose(fp); 597 598 return (0); 599 } 600