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