diff --git a/contrib/netcat/nc.1 b/contrib/netcat/nc.1 --- a/contrib/netcat/nc.1 +++ b/contrib/netcat/nc.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: nc.1,v 1.68 2015/03/26 10:35:04 tobias Exp $ +.\" $OpenBSD: nc.1,v 1.97 2022/09/11 09:58:06 schwarze Exp $ .\" .\" Copyright (c) 1996 David Sacerdote .\" All rights reserved. @@ -25,9 +25,7 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.\" $FreeBSD$ -.\" -.Dd January 17, 2023 +.Dd $Mdocdate: January 17, 2023 $ .Dt NC 1 .Os .Sh NAME @@ -35,30 +33,29 @@ .Nd arbitrary TCP and UDP connections and listens .Sh SYNOPSIS .Nm nc -.Bk -words .Op Fl 46DdEFhklMNnrStUuvz +.Op Fl -crlf .Op Fl e Ar IPsec_policy .Op Fl I Ar length .Op Fl i Ar interval .Op Fl -no-tcpopt -.Op Fl -sctp -.Op Fl -crlf +.Op Fl M Ar ttl +.Op Fl m Ar minttl .Op Fl O Ar length .Op Fl P Ar proxy_username .Op Fl p Ar source_port -.Op Fl s Ar source +.Op Fl s Ar sourceaddr +.Op Fl -sctp +.Op Fl -stats .Op Fl T Ar toskeyword .Op Fl -tun Ar tundev .Op Fl V Ar rtable +.Op Fl W Ar recvlimit .Op Fl w Ar timeout .Op Fl X Ar proxy_protocol -.Oo Xo -.Fl x Ar proxy_address Ns Oo : Ns -.Ar port Oc -.Xc Oc +.Op Fl x Ar proxy_address Ns Op : Ns Ar port .Op Ar destination .Op Ar port -.Ek .Sh DESCRIPTION The .Nm @@ -98,13 +95,9 @@ The options are as follows: .Bl -tag -width Ds .It Fl 4 -Forces -.Nm -to use IPv4 addresses only. +Use IPv4 addresses only. .It Fl 6 -Forces -.Nm -to use IPv6 addresses only. +Use IPv6 addresses only. .It Fl -crlf Convert LF into CRLF when sending data over the network. .It Fl D @@ -115,7 +108,7 @@ Shortcut for .Qo .Li "-e 'in ipsec esp/transport//require'" -.Li "-e 'out ipsec esp/transport//require'" +.Li "-e 'out ipsec esp/transport//require'" .Qc , which enables IPsec ESP transport mode in both directions. @@ -140,47 +133,46 @@ .Xr ssh_config 5 .Cm ProxyUseFdpass option). +Cannot be used with +.Fl U . .It Fl h -Prints out +Print out the .Nm -help. +help text and exit. .It Fl I Ar length -Specifies the size of the TCP receive buffer. +Specify the size of the TCP receive buffer. .It Fl i Ar interval -Specifies a delay time interval between lines of text sent and received. +Sleep for +.Ar interval +seconds between lines of text sent and received. Also causes a delay time between connections to multiple ports. .It Fl k -Forces -.Nm -to stay listening for another connection after its current connection -is completed. -It is an error to use this option without the -.Fl l -option. +When a connection is completed, listen for another one. +Requires +.Fl l . When used together with the .Fl u option, the server socket is not connected and it can receive UDP datagrams from multiple hosts. .It Fl l -Used to specify that -.Nm -should listen for an incoming connection rather than initiate a +Listen for an incoming connection rather than initiating a connection to a remote host. -It is an error to use this option in conjunction with the -.Fl p , -.Fl s , -or -.Fl z -options. +Cannot be used together with any of the options +.Fl psxz . Additionally, any timeouts specified with the .Fl w option are ignored. -.It Fl M +.It Fl -stats Collect per-connection TCP statistics using the .Xr stats 3 framework and print them in JSON format to .Xr stderr 4 after the connection is closed. +.It Fl M Ar ttl +Set the TTL / hop limit of outgoing packets. +.It Fl m Ar minttl +Ask the kernel to drop incoming packets whose TTL / hop limit is under +.Ar minttl . .It Fl N .Xr shutdown 2 the network socket after EOF on the input. @@ -188,6 +180,7 @@ .It Fl n Do not do any DNS or service lookups on any specified addresses, hostnames or ports. +If a name cannot be resolved without DNS, an error will be reported. .It Fl -no-tcpopt Disables the use of TCP options on the socket, by setting the boolean TCP_NOOPT @@ -195,52 +188,51 @@ .It Fl -sctp Use SCTP instead of the default option of TCP. .It Fl O Ar length -Specifies the size of the TCP send buffer. +Specify the size of the TCP send buffer. .It Fl P Ar proxy_username Specifies a username to present to a proxy server that requires authentication. If no username is specified then authentication will not be attempted. Proxy authentication is only supported for HTTP CONNECT proxies at present. .It Fl p Ar source_port -Specifies the source port +Specify the source port .Nm should use, subject to privilege restrictions and availability. -It is an error to use this option in conjunction with the -.Fl l -option. +Cannot be used together with +.Fl l . .It Fl r -Specifies that source and/or destination ports should be chosen randomly +Choose source and/or destination ports randomly instead of sequentially within a range or in the order that the system assigns them. .It Fl S -Enables the RFC 2385 TCP MD5 signature option. -.It Fl s Ar source -Specifies the IP of the interface which is used to send the packets. +Enable the RFC 2385 TCP MD5 signature option. +.It Fl s Ar sourceaddr +Set the source address to send packets from, +which is useful on machines with multiple interfaces. For .Ux Ns -domain datagram sockets, specifies the local temporary socket file to create and use so that datagrams can be received. -It is an error to use this option in conjunction with the +Cannot be used together with .Fl l -option. -.It Fl T Ar toskeyword -Change IPv4 TOS value. -.Ar toskeyword +or +.Fl x . +.It Fl T Ar keyword +Change the IPv4 TOS/IPv6 traffic class value. +.Ar keyword may be one of -.Ar critical , -.Ar inetcontrol , -.Ar lowdelay , -.Ar netcontrol , -.Ar throughput , -.Ar reliability , +.Cm critical , +.Cm inetcontrol , +.Cm lowdelay , +.Cm netcontrol , +.Cm throughput , +.Cm reliability , or one of the DiffServ Code Points: -.Ar ef , -.Ar af11 ... af43 , -.Ar cs0 ... cs7 ; +.Cm ef , +.Cm af11 No ... Cm af43 , +.Cm cs0 No ... Cm cs7 ; or a number in either hex or decimal. .It Fl t -Causes -.Nm -to send RFC 854 DON'T and WON'T responses to RFC 854 DO and WILL requests. +Send RFC 854 DON'T and WON'T responses to RFC 854 DO and WILL requests. This makes it possible to use .Nm to script telnet sessions. @@ -251,11 +243,15 @@ .Xr tun 4 for input and output rather than the default of stdin and stdout. .It Fl U -Specifies to use +Use .Ux Ns -domain sockets. +Cannot be used together with any of the options +.Fl Fx . .It Fl u -Use UDP instead of the default option of TCP. +Use UDP instead of TCP. +Cannot be used together with +.Fl x . For .Ux Ns -domain sockets, use a datagram socket instead of a stream socket. @@ -271,9 +267,11 @@ .Pq Dq FIB to be used. .It Fl v -Have -.Nm -give more verbose output. +Produce more verbose output. +.It Fl W Ar recvlimit +Terminate after receiving +.Ar recvlimit +packets from the network. .It Fl w Ar timeout Connections which cannot be established or are idle timeout after .Ar timeout @@ -289,25 +287,20 @@ flag. The default is no timeout. .It Fl X Ar proxy_protocol -Requests that -.Nm -should use the specified protocol when talking to the proxy server. +Use +.Ar proxy_protocol +when talking to the proxy server. Supported protocols are -.Dq 4 +.Cm 4 (SOCKS v.4), -.Dq 5 +.Cm 5 (SOCKS v.5) and -.Dq connect +.Cm connect (HTTPS proxy). If the protocol is not specified, SOCKS version 5 is used. -.It Xo -.Fl x Ar proxy_address Ns Oo : Ns -.Ar port Oc -.Xc -Requests that -.Nm -should connect to +.It Fl x Ar proxy_address Ns Op : Ns Ar port +Connect to .Ar destination using a proxy at .Ar proxy_address @@ -317,13 +310,15 @@ .Ar port is not specified, the well-known port for the proxy protocol is used (1080 for SOCKS, 3128 for HTTPS). +An IPv6 address can be specified unambiguously by enclosing +.Ar proxy_address +in square brackets. +A proxy cannot be used with any of the options +.Fl lsuU . .It Fl z -Specifies that -.Nm -should just scan for listening daemons, without sending any data to them. -It is an error to use this option in conjunction with the -.Fl l -option. +Only scan for listening daemons, without sending any data to them. +Cannot be used together with +.Fl l . .El .Pp .Ar destination @@ -344,8 +339,9 @@ option is given). .Pp .Ar port -can be a single integer or a range of ports. -Ranges are in the form nn-mm. +can be specified as a numeric port number or as a service name. +Port ranges may be specified as numeric port numbers of the form +.Ar nn Ns - Ns Ar mm . In general, a destination port must be specified, unless the @@ -367,7 +363,7 @@ .Pq or a second machine , connect to the machine and port being listened on: .Pp -.Dl $ nc 127.0.0.1 1234 +.Dl $ nc -N 127.0.0.1 1234 .Pp There should now be a connection between the ports. Anything typed at the second console will be concatenated to the first, @@ -380,7 +376,10 @@ .Sq client . The connection may be terminated using an .Dv EOF -.Pq Sq ^D . +.Pq Sq ^D , +as the +.Fl N +flag was given. .Sh DATA TRANSFER The example in the previous section can be expanded to build a basic data transfer model. @@ -392,13 +391,13 @@ .Nm to listen on a specific port, with output captured into a file: .Pp -.Dl $ nc -l 1234 \*(Gt filename.out +.Dl $ nc -l 1234 > filename.out .Pp Using a second machine, connect to the listening .Nm process, feeding it the file which is to be transferred: .Pp -.Dl $ nc -N host.example.com 1234 \*(Lt filename.in +.Dl $ nc -N host.example.com 1234 < filename.in .Pp After the file has been transferred, the connection will close automatically. .Sh TALKING TO SERVERS @@ -422,10 +421,10 @@ of requests required by the server. As another example, an email may be submitted to an SMTP server using: .Bd -literal -offset indent -$ nc localhost 25 \*(Lt\*(Lt EOF +$ nc localhost 25 << EOF HELO host.example.com -MAIL FROM:\*(Ltuser@host.example.com\*(Gt -RCPT TO:\*(Ltuser2@host.example.com\*(Gt +MAIL FROM: +RCPT TO: DATA Body of email. \&. @@ -522,8 +521,8 @@ .Xr ssh 1 , .Xr tcp 4 .Sh AUTHORS -Original implementation by *Hobbit* -.Aq Mt hobbit@avian.org . +Original implementation by +.An *Hobbit* Aq Mt hobbit@avian.org . .br Rewritten with IPv6 support by .An Eric Jackson Aq Mt ericj@monkey.org . diff --git a/contrib/netcat/netcat.c b/contrib/netcat/netcat.c --- a/contrib/netcat/netcat.c +++ b/contrib/netcat/netcat.c @@ -1,6 +1,7 @@ -/* $OpenBSD: netcat.c,v 1.130 2015/07/26 19:12:28 chl Exp $ */ +/* $OpenBSD: netcat.c,v 1.226 2023/08/14 08:07:27 tb Exp $ */ /* * Copyright (c) 2001 Eric Jackson + * Copyright (c) 2015 Bob Beck. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -24,8 +25,6 @@ * 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. - * - * $FreeBSD$ */ /* @@ -55,6 +54,7 @@ #include #include +#include #include #include #include @@ -65,23 +65,27 @@ #include #include #include +#include +#ifdef __OpenBSD__ +#include +#endif #include -#include "atomicio.h" -#ifndef SUN_LEN -#define SUN_LEN(su) \ - (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path)) -#endif +#include "atomicio.h" #define PORT_MAX 65535 -#define PORT_MAX_LEN 6 #define UNIX_DG_TMP_SOCKET_SIZE 19 -#define POLL_STDIN 0 -#define POLL_NETOUT 1 -#define POLL_NETIN 2 -#define POLL_STDOUT 3 -#define BUFSIZE 16384 +#define POLL_STDIN 0 +#define POLL_NETOUT 1 +#define POLL_NETIN 2 +#define POLL_STDOUT 3 +#define BUFSIZE 16384 + +#define TLS_NOVERIFY (1 << 1) +#define TLS_NONAME (1 << 2) +#define TLS_CCERT (1 << 3) +#define TLS_MUSTSTAPLE (1 << 4) /* Command Line Options */ int dflag; /* detached, no stdin */ @@ -89,7 +93,7 @@ unsigned int iflag; /* Interval Flag */ int kflag; /* More than one connect */ int lflag; /* Bind to local port */ -int FreeBSD_Mflag; /* Measure using stats(3) */ +int FreeBSD_stats; /* Measure using stats(3) */ int Nflag; /* shutdown() network socket */ int nflag; /* Don't do name look up */ int FreeBSD_Oflag; /* Do not use TCP options */ @@ -111,35 +115,74 @@ int Tflag = -1; /* IP Type of Service */ int rtableid = -1; +int usetls; /* use TLS */ +const char *Cflag; /* Public cert file */ +const char *Kflag; /* Private key file */ +const char *oflag; /* OCSP stapling file */ +const char *Rflag; /* Root CA file */ +int tls_cachanged; /* Using non-default CA file */ +int TLSopt; /* TLS options */ +char *tls_expectname; /* required name in peer cert */ +char *tls_expecthash; /* required hash of peer cert */ +char *tls_ciphers; /* TLS ciphers */ +char *tls_protocols; /* TLS protocols */ +FILE *Zflag; /* file to save peer cert */ + +int recvcount, recvlimit; int timeout = -1; int family = AF_UNSPEC; int tun_fd = -1; char *portlist[PORT_MAX+1]; char *unix_dg_tmp_socket; +int ttl = -1; +int minttl = -1; void atelnet(int, unsigned char *, unsigned int); +int strtoport(char *portstr, int udp); void build_ports(char *); -void help(void); -int local_listen(char *, char *, struct addrinfo); -void readwrite(int); +void help(void) __attribute__((noreturn)); +int local_listen(const char *, const char *, struct addrinfo); +#ifdef __OpenBSD__ +void readwrite(int, struct tls *); +#else +void readwrite(int, void *); +#endif void fdpass(int nfd) __attribute__((noreturn)); -int remote_connect(const char *, const char *, struct addrinfo); +int remote_connect(const char *, const char *, struct addrinfo, char *); +#ifdef __OpenBSD__ +int timeout_tls(int, struct tls *, int (*)(struct tls *)); +#endif int timeout_connect(int, const struct sockaddr *, socklen_t); int socks_connect(const char *, const char *, struct addrinfo, const char *, const char *, struct addrinfo, int, const char *); int udptest(int); -int unix_bind(char *); +void connection_info(const char *, const char *, const char *, const char *); +int unix_bind(char *, int); int unix_connect(char *); int unix_listen(char *); void FreeBSD_stats_setup(int); void FreeBSD_stats_print(int); void set_common_sockopts(int, int); -int map_tos(char *, int *); -void report_connect(const struct sockaddr *, socklen_t); +int process_tos_opt(char *, int *); +int process_tls_opt(char *, int *); +#ifdef __OpenBSD__ +void save_peer_cert(struct tls *_tls_ctx, FILE *_fp); +#endif +void report_sock(const char *, const struct sockaddr *, socklen_t, char *); +#ifdef __OpenBSD__ +void report_tls(struct tls *tls_ctx, char * host); +#endif void usage(int); +#ifdef __OpenBSD__ +ssize_t drainbuf(int, unsigned char *, size_t *, struct tls *); +ssize_t fillbuf(int, unsigned char *, size_t *, struct tls *); +void tls_setup_client(struct tls *, int, char *); +struct tls *tls_setup_server(struct tls *, int, char *); +#else ssize_t write_wrapper(int, const void *, size_t); -ssize_t drainbuf(int, unsigned char *, size_t *, int); -ssize_t fillbuf(int, unsigned char *, size_t *); +ssize_t drainbuf(int, unsigned char *, size_t *, void *, int); +ssize_t fillbuf(int, unsigned char *, size_t *, void *); +#endif #ifdef IPSEC void add_ipsec_policy(int, int, char *); @@ -154,38 +197,45 @@ int main(int argc, char *argv[]) { - int ch, s, ret, socksv, ipsec_count; - int numfibs; - size_t intsize = sizeof(int); + int ch, s = -1, ret, socksv; char *host, *uport; + int numfibs, ipsec_count = 0; + size_t intsize = sizeof(int); + char ipaddr[NI_MAXHOST]; struct addrinfo hints; - struct servent *sv; socklen_t len; struct sockaddr_storage cliaddr; - char *proxy; - const char *errstr, *proxyhost = "", *proxyport = NULL, *tundev = NULL; + char *proxy = NULL, *proxyport = NULL; + const char *errstr, *tundev = NULL; struct addrinfo proxyhints; char unix_dg_tmp_socket_buf[UNIX_DG_TMP_SOCKET_SIZE]; +#ifdef __OpenBSD__ + struct tls_config *tls_cfg = NULL; + struct tls *tls_ctx = NULL; + uint32_t protocols; +#endif struct option longopts[] = { { "crlf", no_argument, &FreeBSD_crlf, 1 }, + { "help", no_argument, NULL, 'h' }, { "no-tcpopt", no_argument, &FreeBSD_Oflag, 1 }, { "sctp", no_argument, &FreeBSD_sctp, 1 }, + { "stats", no_argument, &FreeBSD_stats, 1 }, { "tun", required_argument, NULL, FREEBSD_TUN }, { NULL, 0, NULL, 0 } }; ret = 1; - ipsec_count = 0; - s = 0; socksv = 5; host = NULL; uport = NULL; - sv = NULL; +#ifdef __OpenBSD__ + Rflag = tls_default_ca_cert_file(); +#endif signal(SIGPIPE, SIG_IGN); while ((ch = getopt_long(argc, argv, - "46DdEe:FhI:i:klMNnoO:P:p:rSs:tT:UuV:vw:X:x:z", + "46DdEe:FhI:i:klM:m:NnO:P:p:rSs:T:tUuV:vW:w:X:x:z", longopts, NULL)) != -1) { switch (ch) { case '4': @@ -207,15 +257,27 @@ else errx(1, "unsupported proxy protocol"); break; +#ifdef __OpenBSD__ + case 'C': + Cflag = optarg; + break; + case 'c': + usetls = 1; + break; +#endif case 'd': dflag = 1; break; case 'e': +#ifdef __OpenBSD__ + tls_expectname = optarg; +#else /* __OpenBSD__ */ #ifdef IPSEC ipsec_policy[ipsec_count++ % 2] = optarg; -#else +#else /* IPSEC */ errx(1, "IPsec support unavailable."); -#endif +#endif /* IPSEC */ +#endif /* __OpenBSD__ */ break; case 'E': #ifdef IPSEC @@ -228,6 +290,11 @@ case 'F': Fflag = 1; break; +#ifdef __OpenBSD__ + case 'H': + tls_expecthash = optarg; + break; +#endif case 'h': help(); break; @@ -236,6 +303,11 @@ if (errstr) errx(1, "interval %s: %s", errstr, optarg); break; +#ifdef __OpenBSD__ + case 'K': + Kflag = optarg; + break; +#endif case 'k': kflag = 1; break; @@ -243,11 +315,14 @@ lflag = 1; break; case 'M': -#ifndef WITH_STATS - errx(1, "-M requires stats(3) support"); -#else - FreeBSD_Mflag = 1; -#endif + ttl = strtonum(optarg, 0, 255, &errstr); + if (errstr) + errx(1, "ttl is %s", errstr); + break; + case 'm': + minttl = strtonum(optarg, 0, 255, &errstr); + if (errstr) + errx(1, "minttl is %s", errstr); break; case 'N': Nflag = 1; @@ -255,15 +330,18 @@ case 'n': nflag = 1; break; - case 'o': - fprintf(stderr, "option -o is deprecated.\n"); - break; case 'P': Pflag = optarg; break; case 'p': pflag = optarg; break; +#ifdef __OpenBSD__ + case 'R': + tls_cachanged = 1; + Rflag = optarg; + break; +#endif case 'r': rflag = 1; break; @@ -287,6 +365,11 @@ case 'v': vflag = 1; break; + case 'W': + recvlimit = strtonum(optarg, 1, INT_MAX, &errstr); + if (errstr) + errx(1, "receive limit %s: %s", errstr, optarg); + break; case 'w': timeout = strtonum(optarg, 0, INT_MAX / 1000, &errstr); if (errstr) @@ -298,6 +381,12 @@ if ((proxy = strdup(optarg)) == NULL) err(1, NULL); break; + case 'Z': + if (strcmp(optarg, "-") == 0) + Zflag = stderr; + else if ((Zflag = fopen(optarg, "w")) == NULL) + err(1, "can't open %s", optarg); + break; case 'z': zflag = 1; break; @@ -318,13 +407,22 @@ errstr, optarg); } break; +#ifdef __OpenBSD__ + case 'o': + oflag = optarg; + break; +#endif case 'S': Sflag = 1; break; case 'T': errstr = NULL; errno = 0; - if (map_tos(optarg, &Tflag)) +#ifdef __OpenBSD__ + if (process_tls_opt(optarg, &TLSopt)) + break; +#endif + if (process_tos_opt(optarg, &Tflag)) break; if (strlen(optarg) > 1 && optarg[0] == '0' && optarg[1] == 'x') @@ -333,7 +431,7 @@ Tflag = (int)strtonum(optarg, 0, 255, &errstr); if (Tflag < 0 || Tflag > 255 || errstr || errno) - errx(1, "illegal tos value %s", optarg); + errx(1, "illegal tos/tls value %s", optarg); break; case FREEBSD_TUN: tundev = optarg; @@ -348,21 +446,81 @@ argc -= optind; argv += optind; +#ifdef __OpenBSD__ + if (rtableid >= 0) + if (setrtable(rtableid) == -1) + err(1, "setrtable"); +#endif + /* Cruft to make sure options are clean, and used properly. */ - if (argv[0] && !argv[1] && family == AF_UNIX) { + if (argc == 1 && family == AF_UNIX) { host = argv[0]; - uport = NULL; - } else if (argv[0] && !argv[1]) { - if (!lflag) - usage(1); + } else if (argc == 1 && lflag) { uport = argv[0]; - host = NULL; - } else if (argv[0] && argv[1]) { + } else if (argc == 2) { host = argv[0]; uport = argv[1]; } else usage(1); +#ifdef __OpenBSD__ + if (usetls) { + if (Cflag && unveil(Cflag, "r") == -1) + err(1, "unveil %s", Cflag); + if (unveil(Rflag, "r") == -1) + err(1, "unveil %s", Rflag); + if (Kflag && unveil(Kflag, "r") == -1) + err(1, "unveil %s", Kflag); + if (oflag && unveil(oflag, "r") == -1) + err(1, "unveil %s", oflag); + } else if (family == AF_UNIX && uflag && lflag && !kflag) { + /* + * After recvfrom(2) from client, the server connects + * to the client socket. As the client path is determined + * during runtime, we cannot unveil(2). + */ + } else { + if (family == AF_UNIX) { + if (unveil(host, "rwc") == -1) + err(1, "unveil %s", host); + if (uflag && !kflag) { + if (sflag) { + if (unveil(sflag, "rwc") == -1) + err(1, "unveil %s", sflag); + } else { + if (unveil("/tmp", "rwc") == -1) + err(1, "unveil /tmp"); + } + } + } else { + /* no filesystem visibility */ + if (unveil("/", "") == -1) + err(1, "unveil /"); + } + } + + if (family == AF_UNIX) { + if (pledge("stdio rpath wpath cpath tmppath unix", NULL) == -1) + err(1, "pledge"); + } else if (Fflag && Pflag) { + if (pledge("stdio inet dns sendfd tty", NULL) == -1) + err(1, "pledge"); + } else if (Fflag) { + if (pledge("stdio inet dns sendfd", NULL) == -1) + err(1, "pledge"); + } else if (Pflag && usetls) { + if (pledge("stdio rpath inet dns tty", NULL) == -1) + err(1, "pledge"); + } else if (Pflag) { + if (pledge("stdio inet dns tty", NULL) == -1) + err(1, "pledge"); + } else if (usetls) { + if (pledge("stdio rpath inet dns", NULL) == -1) + err(1, "pledge"); + } else if (pledge("stdio inet dns", NULL) == -1) + err(1, "pledge"); +#endif + if (lflag && sflag) errx(1, "cannot use -s and -l"); if (lflag && pflag) @@ -384,6 +542,30 @@ if (tun_fd == -1) errx(1, "unable to open tun device %s", tundev); } + if (uflag && usetls) + errx(1, "cannot use -c and -u"); + if ((family == AF_UNIX) && usetls) + errx(1, "cannot use -c and -U"); + if ((family == AF_UNIX) && Fflag) + errx(1, "cannot use -F and -U"); + if (Fflag && usetls) + errx(1, "cannot use -c and -F"); + if (TLSopt && !usetls) + errx(1, "you must specify -c to use TLS options"); + if (Cflag && !usetls) + errx(1, "you must specify -c to use -C"); + if (Kflag && !usetls) + errx(1, "you must specify -c to use -K"); + if (Zflag && !usetls) + errx(1, "you must specify -c to use -Z"); + if (oflag && !Cflag) + errx(1, "you must specify -C to use -o"); + if (tls_cachanged && !usetls) + errx(1, "you must specify -c to use -R"); + if (tls_expecthash && !usetls) + errx(1, "you must specify -c to use -H"); + if (tls_expectname && !usetls) + errx(1, "you must specify -c to use -e"); /* Get name of temporary socket for unix datagram client */ if ((family == AF_UNIX) && uflag && !lflag) { @@ -391,7 +573,7 @@ unix_dg_tmp_socket = sflag; } else { strlcpy(unix_dg_tmp_socket_buf, "/tmp/nc.XXXXXXXXXX", - UNIX_DG_TMP_SOCKET_SIZE); + UNIX_DG_TMP_SOCKET_SIZE); if (mktemp(unix_dg_tmp_socket_buf) == NULL) err(1, "mktemp"); unix_dg_tmp_socket = unix_dg_tmp_socket_buf; @@ -422,15 +604,29 @@ 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; + if (*proxy == '[') { + ++proxy; + proxyport = strchr(proxy, ']'); + if (proxyport == NULL) + errx(1, "missing closing bracket in proxy"); + *proxyport++ = '\0'; + if (*proxyport == '\0') + /* Use default proxy port. */ + proxyport = NULL; + else { + if (*proxyport == ':') + ++proxyport; + else + errx(1, "garbage proxy port delimiter"); + } + } else { + proxyport = strrchr(proxy, ':'); + if (proxyport != NULL) + *proxyport++ = '\0'; + } memset(&proxyhints, 0, sizeof(struct addrinfo)); proxyhints.ai_family = family; @@ -440,75 +636,155 @@ proxyhints.ai_flags |= AI_NUMERICHOST; } +#ifdef __OpenBSD__ + if (usetls) { + if ((tls_cfg = tls_config_new()) == NULL) + errx(1, "unable to allocate TLS config"); + if (Rflag && tls_config_set_ca_file(tls_cfg, Rflag) == -1) + errx(1, "%s", tls_config_error(tls_cfg)); + if (Cflag && tls_config_set_cert_file(tls_cfg, Cflag) == -1) + errx(1, "%s", tls_config_error(tls_cfg)); + if (Kflag && tls_config_set_key_file(tls_cfg, Kflag) == -1) + errx(1, "%s", tls_config_error(tls_cfg)); + if (oflag && tls_config_set_ocsp_staple_file(tls_cfg, oflag) == -1) + errx(1, "%s", tls_config_error(tls_cfg)); + if (tls_config_parse_protocols(&protocols, tls_protocols) == -1) + errx(1, "invalid TLS protocols `%s'", tls_protocols); + if (tls_config_set_protocols(tls_cfg, protocols) == -1) + errx(1, "%s", tls_config_error(tls_cfg)); + if (tls_config_set_ciphers(tls_cfg, tls_ciphers) == -1) + errx(1, "%s", tls_config_error(tls_cfg)); + if (!lflag && (TLSopt & TLS_CCERT)) + errx(1, "clientcert is only valid with -l"); + if (TLSopt & TLS_NONAME) + tls_config_insecure_noverifyname(tls_cfg); + if (TLSopt & TLS_NOVERIFY) { + if (tls_expecthash != NULL) + errx(1, "-H and -T noverify may not be used " + "together"); + tls_config_insecure_noverifycert(tls_cfg); + } + if (TLSopt & TLS_MUSTSTAPLE) + tls_config_ocsp_require_stapling(tls_cfg); + + if (Pflag) { + if (pledge("stdio inet dns tty", NULL) == -1) + err(1, "pledge"); + } else if (pledge("stdio inet dns", NULL) == -1) + err(1, "pledge"); + } +#endif if (lflag) { - int connfd; ret = 0; if (family == AF_UNIX) { if (uflag) - s = unix_bind(host); + s = unix_bind(host, 0); else s = unix_listen(host); } +#ifdef __OpenBSD__ + if (usetls) { + tls_config_verify_client_optional(tls_cfg); + if ((tls_ctx = tls_server()) == NULL) + errx(1, "tls server creation failed"); + if (tls_configure(tls_ctx, tls_cfg) == -1) + errx(1, "tls configuration failed (%s)", + tls_error(tls_ctx)); + } +#endif /* Allow only one connection at a time, but stay alive. */ for (;;) { - if (family != AF_UNIX) + if (family != AF_UNIX) { + if (s != -1) + close(s); s = local_listen(host, uport, hints); - if (s < 0) + } + if (s == -1) err(1, NULL); - /* - * For UDP and -k, don't connect the socket, let it - * receive datagrams from multiple socket pairs. - */ - if (uflag && kflag) - readwrite(s); - /* - * For UDP and not -k, we will use recvfrom() initially - * to wait for a caller, then use the regular functions - * to talk to the caller. - */ - else if (uflag && !kflag) { - int rv, plen; - char buf[16384]; + if (uflag && kflag) { +#ifdef __OpenBSD__ + if (family == AF_UNIX) { + if (pledge("stdio unix", NULL) == -1) + err(1, "pledge"); + } +#endif + /* + * For UDP and -k, don't connect the socket, + * let it receive datagrams from multiple + * socket pairs. + */ + readwrite(s, NULL); + } else if (uflag && !kflag) { + /* + * For UDP and not -k, we will use recvfrom() + * initially to wait for a caller, then use + * the regular functions to talk to the caller. + */ + int rv; + char buf[2048]; struct sockaddr_storage z; len = sizeof(z); - plen = 2048; - rv = recvfrom(s, buf, plen, MSG_PEEK, + rv = recvfrom(s, buf, sizeof(buf), MSG_PEEK, (struct sockaddr *)&z, &len); - if (rv < 0) + if (rv == -1) err(1, "recvfrom"); rv = connect(s, (struct sockaddr *)&z, len); - if (rv < 0) + if (rv == -1) err(1, "connect"); +#ifdef __OpenBSD__ + if (family == AF_UNIX) { + if (pledge("stdio unix", NULL) == -1) + err(1, "pledge"); + } +#endif if (vflag) - report_connect((struct sockaddr *)&z, len); + report_sock("Connection received", + (struct sockaddr *)&z, len, + family == AF_UNIX ? host : NULL); - readwrite(s); + readwrite(s, NULL); } else { +#ifdef __OpenBSD__ + struct tls *tls_cctx = NULL; +#endif + int connfd; + len = sizeof(cliaddr); - connfd = accept(s, (struct sockaddr *)&cliaddr, - &len); + connfd = accept4(s, (struct sockaddr *)&cliaddr, + &len, SOCK_NONBLOCK); if (connfd == -1) { /* For now, all errnos are fatal */ err(1, "accept"); } if (vflag) - report_connect((struct sockaddr *)&cliaddr, len); - - if (FreeBSD_Mflag) + report_sock("Connection received", + (struct sockaddr *)&cliaddr, len, + family == AF_UNIX ? host : NULL); +#ifdef __OpenBSD__ + if ((usetls) && + (tls_cctx = tls_setup_server(tls_ctx, connfd, host))) + readwrite(connfd, tls_cctx); + if (!usetls) + readwrite(connfd, NULL); + if (tls_cctx) + timeout_tls(s, tls_cctx, tls_close); +#else + if (FreeBSD_stats) FreeBSD_stats_setup(connfd); - readwrite(connfd); + readwrite(connfd, NULL); +#endif close(connfd); +#ifdef __OpenBSD__ + tls_free(tls_cctx); +#endif } - - if (family != AF_UNIX) - close(s); - else if (uflag) { - if (connect(s, NULL, 0) < 0) + if (family == AF_UNIX && uflag) { + if (connect(s, NULL, 0) == -1) err(1, "connect"); } @@ -518,16 +794,18 @@ } else if (family == AF_UNIX) { ret = 0; - if ((s = unix_connect(host)) > 0 && !zflag) { - readwrite(s); + if ((s = unix_connect(host)) > 0) { + if (!zflag) + readwrite(s, NULL); close(s); - } else + } else { + warn("%s", host); ret = 1; + } if (uflag) unlink(unix_dg_tmp_socket); - exit(ret); - + return ret; } else { int i = 0; @@ -535,58 +813,76 @@ build_ports(uport); /* Cycle through portlist, connecting to each port. */ - for (i = 0; portlist[i] != NULL; i++) { - if (s) + for (s = -1, i = 0; portlist[i] != NULL; i++) { + if (s != -1) close(s); - +#ifdef __OpenBSD__ + tls_free(tls_ctx); + tls_ctx = NULL; + + if (usetls) { + if ((tls_ctx = tls_client()) == NULL) + errx(1, "tls client creation failed"); + if (tls_configure(tls_ctx, tls_cfg) == -1) + errx(1, "tls configuration failed (%s)", + tls_error(tls_ctx)); + } +#endif if (xflag) s = socks_connect(host, portlist[i], hints, - proxyhost, proxyport, proxyhints, socksv, + proxy, proxyport, proxyhints, socksv, Pflag); else - s = remote_connect(host, portlist[i], hints); + s = remote_connect(host, portlist[i], hints, + ipaddr); - if (s < 0) + if (s == -1) continue; ret = 0; if (vflag || zflag) { + int print_info = 1; + /* For UDP, make sure we are connected. */ if (uflag) { - if (udptest(s) == -1) { + /* No info on failed or skipped test. */ + if ((print_info = 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"); - } - - fprintf(stderr, - "Connection to %s %s port [%s/%s] " - "succeeded!\n", host, portlist[i], - uflag ? "udp" : "tcp", - sv ? sv->s_name : "*"); + if (print_info == 1) + connection_info(host, portlist[i], + uflag ? "udp" : "tcp", ipaddr); } if (Fflag) fdpass(s); - else if (!zflag) - readwrite(s); + else { +#ifdef __OpenBSD__ + if (usetls) + tls_setup_client(tls_ctx, s, host); + if (!zflag) + readwrite(s, tls_ctx); + if (tls_ctx) + timeout_tls(s, tls_ctx, tls_close); +#else + if (!zflag) + readwrite(s, NULL); +#endif + } } } - if (s) + if (s != -1) close(s); if (tun_fd != -1) close(tun_fd); +#ifdef __OpenBSD__ + tls_free(tls_ctx); + tls_config_free(tls_cfg); +#endif - exit(ret); + return ret; } /* @@ -594,33 +890,128 @@ * Returns a unix socket bound to the given path */ int -unix_bind(char *path) +unix_bind(char *path, int flags) { - struct sockaddr_un sun; - int s; + struct sockaddr_un s_un; + int s, save_errno; /* Create unix domain socket. */ - if ((s = socket(AF_UNIX, uflag ? SOCK_DGRAM : SOCK_STREAM, - 0)) < 0) - return (-1); + if ((s = socket(AF_UNIX, flags | (uflag ? SOCK_DGRAM : SOCK_STREAM), + 0)) == -1) + return -1; - memset(&sun, 0, sizeof(struct sockaddr_un)); - sun.sun_family = AF_UNIX; + memset(&s_un, 0, sizeof(struct sockaddr_un)); + s_un.sun_family = AF_UNIX; - if (strlcpy(sun.sun_path, path, sizeof(sun.sun_path)) >= - sizeof(sun.sun_path)) { + if (strlcpy(s_un.sun_path, path, sizeof(s_un.sun_path)) >= + sizeof(s_un.sun_path)) { close(s); errno = ENAMETOOLONG; - return (-1); + return -1; } - if (bind(s, (struct sockaddr *)&sun, SUN_LEN(&sun)) < 0) { + if (bind(s, (struct sockaddr *)&s_un, sizeof(s_un)) == -1) { + save_errno = errno; close(s); - return (-1); + errno = save_errno; + return -1; } - return (s); + if (vflag) + report_sock("Bound", NULL, 0, path); + + return s; } +#ifdef __OpenBSD__ +int +timeout_tls(int s, struct tls *tls_ctx, int (*func)(struct tls *)) +{ + struct pollfd pfd; + int ret; + + while ((ret = (*func)(tls_ctx)) != 0) { + if (ret == TLS_WANT_POLLIN) + pfd.events = POLLIN; + else if (ret == TLS_WANT_POLLOUT) + pfd.events = POLLOUT; + else + break; + pfd.fd = s; + if ((ret = poll(&pfd, 1, timeout)) == 1) + continue; + else if (ret == 0) { + errno = ETIMEDOUT; + ret = -1; + break; + } else + err(1, "poll failed"); + } + + return ret; +} + +void +tls_setup_client(struct tls *tls_ctx, int s, char *host) +{ + const char *errstr; + + if (tls_connect_socket(tls_ctx, s, + tls_expectname ? tls_expectname : host) == -1) { + errx(1, "tls connection failed (%s)", + tls_error(tls_ctx)); + } + if (timeout_tls(s, tls_ctx, tls_handshake) == -1) { + if ((errstr = tls_error(tls_ctx)) == NULL) + errstr = strerror(errno); + errx(1, "tls handshake failed (%s)", errstr); + } + if (vflag) + report_tls(tls_ctx, host); + if (tls_expecthash && (tls_peer_cert_hash(tls_ctx) == NULL || + strcmp(tls_expecthash, tls_peer_cert_hash(tls_ctx)) != 0)) + errx(1, "peer certificate is not %s", tls_expecthash); + if (Zflag) { + save_peer_cert(tls_ctx, Zflag); + if (Zflag != stderr && (fclose(Zflag) != 0)) + err(1, "fclose failed saving peer cert"); + } +} + +struct tls * +tls_setup_server(struct tls *tls_ctx, int connfd, char *host) +{ + struct tls *tls_cctx; + const char *errstr; + + if (tls_accept_socket(tls_ctx, &tls_cctx, connfd) == -1) { + warnx("tls accept failed (%s)", tls_error(tls_ctx)); + } else if (timeout_tls(connfd, tls_cctx, tls_handshake) == -1) { + if ((errstr = tls_error(tls_cctx)) == NULL) + errstr = strerror(errno); + warnx("tls handshake failed (%s)", errstr); + } else { + int gotcert = tls_peer_cert_provided(tls_cctx); + + if (vflag && gotcert) + report_tls(tls_cctx, host); + if ((TLSopt & TLS_CCERT) && !gotcert) + warnx("No client certificate provided"); + else if (gotcert && tls_expecthash && + (tls_peer_cert_hash(tls_cctx) == NULL || + strcmp(tls_expecthash, tls_peer_cert_hash(tls_cctx)) != 0)) + warnx("peer certificate is not %s", tls_expecthash); + else if (gotcert && tls_expectname && + (!tls_peer_cert_contains_name(tls_cctx, tls_expectname))) + warnx("name (%s) not found in client cert", + tls_expectname); + else { + return tls_cctx; + } + } + return NULL; +} +#endif + /* * unix_connect() * Returns a socket connected to a local unix socket. Returns -1 on failure. @@ -628,33 +1019,33 @@ int unix_connect(char *path) { - struct sockaddr_un sun; - int s; + struct sockaddr_un s_un; + int s, save_errno; if (uflag) { - if ((s = unix_bind(unix_dg_tmp_socket)) < 0) - return (-1); + if ((s = unix_bind(unix_dg_tmp_socket, SOCK_CLOEXEC)) == -1) + return -1; } else { - if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) - return (-1); + if ((s = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0)) == -1) + return -1; } - (void)fcntl(s, F_SETFD, FD_CLOEXEC); - memset(&sun, 0, sizeof(struct sockaddr_un)); - sun.sun_family = AF_UNIX; + memset(&s_un, 0, sizeof(struct sockaddr_un)); + s_un.sun_family = AF_UNIX; - if (strlcpy(sun.sun_path, path, sizeof(sun.sun_path)) >= - sizeof(sun.sun_path)) { + if (strlcpy(s_un.sun_path, path, sizeof(s_un.sun_path)) >= + sizeof(s_un.sun_path)) { close(s); errno = ENAMETOOLONG; - return (-1); + return -1; } - if (connect(s, (struct sockaddr *)&sun, SUN_LEN(&sun)) < 0) { + if (connect(s, (struct sockaddr *)&s_un, sizeof(s_un)) == -1) { + save_errno = errno; close(s); - return (-1); + errno = save_errno; + return -1; } - return (s); - + return s; } /* @@ -665,14 +1056,17 @@ unix_listen(char *path) { int s; - if ((s = unix_bind(path)) < 0) - return (-1); - if (listen(s, 5) < 0) { + if ((s = unix_bind(path, 0)) == -1) + return -1; + if (listen(s, 5) == -1) { close(s); - return (-1); + return -1; } - return (s); + if (vflag) + report_sock("Listening", NULL, 0, path); + + return s; } /* @@ -681,18 +1075,19 @@ * port or source address if needed. Returns -1 on failure. */ int -remote_connect(const char *host, const char *port, struct addrinfo hints) +remote_connect(const char *host, const char *port, struct addrinfo hints, + char *ipaddr) { struct addrinfo *res, *res0; - int s, error, on = 1; + int s = -1, error, herr, on = 1, save_errno; - if ((error = getaddrinfo(host, port, &hints, &res))) - errx(1, "getaddrinfo: %s", gai_strerror(error)); + if ((error = getaddrinfo(host, port, &hints, &res0))) + errx(1, "getaddrinfo for host \"%s\" port %s: %s", host, + port, gai_strerror(error)); - res0 = res; - do { - if ((s = socket(res0->ai_family, res0->ai_socktype, - res0->ai_protocol)) < 0) + for (res = res0; res; res = res->ai_next) { + if ((s = socket(res->ai_family, res->ai_socktype | + SOCK_NONBLOCK, res->ai_protocol)) == -1) continue; if (rtableid >= 0 && (setsockopt(s, SOL_SOCKET, SO_SETFIB, @@ -706,7 +1101,7 @@ /* try IP_BINDANY, but don't insist */ setsockopt(s, IPPROTO_IP, IP_BINDANY, &on, sizeof(on)); memset(&ahints, 0, sizeof(struct addrinfo)); - ahints.ai_family = res0->ai_family; + ahints.ai_family = res->ai_family; ahints.ai_socktype = uflag ? SOCK_DGRAM : SOCK_STREAM; ahints.ai_protocol = uflag ? IPPROTO_UDP : IPPROTO_TCP; ahints.ai_flags = AI_PASSIVE; @@ -714,26 +1109,49 @@ errx(1, "getaddrinfo: %s", gai_strerror(error)); if (bind(s, (struct sockaddr *)ares->ai_addr, - ares->ai_addrlen) < 0) + ares->ai_addrlen) == -1) err(1, "bind failed"); freeaddrinfo(ares); } - set_common_sockopts(s, res0->ai_family); + set_common_sockopts(s, res->ai_family); + + if (ipaddr != NULL) { + herr = getnameinfo(res->ai_addr, res->ai_addrlen, + ipaddr, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); + switch (herr) { + case 0: + break; + case EAI_SYSTEM: + err(1, "getnameinfo"); + default: + errx(1, "getnameinfo: %s", gai_strerror(herr)); + } + } - if (timeout_connect(s, res0->ai_addr, res0->ai_addrlen) == 0) + if (timeout_connect(s, res->ai_addr, res->ai_addrlen) == 0) break; - else if (vflag) - warn("connect to %s port %s (%s) failed", host, port, - uflag ? "udp" : "tcp"); + if (vflag) { + /* only print IP if there is something to report */ + if (nflag || ipaddr == NULL || + (strncmp(host, ipaddr, NI_MAXHOST) == 0)) + warn("connect to %s port %s (%s) failed", host, + port, uflag ? "udp" : "tcp"); + else + warn("connect to %s (%s) port %s (%s) failed", + host, ipaddr, port, uflag ? "udp" : "tcp"); + } + + save_errno = errno; close(s); + errno = save_errno; s = -1; - } while ((res0 = res0->ai_next) != NULL); + } - freeaddrinfo(res); + freeaddrinfo(res0); - return (s); + return s; } int @@ -741,15 +1159,9 @@ { struct pollfd pfd; socklen_t optlen; - int flags, optval; + int optval; int ret; - if (timeout != -1) { - flags = fcntl(s, F_GETFL, 0); - if (fcntl(s, F_SETFL, flags | O_NONBLOCK) == -1) - err(1, "set non-blocking mode"); - } - if ((ret = connect(s, name, namelen)) != 0 && errno == EINPROGRESS) { pfd.fd = s; pfd.events = POLLOUT; @@ -767,10 +1179,7 @@ err(1, "poll failed"); } - if (timeout != -1 && fcntl(s, F_SETFL, flags) == -1) - err(1, "restoring flags"); - - return (ret); + return ret; } /* @@ -779,10 +1188,10 @@ * address. Returns -1 on failure. */ int -local_listen(char *host, char *port, struct addrinfo hints) +local_listen(const char *host, const char *port, struct addrinfo hints) { struct addrinfo *res, *res0; - int s, ret, x = 1; + int s = -1, ret, x = 1, save_errno; int error; /* Allow nodename to be null. */ @@ -795,13 +1204,12 @@ if (host == NULL && hints.ai_family == AF_UNSPEC) hints.ai_family = AF_INET; - if ((error = getaddrinfo(host, port, &hints, &res))) + if ((error = getaddrinfo(host, port, &hints, &res0))) errx(1, "getaddrinfo: %s", gai_strerror(error)); - res0 = res; - do { - if ((s = socket(res0->ai_family, res0->ai_socktype, - res0->ai_protocol)) < 0) + for (res = res0; res; res = res->ai_next) { + if ((s = socket(res->ai_family, res->ai_socktype, + res->ai_protocol)) == -1) continue; if (rtableid >= 0 && (setsockopt(s, SOL_SOCKET, SO_SETFIB, @@ -818,24 +1226,36 @@ err(1, "disable TCP options"); } - set_common_sockopts(s, res0->ai_family); + set_common_sockopts(s, res->ai_family); - if (bind(s, (struct sockaddr *)res0->ai_addr, - res0->ai_addrlen) == 0) + if (bind(s, (struct sockaddr *)res->ai_addr, + res->ai_addrlen) == 0) break; + save_errno = errno; close(s); + errno = save_errno; s = -1; - } while ((res0 = res0->ai_next) != NULL); + } if (!uflag && s != -1) { - if (listen(s, 1) < 0) + if (listen(s, 1) == -1) err(1, "listen"); } + if (vflag && s != -1) { + struct sockaddr_storage ss; + socklen_t len; + + len = sizeof(ss); + if (getsockname(s, (struct sockaddr *)&ss, &len) == -1) + err(1, "getsockname"); + report_sock(uflag ? "Bound" : "Listening", + (struct sockaddr *)&ss, len, NULL); + } - freeaddrinfo(res); + freeaddrinfo(res0); - return (s); + return s; } /* @@ -843,7 +1263,11 @@ * Loop that polls on the network file descriptor and stdin. */ void -readwrite(int net_fd) +#ifdef __OpenBSD__ +readwrite(int net_fd, struct tls *tls_ctx) +#else +readwrite(int net_fd, void *tls_ctx) +#endif { struct pollfd pfd[4]; int stdin_fd = STDIN_FILENO; @@ -878,29 +1302,25 @@ while (1) { /* both inputs are gone, buffers are empty, we are done */ - if (pfd[POLL_STDIN].fd == -1 && pfd[POLL_NETIN].fd == -1 - && stdinbufpos == 0 && netinbufpos == 0) { - if (FreeBSD_Mflag && !stats_printed) + if (pfd[POLL_STDIN].fd == -1 && pfd[POLL_NETIN].fd == -1 && + stdinbufpos == 0 && netinbufpos == 0) { + if (FreeBSD_stats && !stats_printed) FreeBSD_stats_print(net_fd); - close(net_fd); return; } /* both outputs are gone, we can't continue */ if (pfd[POLL_NETOUT].fd == -1 && pfd[POLL_STDOUT].fd == -1) { - if (FreeBSD_Mflag && !stats_printed) + if (FreeBSD_stats && !stats_printed) FreeBSD_stats_print(net_fd); - close(net_fd); return; } /* listen and net in gone, queues empty, done */ - if (lflag && pfd[POLL_NETIN].fd == -1 - && stdinbufpos == 0 && netinbufpos == 0) { - if (FreeBSD_Mflag && !stats_printed) + if (lflag && pfd[POLL_NETIN].fd == -1 && + stdinbufpos == 0 && netinbufpos == 0) { + if (FreeBSD_stats && !stats_printed) FreeBSD_stats_print(net_fd); - close(net_fd); return; } - /* help says -i is for "wait between lines sent". We read and * write arbitrary amounts of data, and we don't want to start * scanning for newlines, so this is as good as it gets */ @@ -911,14 +1331,12 @@ num_fds = poll(pfd, 4, timeout); /* treat poll errors */ - if (num_fds == -1) { - close(net_fd); + if (num_fds == -1) err(1, "polling error"); - } /* timeout happened */ if (num_fds == 0) { - if (FreeBSD_Mflag) + if (FreeBSD_stats) FreeBSD_stats_print(net_fd); return; } @@ -932,16 +1350,16 @@ /* reading is possible after HUP */ if (pfd[POLL_STDIN].events & POLLIN && pfd[POLL_STDIN].revents & POLLHUP && - ! (pfd[POLL_STDIN].revents & POLLIN)) - pfd[POLL_STDIN].fd = -1; + !(pfd[POLL_STDIN].revents & POLLIN)) + pfd[POLL_STDIN].fd = -1; if (pfd[POLL_NETIN].events & POLLIN && pfd[POLL_NETIN].revents & POLLHUP && - ! (pfd[POLL_NETIN].revents & POLLIN)) - pfd[POLL_NETIN].fd = -1; + !(pfd[POLL_NETIN].revents & POLLIN)) + pfd[POLL_NETIN].fd = -1; if (pfd[POLL_NETOUT].revents & POLLHUP) { - if (Nflag) + if (pfd[POLL_NETOUT].fd != -1 && Nflag) shutdown(pfd[POLL_NETOUT].fd, SHUT_WR); pfd[POLL_NETOUT].fd = -1; } @@ -961,9 +1379,16 @@ /* try to read from stdin */ if (pfd[POLL_STDIN].revents & POLLIN && stdinbufpos < BUFSIZE) { ret = fillbuf(pfd[POLL_STDIN].fd, stdinbuf, - &stdinbufpos); - /* error or eof on stdin - remove from pfd */ + &stdinbufpos, NULL); +#ifdef __OpenBSD__ + if (ret == TLS_WANT_POLLIN) + pfd[POLL_STDIN].events = POLLIN; + else if (ret == TLS_WANT_POLLOUT) + pfd[POLL_STDIN].events = POLLOUT; + else if (ret == 0 || ret == -1) +#else if (ret == 0 || ret == -1) +#endif pfd[POLL_STDIN].fd = -1; /* read something - poll net out */ if (stdinbufpos > 0) @@ -975,8 +1400,16 @@ /* try to write to network */ if (pfd[POLL_NETOUT].revents & POLLOUT && stdinbufpos > 0) { ret = drainbuf(pfd[POLL_NETOUT].fd, stdinbuf, - &stdinbufpos, FreeBSD_crlf); + &stdinbufpos, tls_ctx, FreeBSD_crlf); +#ifdef __OpenBSD__ + if (ret == TLS_WANT_POLLIN) + pfd[POLL_NETOUT].events = POLLIN; + else if (ret == TLS_WANT_POLLOUT) + pfd[POLL_NETOUT].events = POLLOUT; + else if (ret == -1) +#else if (ret == -1) +#endif pfd[POLL_NETOUT].fd = -1; /* buffer empty - remove self from polling */ if (stdinbufpos == 0) @@ -988,14 +1421,28 @@ /* try to read from network */ if (pfd[POLL_NETIN].revents & POLLIN && netinbufpos < BUFSIZE) { ret = fillbuf(pfd[POLL_NETIN].fd, netinbuf, - &netinbufpos); + &netinbufpos, tls_ctx); +#ifdef __OpenBSD__ + if (ret == TLS_WANT_POLLIN) + pfd[POLL_NETIN].events = POLLIN; + else if (ret == TLS_WANT_POLLOUT) + pfd[POLL_NETIN].events = POLLOUT; + else if (ret == -1) +#else if (ret == -1) +#endif pfd[POLL_NETIN].fd = -1; /* eof on net in - remove from pfd */ if (ret == 0) { shutdown(pfd[POLL_NETIN].fd, SHUT_RD); pfd[POLL_NETIN].fd = -1; } + if (recvlimit > 0 && ++recvcount >= recvlimit) { + if (pfd[POLL_NETIN].fd != -1) + shutdown(pfd[POLL_NETIN].fd, SHUT_RD); + pfd[POLL_NETIN].fd = -1; + pfd[POLL_STDIN].fd = -1; + } /* read something - poll stdout */ if (netinbufpos > 0) pfd[POLL_STDOUT].events = POLLOUT; @@ -1003,15 +1450,23 @@ if (netinbufpos == BUFSIZE) pfd[POLL_NETIN].events = 0; /* handle telnet */ - if (tflag) + if (pfd[POLL_NETIN].fd != -1 && tflag) atelnet(pfd[POLL_NETIN].fd, netinbuf, netinbufpos); } /* try to write to stdout */ if (pfd[POLL_STDOUT].revents & POLLOUT && netinbufpos > 0) { ret = drainbuf(pfd[POLL_STDOUT].fd, netinbuf, - &netinbufpos, 0); + &netinbufpos, NULL, 0); +#ifdef __OpenBSD__ + if (ret == TLS_WANT_POLLIN) + pfd[POLL_STDOUT].events = POLLIN; + else if (ret == TLS_WANT_POLLOUT) + pfd[POLL_STDOUT].events = POLLOUT; + else if (ret == -1) +#else if (ret == -1) +#endif pfd[POLL_STDOUT].fd = -1; /* buffer empty - remove self from polling */ if (netinbufpos == 0) @@ -1024,7 +1479,7 @@ /* stdin gone and queue empty? */ if (pfd[POLL_STDIN].fd == -1 && stdinbufpos == 0) { if (pfd[POLL_NETOUT].fd != -1 && Nflag) { - if (FreeBSD_Mflag) { + if (FreeBSD_stats) { FreeBSD_stats_print(net_fd); stats_printed = 1; } @@ -1048,12 +1503,19 @@ } ssize_t -drainbuf(int fd, unsigned char *buf, size_t *bufpos, int crlf) +#ifdef __OpenBSD__ +drainbuf(int fd, unsigned char *buf, size_t *bufpos, struct tls *tls) +#else +drainbuf(int fd, unsigned char *buf, size_t *bufpos, void *tls __unused, int crlf) +#endif { ssize_t n = *bufpos, n2 = 0; ssize_t adjust; unsigned char *lf = NULL; + if (fd == -1) + return -1; + if (crlf) { lf = memchr(buf, '\n', *bufpos); if (lf && (lf == buf || *(lf - 1) != '\r')) @@ -1062,6 +1524,20 @@ lf = NULL; } +#ifdef __OpenBSD__ + if (tls) { + n = tls_write(tls, buf, *bufpos); + if (n == -1) + errx(1, "tls write failed (%s)", tls_error(tls)); + } else { + n = write(fd, buf, *bufpos); + /* don't treat EAGAIN, EINTR as error */ + if (n == -1 && (errno == EAGAIN || errno == EINTR)) + n = TLS_WANT_POLLOUT; + } + if (n <= 0) + return n; +#else if (n != 0) { n = write_wrapper(fd, buf, n); if (n <= 0) @@ -1074,7 +1550,7 @@ return n2; n += 1; } - +#endif /* adjust buffer */ adjust = *bufpos - n; if (adjust > 0) @@ -1083,17 +1559,36 @@ return n; } - ssize_t -fillbuf(int fd, unsigned char *buf, size_t *bufpos) +#ifdef __OpenBSD__ +fillbuf(int fd, unsigned char *buf, size_t *bufpos, struct tls *tls) +#else +fillbuf(int fd, unsigned char *buf, size_t *bufpos, void *tls __unused) +#endif { size_t num = BUFSIZE - *bufpos; ssize_t n; + if (fd == -1) + return -1; + +#ifdef __OpenBSD__ + if (tls) { + n = tls_read(tls, buf + *bufpos, num); + if (n == -1) + errx(1, "tls read failed (%s)", tls_error(tls)); + } else { + n = read(fd, buf + *bufpos, num); + /* don't treat EAGAIN, EINTR as error */ + if (n == -1 && (errno == EAGAIN || errno == EINTR)) + n = TLS_WANT_POLLIN; + } +#else n = read(fd, buf + *bufpos, num); /* don't treat EAGAIN, EINTR as error */ if (n == -1 && (errno == EAGAIN || errno == EINTR)) n = -2; +#endif if (n <= 0) return n; *bufpos += n; @@ -1122,9 +1617,9 @@ if (isatty(STDOUT_FILENO)) errx(1, "Cannot pass file descriptor to tty"); - bzero(&mh, sizeof(mh)); - bzero(&cmsgbuf, sizeof(cmsgbuf)); - bzero(&iov, sizeof(iov)); + memset(&mh, 0, sizeof(mh)); + memset(&cmsgbuf, 0, sizeof(cmsgbuf)); + memset(&iov, 0, sizeof(iov)); mh.msg_control = (caddr_t)&cmsgbuf.buf; mh.msg_controllen = sizeof(cmsgbuf.buf); @@ -1139,7 +1634,7 @@ mh.msg_iov = &iov; mh.msg_iovlen = 1; - bzero(&pfd, sizeof(pfd)); + memset(&pfd, 0, sizeof(pfd)); pfd.fd = STDOUT_FILENO; pfd.events = POLLOUT; for (;;) { @@ -1190,6 +1685,26 @@ } } +int +strtoport(char *portstr, int udp) +{ + struct servent *entry; + const char *errstr; + char *proto; + int port = -1; + + proto = udp ? "udp" : "tcp"; + + port = strtonum(portstr, 1, PORT_MAX, &errstr); + if (errstr == NULL) + return port; + if (errno != EINVAL) + errx(1, "port number %s: %s", errstr, portstr); + if ((entry = getservbyname(portstr, proto)) == NULL) + errx(1, "service \"%s\" unknown", portstr); + return ntohs(entry->s_port); +} + /* * build_ports() * Build an array of ports in portlist[], listing each port @@ -1198,56 +1713,48 @@ void build_ports(char *p) { - const char *errstr; char *n; int hi, lo, cp; int x = 0; - if ((n = strchr(p, '-')) != NULL) { + if (isdigit((unsigned char)*p) && (n = strchr(p, '-')) != NULL) { *n = '\0'; n++; /* Make sure the ports are in order: lowest->highest. */ - hi = strtonum(n, 1, PORT_MAX, &errstr); - if (errstr) - errx(1, "port number %s: %s", errstr, n); - lo = strtonum(p, 1, PORT_MAX, &errstr); - if (errstr) - errx(1, "port number %s: %s", errstr, p); - + hi = strtoport(n, uflag); + lo = strtoport(p, uflag); 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. */ + /* + * Initialize portlist with a random permutation. Based on + * Knuth, as in ip_randomid() in sys/netinet/ip_id.c. + */ 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; + for (x = 0; x <= hi - lo; x++) { + cp = arc4random_uniform(x + 1); + portlist[x] = portlist[cp]; + if (asprintf(&portlist[cp], "%d", x + lo) == -1) + err(1, "asprintf"); + } + } else { /* Load ports sequentially. */ + for (cp = lo; cp <= hi; cp++) { + if (asprintf(&portlist[x], "%d", cp) == -1) + err(1, "asprintf"); + x++; } } } else { - hi = strtonum(p, 1, PORT_MAX, &errstr); - if (errstr) - errx(1, "port number %s: %s", errstr, p); - portlist[0] = strdup(p); - if (portlist[0] == NULL) + char *tmp; + + hi = strtoport(p, uflag); + if (asprintf(&tmp, "%d", hi) != -1) + portlist[0] = tmp; + else err(1, NULL); } } @@ -1262,13 +1769,43 @@ { int i, ret; + /* Only write to the socket in scan mode or interactive mode. */ + if (!zflag && !isatty(STDIN_FILENO)) + return 0; + for (i = 0; i <= 3; i++) { if (write(s, "X", 1) == 1) ret = 1; else ret = -1; } - return (ret); + return ret; +} + +void +connection_info(const char *host, const char *port, const char *proto, + const char *ipaddr) +{ + struct servent *sv; + char *service = "*"; + + /* Look up service name unless -n. */ + if (!nflag) { + sv = getservbyport(ntohs(atoi(port)), proto); + if (sv != NULL) + service = sv->s_name; + } + + fprintf(stderr, "Connection to %s", host); + + /* + * if we aren't connecting thru a proxy and + * there is something to report, print IP + */ + if (!nflag && !xflag && strcmp(host, ipaddr) != 0) + fprintf(stderr, " (%s)", ipaddr); + + fprintf(stderr, " %s port [%s/%s] succeeded!\n", port, proto, service); } void @@ -1276,7 +1813,7 @@ { if (setsockopt(s, IPPROTO_TCP, TCP_STATS, - &FreeBSD_Mflag, sizeof(FreeBSD_Mflag)) == -1) { + &FreeBSD_stats, sizeof(FreeBSD_stats)) == -1) { if (errno == EOPNOTSUPP) { warnx("getsockopt(TCP_STATS) failed; " "kernel built without \"options STATS\"?"); @@ -1339,27 +1876,22 @@ if (Sflag) { if (setsockopt(s, IPPROTO_TCP, TCP_MD5SIG, - &x, sizeof(x)) == -1) + &x, sizeof(x)) == -1) err(1, NULL); } if (Dflag) { if (setsockopt(s, SOL_SOCKET, SO_DEBUG, - &x, sizeof(x)) == -1) + &x, sizeof(x)) == -1) err(1, NULL); } if (Tflag != -1) { - int proto, option; - - if (af == AF_INET6) { - proto = IPPROTO_IPV6; - option = IPV6_TCLASS; - } else { - proto = IPPROTO_IP; - option = IP_TOS; - } - - if (setsockopt(s, proto, option, &Tflag, sizeof(Tflag)) == -1) + if (af == AF_INET && setsockopt(s, IPPROTO_IP, + IP_TOS, &Tflag, sizeof(Tflag)) == -1) err(1, "set IP ToS"); + + else if (af == AF_INET6 && setsockopt(s, IPPROTO_IPV6, + IPV6_TCLASS, &Tflag, sizeof(Tflag)) == -1) + err(1, "set IPv6 traffic class"); } if (Iflag) { if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, @@ -1376,7 +1908,7 @@ &FreeBSD_Oflag, sizeof(FreeBSD_Oflag)) == -1) err(1, "disable TCP options"); } - if (FreeBSD_Mflag) + if (FreeBSD_stats) FreeBSD_stats_setup(s); #ifdef IPSEC if (ipsec_policy[0] != NULL) @@ -1384,10 +1916,35 @@ if (ipsec_policy[1] != NULL) add_ipsec_policy(s, af, ipsec_policy[1]); #endif + + if (ttl != -1) { + if (af == AF_INET && setsockopt(s, IPPROTO_IP, + IP_TTL, &ttl, sizeof(ttl))) + err(1, "set IP TTL"); + + else if (af == AF_INET6 && setsockopt(s, IPPROTO_IPV6, + IPV6_UNICAST_HOPS, &ttl, sizeof(ttl))) + err(1, "set IPv6 unicast hops"); + } + + if (minttl != -1) { + if (af == AF_INET && setsockopt(s, IPPROTO_IP, + IP_MINTTL, &minttl, sizeof(minttl))) + err(1, "set IP min TTL"); + +#ifdef __OpenBSD__ + else if (af == AF_INET6 && setsockopt(s, IPPROTO_IPV6, + IPV6_MINHOPCOUNT, &minttl, sizeof(minttl))) + err(1, "set IPv6 min hop count"); +#else + else if (af == AF_INET6) + warn("Unable to set IPv6 min hop count"); +#endif + } } int -map_tos(char *s, int *val) +process_tos_opt(char *s, int *val) { /* DiffServ Codepoints and other TOS mappings */ const struct toskeywords { @@ -1421,43 +1978,163 @@ { "netcontrol", IPTOS_PREC_NETCONTROL }, { "reliability", IPTOS_RELIABILITY }, { "throughput", IPTOS_THROUGHPUT }, - { NULL, -1 }, + { NULL, -1 }, }; for (t = toskeywords; t->keyword != NULL; t++) { if (strcmp(s, t->keyword) == 0) { *val = t->val; - return (1); + return 1; } } - return (0); + return 0; +} + +#ifdef __OpenBSD__ +int +process_tls_opt(char *s, int *flags) +{ + size_t len; + char *v; + + const struct tlskeywords { + const char *keyword; + int flag; + char **value; + } *t, tlskeywords[] = { + { "ciphers", -1, &tls_ciphers }, + { "clientcert", TLS_CCERT, NULL }, + { "muststaple", TLS_MUSTSTAPLE, NULL }, + { "noverify", TLS_NOVERIFY, NULL }, + { "noname", TLS_NONAME, NULL }, + { "protocols", -1, &tls_protocols }, + { NULL, -1, NULL }, + }; + + len = strlen(s); + if ((v = strchr(s, '=')) != NULL) { + len = v - s; + v++; + } + + for (t = tlskeywords; t->keyword != NULL; t++) { + if (strlen(t->keyword) == len && + strncmp(s, t->keyword, len) == 0) { + if (t->value != NULL) { + if (v == NULL) + errx(1, "invalid tls value `%s'", s); + *t->value = v; + } else { + *flags |= t->flag; + } + return 1; + } + } + return 0; +} + +void +save_peer_cert(struct tls *tls_ctx, FILE *fp) +{ + const char *pem; + size_t plen; + + if ((pem = tls_peer_cert_chain_pem(tls_ctx, &plen)) == NULL) + errx(1, "Can't get peer certificate"); + if (fprintf(fp, "%.*s", (int)plen, pem) < 0) + err(1, "unable to save peer cert"); + if (fflush(fp) != 0) + err(1, "unable to flush peer cert"); } void -report_connect(const struct sockaddr *sa, socklen_t salen) +report_tls(struct tls *tls_ctx, char *host) { - char remote_host[NI_MAXHOST]; - char remote_port[NI_MAXSERV]; + time_t t; + const char *ocsp_url; + + fprintf(stderr, "TLS handshake negotiated %s/%s with host %s\n", + tls_conn_version(tls_ctx), tls_conn_cipher(tls_ctx), host); + fprintf(stderr, "Peer name: %s\n", + tls_expectname ? tls_expectname : host); + if (tls_peer_cert_subject(tls_ctx)) + fprintf(stderr, "Subject: %s\n", + tls_peer_cert_subject(tls_ctx)); + if (tls_peer_cert_issuer(tls_ctx)) + fprintf(stderr, "Issuer: %s\n", + tls_peer_cert_issuer(tls_ctx)); + if ((t = tls_peer_cert_notbefore(tls_ctx)) != -1) + fprintf(stderr, "Valid From: %s", ctime(&t)); + if ((t = tls_peer_cert_notafter(tls_ctx)) != -1) + fprintf(stderr, "Valid Until: %s", ctime(&t)); + if (tls_peer_cert_hash(tls_ctx)) + fprintf(stderr, "Cert Hash: %s\n", + tls_peer_cert_hash(tls_ctx)); + ocsp_url = tls_peer_ocsp_url(tls_ctx); + if (ocsp_url != NULL) + fprintf(stderr, "OCSP URL: %s\n", ocsp_url); + switch (tls_peer_ocsp_response_status(tls_ctx)) { + case TLS_OCSP_RESPONSE_SUCCESSFUL: + fprintf(stderr, "OCSP Stapling: %s\n", + tls_peer_ocsp_result(tls_ctx) == NULL ? "" : + tls_peer_ocsp_result(tls_ctx)); + fprintf(stderr, + " response_status=%d cert_status=%d crl_reason=%d\n", + tls_peer_ocsp_response_status(tls_ctx), + tls_peer_ocsp_cert_status(tls_ctx), + tls_peer_ocsp_crl_reason(tls_ctx)); + t = tls_peer_ocsp_this_update(tls_ctx); + fprintf(stderr, " this update: %s", + t != -1 ? ctime(&t) : "\n"); + t = tls_peer_ocsp_next_update(tls_ctx); + fprintf(stderr, " next update: %s", + t != -1 ? ctime(&t) : "\n"); + t = tls_peer_ocsp_revocation_time(tls_ctx); + fprintf(stderr, " revocation: %s", + t != -1 ? ctime(&t) : "\n"); + break; + case -1: + break; + default: + fprintf(stderr, + "OCSP Stapling: failure - response_status %d (%s)\n", + tls_peer_ocsp_response_status(tls_ctx), + tls_peer_ocsp_result(tls_ctx) == NULL ? "" : + tls_peer_ocsp_result(tls_ctx)); + break; + } +} +#endif + +void +report_sock(const char *msg, const struct sockaddr *sa, socklen_t salen, + char *path) +{ + char host[NI_MAXHOST], port[NI_MAXSERV]; int herr; int flags = NI_NUMERICSERV; - + + if (path != NULL) { + fprintf(stderr, "%s on %s\n", msg, path); + return; + } + if (nflag) flags |= NI_NUMERICHOST; - - if ((herr = getnameinfo(sa, salen, - remote_host, sizeof(remote_host), - remote_port, sizeof(remote_port), - flags)) != 0) { - if (herr == EAI_SYSTEM) - err(1, "getnameinfo"); - else - errx(1, "getnameinfo: %s", gai_strerror(herr)); + + herr = getnameinfo(sa, salen, host, sizeof(host), port, sizeof(port), + flags); + switch (herr) { + case 0: + break; + case EAI_SYSTEM: + err(1, "getnameinfo"); + default: + errx(1, "getnameinfo: %s", gai_strerror(herr)); } - - fprintf(stderr, - "Connection from %s %s " - "received!\n", remote_host, remote_port); + + fprintf(stderr, "%s on %s %s\n", msg, host, port); } void @@ -1479,27 +2156,31 @@ \t-F Pass socket fd\n\ \t-h This help text\n\ \t-I length TCP receive buffer length\n\ - \t-i secs\t Delay interval for lines sent, ports scanned\n\ + \t-i interval 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-M ttl Outgoing TTL / Hop Limit\n\ + \t-m minttl Minimum incoming TTL / Hop Limit\n\ \t-N Shutdown the network socket after EOF on stdin\n\ \t-n Suppress name/port resolutions\n\ \t--no-tcpopt Disable TCP options\n\ - \t--sctp\t SCTP mode\n\ - \t--tun tundev Use tun device rather than stdio\n\ \t-O length TCP send buffer length\n\ \t-P proxyuser\tUsername for proxy authentication\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 toskeyword\tSet IP Type of Service\n\ + \t-s sourceaddr Local source address\n\ + \t--sctp\t SCTP mode\n\ + \t--stats Report TCP_STATS via the stats(3) interface\n\ + \t-T keyword TOS value or TLS options\n\ \t-t Answer TELNET negotiation\n\ + \t--tun tundev Use tun device rather than stdio\n\ \t-U Use UNIX domain socket\n\ \t-u UDP mode\n\ - \t-V rtable Specify alternate routing table\n\ + \t-V FIB Specify alternate routing table\n\ \t-v Verbose\n\ - \t-w secs\t Timeout for connects and final net reads\n\ + \t-W recvlimit Terminate after receiving a number of packets\n\ + \t-w timeout 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\ @@ -1541,14 +2222,14 @@ { fprintf(stderr, #ifdef IPSEC - "usage: nc [-46DdEFhklNnrStUuvz] [-e policy] [-I length] [-i interval] [-O length]\n" + "usage: nc [-46DdEFhklNnrStUuvz] [--crlf] [-e policy] [-I length] [-i interval]\n" #else - "usage: nc [-46DdFhklNnrStUuvz] [-I length] [-i interval] [-O length]\n" + "usage: nc [-46DdFhklNnrStUuvz] [--crlf] [-I length] [-i interval]\n" #endif - "\t [--no-tcpopt] [--sctp]\n" - "\t [-P proxy_username] [-p source_port] [-s source] [-T ToS]\n" - "\t [--tun tundev] [-V rtable] [-w timeout] [-X proxy_protocol]\n" - "\t [-x proxy_address[:port]] [destination] [port]\n"); + "\t [-M ttl] [-m minttl] [--no-tcpopt] [-O length] [-P proxy_username]\n" + "\t [-p source_port] [-s sourceaddr] [--sctp] [--stats] [-T ToS]\n" + "\t [--tun tundev] [-V FIB] [-W recvlimit] [-w timeout]\n" + "\t [-X proxy_protocol] [-x proxy_address[:port]] [destination] [port]\n"); if (ret) exit(1); } diff --git a/contrib/netcat/socks.c b/contrib/netcat/socks.c --- a/contrib/netcat/socks.c +++ b/contrib/netcat/socks.c @@ -1,4 +1,4 @@ -/* $OpenBSD: socks.c,v 1.21 2015/03/26 21:19:51 tobias Exp $ */ +/* $OpenBSD: socks.c,v 1.31 2022/06/08 20:20:26 djm Exp $ */ /* * Copyright (c) 1999 Niklas Hallqvist. All rights reserved. @@ -53,7 +53,7 @@ #define SOCKS_DOMAIN 3 #define SOCKS_IPV6 4 -int remote_connect(const char *, const char *, struct addrinfo); +int remote_connect(const char *, const char *, struct addrinfo, char *); int socks_connect(const char *, const char *, struct addrinfo, const char *, const char *, struct addrinfo, int, const char *); @@ -65,7 +65,7 @@ int r; struct addrinfo hints, *res; - bzero(&hints, sizeof(hints)); + memset(&hints, 0, sizeof(hints)); hints.ai_family = v4only ? PF_INET : PF_UNSPEC; hints.ai_flags = numeric ? AI_NUMERICHOST : 0; hints.ai_socktype = SOCK_STREAM; @@ -109,17 +109,68 @@ return (off); } -static const char * -getproxypass(const char *proxyuser, const char *proxyhost) +static void +getproxypass(const char *proxyuser, const char *proxyhost, + char *pw, size_t pwlen) { char prompt[512]; - static char pw[256]; snprintf(prompt, sizeof(prompt), "Proxy password for %s@%s: ", proxyuser, proxyhost); - if (readpassphrase(prompt, pw, sizeof(pw), RPP_REQUIRE_TTY) == NULL) + if (readpassphrase(prompt, pw, pwlen, RPP_REQUIRE_TTY) == NULL) errx(1, "Unable to read proxy passphrase"); - return (pw); +} + +/* + * Error strings adapted from the generally accepted SOCKSv4 spec: + * + * http://ftp.icm.edu.pl/packages/socks/socks4/SOCKS4.protocol + */ +static const char * +socks4_strerror(int e) +{ + switch (e) { + case 90: + return "Succeeded"; + case 91: + return "Request rejected or failed"; + case 92: + return "SOCKS server cannot connect to identd on the client"; + case 93: + return "Client program and identd report different user-ids"; + default: + return "Unknown error"; + } +} + +/* + * Error strings taken almost directly from RFC 1928. + */ +static const char * +socks5_strerror(int e) +{ + switch (e) { + case 0: + return "Succeeded"; + case 1: + return "General SOCKS server failure"; + case 2: + return "Connection not allowed by ruleset"; + case 3: + return "Network unreachable"; + case 4: + return "Host unreachable"; + case 5: + return "Connection refused"; + case 6: + return "TTL expired"; + case 7: + return "Command not supported"; + case 8: + return "Address type not supported"; + default: + return "Unknown error"; + } } int @@ -136,7 +187,6 @@ struct sockaddr_in *in4 = (struct sockaddr_in *)&addr; struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)&addr; in_port_t serverport; - const char *proxypass = NULL; if (proxyport == NULL) proxyport = (socksv == -1) ? HTTP_PROXY_PORT : SOCKS_PORT; @@ -151,7 +201,7 @@ if (authretry++ > 3) errx(1, "Too many authentication failures"); - proxyfd = remote_connect(proxyhost, proxyport, proxyhints); + proxyfd = remote_connect(proxyhost, proxyport, proxyhints, NULL); if (proxyfd < 0) return (-1); @@ -189,7 +239,7 @@ buf[2] = 0; buf[3] = SOCKS_DOMAIN; buf[4] = hlen; - memcpy(buf + 5, host, hlen); + memcpy(buf + 5, host, hlen); memcpy(buf + 5 + hlen, &serverport, sizeof serverport); wlen = 7 + hlen; break; @@ -225,8 +275,10 @@ cnt = atomicio(read, proxyfd, buf, 4); if (cnt != 4) err(1, "read failed (%zu/4)", cnt); - if (buf[1] != 0) - errx(1, "connection failed, SOCKS error %d", buf[1]); + if (buf[1] != 0) { + errx(1, "connection failed, SOCKSv5 error: %s", + socks5_strerror(buf[1])); + } switch (buf[3]) { case SOCKS_IPV4: cnt = atomicio(read, proxyfd, buf + 4, 6); @@ -261,13 +313,15 @@ cnt = atomicio(read, proxyfd, buf, 8); if (cnt != 8) err(1, "read failed (%zu/8)", cnt); - if (buf[1] != 90) - errx(1, "connection failed, SOCKS error %d", buf[1]); + if (buf[1] != 90) { + errx(1, "connection failed, SOCKSv4 error: %s", + socks4_strerror(buf[1])); + } } else if (socksv == -1) { /* HTTP proxy CONNECT */ /* Disallow bad chars in hostname */ - if (strcspn(host, "\r\n\t []:") != strlen(host)) + if (strcspn(host, "\r\n\t []") != strlen(host)) errx(1, "Invalid hostname"); /* Try to be sane about numeric IPv6 addresses */ @@ -280,7 +334,7 @@ "CONNECT %s:%d HTTP/1.0\r\n", host, ntohs(serverport)); } - if (r == -1 || (size_t)r >= sizeof(buf)) + if (r < 0 || (size_t)r >= sizeof(buf)) errx(1, "hostname too long"); r = strlen(buf); @@ -289,22 +343,27 @@ err(1, "write failed (%zu/%d)", cnt, r); if (authretry > 1) { + char proxypass[256]; char resp[1024]; - proxypass = getproxypass(proxyuser, proxyhost); + getproxypass(proxyuser, proxyhost, + proxypass, sizeof proxypass); r = snprintf(buf, sizeof(buf), "%s:%s", proxyuser, proxypass); + explicit_bzero(proxypass, sizeof proxypass); if (r == -1 || (size_t)r >= sizeof(buf) || b64_ntop(buf, strlen(buf), resp, sizeof(resp)) == -1) errx(1, "Proxy username/password too long"); r = snprintf(buf, sizeof(buf), "Proxy-Authorization: " "Basic %s\r\n", resp); - if (r == -1 || (size_t)r >= sizeof(buf)) + if (r < 0 || (size_t)r >= sizeof(buf)) errx(1, "Proxy auth response too long"); r = strlen(buf); if ((cnt = atomicio(vwrite, proxyfd, buf, r)) != r) err(1, "write failed (%zu/%d)", cnt, r); + explicit_bzero(proxypass, sizeof proxypass); + explicit_bzero(buf, sizeof buf); } /* Terminate headers */ @@ -314,7 +373,8 @@ /* Read status reply */ proxy_read_line(proxyfd, buf, sizeof(buf)); if (proxyuser != NULL && - strncmp(buf, "HTTP/1.0 407 ", 12) == 0) { + (strncmp(buf, "HTTP/1.0 407 ", 12) == 0 || + strncmp(buf, "HTTP/1.1 407 ", 12) == 0)) { if (authretry > 1) { fprintf(stderr, "Proxy authentication " "failed\n");