/* netcat.c - Forward stdin/stdout to a file or network connection. * * Copyright 2007 Rob Landley <rob@landley.net> * * TODO: udp, ipv6, genericize for telnet/microcom/tail-f USE_NETCAT(OLDTOY(nc, netcat, TOYFLAG_USR|TOYFLAG_BIN)) USE_NETCAT(NEWTOY(netcat, USE_NETCAT_LISTEN("^tlL")"w#p#s:q#f:", TOYFLAG_BIN)) config NETCAT bool "netcat" default y help usage: netcat [-u] [-wpq #] [-s addr] {IPADDR PORTNUM|-f FILENAME} -f use FILENAME (ala /dev/ttyS0) instead of network -p local port number -q SECONDS quit this many seconds after EOF on stdin. -s local ipv4 address -w SECONDS timeout for connection Use "stty 115200 -F /dev/ttyS0 && stty raw -echo -ctlecho" with netcat -f to connect to a serial port. config NETCAT_LISTEN bool "netcat server options (-let)" default y depends on NETCAT depends on TOYBOX_FORK help usage: netcat [-lL COMMAND...] -l listen for one incoming connection. -L listen for multiple incoming connections (server mode). The command line after -l or -L is executed to handle each incoming connection. If none, the connection is forwarded to stdin/stdout. For a quick-and-dirty server, try something like: netcat -s 127.0.0.1 -p 1234 -tL /bin/bash -l config NETCAT_LISTEN_TTY bool default y depends on NETCAT_LISTEN depends on TOYBOX_FORK help usage: netcat [-t] -t allocate tty (must come before -l or -L) */ #define FOR_netcat #include "toys.h" GLOBALS( char *filename; // -f read from filename instead of network long quit_delay; // -q Exit after EOF from stdin after # seconds. char *source_address; // -s Bind to a specific source address. long port; // -p Bind to a specific source port. long wait; // -w Wait # seconds for a connection. ) static void timeout(int signum) { if (TT.wait) error_exit("Timeout"); // This should be xexit() but would need siglongjmp()... exit(0); } static void set_alarm(int seconds) { xsignal(SIGALRM, seconds ? timeout : SIG_DFL); alarm(seconds); } // Translate x.x.x.x numeric IPv4 address, or else DNS lookup an IPv4 name. static void lookup_name(char *name, uint32_t *result) { struct hostent *hostbyname; hostbyname = gethostbyname(name); // getaddrinfo if (!hostbyname) error_exit("no host '%s'", name); *result = *(uint32_t *)*hostbyname->h_addr_list; } // Worry about a fancy lookup later. static void lookup_port(char *str, uint16_t *port) { *port = SWAP_BE16(atoi(str)); } void netcat_main(void) { int sockfd=-1, pollcount=2; struct pollfd pollfds[2]; memset(pollfds, 0, 2*sizeof(struct pollfd)); pollfds[0].events = pollfds[1].events = POLLIN; set_alarm(TT.wait); // The argument parsing logic can't make "<2" conditional on other // arguments like -f and -l, so we do it by hand here. if ((toys.optflags&FLAG_f) ? toys.optc : (!(toys.optflags&(FLAG_l|FLAG_L)) && toys.optc!=2)) help_exit("Argument count wrong"); if (TT.filename) pollfds[0].fd = xopen(TT.filename, O_RDWR); else { int temp; struct sockaddr_in address; // Setup socket sockfd = xsocket(AF_INET, SOCK_STREAM, 0); fcntl(sockfd, F_SETFD, FD_CLOEXEC); temp = 1; setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &temp, sizeof(temp)); memset(&address, 0, sizeof(address)); address.sin_family = AF_INET; if (TT.source_address || TT.port) { address.sin_port = SWAP_BE16(TT.port); if (TT.source_address) lookup_name(TT.source_address, (uint32_t *)&address.sin_addr); if (bind(sockfd, (struct sockaddr *)&address, sizeof(address))) perror_exit("bind"); } // Dial out if (!CFG_NETCAT_LISTEN || !(toys.optflags&(FLAG_L|FLAG_l))) { // Figure out where to dial out to. lookup_name(*toys.optargs, (uint32_t *)&address.sin_addr); lookup_port(toys.optargs[1], &address.sin_port); temp = connect(sockfd, (struct sockaddr *)&address, sizeof(address)); if (temp<0) perror_exit("connect"); pollfds[0].fd = sockfd; // Listen for incoming connections } else { socklen_t len = sizeof(address); if (listen(sockfd, 5)) error_exit("listen"); if (!TT.port) { getsockname(sockfd, (struct sockaddr *)&address, &len); printf("%d\n", SWAP_BE16(address.sin_port)); fflush(stdout); } // Do we need to return immediately because -l has arguments? if ((toys.optflags & FLAG_l) && toys.optc) { if (CFG_TOYBOX_FORK && xfork()) goto cleanup; close(0); close(1); close(2); } for (;;) { pid_t child = 0; // For -l, call accept from the _new_ process. pollfds[0].fd = accept(sockfd, (struct sockaddr *)&address, &len); if (pollfds[0].fd<0) perror_exit("accept"); // Do we need a tty? if (toys.optflags&FLAG_t) child = forkpty(&(pollfds[1].fd), NULL, NULL, NULL); // Do we need to fork and/or redirect for exec? else { if (toys.optflags&FLAG_L) { toys.stacktop = 0; child = vfork(); } if (!child && toys.optc) { int fd = pollfds[0].fd; dup2(fd, 0); dup2(fd, 1); if (toys.optflags&FLAG_L) dup2(fd, 2); if (fd>2) close(fd); } } if (child<0) error_msg("Fork failed\n"); if (child<1) break; close(pollfds[0].fd); } } } // We have a connection. Disarm timeout. // (Does not play well with -L, but what _should_ that do?) set_alarm(0); if (CFG_NETCAT_LISTEN && ((toys.optflags&(FLAG_L|FLAG_l)) && toys.optc)) xexec(toys.optargs); // Poll loop copying stdin->socket and socket->stdout. for (;;) { int i; if (0>poll(pollfds, pollcount, -1)) perror_exit("poll"); for (i=0; i<pollcount; i++) { if (pollfds[i].revents & POLLIN) { int len = read(pollfds[i].fd, toybuf, sizeof(toybuf)); if (len<1) goto dohupnow; xwrite(i ? pollfds[0].fd : 1, toybuf, len); } else if (pollfds[i].revents & POLLHUP) { dohupnow: // Close half-connection. This is needed for things like // "echo GET / | netcat landley.net 80" if (i) { shutdown(pollfds[0].fd, SHUT_WR); pollcount--; set_alarm(TT.quit_delay); } else goto cleanup; } } } cleanup: if (CFG_TOYBOX_FREE) { close(pollfds[0].fd); close(sockfd); } }