freebsd-nq/contrib/netcat/netcat.c

803 lines
18 KiB
C
Raw Normal View History

/* $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);
}