1 /* 2 * Copyright (c) 2006 Kungliga Tekniska H�gskolan 3 * (Royal Institute of Technology, Stockholm, Sweden). 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * 3. Neither the name of KTH nor the names of its contributors may be 18 * used to endorse or promote products derived from this software without 19 * specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY KTH AND ITS CONTRIBUTORS ``AS IS'' AND ANY 22 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 24 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KTH OR ITS CONTRIBUTORS BE 25 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 28 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 29 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 30 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 31 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 */ 33 34 #include <common.h> 35 RCSID("$Id: gssmaestro.c 21605 2007-07-17 06:51:57Z lha $"); 36 37 static FILE *logfile; 38 39 /* 40 * 41 */ 42 43 struct client { 44 char *name; 45 struct sockaddr *sa; 46 socklen_t salen; 47 krb5_storage *sock; 48 int32_t capabilities; 49 char *target_name; 50 char *moniker; 51 krb5_storage *logsock; 52 int have_log; 53 #ifdef ENABLE_PTHREAD_SUPPORT 54 pthread_t thr; 55 #else 56 pid_t child; 57 #endif 58 }; 59 60 static struct client **clients; 61 static int num_clients; 62 63 static int 64 init_sec_context(struct client *client, 65 int32_t *hContext, int32_t *hCred, 66 int32_t flags, 67 const char *targetname, 68 const krb5_data *itoken, krb5_data *otoken) 69 { 70 int32_t val; 71 krb5_data_zero(otoken); 72 put32(client, eInitContext); 73 put32(client, *hContext); 74 put32(client, *hCred); 75 put32(client, flags); 76 putstring(client, targetname); 77 putdata(client, *itoken); 78 ret32(client, *hContext); 79 ret32(client, val); 80 retdata(client, *otoken); 81 return val; 82 } 83 84 static int 85 accept_sec_context(struct client *client, 86 int32_t *hContext, 87 int32_t flags, 88 const krb5_data *itoken, 89 krb5_data *otoken, 90 int32_t *hDelegCred) 91 { 92 int32_t val; 93 krb5_data_zero(otoken); 94 put32(client, eAcceptContext); 95 put32(client, *hContext); 96 put32(client, flags); 97 putdata(client, *itoken); 98 ret32(client, *hContext); 99 ret32(client, val); 100 retdata(client, *otoken); 101 ret32(client, *hDelegCred); 102 return val; 103 } 104 105 static int 106 acquire_cred(struct client *client, 107 const char *username, 108 const char *password, 109 int32_t flags, 110 int32_t *hCred) 111 { 112 int32_t val; 113 put32(client, eAcquireCreds); 114 putstring(client, username); 115 putstring(client, password); 116 put32(client, flags); 117 ret32(client, val); 118 ret32(client, *hCred); 119 return val; 120 } 121 122 static int 123 toast_resource(struct client *client, 124 int32_t hCred) 125 { 126 int32_t val; 127 put32(client, eToastResource); 128 put32(client, hCred); 129 ret32(client, val); 130 return val; 131 } 132 133 static int 134 goodbye(struct client *client) 135 { 136 put32(client, eGoodBye); 137 return GSMERR_OK; 138 } 139 140 static int 141 get_targetname(struct client *client, 142 char **target) 143 { 144 put32(client, eGetTargetName); 145 retstring(client, *target); 146 return GSMERR_OK; 147 } 148 149 static int32_t 150 encrypt_token(struct client *client, int32_t hContext, int32_t flags, 151 krb5_data *in, krb5_data *out) 152 { 153 int32_t val; 154 put32(client, eEncrypt); 155 put32(client, hContext); 156 put32(client, flags); 157 put32(client, 0); 158 putdata(client, *in); 159 ret32(client, val); 160 retdata(client, *out); 161 return val; 162 } 163 164 static int32_t 165 decrypt_token(struct client *client, int32_t hContext, int flags, 166 krb5_data *in, krb5_data *out) 167 { 168 int32_t val; 169 put32(client, eDecrypt); 170 put32(client, hContext); 171 put32(client, flags); 172 put32(client, 0); 173 putdata(client, *in); 174 ret32(client, val); 175 retdata(client, *out); 176 return val; 177 } 178 179 static int32_t 180 get_mic(struct client *client, int32_t hContext, 181 krb5_data *in, krb5_data *mic) 182 { 183 int32_t val; 184 put32(client, eSign); 185 put32(client, hContext); 186 put32(client, 0); 187 put32(client, 0); 188 putdata(client, *in); 189 ret32(client, val); 190 retdata(client, *mic); 191 return val; 192 } 193 194 static int32_t 195 verify_mic(struct client *client, int32_t hContext, 196 krb5_data *in, krb5_data *mic) 197 { 198 int32_t val; 199 put32(client, eVerify); 200 put32(client, hContext); 201 put32(client, 0); 202 put32(client, 0); 203 putdata(client, *in); 204 putdata(client, *mic); 205 ret32(client, val); 206 return val; 207 } 208 209 210 static int32_t 211 get_version_capa(struct client *client, 212 int32_t *version, int32_t *capa, 213 char **version_str) 214 { 215 put32(client, eGetVersionAndCapabilities); 216 ret32(client, *version); 217 ret32(client, *capa); 218 retstring(client, *version_str); 219 return GSMERR_OK; 220 } 221 222 static int32_t 223 get_moniker(struct client *client, 224 char **moniker) 225 { 226 put32(client, eGetMoniker); 227 retstring(client, *moniker); 228 return GSMERR_OK; 229 } 230 231 static int 232 wait_log(struct client *c) 233 { 234 int32_t port; 235 struct sockaddr_storage sast; 236 socklen_t salen = sizeof(sast); 237 int fd, fd2, ret; 238 239 memset(&sast, 0, sizeof(sast)); 240 241 assert(sizeof(sast) >= c->salen); 242 243 fd = socket(c->sa->sa_family, SOCK_STREAM, 0); 244 if (fd < 0) 245 err(1, "failed to build socket for %s's logging port", c->moniker); 246 247 ((struct sockaddr *)&sast)->sa_family = c->sa->sa_family; 248 ret = bind(fd, (struct sockaddr *)&sast, c->salen); 249 if (ret < 0) 250 err(1, "failed to bind %s's logging port", c->moniker); 251 252 if (listen(fd, SOMAXCONN) < 0) 253 err(1, "failed to listen %s's logging port", c->moniker); 254 255 salen = sizeof(sast); 256 ret = getsockname(fd, (struct sockaddr *)&sast, &salen); 257 if (ret < 0) 258 err(1, "failed to get address of local socket for %s", c->moniker); 259 260 port = socket_get_port((struct sockaddr *)&sast); 261 262 put32(c, eSetLoggingSocket); 263 put32(c, ntohs(port)); 264 265 salen = sizeof(sast); 266 fd2 = accept(fd, (struct sockaddr *)&sast, &salen); 267 if (fd2 < 0) 268 err(1, "failed to accept local socket for %s", c->moniker); 269 close(fd); 270 271 return fd2; 272 } 273 274 275 276 277 static int 278 build_context(struct client *ipeer, struct client *apeer, 279 int32_t flags, int32_t hCred, 280 int32_t *iContext, int32_t *aContext, int32_t *hDelegCred) 281 { 282 int32_t val = GSMERR_ERROR, ic = 0, ac = 0, deleg = 0; 283 krb5_data itoken, otoken; 284 int iDone = 0, aDone = 0; 285 int step = 0; 286 int first_call = 0x80; 287 288 if (apeer->target_name == NULL) 289 errx(1, "apeer %s have no target name", apeer->name); 290 291 krb5_data_zero(&itoken); 292 293 while (!iDone || !aDone) { 294 295 if (iDone) { 296 warnx("iPeer already done, aPeer want extra rtt"); 297 val = GSMERR_ERROR; 298 goto out; 299 } 300 301 val = init_sec_context(ipeer, &ic, &hCred, flags|first_call, 302 apeer->target_name, &itoken, &otoken); 303 step++; 304 switch(val) { 305 case GSMERR_OK: 306 iDone = 1; 307 if (aDone) 308 continue; 309 break; 310 case GSMERR_CONTINUE_NEEDED: 311 break; 312 default: 313 warnx("iPeer %s failed with %d (step %d)", 314 ipeer->name, (int)val, step); 315 goto out; 316 } 317 318 if (aDone) { 319 warnx("aPeer already done, iPeer want extra rtt"); 320 val = GSMERR_ERROR; 321 goto out; 322 } 323 324 val = accept_sec_context(apeer, &ac, flags|first_call, 325 &otoken, &itoken, &deleg); 326 step++; 327 switch(val) { 328 case GSMERR_OK: 329 aDone = 1; 330 if (iDone) 331 continue; 332 break; 333 case GSMERR_CONTINUE_NEEDED: 334 break; 335 default: 336 warnx("aPeer %s failed with %d (step %d)", 337 apeer->name, (int)val, step); 338 val = GSMERR_ERROR; 339 goto out; 340 } 341 first_call = 0; 342 val = GSMERR_OK; 343 } 344 345 if (iContext == NULL || val != GSMERR_OK) { 346 if (ic) 347 toast_resource(ipeer, ic); 348 if (iContext) 349 *iContext = 0; 350 } else 351 *iContext = ic; 352 353 if (aContext == NULL || val != GSMERR_OK) { 354 if (ac) 355 toast_resource(apeer, ac); 356 if (aContext) 357 *aContext = 0; 358 } else 359 *aContext = ac; 360 361 if (hDelegCred == NULL || val != GSMERR_OK) { 362 if (deleg) 363 toast_resource(apeer, deleg); 364 if (hDelegCred) 365 *hDelegCred = 0; 366 } else 367 *hDelegCred = deleg; 368 369 out: 370 return val; 371 } 372 373 static void 374 test_mic(struct client *c1, int32_t hc1, struct client *c2, int32_t hc2) 375 { 376 krb5_data msg, mic; 377 int32_t val; 378 379 msg.data = "foo"; 380 msg.length = 3; 381 382 krb5_data_zero(&mic); 383 384 val = get_mic(c1, hc1, &msg, &mic); 385 if (val) 386 errx(1, "get_mic failed to host: %s", c1->moniker); 387 val = verify_mic(c2, hc2, &msg, &mic); 388 if (val) 389 errx(1, "verify_mic failed to host: %s", c2->moniker); 390 391 krb5_data_free(&mic); 392 } 393 394 static int32_t 395 test_wrap(struct client *c1, int32_t hc1, struct client *c2, int32_t hc2, 396 int conf) 397 { 398 krb5_data msg, wrapped, out; 399 int32_t val; 400 401 msg.data = "foo"; 402 msg.length = 3; 403 404 krb5_data_zero(&wrapped); 405 krb5_data_zero(&out); 406 407 val = encrypt_token(c1, hc1, conf, &msg, &wrapped); 408 if (val) { 409 warnx("encrypt_token failed to host: %s", c1->moniker); 410 return val; 411 } 412 val = decrypt_token(c2, hc2, conf, &wrapped, &out); 413 if (val) { 414 krb5_data_free(&wrapped); 415 warnx("decrypt_token failed to host: %s", c2->moniker); 416 return val; 417 } 418 419 if (msg.length != out.length) { 420 warnx("decrypted'ed token have wrong length (%lu != %lu)", 421 (unsigned long)msg.length, (unsigned long)out.length); 422 val = GSMERR_ERROR; 423 } else if (memcmp(msg.data, out.data, msg.length) != 0) { 424 warnx("decryptd'ed token have wrong data"); 425 val = GSMERR_ERROR; 426 } 427 428 krb5_data_free(&wrapped); 429 krb5_data_free(&out); 430 return val; 431 } 432 433 static int32_t 434 test_token(struct client *c1, int32_t hc1, struct client *c2, int32_t hc2) 435 { 436 int32_t val; 437 int i; 438 439 for (i = 0; i < 10; i++) { 440 test_mic(c1, hc1, c2, hc2); 441 test_mic(c2, hc2, c1, hc1); 442 val = test_wrap(c1, hc1, c2, hc2, 0); 443 if (val) return val; 444 val = test_wrap(c2, hc2, c1, hc1, 0); 445 if (val) return val; 446 val = test_wrap(c1, hc1, c2, hc2, 1); 447 if (val) return val; 448 val = test_wrap(c2, hc2, c1, hc1, 1); 449 if (val) return val; 450 } 451 return GSMERR_OK; 452 } 453 454 static int 455 log_function(void *ptr) 456 { 457 struct client *c = ptr; 458 int32_t cmd, line; 459 char *file, *string; 460 461 while (1) { 462 if (krb5_ret_int32(c->logsock, &cmd)) 463 goto out; 464 465 switch (cmd) { 466 case eLogSetMoniker: 467 if (krb5_ret_string(c->logsock, &file)) 468 goto out; 469 free(file); 470 break; 471 case eLogInfo: 472 case eLogFailure: 473 if (krb5_ret_string(c->logsock, &file)) 474 goto out; 475 if (krb5_ret_int32(c->logsock, &line)) 476 goto out; 477 if (krb5_ret_string(c->logsock, &string)) 478 goto out; 479 printf("%s:%lu: %s\n", 480 file, (unsigned long)line, string); 481 fprintf(logfile, "%s:%lu: %s\n", 482 file, (unsigned long)line, string); 483 fflush(logfile); 484 free(file); 485 free(string); 486 if (krb5_store_int32(c->logsock, 0)) 487 goto out; 488 break; 489 default: 490 errx(1, "client send bad log command: %d", (int)cmd); 491 } 492 } 493 out: 494 495 return 0; 496 } 497 498 static void 499 connect_client(const char *slave) 500 { 501 char *name, *port; 502 struct client *c = ecalloc(1, sizeof(*c)); 503 struct addrinfo hints, *res0, *res; 504 int ret, fd; 505 506 name = estrdup(slave); 507 port = strchr(name, ':'); 508 if (port == NULL) 509 errx(1, "port missing from %s", name); 510 *port++ = 0; 511 512 c->name = estrdup(slave); 513 514 memset(&hints, 0, sizeof(hints)); 515 hints.ai_family = PF_UNSPEC; 516 hints.ai_socktype = SOCK_STREAM; 517 518 ret = getaddrinfo(name, port, &hints, &res0); 519 if (ret) 520 errx(1, "error resolving %s", name); 521 522 for (res = res0, fd = -1; res; res = res->ai_next) { 523 fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); 524 if (fd < 0) 525 continue; 526 if (connect(fd, res->ai_addr, res->ai_addrlen) < 0) { 527 close(fd); 528 fd = -1; 529 continue; 530 } 531 c->sa = ecalloc(1, res->ai_addrlen); 532 memcpy(c->sa, res->ai_addr, res->ai_addrlen); 533 c->salen = res->ai_addrlen; 534 break; /* okay we got one */ 535 } 536 if (fd < 0) 537 err(1, "connect to host: %s", name); 538 freeaddrinfo(res); 539 540 c->sock = krb5_storage_from_fd(fd); 541 close(fd); 542 if (c->sock == NULL) 543 errx(1, "krb5_storage_from_fd"); 544 545 { 546 int32_t version; 547 char *str = NULL; 548 get_version_capa(c, &version, &c->capabilities, &str); 549 if (str) { 550 free(str); 551 } 552 if (c->capabilities & HAS_MONIKER) 553 get_moniker(c, &c->moniker); 554 else 555 c->moniker = c->name; 556 if (c->capabilities & ISSERVER) 557 get_targetname(c, &c->target_name); 558 } 559 560 if (logfile) { 561 int fd; 562 563 printf("starting log socket to client %s\n", c->moniker); 564 565 fd = wait_log(c); 566 567 c->logsock = krb5_storage_from_fd(fd); 568 close(fd); 569 if (c->logsock == NULL) 570 errx(1, "failed to create log krb5_storage"); 571 #ifdef ENABLE_PTHREAD_SUPPORT 572 pthread_create(&c->thr, NULL, log_function, c); 573 #else 574 c->child = fork(); 575 if (c->child == -1) 576 errx(1, "failed to fork"); 577 else if (c->child == 0) { 578 log_function(c); 579 fclose(logfile); 580 exit(0); 581 } 582 #endif 583 } 584 585 586 clients = erealloc(clients, (num_clients + 1) * sizeof(*clients)); 587 588 clients[num_clients] = c; 589 num_clients++; 590 591 free(name); 592 } 593 594 static struct client * 595 get_client(const char *slave) 596 { 597 size_t i; 598 for (i = 0; i < num_clients; i++) 599 if (strcmp(slave, clients[i]->name) == 0) 600 return clients[i]; 601 errx(1, "failed to find client %s", slave); 602 } 603 604 /* 605 * 606 */ 607 608 static int version_flag; 609 static int help_flag; 610 static char *logfile_str; 611 static getarg_strings principals; 612 static getarg_strings slaves; 613 614 struct getargs args[] = { 615 { "principals", 0, arg_strings, &principals, "Test principal", 616 NULL }, 617 { "slaves", 0, arg_strings, &slaves, "Slaves", 618 NULL }, 619 { "log-file", 0, arg_string, &logfile_str, "Logfile", 620 NULL }, 621 { "version", 0, arg_flag, &version_flag, "Print version", 622 NULL }, 623 { "help", 0, arg_flag, &help_flag, NULL, 624 NULL } 625 }; 626 627 static void 628 usage(int ret) 629 { 630 arg_printusage (args, 631 sizeof(args) / sizeof(args[0]), 632 NULL, 633 ""); 634 exit (ret); 635 } 636 637 int 638 main(int argc, char **argv) 639 { 640 int optidx= 0; 641 char *user; 642 char *password; 643 char ***list, **p; 644 size_t num_list, i, j, k; 645 int failed = 0; 646 647 setprogname (argv[0]); 648 649 if (getarg (args, sizeof(args) / sizeof(args[0]), argc, argv, &optidx)) 650 usage (1); 651 652 if (help_flag) 653 usage (0); 654 655 if (version_flag) { 656 print_version (NULL); 657 return 0; 658 } 659 660 if (optidx != argc) 661 usage (1); 662 663 if (principals.num_strings == 0) 664 errx(1, "no principals"); 665 666 user = estrdup(principals.strings[0]); 667 password = strchr(user, ':'); 668 if (password == NULL) 669 errx(1, "password missing from %s", user); 670 *password++ = 0; 671 672 if (slaves.num_strings == 0) 673 errx(1, "no principals"); 674 675 if (logfile_str) { 676 printf("open logfile %s\n", logfile_str); 677 logfile = fopen(logfile_str, "w+"); 678 if (logfile == NULL) 679 err(1, "failed to open: %s", logfile_str); 680 } 681 682 /* 683 * 684 */ 685 686 list = permutate_all(&slaves, &num_list); 687 688 /* 689 * Set up connection to all clients 690 */ 691 692 printf("Connecting to slaves\n"); 693 for (i = 0; i < slaves.num_strings; i++) 694 connect_client(slaves.strings[i]); 695 696 /* 697 * Test acquire credentials 698 */ 699 700 printf("Test acquire credentials\n"); 701 for (i = 0; i < slaves.num_strings; i++) { 702 int32_t hCred, val; 703 704 val = acquire_cred(clients[i], user, password, 1, &hCred); 705 if (val != GSMERR_OK) { 706 warnx("Failed to acquire_cred on host %s: %d", 707 clients[i]->moniker, (int)val); 708 failed = 1; 709 } else 710 toast_resource(clients[i], hCred); 711 } 712 713 if (failed) 714 goto out; 715 716 /* 717 * First test if all slaves can build context to them-self. 718 */ 719 720 printf("Self context tests\n"); 721 for (i = 0; i < num_clients; i++) { 722 int32_t hCred, val, delegCred; 723 int32_t clientC, serverC; 724 struct client *c = clients[i]; 725 726 if (c->target_name == NULL) 727 continue; 728 729 printf("%s connects to self using %s\n", 730 c->moniker, c->target_name); 731 732 val = acquire_cred(c, user, password, 1, &hCred); 733 if (val != GSMERR_OK) 734 errx(1, "failed to acquire_cred: %d", (int)val); 735 736 val = build_context(c, c, 737 GSS_C_REPLAY_FLAG|GSS_C_SEQUENCE_FLAG| 738 GSS_C_INTEG_FLAG|GSS_C_CONF_FLAG| 739 GSS_C_DELEG_FLAG|GSS_C_MUTUAL_FLAG, 740 hCred, &clientC, &serverC, &delegCred); 741 if (val == GSMERR_OK) { 742 test_token(c, clientC, c, serverC); 743 toast_resource(c, clientC); 744 toast_resource(c, serverC); 745 if (delegCred) 746 toast_resource(c, delegCred); 747 } else { 748 warnx("build_context failed: %d", (int)val); 749 } 750 /* 751 * 752 */ 753 754 val = build_context(c, c, 755 GSS_C_INTEG_FLAG|GSS_C_CONF_FLAG, 756 hCred, &clientC, &serverC, &delegCred); 757 if (val == GSMERR_OK) { 758 test_token(c, clientC, c, serverC); 759 toast_resource(c, clientC); 760 toast_resource(c, serverC); 761 if (delegCred) 762 toast_resource(c, delegCred); 763 } else { 764 warnx("build_context failed: %d", (int)val); 765 } 766 767 toast_resource(c, hCred); 768 } 769 /* 770 * Build contexts though all entries in each lists, including the 771 * step from the last entry to the first, ie treat the list as a 772 * circle. 773 * 774 * Only follow the delegated credential, but test "all" 775 * flags. (XXX only do deleg|mutual right now. 776 */ 777 778 printf("\"All\" permutation tests\n"); 779 780 for (i = 0; i < num_list; i++) { 781 int32_t hCred, val, delegCred = 0; 782 int32_t clientC = 0, serverC = 0; 783 struct client *client, *server; 784 785 p = list[i]; 786 787 client = get_client(p[0]); 788 789 val = acquire_cred(client, user, password, 1, &hCred); 790 if (val != GSMERR_OK) 791 errx(1, "failed to acquire_cred: %d", (int)val); 792 793 for (j = 1; j < num_clients + 1; j++) { 794 server = get_client(p[j % num_clients]); 795 796 if (server->target_name == NULL) 797 break; 798 799 for (k = 1; k < j; k++) 800 printf("\t"); 801 printf("%s -> %s\n", client->moniker, server->moniker); 802 803 val = build_context(client, server, 804 GSS_C_REPLAY_FLAG|GSS_C_SEQUENCE_FLAG| 805 GSS_C_INTEG_FLAG|GSS_C_CONF_FLAG| 806 GSS_C_DELEG_FLAG|GSS_C_MUTUAL_FLAG, 807 hCred, &clientC, &serverC, &delegCred); 808 if (val != GSMERR_OK) { 809 warnx("build_context failed: %d", (int)val); 810 break; 811 } 812 813 val = test_token(client, clientC, server, serverC); 814 if (val) 815 break; 816 817 toast_resource(client, clientC); 818 toast_resource(server, serverC); 819 if (!delegCred) { 820 warnx("no delegated cred on %s", server->moniker); 821 break; 822 } 823 toast_resource(client, hCred); 824 hCred = delegCred; 825 client = server; 826 } 827 if (hCred) 828 toast_resource(client, hCred); 829 } 830 831 /* 832 * Close all connections to clients 833 */ 834 835 out: 836 printf("sending goodbye and waiting for log sockets\n"); 837 for (i = 0; i < num_clients; i++) { 838 goodbye(clients[i]); 839 if (clients[i]->logsock) { 840 #ifdef ENABLE_PTHREAD_SUPPORT 841 pthread_join(&clients[i]->thr, NULL); 842 #else 843 waitpid(clients[i]->child, NULL, 0); 844 #endif 845 } 846 } 847 848 printf("done\n"); 849 850 return 0; 851 } 852