Testsuite: limited support for Content-length:
[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
65 /*************************************************
66 * SIGALRM handler - crash out *
67 *************************************************/
68 int tmo_noerror = 0;
69
70 static void
71 sigalrm_handler(int sig)
72 {
73 sig = sig; /* Keep picky compilers happy */
74 printf("\nServer timed out\n");
75 exit(tmo_noerror ? 0 : 99);
76 }
77
78
79 /*************************************************
80 * Get textual IP address *
81 *************************************************/
82
83 /* This function is copied from Exim */
84
85 char *
86 host_ntoa(const void *arg, char *buffer)
87 {
88 char *yield;
89
90 /* The new world. It is annoying that we have to fish out the address from
91 different places in the block, depending on what kind of address it is. It
92 is also a pain that inet_ntop() returns a const char *, whereas the IPv4
93 function inet_ntoa() returns just char *, and some picky compilers insist
94 on warning if one assigns a const char * to a char *. Hence the casts. */
95
96 #if HAVE_IPV6
97 char addr_buffer[46];
98 int family = ((struct sockaddr *)arg)->sa_family;
99 if (family == AF_INET6)
100 {
101 struct sockaddr_in6 *sk = (struct sockaddr_in6 *)arg;
102 yield = (char *)inet_ntop(family, &(sk->sin6_addr), addr_buffer,
103 sizeof(addr_buffer));
104 }
105 else
106 {
107 struct sockaddr_in *sk = (struct sockaddr_in *)arg;
108 yield = (char *)inet_ntop(family, &(sk->sin_addr), addr_buffer,
109 sizeof(addr_buffer));
110 }
111
112 /* If the result is a mapped IPv4 address, show it in V4 format. */
113
114 if (strncmp(yield, "::ffff:", 7) == 0) yield += 7;
115
116 #else /* HAVE_IPV6 */
117
118 /* The old world */
119
120 yield = inet_ntoa(((struct sockaddr_in *)arg)->sin_addr);
121 #endif
122
123 strcpy(buffer, yield);
124 return buffer;
125 }
126
127
128
129 static void
130 printit(char * s, int n)
131 {
132 while(n--)
133 {
134 unsigned char c = *s++;
135 if (c == '\\')
136 printf("\\\\");
137 else if (c >= ' ' && c <= '~') /* assumes ascii */
138 putchar(c);
139 else
140 printf("\\x%02x", c);
141 }
142 putchar('\n');
143 }
144
145
146
147 /*************************************************
148 * Main Program *
149 *************************************************/
150
151 #define v6n 0 /* IPv6 socket number */
152 #define v4n 1 /* IPv4 socket number */
153 #define udn 2 /* Unix domain socket number */
154 #define skn 2 /* Potential number of sockets */
155
156 int main(int argc, char **argv)
157 {
158 int i;
159 int port = 0;
160 int listen_socket[3] = { -1, -1, -1 };
161 int accept_socket;
162 int dup_accept_socket;
163 int connection_count = 1;
164 int count;
165 int on = 1;
166 int timeout = 5;
167 int initial_pause = 0;
168 int use_ipv4 = 1;
169 int use_ipv6 = 1;
170 int debug = 0;
171 int na = 1;
172 line *script = NULL;
173 line *last = NULL;
174 line *s;
175 FILE *in, *out;
176 int linebuf = 1;
177 char *pidfile = NULL;
178
179 char *sockname = NULL;
180 unsigned char buffer[10240];
181
182 struct sockaddr_un sockun; /* don't use "sun" */
183 struct sockaddr_un sockun_accepted;
184 int sockun_len = sizeof(sockun_accepted);
185
186 #if HAVE_IPV6
187 struct sockaddr_in6 sin6;
188 struct sockaddr_in6 accepted;
189 struct in6_addr anyaddr6 = IN6ADDR_ANY_INIT ;
190 #else
191 struct sockaddr_in accepted;
192 #endif
193
194 /* Always need an IPv4 structure */
195
196 struct sockaddr_in sin4;
197
198 int len = sizeof(accepted);
199
200
201 /* Sort out the arguments */
202 if (argc > 1 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h")))
203 {
204 printf("Usage: %s [options] port|socket [connection count]\n", argv[0]);
205 puts("Options"
206 "\n\t-d debug"
207 "\n\t-i n n seconds initial delay"
208 "\n\t-noipv4 disable ipv4"
209 "\n\t-noipv6 disable ipv6"
210 "\n\t-oP file write PID to file"
211 "\n\t-t n n seconds timeout"
212 );
213 exit(0);
214 }
215
216 while (na < argc && argv[na][0] == '-')
217 {
218 if (strcmp(argv[na], "-d") == 0) debug = 1;
219 else if (strcmp(argv[na], "-t") == 0)
220 {
221 if (tmo_noerror = ((timeout = atoi(argv[++na])) < 0)) timeout = -timeout;
222 }
223 else if (strcmp(argv[na], "-i") == 0) initial_pause = atoi(argv[++na]);
224 else if (strcmp(argv[na], "-noipv4") == 0) use_ipv4 = 0;
225 else if (strcmp(argv[na], "-noipv6") == 0) use_ipv6 = 0;
226 else if (strcmp(argv[na], "-oP") == 0) pidfile = argv[++na];
227 else
228 {
229 printf("server: unknown option %s, try -h or --help\n", argv[na]);
230 exit(1);
231 }
232 na++;
233 }
234
235 if (!use_ipv4 && !use_ipv6)
236 {
237 printf("server: -noipv4 and -noipv6 cannot both be given\n");
238 exit(1);
239 }
240
241 if (na >= argc)
242 {
243 printf("server: no port number or socket name given\n");
244 exit(1);
245 }
246
247 if (argv[na][0] == '/')
248 {
249 sockname = argv[na];
250 unlink(sockname); /* in case left lying around */
251 }
252 else port = atoi(argv[na]);
253 na++;
254
255 if (na < argc) connection_count = atoi(argv[na]);
256
257
258 /* Initial pause (before creating listen sockets */
259 if (initial_pause > 0)
260 {
261 if (debug)
262 printf("%d: Inital pause of %d seconds\n", time(NULL), initial_pause);
263 else
264 printf("Inital pause of %d seconds\n", initial_pause);
265 while (initial_pause > 0)
266 initial_pause = sleep(initial_pause);
267 }
268
269 /* Create sockets */
270
271 if (port == 0) /* Unix domain */
272 {
273 if (debug) printf("%d: Creating Unix domain socket\n", time(NULL));
274 listen_socket[udn] = socket(PF_UNIX, SOCK_STREAM, 0);
275 if (listen_socket[udn] < 0)
276 {
277 printf("Unix domain socket creation failed: %s\n", strerror(errno));
278 exit(1);
279 }
280 }
281 else
282 {
283 #if HAVE_IPV6
284 if (use_ipv6)
285 {
286 if (debug) printf("Creating IPv6 socket\n");
287 listen_socket[v6n] = socket(AF_INET6, SOCK_STREAM, 0);
288 if (listen_socket[v6n] < 0)
289 {
290 printf("IPv6 socket creation failed: %s\n", strerror(errno));
291 exit(1);
292 }
293
294 /* If this is an IPv6 wildcard socket, set IPV6_V6ONLY if that option is
295 available. */
296
297 #ifdef IPV6_V6ONLY
298 if (setsockopt(listen_socket[v6n], IPPROTO_IPV6, IPV6_V6ONLY, (char *)(&on),
299 sizeof(on)) < 0)
300 printf("Setting IPV6_V6ONLY on IPv6 wildcard "
301 "socket failed (%s): carrying on without it\n", strerror(errno));
302 #endif /* IPV6_V6ONLY */
303 }
304 #endif /* HAVE_IPV6 */
305
306 /* Create an IPv4 socket if required */
307
308 if (use_ipv4)
309 {
310 if (debug) printf("Creating IPv4 socket\n");
311 listen_socket[v4n] = socket(AF_INET, SOCK_STREAM, 0);
312 if (listen_socket[v4n] < 0)
313 {
314 printf("IPv4 socket creation failed: %s\n", strerror(errno));
315 exit(1);
316 }
317 }
318 }
319
320
321 /* Set SO_REUSEADDR on the IP sockets so that the program can be restarted
322 while a connection is being handled - this can happen as old connections lie
323 around for a bit while crashed processes are tidied away. Without this, a
324 connection will prevent reuse of the smtp port for listening. */
325
326 for (i = v6n; i <= v4n; i++)
327 {
328 if (listen_socket[i] >= 0 &&
329 setsockopt(listen_socket[i], SOL_SOCKET, SO_REUSEADDR, (char *)(&on),
330 sizeof(on)) < 0)
331 {
332 printf("setting SO_REUSEADDR on socket failed: %s\n", strerror(errno));
333 exit(1);
334 }
335 }
336
337
338 /* Now bind the sockets to the required port or path. If a path, ensure
339 anyone can write to it. */
340
341 if (port == 0)
342 {
343 struct stat statbuf;
344 sockun.sun_family = AF_UNIX;
345 if (debug) printf("Binding Unix domain socket\n");
346 sprintf(sockun.sun_path, "%.*s", (int)(sizeof(sockun.sun_path)-1), sockname);
347 if (bind(listen_socket[udn], (struct sockaddr *)&sockun, sizeof(sockun)) < 0)
348 {
349 printf("Unix domain socket bind() failed: %s\n", strerror(errno));
350 exit(1);
351 }
352 (void)stat(sockname, &statbuf);
353 if (debug) printf("Setting Unix domain socket mode: %0x\n",
354 statbuf.st_mode | 0777);
355 if (chmod(sockname, statbuf.st_mode | 0777) < 0)
356 {
357 printf("Unix domain socket chmod() failed: %s\n", strerror(errno));
358 exit(1);
359 }
360 }
361
362 else
363 {
364 for (i = 0; i < skn; i++)
365 {
366 if (listen_socket[i] < 0) continue;
367
368 /* For an IPv6 listen, use an IPv6 socket */
369
370 #if HAVE_IPV6
371 if (i == v6n)
372 {
373 memset(&sin6, 0, sizeof(sin6));
374 sin6.sin6_family = AF_INET6;
375 sin6.sin6_port = htons(port);
376 sin6.sin6_addr = anyaddr6;
377 if (bind(listen_socket[i], (struct sockaddr *)&sin6, sizeof(sin6)) < 0)
378 {
379 printf("IPv6 socket bind() failed: %s\n", strerror(errno));
380 exit(1);
381 }
382 }
383 else
384 #endif
385
386 /* For an IPv4 bind, use an IPv4 socket, even in an IPv6 world. If an IPv4
387 bind fails EADDRINUSE after IPv6 success, carry on, because it means the
388 IPv6 socket will handle IPv4 connections. */
389
390 {
391 memset(&sin4, 0, sizeof(sin4));
392 sin4.sin_family = AF_INET;
393 sin4.sin_addr.s_addr = (S_ADDR_TYPE)INADDR_ANY;
394 sin4.sin_port = htons(port);
395 if (bind(listen_socket[i], (struct sockaddr *)&sin4, sizeof(sin4)) < 0)
396 {
397 if (listen_socket[v6n] < 0 || errno != EADDRINUSE)
398 {
399 printf("IPv4 socket bind() failed: %s\n", strerror(errno));
400 exit(1);
401 }
402 else
403 {
404 close(listen_socket[i]);
405 listen_socket[i] = -1;
406 }
407 }
408 }
409 }
410 }
411
412
413 /* Start listening. If IPv4 fails EADDRINUSE after IPv6 succeeds, ignore the
414 error because it means that the IPv6 socket will handle IPv4 connections. Don't
415 output anything, because it will mess up the test output, which will be
416 different for systems that do this and those that don't. */
417
418 for (i = 0; i <= skn; i++)
419 {
420 if (listen_socket[i] >= 0 && listen(listen_socket[i], 5) < 0)
421 {
422 if (i != v4n || listen_socket[v6n] < 0 || errno != EADDRINUSE)
423 {
424 printf("listen() failed: %s\n", strerror(errno));
425 exit(1);
426 }
427 }
428 }
429
430
431 if (pidfile)
432 {
433 FILE * p;
434 if (!(p = fopen(pidfile, "w")))
435 {
436 fprintf(stderr, "pidfile create failed: %s\n", strerror(errno));
437 exit(1);
438 }
439 fprintf(p, "%ld\n", (long)getpid());
440 fclose(p);
441 }
442
443 /* This program handles only a fixed number of connections, in sequence. Before
444 waiting for the first connection, read the standard input, which contains the
445 script of things to do. A line containing "++++" is treated as end of file.
446 This is so that the Perl driving script doesn't have to close the pipe -
447 because that would cause it to wait for this process, which it doesn't yet want
448 to do. The driving script adds the "++++" automatically - it doesn't actually
449 appear in the test script. Within lines we interpret \xNN and \\ groups */
450
451 while (fgets(CS buffer, sizeof(buffer), stdin) != NULL)
452 {
453 line *next;
454 char * d;
455 int n = (int)strlen(CS buffer);
456
457 if (n > 1 && buffer[0] == '>' && buffer[1] == '>')
458 linebuf = 0;
459 while (n > 0 && isspace(buffer[n-1])) n--;
460 buffer[n] = 0;
461 if (strcmp(CS buffer, "++++") == 0) break;
462 next = malloc(sizeof(line) + n);
463 next->next = NULL;
464 d = next->line;
465 {
466 char * s = CS buffer;
467 do
468 {
469 char ch;
470 char cl = *s;
471 if (cl == '\\' && (cl = *++s) == 'x')
472 {
473 if ((ch = *++s - '0') > 9 && (ch -= 'A'-'9'-1) > 15) ch -= 'a'-'A';
474 if ((cl = *++s - '0') > 9 && (cl -= 'A'-'9'-1) > 15) cl -= 'a'-'A';
475 cl |= ch << 4;
476 }
477 *d++ = cl;
478 }
479 while (*s++);
480 }
481 next->len = d - next->line - 1;
482 if (last == NULL) script = last = next;
483 else last->next = next;
484 last = next;
485 }
486
487 fclose(stdin);
488
489 /* SIGALRM handler crashes out */
490
491 signal(SIGALRM, sigalrm_handler);
492
493 /* s points to the current place in the script */
494
495 s = script;
496
497 for (count = 0; count < connection_count; count++)
498 {
499
500 struct {
501 int left;
502 int in_use;
503 } content_length = { .left = 0, .in_use = 0 };
504
505 alarm(timeout);
506 if (port <= 0)
507 {
508 printf("Listening on %s ... ", sockname);
509 fflush(stdout);
510 accept_socket = accept(listen_socket[udn],
511 (struct sockaddr *)&sockun_accepted, &sockun_len);
512 }
513
514 else
515 {
516 int lcount;
517 int max_socket = 0;
518 fd_set select_listen;
519
520 printf("Listening on port %d ... ", port);
521 fflush(stdout);
522
523 FD_ZERO(&select_listen);
524 for (i = 0; i < skn; i++)
525 {
526 if (listen_socket[i] >= 0) FD_SET(listen_socket[i], &select_listen);
527 if (listen_socket[i] > max_socket) max_socket = listen_socket[i];
528 }
529
530 lcount = select(max_socket + 1, &select_listen, NULL, NULL, NULL);
531 if (lcount < 0)
532 {
533 printf("Select failed\n");
534 fflush(stdout);
535 continue;
536 }
537
538 accept_socket = -1;
539 for (i = 0; i < skn; i++)
540 {
541 if (listen_socket[i] > 0 && FD_ISSET(listen_socket[i], &select_listen))
542 {
543 accept_socket = accept(listen_socket[i],
544 (struct sockaddr *)&accepted, &len);
545 FD_CLR(listen_socket[i], &select_listen);
546 break;
547 }
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 != NULL; 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 {
739 if (content_length.in_use) content_length.left--;
740 }
741 } while (!n);
742 else if (memcmp(ss, buffer, n) != 0)
743 {
744 printf("Comparison failed - bailing out\nExpected: ");
745 printit(ss, n);
746 break;
747 }
748 }
749 else
750 {
751 for (;;)
752 {
753 int n;
754 alarm(timeout);
755 if (fgets(CS buffer+offset, sizeof(buffer)-offset, in) == NULL)
756 {
757 printf("%sxpected EOF read from client\n",
758 (strncmp(ss, "*eof", 4) == 0)? "E" : "Une");
759 s = s->next;
760 goto END_OFF;
761 }
762 alarm(0);
763 n = (int)strlen(CS buffer);
764 if (content_length.in_use) content_length.left -= (n - offset);
765 while (n > 0 && isspace(buffer[n-1])) n--;
766 buffer[n] = 0;
767 printf("%s\n", buffer);
768 if (!data || strcmp(CS buffer, ".") == 0) break;
769 }
770
771 if (strncmp(ss, CS buffer, (int)strlen(ss)) != 0)
772 {
773 printf("Comparison failed - bailing out\n");
774 printf("Expected: %s\n", ss);
775 break;
776 }
777 }
778
779 if (sscanf(buffer, "<Content-length: %d", &content_length.left)) content_length.in_use = 1;
780 if (content_length.in_use && content_length.left <= 0) shutdown(dup_accept_socket, SHUT_RD);
781 }
782 }
783
784 END_OFF:
785 fclose(in);
786 fclose(out);
787 }
788
789 if (s == NULL) printf("End of script\n");
790
791 if (sockname != NULL) unlink(sockname);
792 exit(0);
793 }
794
795 /* End of server.c */