pop3.c (11038B)
1 /* 2 * Copyright (c) 2022, 2023 Jacob R. Edwards <jacob@jacobedwards.org> 3 * 4 * Permission to use, copy, modify, and distribute this software for any 5 * purpose with or without fee is hereby granted, provided that the above 6 * copyright notice and this permission notice appear in all copies. 7 * 8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 */ 16 17 /* ignore const */ 18 #define const 19 20 #include <sys/socket.h> 21 #include <sys/types.h> 22 #include <sys/wait.h> 23 24 #include <netdb.h> 25 26 #include <ctype.h> 27 #include <errno.h> 28 #include <libgen.h> 29 #include <limits.h> 30 #include <readpassphrase.h> 31 #include <signal.h> 32 #include <stdarg.h> 33 #include <stdio.h> 34 #include <stdlib.h> 35 #include <string.h> 36 #include <time.h> 37 #include <unistd.h> 38 39 #ifndef __OpenBSD__ 40 #define pledge(A,B) (0) 41 #include "openbsd-compat/explicit_bzero.h" 42 #endif /* __OpenBSD__ */ 43 44 /* Limits */ 45 #define LISTMAX 9999 /* Maximum number of messages (see scanlisting struct) */ 46 #define LINEMAX 1002 /* Includes CRLF and nul (see RFC 5321 5.4.3.1.6.) */ 47 #define POPMAX 513 /* POP3 status size including CRLF and nul */ 48 #define ARGMAX 40 /* POP3 argument size excluding nul */ 49 #define KEYMAX 4 /* POP3 keyword size excluding nul */ 50 51 /* Messy global error buffers */ 52 static char poperr[128]; 53 static int gaierr; 54 55 /* Flag variables */ 56 static int delete; 57 static int query; 58 static int usestdin; 59 static int trace; 60 static int verbose; 61 62 /* Option and argument variables */ 63 static char *port = "110"; 64 static char *user; 65 static char *host; 66 static char **mailer; 67 68 static int bail; 69 70 struct scanlisting { 71 char msg[4 + 1]; /* See LISTMAX */ 72 int bytes; 73 }; 74 75 void 76 die(char *fmt, ...) 77 { 78 va_list ap; 79 char *info; 80 81 if (gaierr && gaierr != EAI_SYSTEM) 82 info = gai_strerror(gaierr); 83 else if (*poperr) 84 info = poperr; 85 else 86 info = strerror(errno); 87 88 fprintf(stderr, "%s: ", getprogname()); 89 va_start(ap, fmt); 90 vfprintf(stderr, fmt, ap); 91 va_end(ap); 92 fprintf(stderr, ": %s\n", info); 93 exit(1); 94 } 95 96 void 97 usage(char *why) 98 { 99 if (why) 100 fprintf(stderr, "%s\n", why); 101 fprintf(stderr, "usage: %s [-dqstv] [-p port] [-u user] host [mailer [arg ...]]\n", 102 getprogname()); 103 exit(1); 104 } 105 106 int 107 resolve(char *host, char *serv) 108 { 109 struct addrinfo hints; 110 struct addrinfo *res, *res0; 111 int fd; 112 int errno2; 113 114 memset(&hints, 0, sizeof(hints)); 115 hints.ai_family = AF_UNSPEC; 116 hints.ai_socktype = SOCK_STREAM; 117 hints.ai_protocol = IPPROTO_TCP; 118 hints.ai_flags = AI_PASSIVE; 119 120 if ((gaierr = getaddrinfo(host, serv, &hints, &res0)) != 0) 121 return -1; 122 123 fd = -1; 124 errno2 = errno; 125 for (res = res0; fd < 0 && res; res = res->ai_next) { 126 fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); 127 if (fd >= 0) { 128 if (connect(fd, res->ai_addr, res->ai_addrlen)) { 129 close(fd); 130 fd = -1; 131 } 132 } 133 } 134 135 if (fd >= 0) 136 errno = errno2; 137 freeaddrinfo(res0); 138 139 return fd; 140 } 141 142 int 143 pop_split(char **ap, int len, char *buf) 144 { 145 int i; 146 char *p; 147 148 for (i = 0; (p = strsep(&buf, " ")); ++i) 149 if (i < len) 150 ap[i] = p; 151 return i; 152 } 153 154 char * 155 pop_okay(char *buf) 156 { 157 if (!buf) 158 return NULL; 159 160 if (strncmp(buf, "+OK", 3) == 0) 161 return buf + ((buf[3] == ' ') ? 4 : 3); 162 if (strncmp(buf, "-ERR ", 5) == 0) 163 strlcpy(poperr, buf + 5, sizeof(poperr)); 164 return NULL; 165 } 166 167 char * 168 pop_charcheck(char *buf) 169 { 170 while (*buf && *buf != '\r' && *buf != '\n') 171 ++buf; 172 return buf; 173 } 174 175 char * 176 pop_read(FILE *fp, char *buf, int size) 177 { 178 char *p; 179 180 if (!fgets(buf, size, fp)) { 181 if (!errno) 182 errno = ECONNRESET; 183 return NULL; 184 } 185 186 p = pop_charcheck(buf); 187 if (strcmp(p, "\r\n") != 0) { 188 errno = EFTYPE; 189 return NULL; 190 } 191 192 *p = '\0'; 193 if (trace) 194 fprintf(stderr, "<S %s\n", buf); 195 return buf; 196 } 197 198 int 199 pop_write(FILE *fp, char *keyword, char *arg) 200 { 201 char buf[KEYMAX + 1 + ARGMAX + 1]; 202 int len; 203 204 if (arg) 205 len = snprintf(buf, sizeof(buf), "%s %s", keyword, arg); 206 else 207 len = snprintf(buf, sizeof(buf), "%s", keyword); 208 209 if (len < 0) 210 return -1; 211 212 if (len >= sizeof(buf)) { 213 errno = ENOBUFS; 214 return -1; 215 } 216 217 if (*pop_charcheck(buf)) { 218 errno = EFTYPE; 219 return -1; 220 } 221 222 if (trace) 223 fprintf(stderr, "C> %s\n", buf); 224 return fprintf(fp, "%s\r\n", buf) != len + 2; 225 } 226 227 FILE * 228 pop_open(char *host, char *serv) 229 { 230 FILE *fp; 231 char buf[POPMAX]; 232 int fd; 233 234 fd = resolve(host, serv); 235 if (fd < 0) 236 return NULL; 237 238 if ((fp = fdopen(fd, "r+")) == NULL) { 239 close(fd); 240 return NULL; 241 } 242 243 if (!pop_okay(pop_read(fp, buf, sizeof(buf)))) { 244 fclose(fp); 245 return NULL; 246 } 247 return fp; 248 } 249 250 char * 251 pop_comd(FILE *fp, char *buf, int size, char *keyword, char *arg) 252 { 253 if (pop_write(fp, keyword, arg)) 254 return NULL; 255 if (!pop_read(fp, buf, size)) 256 return NULL; 257 return buf; 258 } 259 260 int 261 pop_quit(FILE *fp) 262 { 263 char buf[POPMAX]; 264 265 if (!pop_okay(pop_comd(fp, buf, sizeof(buf), "QUIT", NULL))) { 266 fclose(fp); 267 return 1; 268 } 269 return fclose(fp) < 0; 270 } 271 272 int 273 pop_auth(FILE *fp, char *user, char *pass) 274 { 275 char buf[POPMAX]; 276 277 if (!pop_okay(pop_comd(fp, buf, sizeof(buf), "USER", user))) 278 return 1; 279 if (!pop_okay(pop_comd(fp, buf, sizeof(buf), "PASS", pass))) 280 return 1; 281 return 0; 282 } 283 284 int 285 pop_stat(FILE *fp, int *msgs, int *bytes) 286 { 287 char buf[POPMAX]; 288 char *ap[2]; 289 int nums[2]; 290 char *p; 291 int i; 292 293 if (!pop_comd(fp, buf, sizeof(buf), "STAT", NULL)) 294 return 1; 295 296 if (!(p = pop_okay(buf)) || pop_split(ap, 2, p) < 2) { 297 errno = EFTYPE; 298 return 1; 299 } 300 301 for (i = 0; i < 2; ++i) { 302 nums[i] = strtonum(ap[i], 0, INT_MAX, &p); 303 if (p) 304 return 1; 305 } 306 307 if (msgs) 308 *msgs = nums[0]; 309 if (bytes) 310 *bytes = nums[1]; 311 return 0; 312 } 313 314 struct scanlisting * 315 pop_list(FILE *fp, int *_len) 316 { 317 char *ap[2]; 318 char buf[POPMAX]; 319 struct scanlisting *listings; 320 int len, i; 321 322 if (pop_stat(fp, &len, NULL)) 323 return NULL; 324 325 if (len > LISTMAX) { 326 /* More descriptive errors would be nice, 327 * maybe just print them out. 328 */ 329 errno = EOVERFLOW; 330 return NULL; 331 } 332 333 listings = calloc(len, sizeof(*listings)); 334 if (len == 0 || listings == NULL) { 335 if (_len) 336 *_len = 0; 337 return listings; 338 } 339 340 if (!pop_okay(pop_comd(fp, buf, sizeof(buf), "LIST", NULL))) { 341 free(listings); 342 return NULL; 343 } 344 345 for (i = 0; i < len && pop_read(fp, buf, sizeof(buf)) && 346 strcmp(buf, ".") != 0; ++i) { 347 /* Beginning . would be invalid anyway, 348 * so strtonum can check it. 349 */ 350 if (pop_split(ap, 2, buf) != 2) { 351 free(listings); 352 errno = EFTYPE; 353 return NULL; 354 } 355 356 if (strlcpy(listings[i].msg, ap[0], sizeof(listings[i].msg)) >= 357 sizeof(listings[i].msg)) { 358 free(listings); 359 errno = ENOBUFS; 360 return NULL; 361 } 362 363 listings[i].bytes = strtonum(ap[1], 0, INT_MAX, &ap[0]); 364 if (ap[0]) { 365 free(listings); 366 return NULL; 367 } 368 } 369 370 if (_len) 371 *_len = len; 372 return listings; 373 } 374 375 int 376 pop_readmsg(FILE *fp, char *buf) 377 { 378 int len; 379 380 if (!pop_read(fp, buf, LINEMAX)) 381 return -1; 382 if (strcmp(buf, ".") == 0) 383 return 0; 384 385 len = strlen(buf); 386 if (*buf == '.') 387 memmove(buf, buf + 1, --len); 388 return len + 1; 389 } 390 391 int 392 pop_retr(FILE *fp, char *msg) 393 { 394 char buf[POPMAX]; 395 396 return pop_okay(pop_comd(fp, buf, sizeof(buf), "RETR", msg)) == NULL; 397 } 398 399 int 400 pop_dele(FILE *fp, char *msg) 401 { 402 char buf[POPMAX]; 403 404 return pop_okay(pop_comd(fp, buf, sizeof(buf), "DELE", msg)) == NULL; 405 } 406 407 int 408 writemsg(FILE *fp, char *msg, int out) 409 { 410 char buf[LINEMAX]; 411 int len; 412 413 if (pop_retr(fp, msg)) 414 return 1; 415 while ((len = pop_readmsg(fp, buf)) > 0) { 416 if (dprintf(out, "%s\n", buf) < 0) 417 return 1; 418 } 419 return len != 0; 420 } 421 422 int 423 mail(FILE *fp, char *msg, char **mailer) 424 { 425 int fds[2]; 426 int status; 427 pid_t pid; 428 429 if (pipe(fds)) 430 return -1; 431 432 switch ((pid = fork())) { 433 case -1: 434 close(fds[0]); 435 close(fds[1]); 436 return -1; 437 case 0: 438 if (close(fds[1]) < 0 || dup2(fds[0], 0) < 0) 439 _exit(127); 440 execvp(*mailer, mailer); 441 _exit(127); 442 } 443 444 if (close(fds[0]) < 0 || writemsg(fp, msg, fds[1])) { 445 close(fds[1]); 446 kill(pid, SIGTERM); 447 return -1; 448 } 449 450 if (close(fds[1]) < 0) { 451 kill(pid, SIGTERM); 452 return -1; 453 } 454 455 if (waitpid(pid, &status, 0) < 0) 456 return -1; 457 return WEXITSTATUS(status); 458 } 459 460 int 461 mbox(FILE *fp, char *msg, char **unused) 462 { 463 char buf[LINEMAX]; 464 int escape; 465 int len; 466 time_t tim; 467 char *stim; 468 469 if (pop_retr(fp, msg)) 470 return -1; 471 472 tim = time(NULL); 473 stim = ctime(&tim); 474 if (!stim || printf("From <unknown> %s", stim) < 0) 475 return -1; 476 while ((len = pop_readmsg(fp, buf)) > 0) { 477 escape = strncmp(buf, "From ", 5) == 0; 478 if (printf(escape ? ">%s\n" : "%s\n", buf) < 0) 479 return -1; 480 } 481 482 if (len != 0) { 483 errno = ENOBUFS; 484 return -1; 485 } 486 if (printf("\n") < 0) 487 return -1; 488 return 0; 489 } 490 491 void 492 sigbail(int unused) 493 { 494 bail = 1; 495 } 496 497 int 498 getmail(FILE *fp, int delete, char **mailer) 499 { 500 int len, i; 501 int fails; 502 struct scanlisting *listings; 503 int (*sendmail)(FILE *, char *, char **); 504 505 listings = pop_list(fp, &len); 506 if (listings == NULL) 507 return -1; 508 509 fails = 0; 510 sendmail = mailer ? mail : mbox; 511 for (i = 0; i < len && !bail; ++i) { 512 if (sendmail(fp, listings[i].msg, mailer) || 513 (delete && pop_dele(fp, listings[i].msg))) 514 ++fails; 515 } 516 517 free(listings); 518 return fails + (len - i); 519 } 520 521 int 522 stat(FILE *fp, FILE *out) 523 { 524 int msgs, bytes; 525 526 if (pop_stat(fp, &msgs, &bytes)) 527 return 1;; 528 return fprintf(out, "%d %s %d %s\n", 529 msgs, msgs == 1 ? "message" : "messages", 530 bytes, bytes == 1 ? "byte" : "bytes") < 0; 531 } 532 533 int 534 auth(FILE *fp, char *user, int usestdin) 535 { 536 char pass[ARGMAX + 1]; 537 int status; 538 539 if (!readpassphrase("Passphrase: ", pass, sizeof(pass), 540 usestdin ? RPP_STDIN : RPP_REQUIRE_TTY)) 541 die("unable to read passphrase"); 542 status = pop_auth(fp, user, pass); 543 explicit_bzero(pass, sizeof(pass)); 544 545 return status; 546 } 547 548 int 549 main(int argc, char *argv[]) 550 { 551 FILE *fp; 552 int c; 553 554 if (pledge("stdio tty inet dns proc exec", NULL)) 555 die("pledge"); 556 557 user = getlogin(); 558 while ((c = getopt(argc, argv, "dp:qstu:v")) >= 0) { 559 switch (c) { 560 case 'd': 561 delete = 1; 562 break; 563 case 'p': 564 port = optarg; 565 break; 566 case 'q': 567 query = 1; 568 break; 569 case 's': 570 usestdin = 1; 571 break; 572 case 't': 573 trace = 1; 574 break; 575 case 'u': 576 user = optarg; 577 break; 578 case 'v': 579 verbose = 1; 580 break; 581 default: 582 usage(NULL); 583 } 584 } 585 argc -= optind; 586 argv += optind; 587 588 if (argc < 1) 589 usage("No host"); 590 host = argv[0]; 591 592 if (argc > 1) 593 mailer = argv + 1; 594 595 fp = pop_open(host, port); 596 if (fp == NULL) 597 die("'%s'", host); 598 599 if (auth(fp, user, usestdin)) 600 die("unable to authenticate for '%s'", user); 601 602 if (pledge(mailer ? "stdio proc exec" : "stdio", NULL)) 603 die("pledge"); 604 605 if (query) { 606 if (stat(fp, stdout)) 607 die("unable to stat"); 608 } else { 609 signal(SIGINT, sigbail); 610 signal(SIGTERM, sigbail); 611 if (getmail(fp, delete, mailer)) { 612 pop_quit(fp); 613 die("unable to get mail"); 614 } 615 if (verbose && stat(fp, stderr)) 616 die("unable to stat"); 617 } 618 619 return !!pop_quit(fp); 620 }