/*************************************** This is part of frox: A simple transparent FTP proxy Copyright (C) 2000 James Hollingshead This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA linux.c -- Nasty, non-portable, linux specific stuff which changes from kernel release to kernel release. ie the transparent proxy calls. ***************************************/ #include #include #include "common.h" #include "transdata.h" #if HAVE_LINUX_NETFILTER_IPV4_H # include # include #endif #if TRANS_DATA #if USE_LIBIPTC # include # include #endif #endif static enum { LINUX_2_0, LINUX_2_2, LINUX_2_4, OTHER } kernel; /* Had to add os_init() for the purpose of IP_FILTER on *BSD, but since * it's there might as well use it! */ int os_init(void) { struct utsname tmp; uname(&tmp); if(!strncmp(tmp.release, "2.0.", 4)) kernel = LINUX_2_0; else if(!strncmp(tmp.release, "2.2.", 4)) kernel = LINUX_2_2; else if(!strncmp(tmp.release, "2.4.", 4)) kernel = LINUX_2_4; else kernel = OTHER; return 0; } /* ------------------------------------------------------------- ** ** Get the original destination address of a transparently proxied ** socket. ** ------------------------------------------------------------- */ int get_orig_dest(int fd, struct sockaddr_in *addr) { socklen_t len; len = sizeof(*addr); switch (kernel) { case LINUX_2_0: case LINUX_2_2: return (getsockname(fd, (struct sockaddr *) addr, &len)); default: #ifdef SO_ORIGINAL_DST /*Header support for kernel 2.4 */ if(getsockopt(fd, SOL_IP, SO_ORIGINAL_DST, (struct sockaddr *) addr, &len)) return -1; if(!addr->sin_addr.s_addr) return -1; return 0; #else write_log(ERROR, "Running on a kernel we haven't been compiled for. Oooops."); return (-1); #endif } } /* ------------------------------------------------------------- ** ** Get the address of the interface we connect to the client through ** for putting in our 227 reply. For 2.4 do a getsockname on the ** control socket. For 2.2 this gives us the orriginal destination of ** the transparently proxied connection, so we do some nasty hackery ** instead. ** ------------------------------------------------------------- */ int get_local_address(const int fd, struct sockaddr_in *addr) { int sockfd; socklen_t len; /*If ListenAddress is in the config file then use the address * from that*/ *addr = config.listen_address; if(addr->sin_addr.s_addr != 0) { addr->sin_port = 0; return (0); } switch (kernel) { case LINUX_2_2: /* This piece of code ought to be taken out and shot ** (yes - it opens a UDP socket, does a getsockname, ** and then closes the socket before anything ** happens!) Suggestions for an alternative welcomed */ if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) return (-1); addr->sin_family = AF_INET; addr->sin_addr = info->client_control.address.sin_addr; addr->sin_port = htons(12345); len = sizeof(*addr); if(connect(sockfd, (struct sockaddr *) addr, len) == -1) { close(sockfd); return (-1); } if(getsockname(sockfd, (struct sockaddr *) addr, &len) == -1) { close(sockfd); return (-1); } close(sockfd); addr->sin_port = 0; return (0); case LINUX_2_4: default: len = sizeof(*addr); return (getsockname(fd, (struct sockaddr *) addr, &len)); } } int bindtodevice(int fd) { if(!config.device) return (0); if(setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, (void *) config.device, (socklen_t) strlen(config.device) + 1) != 0) { debug_perr("Binding to device"); return (-1); } write_log(IMPORT, "Bound to device %s", config.device); return (0); } #if TRANS_DATA /* ------------------------------------------------------------- ** ** Functions below are designed for the data link between the client ** and the proxy. We want to fool the client that this connection ** comes from the ftp server it connected to, so we have to be able ** to either connect to the client with a false source address ** (active mode), or intercept the client trying to connect to the ** server's data port (passive mode). ** ** On kernel 2.4 we do this using netfilter snat or dnat through ** libiptc. On 2.2 we simply do bind-to-foreign-address.[Not tested ** recently :) ] ** ** Most of this stuff is a bit of a mess. Perhaps that is ** unavoidable... ** ------------------------------------------------------------- */ #ifndef USE_LIBIPTC int kernel_transdata_setup() { if(kernel != LINUX_2_4) return (0); fprintf(stderr, "You appear to be running a 2.4.x Linux kernel," " but frox was not configured\n" "with --enable-libiptc. Data connections will NOT" " be transparently proxied\n"); return (-1); } int kernel_td_connect(struct fd_request req) { uid_t uid; int sockfd, i; if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { debug_perr("socket"); return (-1); } uid = geteuid(); write_log(VERBOSE, "TDS: Regaining priveliges for bind-to-foreign-address"); seteuid(0); i = bind(sockfd, (struct sockaddr *) &req.remote, sizeof(req.remote)); write_log(VERBOSE, "TDS: Dropping them again"); seteuid(uid); if(i) { debug_err("bind failed"); close(sockfd); return (-1); } i = connect(sockfd, (struct sockaddr *) &req.remote, sizeof(req.remote)); if(i) { close(sockfd); return (-1); } return (sockfd); } int kernel_td_listen(struct fd_request req) { uid_t uid; int i, sockfd; sockfd = socket(AF_INET, SOCK_STREAM, 0); uid = geteuid(); write_log(VERBOSE, "TDS: Regaining privelidges for bind-to-foreign-address"); seteuid(0); i = bind(sockfd, (struct sockaddr *) &req.local, sizeof(req.local)); write_log(VERBOSE, "TDS: Dropping them again"); seteuid(uid); if(i) { debug_err("bind failed"); close(sockfd); return (-1); } if(listen(sockfd, 5)) { debug_perr("listen"); close(sockfd); return (-1); } return (sockfd); } /*Kernel 2.2.x automatically cleans up after bind-to-foreign-address*/ int kernel_td_unlisten(struct fd_request req) { return (0); } void kernel_td_flush(void) { } #else /*USE_LIBIPTC */ #define FROXSNAT "froxsnat" #define FROXDNAT "froxdnat" int init_chains(void); void serve_requests(int fd); int add_entry(const struct ipt_entry *e, const char *chain); int delete_entry(const struct ipt_entry *e, const char *chain); struct ipt_entry *get_entry(struct sockaddr_in src, struct sockaddr_in dst, struct sockaddr_in to, int snat); int kernel_transdata_setup() { if(kernel == LINUX_2_2) return (0); if(init_chains() == -1) { fprintf(stderr, "\nChains " FROXSNAT " and/or " FROXDNAT " do not exist. Data connections\n" "will not be transparently proxied. Read" " README.transdata for details\n\n"); return (-1); } return 0; } int kernel_td_connect(struct fd_request req) { struct sockaddr_in address; struct ipt_entry *e; int sockfd, i; if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { debug_perr("socket"); return (-1); } memset(&address, 0, sizeof(address)); address.sin_family = AF_INET; i = bind_me(sockfd, &address, req.ports); if(i) { debug_err("bind failed"); close(sockfd); return (-1); } /* DO SNAT */ address = config.listen_address; address.sin_port = 0; e = get_entry(address, req.remote, req.local, TRUE); if(e == NULL) { close(sockfd); return -1; } if(add_entry(e, FROXSNAT) == -1) { free(e); close(sockfd); return -1; } i = connect(sockfd, (struct sockaddr *) &req.remote, sizeof(req.remote)); /* UNDO SNAT */ delete_entry(e, FROXSNAT); free(e); if(i) { close(sockfd); return (-1); } return (sockfd); } int kernel_td_listen(struct fd_request req) { struct sockaddr_in address; struct ipt_entry *e; int i, sockfd; sockfd = socket(AF_INET, SOCK_STREAM, 0); i = bind_me(sockfd, &req.local, req.ports); if(i) { debug_err("bind failed"); close(sockfd); return (-1); } if(listen(sockfd, 5)) { debug_perr("listen"); close(sockfd); return (-1); } /*DO DNAT */ address.sin_addr.s_addr = INADDR_ANY; address.sin_port = 0; e = get_entry(address, req.remote, req.local, FALSE); if(e == NULL) { debug_err("Can't get entry"); close(sockfd); return -1; } if(add_entry(e, FROXDNAT) == -1) { debug_err("Unable to add entry"); free(e); close(sockfd); return -1; } return (sockfd); } int kernel_td_unlisten(struct fd_request req) { struct ipt_entry *e; struct sockaddr_in address; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = 0; e = get_entry(address, req.remote, req.local, FALSE); if(e == NULL) { debug_err("Can't get entry"); return -1; } if(delete_entry(e, FROXDNAT) == -1) { debug_err("Unable to delete entry"); free(e); return -1; } return (0); } void kernel_td_flush(void) { iptc_handle_t h; uid_t uid; uid = geteuid(); write_log(VERBOSE, "TDS: Regaining privelidges for flushing chains"); seteuid(0); write_log(VERBOSE, "Flushing chains..."); if((h = iptc_init("nat"))) { iptc_flush_entries(FROXSNAT, &h); iptc_flush_entries(FROXDNAT, &h); if(iptc_commit(&h)) write_log(VERBOSE, " Success"); else write_log(VERBOSE, " Failed"); } write_log(VERBOSE, "TDS: Dropping them again"); seteuid(uid); } int init_chains() { iptc_handle_t h; if(!(h = iptc_init("nat"))) return (-1); if(!iptc_is_chain(FROXSNAT, h)) return (-1); if(!iptc_is_chain(FROXDNAT, h)) return (-1); return (0); } int add_entry(const struct ipt_entry *e, const char *chain) { iptc_handle_t h; uid_t uid; int ret = -1; uid = geteuid(); write_log(VERBOSE, "TDS: Regaining privelidges for inserting firewall rules"); seteuid(0); if((h = iptc_init("nat")) && iptc_append_entry(chain, e, &h) && iptc_commit(&h)) ret = 0; else ret = -1; write_log(VERBOSE, "TDS: Dropping them again"); seteuid(uid); return ret; } /*FIXME deletion by matching entry doesn't seem to be reliable. Should we keep track of rule numbers and delete those?*/ int delete_entry(const struct ipt_entry *e, const char *chain) { iptc_handle_t h; unsigned char *matchmask = NULL; uid_t uid; int ret; uid = geteuid(); write_log(VERBOSE, "TDS: Regaining privelidges for deleting firewall rules"); seteuid(0); if((h = iptc_init("nat")) && (matchmask = malloc(e->next_offset)) && iptc_delete_entry(chain, e, matchmask, &h) && iptc_commit(&h)) ret = 0; else { debug_err(iptc_strerror(errno)); ret = -1; } write_log(VERBOSE, "TDS: Dropping them again"); seteuid(uid); if(matchmask) free(matchmask); return ret; } /* ------------------------------------------------------------- ** ** Set up an ipt_entry structure. Which will do the equivalent of ** "iptables -p tcp -s src -d dst -j (SNAT|DNAT) --to to". If snat is ** TRUE we do snat, otherwise dnat. This probably isn't the correct ** way to use libiptc, but there isn't much sample code/documentation ** and I really couldn't face messing around with dlopen etc. ** ** The return value should be freed by the calling function. ** ** I want my bind-to-foreign-address back :) ** ------------------------------------------------------------- */ struct ipt_entry *get_entry(struct sockaddr_in src, struct sockaddr_in dst, struct sockaddr_in to, int snat) { struct ipt_entry *e; struct ipt_entry_match *match; struct ipt_tcp *tcpinfo; struct ipt_entry_target *target; struct ip_nat_multi_range *mr; unsigned int size1, size2, size3; size1 = IPT_ALIGN(sizeof(struct ipt_entry)); size2 = IPT_ALIGN(sizeof(struct ipt_entry_match) + sizeof(struct ipt_tcp)); size3 = IPT_ALIGN(sizeof(struct ipt_entry_target) + sizeof(struct ip_nat_multi_range)); e = malloc(size1 + size2 + size3); if(e == NULL) { write_log(ERROR, "Malloc failure"); return (NULL); } memset(e, 0, size1 + size2 + size3); /*Offsets to the other bits */ e->target_offset = size1 + size2; e->next_offset = size1 + size2 + size3; /*Set up packet matching rules */ if((e->ip.src.s_addr = src.sin_addr.s_addr) == INADDR_ANY) e->ip.smsk.s_addr = 0; else e->ip.smsk.s_addr = inet_addr("255.255.255.255"); if((e->ip.dst.s_addr = dst.sin_addr.s_addr) == INADDR_ANY) e->ip.dmsk.s_addr = 0; else e->ip.dmsk.s_addr = inet_addr("255.255.255.255"); e->ip.proto = IPPROTO_TCP; e->nfcache = NFC_UNKNOWN; /*Think this stops caching. */ /*TCP specific matching(ie. ports) */ match = (struct ipt_entry_match *) e->elems; match->u.match_size = size2; strcpy(match->u.user.name, "tcp"); tcpinfo = (struct ipt_tcp *) match->data; if(src.sin_port == 0) { tcpinfo->spts[0] = ntohs(0); tcpinfo->spts[1] = ntohs(0xFFFF); } else tcpinfo->spts[0] = tcpinfo->spts[1] = ntohs(src.sin_port); if(dst.sin_port == 0) { tcpinfo->dpts[0] = ntohs(0); tcpinfo->dpts[1] = ntohs(0xFFFF); } else tcpinfo->dpts[0] = tcpinfo->dpts[1] = ntohs(dst.sin_port); /*And the target */ target = (struct ipt_entry_target *) (e->elems + size2); target->u.target_size = size3; if(snat) strcpy(target->u.user.name, "SNAT"); else strcpy(target->u.user.name, "DNAT"); mr = (struct ip_nat_multi_range *) target->data; mr->rangesize = 1; mr->range[0].flags = IP_NAT_RANGE_PROTO_SPECIFIED | IP_NAT_RANGE_MAP_IPS; mr->range[0].min.tcp.port = mr->range[0].max.tcp.port = to.sin_port; mr->range[0].min_ip = mr->range[0].max_ip = to.sin_addr.s_addr; return e; } #endif /*USE_LIBIPTC */ #endif /*TRANS_DATA */