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.
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. */
9 /* ANSI C standard includes */
24 #include <sys/types.h>
26 #include <netinet/in_systm.h>
27 #include <netinet/in.h>
28 #include <netinet/ip.h>
30 #ifdef HAVE_NETINET_IP_VAR_H
31 #include <netinet/ip_var.h>
35 #include <arpa/inet.h>
37 #include <sys/resource.h>
38 #include <sys/socket.h>
50 #define S_ADDR_TYPE u_long
60 /*************************************************
61 * SIGALRM handler - crash out *
62 *************************************************/
65 sigalrm_handler(int sig)
67 sig = sig; /* Keep picky compilers happy */
68 printf("\nServer timed out\n");
73 /*************************************************
74 * Get textual IP address *
75 *************************************************/
77 /* This function is copied from Exim */
80 host_ntoa(const void *arg, char *buffer)
84 /* The new world. It is annoying that we have to fish out the address from
85 different places in the block, depending on what kind of address it is. It
86 is also a pain that inet_ntop() returns a const char *, whereas the IPv4
87 function inet_ntoa() returns just char *, and some picky compilers insist
88 on warning if one assigns a const char * to a char *. Hence the casts. */
92 int family = ((struct sockaddr *)arg)->sa_family;
93 if (family == AF_INET6)
95 struct sockaddr_in6 *sk = (struct sockaddr_in6 *)arg;
96 yield = (char *)inet_ntop(family, &(sk->sin6_addr), addr_buffer,
101 struct sockaddr_in *sk = (struct sockaddr_in *)arg;
102 yield = (char *)inet_ntop(family, &(sk->sin_addr), addr_buffer,
103 sizeof(addr_buffer));
106 /* If the result is a mapped IPv4 address, show it in V4 format. */
108 if (strncmp(yield, "::ffff:", 7) == 0) yield += 7;
110 #else /* HAVE_IPV6 */
114 yield = inet_ntoa(((struct sockaddr_in *)arg)->sin_addr);
117 strcpy(buffer, yield);
122 /*************************************************
124 *************************************************/
126 #define v6n 0 /* IPv6 socket number */
127 #define v4n 1 /* IPv4 socket number */
128 #define udn 2 /* Unix domain socket number */
129 #define skn 2 /* Potential number of sockets */
131 int main(int argc, char **argv)
135 int listen_socket[3] = { -1, -1, -1 };
137 int dup_accept_socket;
138 int connection_count = 1;
151 char *sockname = NULL;
152 unsigned char buffer[10240];
154 struct sockaddr_un sockun; /* don't use "sun" */
155 struct sockaddr_un sockun_accepted;
156 int sockun_len = sizeof(sockun_accepted);
159 struct sockaddr_in6 sin6;
160 struct sockaddr_in6 accepted;
161 struct in6_addr anyaddr6 = IN6ADDR_ANY_INIT ;
163 struct sockaddr_in accepted;
166 /* Always need an IPv4 structure */
168 struct sockaddr_in sin4;
170 int len = sizeof(accepted);
173 /* Sort out the arguments */
175 while (na < argc && argv[na][0] == '-')
177 if (strcmp(argv[na], "-d") == 0) debug = 1;
178 else if (strcmp(argv[na], "-t") == 0) timeout = atoi(argv[++na]);
179 else if (strcmp(argv[na], "-noipv4") == 0) use_ipv4 = 0;
180 else if (strcmp(argv[na], "-noipv6") == 0) use_ipv6 = 0;
183 printf("server: unknown option %s\n", argv[na]);
189 if (!use_ipv4 && !use_ipv6)
191 printf("server: -noipv4 and -noipv6 cannot both be given\n");
197 printf("server: no port number or socket name given\n");
201 if (argv[na][0] == '/')
204 unlink(sockname); /* in case left lying around */
206 else port = atoi(argv[na]);
209 if (na < argc) connection_count = atoi(argv[na]);
214 if (port == 0) /* Unix domain */
216 if (debug) printf("Creating Unix domain socket\n");
217 listen_socket[udn] = socket(PF_UNIX, SOCK_STREAM, 0);
218 if (listen_socket[udn] < 0)
220 printf("Unix domain socket creation failed: %s\n", strerror(errno));
229 if (debug) printf("Creating IPv6 socket\n");
230 listen_socket[v6n] = socket(AF_INET6, SOCK_STREAM, 0);
231 if (listen_socket[v6n] < 0)
233 printf("IPv6 socket creation failed: %s\n", strerror(errno));
237 /* If this is an IPv6 wildcard socket, set IPV6_V6ONLY if that option is
241 if (setsockopt(listen_socket[v6n], IPPROTO_IPV6, IPV6_V6ONLY, (char *)(&on),
243 printf("Setting IPV6_V6ONLY on IPv6 wildcard "
244 "socket failed (%s): carrying on without it\n", strerror(errno));
245 #endif /* IPV6_V6ONLY */
247 #endif /* HAVE_IPV6 */
249 /* Create an IPv4 socket if required */
253 if (debug) printf("Creating IPv4 socket\n");
254 listen_socket[v4n] = socket(AF_INET, SOCK_STREAM, 0);
255 if (listen_socket[v4n] < 0)
257 printf("IPv4 socket creation failed: %s\n", strerror(errno));
264 /* Set SO_REUSEADDR on the IP sockets so that the program can be restarted
265 while a connection is being handled - this can happen as old connections lie
266 around for a bit while crashed processes are tidied away. Without this, a
267 connection will prevent reuse of the smtp port for listening. */
269 for (i = v6n; i <= v4n; i++)
271 if (listen_socket[i] >= 0 &&
272 setsockopt(listen_socket[i], SOL_SOCKET, SO_REUSEADDR, (char *)(&on),
275 printf("setting SO_REUSEADDR on socket failed: %s\n", strerror(errno));
281 /* Now bind the sockets to the required port or path. If a path, ensure
282 anyone can write to it. */
287 sockun.sun_family = AF_UNIX;
288 if (debug) printf("Binding Unix domain socket\n");
289 sprintf(sockun.sun_path, "%.*s", (int)(sizeof(sockun.sun_path)-1), sockname);
290 if (bind(listen_socket[udn], (struct sockaddr *)&sockun, sizeof(sockun)) < 0)
292 printf("Unix domain socket bind() failed: %s\n", strerror(errno));
295 (void)stat(sockname, &statbuf);
296 if (debug) printf("Setting Unix domain socket mode: %0x\n",
297 statbuf.st_mode | 0777);
298 if (chmod(sockname, statbuf.st_mode | 0777) < 0)
300 printf("Unix domain socket chmod() failed: %s\n", strerror(errno));
307 for (i = 0; i < skn; i++)
309 if (listen_socket[i] < 0) continue;
311 /* For an IPv6 listen, use an IPv6 socket */
316 memset(&sin6, 0, sizeof(sin6));
317 sin6.sin6_family = AF_INET6;
318 sin6.sin6_port = htons(port);
319 sin6.sin6_addr = anyaddr6;
320 if (bind(listen_socket[i], (struct sockaddr *)&sin6, sizeof(sin6)) < 0)
322 printf("IPv6 socket bind() failed: %s\n", strerror(errno));
329 /* For an IPv4 bind, use an IPv4 socket, even in an IPv6 world. If an IPv4
330 bind fails EADDRINUSE after IPv6 success, carry on, because it means the
331 IPv6 socket will handle IPv4 connections. */
334 memset(&sin4, 0, sizeof(sin4));
335 sin4.sin_family = AF_INET;
336 sin4.sin_addr.s_addr = (S_ADDR_TYPE)INADDR_ANY;
337 sin4.sin_port = htons(port);
338 if (bind(listen_socket[i], (struct sockaddr *)&sin4, sizeof(sin4)) < 0)
340 if (listen_socket[v6n] < 0 || errno != EADDRINUSE)
342 printf("IPv4 socket bind() failed: %s\n", strerror(errno));
347 close(listen_socket[i]);
348 listen_socket[i] = -1;
356 /* Start listening. If IPv4 fails EADDRINUSE after IPv6 succeeds, ignore the
357 error because it means that the IPv6 socket will handle IPv4 connections. Don't
358 output anything, because it will mess up the test output, which will be
359 different for systems that do this and those that don't. */
361 for (i = 0; i <= skn; i++)
363 if (listen_socket[i] >= 0 && listen(listen_socket[i], 5) < 0)
365 if (i != v4n || listen_socket[v6n] < 0 || errno != EADDRINUSE)
367 printf("listen() failed: %s\n", strerror(errno));
374 /* This program handles only a fixed number of connections, in sequence. Before
375 waiting for the first connection, read the standard input, which contains the
376 script of things to do. A line containing "++++" is treated as end of file.
377 This is so that the Perl driving script doesn't have to close the pipe -
378 because that would cause it to wait for this process, which it doesn't yet want
379 to do. The driving script adds the "++++" automatically - it doesn't actually
380 appear in the test script. */
382 while (fgets(buffer, sizeof(buffer), stdin) != NULL)
385 int n = (int)strlen(buffer);
386 while (n > 0 && isspace(buffer[n-1])) n--;
388 if (strcmp(buffer, "++++") == 0) break;
389 next = malloc(sizeof(line) + n);
391 strcpy(next->line, buffer);
392 if (last == NULL) script = last = next;
393 else last->next = next;
399 /* SIGALRM handler crashes out */
401 signal(SIGALRM, sigalrm_handler);
403 /* s points to the current place in the script */
407 for (count = 0; count < connection_count; count++)
412 printf("Listening on %s ... ", sockname);
414 accept_socket = accept(listen_socket[udn],
415 (struct sockaddr *)&sockun_accepted, &sockun_len);
422 fd_set select_listen;
424 printf("Listening on port %d ... ", port);
427 FD_ZERO(&select_listen);
428 for (i = 0; i < skn; i++)
430 if (listen_socket[i] >= 0) FD_SET(listen_socket[i], &select_listen);
431 if (listen_socket[i] > max_socket) max_socket = listen_socket[i];
434 lcount = select(max_socket + 1, &select_listen, NULL, NULL, NULL);
437 printf("Select failed\n");
443 for (i = 0; i < skn; i++)
445 if (listen_socket[i] > 0 && FD_ISSET(listen_socket[i], &select_listen))
447 accept_socket = accept(listen_socket[i],
448 (struct sockaddr *)&accepted, &len);
449 FD_CLR(listen_socket[i], &select_listen);
456 if (accept_socket < 0)
458 printf("accept() failed: %s\n", strerror(errno));
462 out = fdopen(accept_socket, "w");
464 dup_accept_socket = dup(accept_socket);
467 printf("\nConnection request from [%s]\n", host_ntoa(&accepted, buffer));
470 printf("\nConnection request\n");
472 /* Linux supports a feature for acquiring the peer's credentials, but it
473 appears to be Linux-specific. This code is untested and unused, just
474 saved here for reference. */
476 /**********--------------------
480 if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &cl)==0) {
481 printf("Peer's pid=%d, uid=%d, gid=%d\n",
482 cr.pid, cr.uid, cr.gid);
483 --------------*****************/
486 if (dup_accept_socket < 0)
488 printf("Couldn't dup socket descriptor\n");
489 printf("421 Connection refused: %s\n", strerror(errno));
490 fprintf(out, "421 Connection refused: %s\r\n", strerror(errno));
495 in = fdopen(dup_accept_socket, "r");
497 /* Loop for handling the conversation(s). For use in SMTP sessions, there are
498 default rules for determining input and output lines: the latter start with
499 digits. This means that the input looks like SMTP dialog. However, this
500 doesn't work for other tests (e.g. ident tests) so we have explicit '<' and
501 '>' flags for input and output as well as the defaults. */
503 for (; s != NULL; s = s->next)
507 /* Output lines either start with '>' or a digit. In the '>' case we can
508 fudge the sending of \r\n as required. Default is \r\n, ">>" send nothing,
509 ">CR>" sends \r only, and ">LF>" sends \n only. We can also force a
510 connection closedown by ">*eof". */
515 printf("%s\n", ss++);
517 if (strncmp(ss, "*eof", 4) == 0)
525 else if (strncmp(ss, "CR>", 3) == 0)
526 { end = "\r"; ss += 3; }
527 else if (strncmp(ss, "LF>", 3) == 0)
528 { end = "\n"; ss += 3; }
530 fprintf(out, "%s%s", ss, end);
533 else if (isdigit((unsigned char)ss[0]))
536 fprintf(out, "%s\r\n", ss);
539 /* If the script line starts with "*sleep" we just sleep for a while
540 before continuing. */
542 else if (strncmp(ss, "*sleep ", 7) == 0)
544 int sleepfor = atoi(ss+7);
550 /* Otherwise the script line is the start of an input line we are expecting
551 from the client, or "*eof" indicating we expect the client to close the
552 connection. Read command line or data lines; the latter are indicated
553 by the expected line being just ".". If the line starts with '<', that
554 doesn't form part of the expected input. (This allows for incoming data
555 starting with a digit.) */
560 int data = strcmp(ss, ".") == 0;
575 if (fgets(buffer+offset, sizeof(buffer)-offset, in) == NULL)
577 printf("%sxpected EOF read from client\n",
578 (strncmp(ss, "*eof", 4) == 0)? "E" : "Une");
583 n = (int)strlen(buffer);
584 while (n > 0 && isspace(buffer[n-1])) n--;
586 printf("%s\n", buffer);
587 if (!data || strcmp(buffer, ".") == 0) break;
590 if (strncmp(ss, buffer, (int)strlen(ss)) != 0)
592 printf("Comparison failed - bailing out\n");
593 printf("Expected: %s\n", ss);
604 if (s == NULL) printf("End of script\n");
606 if (sockname != NULL) unlink(sockname);
610 /* End of server.c */