./src/linux.c
Patch: Additional logging in NTP
+- /***************************************
+-
+- 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 <sys/utsname.h>
+- #include <sys/wait.h>
+- #include "common.h"
+- #include "transdata.h"
+-
+- #if HAVE_LINUX_NETFILTER_IPV4_H
+- # include <limits.h>
+- # include <linux/netfilter_ipv4.h>
+- #endif
+-
+- #if TRANS_DATA
+- #if USE_LIBIPTC
+- # include <libiptc.h>
+- # include <linux/netfilter_ipv4/ip_nat.h>
+- #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 */
+-