/* Copyright 2001 Mark Pulford <mark@kyne.com.au>
* This file is subject to the terms and conditions of the GNU General Public
* License. Read the file COPYING found in this archive for details, or
* visit http://www.gnu.org/copyleft/gpl.html
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <netdb.h>
#include <errno.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif
/* Configuration vars */
char *output_name = "-";
int output_fd;
int block_size = 512;
int verbose = 0;
int tcp_nodelay = 0;
#ifdef TCP_CORK
int tcp_cork = 0;
#endif
int total_written = 0;
int make_connect(const char *host, int port);
int wait_connect(int port);
int open_stream(const char *name, int write);
int resolv_addr(struct in_addr *in, const char *addr);
int output(int rfd, char *rname);
int safe_write(int fd, const char *buf, int size);
void init(int argc, char **argv);
char next_arg(int argc, char **argv);
void set_tcp_opt(int fd, int opt);
int size2num(int *size, const char *s);
void usage();
void version();
void warn(const char *msg, ...);
#ifndef HAVE_HSTRERROR
#define hstrerror(a) strherror(a)
char *strherror(int e);
#endif
#ifndef HAVE_INET_ATON
int inet_aton(const char *cp, struct in_addr *inp);
#endif
int main(int argc, char **argv)
{
int rfd;
int i;
int err = 0;
init(argc, argv);
if(optind < argc) {
for(i=optind; i<argc; i++) {
rfd = open_stream(argv[i],0);
if(rfd < 0 || !output(rfd, argv[i]))
err++;
}
} else {
rfd = open_stream("-", 0);
if(!output(rfd, "stdin"))
err++;
}
if(verbose)
warn("%s:\t%d bytes written", output_name, total_written);
return err?1:0;
}
/* Init output_fd and block size or die trying */
void init(int argc, char **argv)
{
int ch;
ch = next_arg(argc, argv);
while(ch != -1) {
switch(ch) {
case ':': case '?':
usage();
exit(-1);
case 'h':
usage();
exit(0);
case 'V':
version();
exit(0);
case 'v':
verbose++;
break;
case 'b':
if(!size2num(&block_size, optarg)) {
warn("bad number given for --block-size");
exit(-1);
}
break;
case 'o':
output_name = optarg;
break;
case 'n':
tcp_nodelay = 1;
break;
#ifdef TCP_CORK
case 'c':
tcp_cork = 1;
break;
#endif
default:
abort(); /* BUG: Unimplemented option */
}
ch = next_arg(argc, argv);
}
#ifdef TCP_CORK
if(tcp_nodelay && tcp_cork) {
warn("--cork and --no-delay cannot be used together");
exit(-1);
}
#endif
output_fd = open_stream(output_name, 1);
if(output_fd < 0)
exit(-1);
}
char next_arg(int argc, char **argv)
{
char *optstr = "b:o:Vvhn"
#ifdef TCP_CORK
"c"
#endif
;
#ifdef HAVE_GETOPT_H
struct option optslong[] = {
{"block-size", 1, NULL, 'b'},
{"output", 1, NULL, 'o'},
{"version", 0, NULL, 'V'},
{"verbose", 0, NULL, 'v'},
{"help", 0, NULL, 'h'},
{"no-delay", 0, NULL, 'n'},
#ifdef TCP_CORK
{"cork", 0, NULL, 'c'},
#endif
{NULL, 0, NULL, 0}
};
return getopt_long(argc, argv, optstr, optslong, NULL);
#else
return getopt(argc, argv, optstr);
#endif
}
void usage()
{
fprintf(stderr, "Usage: ncat [OPTION...] [STREAM...]\n");
fprintf(stderr, " -o --output=STREAM Output to STREAM instead of standard output\n");
fprintf(stderr, " -b --block-size=N Change block size for reading and writing\n");
fprintf(stderr, " -v --verbose Show transfer statistics\n");
fprintf(stderr, " -n --no-delay Disable tcp buffering of small packets\n");
#ifdef TCP_CORK
fprintf(stderr, " -c --cork Buffer partial frames\n");
#endif
fprintf(stderr, " -V --version Display version\n");
fprintf(stderr, " -h --help Display help\n");
}
void version()
{
fprintf(stderr, "ncat v%s\n", VERSION);
}
/* Return: 1 success (rfd closed)
* 0 failure (rfd closed)
* output() exits on fatal error */
int output(int rfd, char *rname)
{
int total_read = 0;
int rsize, wsize;
static char *buf = NULL;
if(!buf) {
buf = malloc(block_size);
if(!buf) {
warn("%s: Out of memory", rname);
exit(-1);
}
}
while((rsize = read(rfd, buf, block_size))) {
if(rsize < 0) {
if(errno == EINTR) {
continue;
} else {
warn("%s: %s", rname, strerror(errno));
close(rfd);
return 0;
}
}
total_read += rsize;
wsize = safe_write(output_fd, buf, rsize);
total_written += wsize;
if(wsize != rsize) {
warn("write during %s: %s", rname, strerror(errno));
exit(-1);
}
}
if(verbose)
warn("%s:\t%d bytes read", rname, total_read);
close(rfd);
return 1;
}
/* Returns bytes written, if != size an error occured */
int safe_write(int fd, const char *buf, int size)
{
int i = 0;
int ret;
while (i < size) {
ret = write(fd, buf+i, size-i);
if(ret < 0) {
if(EINTR == errno)
continue;
else
return i;
}
i += ret;
}
return i;
}
/* returns -1 on error */
int open_stream(const char *name, int write)
{
char *port;
char *host;
int fd;
host = strdup(name);
if(!host) {
warn("%s: Out of memory", name);
return -1;
}
port = strrchr(host, ':');
/* If the stream ends with : force a filename (provided the
* filename is at least 1 character long) */
if(port && !port[1] && strlen(host)>1) {
*port = 0;
port = NULL;
}
if(port == NULL) {
/* open file */
if(!strcmp(host, "-")) {
if(write)
fd = fileno(stdout);
else
fd = fileno(stdin);
} else {
if(write)
fd = open(host, O_WRONLY|O_CREAT, 0666);
else
fd = open(host, O_RDONLY);
}
if(fd < 0)
warn("%s: %s", host, strerror(errno));
} else {
/* TCP stream requested */
if(port == host) {
/* IF the stream is ':', then atoi("") returns 0 */
fd = wait_connect(atoi(port+1));
} else {
*port = 0;
port++;
fd = make_connect(host, atoi(port));
}
if(fd < 0)
return -1;
if(write) {
#ifdef TCP_CORK
/* If possible only send full frames */
if(tcp_cork)
set_tcp_opt(fd, TCP_CORK);
else
#endif
if(tcp_nodelay)
set_tcp_opt(fd, TCP_NODELAY);
}
/* Shutdown unused side of the connection */
if(shutdown(fd, !write) < 0) {
warn("%s: %s", name, strerror(errno));
return -1;
}
}
free(host);
return fd;
}
void set_tcp_opt(int fd, int opt)
{
int on = 1;
if(setsockopt(fd, IPPROTO_TCP, opt, &on, sizeof(on)) < 0) {
warn("setsockopt: %s", strerror(errno));
exit(-1);
}
}
int make_connect(const char *host, int port)
{
struct sockaddr_in addr;
int fd;
if(!resolv_addr(&addr.sin_addr, host)) {
warn("%s:%d: %s", host, port, hstrerror(h_errno));
return -1;
}
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
fd = socket(PF_INET, SOCK_STREAM, 0);
if(fd < 0) {
warn("%s:%d: %s", host, port, strerror(errno));
return -1;
}
if(connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
warn("%s:%d: %s", host, port, strerror(errno));
close(fd);
return -1;
}
return fd;
}
int resolv_addr(struct in_addr *in, const char *addr)
{
struct hostent *h;
if(inet_aton(addr, in))
return 1;
h = gethostbyname(addr);
if(!h)
return 0;
memcpy(in, h->h_addr_list[0], sizeof(*in));
return 1;
}
int wait_connect(int port)
{
struct sockaddr_in addr;
int fd;
int nfd;
int size;
int on = 1;
fd = socket(PF_INET, SOCK_STREAM, 0);
if(fd < 0) {
warn(":%d: %s", port, strerror(errno));
return -1;
}
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
if(bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
warn(":%d: %s", port, strerror(errno));
close(fd);
return -1;
}
if(listen(fd, 1) < 0) {
warn(":%d: %s", port, strerror(errno));
close(fd);
return -1;
}
if(0 == port) {
size = sizeof(addr);
if(getsockname(fd, (struct sockaddr *)&addr, &size) < 0) {
warn(":%d: %s", port, strerror(errno));
close(fd);
return -1;
}
warn("listening on port %d", ntohs(addr.sin_port));
}
size = sizeof(addr);
nfd = accept(fd, (struct sockaddr *)&addr, &size);
if(nfd < 0) {
warn(":%d: %s", port, strerror(errno));
close(fd);
return -1;
}
close(fd); /* Close server sock */
return nfd;
}
void warn(const char *msg, ...)
{
va_list arg;
fprintf(stderr, "ncat: ");
va_start(arg, msg);
vfprintf(stderr, msg, arg);
va_end(arg);
fprintf(stderr, "\n");
}
/* handles b, k, m suffixes
* returns: 0 failure
* 1 success
* Note: this doesn't handle overflow */
int size2num(int *size, const char *s)
{
char *suff;
*size = strtol(s, &suff, 10);
if(suff == s)
return 0; /* No number found */
if(*size < 1)
return 0; /* Must have positive non zero */
if(0 == *suff)
return 1; /* No suffix */
if(0 != suff[1])
return 0; /* suffix too long */
switch(*suff) {
case 'b': case 'B':
*size *= 512;
return 1;
case 'k': case 'K':
*size *= 1024;
return 1;
case 'm': case 'M':
*size *= 1048576;
return 1;
}
return 0; /* Bad modifier */
}
#ifndef HAVE_INET_ATON
int inet_aton(const char *cp, struct in_addr *inp)
{
inp->s_addr = inet_addr(cp);
return -1 != inp->s_addr;
}
#endif
#ifndef HAVE_HSTRERROR
char *strherror(int e)
{
switch(e) {
case HOST_NOT_FOUND:
return "host not found";
case TRY_AGAIN:
return "server failure";
case NO_RECOVERY:
return "non recoverable error during lookup";
case NO_DATA:
return "no such address";
default:
return "unknown error resolving address";
}
}
#endif
syntax highlighted by Code2HTML, v. 0.9.1