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