CRL addition returns count of CRLs added
[exim.git] / test / src / client.c
CommitLineData
c55a77db
PH
1/* A little hacked up program that makes a TCP/IP call and reads a script to
2drive it, for testing Exim server code running as a daemon. It's got a bit
3messy with the addition of support for either OpenSSL or GnuTLS. The code for
4those was hacked out of Exim itself. */
5
6/* ANSI C standard includes */
7
8#include <ctype.h>
9#include <signal.h>
10#include <stdarg.h>
11#include <stddef.h>
12#include <stdio.h>
13#include <stdlib.h>
14#include <string.h>
15#include <time.h>
16
17/* Unix includes */
18
19#include <errno.h>
20#include <dirent.h>
21#include <sys/types.h>
22
23#include <netinet/in_systm.h>
24#include <netinet/in.h>
25#include <netinet/ip.h>
26
27#include <netdb.h>
28#include <arpa/inet.h>
29#include <sys/time.h>
30#include <sys/resource.h>
31#include <sys/socket.h>
32#include <sys/stat.h>
33#include <fcntl.h>
34#include <unistd.h>
35#include <utime.h>
36
37#ifdef AF_INET6
38#define HAVE_IPV6 1
39#endif
40
41#ifndef S_ADDR_TYPE
42#define S_ADDR_TYPE u_long
43#endif
44
45typedef unsigned char uschar;
46
47#define CS (char *)
48#define US (unsigned char *)
49
50#define FALSE 0
51#define TRUE 1
52
53
54
55static int sigalrm_seen = 0;
56
57
58/* TLS support can be optionally included, either for OpenSSL or GnuTLS. The
59latter needs a whole pile of tables. */
60
61#ifdef HAVE_OPENSSL
62#define HAVE_TLS
63#include <openssl/crypto.h>
64#include <openssl/x509.h>
65#include <openssl/pem.h>
66#include <openssl/ssl.h>
67#include <openssl/err.h>
68#include <openssl/rand.h>
69#endif
70
71
72#ifdef HAVE_GNUTLS
73#define HAVE_TLS
74#include <gnutls/gnutls.h>
75#include <gnutls/x509.h>
76
77#define DH_BITS 768
c55a77db
PH
78
79/* Local static variables for GNUTLS */
80
c55a77db
PH
81static gnutls_dh_params dh_params = NULL;
82
83static gnutls_certificate_credentials_t x509_cred = NULL;
84static gnutls_session tls_session = NULL;
85
86static int ssl_session_timeout = 200;
87
88/* Priorities for TLS algorithms to use. */
89
90static const int protocol_priority[16] = { GNUTLS_TLS1, GNUTLS_SSL3, 0 };
91
92static const int kx_priority[16] = {
93 GNUTLS_KX_RSA,
94 GNUTLS_KX_DHE_DSS,
95 GNUTLS_KX_DHE_RSA,
c55a77db
PH
96 0 };
97
98static int default_cipher_priority[16] = {
99 GNUTLS_CIPHER_AES_256_CBC,
100 GNUTLS_CIPHER_AES_128_CBC,
101 GNUTLS_CIPHER_3DES_CBC,
102 GNUTLS_CIPHER_ARCFOUR_128,
103 0 };
104
105static const int mac_priority[16] = {
106 GNUTLS_MAC_SHA,
107 GNUTLS_MAC_MD5,
108 0 };
109
110static const int comp_priority[16] = { GNUTLS_COMP_NULL, 0 };
111static const int cert_type_priority[16] = { GNUTLS_CRT_X509, 0 };
112
113#endif
114
115
116
117
118/*************************************************
119* SIGALRM handler - crash out *
120*************************************************/
121
122static void
123sigalrm_handler_crash(int sig)
124{
125sig = sig; /* Keep picky compilers happy */
126printf("\nClient timed out\n");
127exit(99);
128}
129
130
131/*************************************************
132* SIGALRM handler - set flag *
133*************************************************/
134
135static void
136sigalrm_handler_flag(int sig)
137{
138sig = sig; /* Keep picky compilers happy */
139sigalrm_seen = 1;
140}
141
142
143
144/****************************************************************************/
145/****************************************************************************/
146
147#ifdef HAVE_OPENSSL
148/*************************************************
149* Start an OpenSSL TLS session *
150*************************************************/
151
152int tls_start(int sock, SSL **ssl, SSL_CTX *ctx)
153{
154int rc;
155static const char *sid_ctx = "exim";
156
157RAND_load_file("client.c", -1); /* Not *very* random! */
158
159*ssl = SSL_new (ctx);
160SSL_set_session_id_context(*ssl, sid_ctx, strlen(sid_ctx));
161SSL_set_fd (*ssl, sock);
162SSL_set_connect_state(*ssl);
163
164signal(SIGALRM, sigalrm_handler_flag);
165sigalrm_seen = 0;
166alarm(5);
167rc = SSL_connect (*ssl);
168alarm(0);
169
170if (sigalrm_seen)
171 {
172 printf("SSL_connect timed out\n");
173 return 0;
174 }
175
176if (rc <= 0)
177 {
178 ERR_print_errors_fp(stdout);
179 return 0;
180 }
181
182printf("SSL connection using %s\n", SSL_get_cipher (*ssl));
183return 1;
184}
185
186
187/*************************************************
188* SSL Information callback *
189*************************************************/
190
191static void
192info_callback(SSL *s, int where, int ret)
193{
194where = where;
195ret = ret;
196printf("SSL info: %s\n", SSL_state_string_long(s));
197}
198#endif
199
200
201/****************************************************************************/
202/****************************************************************************/
203
204
205#ifdef HAVE_GNUTLS
206/*************************************************
207* Handle GnuTLS error *
208*************************************************/
209
210/* Called from lots of places when errors occur before actually starting to do
211the TLS handshake, that is, while the session is still in clear.
212
213Argument:
214 prefix prefix text
215 err a GnuTLS error number, or 0 if local error
216
217Returns: doesn't - it dies
218*/
219
220static void
221gnutls_error(uschar *prefix, int err)
222{
bb727765 223fprintf(stderr, "GnuTLS connection error: %s:", prefix);
c55a77db
PH
224if (err != 0) fprintf(stderr, " %s", gnutls_strerror(err));
225fprintf(stderr, "\n");
226exit(1);
227}
228
229
230
231/*************************************************
bb727765 232* Setup up DH parameters *
c55a77db
PH
233*************************************************/
234
235/* For the test suite, the parameters should always be available in the spool
236directory. */
237
238static void
bb727765 239init_dh(void)
c55a77db
PH
240{
241int fd;
242int ret;
243gnutls_datum m;
244uschar filename[200];
245struct stat statbuf;
246
247/* Initialize the data structures for holding the parameters */
248
c55a77db
PH
249ret = gnutls_dh_params_init(&dh_params);
250if (ret < 0) gnutls_error(US"init dh_params", ret);
251
252/* Open the cache file for reading and if successful, read it and set up the
bb727765 253parameters. */
c55a77db
PH
254
255fd = open("aux-fixed/gnutls-params", O_RDONLY, 0);
256if (fd < 0)
257 {
258 fprintf(stderr, "Failed to open spool/gnutls-params: %s\n", strerror(errno));
259 exit(1);
260 }
261
262if (fstat(fd, &statbuf) < 0)
263 {
264 (void)close(fd);
265 return gnutls_error(US"TLS cache stat failed", 0);
266 }
267
268m.size = statbuf.st_size;
269m.data = malloc(m.size);
270if (m.data == NULL)
271 return gnutls_error(US"memory allocation failed", 0);
272if (read(fd, m.data, m.size) != m.size)
273 return gnutls_error(US"TLS cache read failed", 0);
274(void)close(fd);
275
c55a77db
PH
276ret = gnutls_dh_params_import_pkcs3(dh_params, &m, GNUTLS_X509_FMT_PEM);
277if (ret < 0) return gnutls_error(US"DH params import", ret);
278free(m.data);
279}
280
281
282
283
284/*************************************************
285* Initialize for GnuTLS *
286*************************************************/
287
288/*
289Arguments:
290 certificate certificate file
291 privatekey private key file
292*/
293
294static void
295tls_init(uschar *certificate, uschar *privatekey)
296{
297int rc;
298
299rc = gnutls_global_init();
300if (rc < 0) gnutls_error(US"gnutls_global_init", rc);
301
bb727765 302/* Read D-H parameters from the cache file. */
c55a77db 303
bb727765 304init_dh();
c55a77db
PH
305
306/* Create the credentials structure */
307
308rc = gnutls_certificate_allocate_credentials(&x509_cred);
309if (rc < 0) gnutls_error(US"certificate_allocate_credentials", rc);
310
311/* Set the certificate and private keys */
312
313if (certificate != NULL)
314 {
315 rc = gnutls_certificate_set_x509_key_file(x509_cred, CS certificate,
316 CS privatekey, GNUTLS_X509_FMT_PEM);
317 if (rc < 0) gnutls_error("gnutls_certificate", rc);
318 }
319
320/* Associate the parameters with the x509 credentials structure. */
321
322gnutls_certificate_set_dh_params(x509_cred, dh_params);
c55a77db
PH
323}
324
325
326
327/*************************************************
328* Initialize a single GNUTLS session *
329*************************************************/
330
331static gnutls_session
332tls_session_init(void)
333{
334gnutls_session session;
335
336gnutls_init(&session, GNUTLS_CLIENT);
337
338gnutls_cipher_set_priority(session, default_cipher_priority);
339gnutls_compression_set_priority(session, comp_priority);
340gnutls_kx_set_priority(session, kx_priority);
341gnutls_protocol_set_priority(session, protocol_priority);
342gnutls_mac_set_priority(session, mac_priority);
343
344gnutls_cred_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred);
345
346gnutls_dh_set_prime_bits(session, DH_BITS);
347gnutls_db_set_cache_expiration(session, ssl_session_timeout);
348
349return session;
350}
351#endif
352
353
354/****************************************************************************/
355/****************************************************************************/
356
357
358
359
360/*************************************************
361* Main Program *
362*************************************************/
363
4fe99a6c
PP
364const char * const HELP_MESSAGE = "\n\
365Usage: client\n\
366 <IP address>\n\
367 <port>\n\
368 [<outgoing interface>]\n\
369 [<cert file>]\n\
370 [<key file>]\n\
371\n";
c55a77db
PH
372
373int main(int argc, char **argv)
374{
375struct sockaddr *s_ptr;
376struct sockaddr_in s_in4;
377char *interface = NULL;
378char *address = NULL;
379char *certfile = NULL;
380char *keyfile = NULL;
381int argi = 1;
382int host_af, port, s_len, rc, sock, save_errno;
383int timeout = 1;
384int tls_active = 0;
385int sent_starttls = 0;
386int tls_on_connect = 0;
387
388#if HAVE_IPV6
389struct sockaddr_in6 s_in6;
390#endif
391
392#ifdef HAVE_OPENSSL
393SSL_CTX* ctx;
394SSL* ssl;
395#endif
396
397unsigned char outbuffer[10240];
398unsigned char inbuffer[10240];
399unsigned char *inptr = inbuffer;
400
401*inptr = 0; /* Buffer empty */
402
403/* Options */
404
405while (argc >= argi + 1 && argv[argi][0] == '-')
406 {
4fe99a6c
PP
407 if (strcmp(argv[argi], "-help") == 0 ||
408 strcmp(argv[argi], "--help") == 0 ||
409 strcmp(argv[argi], "-h") == 0)
410 {
411 printf(HELP_MESSAGE);
412 exit(0);
413 }
c55a77db
PH
414 if (strcmp(argv[argi], "-tls-on-connect") == 0)
415 {
416 tls_on_connect = 1;
417 argi++;
418 }
419 else if (argv[argi][1] == 't' && isdigit(argv[argi][2]))
420 {
421 timeout = atoi(argv[argi]+1);
422 argi++;
423 }
424 else
425 {
426 printf("Unrecognized option %s\n", argv[argi]);
427 exit(1);
428 }
429 }
430
431/* Mandatory 1st arg is IP address */
432
433if (argc < argi+1)
434 {
435 printf("No IP address given\n");
436 exit(1);
437 }
438
439address = argv[argi++];
440host_af = (strchr(address, ':') != NULL)? AF_INET6 : AF_INET;
441
442/* Mandatory 2nd arg is port */
443
444if (argc < argi+1)
445 {
446 printf("No port number given\n");
447 exit(1);
448 }
449
450port = atoi(argv[argi++]);
451
452/* Optional next arg is interface */
453
454if (argc > argi &&
455 (isdigit((unsigned char)argv[argi][0]) || argv[argi][0] == ':'))
456 interface = argv[argi++];
457
458/* Any more arguments are the name of a certificate file and key file */
459
460if (argc > argi) certfile = argv[argi++];
461if (argc > argi) keyfile = argv[argi++];
462
463
464#if HAVE_IPV6
465/* For an IPv6 address, use an IPv6 sockaddr structure. */
466
467if (host_af == AF_INET6)
468 {
469 s_ptr = (struct sockaddr *)&s_in6;
470 s_len = sizeof(s_in6);
471 }
472else
473#endif
474
475/* For an IPv4 address, use an IPv4 sockaddr structure,
476even on an IPv6 system. */
477
478 {
479 s_ptr = (struct sockaddr *)&s_in4;
480 s_len = sizeof(s_in4);
481 }
482
483printf("Connecting to %s port %d ... ", address, port);
484
485sock = socket(host_af, SOCK_STREAM, 0);
486if (sock < 0)
487 {
488 printf("socket creation failed: %s\n", strerror(errno));
489 exit(1);
490 }
491
492/* Bind to a specific interface if requested. On an IPv6 system, this has
493to be of the same family as the address we are calling. On an IPv4 system the
494test is redundant, but it keeps the code tidier. */
495
496if (interface != NULL)
497 {
498 int interface_af = (strchr(interface, ':') != NULL)? AF_INET6 : AF_INET;
499
500 if (interface_af == host_af)
501 {
502 #if HAVE_IPV6
503
504 /* Set up for IPv6 binding */
505
506 if (host_af == AF_INET6)
507 {
508 memset(&s_in6, 0, sizeof(s_in6));
509 s_in6.sin6_family = AF_INET6;
510 s_in6.sin6_port = 0;
511 if (inet_pton(AF_INET6, interface, &s_in6.sin6_addr) != 1)
512 {
513 printf("Unable to parse \"%s\"", interface);
514 exit(1);
515 }
516 }
517 else
518 #endif
519
520 /* Set up for IPv4 binding */
521
522 {
523 memset(&s_in4, 0, sizeof(s_in4));
524 s_in4.sin_family = AF_INET;
525 s_in4.sin_port = 0;
526 s_in4.sin_addr.s_addr = (S_ADDR_TYPE)inet_addr(interface);
527 }
528
529 /* Bind */
530
531 if (bind(sock, s_ptr, s_len) < 0)
532 {
533 printf("Unable to bind outgoing SMTP call to %s: %s",
534 interface, strerror(errno));
535 exit(1);
536 }
537 }
538 }
539
540/* Set up a remote IPv6 address */
541
542#if HAVE_IPV6
543if (host_af == AF_INET6)
544 {
545 memset(&s_in6, 0, sizeof(s_in6));
546 s_in6.sin6_family = AF_INET6;
547 s_in6.sin6_port = htons(port);
548 if (inet_pton(host_af, address, &s_in6.sin6_addr) != 1)
549 {
550 printf("Unable to parse \"%s\"", address);
551 exit(1);
552 }
553 }
554else
555#endif
556
557/* Set up a remote IPv4 address */
558
559 {
560 memset(&s_in4, 0, sizeof(s_in4));
561 s_in4.sin_family = AF_INET;
562 s_in4.sin_port = htons(port);
563 s_in4.sin_addr.s_addr = (S_ADDR_TYPE)inet_addr(address);
564 }
565
566/* SIGALRM handler crashes out */
567
568signal(SIGALRM, sigalrm_handler_crash);
569alarm(timeout);
570rc = connect(sock, s_ptr, s_len);
571save_errno = errno;
572alarm(0);
573
574/* A failure whose error code is "Interrupted system call" is in fact
575an externally applied timeout if the signal handler has been run. */
576
577if (rc < 0)
578 {
579 close(sock);
580 printf("failed: %s\n", strerror(save_errno));
581 exit(1);
582 }
583
584printf("connected\n");
585
586
587/* --------------- Set up for OpenSSL --------------- */
588
589#ifdef HAVE_OPENSSL
590SSL_library_init();
591SSL_load_error_strings();
592
593ctx = SSL_CTX_new(SSLv23_method());
594if (ctx == NULL)
595 {
596 printf ("SSL_CTX_new failed\n");
597 exit(1);
598 }
599
600if (certfile != NULL)
601 {
602 if (!SSL_CTX_use_certificate_file(ctx, certfile, SSL_FILETYPE_PEM))
603 {
604 printf("SSL_CTX_use_certificate_file failed\n");
605 exit(1);
606 }
607 printf("Certificate file = %s\n", certfile);
608 }
609
610if (keyfile != NULL)
611 {
612 if (!SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM))
613 {
614 printf("SSL_CTX_use_PrivateKey_file failed\n");
615 exit(1);
616 }
617 printf("Key file = %s\n", keyfile);
618 }
619
620SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_BOTH);
621SSL_CTX_set_timeout(ctx, 200);
622SSL_CTX_set_info_callback(ctx, (void (*)())info_callback);
623#endif
624
625
626/* --------------- Set up for GnuTLS --------------- */
627
628#ifdef HAVE_GNUTLS
629if (certfile != NULL) printf("Certificate file = %s\n", certfile);
630if (keyfile != NULL) printf("Key file = %s\n", keyfile);
631tls_init(certfile, keyfile);
632tls_session = tls_session_init();
633gnutls_transport_set_ptr(tls_session, (gnutls_transport_ptr)sock);
634
635/* When the server asks for a certificate and the client does not have one,
636there is a SIGPIPE error in the gnutls_handshake() function for some reason
637that is not understood. As luck would have it, this has never hit Exim itself
638because it ignores SIGPIPE errors. Doing the same here allows it all to work as
639one wants. */
640
641signal(SIGPIPE, SIG_IGN);
642#endif
643
644/* ---------------------------------------------- */
645
646
647/* Start TLS session if configured to do so without STARTTLS */
648
649#ifdef HAVE_TLS
650if (tls_on_connect)
651 {
652 printf("Attempting to start TLS\n");
653
654 #ifdef HAVE_OPENSSL
655 tls_active = tls_start(sock, &ssl, ctx);
656 #endif
657
658 #ifdef HAVE_GNUTLS
659 sigalrm_seen = FALSE;
660 alarm(timeout);
661 tls_active = gnutls_handshake(tls_session) >= 0;
662 alarm(0);
663 #endif
664
665 if (!tls_active)
666 printf("Failed to start TLS\n");
667 else
668 printf("Succeeded in starting TLS\n");
669 }
670#endif
671
672while (fgets(outbuffer, sizeof(outbuffer), stdin) != NULL)
673 {
674 int n = (int)strlen(outbuffer);
675 while (n > 0 && isspace(outbuffer[n-1])) n--;
676 outbuffer[n] = 0;
677
678 /* Expect incoming */
679
680 if (strncmp(outbuffer, "??? ", 4) == 0)
681 {
682 unsigned char *lineptr;
683 printf("%s\n", outbuffer);
684
685 if (*inptr == 0) /* Refill input buffer */
686 {
687 if (tls_active)
688 {
689 #ifdef HAVE_OPENSSL
690 rc = SSL_read (ssl, inbuffer, sizeof(inbuffer) - 1);
691 #endif
692 #ifdef HAVE_GNUTLS
693 rc = gnutls_record_recv(tls_session, CS inbuffer, sizeof(inbuffer) - 1);
694 #endif
695 }
696 else
697 {
698 alarm(timeout);
699 rc = read(sock, inbuffer, sizeof(inbuffer));
700 alarm(0);
701 }
702
703 if (rc < 0)
704 {
705 printf("Read error %s\n", strerror(errno));
706 exit(1) ;
707 }
708 else if (rc == 0)
709 {
710 printf("Unexpected EOF read\n");
711 close(sock);
712 exit(1);
713 }
714 else
715 {
716 inbuffer[rc] = 0;
717 inptr = inbuffer;
718 }
719 }
720
721 lineptr = inptr;
722 while (*inptr != 0 && *inptr != '\r' && *inptr != '\n') inptr++;
723 if (*inptr != 0)
724 {
725 *inptr++ = 0;
726 if (*inptr == '\n') inptr++;
727 }
728
729 printf("<<< %s\n", lineptr);
730 if (strncmp(lineptr, outbuffer + 4, (int)strlen(outbuffer) - 4) != 0)
731 {
732 printf("\n******** Input mismatch ********\n");
733 exit(1);
734 }
735
736 #ifdef HAVE_TLS
737 if (sent_starttls)
738 {
739 if (lineptr[0] == '2')
740 {
741 printf("Attempting to start TLS\n");
742 fflush(stdout);
743
744 #ifdef HAVE_OPENSSL
745 tls_active = tls_start(sock, &ssl, ctx);
746 #endif
747
748 #ifdef HAVE_GNUTLS
749 sigalrm_seen = FALSE;
750 alarm(timeout);
751 tls_active = gnutls_handshake(tls_session) >= 0;
752 alarm(0);
753 #endif
754
755 if (!tls_active)
756 {
757 printf("Failed to start TLS\n");
758 fflush(stdout);
759 }
760 else
761 printf("Succeeded in starting TLS\n");
762 }
763 else printf("Abandoning TLS start attempt\n");
764 }
765 sent_starttls = 0;
766 #endif
767 }
768
769 /* Wait for a bit before proceeding */
770
771 else if (strncmp(outbuffer, "+++ ", 4) == 0)
772 {
773 printf("%s\n", outbuffer);
774 sleep(atoi(outbuffer + 4));
775 }
776
777 /* Send outgoing, but barf if unconsumed incoming */
778
779 else
780 {
781 unsigned char *escape;
782
783 if (*inptr != 0)
784 {
785 printf("Unconsumed input: %s", inptr);
786 printf(" About to send: %s\n", outbuffer);
787 exit(1);
788 }
789
790 #ifdef HAVE_TLS
791
792 /* Shutdown TLS */
793
794 if (strcmp(outbuffer, "stoptls") == 0 ||
795 strcmp(outbuffer, "STOPTLS") == 0)
796 {
797 if (!tls_active)
798 {
799 printf("STOPTLS read when TLS not active\n");
800 exit(1);
801 }
802 printf("Shutting down TLS encryption\n");
803
804 #ifdef HAVE_OPENSSL
805 SSL_shutdown(ssl);
806 SSL_free(ssl);
807 #endif
808
809 #ifdef HAVE_GNUTLS
810 gnutls_bye(tls_session, GNUTLS_SHUT_WR);
811 gnutls_deinit(tls_session);
812 tls_session = NULL;
813 gnutls_global_deinit();
814 #endif
815
816 tls_active = 0;
817 continue;
818 }
819
820 /* Remember that we sent STARTTLS */
821
822 sent_starttls = (strcmp(outbuffer, "starttls") == 0 ||
823 strcmp(outbuffer, "STARTTLS") == 0);
824
825 /* Fudge: if the command is "starttls_wait", we send the starttls bit,
826 but we haven't set the flag, so that there is no negotiation. This is for
827 testing the server's timeout. */
828
829 if (strcmp(outbuffer, "starttls_wait") == 0)
830 {
831 outbuffer[8] = 0;
832 n = 8;
833 }
834 #endif
835
836 printf(">>> %s\n", outbuffer);
837 strcpy(outbuffer + n, "\r\n");
838
839 /* Turn "\n" and "\r" into the relevant characters. This is a hack. */
840
841 while ((escape = strstr(outbuffer, "\\r")) != NULL)
842 {
843 *escape = '\r';
844 memmove(escape + 1, escape + 2, (n + 2) - (escape - outbuffer) - 2);
845 n--;
846 }
847
848 while ((escape = strstr(outbuffer, "\\n")) != NULL)
849 {
850 *escape = '\n';
851 memmove(escape + 1, escape + 2, (n + 2) - (escape - outbuffer) - 2);
852 n--;
853 }
854
855 /* OK, do it */
856
857 alarm(timeout);
858 if (tls_active)
859 {
860 #ifdef HAVE_OPENSSL
861 rc = SSL_write (ssl, outbuffer, n + 2);
862 #endif
863 #ifdef HAVE_GNUTLS
864 rc = gnutls_record_send(tls_session, CS outbuffer, n + 2);
865 if (rc < 0)
866 {
867 printf("GnuTLS write error: %s\n", gnutls_strerror(rc));
868 exit(1);
869 }
870 #endif
871 }
872 else
873 {
874 rc = write(sock, outbuffer, n + 2);
875 }
876 alarm(0);
877
878 if (rc < 0)
879 {
880 printf("Write error: %s\n", strerror(errno));
881 exit(1);
882 }
883 }
884 }
885
886printf("End of script\n");
887close(sock);
888
889exit(0);
890}
891
892/* End of client.c */