netcat.c revision f8c3982ab1838a24e4b671d13329f52bbbebc2a7
/* $OpenBSD: netcat.c,v 1.89 2007/02/20 14:11:17 jmc Exp $ */
/*
* Copyright (c) 2001 Eric Jackson <ericj@monkey.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Re-written nc(1) for OpenBSD. Original implementation by
* *Hobbit* <hobbit@avian.org>.
*/
/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/tcp.h>
#include <netinet/ip.h>
#include <arpa/telnet.h>
#include <err.h>
#include <errno.h>
#include <netdb.h>
#include <poll.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include "atomicio.h"
#include "strtonum.h"
#ifndef SUN_LEN
#define SUN_LEN(su) \
(sizeof (*(su)) - sizeof ((su)->sun_path) + strlen((su)->sun_path))
#endif
#define PORT_MIN 1
#define PORT_MAX 65535
#define PORT_MAX_LEN 6
/* Command Line Options */
int dflag; /* detached, no stdin */
unsigned int iflag; /* Interval Flag */
int kflag; /* More than one connect */
int lflag; /* Bind to local port */
int nflag; /* Don't do name lookup */
char *Pflag; /* Proxy username */
char *pflag; /* Localport flag */
int rflag; /* Random ports flag */
char *sflag; /* Source Address */
int tflag; /* Telnet Emulation */
int uflag; /* UDP - Default to TCP */
int vflag; /* Verbosity */
int xflag; /* Socks proxy */
int Xflag; /* indicator of Socks version set */
int zflag; /* Port Scan Flag */
int Dflag; /* sodebug */
int Tflag = -1; /* IP Type of Service */
int timeout = -1;
int family = AF_UNSPEC;
char *portlist[PORT_MAX+1];
void atelnet(int, unsigned char *, unsigned int);
void build_ports(char *);
void help(void);
int local_listen(char *, char *, struct addrinfo);
void readwrite(int);
int remote_connect(const char *, const char *, struct addrinfo);
int socks_connect(const char *, const char *,
const char *, const char *, struct addrinfo, int, const char *);
int udptest(int);
int unix_connect(char *);
int unix_listen(char *);
void set_common_sockopts(int);
int parse_iptos(char *);
void usage(int);
char *print_addr(char *, size_t, struct sockaddr *, int, int);
int
main(int argc, char *argv[])
{
int ch, s, ret, socksv;
char *host, *uport, *proxy;
struct addrinfo hints;
struct servent *sv;
socklen_t len;
struct sockaddr_storage cliaddr;
const char *errstr, *proxyhost = "", *proxyport = NULL;
struct addrinfo proxyhints;
ret = 1;
s = 0;
socksv = 5;
host = NULL;
uport = NULL;
sv = NULL;
while ((ch = getopt(argc, argv,
"46Ddhi:klnP:p:rs:T:tUuvw:X:x:z")) != -1) {
switch (ch) {
case '4':
family = AF_INET;
break;
case '6':
family = AF_INET6;
break;
case 'U':
family = AF_UNIX;
break;
case 'X':
Xflag = 1;
if (strcasecmp(optarg, "connect") == 0)
socksv = -1; /* HTTP proxy CONNECT */
else if (strcmp(optarg, "4") == 0)
socksv = 4; /* SOCKS v.4 */
else if (strcmp(optarg, "5") == 0)
socksv = 5; /* SOCKS v.5 */
else
errx(1, "unsupported proxy protocol");
break;
case 'd':
dflag = 1;
break;
case 'h':
help();
break;
case 'i':
iflag = strtonum(optarg, 0, UINT_MAX, &errstr);
if (errstr)
errx(1, "interval %s: %s", errstr, optarg);
break;
case 'k':
kflag = 1;
break;
case 'l':
lflag = 1;
break;
case 'n':
nflag = 1;
break;
case 'P':
Pflag = optarg;
break;
case 'p':
pflag = optarg;
break;
case 'r':
rflag = 1;
break;
case 's':
sflag = optarg;
break;
case 't':
tflag = 1;
break;
case 'u':
uflag = 1;
break;
case 'v':
vflag = 1;
break;
case 'w':
timeout = strtonum(optarg, 0, INT_MAX / 1000, &errstr);
if (errstr)
errx(1, "timeout %s: %s", errstr, optarg);
timeout *= 1000;
break;
case 'x':
xflag = 1;
if ((proxy = strdup(optarg)) == NULL)
err(1, NULL);
break;
case 'z':
zflag = 1;
break;
case 'D':
Dflag = 1;
break;
case 'T':
Tflag = parse_iptos(optarg);
break;
default:
usage(1);
}
}
argc -= optind;
argv += optind;
/* Cruft to make sure options are clean, and used properly. */
if (argv[0] && !argv[1] && family == AF_UNIX) {
if (uflag)
errx(1, "cannot use -u and -U");
host = argv[0];
uport = NULL;
} else if (argv[0] && !argv[1]) {
if (!lflag)
usage(1);
uport = argv[0];
host = NULL;
} else if (argv[0] && argv[1]) {
if (family == AF_UNIX)
usage(1);
host = argv[0];
uport = argv[1];
} else {
if (!(lflag && pflag))
usage(1);
}
if (lflag && sflag)
errx(1, "cannot use -s and -l");
if (lflag && rflag)
errx(1, "cannot use -r and -l");
if (lflag && pflag) {
if (uport)
usage(1);
uport = pflag;
}
if (lflag && zflag)
errx(1, "cannot use -z and -l");
if (!lflag && kflag)
errx(1, "must use -l with -k");
if (lflag && (Pflag || xflag || Xflag))
errx(1, "cannot use -l with -P, -X or -x");
/* Initialize addrinfo structure. */
if (family != AF_UNIX) {
(void) memset(&hints, 0, sizeof (struct addrinfo));
hints.ai_family = family;
hints.ai_socktype = uflag ? SOCK_DGRAM : SOCK_STREAM;
hints.ai_protocol = uflag ? IPPROTO_UDP : IPPROTO_TCP;
if (nflag)
hints.ai_flags |= AI_NUMERICHOST;
}
if (xflag) {
if (uflag)
errx(1, "no proxy support for UDP mode");
if (lflag)
errx(1, "no proxy support for listen");
if (family == AF_UNIX)
errx(1, "no proxy support for unix sockets");
if (family == AF_INET6)
errx(1, "no proxy support for IPv6");
if (sflag)
errx(1, "no proxy support for local source address");
if ((proxyhost = strtok(proxy, ":")) == NULL)
errx(1, "missing port specification");
proxyport = strtok(NULL, ":");
(void) memset(&proxyhints, 0, sizeof (struct addrinfo));
proxyhints.ai_family = family;
proxyhints.ai_socktype = SOCK_STREAM;
proxyhints.ai_protocol = IPPROTO_TCP;
if (nflag)
proxyhints.ai_flags |= AI_NUMERICHOST;
}
if (lflag) {
int connfd;
ret = 0;
if (family == AF_UNIX)
s = unix_listen(host);
/* Allow only one connection at a time, but stay alive. */
for (;;) {
if (family != AF_UNIX)
s = local_listen(host, uport, hints);
if (s < 0)
err(1, NULL);
/*
* For UDP, we will use recvfrom() initially
* to wait for a caller, then use the regular
* functions to talk to the caller.
*/
if (uflag) {
int rv, plen;
char buf[8192];
struct sockaddr_storage z;
len = sizeof (z);
plen = 1024;
rv = recvfrom(s, buf, plen, MSG_PEEK,
(struct sockaddr *)&z, &len);
if (rv < 0)
err(1, "recvfrom");
rv = connect(s, (struct sockaddr *)&z, len);
if (rv < 0)
err(1, "connect");
connfd = s;
} else {
len = sizeof (cliaddr);
connfd = accept(s, (struct sockaddr *)&cliaddr,
&len);
if ((connfd != -1) && vflag) {
char ntop[NI_MAXHOST + NI_MAXSERV];
(void) fprintf(stderr,
"Received connection from %s\n",
print_addr(ntop, sizeof (ntop),
(struct sockaddr *)&cliaddr, len,
nflag ? NI_NUMERICHOST : 0));
}
}
readwrite(connfd);
(void) close(connfd);
if (family != AF_UNIX)
(void) close(s);
if (!kflag)
break;
}
} else if (family == AF_UNIX) {
ret = 0;
if ((s = unix_connect(host)) > 0 && !zflag) {
readwrite(s);
(void) close(s);
} else
ret = 1;
exit(ret);
} else { /* AF_INET or AF_INET6 */
int i = 0;
/* Construct the portlist[] array. */
build_ports(uport);
/* Cycle through portlist, connecting to each port. */
for (i = 0; portlist[i] != NULL; i++) {
if (s)
(void) close(s);
if (xflag)
s = socks_connect(host, portlist[i],
proxyhost, proxyport, proxyhints, socksv,
Pflag);
else
s = remote_connect(host, portlist[i], hints);
if (s < 0)
continue;
ret = 0;
if (vflag || zflag) {
/* For UDP, make sure we are connected. */
if (uflag) {
if (udptest(s) == -1) {
ret = 1;
continue;
}
}
/* Don't look up port if -n. */
if (nflag)
sv = NULL;
else {
sv = getservbyport(
ntohs(atoi(portlist[i])),
uflag ? "udp" : "tcp");
}
(void) fprintf(stderr, "Connection to %s %s "
"port [%s/%s] succeeded!\n",
host, portlist[i], uflag ? "udp" : "tcp",
sv ? sv->s_name : "*");
}
if (!zflag)
readwrite(s);
}
}
if (s)
(void) close(s);
return (ret);
}
/*
* print IP address and (optionally) a port
*/
char *
print_addr(char *ntop, size_t ntlen, struct sockaddr *addr, int len, int flags)
{
char port[NI_MAXSERV];
int e;
/* print port always as number */
if ((e = getnameinfo(addr, len, ntop, ntlen,
port, sizeof (port), flags|NI_NUMERICSERV)) != 0) {
return ((char *)gai_strerror(e));
}
(void) snprintf(ntop, ntlen, "%s port %s", ntop, port);
return (ntop);
}
/*
* unix_connect()
* Returns a socket connected to a local unix socket. Returns -1 on failure.
*/
int
unix_connect(char *path)
{
struct sockaddr_un sunaddr;
int s;
if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
return (-1);
(void) memset(&sunaddr, 0, sizeof (struct sockaddr_un));
sunaddr.sun_family = AF_UNIX;
if (strlcpy(sunaddr.sun_path, path, sizeof (sunaddr.sun_path)) >=
sizeof (sunaddr.sun_path)) {
(void) close(s);
errno = ENAMETOOLONG;
return (-1);
}
if (connect(s, (struct sockaddr *)&sunaddr, SUN_LEN(&sunaddr)) < 0) {
(void) close(s);
return (-1);
}
return (s);
}
/*
* unix_listen()
* Create a unix domain socket, and listen on it.
*/
int
unix_listen(char *path)
{
struct sockaddr_un sunaddr;
int s;
/* Create unix domain socket. */
if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
return (-1);
(void) memset(&sunaddr, 0, sizeof (struct sockaddr_un));
sunaddr.sun_family = AF_UNIX;
if (strlcpy(sunaddr.sun_path, path, sizeof (sunaddr.sun_path)) >=
sizeof (sunaddr.sun_path)) {
(void) close(s);
errno = ENAMETOOLONG;
return (-1);
}
if (bind(s, (struct sockaddr *)&sunaddr, SUN_LEN(&sunaddr)) < 0) {
(void) close(s);
return (-1);
}
if (listen(s, 5) < 0) {
(void) close(s);
return (-1);
}
return (s);
}
/*
* remote_connect()
* Returns a socket connected to a remote host. Properly binds to a local
* port or source address if needed. Returns -1 on failure.
*/
int
remote_connect(const char *host, const char *port, struct addrinfo hints)
{
struct addrinfo *res, *res0;
int s, error;
if ((error = getaddrinfo(host, port, &hints, &res)))
errx(1, "getaddrinfo: %s", gai_strerror(error));
res0 = res;
do {
if ((s = socket(res0->ai_family, res0->ai_socktype,
res0->ai_protocol)) < 0) {
warn("failed to create socket");
continue;
}
/* Bind to a local port or source address if specified. */
if (sflag || pflag) {
struct addrinfo ahints, *ares;
(void) memset(&ahints, 0, sizeof (struct addrinfo));
ahints.ai_family = res0->ai_family;
ahints.ai_socktype = uflag ? SOCK_DGRAM : SOCK_STREAM;
ahints.ai_protocol = uflag ? IPPROTO_UDP : IPPROTO_TCP;
ahints.ai_flags = AI_PASSIVE;
if ((error = getaddrinfo(sflag, pflag, &ahints, &ares)))
errx(1, "getaddrinfo: %s", gai_strerror(error));
if (bind(s, (struct sockaddr *)ares->ai_addr,
ares->ai_addrlen) < 0)
errx(1, "bind failed: %s", strerror(errno));
freeaddrinfo(ares);
if (vflag && !lflag) {
if (sflag != NULL)
(void) fprintf(stderr,
"Using source address: %s\n",
sflag);
if (pflag != NULL)
(void) fprintf(stderr,
"Using source port: %s\n", pflag);
}
}
set_common_sockopts(s);
if (connect(s, res0->ai_addr, res0->ai_addrlen) == 0)
break;
else if (vflag) {
char ntop[NI_MAXHOST + NI_MAXSERV];
warn("connect to %s [host %s] (%s) failed",
print_addr(ntop, sizeof (ntop),
res0->ai_addr, res0->ai_addrlen, NI_NUMERICHOST),
host, uflag ? "udp" : "tcp");
}
(void) close(s);
s = -1;
} while ((res0 = res0->ai_next) != NULL);
freeaddrinfo(res);
return (s);
}
/*
* local_listen()
* Returns a socket listening on a local port, binds to specified source
* address. Returns -1 on failure.
*/
int
local_listen(char *host, char *port, struct addrinfo hints)
{
struct addrinfo *res, *res0;
int s, ret, x = 1;
int error;
/* Allow nodename to be null. */
hints.ai_flags |= AI_PASSIVE;
if ((error = getaddrinfo(host, port, &hints, &res)))
errx(1, "getaddrinfo: %s", gai_strerror(error));
res0 = res;
do {
if ((s = socket(res0->ai_family, res0->ai_socktype,
res0->ai_protocol)) < 0) {
warn("failed to create socket");
continue;
}
ret = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &x, sizeof (x));
if (ret == -1)
err(1, NULL);
set_common_sockopts(s);
if (bind(s, (struct sockaddr *)res0->ai_addr,
res0->ai_addrlen) == 0)
break;
(void) close(s);
s = -1;
} while ((res0 = res0->ai_next) != NULL);
if (!uflag && s != -1) {
if (listen(s, 1) < 0)
err(1, "listen");
}
freeaddrinfo(res);
return (s);
}
/*
* readwrite()
* Loop that polls on the network file descriptor and stdin.
*/
void
readwrite(int nfd)
{
struct pollfd pfd[2];
unsigned char buf[8192];
int n, wfd = fileno(stdin);
int lfd = fileno(stdout);
int plen;
plen = 1024;
/* Setup Network FD */
pfd[0].fd = nfd;
pfd[0].events = POLLIN;
/* Set up STDIN FD. */
pfd[1].fd = wfd;
pfd[1].events = POLLIN;
while (pfd[0].fd != -1) {
if (iflag)
(void) sleep(iflag);
if ((n = poll(pfd, 2 - dflag, timeout)) < 0) {
(void) close(nfd);
err(1, "Polling Error");
}
if (n == 0)
return;
if (pfd[0].revents & (POLLIN|POLLHUP)) {
if ((n = read(nfd, buf, plen)) < 0)
return;
else if (n == 0) {
(void) shutdown(nfd, SHUT_RD);
pfd[0].fd = -1;
pfd[0].events = 0;
} else {
if (tflag)
atelnet(nfd, buf, n);
if (atomicio(vwrite, lfd, buf, n) != n)
return;
}
}
/*
* handle the case of disconnected pipe: after pipe
* is closed (indicated by POLLHUP) there may still
* be some data lingering (POLLIN). After we read
* the data, only POLLHUP remains, read() returns 0
* and we are finished.
*/
if (!dflag && (pfd[1].revents & (POLLIN|POLLHUP))) {
if ((n = read(wfd, buf, plen)) < 0)
return;
else if (n == 0) {
(void) shutdown(nfd, SHUT_WR);
pfd[1].fd = -1;
pfd[1].events = 0;
} else {
if (atomicio(vwrite, nfd, buf, n) != n)
return;
}
}
}
}
/* Deal with RFC 854 WILL/WONT DO/DONT negotiation. */
void
atelnet(int nfd, unsigned char *buf, unsigned int size)
{
unsigned char *p, *end;
unsigned char obuf[4];
end = buf + size;
obuf[0] = '\0';
for (p = buf; p < end; p++) {
if (*p != IAC)
break;
obuf[0] = IAC;
obuf[1] = 0;
p++;
/* refuse all options */
if ((*p == WILL) || (*p == WONT))
obuf[1] = DONT;
if ((*p == DO) || (*p == DONT))
obuf[1] = WONT;
if (obuf[1]) {
p++;
obuf[2] = *p;
obuf[3] = '\0';
if (atomicio(vwrite, nfd, obuf, 3) != 3)
warn("Write Error!");
obuf[0] = '\0';
}
}
}
/*
* build_ports()
* Build an array of ports in portlist[], listing each port
* that we should try to connect to.
*/
void
build_ports(char *p)
{
const char *errstr;
char *n;
int hi, lo, cp;
int x = 0;
if ((n = strchr(p, '-')) != NULL) {
if (lflag)
errx(1, "Cannot use -l with multiple ports!");
*n = '\0';
n++;
/* Make sure the ports are in order: lowest->highest. */
hi = strtonum(n, PORT_MIN, PORT_MAX, &errstr);
if (errstr)
errx(1, "port number %s: %s", errstr, n);
lo = strtonum(p, PORT_MIN, PORT_MAX, &errstr);
if (errstr)
errx(1, "port number %s: %s", errstr, p);
if (lo > hi) {
cp = hi;
hi = lo;
lo = cp;
}
/* Load ports sequentially. */
for (cp = lo; cp <= hi; cp++) {
portlist[x] = calloc(1, PORT_MAX_LEN);
if (portlist[x] == NULL)
err(1, NULL);
(void) snprintf(portlist[x], PORT_MAX_LEN, "%d", cp);
x++;
}
/* Randomly swap ports. */
if (rflag) {
int y;
char *c;
srandom(time((time_t *)0));
for (x = 0; x <= (hi - lo); x++) {
y = (random() & 0xFFFF) % (hi - lo);
c = portlist[x];
portlist[x] = portlist[y];
portlist[y] = c;
}
}
} else {
hi = strtonum(p, PORT_MIN, PORT_MAX, &errstr);
if (errstr)
errx(1, "port number %s: %s", errstr, p);
portlist[0] = calloc(1, PORT_MAX_LEN);
if (portlist[0] == NULL)
err(1, NULL);
portlist[0] = p;
}
}
/*
* udptest()
* Do a few writes to see if the UDP port is there.
* XXX - Better way of doing this? Doesn't work for IPv6.
* Also fails after around 100 ports checked.
*/
int
udptest(int s)
{
int i, ret;
for (i = 0; i <= 3; i++) {
if (write(s, "X", 1) == 1)
ret = 1;
else
ret = -1;
}
return (ret);
}
void
set_common_sockopts(int s)
{
int x = 1;
if (Dflag) {
if (setsockopt(s, SOL_SOCKET, SO_DEBUG, &x, sizeof (x)) == -1)
err(1, NULL);
}
if (Tflag != -1) {
if (setsockopt(s, IPPROTO_IP, IP_TOS, &Tflag,
sizeof (Tflag)) == -1)
err(1, "set IP ToS");
}
}
int
parse_iptos(char *s)
{
int tos = -1;
if (strcmp(s, "lowdelay") == 0)
return (IPTOS_LOWDELAY);
if (strcmp(s, "throughput") == 0)
return (IPTOS_THROUGHPUT);
if (strcmp(s, "reliability") == 0)
return (IPTOS_RELIABILITY);
if (sscanf(s, "0x%x", (unsigned int *) &tos) != 1 ||
tos < 0 || tos > 0xff)
errx(1, "invalid IP Type of Service");
return (tos);
}
void
help(void)
{
usage(0);
(void) fprintf(stderr, "\tCommand Summary:\n\
\t-4 Use IPv4\n\
\t-6 Use IPv6\n\
\t-D Enable the debug socket option\n\
\t-d Detach from stdin\n\
\t-h This help text\n\
\t-i secs\t Delay interval for lines sent, ports scanned\n\
\t-k Keep inbound sockets open for multiple connects\n\
\t-l Listen mode, for inbound connects\n\
\t-n Suppress name/port resolutions\n\
\t-P proxyuser\tUsername for proxy authentication\n\
\t-p port\t Specify local port or listen port\n\
\t-r Randomize remote ports\n\
\t-s addr\t Local source address\n\
\t-T ToS\t Set IP Type of Service\n\
\t-t Answer TELNET negotiation\n\
\t-U Use UNIX domain socket\n\
\t-u UDP mode\n\
\t-v Verbose\n\
\t-w secs\t Timeout for connects and final net reads\n\
\t-X proto Proxy protocol: \"4\", \"5\" (SOCKS) or \"connect\"\n\
\t-x addr[:port]\tSpecify proxy address and port\n\
\t-z Zero-I/O mode [used for scanning]\n\
Port numbers can be individual or ranges: lo-hi [inclusive]\n");
exit(1);
}
void
usage(int ret)
{
(void) fprintf(stderr,
"usage: nc [-46DdhklnrtUuvz] [-i interval] [-P proxy_username]"
" [-p port]\n");
(void) fprintf(stderr,
"\t [-s source_ip_address] [-T ToS] [-w timeout]"
" [-X proxy_protocol]\n");
(void) fprintf(stderr,
"\t [-x proxy_address[:port]] [hostname]"
" [port[s]]\n");
if (ret)
exit(1);
}