freebsd-dev/contrib/netcat/netcat.c
Xin LI 8c384020b3 Import a (stripped) snapshot of OpenBSD's nc(1) an excellent
reimplementation of the famous tool that can do arbitrary TCP
and UDP connections and listens.

This gaves sysadm the same tool the crackers have, so that
they may learn what the network is about and protect it better.
For developers, this is an invaluable debugging tool, and a
good build block of scripts.

Discussed on:	freebsd-hackers@
2005-02-04 08:41:44 +00:00

803 lines
18 KiB
C

/* $OpenBSD: netcat.c,v 1.76 2004/12/10 16:51:31 hshoexer 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>.
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <netinet/tcp.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>
#ifndef SUN_LEN
#define SUN_LEN(su) \
(sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
#endif
#define PORT_MAX 65535
#define PORT_MAX_LEN 6
/* Command Line Options */
int dflag; /* detached, no stdin */
int iflag; /* Interval Flag */
int kflag; /* More than one connect */
int lflag; /* Bind to local port */
int nflag; /* Don't do name look up */
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 zflag; /* Port Scan Flag */
int Dflag; /* sodebug */
int Sflag; /* TCP MD5 signature option */
int timeout = -1;
int family = AF_UNSPEC;
char *portlist[PORT_MAX+1];
ssize_t atomicio(ssize_t (*)(int, void *, size_t), int, void *, size_t);
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(char *, char *, struct addrinfo);
int socks_connect(char *, char *, struct addrinfo, char *, char *,
struct addrinfo, int);
int udptest(int);
int unix_connect(char *);
int unix_listen(char *);
void usage(int);
int
main(int argc, char *argv[])
{
int ch, s, ret, socksv;
char *host, *uport, *endp;
struct addrinfo hints;
struct servent *sv;
socklen_t len;
struct sockaddr_storage cliaddr;
char *proxy;
char *proxyhost = "", *proxyport = NULL;
struct addrinfo proxyhints;
ret = 1;
s = 0;
socksv = 5;
host = NULL;
uport = NULL;
endp = NULL;
sv = NULL;
while ((ch = getopt(argc, argv, "46Ddhi:klnp:rSs: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':
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 = (int)strtoul(optarg, &endp, 10);
if (iflag < 0 || *endp != '\0')
errx(1, "interval cannot be negative");
break;
case 'k':
kflag = 1;
break;
case 'l':
lflag = 1;
break;
case 'n':
nflag = 1;
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 = (int)strtoul(optarg, &endp, 10);
if (timeout < 0 || *endp != '\0')
errx(1, "timeout cannot be negative");
if (timeout >= (INT_MAX / 1000))
errx(1, "timeout too large");
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 'S':
Sflag = 1;
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]) {
host = argv[0];
uport = argv[1];
} else
usage(1);
if (lflag && sflag)
errx(1, "cannot use -s and -l");
if (lflag && pflag)
errx(1, "cannot use -p and -l");
if (lflag && zflag)
errx(1, "cannot use -z and -l");
if (!lflag && kflag)
errx(1, "must use -l with -k");
/* Initialize addrinfo structure. */
if (family != AF_UNIX) {
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");
/* XXX IPv6 transport to proxy would probably work */
if (family == AF_INET6)
errx(1, "no proxy support for IPv6");
if (sflag)
errx(1, "no proxy support for local source address");
proxyhost = strsep(&proxy, ":");
proxyport = proxy;
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;
char buf[1024];
struct sockaddr_storage z;
len = sizeof(z);
rv = recvfrom(s, buf, sizeof(buf), 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 {
connfd = accept(s, (struct sockaddr *)&cliaddr,
&len);
}
readwrite(connfd);
close(connfd);
if (family != AF_UNIX)
close(s);
if (!kflag)
break;
}
} else if (family == AF_UNIX) {
ret = 0;
if ((s = unix_connect(host)) > 0 && !zflag) {
readwrite(s);
close(s);
} else
ret = 1;
exit(ret);
} else {
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)
close(s);
if (xflag)
s = socks_connect(host, portlist[i], hints,
proxyhost, proxyport, proxyhints, socksv);
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");
}
printf("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)
close(s);
exit(ret);
}
/*
* unix_connect()
* Returns a socket connected to a local unix socket. Returns -1 on failure.
*/
int
unix_connect(char *path)
{
struct sockaddr_un sun;
int s;
if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
return (-1);
(void)fcntl(s, F_SETFD, 1);
memset(&sun, 0, sizeof(struct sockaddr_un));
sun.sun_family = AF_UNIX;
if (strlcpy(sun.sun_path, path, sizeof(sun.sun_path)) >=
sizeof(sun.sun_path)) {
close(s);
errno = ENAMETOOLONG;
return (-1);
}
if (connect(s, (struct sockaddr *)&sun, SUN_LEN(&sun)) < 0) {
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 sun;
int s;
/* Create unix domain socket. */
if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
return (-1);
memset(&sun, 0, sizeof(struct sockaddr_un));
sun.sun_family = AF_UNIX;
if (strlcpy(sun.sun_path, path, sizeof(sun.sun_path)) >=
sizeof(sun.sun_path)) {
close(s);
errno = ENAMETOOLONG;
return (-1);
}
if (bind(s, (struct sockaddr *)&sun, SUN_LEN(&sun)) < 0) {
close(s);
return (-1);
}
if (listen(s, 5) < 0) {
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(char *host, char *port, struct addrinfo hints)
{
struct addrinfo *res, *res0;
int s, error, x = 1;
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)
continue;
/* Bind to a local port or source address if specified. */
if (sflag || pflag) {
struct addrinfo ahints, *ares;
if (!(sflag && pflag)) {
if (!sflag)
sflag = NULL;
else
pflag = NULL;
}
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 (Sflag) {
if (setsockopt(s, IPPROTO_TCP, TCP_MD5SIG,
&x, sizeof(x)) == -1)
err(1, NULL);
}
if (Dflag) {
if (setsockopt(s, SOL_SOCKET, SO_DEBUG,
&x, sizeof(x)) == -1)
err(1, NULL);
}
if (connect(s, res0->ai_addr, res0->ai_addrlen) == 0)
break;
else if (vflag)
warn("connect to %s port %s (%s) failed", host, port,
uflag ? "udp" : "tcp");
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;
/*
* In the case of binding to a wildcard address
* default to binding to an ipv4 address.
*/
if (host == NULL && hints.ai_family == AF_UNSPEC)
hints.ai_family = AF_INET;
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)
continue;
ret = setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &x, sizeof(x));
if (ret == -1)
err(1, NULL);
if (Sflag) {
ret = setsockopt(s, IPPROTO_TCP, TCP_MD5SIG,
&x, sizeof(x));
if (ret == -1)
err(1, NULL);
}
if (Dflag) {
if (setsockopt(s, SOL_SOCKET, SO_DEBUG,
&x, sizeof(x)) == -1)
err(1, NULL);
}
if (bind(s, (struct sockaddr *)res0->ai_addr,
res0->ai_addrlen) == 0)
break;
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[BUFSIZ];
int wfd = fileno(stdin), n;
int lfd = fileno(stdout);
/* 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)
sleep(iflag);
if ((n = poll(pfd, 2 - dflag, timeout)) < 0) {
close(nfd);
err(1, "Polling Error");
}
if (n == 0)
return;
if (pfd[0].revents & POLLIN) {
if ((n = read(nfd, buf, sizeof(buf))) < 0)
return;
else if (n == 0) {
shutdown(nfd, SHUT_RD);
pfd[0].fd = -1;
pfd[0].events = 0;
} else {
if (tflag)
atelnet(nfd, buf, n);
if (atomicio((ssize_t (*)(int, void *, size_t))write,
lfd, buf, n) != n)
return;
}
}
if (!dflag && pfd[1].revents & POLLIN) {
if ((n = read(wfd, buf, sizeof(buf))) < 0)
return;
else if (n == 0) {
shutdown(nfd, SHUT_WR);
pfd[1].fd = -1;
pfd[1].events = 0;
} else {
if (atomicio((ssize_t (*)(int, void *, size_t))write,
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;
p++;
if ((*p == WILL) || (*p == WONT))
obuf[1] = DONT;
if ((*p == DO) || (*p == DONT))
obuf[1] = WONT;
if (obuf) {
p++;
obuf[2] = *p;
obuf[3] = '\0';
if (atomicio((ssize_t (*)(int, void *, size_t))write,
nfd, obuf, 3) != 3)
warnx("Write Error!");
obuf[0] = '\0';
}
}
}
/*
* build_ports()
* Build an array or ports in portlist[], listing each port
* that we should try to connect to.
*/
void
build_ports(char *p)
{
char *n, *endp;
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 = (int)strtoul(n, &endp, 10);
if (hi <= 0 || hi > PORT_MAX || *endp != '\0')
errx(1, "port range not valid");
lo = (int)strtoul(p, &endp, 10);
if (lo <= 0 || lo > PORT_MAX || *endp != '\0')
errx(1, "port range not valid");
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);
snprintf(portlist[x], PORT_MAX_LEN, "%d", cp);
x++;
}
/* Randomly swap ports. */
if (rflag) {
int y;
char *c;
for (x = 0; x <= (hi - lo); x++) {
y = (arc4random() & 0xFFFF) % (hi - lo);
c = portlist[x];
portlist[x] = portlist[y];
portlist[y] = c;
}
}
} else {
hi = (int)strtoul(p, &endp, 10);
if (hi <= 0 || hi > PORT_MAX || *endp != '\0')
errx(1, "port range not valid");
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
help(void)
{
usage(0);
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 port\t Specify local port for remote connects\n\
\t-r Randomize remote ports\n\
\t-S Enable the TCP MD5 signature option\n\
\t-s addr\t Local source address\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)
{
fprintf(stderr, "usage: nc [-46DdhklnrStUuvz] [-i interval] [-p source_port]\n");
fprintf(stderr, "\t [-s source_ip_address] [-w timeout] [-X proxy_version]\n");
fprintf(stderr, "\t [-x proxy_address[:port]] [hostname] [port[s]]\n");
if (ret)
exit(1);
}