tidying
[users/jgh/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 */