diff --git a/utils/.gitignore b/utils/.gitignore index 645b3256..ce68ef78 100644 --- a/utils/.gitignore +++ b/utils/.gitignore @@ -33,6 +33,7 @@ memstat mkdir mktemp mv +nc nl pager passwd diff --git a/utils/Makefile b/utils/Makefile index a218a727..1921f4ba 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -44,6 +44,7 @@ memstat \ mkdir \ mktemp \ mv \ +nc \ nl \ pager \ passwd \ @@ -84,6 +85,7 @@ chkblayout.1 \ chvideomode.1 \ kernelinfo.1 \ logname.1 \ +nc.1 \ memstat.1 \ pager.1 \ passwd.1 \ diff --git a/utils/nc.c b/utils/nc.c new file mode 100644 index 00000000..6684f04d --- /dev/null +++ b/utils/nc.c @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2016, 2023 Jonas 'Sortie' Termansen. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * nc.c + * Network client and server. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int fd; +static char outgoing[65536]; +static char incoming[65536]; + +static void* write_thread(void* ctx) +{ + (void) ctx; + ssize_t amount; + while ( 0 < (amount = read(0, outgoing, sizeof(outgoing))) ) + { + ssize_t sofar = 0; + while ( sofar < amount ) + { + // TODO: How to handle EPIPE. Is MSG_NOSIGNAL desirable? + int flags = MSG_NOSIGNAL; + ssize_t done = send(fd, outgoing + sofar, amount - sofar, flags); + if ( done <= 0 ) + err(1, "send"); + sofar += done; + } + } + if ( amount < 0 ) + err(1, "stdin: read"); + if ( shutdown(fd, SHUT_WR) < 0 ) + err(1, "shutdown"); + return NULL; +} + +int main(int argc, char* argv[]) +{ + bool flag_ipv4 = false; + bool flag_ipv6 = false; + bool flag_listen = false; + bool flag_udp = false; + bool flag_verbose = false; + + int opt; + while ( (opt = getopt(argc, argv, "46luv")) != -1 ) + { + switch ( opt ) + { + case '4': flag_ipv4 = true; break; + case '6': flag_ipv6 = true; break; + case 'l': flag_listen = true; break; + case 'u': flag_udp = true; break; + case 'v': flag_verbose = true; break; + default: return 1; + } + } + + if ( argc - optind < 1 ) + errx(1, "No host given"); + if ( argc - optind < 2 ) + errx(1, "No service given"); + if ( argc - optind > 2 ) + errx(1, "Unexpected extra operand: %s", argv[optind + 2]); + const char* host = argv[optind + 0]; + const char* service = argv[optind + 1]; + + if ( 1 < flag_ipv4 + flag_ipv6 ) + errx(1, "The -4 and -6 options are incompatible"); + + struct addrinfo hints = + { + .ai_flags = flag_listen ? AI_PASSIVE : 0, + .ai_family = flag_ipv6 ? AF_INET6 : flag_ipv4 ? AF_INET : AF_UNSPEC, + .ai_socktype = flag_udp ? SOCK_DGRAM : SOCK_STREAM, + .ai_protocol = 0, + }; + struct addrinfo* res0 = NULL; + int status = getaddrinfo(host, service, &hints, &res0); + if ( status == EAI_SYSTEM ) + err(1, "%s", host); + if ( status ) + errx(1, "%s: %s", host, gai_strerror(status)); + if ( !res0 ) + errx(1, "%s: %s", host, gai_strerror(EAI_NONAME)); + + for ( struct addrinfo* res = res0; res; res = res->ai_next ) + { + if ( (fd = socket(res->ai_family, res->ai_socktype, + res->ai_protocol)) < 0 ) + { + if ( res->ai_next ) + continue; + err(1, "socket"); + } + if ( flag_listen ) + { + if ( bind(fd, res->ai_addr, res->ai_addrlen) < 0 ) + { + close(fd); + if ( res->ai_next ) + continue; + err(1, "bind: %s: %s", host, service); + } + if ( listen(fd, 1) < 0 ) + err(1, "listen: %s: %s", host, service); + if ( flag_verbose ) + { + char host[NI_MAXHOST]; + char serv[NI_MAXSERV]; + if ( getnameinfo(res->ai_addr, res->ai_addrlen, + host, sizeof(host), serv, sizeof(serv), + NI_NUMERICHOST | NI_NUMERICSERV) < 0 ) + { + strlcpy(host, "unknown", sizeof(host)); + strlcpy(serv, "unknown", sizeof(serv)); + } + fprintf(stderr, "Listening on %s:%s\n", host, serv); + } + // TODO: UDP support. + struct sockaddr_storage addr; + socklen_t addrlen = sizeof(addr); + int nfd = accept(fd, (struct sockaddr*) &addr, &addrlen); + if ( nfd < 0 ) + err(1, "accept: %s: %s", host, service); + close(fd); + fd = nfd; + if ( flag_verbose ) + { + char host[NI_MAXHOST]; + char serv[NI_MAXSERV]; + if ( getnameinfo((const struct sockaddr*) &addr, addrlen, + host, sizeof(host), serv, sizeof(serv), + NI_NUMERICHOST | NI_NUMERICSERV) < 0 ) + { + strlcpy(host, "unknown", sizeof(host)); + strlcpy(serv, "unknown", sizeof(serv)); + } + fprintf(stderr, "Connection from %s:%s\n", host, serv); + } + } + else + { + if ( connect(fd, res->ai_addr, res->ai_addrlen) < 0 ) + { + close(fd); + if ( res->ai_next ) + continue; + err(1, "connect: %s: %s", host, service); + } + if ( flag_verbose ) + { + char host[NI_MAXHOST]; + char serv[NI_MAXSERV]; + if ( getnameinfo(res->ai_addr, res->ai_addrlen, + host, sizeof(host), serv, sizeof(serv), + NI_NUMERICHOST | NI_NUMERICSERV) < 0 ) + { + strlcpy(host, "unknown", sizeof(host)); + strlcpy(serv, "unknown", sizeof(serv)); + } + fprintf(stderr, "Connected to %s:%s\n", host, serv); + } + } + break; + } + + freeaddrinfo(res0); + + pthread_t write_pthread; + int errnum; + if ( (errnum = pthread_create(&write_pthread, NULL, write_thread, NULL)) ) + { + errno = errnum; + err(1, "pthread_create"); + } + + ssize_t amount; + while ( 0 < (amount = recv(fd, incoming, sizeof(incoming), 0)) ) + { + ssize_t sofar = 0; + while ( sofar < amount ) + { + // TODO: How to handle if this causes SIGPIPE? + ssize_t done = write(1, incoming + sofar, amount - sofar); + if ( done <= 0 ) + err(1, "stdout: write"); + sofar += done; + } + } + if ( amount < 0 ) + err(1, "recv"); + + pthread_join(write_pthread, NULL); + + close(fd); + + return 0; +}