1#if HAVE_CONFIG_H 2#include <config.h> 3#endif 4#include <signal.h> 5#include <sys/wait.h> 6#include <sys/mman.h> 7#include <assert.h> 8#include "cunit/cunit.h" 9#include "imap/saslclient.h" 10#include <sasl/saslutil.h> 11#include <sasl/saslplug.h> 12#include "xmalloc.h" 13#include "imap/mutex.h" 14#include "prot.h" 15#include "imap/backend.h" 16 17struct server_config { 18 int sasl_plain; 19 int sasl_login; 20 int sasl_digestmd5; 21 int starttls; 22 int deflate; 23 int caps_one_per_line; 24}; 25 26/* 27 * This is a useful hack. All the test server's state is 28 * stored using this structure in an anonymous page which 29 * is shared between the client and server. This lets the 30 * test code in the client reach into the server state and 31 * either tweak the behaviour via the config or do asserts 32 * on the per-connection state (which makes integrating 33 * with CUnit a whole lot easier, as CU asserts in a child 34 * process are really not helpful). 35 */ 36struct server_state { 37 /* dynamic configuration */ 38 struct server_config config; 39 40 /* global state */ 41 int rend_sock; 42 43 /* per-connection state */ 44 int is_connected; 45 int is_authenticated; 46 int is_tls; 47 sasl_conn_t *saslconn; /* the sasl connection context */ 48#ifdef HAVE_SSL 49 SSL *tls_conn; 50#endif 51 struct protstream *in; 52 struct protstream *out; 53}; 54 55 56#define HOST "localhost" 57#define SASLSERVICE "vorpal" 58#define USERID "fbloggs" 59#define PASSWORD "shibboleth" 60extern int verbose; 61 62static sasl_callback_t *callbacks; 63static struct server_state *server_state; 64static const struct server_config default_server_config = { 65 .sasl_plain = 1, 66 .sasl_login = 0, 67 .sasl_digestmd5 = 0, 68 .starttls = 0, 69 .deflate = 0, 70 .caps_one_per_line = 1 71}; 72static const struct capa_t default_capa[] = { 73 { "CCSASL", CAPA_AUTH }, 74 { "CCSTARTTLS", CAPA_STARTTLS }, 75 { "CCCOMPRESS=DEFLATE", CAPA_COMPRESS }, 76 { NULL, 0 } 77}; 78static char default_service[32]; 79 80static int init_sasl(int isclient); 81 82static struct protocol_t test_prot = 83{ 84 /* .service is setup in default_conditions() */ 85 .sasl_service = SASLSERVICE, 86 .type = TYPE_STD, 87 .u.std = { 88 .banner = { 89 .auto_capa = 1, 90 .resp = "OK" 91 }, 92 .capa_cmd = { 93 .cmd = "XXCAPABILITY", 94 .arg = NULL, 95 .resp = "OK", 96 .postcapability = NULL, 97 .formatflags = CAPAF_ONE_PER_LINE|CAPAF_SKIP_FIRST_WORD, 98 /* .capa is setup in default_conditions() */ 99 }, 100 .tls_cmd = { 101 .cmd = "XXSTARTTLS", 102 .ok = "OK", 103 .fail = "NO", 104 .auto_capa = 0 105 }, 106 .sasl_cmd = { 107 .cmd = "XXAUTHENTICATE", 108 .maxlen = USHRT_MAX, 109 .quote = 0, 110 .ok = "OK", 111 .fail = "NO", 112 .cont = "+ ", 113 .cancel = "*", 114 .parse_success = NULL, 115 .auto_capa = 0 116 }, 117 .compress_cmd = { 118 .cmd = NULL, 119 .unsol = NULL, 120 .ok = NULL 121 }, 122 .ping_cmd = { 123 .cmd = "XXNOOP", 124 .unsol = NULL, 125 .ok = "OK" 126 }, 127 .logout_cmd = { 128 .cmd = "XXLOGOUT", 129 .unsol = NULL, 130 .ok = "OK" 131 } 132 } 133}; 134 135 136/* 137 * Setup default test conditions, on both the client and server. 138 */ 139static void default_conditions(void) 140{ 141 server_state->config = default_server_config; 142 143 test_prot.service = default_service; 144 memcpy(&test_prot.u.std.capa_cmd.capa, default_capa, sizeof(default_capa)); 145 test_prot.u.std.capa_cmd.formatflags = CAPAF_ONE_PER_LINE|CAPAF_SKIP_FIRST_WORD; 146} 147 148/* ====================================================================== */ 149 150/* 151 * Test connecting to a host which doesn't exist. 152 */ 153static void test_badhost(void) 154{ 155 struct backend *be; 156 const char *auth_status = NULL; 157 158 default_conditions(); 159 160 be = backend_connect(NULL, "nonexistanthost", &test_prot, 161 USERID, callbacks, &auth_status, /*fd*/-1); 162 CU_ASSERT_PTR_NULL(be); 163 CU_ASSERT_EQUAL(server_state->is_connected, 0); 164} 165 166/* 167 * Test connecting to the wrong port on the right host. 168 */ 169static void test_badservice(void) 170{ 171 struct backend *be; 172 const char *auth_status = NULL; 173 174 default_conditions(); 175 test_prot.service = "nonexistantservice"; 176 177 be = backend_connect(NULL, HOST, &test_prot, 178 USERID, callbacks, &auth_status, /*fd*/-1); 179 CU_ASSERT_PTR_NULL(be); 180 CU_ASSERT_EQUAL(server_state->is_connected, 0); 181} 182 183/* 184 * Test authenticating with the PLAIN mechanism. 185 */ 186static void test_sasl_plain(void) 187{ 188 struct backend *be; 189 const char *auth_status = NULL; 190 char *mechs; 191 int r; 192 193 default_conditions(); 194 server_state->config.sasl_plain = 1; 195 196 be = backend_connect(NULL, HOST, &test_prot, 197 USERID, callbacks, &auth_status, /*fd*/-1); 198 CU_ASSERT_PTR_NOT_NULL_FATAL(be); 199 CU_ASSERT_EQUAL(server_state->is_connected, 1); 200 CU_ASSERT_EQUAL(server_state->is_authenticated, 1); 201 CU_ASSERT_EQUAL(server_state->is_tls, 0); 202 203 mechs = backend_get_cap_params(be, CAPA_AUTH); 204 CU_ASSERT_STRING_EQUAL(mechs, "PLAIN"); 205 free(mechs); 206 207 r = backend_ping(be, NULL); 208 CU_ASSERT_EQUAL(r, 0); 209 210 backend_disconnect(be); 211 free(be); 212} 213 214#if 0 215/* 216 * Test authenticating with the LOGIN mechanism. 217 * This test doesn't work for me. I have NFI why - gnb. 218 */ 219static void not_test_sasl_login(void) 220{ 221 struct backend *be; 222 const char *auth_status = NULL; 223 char *mechs; 224 int r; 225 226 default_conditions(); 227 server_state->config.sasl_plain = 0; 228 server_state->config.sasl_login = 1; 229 230 be = backend_connect(NULL, HOST, &test_prot, 231 USERID, callbacks, &auth_status, /*fd*/-1); 232 CU_ASSERT_PTR_NOT_NULL_FATAL(be); 233 CU_ASSERT_EQUAL(server_state->is_connected, 1); 234 CU_ASSERT_EQUAL(server_state->is_authenticated, 1); 235 CU_ASSERT_EQUAL(server_state->is_tls, 0); 236 237 mechs = backend_get_cap_params(be, CAPA_AUTH); 238 CU_ASSERT_STRING_EQUAL(mechs, "LOGIN"); 239 free(mechs); 240 241 r = backend_ping(be); 242 CU_ASSERT_EQUAL(r, 0); 243 244 backend_disconnect(be); 245 free(be); 246} 247#endif 248 249/* 250 * Test authenticating with the DIGEST-MD5 mechanism. 251 */ 252static void test_sasl_digestmd5(void) 253{ 254 struct backend *be; 255 const char *auth_status = NULL; 256 char *mechs; 257 int r; 258 259 default_conditions(); 260 server_state->config.sasl_plain = 0; 261 server_state->config.sasl_digestmd5 = 1; 262 263 be = backend_connect(NULL, HOST, &test_prot, 264 USERID, callbacks, &auth_status, /*fd*/-1); 265 CU_ASSERT_PTR_NOT_NULL_FATAL(be); 266 CU_ASSERT_EQUAL(server_state->is_connected, 1); 267 CU_ASSERT_EQUAL(server_state->is_authenticated, 1); 268 CU_ASSERT_EQUAL(server_state->is_tls, 0); 269 270 mechs = backend_get_cap_params(be, CAPA_AUTH); 271 CU_ASSERT_STRING_EQUAL(mechs, "DIGEST-MD5"); 272 free(mechs); 273 274 r = backend_ping(be, NULL); 275 CU_ASSERT_EQUAL(r, 0); 276 277 backend_disconnect(be); 278 free(be); 279} 280 281/* Common routine to test the semantics of capabilities */ 282static void caps_common(void) 283{ 284#define CAPA_FOO (1<<(CAPA_COMPRESS+1)) 285#define CAPA_BAZ (1<<(CAPA_COMPRESS+2)) 286#define CAPA_QUUX (1<<(CAPA_COMPRESS+3)) 287#define CAPA_FNORD (1<<(CAPA_COMPRESS+4)) 288#define CAPA_CAPITALS (1<<(CAPA_COMPRESS+5)) 289#define CAPA_MIA (1<<(CAPA_COMPRESS+6)) 290 struct backend *be; 291 const char *auth_status = NULL; 292 char *params; 293 int r; 294 int n; 295 static const struct capa_t extra_capa[] = { 296 { "FOO", CAPA_FOO }, 297 { "BAZ", CAPA_BAZ }, 298 { "QUUX", CAPA_QUUX }, 299 { "FNORD=BOO", CAPA_FNORD }, 300 { "CAPITALS", CAPA_CAPITALS }, 301 { "MIA", CAPA_MIA }, 302 { NULL, 0 } 303 /* No more room in the fixed size array, dammit */ 304 }; 305 306 /* append some extra capabilities */ 307 n = sizeof(default_capa)/sizeof(default_capa[0]) - 1; 308 CU_ASSERT_FATAL(n * sizeof(struct capa_t) + 309 sizeof(extra_capa) <= sizeof(test_prot.u.std.capa_cmd.capa)); 310 memcpy(&test_prot.u.std.capa_cmd.capa[n], extra_capa, sizeof(extra_capa)); 311 312 be = backend_connect(NULL, HOST, &test_prot, 313 USERID, callbacks, &auth_status, /*fd*/-1); 314 CU_ASSERT_PTR_NOT_NULL_FATAL(be); 315 CU_ASSERT_EQUAL(server_state->is_connected, 1); 316 CU_ASSERT_EQUAL(server_state->is_authenticated, 1); 317 CU_ASSERT_EQUAL(server_state->is_tls, 0); 318 319 /* FOO is present, its parameter is BAR */ 320 CU_ASSERT_EQUAL(!!CAPA(be, CAPA_FOO), 1); 321 params = backend_get_cap_params(be, CAPA_FOO); 322 CU_ASSERT_STRING_EQUAL(params, "BAR"); 323 free(params); 324 325 /* BAZ is present, it has no parameters */ 326 CU_ASSERT_EQUAL(!!CAPA(be, CAPA_BAZ), 1); 327 params = backend_get_cap_params(be, CAPA_BAZ); 328 CU_ASSERT_PTR_NULL(params); 329 330 /* QUUX is present, its parameters are FOONLY and FMEH */ 331 CU_ASSERT_EQUAL(!!CAPA(be, CAPA_QUUX), 1); 332 params = backend_get_cap_params(be, CAPA_QUUX); 333 CU_ASSERT_STRING_EQUAL(params, "FOONLY FMEH"); 334 free(params); 335 336 /* FNORD is present, its parameters are BOO and GNERGH, 337 * but we asked specifically for FNORD=BOO so CAPA() 338 * should succeed but we should see no params. */ 339 CU_ASSERT_EQUAL(!!CAPA(be, CAPA_FNORD), 1); 340 params = backend_get_cap_params(be, CAPA_FNORD); 341 CU_ASSERT_PTR_NULL(params); 342 343 /* CAPITALS is present, its parameter is Oslo. Note the 344 * name is matched case-insensitive but the parameters 345 * are returned as seen on the wire. */ 346 CU_ASSERT_EQUAL(!!CAPA(be, CAPA_CAPITALS), 1); 347 params = backend_get_cap_params(be, CAPA_CAPITALS); 348 CU_ASSERT_STRING_EQUAL(params, "Oslo"); 349 free(params); 350 351 /* MIA is missing in action */ 352 CU_ASSERT_EQUAL(!!CAPA(be, CAPA_MIA), 0); 353 params = backend_get_cap_params(be, CAPA_MIA); 354 CU_ASSERT_PTR_NULL(params); 355 356 r = backend_ping(be, NULL); 357 CU_ASSERT_EQUAL(r, 0); 358 359 backend_disconnect(be); 360 free(be); 361#undef CAPA_FOO 362#undef CAPA_BAZ 363#undef CAPA_QUUX 364#undef CAPA_FNORD 365#undef CAPA_CAPITALS 366#undef CAPA_MIA 367} 368 369/* 370 * Test parsing capabilities in multi-line format 371 */ 372static void test_multiline_caps(void) 373{ 374 default_conditions(); 375 server_state->config.caps_one_per_line = 1; 376 test_prot.u.std.capa_cmd.formatflags = CAPAF_ONE_PER_LINE|CAPAF_SKIP_FIRST_WORD; 377 caps_common(); 378} 379 380/* 381 * Test parsing capabilities in one-line format 382 */ 383static void test_oneline_caps(void) 384{ 385 default_conditions(); 386 server_state->config.caps_one_per_line = 0; 387 test_prot.u.std.capa_cmd.formatflags = CAPAF_MANY_PER_LINE; 388 caps_common(); 389} 390 391#ifdef HAVE_SSL 392/* 393 * Test STARTTLS 394 */ 395static void test_starttls(void) 396{ 397 struct backend *be; 398 const char *auth_status = NULL; 399 char *mechs; 400 int r; 401 402 default_conditions(); 403 server_state->config.sasl_plain = 1; 404 server_state->config.starttls = 1; 405 406 be = backend_connect(NULL, HOST, &test_prot, 407 USERID, callbacks, &auth_status, /*fd*/-1); 408 CU_ASSERT_PTR_NOT_NULL_FATAL(be); 409 CU_ASSERT_EQUAL(server_state->is_connected, 1); 410 CU_ASSERT_EQUAL(server_state->is_authenticated, 1); 411 CU_ASSERT_EQUAL(server_state->is_tls, 1); 412 413 mechs = backend_get_cap_params(be, CAPA_AUTH); 414 CU_ASSERT_STRING_EQUAL(mechs, "PLAIN"); 415 free(mechs); 416 417 CU_ASSERT(CAPA(be, CAPA_STARTTLS)) 418 419 r = backend_ping(be, NULL); 420 CU_ASSERT_EQUAL(r, 0); 421 422 backend_disconnect(be); 423 free(be); 424} 425#else 426/* 427 * cunit.pl doesn't process C macros, so it expects this to exist 428 * regardless of the state of HAVE_SSL 429 */ 430static void test_starttls(void) { } 431#endif 432 433/* TODO: test UNIX socket comms too */ 434/* TODO: test IPv6 socket comms too */ 435/* TODO: test connect() timeout */ 436 437/* ====================================================================== */ 438 439/* 440 * Allocate, and return a mapped pointer to, an anonymous page in 441 * memory (a non-heap non-text page with no backing file) which will be 442 * shared across fork(). The page is assumed to be zeroed. 443 */ 444static void *get_anonymous_page(void) 445{ 446 void *page; 447 size_t pagesize = getpagesize(); 448 449#if defined(MAP_ANON) && !defined(MAP_ANONYMOUS) 450 /* FreeBSD */ 451#define MAP_ANONYMOUS MAP_ANON 452#endif 453 454#ifdef MAP_ANONYMOUS 455 /* Linux and Solaris */ 456 page = mmap(NULL, pagesize, PROT_READ|PROT_WRITE, 457 MAP_ANONYMOUS|MAP_SHARED, -1, 0); 458 if (page == MAP_FAILED) { 459 perror("mmap"); 460 return NULL; 461 } 462#else 463 int fd; 464 465 fd = open("/dev/zero", O_RDWR); 466 if (fd < 0) { 467 perror("/dev/zero"); 468 return NULL; 469 } 470 page = mmap(NULL, pagesize, PROT_READ|PROT_WRITE, 471 MAP_SHARED, fd, 0); 472 if (page == MAP_FAILED) { 473 perror("mmap"); 474 page = NULL; 475 } 476 close(fd); 477#endif 478 479 return page; 480} 481 482/* 483 * Free an anonymous page returned from get_anonymous_page() 484 */ 485static void free_anonymous_page(void *page) 486{ 487 munmap(page, getpagesize()); 488} 489 490/* 491 * Create a bound and listening TCP4 server socket, bound to the IPv4 492 * loopback address and a port chosen by the kernel. 493 * 494 * Returns: socket, or -1 on error. *@portp is filled with the port 495 * number. 496 */ 497static int create_server_socket(int *portp) 498{ 499 int sock; 500 struct sockaddr_in sin; 501 int r; 502 503 sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); 504 if (sock < 0) { 505 perror("socket(TCP)"); 506 return -1; 507 } 508 509 memset(&sin, 0, sizeof(sin)); 510 sin.sin_family = AF_INET; 511 sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 512 if (*portp) 513 sin.sin_port = htons(*portp); 514 515 r = bind(sock, (struct sockaddr *)&sin, sizeof(sin)); 516 if (r < 0) { 517 perror("bind"); 518 goto error; 519 } 520 521 r = listen(sock, 5); 522 if (r < 0) { 523 perror("listen"); 524 goto error; 525 } 526 527 if (!*portp) { 528 socklen_t len = sizeof(sin); 529 r = getsockname(sock, (struct sockaddr *)&sin, &len); 530 if (r < 0) { 531 perror("getsockname"); 532 goto error; 533 } 534 if (len != sizeof(sin) || sin.sin_family != AF_INET) { 535 fprintf(stderr, "Bad address from getsockname()\n"); 536 goto error; 537 } 538 *portp = ntohs(sin.sin_port); 539 } 540 541 return sock; 542 543error: 544 close(sock); 545 return -1; 546} 547 548/* 549 * Block until an incoming connection is received, then establish the 550 * connection and initialise per-connection state. On success, set 551 * @state->is_connected. 552 * 553 * Returns 0 on success, -1 on failure (errors indicate some resource 554 * limitation in the server, not any behaviour of the client, and 555 * should be fatal). 556 */ 557static int server_accept(struct server_state *state) 558{ 559 int conn_sock; 560 int r; 561 562 if (verbose > 1) 563 fprintf(stderr, "Server waiting for connection\n"); 564 565 conn_sock = accept(state->rend_sock, NULL, NULL); 566 if (conn_sock < 0) { 567 perror("accept"); 568 return -1; 569 } 570 if (verbose > 1) 571 fprintf(stderr, "Server accepted connection\n"); 572 573 state->in = prot_new(conn_sock, /*read*/0); 574 state->out = prot_new(dup(conn_sock), /*write*/1); 575 if (!state->in || !state->out) { 576 perror("prot_new"); 577 return -1; 578 } 579 580 r = sasl_server_new(SASLSERVICE, HOST, 581 /*user_realm*/NULL, /*iplocalport*/NULL, 582 /*ipremoteport*/NULL, /*callbacks*/NULL, 583 /*flags*/0, &state->saslconn); 584 if (r != SASL_OK) { 585 fprintf(stderr, "sasl_server_new() failed with error %d\n", r); 586 return -1; 587 } 588 589 state->is_authenticated = 0; 590 state->is_connected = 1; 591 state->is_tls = 0; 592#ifdef HAVE_SSL 593 state->tls_conn = NULL; 594#endif 595 596 return 0; 597} 598 599/* 600 * Handle the end of a connection, by cleaning up all the per-connection 601 * state. In particular, clears @state->is_connected. 602 */ 603static void server_unaccept(struct server_state *state) 604{ 605 prot_free(state->in); 606 state->in = NULL; 607 prot_free(state->out); 608 state->out = NULL; 609 sasl_dispose(&state->saslconn); 610 state->is_connected = 0; 611 state->is_authenticated = 0; 612 state->is_tls = 0; 613#ifdef HAVE_SSL 614 if (state->tls_conn) { 615 tls_reset_servertls(&state->tls_conn); 616 state->tls_conn = NULL; 617 } 618#endif 619} 620 621/* 622 * Main routine for pushing text back to the client. 623 */ 624static void server_printf(struct server_state *, const char *fmt, ...) 625 __attribute__((format(printf,2,3))); 626 627static void server_printf(struct server_state *state, const char *fmt, ...) 628{ 629 va_list args; 630 int r; 631 char buf[2048]; 632 633 if (verbose > 1) { 634 va_start(args, fmt); 635 fprintf(stderr, "S: "); 636 vfprintf(stderr, fmt, args); 637 va_end(args); 638 } 639 640 va_start(args, fmt); 641 r = vsnprintf(buf, sizeof(buf), fmt, args); 642 assert(r < (int)sizeof(buf)); 643 va_end(args); 644 645 r = prot_write(state->out, buf, strlen(buf)); 646 assert(r >= 0); 647} 648 649/* 650 * Flush any pending output back to the client. 651 */ 652static void server_flush(struct server_state *state) 653{ 654 prot_flush(state->out); 655} 656 657/* 658 * Get a line of text from the client. Blocks until 659 * a while line is available. 660 * 661 * Returns: 0 on success, -1 on error or end of file. 662 */ 663static int server_getline(struct server_state *state, 664 char *buf, int maxlen) 665{ 666 if (!prot_fgets(buf, maxlen, state->in)) 667 return -1; 668 if (verbose > 1) 669 fprintf(stderr, "C: %s\n", buf); 670 return 0; 671} 672 673/* 674 * Emit to the client a banner including the server's capability 675 * strings. This might be in response to the client connecting, or to 676 * the client issuing the XXCAPABILITY command. Various flags in 677 * @state->config control which of the magical caps known by the 678 * backend.c client code are reported. Some other test capabilities are 679 * always reported. Depending on @state->config.caps_one_per_line, the 680 * caps are listed in a multi-line format, one cap per line (LMTP, POP3, 681 * ManageSieve, and sync style) or a single-line, many caps per line 682 * format (IMAP style). 683 */ 684static void server_emit_caps(struct server_state *state) 685{ 686 const char *saslmechs = NULL; 687 char *p; 688 char *b; 689 int n = 0; 690 const char *name; 691 const char *words[256]; /* array of: 692 * NAME, VALUE, VALUE, NULL, 693 * NAME, VALUE, NULL, 694 * NULL */ 695 char line[1024]; 696 static const char banner[] = "Toy Test server v0.0.1"; 697 698 /* 699 * The ccSASL cap reports a list of SASL mechanism names. 700 * Note that we suppress them all if STARTTLS is enabled. 701 */ 702 if (!state->config.starttls || state->is_tls) { 703 int got_login = 0; 704 int got_plain = 0; 705 int got_digestmd5 = 0; 706 707 /* First see what mechanisms SASL has; no point reporting 708 * mechanisms which aren't actually available. */ 709 sasl_listmech(state->saslconn, /*user*/NULL, /*prefix*/"", 710 /*sep*/" ", /*suffix*/"", &saslmechs, /*plen*/NULL, 711 /*pcount*/NULL); 712 b = xstrdup(saslmechs); 713 /* Build our own list of mechanisms, which is the intersection 714 * of the ones configured in SASL and the ones our server config 715 * allows us. */ 716 words[n++] = "ccSASL"; 717 for (p = strtok(b, " ") ; p ; p = strtok(NULL, " ")) { 718 if (!strcasecmp(p, "LOGIN") && state->config.sasl_login) { 719 words[n++] = "LOGIN"; 720 got_login = 1; 721 } 722 if (!strcasecmp(p, "LOGIN") && state->config.sasl_plain) { 723 words[n++] = "PLAIN"; 724 got_plain = 1; 725 } 726 if (!strcasecmp(p, "DIGEST-MD5") && state->config.sasl_digestmd5) { 727 words[n++] = "DIGEST-MD5"; 728 got_digestmd5 = 1; 729 } 730 } 731 words[n++] = NULL; 732 free(b); 733 734 if (state->config.sasl_login && !got_login) 735 fprintf(stderr, "Server failed to find requested " 736 "SASL mechanism \"LOGIN\"\n"); 737 if (state->config.sasl_plain && !got_plain) 738 fprintf(stderr, "Server failed to find requested " 739 "SASL mechanism \"PLAIN\"\n"); 740 if (state->config.sasl_digestmd5 && !got_digestmd5) 741 fprintf(stderr, "Server failed to find requested " 742 "SASL mechanism \"DIGEST-MD5\"\n"); 743 } 744 745 /* 746 * The ccSTARTTLS cap reports the ability to do STARTTLS 747 */ 748 if (state->config.starttls) { 749 words[n++] = "ccSTARTTLS"; 750 words[n++] = NULL; 751 } 752 753 /* 754 * The ccCOMPRESS=DEFLATE cap reports the ability to do Deflate 755 * compression. 756 */ 757 if (state->config.deflate) { 758 words[n++] = "ccCOMPRESS"; 759 words[n++] = "DEFLATE"; 760 words[n++] = NULL; 761 } 762 763 /* 764 * Various test capabilities; the test code in the client knows what 765 * to expect for these. 766 */ 767 words[n++] = "FOO"; 768 words[n++] = "BAR"; 769 words[n++] = NULL; 770 771 words[n++] = "BAZ"; 772 words[n++] = NULL; 773 774 words[n++] = "QUUX"; 775 words[n++] = "FOONLY"; 776 words[n++] = "FMEH"; 777 words[n++] = NULL; 778 779 words[n++] = "FNORD"; 780 words[n++] = "BOO"; 781 words[n++] = "GNERGH"; 782 words[n++] = NULL; 783 784 words[n++] = "cApItAls"; 785 words[n++] = "Oslo"; 786 words[n++] = NULL; 787 788 words[n++] = NULL; 789 790 /* 791 * Finally we have all the capability names and values, now 792 * pull them out of words[] and emit lines to the client. 793 */ 794 n = 0; 795 if (state->config.caps_one_per_line) { 796 /* Multi-line, one cap and all its values on each line (LMTP, 797 * ManageSieve, POP3 and sync style) */ 798 while ((name = words[n++])) { 799 line[0] = '\0'; 800 for ( ; words[n] ; n++) { 801 strcat(line, " "); 802 strcat(line, words[n]); 803 } 804 n++; 805 server_printf(state, "* %s%s\r\n", name, line); 806 } 807 server_printf(state, "OK %s\r\n", banner); 808 } else { 809 /* One-line, NAME=VALUE pairs inside IMAP-style response code */ 810 line[0] = '\0'; 811 while ((name = words[n++])) { 812 if (words[n]) { 813 for ( ; words[n] ; n++) { 814 strcat(line, " "); 815 strcat(line, name); 816 strcat(line, "="); 817 strcat(line, words[n]); 818 } 819 } else { 820 strcat(line, " "); 821 strcat(line, name); 822 } 823 n++; 824 } 825 server_printf(state, "OK [CAPABILITY%s] %s\r\n", line, banner); 826 } 827 server_flush(state); 828} 829 830/* 831 * Handle the XXAUTHENTICATE command from the client. The 1st and 2nd 832 * arguments to the command on wire are passed as @mech and 833 * @initial_in_b64. Handles any SASL conversation with the client (e.g. 834 * server challenges and client responses), and telling the client the 835 * final status. If successful, sets @state->is_authenticated. 836 */ 837static void cmd_authenticate(struct server_state *state, 838 const char *mech, const char *initial_in_b64) 839{ 840 char *word; 841 const char *server_out; 842 unsigned int server_out_len; 843 int r; 844 unsigned int buflen = 0; 845 char buf[21848]; 846 static const char sep[] = " \t\r\n"; 847 848 if (!mech) 849 goto badsyntax; 850 851 if (initial_in_b64) { 852 r = sasl_decode64(initial_in_b64, strlen(initial_in_b64), 853 buf, sizeof(buf), &buflen); 854 if (r != SASL_OK) 855 goto badsyntax; 856 } else { 857 buflen = 0; 858 } 859 860 server_out = NULL; 861 server_out_len = 0; 862 r = sasl_server_start(state->saslconn, mech, 863 buf, buflen, 864 &server_out, &server_out_len); 865 866 while (r == SASL_CONTINUE) { 867 if (server_out_len) 868 sasl_encode64(server_out, server_out_len, buf, sizeof(buf), NULL); 869 else 870 strcpy(buf, "="); 871 server_printf(state, "+ %s\n", buf); 872 server_flush(state); 873 874 if (server_getline(state, buf, sizeof(buf)) < 0) { 875 fprintf(stderr, "No response to AUTH challenge\n"); 876 return; 877 } 878 word = strtok(buf, sep); 879 if (word) { 880 r = sasl_decode64(word, strlen(word), buf, sizeof(buf), &buflen); 881 if (r != SASL_OK) 882 goto badsyntax; 883 } else { 884 buflen = 0; 885 } 886 887 server_out = NULL; 888 server_out_len = 0; 889 r = sasl_server_step(state->saslconn, buf, buflen, 890 &server_out, &server_out_len); 891 } 892 893 if (r != SASL_OK) { 894 server_printf(state, "BAD sasl error code %d\r\n", r); 895 server_flush(state); 896 return; 897 } 898 899 state->is_authenticated = 1; 900 server_printf(state, "OK\r\n"); 901 server_flush(state); 902 return; 903 904badsyntax: 905 server_printf(state, "BAD syntax\r\n"); 906 server_flush(state); 907} 908 909/* 910 * Handle the XXSTARTTLS command from the client. 911 * If successful, sets @state->is_tls. 912 */ 913static void cmd_starttls(struct server_state *state) 914{ 915#ifdef HAVE_SSL 916 int r; 917 char *auth_id = NULL; 918 SSL *tls_conn = NULL; 919 sasl_ssf_t ssf; 920 921 r = tls_init_serverengine("backend_test", /*verifydepth*/5, 922 /*askcert*/1, NULL); 923 if (r < 0) { 924 server_printf(state, "BAD error initializing TLS\r\n"); 925 server_flush(state); 926 return; 927 } 928 929 server_printf(state, "OK Begin TLS negotiation now\r\n"); 930 /* must flush our buffers before starting tls */ 931 server_flush(state); 932 933 r = tls_start_servertls(/*readfd*/state->in->fd, 934 /*writefd*/state->out->fd, 935 /*timeout_sec*/3000, 936 (int *)&ssf, &auth_id, &tls_conn); 937 if (r < 0) { 938 server_printf(state, "BAD STARTTLS negotiation failed\r\n"); 939 server_flush(state); 940 return; 941 } 942 943 /* tell SASL about the negotiated layer */ 944 r = sasl_setprop(state->saslconn, SASL_SSF_EXTERNAL, &ssf); 945 if (r != SASL_OK) { 946 server_printf(state, "BAD sasl_setprop(SASL_SSF_EXTERNAL) " 947 "failed: cmd_starttls()\r\n"); 948 server_flush(state); 949 return; 950 } 951 952 r = sasl_setprop(state->saslconn, SASL_AUTH_EXTERNAL, auth_id); 953 if (r != SASL_OK) { 954 server_printf(state, "BAD sasl_setprop(SASL_AUTH_EXTERNAL) " 955 "failed: cmd_starttls()\r\n"); 956 server_flush(state); 957 return; 958 } 959 960 /* tell the prot layer about our new layers */ 961 prot_settls(state->in, tls_conn); 962 prot_settls(state->out, tls_conn); 963 state->tls_conn = tls_conn; 964 state->is_tls = 1; 965#else 966 server_printf(state, "BAD this server is not built with SSL\r\n"); 967 server_flush(state); 968#endif 969} 970 971/* 972 * Server main command loop for a single connection. Blocks waiting for 973 * commands from the client, and handles each command. Returns when the 974 * client sends a XXLOGOUT command or drops the connection. 975 */ 976static void server_cmdloop(struct server_state *state) 977{ 978 char *command; 979 char buf[1024]; 980 static const char sep[] = " \t\r\n"; 981 982 /* Emit the connection banner */ 983 server_emit_caps(state); 984 985 while (server_getline(state, buf, sizeof(buf)) == 0) { 986 command = strtok(buf, sep); 987 988 if (!command) { 989 server_printf(state, "BAD command\r\n"); 990 server_flush(state); 991 continue; 992 } 993 994 if (!strcasecmp(command, "XXLOGOUT")) { 995 /* graceful disconnect */ 996 server_printf(state, "OK\r\n"); 997 server_flush(state); 998 break; 999 } else if (!strcasecmp(command, "XXNOOP")) { 1000 server_printf(state, "OK\r\n"); 1001 server_flush(state); 1002 } else if (!strcasecmp(command, "XXCAPABILITY")) { 1003 server_emit_caps(state); 1004 } else if (!strcasecmp(command, "XXAUTHENTICATE")) { 1005 char *mech = strtok(NULL, sep); 1006 char *initial_in = strtok(NULL, sep); 1007 cmd_authenticate(state, mech, initial_in); 1008 } else if (!strcasecmp(command, "XXSTARTTLS")) { 1009 cmd_starttls(state); 1010 } else { 1011 server_printf(state, "BAD command\r\n"); 1012 server_flush(state); 1013 } 1014 } 1015} 1016 1017/* 1018 * Start the server. Forks a child process, which does some once-off 1019 * server initialisation and then enters a service loop. The service 1020 * loop is very dumb: it serves a single connection at a time, with no 1021 * forking or threading or async request handling. 1022 * 1023 * Returns: pid of the child process, or -1 on error. 1024 */ 1025static pid_t server_start(struct server_state *state) 1026{ 1027 pid_t pid; 1028 1029 pid = fork(); 1030 if (pid < 0) { 1031 perror("fork"); 1032 return -1; 1033 } 1034 if (pid) { 1035 /* We are the parent process. We don't need to wait for the 1036 * child to finish starting, as we have a bound socket we can 1037 * connect immediately (it just might take the child a little 1038 * while to respond to the first command). */ 1039 close(state->rend_sock); 1040 return pid; 1041 } 1042 /* We are the child process. */ 1043 1044 if (init_sasl(/*client*/0) < 0) 1045 exit(1); 1046 1047 /* Main connection loop. This is a toy, single-threaded server 1048 * which handles one connection at a time. */ 1049 for (;;) 1050 { 1051 /* Wait for a connection */ 1052 if (server_accept(state) < 0) 1053 exit(1); 1054 1055 /* Run the main command loop for this connection */ 1056 server_cmdloop(state); 1057 1058 /* Forget the per-connection state */ 1059 server_unaccept(state); 1060 } 1061} 1062 1063/* 1064 * Shut down the server, given it's PID. It's pretty simple and brutal: 1065 * we just send it SIGTERM and wait to reap it. 1066 */ 1067static void server_shutdown(pid_t pid) 1068{ 1069 int status; 1070 pid_t r; 1071 1072 kill(pid, SIGTERM); 1073 for (;;) { 1074 r = waitpid(pid, &status, 0); 1075 if (r < 0) { 1076 if (errno == EINTR) 1077 continue; 1078 if (errno != ESRCH) 1079 perror("waitpid"); 1080 return; 1081 } 1082 if (r == pid) 1083 return; /* ok, killed it */ 1084 fprintf(stderr, "WTF? waitpid(%d) = %d?\n", pid, r); 1085 } 1086} 1087 1088/* 1089 * Callback which we use to fakes server-side password checking for 1090 * those mechanisms where a plaintext password from the client is 1091 * available to the SASL server code, i.e. PLAIN and LOGIN. 1092 */ 1093static int server_checkpass(sasl_conn_t *conn __attribute__((unused)), 1094 void *context __attribute__((unused)), 1095 const char *user, const char *pass, 1096 unsigned passlen, 1097 struct propctx *propctx __attribute__((unused))) 1098{ 1099 if (strcmp(user, USERID)) 1100 return SASL_NOUSER; 1101 if (passlen != strlen(PASSWORD) || 1102 strncmp(pass, PASSWORD, passlen)) 1103 return SASL_BADAUTH; 1104 return SASL_OK; 1105} 1106 1107/* The auxprop API changed in commit a321326, Oct 2008 */ 1108#if SASL_AUXPROP_PLUG_VERSION > 4 1109# define AUXPROP_RTYPE int 1110# define AUXPROP_RET 0 1111#else 1112# define AUXPROP_RTYPE void 1113# define AUXPROP_RET 1114#endif 1115 1116/* 1117 * Callback which we use to fake out server-side password checking for 1118 * those mechanisms where a plaintext password from the client is *NOT* 1119 * available, and so the SASL server code needs to retrieve the password 1120 * from an auxprop plugin. 1121 */ 1122static AUXPROP_RTYPE server_auxprop_lookup(void *glob_context __attribute__((unused)), 1123 sasl_server_params_t *sparams, 1124 unsigned flags __attribute__((unused)), 1125 const char *user, 1126 unsigned ulen) 1127{ 1128 const struct propval *prop; 1129 1130 if (ulen != strlen(USERID) || 1131 strncmp(user, USERID, ulen)) 1132 return AUXPROP_RET; 1133 1134 prop = sparams->utils->prop_get(sparams->propctx); 1135 if (!prop) 1136 return AUXPROP_RET; 1137 for ( ; prop->name ; prop++) { 1138 if (!strcmp(prop->name, "*userPassword") || 1139 !strcmp(prop->name, "*cmusaslsecretDIGEST-MD5")) { 1140 if (prop->values) 1141 sparams->utils->prop_erase(sparams->propctx, prop->name); 1142 sparams->utils->prop_set(sparams->propctx, prop->name, 1143 PASSWORD, strlen(PASSWORD)); 1144 } 1145 } 1146 1147 return AUXPROP_RET; 1148} 1149 1150/* 1151 * Helps create a fake "auxiliary propert plugin" for the SASL library, 1152 * which is how we hook into the DIGEST-MD5 mechanism when it wants to 1153 * get a plaintext password to check aginst the hash received from the 1154 * client. 1155 */ 1156static int server_auxprop_init(const sasl_utils_t *utils __attribute__((unused)), 1157 int max_version, 1158 int *out_version, 1159 sasl_auxprop_plug_t **plugp, 1160 const char *plugname __attribute__((unused))) 1161{ 1162 static sasl_auxprop_plug_t plug = { 1163 .features = 0, 1164 .auxprop_lookup = server_auxprop_lookup, 1165 .name = "testserver-hack" 1166 }; 1167 1168 *out_version = max_version; 1169 *plugp = &plug; 1170 return SASL_OK; 1171} 1172 1173/* 1174 * Initialise the SASL library, client or server. 1175 * 1176 * Returns: 0 on success, -ve on error. 1177 */ 1178static int init_sasl(int isclient) 1179{ 1180 int r; 1181 1182 /* set the SASL allocation functions */ 1183 sasl_set_alloc((sasl_malloc_t *)xmalloc, 1184 (sasl_calloc_t *)xcalloc, 1185 (sasl_realloc_t *)xrealloc, 1186 (sasl_free_t *)free); 1187 1188 /* set the SASL mutex functions */ 1189 sasl_set_mutex(cyrus_mutex_alloc, cyrus_mutex_lock, 1190 cyrus_mutex_unlock, cyrus_mutex_free); 1191 1192 if (isclient) { 1193 static const struct sasl_callback client_cb[] = { 1194 { SASL_CB_LIST_END, NULL, NULL } 1195 }; 1196 1197 /* load the SASL client plugins */ 1198 if (sasl_client_init(client_cb)) { 1199 fprintf(stderr, "could not init sasl client\n"); 1200 return -1; 1201 } 1202 1203 callbacks = mysasl_callbacks(USERID, USERID, NULL, PASSWORD); 1204 if (!callbacks) 1205 return -1; 1206 } else { 1207 static const struct sasl_callback server_cb[] = { 1208 { SASL_CB_SERVER_USERDB_CHECKPASS, (void *)&server_checkpass, NULL }, 1209 { SASL_CB_LIST_END, NULL, NULL } 1210 }; 1211 1212 /* load the SASL server plugins */ 1213 r = sasl_server_init(server_cb, "testserver"); 1214 if (r != SASL_OK) { 1215 fprintf(stderr, "sasl_server_init() failed: error %d\n", r); 1216 return -1; 1217 } 1218 1219 sasl_auxprop_add_plugin("testserver", server_auxprop_init); 1220 } 1221 1222 return 0; 1223} 1224 1225/* ====================================================================== */ 1226 1227static struct server_state *server_state; 1228static pid_t server_pid; 1229static int sasl_initialised; 1230static int old_session_timeout; 1231static char *old_config_dir; 1232static char *old_tls_ca_file; 1233static char *old_tls_cert_file; 1234static char *old_tls_key_file; 1235 1236/* 1237 * Test suite setup function. Sets up the global 1238 * server_state page. Forks a server process which listens 1239 * on a TCP port, and writes the port number into default_service[] 1240 * so that the client code can connect to it. 1241 */ 1242static int set_up(void) 1243{ 1244 int rend_sock, port = 0; 1245 char *sr; 1246 static char cwd[PATH_MAX]; 1247 1248 if (verbose > 1) 1249 fprintf(stderr, "Starting server!\n"); 1250 1251 sr = getcwd(cwd, sizeof(cwd)); 1252 if (!sr) { 1253 fprintf(stderr, "getcwd() failed: %s\n", strerror(errno)); 1254 return -1; 1255 } 1256 1257 old_config_dir = (char *)config_dir; 1258 config_dir = xstrdup(cwd); 1259 1260 old_tls_ca_file = (char *)imapopts[IMAPOPT_TLS_SERVER_CA_FILE].val.s; 1261 imapopts[IMAPOPT_TLS_SERVER_CA_FILE].val.s = 1262 strconcat(cwd, "/cacert.pem", (char *)NULL); 1263 1264 old_tls_cert_file = (char *)imapopts[IMAPOPT_TLS_SERVER_CERT].val.s; 1265 imapopts[IMAPOPT_TLS_SERVER_CERT].val.s = 1266 strconcat(cwd, "/cert.pem", (char *)NULL); 1267 1268 old_tls_key_file = (char *)imapopts[IMAPOPT_TLS_SERVER_KEY].val.s; 1269 imapopts[IMAPOPT_TLS_SERVER_KEY].val.s = 1270 strconcat(cwd, "/key.pem", (char *)NULL); 1271 1272 /* disable SSL session caching */ 1273 old_session_timeout = imapopts[IMAPOPT_TLS_SESSION_TIMEOUT].val.i; 1274 imapopts[IMAPOPT_TLS_SESSION_TIMEOUT].val.i = 0; 1275 1276 rend_sock = create_server_socket(&port); 1277 if (rend_sock < 0) 1278 return -1; 1279 1280 server_state = get_anonymous_page(); 1281 if (!server_state) { 1282 close(rend_sock); 1283 return -1; 1284 } 1285 server_state->rend_sock = rend_sock; 1286 1287 if (verbose > 1) 1288 fprintf(stderr, "Bound to port tcp/%u\n", port); 1289 snprintf(default_service, sizeof(default_service), "%d", port); 1290 1291 server_pid = server_start(server_state); 1292 if (server_pid < 0) 1293 return -1; 1294 1295 /* 1296 * Initialise the SASL library in the client process. This init 1297 * function can actually be called multiple times in the lifetime of 1298 * a test runner process, which is perhaps surprising, but we have 1299 * to deal with it. We do however seem to be able to get away with 1300 * initialising SASL multiple times as long as we call sasl_done() 1301 * after we're done. 1302 */ 1303 if (init_sasl(/*client*/1) < 0) 1304 return -1; 1305 sasl_initialised = 1; 1306 1307 return 0; 1308} 1309 1310/* 1311 * Test suite teardown function. Shuts down the server and 1312 * removes the global server_state page. 1313 */ 1314static int tear_down(void) 1315{ 1316 if (verbose > 1) 1317 fprintf(stderr, "Cleaning up server! NOT.\n"); 1318 if (server_pid > 1) 1319 server_shutdown(server_pid); 1320 if (server_state) 1321 free_anonymous_page(server_state); 1322 if (callbacks) { 1323 free_callbacks(callbacks); 1324 callbacks = NULL; 1325 } 1326 if (sasl_initialised) { 1327 sasl_done(); 1328 sasl_initialised = 0; 1329 } 1330 1331 free((char *)config_dir); 1332 config_dir = old_config_dir; 1333 1334 imapopts[IMAPOPT_TLS_SESSION_TIMEOUT].val.i = old_session_timeout; 1335 1336 free((char *)imapopts[IMAPOPT_TLS_SERVER_CA_FILE].val.s); 1337 imapopts[IMAPOPT_TLS_SERVER_CA_FILE].val.s = old_tls_ca_file; 1338 1339 free((char *)imapopts[IMAPOPT_TLS_SERVER_CERT].val.s); 1340 imapopts[IMAPOPT_TLS_SERVER_CERT].val.s = old_tls_cert_file; 1341 1342 free((char *)imapopts[IMAPOPT_TLS_SERVER_KEY].val.s); 1343 imapopts[IMAPOPT_TLS_SERVER_KEY].val.s = old_tls_key_file; 1344 1345 return 0; 1346} 1347 1348/* ====================================================================== */ 1349/* vim: set ft=c: */ 1350