From e4976bb0731d14a7d8bfa53067ec1d99e94617cb Mon Sep 17 00:00:00 2001 From: mdw Date: Thu, 5 May 2005 23:32:05 +0000 Subject: [PATCH] It seems to work! --- Makefile | 17 ++ noip | 6 + noip.1 | 278 ++++++++++++++++++++ noip.c | 901 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ uopen | 6 + uopen.1 | 68 +++++ uopen.c | 118 +++++++++ 7 files changed, 1394 insertions(+) create mode 100644 Makefile create mode 100755 noip create mode 100644 noip.1 create mode 100644 noip.c create mode 100755 uopen create mode 100644 uopen.1 create mode 100644 uopen.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..dcf4b74 --- /dev/null +++ b/Makefile @@ -0,0 +1,17 @@ +CC = gcc +LD = gcc +CFLAGS = -O2 -g -fPIC -Wall +LDFLAGS = -shared +LIBS = -ldl + +SOURCES = noip.c uopen.c +TARGETS = noip.so uopen.so +all: $(TARGETS) +%.o: %.c + $(CC) -c $(CFLAGS) $< -o $@ +noip.so: $(patsubst %.c, %.o, noip.c) + $(LD) $(LDFLAGS) $< $(LIBS) -o $@ +uopen.so: $(patsubst %.c, %.o, uopen.c) + $(LD) $(LDFLAGS) $< $(LIBS) -o $@ +clean: + rm -f $(OBJECTS) $(TARGETS) \ No newline at end of file diff --git a/noip b/noip new file mode 100755 index 0000000..7e385f5 --- /dev/null +++ b/noip @@ -0,0 +1,6 @@ +#! /bin/sh + +set -e +export LD_PRELOAD=$LD_PRELOAD${LD_PRELOAD+ }./noip.so +[ $# -eq 0 ] && set -- "$SHELL" +exec "$@" diff --git a/noip.1 b/noip.1 new file mode 100644 index 0000000..fe56efd --- /dev/null +++ b/noip.1 @@ -0,0 +1,278 @@ +.de VS +.sp 1 +.RS +.nf +.ft B +.. +.de VE +.ft R +.fi +.RE +.sp 1 +.. +.TH noip 1 "5 May 2005" "Straylight/Edgeware" "Preload hacks" +.SH NAME +noip \- run programs without the ability to use IP sockets +.SH SYNOPSIS +.B noip +.RI [ command +.RI [ args ...]] +.SH DESCRIPTION +The +.B noip +program runs +.I command +(by default, the user's shell, as determined by the +.B SHELL +environment variable) in an environment where attempts to use TCP/IP +networking are (mostly) transparently translated into the use of +Unix-domain sockets in a private directory. +.PP +There are many programs which use TCP/IP for their own internal +communications needs, largely unnecessarily. This can present security +problems: even if a program binds its listening sockets to +.BR localhost , +other users on the same system can still connect, and many such programs +don't seem to have authentication systems. +.PP +.B noip +addresses this problem by intercepting a program's networking calls and +making it use Unix-domain sockets in a private directory instead of +TCP/IP. Now its communications are truly private to the running user. +.SS The socket directory +The +.B noip +program keeps its sockets in a directory whose name can be configured, +but by default is +.BI noip- \c +.IR username , +where +.I username +is determined by the +.B USER +or +.B LOGNAME +environment variables, or is +.BI uid- \c +.IR realuid +in the temporary directory, which in turn is determined by the +.B TMPDIR +or +.B TMP +environment variables, or is +.BR /tmp . +The sockets in this directory are simply named +.IB dottedquad : port +after the Internet sockets they represent. +.PP +If the socket directory does not exist when a program running under +.B noip +starts up, it is created and made readable and writable only by the +current user. Also, it is scanned and any apparently stale sockets are +removed. +.SS Configuration +The operation of +.B noip +is controlled by a configuration file. By default, +.B noip +reads configuration from +.B .noip +in the calling user's home directory, as determined by the +.B HOME +environment, or, failing that, looking up the +.I real +(not effective) user id in the password database. +.PP +The configuration file has a simple line-based format. A line is +ignored if it consists only of whitespace, or if its first whitespace +character is +.RB ` # '. +Otherwise, the first whitespace-delimited word is a keyword and the +remainder of the line is a value. The following keywords are +recognized. +.TP +.BR "debug " [\fInumber\fR] +If +.I number +is nonzero, turn debugging on; if it is zero, turn debugging off. The +default, if no +.I number +is given, is to turn debugging on. Debugging is written to standard +error. Some debugging is produced before the configuration file is +read; the environment variable +.B NOIP_DEBUG +can be used to control this. +.TP +.BI "socketdir " directory +Store the Unix-domain sockets in +.IR directory +rather than the default. +.TP +.BI "realbind " acl-entry +Add an entry to the +.B realbind +access control list (ACL). When a program attempts to +.BR bind (2) +a socket to an address, the +.B realbind +ACL is consulted. If the address is matched, then the program is +allowed to bind a real Internet socket to that address; otherwise, the +socket is bound to a Unix-domain socket. +.TP +.BI "realbind " acl-entry +Add an entry to the +.B realconnect +access control list (ACL). When a program attempts to +.BR connect (2) +a socket to an address, or to contact another socket using +.BR sendto (2) +or +.BR sendmsg (2), +the +.B realconnect +ACL is consulted. If the destination address is matched, then the +program is allowed to contact the real Internet socket; otherwise, the +attempt is made to contact a Unix-domain socket. +.PP +(Aside: An attempt to connect to a remote host may not be a hopeless failure, +even if a real IP socket is denied: +.B noip +deliberately makes no attempt to check that addresses being bound to +sockets correspond to locally available addresses; and besides, sockets +can be introduced into the directory by other programs simulating remote +servers.) +.PP +An +.I acl-entry +has this format: +.IP +.BR + | \- +.IR address \c +.RB [ \- \c +.IR address | \c +.BR / \c +.IR mask ]| \c +.BR local | any +.RB [ : \c +.IR port [ \c +.BI \- \c +.IR port ]] +.PP +(The spaces in the above are optional.) +.PP +The leading sign says whether +matching addresses should be +.I accepted +.RB (` + ') +or +.I denied +.RB (` \- '). +.PP +The IP-address portion may be any of the following +.TP +.B any +Matches all addresses. +.TP +.B local +Matches the address of one of the machine's network interfaces. +.TP +.I address +Matches just the given address +.TP +.IB address \- address +Matches any address which falls in the given range. Addresses are +compared lexicographically, with octets to the left given precedence +over octets to the right. +.TP +.IB address / mask +Matches an address in the given network. The +.I mask +may be a netmask in dotted-quad form, or a one-bit-count. +.PP +The port portion may be omitted (which means `match any port'), or may +be a single +.I port +or a range +.IB port \- port +to match. +.PP +Range comparisons are always inclusive of both endpoints. +.PP +ACL entries are processed in the order they appear in the configuration +file. The default action of an ACL, used if none of its entries match, +is the opposite of the last actual entry in the list: if the last entry +says `accept', then the default is to deny, and vice-versa. If the ACL +is empty, the default is to deny all addresses. +.PP +For example, it may be useful to allow access at least to a DNS server. +This can be accomplished by adding a line +.VS +realconnect +1.2.3.4:52 +.VE +to the configuration file, where 1.2.3.4 is the IP address of one of +your DNS server. +.SS Example applications +SLIME is an Emacs extension for doing interactive programming with Lisp +systems. It communicates with the Lisp system using TCP sockets, since +Unix-domain sockets are unavailable on Windows, and besides, they are +less well supported by Lisp implementations. Unfortunately, when the +author wrote this program, SLIME applied no authentication on its TCP +port, allowing any local user to take over the running Lisp. Worse, +some Lisps are unable to bind a listening socket to a particular +address, leaving the socket potentially available to anyone on the +network. By running Emacs under +.BR noip , +the security hole is closed completely and no messing with +authentication secrets is needed. +.PP +SSH is an excellent tool for secure communications over hostile +networks. In particular, its ability to forward TCP connections to a +port on one side of an SSH tunnel to the other side is very useful. +However, such a forwarded port is available to all users on the source +side of the tunnel. Using +.B noip +and a suitable configuration, a user can restrict access to a forwarded +port to himself or a small group. +.SH BUGS +.B noip +is implemented as an +.B LD_PRELOAD +hack. It won't work on setuid programs. Also, perhaps more +importantly, it can't do anything a +.I malicious +program use of networking: a program could theoretically issue sockets +system calls directly instead of using the C library calls that +.B noip +intercepts. It is intended only as a tool for enhancing the security of +software written by well-meaning programmers who don't understand the +security aspects of writing networking code. +.PP +It's very hard to tell exactly what state a Unix-domain socket is in. +If the filesystem object isn't there, it's not active, but if it +.I is +then the socket might be in use or it might be stale. +.B noip +consults +.B /proc/net/unix +to decide whether a socket is in use, but this can fail in two ways. +Firstly, if the socket is created and renamed, the kernel doesn't +notice, and +.B noip +will think that the new name is stale. Secondly, if the socket is +created, used, unlinked while it's still in use, and recreated, then +.B noip +will think that it's in use when in fact it's gone stale. Don't mess +with +.BR noip 's +sockets unless you know what you're doing. +.PP +The procedure to replace a Unix-domain socket by an Internet one is +fairly thorough, but there are some missing cases. In particular, if +the socket being bound or connected is a duplicate (using +.BR dup (2)) +then only one of the copies will be fixed. Similarly, copies owned by +child processes will be unaffected. +.PP +This manual is surprisingly long and complicated for such a simple hack. +.SH AUTHOR +Mark Wooding, diff --git a/noip.c b/noip.c new file mode 100644 index 0000000..735a58a --- /dev/null +++ b/noip.c @@ -0,0 +1,901 @@ +#define _GNU_SOURCE +#undef sun +#undef SUN +#define DEBUG + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +enum { UNUSED, STALE, USED }; +enum { WANT_FRESH, WANT_EXISTING }; +enum { DENY, ALLOW }; +typedef struct aclnode { + struct aclnode *next; + int act; + unsigned long minaddr, maxaddr; + unsigned short minport, maxport; +} aclnode; + +#define MAX_LOCAL_IPADDRS 16 +static struct in_addr local_ipaddrs[MAX_LOCAL_IPADDRS]; +static int n_local_ipaddrs; + +static uid_t uid; +static char *sockdir = 0; +static int debug = 0; + +static aclnode *bind_real, **bind_tail = &bind_real; +static aclnode *connect_real, **connect_tail = &connect_real; + +/* --- Import magic --- */ + +#define IMPORTS(_) \ + _(socket, int, (int, int, int)) \ + _(socketpair, int, (int, int, int, int *)) \ + _(connect, int, (int, const struct sockaddr *, socklen_t)) \ + _(bind, int, (int, const struct sockaddr *, socklen_t)) \ + _(accept, int, (int, struct sockaddr *, socklen_t *)) \ + _(getsockname, int, (int, struct sockaddr *, socklen_t *)) \ + _(getpeername, int, (int, struct sockaddr *, socklen_t *)) \ + _(getsockopt, int, (int, int, int, void *, socklen_t *)) \ + _(setsockopt, int, (int, int, int, const void *, socklen_t)) \ + _(sendto, ssize_t, (int, const void *buf, size_t, int, \ + const struct sockaddr *to, socklen_t tolen)) \ + _(recvfrom, ssize_t, (int, void *buf, size_t, int, \ + struct sockaddr *from, socklen_t *fromlen)) \ + _(sendmsg, ssize_t, (int, const struct msghdr *, int)) \ + _(recvmsg, ssize_t, (int, struct msghdr *, int)) \ + _(close, int, (int)) + +#define DECL(imp, ret, args) static ret (*real_##imp) args; +IMPORTS(DECL) +#undef DECL + +static void setup(void) __attribute__((constructor)); +static void import(void) +{ +#define IMPORT(imp, ret, args) \ + real_##imp = (ret (*)args)dlsym(RTLD_NEXT, #imp); + IMPORTS(IMPORT) +#undef IMPORT +} + +/* --- Support --- */ + +#define SA(sa) ((struct sockaddr *)(sa)) +#define SIN(sa) ((struct sockaddr_in *)(sa)) +#define SUN(sa) ((struct sockaddr_un *)(sa)) + +#define UC(ch) ((unsigned char)(ch)) + +#define NEW(x) ((x) = xmalloc(sizeof(*x))) +#define NEWV(x, n) ((x) = xmalloc(sizeof(*x) * (n))) + +#ifdef DEBUG +# define D(body) { if (debug) { body } } +#else +# define D(body) ; +#endif + +#define PRESERVING_ERRNO(body) do { \ + int _err = errno; { body } errno = _err; \ +} while (0) + +static void *xmalloc(size_t n) +{ + void *p; + if (!n) return (0); + if ((p = malloc(n)) == 0) { perror("malloc"); exit(1); } + return (p); +} + +static char *xstrdup(const char *p) +{ + size_t n = strlen(p) + 1; + char *q = xmalloc(n); + memcpy(q, p, n); + return (q); +} + +static int unix_socket_status(struct sockaddr_un *sun, int quick_p) +{ + struct stat st; + FILE *fp = 0; + size_t len, n; + int rc; + char buf[256]; + + rc = UNUSED; + if (stat(sun->sun_path, &st) && errno == ENOENT) + goto done; + rc = USED; + if (quick_p) + goto done; + + if ((fp = fopen("/proc/net/unix", "r")) == 0) + goto done; + fgets(buf, sizeof(buf), fp); /* skip header */ + len = strlen(sun->sun_path); + while (fgets(buf, sizeof(buf), fp)) { + n = strlen(buf); + if (n >= len + 2 && buf[n - len - 2] == ' ' && buf[n - 1] == '\n' && + memcmp(buf + n - len - 1, sun->sun_path, len) == 0) + goto done; + } + if (ferror(fp)) + goto done; + rc = STALE; +done: + if (fp) fclose(fp); + return (rc); +} + +#ifdef DEBUG + +static void dump_aclnode(aclnode *a) +{ + char minbuf[16], maxbuf[16]; + struct in_addr amin, amax; + + amin.s_addr = htonl(a->minaddr); + amax.s_addr = htonl(a->maxaddr); + fprintf(stderr, "noip: %c ", a->act ? '+' : '-'); + if (a->minaddr == 0 && a->maxaddr == 0xffffffff) + fprintf(stderr, "any"); + else { + fprintf(stderr, "%s", + inet_ntop(AF_INET, &amin, minbuf, sizeof(minbuf))); + if (a->maxaddr != a->minaddr) { + fprintf(stderr, "-%s", + inet_ntop(AF_INET, &amax, maxbuf, sizeof(maxbuf))); + } + } + if (a->minport != 0 || a->maxport != 0xffff) { + fprintf(stderr, ":%u", (unsigned)a->minport); + if (a->minport != a->maxport) + fprintf(stderr, "-%u", (unsigned)a->maxport); + } + fputc('\n', stderr); +} + +#endif + +static int acl_allows_p(aclnode *a, const struct sockaddr_in *sin) +{ + unsigned long addr = ntohl(sin->sin_addr.s_addr); + unsigned short port = ntohs(sin->sin_port); + int act = ALLOW; + + D( char buf[16]; + fprintf(stderr, "noip: check %s:%u\n", + inet_ntop(AF_INET, &sin->sin_addr, buf, sizeof(buf)), + ntohs((unsigned)sin->sin_port)); ) + for (; a; a = a->next) { + D( dump_aclnode(a); ) + if (a->minaddr <= addr && addr <= a->maxaddr && + a->minport <= port && port <= a->maxport) { + D( fprintf(stderr, "noip: aha! %s\n", a->act ? "ALLOW" : "DENY"); ) + return (a->act); + } + act = a->act; + } + D( fprintf(stderr, "noip: nothing found: %s\n", act ? "DENY" : "ALLOW"); ) + return (!act); +} + +#ifdef DEBUG + +static void dump_acl(aclnode *a) +{ + int act = ALLOW; + + for (; a; a = a->next) { + dump_aclnode(a); + act = a->act; + } + fprintf(stderr, "noip: [default policy: %s]\n", + act == ALLOW ? "DENY" : "ALLOW"); +} + +#endif + +static int encode_inet_addr(struct sockaddr_un *sun, + const struct sockaddr_in *sin, + int want) +{ + int i; + int desperate_p = 0; + char buf[INET_ADDRSTRLEN]; + int rc; + + D( fprintf(stderr, "noip: encode %s:%u (%s)", + inet_ntop(AF_INET, &sin->sin_addr, buf, sizeof(buf)), + (unsigned)ntohs(sin->sin_port), + want == WANT_EXISTING ? "EXISTING" : "FRESH"); ) + sun->sun_family = AF_UNIX; + if (sin->sin_port || want == WANT_EXISTING) { + snprintf(sun->sun_path, sizeof(sun->sun_path), "%s/%s:%u", sockdir, + inet_ntop(AF_INET, &sin->sin_addr, buf, sizeof(buf)), + (unsigned)ntohs(sin->sin_port)); + rc = unix_socket_status(sun, 0); + if (rc == STALE) unlink(sun->sun_path); + if (rc != USED && want == WANT_EXISTING) { + snprintf(sun->sun_path, sizeof(sun->sun_path), "%s/0.0.0.0:%u", + sockdir, (unsigned)ntohs(sin->sin_port)); + if (unix_socket_status(sun, 0) == STALE) unlink(sun->sun_path); + } + } else { + for (desperate_p = 0; desperate_p < 2; desperate_p++) { + for (i = 16384; i < 65536; i++) { + snprintf(sun->sun_path, sizeof(sun->sun_path), "%s/%s:%u", sockdir, + inet_ntop(AF_INET, &sin->sin_addr, buf, sizeof(buf)), + (unsigned)i); + rc = unix_socket_status(sun, !desperate_p); + switch (rc) { + case STALE: unlink(sun->sun_path); + case UNUSED: goto found; + } + } + } + errno = EADDRINUSE; + D( fprintf(stderr, " -- can't resolve\n"); ) + return (-1); + found:; + } + D( fprintf(stderr, " -> `%s'\n", sun->sun_path); ) + return (0); +} + +static int decode_inet_addr(struct sockaddr_in *sin, + const struct sockaddr_un *sun, + socklen_t len) +{ + char buf[INET_ADDRSTRLEN + 16]; + char *p; + size_t n = strlen(sockdir), nn = strlen(sun->sun_path); + struct sockaddr_in sin_mine; + unsigned long port; + + if (!sin) + sin = &sin_mine; + if (sun->sun_family != AF_UNIX) + return (-1); + if (len < sizeof(sun)) ((char *)sun)[len] = 0; + D( fprintf(stderr, "noip: decode (%d) `%s'", + *sun->sun_path, sun->sun_path); ) + if (!sun->sun_path[0]) { + sin->sin_family = AF_INET; + sin->sin_addr.s_addr = INADDR_ANY; + sin->sin_port = 0; + D( fprintf(stderr, " -- unbound socket\n"); ) + return (0); + } + if (nn < n + 1 || nn - n >= sizeof(buf) || sun->sun_path[n] != '/' || + memcmp(sun->sun_path, sockdir, n) != 0) { + D( fprintf(stderr, " -- not one of ours\n"); ) + return (-1); + } + memcpy(buf, sun->sun_path + n + 1, nn - n); + if ((p = strchr(buf, ':')) == 0) { + D( fprintf(stderr, " -- malformed (no port)\n"); ) + return (-1); + } + *p++ = 0; + sin->sin_family = AF_INET; + if (inet_pton(AF_INET, buf, &sin->sin_addr) <= 0) { + D( fprintf(stderr, " -- malformed (bad address `%s')\n", buf); ) + return (-1); + } + port = strtoul(p, &p, 10); + if (*p || port >= 65536) { + D( fprintf(stderr, " -- malformed (port out of range)"); ) + return (-1); + } + sin->sin_port = htons(port); + D( fprintf(stderr, " -> %s:%u\n", + inet_ntop(AF_INET, &sin->sin_addr, buf, sizeof(buf)), + (unsigned)port); ) + return (0); +} + +static int fixup_real_ip_socket(int sk) +{ + int nsk; + int type; + int f, fd; + struct sockaddr_un sun; + struct sockaddr_in sin; + socklen_t len; + +#define OPTS(_) \ + _(DEBUG, int) \ + _(REUSEADDR, int) \ + _(DONTROUTE, int) \ + _(BROADCAST, int) \ + _(SNDBUF, int) \ + _(RCVBUF, int) \ + _(OOBINLINE, int) \ + _(NO_CHECK, int) \ + _(LINGER, struct linger) \ + _(BSDCOMPAT, int) \ + _(RCVLOWAT, int) \ + _(RCVTIMEO, struct timeval) \ + _(SNDTIMEO, struct timeval) + + len = sizeof(sun); + if (real_getsockname(sk, SA(&sun), &len)) + return (-1); + if (decode_inet_addr(&sin, &sun, len)) + return (0); /* Not one of ours */ + len = sizeof(type); + if (real_getsockopt(sk, SOL_SOCKET, SO_TYPE, &type, &len) < 0 || + (nsk = real_socket(PF_INET, type, 0)) < 0) + return (-1); +#define FIX(opt, ty) do { \ + ty ov_; \ + len = sizeof(ov_); \ + if (real_getsockopt(sk, SOL_SOCKET, SO_##opt, &ov_, &len) < 0 || \ + real_setsockopt(nsk, SOL_SOCKET, SO_##opt, &ov_, len)) { \ + real_close(nsk); \ + return (-1); \ + } \ +} while (0); + OPTS(FIX) +#undef FIX + if ((f = fcntl(sk, F_GETFL)) < 0 || + (fd = fcntl(sk, F_GETFD)) < 0 || + fcntl(nsk, F_SETFL, f) < 0 || + dup2(nsk, sk) < 0) { + real_close(nsk); + return (-1); + } + unlink(sun.sun_path); + real_close(nsk); + if (fcntl(sk, F_SETFD, fd) < 0) { + perror("noip: fixup_real_ip_socket F_SETFD"); + abort(); + } + return (0); +} + +static int do_implicit_bind(int sk, const struct sockaddr **sa, + socklen_t *len, struct sockaddr_un *sun) +{ + struct sockaddr_in sin; + socklen_t mylen = sizeof(*sun); + + if (acl_allows_p(connect_real, SIN(*sa))) { + if (fixup_real_ip_socket(sk)) + return (-1); + } else { + if (real_getsockname(sk, SA(sun), &mylen) < 0) + return (-1); + if (sun->sun_family == AF_UNIX) { + if (mylen < sizeof(*sun)) ((char *)sun)[mylen] = 0; + if (!sun->sun_path[0]) { + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = INADDR_LOOPBACK; + sin.sin_port = 0; + encode_inet_addr(sun, &sin, WANT_FRESH); + if (real_bind(sk, SA(sun), SUN_LEN(sun))) + return (-1); + } + encode_inet_addr(sun, SIN(*sa), WANT_EXISTING); + *sa = SA(sun); + *len = SUN_LEN(sun); + } + } + return (0); +} + +static void return_fake_name(struct sockaddr *sa, socklen_t len, + struct sockaddr *fake, socklen_t *fakelen) +{ + struct sockaddr_in sin; + socklen_t alen; + + if (sa->sa_family == AF_UNIX && !decode_inet_addr(&sin, SUN(sa), len)) { + sa = SA(&sin); + len = sizeof(sin); + } + alen = len; + if (len > *fakelen) + len = *fakelen; + if (len > 0) + memcpy(fake, sa, len); + *fakelen = alen; +} + +/* --- Configuration --- */ + +static char *home(void) +{ + char *p; + struct passwd *pw; + + if ((p = getenv("HOME")) != 0) return (p); + else if ((pw = getpwuid(uid)) != 0) return (pw->pw_dir); + else return "/notexist"; +} + +static char *tmpdir(void) +{ + char *p; + + if ((p = getenv("TMPDIR")) != 0) return (p); + else if ((p = getenv("TMP")) != 0) return (p); + else return ("/tmp"); +} + +static char *user(void) +{ + static char buf[16]; + char *p; + struct passwd *pw; + + if ((p = getenv("USER")) != 0) return (p); + else if ((p = getenv("LOGNAME")) != 0) return (p); + else if ((pw = getpwuid(uid)) != 0) return (pw->pw_name); + else { + snprintf(buf, sizeof(buf), "uid-%lu", (unsigned long)uid); + return (buf); + } +} + +#define SKIPSPC do { while (*p && isspace(UC(*p))) p++; } while (0) +#define NEXTWORD(q) do { \ + SKIPSPC; \ + q = p; \ + while (*p && !isspace(UC(*p))) p++; \ + if (*p) *p++ = 0; \ +} while (0) +#define NEXTADDR(q, del) do { \ + SKIPSPC; \ + q = p; \ + while (*p && (*p == '.' || isdigit(UC(*p)))) p++; \ + del = *p; \ + if (*p) *p++ = 0; \ +} while (0) +#define NEXTNUMBER(q, del) do { \ + SKIPSPC; \ + q = p; \ + while (*p && isdigit(UC(*p))) p++; \ + del = *p; \ + if (*p) *p++ = 0; \ +} while (0) +#define RESCAN(del) do { if (del) *--p = del; } while (0) +#define KWMATCHP(kw) (strncmp(p, kw, sizeof(kw) - 1) == 0 && \ + !isalnum(UC(p[sizeof(kw) - 1])) && \ + (p += sizeof(kw) - 1)) + +static void parse_ports(char **pp, unsigned short *min, unsigned short *max) +{ + char *p = *pp, *q; + int del; + + SKIPSPC; + if (*p != ':') { + *min = 0; + *max = 0xffff; + } else { + p++; + NEXTNUMBER(q, del); + *min = atoi(q); + RESCAN(del); + SKIPSPC; + if (*p == '-') { + NEXTNUMBER(q, del); + *max = atoi(q); + RESCAN(del); + } else + *max = *min; + } + *pp = p; +} + +#define ACLNODE(tail_, act_, \ + minaddr_, maxaddr_, minport_, maxport_) do { \ + aclnode *a_; \ + NEW(a_); \ + a_->act = act_; \ + a_->minaddr = minaddr_; a_->maxaddr = maxaddr_; \ + a_->minport = minport_; a_->maxport = maxport_; \ + *tail_ = a_; tail_ = &a_->next; \ +} while (0) + +static void parse_acl_line(char **pp, aclnode ***tail) +{ + struct in_addr addr; + unsigned long minaddr, maxaddr, mask; + unsigned short minport, maxport; + int i, n; + int act; + int del; + char *p = *pp; + char *q; + + SKIPSPC; + if (*p == '+') act = ALLOW; + else if (*p == '-') act = DENY; + else goto bad; + + p++; + SKIPSPC; + if (KWMATCHP("any")) { + minaddr = 0; + maxaddr = 0xffffffff; + goto justone; + } else if (KWMATCHP("local")) { + parse_ports(&p, &minport, &maxport); + ACLNODE(*tail, act, 0, 0, minport, maxport); + ACLNODE(*tail, act, 0xffffffff, 0xffffffff, minport, maxport); + for (i = 0; i < n_local_ipaddrs; i++) { + minaddr = ntohl(local_ipaddrs[i].s_addr); + ACLNODE(*tail, act, minaddr, minaddr, minport, maxport); + } + } else { + if (*p == ':') { + minaddr = 0; + maxaddr = 0xffffffff; + } else { + NEXTADDR(q, del); + if (inet_pton(AF_INET, q, &addr) <= 0) goto bad; + minaddr = ntohl(addr.s_addr); + RESCAN(del); + SKIPSPC; + if (*p == '-') { + p++; + NEXTADDR(q, del); + if (inet_pton(AF_INET, q, &addr) <= 0) goto bad; + RESCAN(del); + maxaddr = ntohl(addr.s_addr); + } else if (*p == '/') { + p++; + NEXTADDR(q, del); + if (strchr(q, '.')) { + if (inet_pton(AF_INET, q, &addr) <= 0) goto bad; + mask = ntohl(addr.s_addr); + } else { + n = atoi(q); + mask = (~0ul << (32 - n)) & 0xffffffff; + } + RESCAN(del); + minaddr &= mask; + maxaddr = minaddr | (mask ^ 0xffffffff); + } else + maxaddr = minaddr; + } + justone: + parse_ports(&p, &minport, &maxport); + ACLNODE(*tail, act, minaddr, maxaddr, minport, maxport); + } + return; + +bad: + D( fprintf(stderr, "noip: bad acl spec (ignored)\n"); ) + return; +} + +static void readconfig(void) +{ + FILE *fp; + char buf[1024]; + size_t n; + char *p, *cmd; + + if ((p = getenv("NOIP_CONFIG")) == 0) + snprintf(p = buf, sizeof(buf), "%s/.noip", home()); + D( fprintf(stderr, "noip: config file: %s\n", p); ) + if ((fp = fopen(p, "r")) == 0) + goto done; + + while (fgets(buf, sizeof(buf), fp)) { + n = strlen(buf); + p = buf; + + SKIPSPC; + if (!*p || *p == '#') continue; + while (n && isspace(UC(buf[n - 1]))) n--; + buf[n] = 0; + NEXTWORD(cmd); + SKIPSPC; + + if (strcmp(cmd, "socketdir") == 0) + sockdir = xstrdup(p); + else if (strcmp(cmd, "realbind") == 0) + parse_acl_line(&p, &bind_tail); + else if (strcmp(cmd, "realconnect") == 0) + parse_acl_line(&p, &connect_tail); + else if (strcmp(cmd, "debug") == 0) + debug = *p ? atoi(p) : 1; + else + D( fprintf(stderr, "noip: bad config command %s\n", cmd); ) + } + fclose(fp); + +done: + *bind_tail = 0; + *connect_tail = 0; + if (!sockdir) { + snprintf(buf, sizeof(buf), "%s/noip-%s", tmpdir(), user()); + sockdir = xstrdup(buf); + } + D( fprintf(stderr, "noip: sockdir: %s\n", sockdir); + fprintf(stderr, "noip: realbind acl:\n"); + dump_acl(bind_real); + fprintf(stderr, "noip: realconnect acl:\n"); + dump_acl(connect_real); ) +} + +/* --- Hooks --- */ + +int socket(int pf, int ty, int proto) +{ + if (pf == PF_INET) { + pf = PF_UNIX; + proto = 0; + } + return real_socket(pf, ty, proto); +} + +int socketpair(int pf, int ty, int proto, int *sk) +{ + if (pf == PF_INET) { + pf = PF_UNIX; + proto = 0; + } + return (real_socketpair(pf, ty, proto, sk)); +} + +int bind(int sk, const struct sockaddr *sa, socklen_t len) +{ + struct sockaddr_un sun; + + if (sa->sa_family == AF_INET) { + PRESERVING_ERRNO({ + if (acl_allows_p(bind_real, SIN(sa))) { + if (fixup_real_ip_socket(sk)) + return (-1); + } else { + encode_inet_addr(&sun, SIN(sa), WANT_FRESH); + sa = SA(&sun); + len = SUN_LEN(&sun); + } + }); + } + return real_bind(sk, sa, len); +} + +int connect(int sk, const struct sockaddr *sa, socklen_t len) +{ + struct sockaddr_un sun; + + if (sa->sa_family == AF_INET) { + PRESERVING_ERRNO({ + do_implicit_bind(sk, &sa, &len, &sun); + }); + } + return real_connect(sk, sa, len); +} + +ssize_t sendto(int sk, const void *buf, size_t len, int flags, + const struct sockaddr *to, socklen_t tolen) +{ + struct sockaddr_un sun; + + if (to && to->sa_family == AF_INET) { + PRESERVING_ERRNO({ + do_implicit_bind(sk, &to, &tolen, &sun); + }); + } + return real_sendto(sk, buf, len, flags, to, tolen); +} + +ssize_t recvfrom(int sk, void *buf, size_t len, int flags, + struct sockaddr *from, socklen_t *fromlen) +{ + char sabuf[1024]; + socklen_t mylen = sizeof(sabuf); + ssize_t n; + + if (!from) + return real_recvfrom(sk, buf, len, flags, 0, 0); + PRESERVING_ERRNO({ + n = real_recvfrom(sk, buf, len, flags, SA(sabuf), &mylen); + if (n < 0) + return (-1); + return_fake_name(SA(sabuf), mylen, from, fromlen); + }); + return (n); +} + +ssize_t sendmsg(int sk, const struct msghdr *msg, int flags) +{ + struct sockaddr_un sun; + const struct sockaddr *sa; + struct msghdr mymsg; + + if (msg->msg_name && SA(msg->msg_name)->sa_family == AF_INET) { + PRESERVING_ERRNO({ + sa = SA(msg->msg_name); + mymsg = *msg; + do_implicit_bind(sk, &sa, &mymsg.msg_namelen, &sun); + mymsg.msg_name = SA(sa); + msg = &mymsg; + }); + } + return real_sendmsg(sk, msg, flags); +} + +ssize_t recvmsg(int sk, struct msghdr *msg, int flags) +{ + char sabuf[1024]; + struct sockaddr *sa; + socklen_t len; + ssize_t n; + + if (!msg->msg_name) + return real_recvmsg(sk, msg, flags); + PRESERVING_ERRNO({ + sa = SA(msg->msg_name); + len = msg->msg_namelen; + msg->msg_name = sabuf; + msg->msg_namelen = sizeof(sabuf); + n = real_recvmsg(sk, msg, flags); + if (n < 0) + return (-1); + return_fake_name(SA(sabuf), msg->msg_namelen, sa, &len); + msg->msg_name = sa; + msg->msg_namelen = len; + }); + return (n); +} + +int accept(int sk, struct sockaddr *sa, socklen_t *len) +{ + char sabuf[1024]; + socklen_t mylen = sizeof(sabuf); + int nsk = real_accept(sk, SA(sabuf), &mylen); + + if (nsk < 0) + return (-1); + return_fake_name(SA(sabuf), mylen, sa, len); + return (nsk); +} + +int getsockname(int sk, struct sockaddr *sa, socklen_t *len) +{ + PRESERVING_ERRNO({ + char sabuf[1024]; + socklen_t mylen = sizeof(sabuf); + if (real_getsockname(sk, SA(sabuf), &mylen)) + return (-1); + return_fake_name(SA(sabuf), mylen, sa, len); + }); + return (0); +} + +int getpeername(int sk, struct sockaddr *sa, socklen_t *len) +{ + PRESERVING_ERRNO({ + char sabuf[1024]; + socklen_t mylen = sizeof(sabuf); + if (real_getpeername(sk, SA(sabuf), &mylen)) + return (-1); + return_fake_name(SA(sabuf), mylen, sa, len); + }); + return (0); +} + +int getsockopt(int sk, int lev, int opt, void *p, socklen_t *len) +{ + switch (lev) { + case SOL_IP: + case SOL_TCP: + case SOL_UDP: + if (*len > 0) + memset(p, 0, *len); + return (0); + } + return real_getsockopt(sk, lev, opt, p, len); +} + +int setsockopt(int sk, int lev, int opt, const void *p, socklen_t len) +{ + switch (lev) { + case SOL_IP: + case SOL_TCP: + case SOL_UDP: + return (0); + } + switch (opt) { + case SO_BINDTODEVICE: + case SO_ATTACH_FILTER: + case SO_DETACH_FILTER: + return (0); + } + return real_setsockopt(sk, lev, opt, p, len); +} + +/* --- Initialization --- */ + +static void cleanup_sockdir(void) +{ + DIR *dir; + struct dirent *d; + struct sockaddr_un sun; + + if ((dir = opendir(sockdir)) == 0) + return; + while ((d = readdir(dir)) != 0) { + if (d->d_name[0] == '.') continue; + snprintf(sun.sun_path, sizeof(sun.sun_path), + "%s/%s", sockdir, d->d_name); + if (unix_socket_status(&sun, 0) == STALE) { + D( fprintf(stderr, "noip: clearing away stale socket %s\n", + d->d_name); ) + unlink(sun.sun_path); + } + } + closedir(dir); +} + +static void get_local_ipaddrs(void) +{ + struct if_nameindex *ifn; + struct ifreq ifr; + int sk; + int i; + + ifn = if_nameindex(); + if ((sk = real_socket(PF_INET, SOCK_STREAM, 00)) < 0) + return; + for (i = n_local_ipaddrs = 0; + n_local_ipaddrs < MAX_LOCAL_IPADDRS && + ifn[i].if_name && *ifn[i].if_name; + i++) { + strcpy(ifr.ifr_name, ifn[i].if_name); + if (ioctl(sk, SIOCGIFADDR, &ifr) || ifr.ifr_addr.sa_family != AF_INET) + continue; + local_ipaddrs[n_local_ipaddrs++] = + SIN(&ifr.ifr_addr)->sin_addr; + D( fprintf(stderr, "noip: local addr %s = %s\n", ifn[i].if_name, + inet_ntoa(local_ipaddrs[n_local_ipaddrs - 1])); ) + } + close(sk); +} + +static void setup(void) +{ + PRESERVING_ERRNO({ + char *p; + + import(); + uid = getuid(); + if ((p = getenv("NOIP_DEBUG")) && atoi(p)) + debug = 1; + get_local_ipaddrs(); + readconfig(); + mkdir(sockdir, 0700); + cleanup_sockdir(); + }); +} diff --git a/uopen b/uopen new file mode 100755 index 0000000..aab9b83 --- /dev/null +++ b/uopen @@ -0,0 +1,6 @@ +#! /bin/sh + +set -e +export LD_PRELOAD=$LD_PRELOAD${LD_PRELOAD+ }./uopen.so +[ $# -eq 0 ] && set -- "${SHELL-/bin/sh}" +exec "$@" diff --git a/uopen.1 b/uopen.1 new file mode 100644 index 0000000..d5a5ac8 --- /dev/null +++ b/uopen.1 @@ -0,0 +1,68 @@ +.de VS +.sp 1 +.RS +.nf +.ft B +.. +.de VE +.ft R +.fi +.RE +.sp 1 +.. +.TH uopen 3 "5 May 2005" "Straylight/Edgeware" "Preload hacks" +.SH NAME +uopen \- connect to Unix-domain sockets using open(2) +.SH SYNOPSIS +.B uopen +.RI [ command +.RI [ args ...]] +.SH DESCRIPTION +The +.B uopen +command runs +.I command +(by default, the calling user's shell, as determined by the +.B SHELL +environment variable) in an environment where an attempt by the program +to +.BR open (2) +a Unix-domain socket results in a connection being made to the socket. +.PP +This might be used to implement a signature server which chooses a +random signature for newly-written news and mail messages. This can be +done by writing a simple program to choose and print a signature, +causing it to be invoked in response to connections to the socket +.B $HOME/.signature +(for example, by +.BR fw (1)) +and running the message-drafting program under the control of +.BR uopen . +If we were willing to accept +.BR fortune (5) +as our signature generator, this can be done using, say +.VS +fw -d -p$HOME/.sigd.pid \e + "from unix:$HOME/.signature to exec[fortune -s]" +uopen emacs -nw -f vm +.VE +.SH BUGS +.B uopen +is implemented as an +.B LD_PRELOAD +hack. It won't work on setuid programs. +.PP +The code is needlessly Linux-specific in some places. +.PP +It may not catch some uses of +.BR open (2) +or its friends from inside the Linux C library. It's a strange and +murky world in there, and glibc does all manner of strange linker tricks +to stop you messing with +.BR open (2). +.SH SEE ALSO +.BR fw (1), +.BR ld.so (8), +.BR open (2). +.SH AUTHOR +Mark Wooding, diff --git a/uopen.c b/uopen.c new file mode 100644 index 0000000..206ec75 --- /dev/null +++ b/uopen.c @@ -0,0 +1,118 @@ +#define _GNU_SOURCE +#undef sun + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#define IMPORTS(_) \ + _(open, int, (const char *, int, ...)) \ + _(open64, int, (const char *, int, ...)) \ + _(fopen, FILE *, (const char *, const char *)) \ + _(freopen, FILE *, (const char *, const char *, FILE *)) + +#define DECL(imp, ret, args) static ret (*real_##imp) args; +IMPORTS(DECL) +#undef DECL + +static void setup(void) __attribute__((constructor)); +static void import(void) +{ +#define IMPORT(imp, ret, args) \ + real_##imp = (ret (*)args)dlsym(RTLD_NEXT, #imp); + IMPORTS(IMPORT) +#undef IMPORT +} + +#define SA(sa) ((struct sockaddr *)(sa)) + +#define PRESERVING_ERRNO(body) do { \ + int _err = errno; { body } errno = _err; \ +} while (0) + +static int maybe_connect(const char *fn, int *fdp) +{ + int fd; + struct sockaddr_un sun; + struct stat st; + + if (stat(fn, &st) || !S_ISSOCK(st.st_mode)) + return (0); + *fdp = -1; + if (strlen(fn) >= sizeof(sun.sun_path)) { + errno = EINVAL; + return (1); + } + strncpy(sun.sun_path, fn, sizeof(sun.sun_path)); + sun.sun_family = AF_UNIX; + if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0 || + connect(fd, SA(&sun), SUN_LEN(&sun))) { + PRESERVING_ERRNO( if (fd >= 0) close(fd); ); + return (1); + } + *fdp = fd; + return (1); +} + +#define OPEN_VENEER(open) \ + int open(const char *fn, int how, ...) \ + { \ + int mode = 0; \ + int fd; \ + va_list ap; \ + \ + PRESERVING_ERRNO({ \ + if (how & O_CREAT) \ + { va_start(ap, how); mode = va_arg(ap, int); va_end(ap); } \ + if (!maybe_connect(fn, &fd)) fd = real_##open(fn, how, mode); \ + if (fd < 0) return (-1); \ + }); \ + return (fd); \ + } +OPEN_VENEER(open) +OPEN_VENEER(open64) + +FILE *fopen(const char *fn, const char *how) +{ + int fd; + FILE *fp = 0; + + PRESERVING_ERRNO({ + if (!maybe_connect(fn, &fd)) + fp = real_fopen(fn, how); + else if (fd >= 0 && (fp = fdopen(fd, how)) == 0) + PRESERVING_ERRNO( close(fd); ); + }); + return (fp); +} + +FILE *freopen(const char *fn, const char *how, FILE *fp) +{ + int fd; + + PRESERVING_ERRNO({ + if (!maybe_connect(fn, &fd)) + fp = real_freopen(fn, how, fp); + else if (fd >= 0) { + if (fflush(fp) || dup2(fd, fileno(fp))) fp = 0; + PRESERVING_ERRNO( close(fd); ); + } + }); + return (fp); +} + +static void setup(void) +{ + PRESERVING_ERRNO({ + import(); + }); +} -- 2.11.0