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