1/* smtpauth.c -- authenticate to SMTP server, then give normal protocol 2 * 3 * uses sfio 4 * 5 */ 6 7#include <config.h> 8 9#include <sfio.h> 10#include <sfio/stdio.h> 11#include <ctype.h> 12#include <pwd.h> 13#include <sys/types.h> 14#include <sys/socket.h> 15#include <sys/file.h> 16#include <netinet/in.h> 17#include <netdb.h> 18#include <unistd.h> 19#include <string.h> 20#include <assert.h> 21#include <errno.h> 22#include <fcntl.h> 23#include <stdlib.h> 24 25#include <sys/socket.h> 26#include <sys/file.h> 27#include <netinet/in.h> 28#include <netdb.h> 29 30#ifdef HAVE_SYS_SELECT_H 31#include <sys/select.h> 32#endif 33 34#include <sasl.h> 35#include <saslutil.h> 36 37#include "sfsasl.h" 38 39/* from OS: */ 40extern char *getpass(); 41extern struct hostent *gethostbyname(); 42 43static char *authname = NULL; 44static char *username = NULL; 45static char *realm = NULL; 46 47extern char *optarg; 48extern int optind; 49 50int verbose = 0; 51int emacs = 0; 52 53int iptostring(const struct sockaddr *addr, socklen_t addrlen, 54 char *out, unsigned outlen) { 55 char hbuf[NI_MAXHOST], pbuf[NI_MAXSERV]; 56 int niflags; 57 58 if(!addr || !out) return SASL_BADPARAM; 59 60 niflags = (NI_NUMERICHOST | NI_NUMERICSERV); 61#ifdef NI_WITHSCOPEID 62 if (addr->sa_family == AF_INET6) 63 niflags |= NI_WITHSCOPEID; 64#endif 65 if (getnameinfo(addr, addrlen, hbuf, sizeof(hbuf), pbuf, sizeof(pbuf), 66 niflags) != 0) 67 return SASL_BADPARAM; 68 69 if(outlen < strlen(hbuf) + strlen(pbuf) + 2) 70 return SASL_BUFOVER; 71 72 snprintf(out, outlen, "%s;%s", hbuf, pbuf); 73 74 return SASL_OK; 75} 76 77void usage(char *p) 78{ 79 fprintf(stderr, "%s [-v] [-l] [-u username] [-a authname] [-s ssf] [-m mech] host[:port]\n", p); 80 fprintf(stderr, " -v\tVerbose Output\n"); 81 fprintf(stderr, " -l\tLMTP semantics\n"); 82 exit(EX_USAGE); 83} 84 85#define ISGOOD(r) (((r) / 100) == 2) 86#define TEMPFAIL(r) (((r) / 100) == 4) 87#define PERMFAIL(r) (((r) / 100) == 5) 88#define ISCONT(s) (s && (s[3] == '-')) 89 90static int ask_code(const char *s) 91{ 92 int ret = 0; 93 94 if (s==NULL) return -1; 95 96 if (strlen(s) < 3) return -1; 97 98 /* check to make sure 0-2 are digits */ 99 if ((isdigit((int) s[0])==0) || 100 (isdigit((int) s[1])==0) || 101 (isdigit((int) s[2])==0)) 102 { 103 return -1; 104 } 105 106 ret = ((s[0]-'0')*100)+((s[1]-'0')*10)+(s[2]-'0'); 107 108 return ret; 109} 110 111static void chop(char *s) 112{ 113 char *p; 114 115 assert(s); 116 p = s + strlen(s) - 1; 117 if (p[0] == '\n') { 118 *p-- = '\0'; 119 } 120 if (p >= s && p[0] == '\r') { 121 *p-- = '\0'; 122 } 123} 124 125void interaction (int id, const char *prompt, 126 char **tresult, unsigned int *tlen) 127{ 128 char result[1024]; 129 130 if (id==SASL_CB_PASS) { 131 fprintf(stderr, "%s: ", prompt); 132 *tresult = strdup(getpass("")); /* leaks! */ 133 *tlen= strlen(*tresult); 134 return; 135 } else if (id==SASL_CB_USER) { 136 if (username != NULL) { 137 strcpy(result, username); 138 } else { 139 strcpy(result, getpwuid(getuid())->pw_name); 140 } 141 } else if (id==SASL_CB_AUTHNAME) { 142 if (authname != NULL) { 143 strcpy(result, authname); 144 } else { 145 strcpy(result, getpwuid(getuid())->pw_name); 146 } 147 } else if ((id==SASL_CB_GETREALM) && (realm != NULL)) { 148 strcpy(result, realm); 149 } else { 150 int c; 151 152 fprintf(stderr, "%s: ",prompt); 153 fgets(result, sizeof(result) - 1, stdin); 154 c = strlen(result); 155 result[c - 1] = '\0'; 156 } 157 158 *tlen = strlen(result); 159 *tresult = (char *) malloc(*tlen+1); /* leaks! */ 160 memset(*tresult, 0, *tlen+1); 161 memcpy((char *) *tresult, result, *tlen); 162} 163 164void fillin_interactions(sasl_interact_t *tlist) 165{ 166 while (tlist->id != SASL_CB_LIST_END) 167 { 168 interaction(tlist->id, tlist->prompt, 169 (void *) &(tlist->result), 170 &(tlist->len)); 171 tlist++; 172 } 173} 174 175static sasl_callback_t callbacks[] = { 176 { SASL_CB_GETREALM, NULL, NULL }, 177 { SASL_CB_USER, NULL, NULL }, 178 { SASL_CB_AUTHNAME, NULL, NULL }, 179 { SASL_CB_PASS, NULL, NULL }, 180 { SASL_CB_LIST_END, NULL, NULL } 181}; 182 183static sasl_security_properties_t *make_secprops(int min,int max) 184{ 185 sasl_security_properties_t *ret=(sasl_security_properties_t *) 186 malloc(sizeof(sasl_security_properties_t)); 187 188 ret->maxbufsize = 8192; 189 ret->min_ssf = min; 190 ret->max_ssf = max; 191 192 ret->security_flags = 0; 193 ret->property_names = NULL; 194 ret->property_values = NULL; 195 196 return ret; 197} 198 199Sfio_t *debug; 200 201int main(int argc, char **argv) 202{ 203 char *mechlist = NULL; 204 const char *mechusing = NULL; 205 int minssf = 0, maxssf = 128; 206 char *p; 207 Sfio_t *server_in, *server_out; 208 sasl_conn_t *conn = NULL; 209 sasl_interact_t *client_interact = NULL; 210 char in[4096]; 211 const char *out; 212 unsigned int inlen, outlen; 213 unsigned len; 214 char out64[4096]; 215 int c; 216 217 char *host; 218 struct servent *service; 219 int port; 220 struct hostent *hp; 221 struct sockaddr_in addr; 222 char remote_ip[64], local_ip[64]; 223 int sock; 224 225 char buf[1024]; 226 int sz; 227 char greeting[1024]; 228 int code; 229 int do_lmtp=0; 230 int r = 0; 231 232 debug = stderr; 233 234 while ((c = getopt(argc, argv, "vElm:s:u:a:d:")) != EOF) { 235 switch (c) { 236 case 'm': 237 mechlist = optarg; 238 break; 239 240 case 'l': 241 do_lmtp = 1; 242 break; 243 244 case 's': 245 maxssf = atoi(optarg); 246 break; 247 248 case 'u': 249 username = optarg; 250 break; 251 252 case 'a': 253 authname = optarg; 254 break; 255 256 case 'v': 257 verbose++; 258 break; 259 260 case 'E': 261 emacs++; 262 break; 263 264 case 'd': 265 sprintf(buf, "%s-%d", optarg, getpid()); 266 debug = sfopen(NULL, buf, "w"); 267 sfsetbuf(debug, NULL, 0); 268 break; 269 270 case '?': 271 default: 272 usage(argv[0]); 273 break; 274 } 275 } 276 277 if (optind != argc - 1) { 278 usage(argv[0]); 279 } 280 281 host = argv[optind]; 282 p = strchr(host, ':'); 283 if (p) { 284 *p++ = '\0'; 285 } else { 286 if(do_lmtp) { 287 p = "lmtp"; 288 } else { 289 p = "smtp"; 290 } 291 } 292 service = getservbyname(p, "tcp"); 293 if (service) { 294 port = service->s_port; 295 } else { 296 port = atoi(p); 297 if (!port) usage(argv[0]); 298 port = htons(port); 299 } 300 301 if ((hp = gethostbyname(host)) == NULL) { 302 perror("gethostbyname"); 303 exit(EX_NOHOST); 304 } 305 306 if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { 307 perror("socket"); 308 exit(EX_OSERR); 309 } 310 311 addr.sin_family = AF_INET; 312 memcpy(&addr.sin_addr, hp->h_addr, hp->h_length); 313 addr.sin_port = port; 314 315 if (connect(sock, (struct sockaddr *) &addr, sizeof (addr)) < 0) { 316 perror("connect"); 317 exit(EX_NOHOST); 318 } 319 320 server_in = sfnew(NULL, NULL, SF_UNBOUND, sock, SF_READ); 321 server_out = sfnew(NULL, NULL, SF_UNBOUND, sock, SF_WRITE); 322 323 /* read greeting */ 324 greeting[0] = '\0'; 325 for (;;) { 326 sfsync(server_out); 327 if (fgets(buf, sizeof(buf)-1, server_in)) { 328 if (greeting[0] == '\0') { 329 strncpy(greeting, buf, sizeof(greeting) - 1); 330 } 331 332 if (verbose) fprintf(debug, "%s", buf); 333 code = ask_code(buf); 334 if (ISCONT(buf) && ISGOOD(code)) continue; 335 } else { 336 code = 400; 337 } 338 break; 339 } 340 341 if (!ISGOOD(code)) goto done; 342 343 /* EHLO */ 344 gethostname(buf, sizeof(buf)-1); 345 if(do_lmtp) { 346 if(verbose) fprintf(debug, "LHLO %s\r\n", buf); 347 fprintf(server_out, "LHLO %s\r\n", buf); 348 } else { 349 if (verbose) fprintf(debug, "EHLO %s\r\n", buf); 350 fprintf(server_out, "EHLO %s\r\n", buf); 351 } 352 353 /* read responses */ 354 for (;;) { 355 sfsync(server_out); 356 if (!fgets(buf, sizeof(buf)-1, server_in)) { 357 code = 400; 358 goto done; 359 } 360 if (verbose) fprintf(debug, "%s", buf); 361 code = ask_code(buf); 362 if (code == 250) { 363 /* we're only looking for AUTH */ 364 if (!strncasecmp(buf + 4, "AUTH ", 5)) { 365 chop(buf); 366 if (!mechlist) mechlist = strdup(buf + 9); 367 } 368 } 369 if (ISCONT(buf) && ISGOOD(code)) { 370 continue; 371 } else { 372 break; 373 } 374 } 375 if (!ISGOOD(code)) goto done; 376 377 /* attempt authentication */ 378 if (!mechlist) { 379 if (verbose > 2) fprintf(debug, "no authentication\n"); 380 goto doneauth; 381 } 382 383 if (!r) r = sasl_client_init(callbacks); 384 if (!r) { 385 struct sockaddr_in saddr_r; 386 int addrsize = sizeof(struct sockaddr_in); 387 388 if (getpeername(sock, (struct sockaddr *) &saddr_r, &addrsize) < 0) { 389 perror("getpeername"); 390 exit(EX_NOHOST); 391 } 392 r = iptostring((struct sockaddr *)&saddr_r, 393 sizeof(struct sockaddr_in), remote_ip, 64); 394 } 395 if (!r) { 396 struct sockaddr_in saddr_l; 397 int addrsize = sizeof(struct sockaddr_in); 398 399 if (getsockname(sock, (struct sockaddr *) &saddr_l, &addrsize) < 0) { 400 perror("getsockname"); 401 exit(EX_OSERR); 402 } 403 r = iptostring((struct sockaddr *)&saddr_l, 404 sizeof(struct sockaddr_in), local_ip, 64); 405 } 406 407 if (!r) { 408 if(do_lmtp) { 409 r = sasl_client_new("lmtp", host, local_ip, remote_ip, 410 NULL, 0, &conn); 411 } else { 412 r = sasl_client_new("smtp", host, local_ip, remote_ip, 413 NULL, 0, &conn); 414 } 415 } 416 417 if (!r) { 418 sasl_security_properties_t *secprops = make_secprops(minssf, maxssf); 419 r = sasl_setprop(conn, SASL_SEC_PROPS, secprops); 420 free(secprops); 421 } 422 423 if (!r) { 424 do { 425 r = sasl_client_start(conn, mechlist, 426 &client_interact, &out, &outlen, &mechusing); 427 if (r == SASL_INTERACT) { 428 fillin_interactions(client_interact); 429 } 430 } while (r == SASL_INTERACT); 431 432 if (r == SASL_OK || r == SASL_CONTINUE) { 433 if (outlen > 0) { 434 r = sasl_encode64(out, outlen, out64, sizeof out64, NULL); 435 if (!r) { 436 if (verbose) 437 fprintf(debug, "AUTH %s %s\r\n", mechusing, out64); 438 fprintf(server_out, "AUTH %s %s\r\n", mechusing, out64); 439 } 440 } else { 441 if (verbose) fprintf(debug, "AUTH %s\r\n", mechusing); 442 fprintf(server_out, "AUTH %s\r\n", mechusing); 443 } 444 } else { 445 fprintf(debug, "\nclient start failed: %s\n", sasl_errdetail(conn)); 446 } 447 448 } 449 450 /* jump to doneauth if we succeed */ 451 while (r == SASL_OK || r == SASL_CONTINUE) { 452 sfsync(server_out); 453 if (!fgets(buf, sizeof(buf)-1, server_in)) { 454 code = 400; 455 goto done; 456 } 457 if (verbose) fprintf(debug, "%s", buf); 458 code = ask_code(buf); 459 if (ISCONT(buf)) continue; 460 if (ISGOOD(code)) { 461 if (code != 235) { 462 /* weird! */ 463 } 464 /* yay, we won! */ 465 sfdcsasl(server_in, conn); 466 sfdcsasl(server_out, conn); 467 goto doneauth; 468 } else if (code != 334) { 469 /* unexpected response */ 470 break; 471 } 472 len = strlen(buf); 473 if (len > 0 && buf[len-1] == '\n') { 474 buf[len-1] = '\0'; 475 } 476 r = sasl_decode64(buf + 4, strlen(buf) - 6, in, 4096, &inlen); 477 if (r != SASL_OK) break; 478 479 do { 480 r = sasl_client_step(conn, in, inlen, &client_interact, 481 &out, &outlen); 482 if (r == SASL_INTERACT) { 483 fillin_interactions(client_interact); 484 } 485 } while (r == SASL_INTERACT); 486 487 if (r == SASL_OK || r == SASL_CONTINUE) { 488 r = sasl_encode64(out, outlen, out64, sizeof out64, NULL); 489 } 490 if (r == SASL_OK) { 491 if (verbose) fprintf(debug, "%s\r\n", out64); 492 fprintf(server_out, "%s\r\n", out64); 493 } 494 } 495 496 /* auth failed! */ 497 if (!r) { 498 fprintf(debug, "%d authentication failed\n", code); 499 } else { 500 fprintf(debug, "400 authentication failed: %s\n", 501 sasl_errstring(r, NULL, NULL)); 502 } 503 exit(EX_SOFTWARE); 504 505 doneauth: 506 /* ready for application */ 507 greeting[3] = '-'; 508 printf("%s", greeting); 509 printf("220 %s %s\r\n", host, conn ? "authenticated" : "no auth"); 510 511 fcntl(0, F_SETFL, O_NONBLOCK); 512 fcntl(sock, F_SETFL, O_NONBLOCK); 513 sfset(stdin, SF_SHARE, 0); 514 515 /* feed data back 'n forth */ 516 for (;;) { 517 Sfio_t *flist[3]; 518 519 top: 520 flist[0] = stdin; 521 flist[1] = server_in; 522 523 /* sfpoll */ 524 if (verbose > 5) fprintf(debug, "poll\n"); 525 r = sfpoll(flist, 2, -1); 526 if (verbose > 5) fprintf(debug, "poll 2\n"); 527 528 while (r--) { 529 if (flist[r] == server_in) { 530 do { 531 if (verbose > 5) fprintf(debug, "server!\n"); 532 errno = 0; 533 sz = sfread(server_in, buf, sizeof(buf)-1); 534 if (sz == 0 && (errno == EAGAIN)) goto top; 535 if (sz <= 0) goto out; 536 buf[sz] = '\0'; 537 if (verbose > 5) fprintf(debug, "server 2 '%s'!\n", buf); 538 sfwrite(stdout, buf, sz); 539 } while (sfpoll(&server_in, 1, 0)); 540 sfsync(stdout); 541 } else if (flist[r] == stdin) { 542 Sfio_t *p[1]; 543 544 p[0] = stdin; 545 do { 546 if (verbose > 5) fprintf(debug, "stdin!\n"); 547 errno = 0; 548 sz = sfread(stdin, buf, sizeof(buf)-1); 549 if (sz == 0 && (errno == EAGAIN)) goto top; 550 if (sz <= 0) goto out; 551 buf[sz] = '\0'; 552 if (verbose > 5) fprintf(debug, "stdin 2 '%s'!\n", buf); 553 if (emacs) { 554 int i; 555 556 /* fix emacs stupidness */ 557 for (i = 0; i < sz - 1; i++) { 558 if (buf[i] == '\n' && buf[i+1] == '\n') 559 buf[i++] = '\r'; 560 } 561 if (buf[sz-2] != '\r' && buf[sz-1] == '\n') { 562 sfungetc(stdin, buf[sz--]); 563 buf[sz] = '\0'; 564 } 565 566 if (verbose > 5) fprintf(debug, "emacs '%s'!\n", buf); 567 } 568 sfwrite(server_out, buf, sz); 569 if (verbose > 7) fprintf(debug, "stdin 3!\n"); 570 } while (sfpoll(p, 1, 0)); 571 sfsync(server_out); 572 } else { 573 abort(); 574 } 575 } 576 } 577 out: 578 if (verbose > 3) fprintf(debug, "exiting! %d %s\n", sz, strerror(errno)); 579 exit(EX_OK); 580 581 done: 582 if (ISGOOD(code)) { 583 if (verbose > 1) fprintf(debug, "ok\n"); 584 exit(EX_OK); 585 } 586 if (TEMPFAIL(code)) { 587 if (verbose > 1) fprintf(debug, "tempfail\n"); 588 exit(EX_TEMPFAIL); 589 } 590 if (PERMFAIL(code)) { 591 if (verbose > 1) fprintf(debug, "permfail\n"); 592 exit(EX_UNAVAILABLE); 593 } 594 595 if (verbose > 1) fprintf(debug, "unknown failure\n"); 596 exit(EX_TEMPFAIL); 597} 598