/* 3APA3A simplest proxy server (c) 2002-2026 by Vladimir Dubrovin please read License Agreement */ #include "structures.h" #include "proxy.h" #ifdef WITH_TRANSPARENT #ifdef WITH_NETFILTER #include #include #include #include #include #endif #ifdef WITH_PF #include #include #include #include #include #include #endif /* Where the address the client was trying to reach is read from. AUTO uses what the platform offers, which is the only thing an installation usually needs. The rest name one mechanism, for a machine that has more than one and redirects with a particular one. */ #define TRANSPARENT_AUTO 0 #define TRANSPARENT_NETFILTER 1 #define TRANSPARENT_PF 2 #define TRANSPARENT_SOCKET 3 static struct pluginlink * pl; static int transparent_loaded = 0; static int transparent_mode = TRANSPARENT_AUTO; #ifdef WITH_PF static int pf_device = -1; /* Ask the packet filter what the connection was addressed to before it was redirected. pf keeps that in its state table rather than on the socket, so it has to be looked up with the addresses of both ends. */ static int transparent_pf(struct clientparam *param) { struct pfioc_natlook nl; if(pf_device < 0){ pf_device = open("/dev/pf", O_RDONLY); if(pf_device < 0) return 1; } memset(&nl, 0, sizeof(nl)); nl.proto = IPPROTO_TCP; nl.direction = PF_OUT; #ifndef NOIPV6 if(*SAFAMILY(¶m->sincr) == AF_INET6){ nl.af = AF_INET6; memcpy(&nl.saddr.v6, SAADDR(¶m->sincr), 16); memcpy(&nl.daddr.v6, SAADDR(¶m->sincl), 16); } else #endif { nl.af = AF_INET; memcpy(&nl.saddr.v4, SAADDR(¶m->sincr), 4); memcpy(&nl.daddr.v4, SAADDR(¶m->sincl), 4); } nl.sport = *SAPORT(¶m->sincr); nl.dport = *SAPORT(¶m->sincl); if(ioctl(pf_device, DIOCNATLOOK, &nl)) return 1; memset(¶m->req, 0, sizeof(param->req)); *SAFAMILY(¶m->req) = nl.af; #ifndef NOIPV6 if(nl.af == AF_INET6) memcpy(SAADDR(¶m->req), &nl.rdaddr.v6, 16); else #endif memcpy(SAADDR(¶m->req), &nl.rdaddr.v4, 4); *SAPORT(¶m->req) = nl.rdport; return 0; } #endif #ifdef WITH_NETFILTER /* Linux keeps the original address for the connection it redirected. */ static int transparent_netfilter(struct clientparam *param) { socklen_t len = sizeof(param->req); #ifdef SO_ORIGINAL_DST if(getsockopt(param->clisock, #ifndef NOIPV6 #ifdef SOL_IPV6 *SAFAMILY(¶m->sincr) == AF_INET6?SOL_IPV6: #endif #endif SOL_IP, SO_ORIGINAL_DST, (struct sockaddr *) ¶m->req, &len) || !memcmp((char *)SAADDR(¶m->req), "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", SAADDRLEN(¶m->req))){ return 1; } return 0; #else #error No SO_ORIGINAL_DST defined #endif } #endif /* Some redirections leave the original address on the socket itself, so the local address of the accepted connection is what the client asked for. A connection which was not redirected at all arrives at the address the service listens on, and taking that as the destination would send the service to itself. Refuse instead of making the connection. */ static int transparent_socket(struct clientparam *param) { if(*SAFAMILY(¶m->sincl) != AF_INET && *SAFAMILY(¶m->sincl) != AF_INET6) return 1; if(*SAPORT(¶m->sincl) == *SAPORT(¶m->srv->intsa) && (SAISNULL(¶m->srv->intsa) || !memcmp(SAADDR(¶m->sincl), SAADDR(¶m->srv->intsa), SAADDRLEN(¶m->sincl)))) return 2; param->req = param->sincl; param->sincl = param->srv->intsa; return 0; } static void* transparent_filter_open(void * idata, struct srvparam * param){ return idata; } static FILTER_ACTION transparent_filter_client(void *fo, struct clientparam * param, void** fc){ char addrbuf[64]; int res = 1; #ifdef WITH_NETFILTER if(transparent_mode == TRANSPARENT_AUTO || transparent_mode == TRANSPARENT_NETFILTER) res = transparent_netfilter(param); #endif #ifdef WITH_PF if(res && (transparent_mode == TRANSPARENT_AUTO || transparent_mode == TRANSPARENT_PF)) res = transparent_pf(param); #endif if(res && (transparent_mode == TRANSPARENT_AUTO || transparent_mode == TRANSPARENT_SOCKET)){ res = transparent_socket(param); if(res == 2){ param->srv->logfunc(param, (unsigned char *)"transparent: connection was not redirected"); return REJECT; } } /* Nothing knows where this was going: leave the request alone, so the service decides as it would without the command. */ if(res) return PASS; pl->myinet_ntop(*SAFAMILY(¶m->req), SAADDR(¶m->req), (char *)addrbuf, sizeof(addrbuf)); if(param->hostname) pl->freefunc(param->hostname); param->hostname = (unsigned char *)pl->strdupfunc(addrbuf); param->sinsr = param->req; return PASS; } static void transparent_filter_clear(void *fo){ } static void transparent_filter_close(void *fo){ } static struct filter transparent_filter = { NULL, "Transparent filter", "Transparent filter", transparent_filter_open, transparent_filter_client, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, transparent_filter_clear, transparent_filter_close }; int h_transparent(int argc, unsigned char **argv){ transparent_mode = TRANSPARENT_AUTO; if(argc > 1){ if(!strcmp((char *)argv[1], "auto")) transparent_mode = TRANSPARENT_AUTO; else if(!strcmp((char *)argv[1], "netfilter")){ #ifndef WITH_NETFILTER fprintf(stderr, "transparent: netfilter is not available in this build\n"); return 1; #else transparent_mode = TRANSPARENT_NETFILTER; #endif } else if(!strcmp((char *)argv[1], "pf")){ #ifndef WITH_PF fprintf(stderr, "transparent: pf is not available in this build\n"); return 1; #else transparent_mode = TRANSPARENT_PF; #endif } else if(!strcmp((char *)argv[1], "socket")) transparent_mode = TRANSPARENT_SOCKET; else { fprintf(stderr, "transparent: unknown mode %s, expected auto, netfilter, pf or socket\n", argv[1]); return 1; } } transparent_filter.filter_open = transparent_filter_open; return 0; } int h_notransparent(int argc, unsigned char **argv){ transparent_filter.filter_open = NULL; return 0; } void transparent_install(void){ pl = &pluginlink; /* A reload runs this again: the filter is a single static entry, so it is only linked in once, and the commands decide whether it acts. */ if(!transparent_loaded){ transparent_loaded = 1; transparent_filter.next = pl->conf->filters; pl->conf->filters = &transparent_filter; } transparent_filter.filter_open = NULL; transparent_mode = TRANSPARENT_AUTO; } #endif