1/* netcat.c - Forward stdin/stdout to a file or network connection.
2 *
3 * Copyright 2007 Rob Landley <rob@landley.net>
4 *
5 * TODO: udp, ipv6, genericize for telnet/microcom/tail-f
6
7USE_NETCAT(OLDTOY(nc, netcat, TOYFLAG_USR|TOYFLAG_BIN))
8USE_NETCAT(NEWTOY(netcat, USE_NETCAT_LISTEN("^tlL")"w#p#s:q#f:", TOYFLAG_BIN))
9
10config NETCAT
11  bool "netcat"
12  default y
13  help
14    usage: netcat [-u] [-wpq #] [-s addr] {IPADDR PORTNUM|-f FILENAME}
15
16    -f	use FILENAME (ala /dev/ttyS0) instead of network
17    -p	local port number
18    -q	SECONDS quit this many seconds after EOF on stdin.
19    -s	local ipv4 address
20    -w	SECONDS timeout for connection
21
22    Use "stty 115200 -F /dev/ttyS0 && stty raw -echo -ctlecho" with
23    netcat -f to connect to a serial port.
24
25config NETCAT_LISTEN
26  bool "netcat server options (-let)"
27  default y
28  depends on NETCAT
29  help
30    usage: netcat [-t] [-lL COMMAND...]
31
32    -t	allocate tty (must come before -l or -L)
33    -l	listen for one incoming connection.
34    -L	listen for multiple incoming connections (server mode).
35
36    The command line after -l or -L is executed to handle each incoming
37    connection. If none, the connection is forwarded to stdin/stdout.
38
39    For a quick-and-dirty server, try something like:
40    netcat -s 127.0.0.1 -p 1234 -tL /bin/bash -l
41*/
42
43#define FOR_netcat
44#include "toys.h"
45
46GLOBALS(
47  char *filename;        // -f read from filename instead of network
48  long quit_delay;       // -q Exit after EOF from stdin after # seconds.
49  char *source_address;  // -s Bind to a specific source address.
50  long port;             // -p Bind to a specific source port.
51  long wait;             // -w Wait # seconds for a connection.
52)
53
54static void timeout(int signum)
55{
56  if (TT.wait) error_exit("Timeout");
57  // This should be xexit() but would need siglongjmp()...
58  exit(0);
59}
60
61static void set_alarm(int seconds)
62{
63  xsignal(SIGALRM, seconds ? timeout : SIG_DFL);
64  alarm(seconds);
65}
66
67// Translate x.x.x.x numeric IPv4 address, or else DNS lookup an IPv4 name.
68static void lookup_name(char *name, uint32_t *result)
69{
70  struct hostent *hostbyname;
71
72  hostbyname = gethostbyname(name); // getaddrinfo
73  if (!hostbyname) error_exit("no host '%s'", name);
74  *result = *(uint32_t *)*hostbyname->h_addr_list;
75}
76
77// Worry about a fancy lookup later.
78static void lookup_port(char *str, uint16_t *port)
79{
80  *port = SWAP_BE16(atoi(str));
81}
82
83void netcat_main(void)
84{
85  int sockfd=-1, pollcount=2;
86  struct pollfd pollfds[2];
87
88  memset(pollfds, 0, 2*sizeof(struct pollfd));
89  pollfds[0].events = pollfds[1].events = POLLIN;
90  set_alarm(TT.wait);
91
92  // The argument parsing logic can't make "<2" conditional on other
93  // arguments like -f and -l, so we do it by hand here.
94  if (toys.optflags&FLAG_f) {
95    if (toys.optc) toys.exithelp++;
96  } else if (!(toys.optflags&(FLAG_l|FLAG_L)) && toys.optc!=2) toys.exithelp++;
97
98  if (toys.exithelp) error_exit("Argument count wrong");
99
100  if (TT.filename) pollfds[0].fd = xopen(TT.filename, O_RDWR);
101  else {
102    int temp;
103    struct sockaddr_in address;
104
105    // Setup socket
106    sockfd = xsocket(AF_INET, SOCK_STREAM, 0);
107    fcntl(sockfd, F_SETFD, FD_CLOEXEC);
108    temp = 1;
109    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &temp, sizeof(temp));
110    memset(&address, 0, sizeof(address));
111    address.sin_family = AF_INET;
112    if (TT.source_address || TT.port) {
113      address.sin_port = SWAP_BE16(TT.port);
114      if (TT.source_address)
115        lookup_name(TT.source_address, (uint32_t *)&address.sin_addr);
116      if (bind(sockfd, (struct sockaddr *)&address, sizeof(address)))
117        perror_exit("bind");
118    }
119
120    // Dial out
121
122    if (!CFG_NETCAT_LISTEN || !(toys.optflags&(FLAG_L|FLAG_l))) {
123      // Figure out where to dial out to.
124      lookup_name(*toys.optargs, (uint32_t *)&address.sin_addr);
125      lookup_port(toys.optargs[1], &address.sin_port);
126      temp = connect(sockfd, (struct sockaddr *)&address, sizeof(address));
127      if (temp<0) perror_exit("connect");
128      pollfds[0].fd = sockfd;
129
130    // Listen for incoming connections
131
132    } else {
133      socklen_t len = sizeof(address);
134
135      if (listen(sockfd, 5)) error_exit("listen");
136      if (!TT.port) {
137        getsockname(sockfd, (struct sockaddr *)&address, &len);
138        printf("%d\n", SWAP_BE16(address.sin_port));
139        fflush(stdout);
140      }
141      // Do we need to return immediately because -l has arguments?
142
143      if ((toys.optflags & FLAG_l) && toys.optc) {
144        if (xfork()) goto cleanup;
145        close(0);
146        close(1);
147        close(2);
148      }
149
150      for (;;) {
151        pid_t child = 0;
152
153        // For -l, call accept from the _new_ process.
154
155        pollfds[0].fd = accept(sockfd, (struct sockaddr *)&address, &len);
156        if (pollfds[0].fd<0) perror_exit("accept");
157
158        // Do we need a tty?
159
160        if (toys.optflags&FLAG_t)
161          child = forkpty(&(pollfds[1].fd), NULL, NULL, NULL);
162
163        // Do we need to fork and/or redirect for exec?
164
165        else {
166          if (toys.optflags&FLAG_L) child = fork();
167          if (!child && toys.optc) {
168            int fd = pollfds[0].fd;
169
170            dup2(fd, 0);
171            dup2(fd, 1);
172            if (toys.optflags&FLAG_L) dup2(fd, 2);
173            if (fd>2) close(fd);
174          }
175        }
176
177        if (child<0) error_msg("Fork failed\n");
178        if (child<1) break;
179        close(pollfds[0].fd);
180      }
181    }
182  }
183
184  // We have a connection.  Disarm timeout.
185  // (Does not play well with -L, but what _should_ that do?)
186  set_alarm(0);
187
188  if (CFG_NETCAT_LISTEN && (toys.optflags&(FLAG_L|FLAG_l) && toys.optc))
189    xexec(toys.optargs);
190
191  // Poll loop copying stdin->socket and socket->stdout.
192  for (;;) {
193    int i;
194
195    if (0>poll(pollfds, pollcount, -1)) perror_exit("poll");
196
197    for (i=0; i<pollcount; i++) {
198      if (pollfds[i].revents & POLLIN) {
199        int len = read(pollfds[i].fd, toybuf, sizeof(toybuf));
200        if (len<1) goto dohupnow;
201        xwrite(i ? pollfds[0].fd : 1, toybuf, len);
202      } else if (pollfds[i].revents & POLLHUP) {
203dohupnow:
204        // Close half-connection.  This is needed for things like
205        // "echo GET / | netcat landley.net 80"
206        if (i) {
207          shutdown(pollfds[0].fd, SHUT_WR);
208          pollcount--;
209          set_alarm(TT.quit_delay);
210        } else goto cleanup;
211      }
212    }
213  }
214cleanup:
215  if (CFG_TOYBOX_FREE) {
216    close(pollfds[0].fd);
217    close(sockfd);
218  }
219}
220