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>
35RCSID("$Id: gssmaestro.c 21605 2007-07-17 06:51:57Z lha $");
36
37static FILE *logfile;
38
39/*
40 *
41 */
42
43struct 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
60static struct client **clients;
61static int num_clients;
62
63static int
64init_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
84static int
85accept_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
105static int
106acquire_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
122static int
123toast_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
133static int
134goodbye(struct client *client)
135{
136    put32(client, eGoodBye);
137    return GSMERR_OK;
138}
139
140static int
141get_targetname(struct client *client,
142	       char **target)
143{
144    put32(client, eGetTargetName);
145    retstring(client, *target);
146    return GSMERR_OK;
147}
148
149static int32_t
150encrypt_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
164static int32_t
165decrypt_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
179static int32_t
180get_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
194static int32_t
195verify_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
210static int32_t
211get_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
222static int32_t
223get_moniker(struct client *client,
224	    char **moniker)
225{
226    put32(client, eGetMoniker);
227    retstring(client, *moniker);
228    return GSMERR_OK;
229}
230
231static int
232wait_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
277static int
278build_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
369out:
370    return val;
371}
372
373static void
374test_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
394static int32_t
395test_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
433static int32_t
434test_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
454static int
455log_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    }
493out:
494
495    return 0;
496}
497
498static void
499connect_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
594static struct client *
595get_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
608static int version_flag;
609static int help_flag;
610static char *logfile_str;
611static getarg_strings principals;
612static getarg_strings slaves;
613
614struct 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
627static void
628usage(int ret)
629{
630    arg_printusage (args,
631		    sizeof(args) / sizeof(args[0]),
632		    NULL,
633		    "");
634    exit (ret);
635}
636
637int
638main(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
835out:
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