X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/preload-hacks/blobdiff_plain/81e3737d842818a305e23802405f4e15b5e8381d..c0ae47b31fbdf9729f6ea531d674b93252b1b775:/noip.c diff --git a/noip.c b/noip.c index 1d40850..fea6f74 100644 --- a/noip.c +++ b/noip.c @@ -59,7 +59,11 @@ /*----- Data structures ---------------------------------------------------*/ -enum { UNUSED, STALE, USED }; /* Unix socket status values */ +/* Unix socket status values. */ +#define UNUSED 0u /* No sign of anyone using it */ +#define STALE 1u /* Socket exists, but is abandoned */ +#define USED 16u /* Socket is in active use */ + enum { DENY, ALLOW }; /* ACL verdicts */ static int address_families[] = { AF_INET, AF_INET6, -1 }; @@ -339,6 +343,43 @@ static void copy_sockaddr(struct sockaddr *sa_dst, const struct sockaddr *sa_src) { memcpy(sa_dst, sa_src, family_socklen(sa_src->sa_family)); } +/* Convert an AF_INET socket address into the equivalent IPv4-mapped AF_INET6 + * address. + */ +static void map_ipv4_sockaddr(struct sockaddr_in6 *a6, + const struct sockaddr_in *a4) +{ + size_t i; + in_addr_t a = ntohl(a4->sin_addr.s_addr); + + a6->sin6_family = AF_INET6; + a6->sin6_port = a4->sin_port; + a6->sin6_scope_id = 0; + a6->sin6_flowinfo = 0; + for (i = 0; i < 10; i++) a6->sin6_addr.s6_addr[i] = 0; + for (i = 10; i < 12; i++) a6->sin6_addr.s6_addr[i] = 0xff; + for (i = 0; i < 4; i++) a6->sin6_addr.s6_addr[15 - i] = (a >> 8*i)&0xff; +} + +/* Convert an AF_INET6 socket address containing an IPv4-mapped IPv6 address + * into the equivalent AF_INET4 address. Return zero on success, or -1 if + * the address has the wrong form. + */ +static int unmap_ipv4_sockaddr(struct sockaddr_in *a4, + const struct sockaddr_in6 *a6) +{ + size_t i; + in_addr_t a; + + for (i = 0; i < 10; i++) if (a6->sin6_addr.s6_addr[i] != 0) return (-1); + for (i = 10; i < 12; i++) if (a6->sin6_addr.s6_addr[i] != 0xff) return (-1); + for (i = 0, a = 0; i < 4; i++) a |= a6->sin6_addr.s6_addr[15 - i] << 8*i; + a4->sin_family = AF_INET; + a4->sin_port = a6->sin6_port; + a4->sin_addr.s_addr = htonl(a); + return (0); +} + /* Answer whether two addresses are equal. */ static int ipaddr_equal_p(int af, const ipaddr *a, const ipaddr *b) { @@ -663,15 +704,17 @@ static int unix_socket_status(struct sockaddr_un *sun, int quickp) goto done; if (!fgets(buf, sizeof(buf), fp)) goto done; /* skip header */ len = strlen(sun->sun_path); + rc = 0; 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; + memcmp(buf + n - len - 1, sun->sun_path, len) == 0) { + rc |= USED; + } } if (ferror(fp)) goto done; - rc = STALE; + if (!rc) rc = STALE; done: if (fp) fclose(fp); @@ -691,9 +734,9 @@ static int encode_single_inet_addr(const struct sockaddr *sa, snprintf(sun->sun_path, sizeof(sun->sun_path), "%s/%s", sockdir, present_sockaddr(sa, 0, buf, sizeof(buf))); - if ((rc = unix_socket_status(sun, quickp)) == USED) return (USED); - else if (rc == STALE) unlink(sun->sun_path); - return (UNUSED); + rc = unix_socket_status(sun, quickp); + if (rc == STALE) unlink(sun->sun_path); + return (rc); } /* Convert the IP address SA to a Unix-domain address SUN. Fail if the @@ -704,7 +747,7 @@ static int encode_unused_inet_addr(struct sockaddr *sa, struct sockaddr_un *sun, int desperatep) { - address waddr; + address waddr, maddr; struct sockaddr_un wsun; int port = port_from_sockaddr(sa); @@ -712,7 +755,7 @@ static int encode_unused_inet_addr(struct sockaddr *sa, * desperate. If the socket is in use, we fail here. (This could get * racy. Let's not worry about that for now.) */ - if (encode_single_inet_addr(sa, sun, !desperatep) == USED) + if (encode_single_inet_addr(sa, sun, !desperatep)&USED) return (-1); /* Next, check the corresponding wildcard address, so as to avoid @@ -720,9 +763,28 @@ static int encode_unused_inet_addr(struct sockaddr *sa, */ wildcard_address(sa->sa_family, &waddr.sa); port_to_sockaddr(&waddr.sa, port); - if (encode_single_inet_addr(&waddr.sa, &wsun, !desperatep) == USED) + if (encode_single_inet_addr(&waddr.sa, &wsun, !desperatep)&USED) return (-1); + /* We're not done yet. If this is an IPv4 address, then /also/ check (a) + * the v6-mapped version, (b) the v6-mapped v4 wildcard, /and/ (c) the v6 + * wildcard. Ugh! + */ + if (sa->sa_family == AF_INET) { + map_ipv4_sockaddr(&maddr.sin6, SIN(&sa)); + if (encode_single_inet_addr(&maddr.sa, &wsun, !desperatep)&USED) + return (-1); + + map_ipv4_sockaddr(&maddr.sin6, &waddr.sin); + if (encode_single_inet_addr(&maddr.sa, &wsun, !desperatep)&USED) + return (-1); + + wildcard_address(AF_INET6, &waddr.sa); + port_to_sockaddr(&waddr.sa, port); + if (encode_single_inet_addr(&waddr.sa, &wsun, !desperatep)&USED) + return (-1); + } + /* All is well. */ return (0); } @@ -741,7 +803,9 @@ static int encode_inet_addr(struct sockaddr_un *sun, int i; int desperatep = 0; address addr; + struct sockaddr_in6 sin6; int port = port_from_sockaddr(sa); + int rc; char buf[ADDRBUFSZ]; D( fprintf(stderr, "noip(%d): encode %s (%s)", getpid(), @@ -756,15 +820,48 @@ static int encode_inet_addr(struct sockaddr_un *sun, /* Try the address as given. If it's in use, or we don't necessarily * want an existing socket, then we're done. */ - if (encode_single_inet_addr(sa, sun, 0) == USED || (f&ENCF_FRESH)) - goto found; - - /* We're looking for a socket which already exists. Try the - * corresponding wildcard address. + rc = encode_single_inet_addr(sa, sun, 0); + if ((rc&USED) || (f&ENCF_FRESH)) goto found; + + /* We're looking for a socket which already exists. This is + * unfortunately difficult, because we must deal both with wildcards and + * v6-mapped IPv4 addresses. + * + * * We've just tried searching for a socket whose name is an exact + * match for our remote address. If the remote address is IPv4, then + * we should try again with the v6-mapped equivalent. + * + * * Failing that, we try again with the wildcard address for the + * appropriate address family. + * + * * Failing /that/, if the remote address is IPv4, then we try + * /again/, increasingly desperately, first with the v6-mapped IPv4 + * wildcard address, and then with the IPv6 wildcard address. This + * will cause magic v6-mapping to occur when the connection is + * accepted, which we hope won't cause too much trouble. */ + + if (sa->sa_family == AF_INET) { + map_ipv4_sockaddr(&addr.sin6, SIN(sa)); + if (encode_single_inet_addr(&addr.sa, sun, 0)&USED) goto found; + } + wildcard_address(sa->sa_family, &addr.sa); port_to_sockaddr(&addr.sa, port); - encode_single_inet_addr(&addr.sa, sun, 0); + if (encode_single_inet_addr(&addr.sa, sun, 0)&USED) goto found; + + if (sa->sa_family == AF_INET) { + map_ipv4_sockaddr(&sin6, &addr.sin); + if (encode_single_inet_addr(SA(&sin6), sun, 0)&USED) goto found; + wildcard_address(AF_INET6, &addr.sa); + port_to_sockaddr(&addr.sa, port); + if (encode_single_inet_addr(&addr.sa, sun, 0)&USED) goto found; + } + + /* Well, this isn't going to work (unless a miraculous race is lost), but + * we might as well try. + */ + encode_single_inet_addr(sa, sun, 1); } else { /* We want a fresh new socket. */ @@ -912,16 +1009,25 @@ static int fixup_real_ip_socket(int sk, int af_hint, int *tmp) * deception. Whatever happens, put the result at FAKE and store its length * at FAKELEN. */ +#define FNF_V6MAPPED 1u static void return_fake_name(struct sockaddr *sa, socklen_t len, - struct sockaddr *fake, socklen_t *fakelen) + struct sockaddr *fake, socklen_t *fakelen, + unsigned f) { address addr; + struct sockaddr_in6 sin6; socklen_t alen; if (sa->sa_family == AF_UNIX && !decode_inet_addr(&addr.sa, 0, SUN(sa), len)) { - sa = &addr.sa; - len = family_socklen(addr.sa.sa_family); + if (addr.sa.sa_family != AF_INET || !(f&FNF_V6MAPPED)) { + sa = &addr.sa; + len = family_socklen(addr.sa.sa_family); + } else { + map_ipv4_sockaddr(&sin6, &addr.sin); + sa = SA(&sin6); + len = family_socklen(AF_INET6); + } } alen = len; if (len > *fakelen) len = *fakelen; @@ -929,6 +1035,30 @@ static void return_fake_name(struct sockaddr *sa, socklen_t len, *fakelen = alen; } +/* Variant of `return_fake_name' above, specifically handling the weirdness + * of remote v6-mapped IPv4 addresses. If SK's fake local address is IPv6, + * and the remote address is IPv4, then return a v6-mapped version of the + * remote address. + */ +static void return_fake_peer(int sk, struct sockaddr *sa, socklen_t len, + struct sockaddr *fake, socklen_t *fakelen) +{ + char sabuf[1024]; + socklen_t mylen = sizeof(sabuf); + unsigned fnf = 0; + address addr; + int rc; + + PRESERVING_ERRNO({ + rc = real_getsockname(sk, SA(sabuf), &mylen); + if (!rc && sa->sa_family == AF_UNIX && + !decode_inet_addr(&addr.sa, 0, SUN(sabuf), mylen) && + addr.sa.sa_family == AF_INET6) + fnf |= FNF_V6MAPPED; + }); + return_fake_name(sa, len, fake, fakelen, fnf); +} + /*----- Implicit binding --------------------------------------------------*/ #ifdef DEBUG @@ -962,44 +1092,92 @@ static void dump_impbind_list(void) /* The socket SK is about to be used to communicate with the remote address * SA. Assign it a local address so that getpeername(2) does something * useful. + * + * If the flag `IBF_V6MAPPED' is set then, then SA must be an `AF_INET' + * address; after deciding on the appropriate local address, convert it to be + * an IPv4-mapped IPv6 address before final conversion to a Unix-domain + * socket address and actually binding. Note that this could well mean that + * the socket ends up bound to the v6-mapped v4 wildcard address + * ::ffff:0.0.0.0, which looks very strange but is meaningful. */ -static int do_implicit_bind(int sk, const struct sockaddr **sa, - socklen_t *len, struct sockaddr_un *sun) +#define IBF_V6MAPPED 1u +static int do_implicit_bind(int sk, const struct sockaddr *sa, unsigned f) { address addr; - socklen_t mylen = sizeof(*sun); + struct sockaddr_in6 sin6; + struct sockaddr_un sun; const impbind *i; Dpid; - if (acl_allows_p(connect_real, *sa)) { - if (fixup_real_ip_socket(sk, (*sa)->sa_family, 0)) 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]) { - D( fprintf(stderr, "noip(%d): checking impbind list...\n", pid); ) - for (i = impbinds; i; i = i->next) { - D( dump_impbind(i); ) - if ((*sa)->sa_family == i->af && - sockaddr_in_range_p(*sa, &i->minaddr, &i->maxaddr)) { - D( fprintf(stderr, "noip(%d): match!\n", pid); ) - addr.sa.sa_family = (*sa)->sa_family; - ipaddr_to_sockaddr(&addr.sa, &i->bindaddr); - goto found; - } - } - D( fprintf(stderr, "noip(%d): no match; using wildcard\n", pid); ) - wildcard_address((*sa)->sa_family, &addr.sa); - found: - encode_inet_addr(sun, &addr.sa, ENCF_FRESH); - if (real_bind(sk, SA(sun), SUN_LEN(sun))) return (-1); - } - encode_inet_addr(sun, *sa, 0); - *sa = SA(sun); - *len = SUN_LEN(sun); + D( fprintf(stderr, "noip(%d): checking impbind list...\n", pid); ) + for (i = impbinds; i; i = i->next) { + D( dump_impbind(i); ) + if (sa->sa_family == i->af && + sockaddr_in_range_p(sa, &i->minaddr, &i->maxaddr)) { + D( fprintf(stderr, "noip(%d): match!\n", pid); ) + addr.sa.sa_family = sa->sa_family; + ipaddr_to_sockaddr(&addr.sa, &i->bindaddr); + goto found; } } + D( fprintf(stderr, "noip(%d): no match; using wildcard\n", pid); ) + wildcard_address(sa->sa_family, &addr.sa); +found: + if (addr.sa.sa_family != AF_INET || !(f&IBF_V6MAPPED)) sa = &addr.sa; + else { map_ipv4_sockaddr(&sin6, &addr.sin); sa = SA(&sin6); } + encode_inet_addr(&sun, sa, ENCF_FRESH); + D( fprintf(stderr, "noip(%d): implicitly binding to %s\n", + pid, sun.sun_path); ) + if (real_bind(sk, SA(&sun), SUN_LEN(&sun))) return (-1); + return (0); +} + +/* The socket SK is about to communicate with the remote address *SA. Ensure + * that the socket has a local address, and adjust *SA to refer to the real + * remote endpoint. + * + * If we need to translate the remote address, then the Unix-domain endpoint + * address will end in *SUN, and *SA will be adjusted to point to it. + */ +static int fixup_client_socket(int sk, const struct sockaddr **sa_r, + socklen_t *len_r, struct sockaddr_un *sun) +{ + struct sockaddr_in sin; + socklen_t mylen = sizeof(*sun); + const struct sockaddr *sa = *sa_r; + unsigned ibf = 0; + + /* If this isn't a Unix-domain socket then there's nothing to do. */ + if (real_getsockname(sk, SA(sun), &mylen) < 0) return (-1); + if (sun->sun_family != AF_UNIX) return (0); + if (mylen < sizeof(*sun)) ((char *)sun)[mylen] = 0; + + /* If the remote address is v6-mapped IPv4, then unmap it so as to search + * for IPv4 servers. Also remember to v6-map the local address when we + * autobind. + */ + if (sa->sa_family == AF_INET6 && !(unmap_ipv4_sockaddr(&sin, SIN6(sa)))) { + sa = SA(&sin); + ibf |= IBF_V6MAPPED; + } + + /* If we're allowed to talk to a real remote endpoint, then fix things up + * as necessary and proceed. + */ + if (acl_allows_p(connect_real, sa)) { + if (fixup_real_ip_socket(sk, (*sa_r)->sa_family, 0)) return (-1); + return (0); + } + + /* Speaking of which, if we don't have a local address, then we should + * arrange one now. + */ + if (!sun->sun_path[0] && do_implicit_bind(sk, sa, ibf)) return (-1); + + /* And then come up with a remote address. */ + encode_inet_addr(sun, sa, 0); + *sa_r = SA(sun); + *len_r = SUN_LEN(sun); return (0); } @@ -1584,7 +1762,7 @@ int connect(int sk, const struct sockaddr *sa, socklen_t len) } else { D( fprintf(stderr, " -> checking...\n"); ) PRESERVING_ERRNO({ - do_implicit_bind(sk, &sa, &len, &sun); + fixup_client_socket(sk, &sa, &len, &sun); }); D( fprintf(stderr, "noip(%d): CONNECT ...", pid); ) rc = real_connect(sk, sa, len); @@ -1617,7 +1795,7 @@ ssize_t sendto(int sk, const void *buf, size_t len, int flags, else { D( fprintf(stderr, " -> checking...\n"); ) PRESERVING_ERRNO({ - do_implicit_bind(sk, &to, &tolen, &sun); + fixup_client_socket(sk, &to, &tolen, &sun); }); D( fprintf(stderr, "noip(%d): SENDTO ...", pid); ) } @@ -1641,14 +1819,14 @@ ssize_t recvfrom(int sk, void *buf, size_t len, int flags, D( fprintf(stderr, " -> null addr; pass through"); ) n = real_recvfrom(sk, buf, len, flags, 0, 0); } else { - PRESERVING_ERRNO({ - n = real_recvfrom(sk, buf, len, flags, SA(sabuf), &mylen); - if (n >= 0) { - D( fprintf(stderr, " -> converting...\n"); ) - return_fake_name(SA(sabuf), mylen, from, fromlen); - D( fprintf(stderr, "noip(%d): ... RECVFROM", pid); ) - } - }); + n = real_recvfrom(sk, buf, len, flags, SA(sabuf), &mylen); + if (n >= 0) { + D( fprintf(stderr, " -> converting...\n"); ) + PRESERVING_ERRNO({ + return_fake_peer(sk, SA(sabuf), mylen, from, fromlen); + }); + D( fprintf(stderr, "noip(%d): ... RECVFROM", pid); ) + } } D( dump_addrresult(n, from, fromlen ? *fromlen : 0); ) return (n); @@ -1677,7 +1855,7 @@ ssize_t sendmsg(int sk, const struct msghdr *msg, int flags) D( fprintf(stderr, " -> checking...\n"); ) PRESERVING_ERRNO({ mymsg = *msg; - do_implicit_bind(sk, &sa, &mymsg.msg_namelen, &sun); + fixup_client_socket(sk, &sa, &mymsg.msg_namelen, &sun); mymsg.msg_name = SA(sa); msg = &mymsg; }); @@ -1703,18 +1881,18 @@ ssize_t recvmsg(int sk, struct msghdr *msg, int flags) D( fprintf(stderr, " -> null addr; pass through"); ) return (real_recvmsg(sk, msg, flags)); } else { - PRESERVING_ERRNO({ - msg->msg_name = sabuf; - msg->msg_namelen = sizeof(sabuf); - n = real_recvmsg(sk, msg, flags); - if (n >= 0) { - D( fprintf(stderr, " -> converting...\n"); ) - return_fake_name(SA(sabuf), msg->msg_namelen, sa, &len); - D( fprintf(stderr, "noip(%d): ... RECVMSG", pid); ) - } - msg->msg_name = sa; - msg->msg_namelen = len; - }); + msg->msg_name = sabuf; + msg->msg_namelen = sizeof(sabuf); + n = real_recvmsg(sk, msg, flags); + if (n >= 0) { + D( fprintf(stderr, " -> converting...\n"); ) + PRESERVING_ERRNO({ + return_fake_peer(sk, SA(sabuf), msg->msg_namelen, sa, &len); + }); + } + D( fprintf(stderr, "noip(%d): ... RECVMSG", pid); ) + msg->msg_name = sa; + msg->msg_namelen = len; } D( dump_addrresult(n, sa, len); ) return (n); @@ -1734,7 +1912,7 @@ int accept(int sk, struct sockaddr *sa, socklen_t *len) else if (!sa) D( fprintf(stderr, " -> address not wanted"); ) else { D( fprintf(stderr, " -> converting...\n"); ) - return_fake_name(SA(sabuf), mylen, sa, len); + return_fake_peer(sk, SA(sabuf), mylen, sa, len); D( fprintf(stderr, "noip(%d): ... ACCEPT", pid); ) } D( dump_addrresult(nsk, sa, len ? *len : 0); ) @@ -1752,7 +1930,7 @@ int getsockname(int sk, struct sockaddr *sa, socklen_t *len) rc = real_getsockname(sk, SA(sabuf), &mylen); if (rc >= 0) { D( fprintf(stderr, " -> converting...\n"); ) - return_fake_name(SA(sabuf), mylen, sa, len); + return_fake_name(SA(sabuf), mylen, sa, len, 0); D( fprintf(stderr, "noip(%d): ... GETSOCKNAME", pid); ) } D( dump_addrresult(rc, sa, *len); ) @@ -1770,7 +1948,7 @@ int getpeername(int sk, struct sockaddr *sa, socklen_t *len) rc = real_getpeername(sk, SA(sabuf), &mylen); if (rc >= 0) { D( fprintf(stderr, " -> converting...\n"); ) - return_fake_name(SA(sabuf), mylen, sa, len); + return_fake_peer(sk, SA(sabuf), mylen, sa, len); D( fprintf(stderr, "noip(%d): ... GETPEERNAME", pid); ) } D( dump_addrresult(rc, sa, *len); )