+/* 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
+
+static void dump_impbind(const impbind *i)
+{
+ char buf[ADDRBUFSZ];
+
+ fprintf(stderr, "noip(%d): ", getpid());
+ dump_addrrange(i->af, &i->minaddr, &i->maxaddr);
+ switch (i->how) {
+ case SAME: fprintf(stderr, " <self>"); break;
+ case EXPLICIT:
+ fprintf(stderr, " %s", inet_ntop(i->af, &i->bindaddr,
+ buf, sizeof(buf)));
+ break;
+ default: abort();
+ }
+ fputc('\n', stderr);
+}
+
+static void dump_impbind_list(void)
+{
+ const impbind *i;
+
+ for (i = impbinds; i; i = i->next) dump_impbind(i);
+}
+
+#endif
+
+/* 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.
+ */
+#define IBF_V6MAPPED 1u
+static int do_implicit_bind(int sk, const struct sockaddr *sa, unsigned f)
+{
+ address addr;
+ struct sockaddr_in6 sin6;
+ struct sockaddr_un sun;
+ const impbind *i;
+ Dpid;
+
+ 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);
+}
+