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