4bdde65bf7154bfa7679e687c2b2b255ae32f3f9
[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
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 */