1 /* $OpenBSD: smtpc.c,v 1.15 2021/04/10 10:19:19 eric 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/types.h> 20 #include <sys/socket.h> 21 22 #include <event.h> 23 #include <limits.h> 24 #include <netdb.h> 25 #include <pwd.h> 26 #include <resolv.h> 27 #include <stdarg.h> 28 #include <stdio.h> 29 #include <stdlib.h> 30 #include <string.h> 31 #include <syslog.h> 32 #include <tls.h> 33 #include <unistd.h> 34 35 #include "smtp.h" 36 #include "log.h" 37 38 static void parse_server(char *); 39 static void parse_message(FILE *); 40 static void resume(void); 41 42 static int verbose = 1; 43 static int done = 0; 44 static int noaction = 0; 45 static struct addrinfo *res0, *ai; 46 static struct smtp_params params; 47 static struct smtp_mail mail; 48 static const char *servname = NULL; 49 static struct tls_config *tls_config; 50 51 static void 52 usage(void) 53 { 54 extern char *__progname; 55 56 fprintf(stderr, "usage: %s [-Chnv] [-a authfile] [-F from] [-H helo] " 57 "[-s server] [recipient ...]\n", __progname); 58 exit(1); 59 } 60 61 int 62 main(int argc, char **argv) 63 { 64 char hostname[256]; 65 FILE *authfile; 66 int ch, i; 67 char *server = "localhost"; 68 char *authstr = NULL; 69 size_t alloc = 0; 70 ssize_t len; 71 struct passwd *pw; 72 73 log_init(1, 0); 74 75 if (gethostname(hostname, sizeof(hostname)) == -1) 76 fatal("gethostname"); 77 78 if ((pw = getpwuid(getuid())) == NULL) 79 fatal("getpwuid"); 80 81 memset(¶ms, 0, sizeof(params)); 82 83 params.linemax = 16392; 84 params.ibufmax = 65536; 85 params.obufmax = 65536; 86 params.timeout = 100000; 87 params.helo = hostname; 88 89 params.tls_verify = 1; 90 91 memset(&mail, 0, sizeof(mail)); 92 mail.from = pw->pw_name; 93 94 while ((ch = getopt(argc, argv, "CF:H:S:a:hns:v")) != -1) { 95 switch (ch) { 96 case 'C': 97 params.tls_verify = 0; 98 break; 99 case 'F': 100 mail.from = optarg; 101 break; 102 case 'H': 103 params.helo = optarg; 104 break; 105 case 'S': 106 servname = optarg; 107 break; 108 case 'a': 109 if ((authfile = fopen(optarg, "r")) == NULL) 110 fatal("%s: open", optarg); 111 if ((len = getline(&authstr, &alloc, authfile)) == -1) 112 fatal("%s: Failed to read username", optarg); 113 if (authstr[len - 1] == '\n') 114 authstr[len - 1] = '\0'; 115 params.auth_user = authstr; 116 authstr = NULL; 117 len = 0; 118 if ((len = getline(&authstr, &alloc, authfile)) == -1) 119 fatal("%s: Failed to read password", optarg); 120 if (authstr[len - 1] == '\n') 121 authstr[len - 1] = '\0'; 122 params.auth_pass = authstr; 123 fclose(authfile); 124 break; 125 case 'h': 126 usage(); 127 break; 128 case 'n': 129 noaction = 1; 130 break; 131 case 's': 132 server = optarg; 133 break; 134 case 'v': 135 verbose++; 136 break; 137 default: 138 usage(); 139 } 140 } 141 142 log_setverbose(verbose); 143 144 argc -= optind; 145 argv += optind; 146 147 if (argc) { 148 mail.rcpt = calloc(argc, sizeof(*mail.rcpt)); 149 if (mail.rcpt == NULL) 150 fatal("calloc"); 151 for (i = 0; i < argc; i++) 152 mail.rcpt[i].to = argv[i]; 153 mail.rcptcount = argc; 154 } 155 156 tls_init(); 157 event_init(); 158 159 tls_config = tls_config_new(); 160 if (tls_config == NULL) 161 fatal("tls_config_new"); 162 if (tls_config_set_ca_file(tls_config, tls_default_ca_cert_file()) == -1) 163 fatal("tls_set_ca_file"); 164 if (!params.tls_verify) { 165 tls_config_insecure_noverifycert(tls_config); 166 tls_config_insecure_noverifyname(tls_config); 167 tls_config_insecure_noverifytime(tls_config); 168 } else 169 tls_config_verify(tls_config); 170 171 if (pledge("stdio inet dns tmppath", NULL) == -1) 172 fatal("pledge"); 173 174 if (!noaction) 175 parse_message(stdin); 176 177 if (pledge("stdio inet dns", NULL) == -1) 178 fatal("pledge"); 179 180 parse_server(server); 181 182 if (pledge("stdio inet", NULL) == -1) 183 fatal("pledge"); 184 185 resume(); 186 187 log_debug("done..."); 188 189 return 0; 190 } 191 192 static void 193 parse_server(char *server) 194 { 195 struct addrinfo hints; 196 char *scheme, *creds, *host, *port, *p, *c; 197 int error; 198 199 creds = NULL; 200 host = NULL; 201 port = NULL; 202 scheme = server; 203 204 p = strstr(server, "://"); 205 if (p) { 206 *p = '\0'; 207 p += 3; 208 /* check for credentials */ 209 c = strrchr(p, '@'); 210 if (c) { 211 creds = p; 212 *c = '\0'; 213 host = c + 1; 214 } else 215 host = p; 216 } else { 217 /* Assume a simple server name */ 218 scheme = "smtp"; 219 host = server; 220 } 221 222 if (host[0] == '[') { 223 /* IPV6 address? */ 224 p = strchr(host, ']'); 225 if (p) { 226 if (p[1] == ':' || p[1] == '\0') { 227 *p++ = '\0'; /* remove ']' */ 228 host++; /* skip '[' */ 229 if (*p == ':') 230 port = p + 1; 231 } 232 } 233 } 234 else { 235 port = strchr(host, ':'); 236 if (port) 237 *port++ = '\0'; 238 } 239 240 if (port && port[0] == '\0') 241 port = NULL; 242 243 if (creds) { 244 p = strchr(creds, ':'); 245 if (p == NULL) 246 fatalx("invalid credentials"); 247 *p = '\0'; 248 249 params.auth_user = creds; 250 params.auth_pass = p + 1; 251 } 252 params.tls_req = TLS_YES; 253 254 if (!strcmp(scheme, "lmtp")) { 255 params.lmtp = 1; 256 } 257 else if (!strcmp(scheme, "lmtp+tls")) { 258 params.lmtp = 1; 259 params.tls_req = TLS_FORCE; 260 } 261 else if (!strcmp(scheme, "lmtp+notls")) { 262 params.lmtp = 1; 263 params.tls_req = TLS_NO; 264 } 265 else if (!strcmp(scheme, "smtps")) { 266 params.tls_req = TLS_SMTPS; 267 if (port == NULL) 268 port = "smtps"; 269 } 270 else if (!strcmp(scheme, "smtp")) { 271 } 272 else if (!strcmp(scheme, "smtp+tls")) { 273 params.tls_req = TLS_FORCE; 274 } 275 else if (!strcmp(scheme, "smtp+notls")) { 276 params.tls_req = TLS_NO; 277 } 278 else 279 fatalx("invalid url scheme %s", scheme); 280 281 if (port == NULL) 282 port = "smtp"; 283 284 if (servname == NULL) 285 servname = host; 286 params.tls_servname = servname; 287 288 memset(&hints, 0, sizeof(hints)); 289 hints.ai_family = AF_UNSPEC; 290 hints.ai_socktype = SOCK_STREAM; 291 error = getaddrinfo(host, port, &hints, &res0); 292 if (error) 293 fatalx("%s: %s", host, gai_strerror(error)); 294 ai = res0; 295 } 296 297 void 298 parse_message(FILE *ifp) 299 { 300 char *line = NULL; 301 size_t linesz = 0; 302 ssize_t len; 303 304 if ((mail.fp = tmpfile()) == NULL) 305 fatal("tmpfile"); 306 307 for (;;) { 308 if ((len = getline(&line, &linesz, ifp)) == -1) { 309 if (feof(ifp)) 310 break; 311 fatal("getline"); 312 } 313 314 if (len >= 2 && line[len - 2] == '\r' && line[len - 1] == '\n') 315 line[--len - 1] = '\n'; 316 317 if (fwrite(line, 1, len, mail.fp) != len) 318 fatal("fwrite"); 319 320 if (line[len - 1] != '\n' && fputc('\n', mail.fp) == EOF) 321 fatal("fputc"); 322 } 323 324 fclose(ifp); 325 rewind(mail.fp); 326 } 327 328 void 329 resume(void) 330 { 331 static int started = 0; 332 char host[256]; 333 char serv[16]; 334 335 if (done) { 336 event_loopexit(NULL); 337 return; 338 } 339 340 if (ai == NULL) 341 fatalx("no more host"); 342 343 getnameinfo(ai->ai_addr, ai->ai_addr->sa_len, 344 host, sizeof(host), serv, sizeof(serv), 345 NI_NUMERICHOST | NI_NUMERICSERV); 346 log_debug("trying host %s port %s...", host, serv); 347 348 params.dst = ai->ai_addr; 349 if (smtp_connect(¶ms, NULL) == NULL) 350 fatal("smtp_connect"); 351 352 if (started == 0) { 353 started = 1; 354 event_loop(0); 355 } 356 } 357 358 void 359 log_trace(int lvl, const char *emsg, ...) 360 { 361 va_list ap; 362 363 if (verbose > lvl) { 364 va_start(ap, emsg); 365 vlog(LOG_DEBUG, emsg, ap); 366 va_end(ap); 367 } 368 } 369 370 void 371 smtp_require_tls(void *tag, struct smtp_client *proto) 372 { 373 struct tls *tls; 374 375 tls = tls_client(); 376 if (tls == NULL) 377 fatal("tls_client"); 378 379 if (tls_configure(tls, tls_config) == -1) 380 fatal("tls_configure"); 381 382 smtp_set_tls(proto, tls); 383 } 384 385 void 386 smtp_ready(void *tag, struct smtp_client *proto) 387 { 388 log_debug("connection ready..."); 389 390 if (done || noaction) 391 smtp_quit(proto); 392 else 393 smtp_sendmail(proto, &mail); 394 } 395 396 void 397 smtp_failed(void *tag, struct smtp_client *proto, int failure, const char *detail) 398 { 399 switch (failure) { 400 case FAIL_INTERNAL: 401 log_warnx("internal error: %s", detail); 402 break; 403 case FAIL_CONN: 404 log_warnx("connection error: %s", detail); 405 break; 406 case FAIL_PROTO: 407 log_warnx("protocol error: %s", detail); 408 break; 409 case FAIL_IMPL: 410 log_warnx("missing feature: %s", detail); 411 break; 412 case FAIL_RESP: 413 log_warnx("rejected by server: %s", detail); 414 break; 415 default: 416 fatalx("unknown failure %d: %s", failure, detail); 417 } 418 } 419 420 void 421 smtp_status(void *tag, struct smtp_client *proto, struct smtp_status *status) 422 { 423 log_info("%s: %s: %s", status->rcpt->to, status->cmd, status->status); 424 } 425 426 void 427 smtp_done(void *tag, struct smtp_client *proto, struct smtp_mail *mail) 428 { 429 int i; 430 431 log_debug("mail done..."); 432 433 if (noaction) 434 return; 435 436 for (i = 0; i < mail->rcptcount; i++) 437 if (!mail->rcpt[i].done) 438 return; 439 440 done = 1; 441 } 442 443 void 444 smtp_closed(void *tag, struct smtp_client *proto) 445 { 446 log_debug("connection closed..."); 447 448 ai = ai->ai_next; 449 if (noaction && ai == NULL) 450 done = 1; 451 452 resume(); 453 } 454