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