1 /* $OpenBSD: smtpc.c,v 1.11 2020/09/14 18:32:11 millert 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 <unistd.h> 33 34 #include <openssl/ssl.h> 35 36 #include "smtp.h" 37 #include "ssl.h" 38 #include "log.h" 39 40 static void parse_server(char *); 41 static void parse_message(FILE *); 42 static void resume(void); 43 44 static int verbose = 1; 45 static int done = 0; 46 static int noaction = 0; 47 static struct addrinfo *res0, *ai; 48 static struct smtp_params params; 49 static struct smtp_mail mail; 50 static const char *servname = NULL; 51 52 static SSL_CTX *ssl_ctx; 53 54 static void 55 usage(void) 56 { 57 extern char *__progname; 58 59 fprintf(stderr, 60 "usage: %s [-Chnv] [-F from] [-H helo] [-s server] [-S name] rcpt ...\n", 61 __progname); 62 exit(1); 63 } 64 65 int 66 main(int argc, char **argv) 67 { 68 char hostname[256]; 69 int ch, i; 70 char *server = "localhost"; 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: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 'h': 109 usage(); 110 break; 111 case 'n': 112 noaction = 1; 113 break; 114 case 's': 115 server = optarg; 116 break; 117 case 'v': 118 verbose++; 119 break; 120 default: 121 usage(); 122 } 123 } 124 125 log_setverbose(verbose); 126 127 argc -= optind; 128 argv += optind; 129 130 if (argc) { 131 mail.rcpt = calloc(argc, sizeof(*mail.rcpt)); 132 if (mail.rcpt == NULL) 133 fatal("calloc"); 134 for (i = 0; i < argc; i++) 135 mail.rcpt[i].to = argv[i]; 136 mail.rcptcount = argc; 137 } 138 139 ssl_init(); 140 event_init(); 141 142 ssl_ctx = ssl_ctx_create(NULL, NULL, 0, NULL); 143 if (!SSL_CTX_load_verify_locations(ssl_ctx, 144 X509_get_default_cert_file(), NULL)) 145 fatal("SSL_CTX_load_verify_locations"); 146 if (!SSL_CTX_set_ssl_version(ssl_ctx, SSLv23_client_method())) 147 fatal("SSL_CTX_set_ssl_version"); 148 SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE , NULL); 149 150 if (pledge("stdio inet dns tmppath", NULL) == -1) 151 fatal("pledge"); 152 153 if (!noaction) 154 parse_message(stdin); 155 156 if (pledge("stdio inet dns", NULL) == -1) 157 fatal("pledge"); 158 159 parse_server(server); 160 161 if (pledge("stdio inet", NULL) == -1) 162 fatal("pledge"); 163 164 resume(); 165 166 log_debug("done..."); 167 168 return 0; 169 } 170 171 static void 172 parse_server(char *server) 173 { 174 struct addrinfo hints; 175 char *scheme, *creds, *host, *port, *p, *c; 176 int error; 177 178 creds = NULL; 179 host = NULL; 180 port = NULL; 181 scheme = server; 182 183 p = strstr(server, "://"); 184 if (p) { 185 *p = '\0'; 186 p += 3; 187 /* check for credentials */ 188 c = strrchr(p, '@'); 189 if (c) { 190 creds = p; 191 *c = '\0'; 192 host = c + 1; 193 } else 194 host = p; 195 } else { 196 /* Assume a simple server name */ 197 scheme = "smtp"; 198 host = server; 199 } 200 201 if (host[0] == '[') { 202 /* IPV6 address? */ 203 p = strchr(host, ']'); 204 if (p) { 205 if (p[1] == ':' || p[1] == '\0') { 206 *p++ = '\0'; /* remove ']' */ 207 host++; /* skip '[' */ 208 if (*p == ':') 209 port = p + 1; 210 } 211 } 212 } 213 else { 214 port = strchr(host, ':'); 215 if (port) 216 *port++ = '\0'; 217 } 218 219 if (port && port[0] == '\0') 220 port = NULL; 221 222 if (creds) { 223 p = strchr(creds, ':'); 224 if (p == NULL) 225 fatalx("invalid credentials"); 226 *p = '\0'; 227 228 params.auth_user = creds; 229 params.auth_pass = p + 1; 230 } 231 params.tls_req = TLS_YES; 232 233 if (!strcmp(scheme, "lmtp")) { 234 params.lmtp = 1; 235 } 236 else if (!strcmp(scheme, "lmtp+tls")) { 237 params.lmtp = 1; 238 params.tls_req = TLS_FORCE; 239 } 240 else if (!strcmp(scheme, "lmtp+notls")) { 241 params.lmtp = 1; 242 params.tls_req = TLS_NO; 243 } 244 else if (!strcmp(scheme, "smtps")) { 245 params.tls_req = TLS_SMTPS; 246 if (port == NULL) 247 port = "smtps"; 248 } 249 else if (!strcmp(scheme, "smtp")) { 250 } 251 else if (!strcmp(scheme, "smtp+tls")) { 252 params.tls_req = TLS_FORCE; 253 } 254 else if (!strcmp(scheme, "smtp+notls")) { 255 params.tls_req = TLS_NO; 256 } 257 else 258 fatalx("invalid url scheme %s", scheme); 259 260 if (port == NULL) 261 port = "smtp"; 262 263 if (servname == NULL) 264 servname = host; 265 266 memset(&hints, 0, sizeof(hints)); 267 hints.ai_family = AF_UNSPEC; 268 hints.ai_socktype = SOCK_STREAM; 269 error = getaddrinfo(host, port, &hints, &res0); 270 if (error) 271 fatalx("%s: %s", host, gai_strerror(error)); 272 ai = res0; 273 } 274 275 void 276 parse_message(FILE *ifp) 277 { 278 char *line = NULL; 279 size_t linesz = 0; 280 ssize_t len; 281 282 if ((mail.fp = tmpfile()) == NULL) 283 fatal("tmpfile"); 284 285 for (;;) { 286 if ((len = getline(&line, &linesz, ifp)) == -1) { 287 if (feof(ifp)) 288 break; 289 fatal("getline"); 290 } 291 292 if (len >= 2 && line[len - 2] == '\r' && line[len - 1] == '\n') 293 line[--len - 1] = '\n'; 294 295 if (fwrite(line, 1, len, mail.fp) != len) 296 fatal("fwrite"); 297 298 if (line[len - 1] != '\n' && fputc('\n', mail.fp) == EOF) 299 fatal("fputc"); 300 } 301 302 fclose(ifp); 303 rewind(mail.fp); 304 } 305 306 void 307 resume(void) 308 { 309 static int started = 0; 310 char host[256]; 311 char serv[16]; 312 313 if (done) { 314 event_loopexit(NULL); 315 return; 316 } 317 318 if (ai == NULL) 319 fatalx("no more host"); 320 321 getnameinfo(ai->ai_addr, ai->ai_addr->sa_len, 322 host, sizeof(host), serv, sizeof(serv), 323 NI_NUMERICHOST | NI_NUMERICSERV); 324 log_debug("trying host %s port %s...", host, serv); 325 326 params.dst = ai->ai_addr; 327 if (smtp_connect(¶ms, NULL) == NULL) 328 fatal("smtp_connect"); 329 330 if (started == 0) { 331 started = 1; 332 event_loop(0); 333 } 334 } 335 336 void 337 log_trace(int lvl, const char *emsg, ...) 338 { 339 va_list ap; 340 341 if (verbose > lvl) { 342 va_start(ap, emsg); 343 vlog(LOG_DEBUG, emsg, ap); 344 va_end(ap); 345 } 346 } 347 348 void 349 smtp_verify_server_cert(void *tag, struct smtp_client *proto, void *ctx) 350 { 351 SSL *ssl = ctx; 352 X509 *cert; 353 long res; 354 int match; 355 356 if ((cert = SSL_get_peer_certificate(ssl))) { 357 (void)ssl_check_name(cert, servname, &match); 358 X509_free(cert); 359 res = SSL_get_verify_result(ssl); 360 if (res == X509_V_OK) { 361 if (match) { 362 log_debug("valid certificate"); 363 smtp_cert_verified(proto, CERT_OK); 364 } 365 else { 366 log_debug("certificate does not match hostname"); 367 smtp_cert_verified(proto, CERT_INVALID); 368 } 369 return; 370 } 371 log_debug("certificate validation error %ld", res); 372 } 373 else 374 log_debug("no certificate provided"); 375 376 smtp_cert_verified(proto, CERT_INVALID); 377 } 378 379 void 380 smtp_require_tls(void *tag, struct smtp_client *proto) 381 { 382 SSL *ssl = NULL; 383 384 if ((ssl = SSL_new(ssl_ctx)) == NULL) 385 fatal("SSL_new"); 386 smtp_set_tls(proto, ssl); 387 } 388 389 void 390 smtp_ready(void *tag, struct smtp_client *proto) 391 { 392 log_debug("connection ready..."); 393 394 if (done || noaction) 395 smtp_quit(proto); 396 else 397 smtp_sendmail(proto, &mail); 398 } 399 400 void 401 smtp_failed(void *tag, struct smtp_client *proto, int failure, const char *detail) 402 { 403 switch (failure) { 404 case FAIL_INTERNAL: 405 log_warnx("internal error: %s", detail); 406 break; 407 case FAIL_CONN: 408 log_warnx("connection error: %s", detail); 409 break; 410 case FAIL_PROTO: 411 log_warnx("protocol error: %s", detail); 412 break; 413 case FAIL_IMPL: 414 log_warnx("missing feature: %s", detail); 415 break; 416 case FAIL_RESP: 417 log_warnx("rejected by server: %s", detail); 418 break; 419 default: 420 fatalx("unknown failure %d: %s", failure, detail); 421 } 422 } 423 424 void 425 smtp_status(void *tag, struct smtp_client *proto, struct smtp_status *status) 426 { 427 log_info("%s: %s: %s", status->rcpt->to, status->cmd, status->status); 428 } 429 430 void 431 smtp_done(void *tag, struct smtp_client *proto, struct smtp_mail *mail) 432 { 433 int i; 434 435 log_debug("mail done..."); 436 437 if (noaction) 438 return; 439 440 for (i = 0; i < mail->rcptcount; i++) 441 if (!mail->rcpt[i].done) 442 return; 443 444 done = 1; 445 } 446 447 void 448 smtp_closed(void *tag, struct smtp_client *proto) 449 { 450 log_debug("connection closed..."); 451 452 ai = ai->ai_next; 453 if (noaction && ai == NULL) 454 done = 1; 455 456 resume(); 457 } 458