tidying
[exim.git] / test / src / server.c
1 /* A little hacked up program that listens on a given port and allows a script
2 to play the part of a remote MTA for testing purposes. This scripted version is
3 hacked from my original interactive version. A further hack allows it to listen
4 on a Unix domain socket as an alternative to a TCP/IP port.
5
6 In an IPv6 world, listening happens on both an IPv6 and an IPv4 socket, always
7 on all interfaces, unless the option -noipv6 is given. */
8
9 /* ANSI C standard includes */
10
11 #include <ctype.h>
12 #include <signal.h>
13 #include <stdarg.h>
14 #include <stddef.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <time.h>
19
20 /* Unix includes */
21
22 #include <errno.h>
23 #include <dirent.h>
24 #include <sys/types.h>
25
26 #include <netinet/in_systm.h>
27 #include <netinet/in.h>
28 #include <netinet/ip.h>
29
30 #ifdef HAVE_NETINET_IP_VAR_H
31 # include <netinet/ip_var.h>
32 #endif
33
34 #include <netdb.h>
35 #include <arpa/inet.h>
36 #include <sys/time.h>
37 #include <sys/resource.h>
38 #include <sys/socket.h>
39 #include <sys/un.h>
40 #include <sys/stat.h>
41 #include <fcntl.h>
42 #include <unistd.h>
43 #include <utime.h>
44
45 #ifdef AF_INET6
46 # define HAVE_IPV6 1
47 #endif
48
49 #ifndef S_ADDR_TYPE
50 # define S_ADDR_TYPE u_long
51 #endif
52
53 #ifndef CS
54 # define CS (char *)
55 #endif
56
57
58 typedef struct line {
59 struct line *next;
60 unsigned len;
61 char line[1];
62 } line;
63
64 typedef unsigned BOOL;
65 #define FALSE 0
66 #define TRUE 1
67
68
69 /*************************************************
70 * SIGALRM handler - crash out *
71 *************************************************/
72 int tmo_noerror = 0;
73
74 static void
75 sigalrm_handler(int sig)
76 {
77 sig = sig; /* Keep picky compilers happy */
78 printf("\nServer timed out\n");
79 exit(tmo_noerror ? 0 : 99);
80 }
81
82
83 /*************************************************
84 * Get textual IP address *
85 *************************************************/
86
87 /* This function is copied from Exim */
88
89 char *
90 host_ntoa(const void *arg, char *buffer)
91 {
92 char *yield;
93
94 /* The new world. It is annoying that we have to fish out the address from
95 different places in the block, depending on what kind of address it is. It
96 is also a pain that inet_ntop() returns a const char *, whereas the IPv4
97 function inet_ntoa() returns just char *, and some picky compilers insist
98 on warning if one assigns a const char * to a char *. Hence the casts. */
99
100 #if HAVE_IPV6
101 char addr_buffer[46];
102 int family = ((struct sockaddr *)arg)->sa_family;
103 if (family == AF_INET6)
104 {
105 struct sockaddr_in6 *sk = (struct sockaddr_in6 *)arg;
106 yield = (char *)inet_ntop(family, &(sk->sin6_addr), addr_buffer,
107 sizeof(addr_buffer));
108 }
109 else
110 {
111 struct sockaddr_in *sk = (struct sockaddr_in *)arg;
112 yield = (char *)inet_ntop(family, &(sk->sin_addr), addr_buffer,
113 sizeof(addr_buffer));
114 }
115
116 /* If the result is a mapped IPv4 address, show it in V4 format. */
117
118 if (strncmp(yield, "::ffff:", 7) == 0) yield += 7;
119
120 #else /* HAVE_IPV6 */
121
122 /* The old world */
123
124 yield = inet_ntoa(((struct sockaddr_in *)arg)->sin_addr);
125 #endif
126
127 strcpy(buffer, yield);
128 return buffer;
129 }
130
131
132
133 static void
134 printit(char * s, int n)
135 {
136 while(n--)
137 {
138 unsigned char c = *s++;
139 if (c == '\\')
140 printf("\\\\");
141 else if (c >= ' ' && c <= '~') /* assumes ascii */
142 putchar(c);
143 else
144 printf("\\x%02x", c);
145 }
146 putchar('\n');
147 }
148
149
150
151 /*************************************************
152 * Main Program *
153 *************************************************/
154
155 #define v6n 0 /* IPv6 socket number */
156 #define v4n 1 /* IPv4 socket number */
157 #define udn 2 /* Unix domain socket number */
158 #define skn 2 /* Potential number of sockets */
159
160 int main(int argc, char **argv)
161 {
162 int i;
163 int port = 0;
164 int listen_socket[3] = { -1, -1, -1 };
165 int accept_socket;
166 int dup_accept_socket;
167 int connection_count = 1;
168 int count;
169 int on = 1;
170 int timeout = 5;
171 int initial_pause = 0;
172 int use_ipv4 = 1;
173 int use_ipv6 = 1;
174 int debug = 0;
175 int na = 1;
176 line *script = NULL;
177 line *last = NULL;
178 line *s;
179 FILE *in, *out;
180 int linebuf = 1;
181 char *pidfile = NULL;
182
183 char *sockname = NULL;
184 unsigned char buffer[10240];
185
186 struct sockaddr_un sockun; /* don't use "sun" */
187 struct sockaddr_un sockun_accepted;
188 int sockun_len = sizeof(sockun_accepted);
189
190 #if HAVE_IPV6
191 struct sockaddr_in6 sin6;
192 struct sockaddr_in6 accepted;
193 struct in6_addr anyaddr6 = IN6ADDR_ANY_INIT ;
194 #else
195 struct sockaddr_in accepted;
196 #endif
197
198 /* Always need an IPv4 structure */
199
200 struct sockaddr_in sin4;
201
202 int len = sizeof(accepted);
203
204
205 /* Sort out the arguments */
206 if (argc > 1 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h")))
207 {
208 printf("Usage: %s [options] port|socket [connection count]\n", argv[0]);
209 puts("Options"
210 "\n\t-d debug"
211 "\n\t-i n n seconds initial delay"
212 "\n\t-noipv4 disable ipv4"
213 "\n\t-noipv6 disable ipv6"
214 "\n\t-oP file write PID to file"
215 "\n\t-t n n seconds timeout"
216 );
217 exit(0);
218 }
219
220 while (na < argc && argv[na][0] == '-')
221 {
222 if (strcmp(argv[na], "-d") == 0) debug = 1;
223 else if (strcmp(argv[na], "-t") == 0)
224 {
225 if (tmo_noerror = ((timeout = atoi(argv[++na])) < 0)) timeout = -timeout;
226 }
227 else if (strcmp(argv[na], "-i") == 0) initial_pause = atoi(argv[++na]);
228 else if (strcmp(argv[na], "-noipv4") == 0) use_ipv4 = 0;
229 else if (strcmp(argv[na], "-noipv6") == 0) use_ipv6 = 0;
230 else if (strcmp(argv[na], "-oP") == 0) pidfile = argv[++na];
231 else
232 {
233 printf("server: unknown option %s, try -h or --help\n", argv[na]);
234 exit(1);
235 }
236 na++;
237 }
238
239 if (!use_ipv4 && !use_ipv6)
240 {
241 printf("server: -noipv4 and -noipv6 cannot both be given\n");
242 exit(1);
243 }
244
245 if (na >= argc)
246 {
247 printf("server: no port number or socket name given\n");
248 exit(1);
249 }
250
251 if (argv[na][0] == '/')
252 {
253 sockname = argv[na];
254 unlink(sockname); /* in case left lying around */
255 }
256 else port = atoi(argv[na]);
257 na++;
258
259 if (na < argc) connection_count = atoi(argv[na]);
260
261
262 /* Initial pause (before creating listen sockets */
263 if (initial_pause > 0)
264 {
265 if (debug)
266 printf("%d: Inital pause of %d seconds\n", time(NULL), initial_pause);
267 else
268 printf("Inital pause of %d seconds\n", initial_pause);
269 while (initial_pause > 0)
270 initial_pause = sleep(initial_pause);
271 }
272
273 /* Create sockets */
274
275 if (port == 0) /* Unix domain */
276 {
277 if (debug) printf("%d: Creating Unix domain socket\n", time(NULL));
278 listen_socket[udn] = socket(PF_UNIX, SOCK_STREAM, 0);
279 if (listen_socket[udn] < 0)
280 {
281 printf("Unix domain socket creation failed: %s\n", strerror(errno));
282 exit(1);
283 }
284 }
285 else
286 {
287 #if HAVE_IPV6
288 if (use_ipv6)
289 {
290 if (debug) printf("Creating IPv6 socket\n");
291 listen_socket[v6n] = socket(AF_INET6, SOCK_STREAM, 0);
292 if (listen_socket[v6n] < 0)
293 {
294 printf("IPv6 socket creation failed: %s\n", strerror(errno));
295 exit(1);
296 }
297
298 /* If this is an IPv6 wildcard socket, set IPV6_V6ONLY if that option is
299 available. */
300
301 #ifdef IPV6_V6ONLY
302 if (setsockopt(listen_socket[v6n], IPPROTO_IPV6, IPV6_V6ONLY, (char *)(&on),
303 sizeof(on)) < 0)
304 printf("Setting IPV6_V6ONLY on IPv6 wildcard "
305 "socket failed (%s): carrying on without it\n", strerror(errno));
306 #endif /* IPV6_V6ONLY */
307 }
308 #endif /* HAVE_IPV6 */
309
310 /* Create an IPv4 socket if required */
311
312 if (use_ipv4)
313 {
314 if (debug) printf("Creating IPv4 socket\n");
315 listen_socket[v4n] = socket(AF_INET, SOCK_STREAM, 0);
316 if (listen_socket[v4n] < 0)
317 {
318 printf("IPv4 socket creation failed: %s\n", strerror(errno));
319 exit(1);
320 }
321 }
322 }
323
324
325 /* Set SO_REUSEADDR on the IP sockets so that the program can be restarted
326 while a connection is being handled - this can happen as old connections lie
327 around for a bit while crashed processes are tidied away. Without this, a
328 connection will prevent reuse of the smtp port for listening. */
329
330 for (i = v6n; i <= v4n; i++)
331 {
332 if (listen_socket[i] >= 0 &&
333 setsockopt(listen_socket[i], SOL_SOCKET, SO_REUSEADDR, (char *)(&on),
334 sizeof(on)) < 0)
335 {
336 printf("setting SO_REUSEADDR on socket failed: %s\n", strerror(errno));
337 exit(1);
338 }
339 }
340
341
342 /* Now bind the sockets to the required port or path. If a path, ensure
343 anyone can write to it. */
344
345 if (port == 0)
346 {
347 struct stat statbuf;
348 sockun.sun_family = AF_UNIX;
349 if (debug) printf("Binding Unix domain socket\n");
350 sprintf(sockun.sun_path, "%.*s", (int)(sizeof(sockun.sun_path)-1), sockname);
351 if (bind(listen_socket[udn], (struct sockaddr *)&sockun, sizeof(sockun)) < 0)
352 {
353 printf("Unix domain socket bind() failed: %s\n", strerror(errno));
354 exit(1);
355 }
356 (void)stat(sockname, &statbuf);
357 if (debug) printf("Setting Unix domain socket mode: %0x\n",
358 statbuf.st_mode | 0777);
359 if (chmod(sockname, statbuf.st_mode | 0777) < 0)
360 {
361 printf("Unix domain socket chmod() failed: %s\n", strerror(errno));
362 exit(1);
363 }
364 }
365
366 else
367 {
368 for (i = 0; i < skn; i++)
369 {
370 if (listen_socket[i] < 0) continue;
371
372 /* For an IPv6 listen, use an IPv6 socket */
373
374 #if HAVE_IPV6
375 if (i == v6n)
376 {
377 memset(&sin6, 0, sizeof(sin6));
378 sin6.sin6_family = AF_INET6;
379 sin6.sin6_port = htons(port);
380 sin6.sin6_addr = anyaddr6;
381 if (bind(listen_socket[i], (struct sockaddr *)&sin6, sizeof(sin6)) < 0)
382 {
383 printf("IPv6 socket bind() failed: %s\n", strerror(errno));
384 exit(1);
385 }
386 }
387 else
388 #endif
389
390 /* For an IPv4 bind, use an IPv4 socket, even in an IPv6 world. If an IPv4
391 bind fails EADDRINUSE after IPv6 success, carry on, because it means the
392 IPv6 socket will handle IPv4 connections. */
393
394 {
395 memset(&sin4, 0, sizeof(sin4));
396 sin4.sin_family = AF_INET;
397 sin4.sin_addr.s_addr = (S_ADDR_TYPE)INADDR_ANY;
398 sin4.sin_port = htons(port);
399 if (bind(listen_socket[i], (struct sockaddr *)&sin4, sizeof(sin4)) < 0)
400 {
401 if (listen_socket[v6n] < 0 || errno != EADDRINUSE)
402 {
403 printf("IPv4 socket bind() failed: %s\n", strerror(errno));
404 exit(1);
405 }
406 else
407 {
408 close(listen_socket[i]);
409 listen_socket[i] = -1;
410 }
411 }
412 }
413 }
414 }
415
416
417 /* Start listening. If IPv4 fails EADDRINUSE after IPv6 succeeds, ignore the
418 error because it means that the IPv6 socket will handle IPv4 connections. Don't
419 output anything, because it will mess up the test output, which will be
420 different for systems that do this and those that don't. */
421
422 for (i = 0; i <= skn; i++)
423 {
424 if (listen_socket[i] >= 0 && listen(listen_socket[i], 5) < 0)
425 {
426 if (i != v4n || listen_socket[v6n] < 0 || errno != EADDRINUSE)
427 {
428 printf("listen() failed: %s\n", strerror(errno));
429 exit(1);
430 }
431 }
432 }
433
434
435 if (pidfile)
436 {
437 FILE * p;
438 if (!(p = fopen(pidfile, "w")))
439 {
440 fprintf(stderr, "pidfile create failed: %s\n", strerror(errno));
441 exit(1);
442 }
443 fprintf(p, "%ld\n", (long)getpid());
444 fclose(p);
445 }
446
447 /* This program handles only a fixed number of connections, in sequence. Before
448 waiting for the first connection, read the standard input, which contains the
449 script of things to do. A line containing "++++" is treated as end of file.
450 This is so that the Perl driving script doesn't have to close the pipe -
451 because that would cause it to wait for this process, which it doesn't yet want
452 to do. The driving script adds the "++++" automatically - it doesn't actually
453 appear in the test script. Within lines we interpret \xNN and \\ groups */
454
455 while (fgets(CS buffer, sizeof(buffer), stdin) != NULL)
456 {
457 line *next;
458 char * d;
459 int n = (int)strlen(CS buffer);
460
461 if (n > 1 && buffer[0] == '>' && buffer[1] == '>')
462 linebuf = 0;
463 while (n > 0 && isspace(buffer[n-1])) n--;
464 buffer[n] = 0;
465 if (strcmp(CS buffer, "++++") == 0) break;
466 next = malloc(sizeof(line) + n);
467 next->next = NULL;
468 d = next->line;
469 {
470 char * s = CS buffer;
471 do
472 {
473 char ch;
474 char cl = *s;
475 if (cl == '\\' && (cl = *++s) == 'x')
476 {
477 if ((ch = *++s - '0') > 9 && (ch -= 'A'-'9'-1) > 15) ch -= 'a'-'A';
478 if ((cl = *++s - '0') > 9 && (cl -= 'A'-'9'-1) > 15) cl -= 'a'-'A';
479 cl |= ch << 4;
480 }
481 *d++ = cl;
482 }
483 while (*s++);
484 }
485 next->len = d - next->line - 1;
486 if (last == NULL) script = last = next;
487 else last->next = next;
488 last = next;
489 }
490
491 fclose(stdin);
492
493 /* SIGALRM handler crashes out */
494
495 signal(SIGALRM, sigalrm_handler);
496
497 /* s points to the current place in the script */
498
499 s = script;
500
501 for (count = 0; count < connection_count; count++)
502 {
503 struct {
504 int left;
505 BOOL in_use;
506 } content_length = { 0, FALSE };
507
508 alarm(timeout);
509 if (port <= 0)
510 {
511 printf("Listening on %s ... ", sockname);
512 fflush(stdout);
513 accept_socket = accept(listen_socket[udn],
514 (struct sockaddr *)&sockun_accepted, &sockun_len);
515 }
516
517 else
518 {
519 int lcount;
520 int max_socket = 0;
521 fd_set select_listen;
522
523 printf("Listening on port %d ... ", port);
524 fflush(stdout);
525
526 FD_ZERO(&select_listen);
527 for (i = 0; i < skn; i++)
528 {
529 if (listen_socket[i] >= 0) FD_SET(listen_socket[i], &select_listen);
530 if (listen_socket[i] > max_socket) max_socket = listen_socket[i];
531 }
532
533 if ((lcount = select(max_socket + 1, &select_listen, NULL, NULL, NULL)) < 0)
534 {
535 printf("Select failed\n");
536 fflush(stdout);
537 continue;
538 }
539
540 accept_socket = -1;
541 for (i = 0; i < skn; i++)
542 if (listen_socket[i] > 0 && FD_ISSET(listen_socket[i], &select_listen))
543 {
544 accept_socket = accept(listen_socket[i],
545 (struct sockaddr *)&accepted, &len);
546 FD_CLR(listen_socket[i], &select_listen);
547 break;
548 }
549 }
550 alarm(0);
551
552 if (accept_socket < 0)
553 {
554 printf("accept() failed: %s\n", strerror(errno));
555 exit(1);
556 }
557
558 out = fdopen(accept_socket, "w");
559
560 dup_accept_socket = dup(accept_socket);
561
562 if (port > 0)
563 printf("\nConnection request from [%s]\n", host_ntoa(&accepted, CS buffer));
564 else
565 {
566 printf("\nConnection request\n");
567
568 /* Linux supports a feature for acquiring the peer's credentials, but it
569 appears to be Linux-specific. This code is untested and unused, just
570 saved here for reference. */
571
572 /**********--------------------
573 struct ucred cr;
574 int cl=sizeof(cr);
575
576 if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &cl)==0) {
577 printf("Peer's pid=%d, uid=%d, gid=%d\n",
578 cr.pid, cr.uid, cr.gid);
579 --------------*****************/
580 }
581 fflush(stdout);
582
583 if (dup_accept_socket < 0)
584 {
585 printf("Couldn't dup socket descriptor\n");
586 printf("421 Connection refused: %s\n", strerror(errno));
587 fprintf(out, "421 Connection refused: %s\r\n", strerror(errno));
588 fclose(out);
589 exit(2);
590 }
591
592 in = fdopen(dup_accept_socket, "r");
593
594 /* Loop for handling the conversation(s). For use in SMTP sessions, there are
595 default rules for determining input and output lines: the latter start with
596 digits. This means that the input looks like SMTP dialog. However, this
597 doesn't work for other tests (e.g. ident tests) so we have explicit '<' and
598 '>' flags for input and output as well as the defaults. */
599
600 for (; s; s = s->next)
601 {
602 char *ss = s->line;
603
604 /* Output lines either start with '>' or a digit. In the '>' case we can
605 fudge the sending of \r\n as required. Default is \r\n, ">>" send nothing,
606 ">CR>" sends \r only, and ">LF>" sends \n only. We can also force a
607 connection closedown by ">*eof". */
608
609 if (ss[0] == '>')
610 {
611 char *end = "\r\n";
612 unsigned len = s->len;
613 printit(ss++, len--);
614
615 if (strncmp(ss, "*eof", 4) == 0)
616 {
617 s = s->next;
618 goto END_OFF;
619 }
620
621 if (*ss == '>')
622 { end = ""; ss++; len--; }
623 else if (strncmp(ss, "CR>", 3) == 0)
624 { end = "\r"; ss += 3; len -= 3; }
625 else if (strncmp(ss, "LF>", 3) == 0)
626 { end = "\n"; ss += 3; len -= 3; }
627
628 fwrite(ss, 1, len, out);
629 if (*end) fprintf(out, end);
630 }
631
632 else if (isdigit((unsigned char)ss[0]))
633 {
634 printf("%s\n", ss);
635 fprintf(out, "%s\r\n", ss);
636 }
637
638 /* If the script line starts with "*sleep" we just sleep for a while
639 before continuing. */
640
641 else if (strncmp(ss, "*sleep ", 7) == 0)
642 {
643 int sleepfor = atoi(ss+7);
644 printf("%s\n", ss);
645 fflush(out);
646 sleep(sleepfor);
647 }
648
649 /* If the script line starts with "*data " we expect a numeric argument,
650 and we expect to read (and discard) that many data bytes from the input. */
651
652 else if (strncmp(ss, "*data ", 6) == 0)
653 {
654 int dlen = atoi(ss+6);
655 int n;
656
657 alarm(timeout);
658
659 if (!linebuf)
660 while (dlen > 0)
661 {
662 n = dlen < sizeof(buffer) ? dlen : sizeof(buffer);
663 if ((n = read(dup_accept_socket, CS buffer, n)) == 0)
664 {
665 printf("Unxpected EOF read from client\n");
666 s = s->next;
667 goto END_OFF;
668 }
669 dlen -= n;
670 }
671 else
672 while (dlen-- > 0)
673 if (fgetc(in) == EOF)
674 {
675 printf("Unxpected EOF read from client\n");
676 s = s->next;
677 goto END_OFF;
678 }
679 }
680
681 /* Otherwise the script line is the start of an input line we are expecting
682 from the client, or "*eof" indicating we expect the client to close the
683 connection. Read command line or data lines; the latter are indicated
684 by the expected line being just ".". If the line starts with '<', that
685 doesn't form part of the expected input. (This allows for incoming data
686 starting with a digit.) If the line starts with '<<' we operate in
687 unbuffered rather than line mode and assume that a single read gets the
688 entire message. */
689
690 else
691 {
692 int offset;
693 int data = strcmp(ss, ".") == 0;
694
695 if (ss[0] != '<')
696 offset = 0;
697 else
698 {
699 buffer[0] = '<';
700 if (ss[1] != '<')
701 offset = 1;
702 else
703 {
704 buffer[1] = '<';
705 offset = 2;
706 }
707 }
708
709 fflush(out);
710
711 if (!linebuf)
712 {
713 int n;
714 char c;
715
716 alarm(timeout);
717 n = read(dup_accept_socket, CS buffer+offset, s->len - offset);
718 if (content_length.in_use) content_length.left -= n;
719 if (n == 0)
720 {
721 printf("%sxpected EOF read from client\n",
722 (strncmp(ss, "*eof", 4) == 0)? "E" : "Une");
723 s = s->next;
724 goto END_OFF;
725 }
726 if (offset != 2)
727 while (read(dup_accept_socket, &c, 1) == 1 && c != '\n') ;
728 alarm(0);
729 n += offset;
730
731 printit(buffer, n);
732
733 if (data) do
734 {
735 n = (read(dup_accept_socket, &c, 1) == 1 && c == '.');
736 if (content_length.in_use) content_length.left--;
737 while (c != '\n' && read(dup_accept_socket, &c, 1) == 1)
738 if (content_length.in_use) content_length.left--;
739 } while (!n);
740 else if (memcmp(ss, buffer, n) != 0)
741 {
742 printf("Comparison failed - bailing out\nExpected: ");
743 printit(ss, n);
744 break;
745 }
746 }
747 else
748 {
749 for (;;)
750 {
751 int n;
752 alarm(timeout);
753 if (fgets(CS buffer+offset, sizeof(buffer)-offset, in) == NULL)
754 {
755 printf("%sxpected EOF read from client\n",
756 (strncmp(ss, "*eof", 4) == 0)? "E" : "Une");
757 s = s->next;
758 goto END_OFF;
759 }
760 alarm(0);
761 n = strlen(CS buffer);
762 if (content_length.in_use) content_length.left -= (n - offset);
763 while (n > 0 && isspace(buffer[n-1])) n--;
764 buffer[n] = 0;
765 printf("%s\n", buffer);
766 if (!data || strcmp(CS buffer, ".") == 0) break;
767 }
768
769 if (strncmp(ss, CS buffer, (int)strlen(ss)) != 0)
770 {
771 printf("Comparison failed - bailing out\n");
772 printf("Expected: %s\n", ss);
773 break;
774 }
775 }
776
777 if (sscanf(buffer, "<Content-length: %d", &content_length.left)) content_length.in_use = TRUE;
778 if (content_length.in_use && content_length.left <= 0) shutdown(dup_accept_socket, SHUT_RD);
779 }
780 }
781
782 END_OFF:
783 fclose(in);
784 fclose(out);
785 }
786
787 if (s == NULL) printf("End of script\n");
788
789 if (sockname) unlink(sockname);
790 exit(0);
791 }
792
793 /* End of server.c */