/* * tcp.c - TCP module * * This file is part of zsh, the Z shell. * * Copyright (c) 1998-2001 Peter Stephenson * All rights reserved. * * Permission is hereby granted, without written agreement and without * license or royalty fees, to use, copy, modify, and distribute this * software and to distribute modified versions of this software for any * purpose, provided that the above copyright notice and the following * two paragraphs appear in all copies of this software. * * In no event shall Peter Stephenson or the Zsh Development * Group be liable to any party for direct, indirect, special, incidental, * or consequential damages arising out of the use of this software and * its documentation, even if Peter Stephenson, and the Zsh * Development Group have been advised of the possibility of such damage. * * Peter Stephenson and the Zsh Development Group specifically * disclaim any warranties, including, but not limited to, the implied * warranties of merchantability and fitness for a particular purpose. The * software provided hereunder is on an "as is" basis, and Peter Stephenson * and the Zsh Development Group have no obligation to provide maintenance, * support, updates, enhancements, or modifications. * */ /* * We need to include the zsh headers later to avoid clashes with * the definitions on some systems, however we need the configuration * file to decide whether we can include netinet/in_systm.h, which * doesn't exist on cygwin. */ #include "tcp.h" /* * For some reason, configure doesn't always detect netinet/in_systm.h. * On some systems, including linux, this seems to be because gcc is * throwing up a warning message about the redefinition of * __USE_LARGEFILE. This means the problem is somewhere in the * header files where we can't get at it. For now, revert to * not including this file only on systems where we know it's missing. * Currently this is just cygwin. */ #ifndef __CYGWIN__ # include #endif #include #include #include /* it's a TELNET based protocol, but don't think I like doing this */ #include /* * We use poll() in preference to select because some subset of manuals says * that's the thing to do, plus it's a bit less fiddly. I don't actually * have access to a system with poll but not select, however, though * both bits of the code have been tested on a machine with both. */ #ifdef HAVE_POLL_H # include #endif #if defined(HAVE_POLL) && !defined(POLLIN) && !defined(POLLNORM) # undef HAVE_POLL #endif #ifdef USE_LOCAL_H_ERRNO int h_errno; #endif /* We use the RFC 2553 interfaces. If the functions don't exist in the library, simulate them. */ #ifndef INET_ADDRSTRLEN # define INET_ADDRSTRLEN 16 #endif #ifndef INET6_ADDRSTRLEN # define INET6_ADDRSTRLEN 46 #endif /**/ #ifndef HAVE_INET_NTOP /**/ mod_export char const * zsh_inet_ntop(int af, void const *cp, char *buf, size_t len) { if (af != AF_INET) { errno = EAFNOSUPPORT; return NULL; } if (len < INET_ADDRSTRLEN) { errno = ENOSPC; return NULL; } strcpy(buf, inet_ntoa(*(struct in_addr *)cp)); return buf; } /**/ #else /* !HAVE_INET_NTOP */ /**/ # define zsh_inet_ntop inet_ntop /**/ #endif /* !HAVE_INET_NTOP */ /**/ #ifndef HAVE_INET_ATON # ifndef INADDR_NONE # define INADDR_NONE 0xffffffffUL # endif /**/ mod_export int zsh_inet_aton(char const *src, struct in_addr *dst) { return (dst->s_addr = inet_addr(src)) != INADDR_NONE; } /**/ #else /* !HAVE_INET_ATON */ /**/ # define zsh_inet_aton inet_aton /**/ #endif /* !HAVE_INET_ATON */ /**/ #ifndef HAVE_INET_PTON /**/ mod_export int zsh_inet_pton(int af, char const *src, void *dst) { if (af != AF_INET) { errno = EAFNOSUPPORT; return -1; } return !!zsh_inet_aton(src, dst); } #else /* !HAVE_INET_PTON */ # define zsh_inet_pton inet_pton /**/ #endif /* !HAVE_INET_PTON */ /**/ #ifndef HAVE_GETIPNODEBYNAME /**/ # ifndef HAVE_GETHOSTBYNAME2 /**/ mod_export struct hostent * zsh_gethostbyname2(char const *name, int af) { if (af != AF_INET) { h_errno = NO_RECOVERY; return NULL; } return gethostbyname(name); } /**/ #else /* !HAVE_GETHOSTBYNAME2 */ /**/ # define zsh_gethostbyname2 gethostbyname2 /**/ # endif /* !HAVE_GETHOSTBYNAME2 */ /* note: this is not a complete implementation. If ignores the flags, and does not provide the memory allocation of the standard interface. Each returned structure will overwrite the previous one. */ /**/ mod_export struct hostent * zsh_getipnodebyname(char const *name, int af, int flags, int *errorp) { static struct hostent ahe; static char nbuf[16]; static char *addrlist[] = { nbuf, NULL }; # ifdef SUPPORT_IPV6 static char pbuf[INET6_ADDRSTRLEN]; # else static char pbuf[INET_ADDRSTRLEN]; # endif struct hostent *he; if (zsh_inet_pton(af, name, nbuf) == 1) { zsh_inet_ntop(af, nbuf, pbuf, sizeof(pbuf)); ahe.h_name = pbuf; ahe.h_aliases = addrlist+1; ahe.h_addrtype = af; ahe.h_length = (af == AF_INET) ? 4 : 16; ahe.h_addr_list = addrlist; return &ahe; } he = zsh_gethostbyname2(name, af); if (!he) *errorp = h_errno; return he; } /**/ mod_export void freehostent(struct hostent *ptr) { } /**/ #else /* !HAVE_GETIPNODEBYNAME */ /**/ # define zsh_getipnodebyname getipnodebyname /**/ #endif /* !HAVE_GETIPNODEBYNAME */ Tcp_session ztcp_head = NULL, ztcp_tail = NULL; static Tcp_session zts_head(void) { return ztcp_head; } static Tcp_session zts_next(Tcp_session cur) { return cur ? cur->next : NULL; } /* "allocate" a tcp_session */ static Tcp_session zts_alloc(int ztflags) { Tcp_session sess; sess = (Tcp_session)zcalloc(sizeof(struct tcp_session)); if (!sess) return NULL; sess->fd=-1; sess->next=NULL; sess->flags=ztflags; if (!zts_head()) { ztcp_head = ztcp_tail = sess; } else { ztcp_tail->next = sess; ztcp_tail = sess; } return sess; } /**/ mod_export Tcp_session tcp_socket(int domain, int type, int protocol, int ztflags) { Tcp_session sess; sess = zts_alloc(ztflags); if (!sess) return NULL; sess->fd = socket(domain, type, protocol); return sess; } static int zts_delete(Tcp_session sess) { Tcp_session tsess; tsess = zts_head(); if(!sess) return 1; if (tsess == sess) { ztcp_head = sess->next; free(sess); return 0; } while((tsess->next != sess) && (tsess->next)) { tsess = zts_next(tsess); } if (!tsess->next) return 1; if (ztcp_tail == sess) ztcp_tail = tsess; tsess->next = sess->next; free(sess); return 0; } static Tcp_session zts_byfd(int fd) { Tcp_session tsess; tsess = zts_head(); while(tsess != NULL) { if (tsess->fd == fd) return tsess; tsess = zts_next(tsess); } return NULL; } static void tcp_cleanup(void) { Tcp_session sess, prev; for(sess = zts_head(); sess != NULL; sess = zts_next(prev)) { prev = sess; tcp_close(sess); } } /**/ mod_export int tcp_close(Tcp_session sess) { int err; if (sess && sess->fd != -1) { err = close(sess->fd); if (err) { zwarn("connection close failed: %e", NULL, errno); return -1; } zts_delete(sess); return 0; } zts_delete(sess); return -1; } /**/ mod_export int tcp_connect(Tcp_session sess, char *addrp, struct hostent *zhost, int d_port) { int salen; #ifdef SUPPORT_IPV6 if (zhost->h_addrtype==AF_INET6) { memcpy(&(sess->peer.in6.sin6_addr), addrp, zhost->h_length); sess->peer.in6.sin6_port = d_port; sess->peer.in6.sin6_flowinfo = 0; # ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_SCOPE_ID sess->peer.in6.sin6_scope_id = 0; # endif salen = sizeof(struct sockaddr_in6); } else #endif /* SUPPORT_IPV6 */ { memcpy(&(sess->peer.in.sin_addr), addrp, zhost->h_length); sess->peer.in.sin_port = d_port; sess->peer.a.sa_family = zhost->h_addrtype; salen = sizeof(struct sockaddr_in); } return connect(sess->fd, (struct sockaddr *)&(sess->peer), salen); } static int bin_ztcp(char *nam, char **args, char *ops, int func) { int herrno, err=1, destport, force=0, verbose=0, test=0, targetfd=0, len; char **addrp, *desthost, *localname, *remotename, **dargs; struct hostent *zthost = NULL, *ztpeer = NULL; struct servent *srv; Tcp_session sess = NULL; if (ops['f']) force = 1; if (ops['v']) verbose = 1; if (ops['t']) test = 1; if (ops['d']) { targetfd = atoi(args[0]); dargs = args + 1; if (!targetfd) { zwarnnam(nam, "%s is an invalid argument to -d", args[0], 0); return 1; } } else dargs = args; if (ops['c']) { if (!dargs[0]) { tcp_cleanup(); } else { targetfd = atoi(dargs[0]); sess = zts_byfd(targetfd); if(!targetfd) { zwarnnam(nam, "%s is an invalid argument to -c", dargs[0], 0); return 1; } if (sess) { if ((sess->flags & ZTCP_ZFTP) && !force) { zwarnnam(nam, "use -f to force closure of a zftp control connection", NULL, 0); return 1; } tcp_close(sess); return 0; } else { zwarnnam(nam, "fd %s not found in tcp table", dargs[0], 0); return 1; } } } else if (ops['l']) { int lport = 0; if (!dargs[0]) { zwarnnam(nam, "-l requires an argument", NULL, 0); return 1; } srv = getservbyname(dargs[0], "tcp"); if (srv) lport = srv->s_port; else lport = htons(atoi(dargs[0])); if (!lport) { zwarnnam(nam, "bad service name or port number", NULL, 0); return 1; } sess = tcp_socket(PF_INET, SOCK_STREAM, 0, ZTCP_LISTEN); if (!sess) { zwarnnam(nam, "unable to allocate a TCP session slot", NULL, 0); return 1; } #ifdef SO_OOBINLINE len = 1; setsockopt(sess->fd, SOL_SOCKET, SO_OOBINLINE, (char *)&len, sizeof(len)); #endif if (!zsh_inet_aton("0.0.0.0", &(sess->sock.in.sin_addr))) { zwarnnam(nam, "bad address: %s", "0.0.0.0", 0); return 1; } sess->sock.in.sin_family = AF_INET; sess->sock.in.sin_port = lport; if (bind(sess->fd, (struct sockaddr *)&sess->sock.in, sizeof(struct sockaddr_in))) { zwarnnam(nam, "could not bind to %s: %e", "0.0.0.0", errno); tcp_close(sess); return 1; } if (listen(sess->fd, 1)) { zwarnnam(nam, "could not listen on socket: %e", NULL, errno); tcp_close(sess); return 1; } if (targetfd) { redup(sess->fd,targetfd); sess->fd = targetfd; } else { /* move the fd since no one will want to read from it */ sess->fd = movefd(sess->fd); } setiparam("REPLY", sess->fd); if(verbose) fprintf(shout, "%d listener is on fd %d\n", ntohs(sess->sock.in.sin_port), sess->fd); return 0; } else if (ops['a']) { int lfd, rfd; if (!dargs[0]) { zwarnnam(nam, "-a requires an argument", NULL, 0); return 1; } lfd = atoi(dargs[0]); if (!lfd) { zwarnnam(nam, "invalid numerical argument", NULL, 0); return 1; } sess = zts_byfd(lfd); if (!sess) { zwarnnam(nam, "fd %s is not registered as a tcp connection", dargs[0], 0); return 1; } if (!(sess->flags & ZTCP_LISTEN)) { zwarnnam(nam, "tcp connection not a listener", NULL, 0); return 1; } if(test) { #if defined(HAVE_POLL) || defined(HAVE_SELECT) # ifdef HAVE_POLL struct pollfd pfd; int ret; pfd.fd = lfd; pfd.events = POLLIN; if((ret = poll(&pfd, 1, 0)) == 0) return 1; else if (ret == -1) { zwarnnam(nam, "poll error: %e", NULL, errno); return 1; } # else fd_set rfds; struct timeval tv; int ret; FD_ZERO(&rfds); FD_SET(lfd, &rfds); tv.tv_sec = 0; tv.tv_usec = 0; if(ret = select(lfd+1, &rfds, NULL, NULL, &tv)) return 1; else if (ret == -1) { zwarnnam(nam, "select error: %e", NULL, errno); return 1; } # endif #else zwarnnam(nam, "not currently supported", NULL, 0); return 1; #endif } sess = zts_alloc(ZTCP_INBOUND); if ((rfd = accept(lfd, (struct sockaddr *)&sess->peer.in, &len)) == -1) { zwarnnam(nam, "could not accept connection: %e", NULL, errno); tcp_close(sess); return 1; } if (targetfd) { redup(rfd, targetfd); sess->fd = targetfd; } else { sess->fd = rfd; } setiparam("REPLY", sess->fd); if(verbose) fprintf(shout, "%d is on fd %d\n", ntohs(sess->peer.in.sin_port), sess->fd); } else { if (!dargs[0]) { for(sess = zts_head(); sess != NULL; sess = zts_next(sess)) { if (sess->fd != -1) { zthost = gethostbyaddr(&(sess->sock.in.sin_addr), sizeof(struct sockaddr_in), AF_INET); if (zthost) localname = zthost->h_name; else localname = ztrdup(inet_ntoa(sess->sock.in.sin_addr)); ztpeer = gethostbyaddr(&(sess->peer.in.sin_addr), sizeof(struct sockaddr_in), AF_INET); if (ztpeer) remotename = ztpeer->h_name; else remotename = ztrdup(inet_ntoa(sess->sock.in.sin_addr)); fprintf(shout, "%s:%d %s %s:%d is on fd %d%s\n", localname, ntohs(sess->sock.in.sin_port), (sess->flags & ZTCP_LISTEN) ? "-<" : (sess->flags & ZTCP_INBOUND) ? "<-" : "->", remotename, ntohs(sess->peer.in.sin_port), sess->fd, (sess->flags & ZTCP_ZFTP) ? " ZFTP" : ""); } } return 0; } else if (!dargs[1]) { destport = htons(23); } else { srv = getservbyname(dargs[1],"tcp"); if (srv) destport = srv->s_port; else destport = htons(atoi(dargs[1])); } desthost = ztrdup(dargs[0]); zthost = zsh_getipnodebyname(desthost, AF_INET, 0, &herrno); if (!zthost || errflag) { zwarnnam(nam, "host resolution failure: %s", desthost, 0); return 1; } sess = tcp_socket(PF_INET, SOCK_STREAM, 0, 0); if (!sess) { zwarnnam(nam, "unable to allocate a TCP session slot", NULL, 0); return 1; } #ifdef SO_OOBINLINE len = 1; setsockopt(sess->fd, SOL_SOCKET, SO_OOBINLINE, (char *)&len, sizeof(len)); #endif if (sess->fd < 0) { zwarnnam(nam, "socket creation failed: %e", NULL, errno); zsfree(desthost); zts_delete(sess); return 1; } for (addrp = zthost->h_addr_list; err && *addrp; addrp++) { if (zthost->h_length != 4) zwarnnam(nam, "address length mismatch", NULL, 0); do { err = tcp_connect(sess, *addrp, zthost, destport); } while (err && errno == EINTR && !errflag); } if (err) { zwarnnam(nam, "connection failed: %e", NULL, errno); tcp_close(sess); } else { if (targetfd) { redup(sess->fd, targetfd); sess->fd = targetfd; } setiparam("REPLY", sess->fd); if (verbose) fprintf(shout, "%s:%d is now on fd %d\n", desthost, destport, sess->fd); } zsfree(desthost); } return 0; } static struct builtin bintab[] = { BUILTIN("ztcp", 0, bin_ztcp, 0, 3, 0, "acdfltv", NULL), }; /* The load/unload routines required by the zsh library interface */ /**/ int setup_(Module m) { return 0; } /**/ int boot_(Module m) { return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); } /**/ int cleanup_(Module m) { tcp_cleanup(); return 0; } /**/ int finish_(Module m) { return 0; }