1 /* $OpenBSD: enqueue.c,v 1.24 2009/09/21 20:35:26 jacekm 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 <unistd.h> 38 39 #include "smtpd.h" 40 #include "client.h" 41 42 extern struct imsgbuf *ibuf; 43 44 void usage(void); 45 void sighdlr(int); 46 int main(int, char *[]); 47 void build_from(char *, struct passwd *); 48 int parse_message(FILE *, int, int, struct buf *); 49 void parse_addr(char *, size_t, int); 50 void parse_addr_terminal(int); 51 char *qualify_addr(char *); 52 void rcpt_add(char *); 53 int open_connection(void); 54 55 enum headerfields { 56 HDR_NONE, 57 HDR_FROM, 58 HDR_TO, 59 HDR_CC, 60 HDR_BCC, 61 HDR_SUBJECT, 62 HDR_DATE, 63 HDR_MSGID 64 }; 65 66 struct { 67 char *word; 68 enum headerfields type; 69 } keywords[] = { 70 { "From:", HDR_FROM }, 71 { "To:", HDR_TO }, 72 { "Cc:", HDR_CC }, 73 { "Bcc:", HDR_BCC }, 74 { "Subject:", HDR_SUBJECT }, 75 { "Date:", HDR_DATE }, 76 { "Message-Id:", HDR_MSGID } 77 }; 78 79 #define SMTP_LINELEN 1000 80 #define SMTP_TIMEOUT 120 81 #define TIMEOUTMSG "Timeout\n" 82 83 #define WSP(c) (c == ' ' || c == '\t') 84 85 int verbose = 0; 86 char host[MAXHOSTNAMELEN]; 87 char *user = NULL; 88 time_t timestamp; 89 90 struct { 91 int fd; 92 char *from; 93 char *fromname; 94 char **rcpts; 95 int rcpt_cnt; 96 char *data; 97 size_t len; 98 int saw_date; 99 int saw_msgid; 100 int saw_from; 101 } msg; 102 103 struct { 104 u_int quote; 105 u_int comment; 106 u_int esc; 107 u_int brackets; 108 size_t wpos; 109 char buf[SMTP_LINELEN]; 110 } pstate; 111 112 void 113 sighdlr(int sig) 114 { 115 if (sig == SIGALRM) { 116 write(STDERR_FILENO, TIMEOUTMSG, sizeof(TIMEOUTMSG)); 117 _exit (2); 118 } 119 } 120 121 int 122 enqueue(int argc, char *argv[]) 123 { 124 int i, ch, tflag = 0, noheader, ret; 125 char *fake_from = NULL; 126 struct passwd *pw; 127 struct smtp_client *sp; 128 struct buf *body; 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(SMTP_TIMEOUT); 189 190 msg.fd = open_connection(); 191 192 /* init session */ 193 if ((sp = client_init(msg.fd, "localhost")) == NULL) 194 err(1, "client_init failed"); 195 if (verbose) 196 client_verbose(sp, stdout); 197 198 /* parse message */ 199 if ((body = buf_dynamic(0, SIZE_T_MAX)) < 0) 200 err(1, "buf_dynamic failed"); 201 noheader = parse_message(stdin, fake_from == NULL, tflag, body); 202 203 /* set envelope from */ 204 if (client_sender(sp, "%s", msg.from) < 0) 205 err(1, "client_sender failed"); 206 207 /* add recipients */ 208 if (msg.rcpt_cnt == 0) 209 errx(1, "no recipients"); 210 for (i = 0; i < msg.rcpt_cnt; i++) 211 if (client_rcpt(sp, "%s", msg.rcpts[i]) < 0) 212 err(1, "client_rcpt failed"); 213 214 /* add From */ 215 if (!msg.saw_from) { 216 if (msg.fromname != NULL) { 217 if (client_data_printf(sp, 218 "From: %s <%s>\n", msg.fromname, msg.from) < 0) 219 err(1, "client_data_printf failed"); 220 } else 221 if (client_data_printf(sp, 222 "From: %s\n", msg.from) < 0) 223 err(1, "client_data_printf failed"); 224 } 225 226 /* add Date */ 227 if (!msg.saw_date) 228 if (client_data_printf(sp, 229 "Date: %s\n", time_to_text(timestamp)) < 0) 230 err(1, "client_data_printf failed"); 231 232 /* add Message-Id */ 233 if (!msg.saw_msgid) 234 if (client_data_printf(sp, 235 "Message-Id: <%llu.enqueue@%s>\n", 236 queue_generate_id(), host) < 0) 237 err(1, "client_data_printf failed"); 238 239 /* add separating newline */ 240 if (noheader) 241 if (client_data_printf(sp, "\n") < 0) 242 err(1, "client_data_printf failed"); 243 244 if (client_data_printf(sp, "%.*s", buf_size(body), body->buf) < 0) 245 err(1, "client_data_printf failed"); 246 buf_free(body); 247 248 /* run the protocol engine */ 249 for (;;) { 250 while ((ret = client_read(sp)) == CLIENT_WANT_READ) 251 ; 252 if (ret == CLIENT_ERROR) 253 errx(1, "read error: %s", client_strerror(sp)); 254 if (ret == CLIENT_RCPT_FAIL) 255 errx(1, "recipient refused: %s", client_reply(sp)); 256 if (ret == CLIENT_DONE) 257 break; 258 while ((ret = client_write(sp)) == CLIENT_WANT_WRITE) 259 ; 260 if (ret == CLIENT_ERROR) 261 errx(1, "write error: %s", client_strerror(sp)); 262 } 263 264 client_close(sp); 265 266 close(msg.fd); 267 exit (0); 268 } 269 270 void 271 build_from(char *fake_from, struct passwd *pw) 272 { 273 char *p; 274 275 if (fake_from == NULL) 276 msg.from = qualify_addr(user); 277 else { 278 if (fake_from[0] == '<') { 279 if (fake_from[strlen(fake_from) - 1] != '>') 280 errx(1, "leading < but no trailing >"); 281 fake_from[strlen(fake_from) - 1] = 0; 282 if ((p = malloc(strlen(fake_from))) == NULL) 283 err(1, "malloc"); 284 strlcpy(p, fake_from + 1, strlen(fake_from)); 285 286 msg.from = qualify_addr(p); 287 free(p); 288 } else 289 msg.from = qualify_addr(fake_from); 290 } 291 292 if (msg.fromname == NULL && fake_from == NULL && pw != NULL) { 293 int len, apos; 294 295 len = strcspn(pw->pw_gecos, ","); 296 if ((p = memchr(pw->pw_gecos, '&', len))) { 297 apos = p - pw->pw_gecos; 298 if (asprintf(&msg.fromname, "%.*s%s%.*s", 299 apos, pw->pw_gecos, 300 pw->pw_name, 301 len - apos - 1, p + 1) == -1) 302 err(1, NULL); 303 msg.fromname[apos] = toupper(msg.fromname[apos]); 304 } else { 305 if (asprintf(&msg.fromname, "%.*s", len, 306 pw->pw_gecos) == -1) 307 err(1, NULL); 308 } 309 } 310 } 311 312 int 313 parse_message(FILE *fin, int get_from, int tflag, struct buf *body) 314 { 315 char *buf; 316 size_t len; 317 u_int i, cur = HDR_NONE; 318 u_int header_seen = 0, header_done = 0; 319 320 bzero(&pstate, sizeof(pstate)); 321 for (;;) { 322 buf = fgetln(fin, &len); 323 if (buf == NULL && ferror(fin)) 324 err(1, "fgetln"); 325 if (buf == NULL && feof(fin)) 326 break; 327 328 /* account for \r\n linebreaks */ 329 if (len >= 2 && buf[len - 2] == '\r' && buf[len - 1] == '\n') 330 buf[--len - 1] = '\n'; 331 332 if (len == 1 && buf[0] == '\n') /* end of header */ 333 header_done = 1; 334 335 if (buf == NULL || len < 1) 336 err(1, "fgetln weird"); 337 338 if (!WSP(buf[0])) { /* whitespace -> continuation */ 339 if (cur == HDR_FROM) 340 parse_addr_terminal(1); 341 if (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC) 342 parse_addr_terminal(0); 343 cur = HDR_NONE; 344 } 345 346 for (i = 0; !header_done && cur == HDR_NONE && 347 i < nitems(keywords); i++) 348 if (len > strlen(keywords[i].word) && 349 !strncasecmp(buf, keywords[i].word, 350 strlen(keywords[i].word))) 351 cur = keywords[i].type; 352 353 if (cur != HDR_NONE) 354 header_seen = 1; 355 356 if (cur != HDR_BCC) { 357 if (buf_add(body, buf, len) < 0) 358 err(1, "buf_add failed"); 359 if (buf[len - 1] != '\n' && buf_add(body, "\n", 1) < 0) 360 err(1, "buf_add failed"); 361 } 362 363 /* 364 * using From: as envelope sender is not sendmail compatible, 365 * but I really want it that way - maybe needs a knob 366 */ 367 if (cur == HDR_FROM) { 368 msg.saw_from++; 369 if (get_from) 370 parse_addr(buf, len, 1); 371 } 372 373 if (tflag && (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC)) 374 parse_addr(buf, len, 0); 375 376 if (cur == HDR_DATE) 377 msg.saw_date++; 378 if (cur == HDR_MSGID) 379 msg.saw_msgid++; 380 } 381 382 return (!header_seen); 383 } 384 385 void 386 parse_addr(char *s, size_t len, int is_from) 387 { 388 size_t pos = 0; 389 int terminal = 0; 390 391 /* unless this is a continuation... */ 392 if (!WSP(s[pos]) && s[pos] != ',' && s[pos] != ';') { 393 /* ... skip over everything before the ':' */ 394 for (; pos < len && s[pos] != ':'; pos++) 395 ; /* nothing */ 396 /* ... and check & reset parser state */ 397 parse_addr_terminal(is_from); 398 } 399 400 /* skip over ':' ',' ';' and whitespace */ 401 for (; pos < len && !pstate.quote && (WSP(s[pos]) || s[pos] == ':' || 402 s[pos] == ',' || s[pos] == ';'); pos++) 403 ; /* nothing */ 404 405 for (; pos < len; pos++) { 406 if (!pstate.esc && !pstate.quote && s[pos] == '(') 407 pstate.comment++; 408 if (!pstate.comment && !pstate.esc && s[pos] == '"') 409 pstate.quote = !pstate.quote; 410 411 if (!pstate.comment && !pstate.quote && !pstate.esc) { 412 if (s[pos] == ':') { /* group */ 413 for(pos++; pos < len && WSP(s[pos]); pos++) 414 ; /* nothing */ 415 pstate.wpos = 0; 416 } 417 if (s[pos] == '\n' || s[pos] == '\r') 418 break; 419 if (s[pos] == ',' || s[pos] == ';') { 420 terminal = 1; 421 break; 422 } 423 if (s[pos] == '<') { 424 pstate.brackets = 1; 425 pstate.wpos = 0; 426 } 427 if (pstate.brackets && s[pos] == '>') 428 terminal = 1; 429 } 430 431 if (!pstate.comment && !terminal && (!(!(pstate.quote || 432 pstate.esc) && (s[pos] == '<' || WSP(s[pos]))))) { 433 if (pstate.wpos >= sizeof(pstate.buf)) 434 errx(1, "address exceeds buffer size"); 435 pstate.buf[pstate.wpos++] = s[pos]; 436 } 437 438 if (!pstate.quote && pstate.comment && s[pos] == ')') 439 pstate.comment--; 440 441 if (!pstate.esc && !pstate.comment && !pstate.quote && 442 s[pos] == '\\') 443 pstate.esc = 1; 444 else 445 pstate.esc = 0; 446 } 447 448 if (terminal) 449 parse_addr_terminal(is_from); 450 451 for (; pos < len && (s[pos] == '\r' || s[pos] == '\n'); pos++) 452 ; /* nothing */ 453 454 if (pos < len) 455 parse_addr(s + pos, len - pos, is_from); 456 } 457 458 void 459 parse_addr_terminal(int is_from) 460 { 461 if (pstate.comment || pstate.quote || pstate.esc) 462 errx(1, "syntax error in address"); 463 if (pstate.wpos) { 464 if (pstate.wpos >= sizeof(pstate.buf)) 465 errx(1, "address exceeds buffer size"); 466 pstate.buf[pstate.wpos] = '\0'; 467 if (is_from) 468 msg.from = qualify_addr(pstate.buf); 469 else 470 rcpt_add(pstate.buf); 471 pstate.wpos = 0; 472 } 473 } 474 475 char * 476 qualify_addr(char *in) 477 { 478 char *out; 479 480 if (strchr(in, '@') == NULL) { 481 if (asprintf(&out, "%s@%s", in, host) == -1) 482 err(1, "qualify asprintf"); 483 } else 484 if ((out = strdup(in)) == NULL) 485 err(1, "qualify strdup"); 486 487 return (out); 488 } 489 490 void 491 rcpt_add(char *addr) 492 { 493 void *nrcpts; 494 495 if ((nrcpts = realloc(msg.rcpts, 496 sizeof(char *) * (msg.rcpt_cnt + 1))) == NULL) 497 err(1, "rcpt_add realloc"); 498 msg.rcpts = nrcpts; 499 msg.rcpts[msg.rcpt_cnt++] = qualify_addr(addr); 500 } 501 502 int 503 open_connection(void) 504 { 505 struct imsg imsg; 506 int fd; 507 int n; 508 509 imsg_compose(ibuf, IMSG_SMTP_ENQUEUE, 0, 0, -1, NULL, 0); 510 511 while (ibuf->w.queued) 512 if (msgbuf_write(&ibuf->w) < 0) 513 err(1, "write error"); 514 515 while (1) { 516 if ((n = imsg_read(ibuf)) == -1) 517 errx(1, "imsg_read error"); 518 if (n == 0) 519 errx(1, "pipe closed"); 520 521 if ((n = imsg_get(ibuf, &imsg)) == -1) 522 errx(1, "imsg_get error"); 523 if (n == 0) 524 continue; 525 526 switch (imsg.hdr.type) { 527 case IMSG_CTL_OK: 528 break; 529 case IMSG_CTL_FAIL: 530 errx(1, "server disallowed submission request"); 531 default: 532 errx(1, "unexpected imsg reply type"); 533 } 534 535 fd = imsg.fd; 536 imsg_free(&imsg); 537 538 break; 539 } 540 541 return fd; 542 } 543 544 int 545 enqueue_offline(int argc, char *argv[]) 546 { 547 char path[MAXPATHLEN]; 548 FILE *fp; 549 int i, fd, ch; 550 551 if (! bsnprintf(path, sizeof(path), "%s%s/%d.XXXXXXXXXX", PATH_SPOOL, 552 PATH_OFFLINE, time(NULL))) 553 err(1, "snprintf"); 554 555 if ((fd = mkstemp(path)) == -1 || (fp = fdopen(fd, "w+")) == NULL) { 556 warn("cannot create temporary file %s", path); 557 if (fd != -1) 558 unlink(path); 559 exit(1); 560 } 561 562 for (i = 1; i < argc; i++) { 563 if (strchr(argv[i], '|') != NULL) { 564 warnx("%s contains illegal character", argv[i]); 565 unlink(path); 566 exit(1); 567 } 568 fprintf(fp, "%s%s", i == 1 ? "" : "|", argv[i]); 569 } 570 571 fprintf(fp, "\n"); 572 573 while ((ch = fgetc(stdin)) != EOF) 574 if (fputc(ch, fp) == EOF) { 575 warn("write error"); 576 unlink(path); 577 exit(1); 578 } 579 580 if (ferror(stdin)) { 581 warn("read error"); 582 unlink(path); 583 exit(1); 584 } 585 586 fclose(fp); 587 588 return (0); 589 } 590