1 /* $OpenBSD: smtpc.c,v 1.20 2023/05/16 17:48:52 op Exp $ */ 2 3 /* 4 * Copyright (c) 2018 Eric Faurot <eric@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <sys/socket.h> 20 21 #include <event.h> 22 #include <netdb.h> 23 #include <pwd.h> 24 #include <stdio.h> 25 #include <stdlib.h> 26 #include <string.h> 27 #include <syslog.h> 28 #include <tls.h> 29 #include <unistd.h> 30 31 #include "smtp.h" 32 #include "log.h" 33 34 static void parse_server(char *); 35 static void parse_message(FILE *); 36 static void resume(void); 37 38 static int verbose = 1; 39 static int done = 0; 40 static int noaction = 0; 41 static struct addrinfo *res0, *ai; 42 static struct smtp_params params; 43 static struct smtp_mail mail; 44 static const char *servname = NULL; 45 static struct tls_config *tls_config; 46 47 static int nosni = 0; 48 static const char *cafile = NULL; 49 static const char *protocols = NULL; 50 static const char *ciphers = NULL; 51 52 static void 53 usage(void) 54 { 55 extern char *__progname; 56 57 fprintf(stderr, "usage: %s [-Chnv] [-a authfile] [-F from] [-H helo] " 58 "[-s server] [-T params] [recipient ...]\n", __progname); 59 exit(1); 60 } 61 62 static void 63 parse_tls_options(char *opt) 64 { 65 static char * const tokens[] = { 66 #define CAFILE 0 67 "cafile", 68 #define CIPHERS 1 69 "ciphers", 70 #define NOSNI 2 71 "nosni", 72 #define NOVERIFY 3 73 "noverify", 74 #define PROTOCOLS 4 75 "protocols", 76 #define SERVERNAME 5 77 "servername", 78 NULL }; 79 char *value; 80 81 while (*opt) { 82 switch (getsubopt(&opt, tokens, &value)) { 83 case CAFILE: 84 if (value == NULL) 85 fatalx("missing value for cafile"); 86 cafile = value; 87 break; 88 case CIPHERS: 89 if (value == NULL) 90 fatalx("missing value for ciphers"); 91 ciphers = value; 92 break; 93 case NOSNI: 94 if (value != NULL) 95 fatalx("no value expected for nosni"); 96 nosni = 1; 97 break; 98 case NOVERIFY: 99 if (value != NULL) 100 fatalx("no value expected for noverify"); 101 params.tls_verify = 0; 102 break; 103 case PROTOCOLS: 104 if (value == NULL) 105 fatalx("missing value for protocols"); 106 protocols = value; 107 break; 108 case SERVERNAME: 109 if (value == NULL) 110 fatalx("missing value for servername"); 111 servname = value; 112 break; 113 case -1: 114 if (suboptarg) 115 fatalx("invalid TLS option \"%s\"", suboptarg); 116 fatalx("missing TLS option"); 117 } 118 } 119 } 120 121 int 122 main(int argc, char **argv) 123 { 124 char hostname[256]; 125 FILE *authfile; 126 int ch, i; 127 uint32_t protos; 128 char *server = "localhost"; 129 char *authstr = NULL; 130 size_t alloc = 0; 131 ssize_t len; 132 struct passwd *pw; 133 134 log_init(1, 0); 135 136 if (gethostname(hostname, sizeof(hostname)) == -1) 137 fatal("gethostname"); 138 139 if ((pw = getpwuid(getuid())) == NULL) 140 fatal("getpwuid"); 141 142 memset(¶ms, 0, sizeof(params)); 143 144 params.linemax = 16392; 145 params.ibufmax = 65536; 146 params.obufmax = 65536; 147 params.timeout = 100000; 148 params.helo = hostname; 149 150 params.tls_verify = 1; 151 152 memset(&mail, 0, sizeof(mail)); 153 mail.from = pw->pw_name; 154 155 while ((ch = getopt(argc, argv, "CF:H:S:T:a:hns:v")) != -1) { 156 switch (ch) { 157 case 'C': 158 params.tls_verify = 0; 159 break; 160 case 'F': 161 mail.from = optarg; 162 break; 163 case 'H': 164 params.helo = optarg; 165 break; 166 case 'S': 167 servname = optarg; 168 break; 169 case 'T': 170 parse_tls_options(optarg); 171 break; 172 case 'a': 173 if ((authfile = fopen(optarg, "r")) == NULL) 174 fatal("%s: open", optarg); 175 if ((len = getline(&authstr, &alloc, authfile)) == -1) 176 fatal("%s: Failed to read username", optarg); 177 if (authstr[len - 1] == '\n') 178 authstr[len - 1] = '\0'; 179 params.auth_user = authstr; 180 authstr = NULL; 181 len = 0; 182 if ((len = getline(&authstr, &alloc, authfile)) == -1) 183 fatal("%s: Failed to read password", optarg); 184 if (authstr[len - 1] == '\n') 185 authstr[len - 1] = '\0'; 186 params.auth_pass = authstr; 187 fclose(authfile); 188 break; 189 case 'h': 190 usage(); 191 break; 192 case 'n': 193 noaction = 1; 194 break; 195 case 's': 196 server = optarg; 197 break; 198 case 'v': 199 verbose++; 200 break; 201 default: 202 usage(); 203 } 204 } 205 206 log_setverbose(verbose); 207 208 argc -= optind; 209 argv += optind; 210 211 if (argc) { 212 mail.rcpt = calloc(argc, sizeof(*mail.rcpt)); 213 if (mail.rcpt == NULL) 214 fatal("calloc"); 215 for (i = 0; i < argc; i++) 216 mail.rcpt[i].to = argv[i]; 217 mail.rcptcount = argc; 218 } 219 220 event_init(); 221 222 tls_config = tls_config_new(); 223 if (tls_config == NULL) 224 fatal("tls_config_new"); 225 226 if (protocols) { 227 if (tls_config_parse_protocols(&protos, protocols) == -1) 228 fatalx("failed to parse protocol '%s'", protocols); 229 if (tls_config_set_protocols(tls_config, protos) == -1) 230 fatalx("tls_config_set_protocols: %s", 231 tls_config_error(tls_config)); 232 } 233 if (ciphers && tls_config_set_ciphers(tls_config, ciphers) == -1) 234 fatalx("tls_config_set_ciphers: %s", 235 tls_config_error(tls_config)); 236 237 if (cafile == NULL) 238 cafile = tls_default_ca_cert_file(); 239 if (tls_config_set_ca_file(tls_config, cafile) == -1) 240 fatalx("tls_set_ca_file: %s", tls_config_error(tls_config)); 241 if (!params.tls_verify) { 242 tls_config_insecure_noverifycert(tls_config); 243 tls_config_insecure_noverifyname(tls_config); 244 tls_config_insecure_noverifytime(tls_config); 245 } else 246 tls_config_verify(tls_config); 247 248 if (pledge("stdio inet dns tmppath", NULL) == -1) 249 fatal("pledge"); 250 251 if (!noaction) 252 parse_message(stdin); 253 254 if (pledge("stdio inet dns", NULL) == -1) 255 fatal("pledge"); 256 257 parse_server(server); 258 259 if (pledge("stdio inet", NULL) == -1) 260 fatal("pledge"); 261 262 resume(); 263 264 log_debug("done..."); 265 266 return 0; 267 } 268 269 static void 270 parse_server(char *server) 271 { 272 struct addrinfo hints; 273 char *scheme, *creds, *host, *port, *p, *c; 274 int error; 275 276 creds = NULL; 277 host = NULL; 278 port = NULL; 279 scheme = server; 280 281 p = strstr(server, "://"); 282 if (p) { 283 *p = '\0'; 284 p += 3; 285 /* check for credentials */ 286 c = strrchr(p, '@'); 287 if (c) { 288 creds = p; 289 *c = '\0'; 290 host = c + 1; 291 } else 292 host = p; 293 } else { 294 /* Assume a simple server name */ 295 scheme = "smtp"; 296 host = server; 297 } 298 299 if (host[0] == '[') { 300 /* IPV6 address? */ 301 p = strchr(host, ']'); 302 if (p) { 303 if (p[1] == ':' || p[1] == '\0') { 304 *p++ = '\0'; /* remove ']' */ 305 host++; /* skip '[' */ 306 if (*p == ':') 307 port = p + 1; 308 } 309 } 310 } 311 else { 312 port = strchr(host, ':'); 313 if (port) 314 *port++ = '\0'; 315 } 316 317 if (port && port[0] == '\0') 318 port = NULL; 319 320 if (creds) { 321 p = strchr(creds, ':'); 322 if (p == NULL) 323 fatalx("invalid credentials"); 324 *p = '\0'; 325 326 params.auth_user = creds; 327 params.auth_pass = p + 1; 328 } 329 params.tls_req = TLS_YES; 330 331 if (!strcmp(scheme, "lmtp")) { 332 params.lmtp = 1; 333 } 334 else if (!strcmp(scheme, "lmtp+tls")) { 335 params.lmtp = 1; 336 params.tls_req = TLS_FORCE; 337 } 338 else if (!strcmp(scheme, "lmtp+notls")) { 339 params.lmtp = 1; 340 params.tls_req = TLS_NO; 341 } 342 else if (!strcmp(scheme, "smtps")) { 343 params.tls_req = TLS_SMTPS; 344 if (port == NULL) 345 port = "smtps"; 346 } 347 else if (!strcmp(scheme, "smtp")) { 348 } 349 else if (!strcmp(scheme, "smtp+tls")) { 350 params.tls_req = TLS_FORCE; 351 } 352 else if (!strcmp(scheme, "smtp+notls")) { 353 params.tls_req = TLS_NO; 354 } 355 else 356 fatalx("invalid url scheme %s", scheme); 357 358 if (port == NULL) 359 port = "smtp"; 360 361 if (servname == NULL) 362 servname = host; 363 if (nosni == 0) 364 params.tls_servname = servname; 365 366 memset(&hints, 0, sizeof(hints)); 367 hints.ai_family = AF_UNSPEC; 368 hints.ai_socktype = SOCK_STREAM; 369 error = getaddrinfo(host, port, &hints, &res0); 370 if (error) 371 fatalx("%s: %s", host, gai_strerror(error)); 372 ai = res0; 373 } 374 375 void 376 parse_message(FILE *ifp) 377 { 378 char *line = NULL; 379 size_t linesz = 0; 380 ssize_t len; 381 382 if ((mail.fp = tmpfile()) == NULL) 383 fatal("tmpfile"); 384 385 for (;;) { 386 if ((len = getline(&line, &linesz, ifp)) == -1) { 387 if (feof(ifp)) 388 break; 389 fatal("getline"); 390 } 391 392 if (len >= 2 && line[len - 2] == '\r' && line[len - 1] == '\n') 393 line[--len - 1] = '\n'; 394 395 if (fwrite(line, 1, len, mail.fp) != len) 396 fatal("fwrite"); 397 398 if (line[len - 1] != '\n' && fputc('\n', mail.fp) == EOF) 399 fatal("fputc"); 400 } 401 402 fclose(ifp); 403 rewind(mail.fp); 404 } 405 406 void 407 resume(void) 408 { 409 static int started = 0; 410 char host[256]; 411 char serv[16]; 412 413 if (done) { 414 event_loopexit(NULL); 415 return; 416 } 417 418 if (ai == NULL) 419 fatalx("no more host"); 420 421 getnameinfo(ai->ai_addr, ai->ai_addr->sa_len, 422 host, sizeof(host), serv, sizeof(serv), 423 NI_NUMERICHOST | NI_NUMERICSERV); 424 log_debug("trying host %s port %s...", host, serv); 425 426 params.dst = ai->ai_addr; 427 if (smtp_connect(¶ms, NULL) == NULL) 428 fatal("smtp_connect"); 429 430 if (started == 0) { 431 started = 1; 432 event_loop(0); 433 } 434 } 435 436 void 437 log_trace(int lvl, const char *emsg, ...) 438 { 439 va_list ap; 440 441 if (verbose > lvl) { 442 va_start(ap, emsg); 443 vlog(LOG_DEBUG, emsg, ap); 444 va_end(ap); 445 } 446 } 447 448 void 449 smtp_require_tls(void *tag, struct smtp_client *proto) 450 { 451 struct tls *tls; 452 453 tls = tls_client(); 454 if (tls == NULL) 455 fatal("tls_client"); 456 457 if (tls_configure(tls, tls_config) == -1) 458 fatalx("tls_configure: %s", tls_error(tls)); 459 460 smtp_set_tls(proto, tls); 461 } 462 463 void 464 smtp_ready(void *tag, struct smtp_client *proto) 465 { 466 log_debug("connection ready..."); 467 468 if (done || noaction) 469 smtp_quit(proto); 470 else 471 smtp_sendmail(proto, &mail); 472 } 473 474 void 475 smtp_failed(void *tag, struct smtp_client *proto, int failure, const char *detail) 476 { 477 switch (failure) { 478 case FAIL_INTERNAL: 479 log_warnx("internal error: %s", detail); 480 break; 481 case FAIL_CONN: 482 log_warnx("connection error: %s", detail); 483 break; 484 case FAIL_PROTO: 485 log_warnx("protocol error: %s", detail); 486 break; 487 case FAIL_IMPL: 488 log_warnx("missing feature: %s", detail); 489 break; 490 case FAIL_RESP: 491 log_warnx("rejected by server: %s", detail); 492 break; 493 default: 494 fatalx("unknown failure %d: %s", failure, detail); 495 } 496 } 497 498 void 499 smtp_status(void *tag, struct smtp_client *proto, struct smtp_status *status) 500 { 501 log_info("%s: %s: %s", status->rcpt->to, status->cmd, status->status); 502 } 503 504 void 505 smtp_done(void *tag, struct smtp_client *proto, struct smtp_mail *mail) 506 { 507 int i; 508 509 log_debug("mail done..."); 510 511 if (noaction) 512 return; 513 514 for (i = 0; i < mail->rcptcount; i++) 515 if (!mail->rcpt[i].done) 516 return; 517 518 done = 1; 519 } 520 521 void 522 smtp_closed(void *tag, struct smtp_client *proto) 523 { 524 log_debug("connection closed..."); 525 526 ai = ai->ai_next; 527 if (noaction && ai == NULL) 528 done = 1; 529 530 resume(); 531 } 532