/* * Copyright (C) 1999-2004 Joachim Wieland * * 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, USA. */ #include #include #include "jftpgw.h" #ifdef HAVE_LIBWRAP #include #include #ifndef LIBWRAP_ALLOW_FACILITY # define LIBWRAP_ALLOW_FACILITY LOG_AUTH #endif #ifndef LIBWRAP_ALLOW_SEVERITY # define LIBWRAP_ALLOW_SEVERITY LOG_INFO #endif #ifndef LIBWRAP_DENY_FACILITY # define LIBWRAP_DENY_FACILITY LOG_AUTH #endif #ifndef LIBWRAP_DENY_SEVERITY # define LIBWRAP_DENY_SEVERITY LOG_WARNING #endif int allow_severity = LIBWRAP_ALLOW_FACILITY | LIBWRAP_ALLOW_SEVERITY; int deny_severity = LIBWRAP_DENY_FACILITY | LIBWRAP_DENY_SEVERITY; #endif extern struct hostent_list* hostcache; extern struct serverinfo srvinfo; int chlds_exited; int should_read_config; struct descriptor_set { fd_set set; int maxfd; }; static int child_setup(int, struct clientinfo*); static struct descriptor_set listen_on_ifaces(const char*, struct clientinfo*); static int get_connecting_socket(struct descriptor_set); static int say_welcome(int); static char* prependcode(const char* s, int code); /* bindport binds to the specified PORT on HOSTNAME (which may also be a * dot-notation IP and returns the socket descriptor * * Parameters: hostname & port: Where to bind * * Return value: The socket descriptor of the bound socket * * Called by: waitclient * * */ int bindport(const char *hostname, int port) { unsigned long inetaddr =1; int shandle; int one = 1; struct sockaddr_in sin; unsigned long host_ip; memset((void*)&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; inetaddr = inet_addr(hostname); if (inetaddr == (unsigned long int) UINT_MAX) { /* see if HOSTNAME is an interface */ if (get_interface_ip(hostname, &sin) == 0) { /* found it */ /* sin.sin_addr is already set */ sin.sin_family = AF_INET; } else { /* HOSTNAME was probably a name since inet_addr * returned an error. * Look up the name to get the IP */ host_ip = hostent_get_ip(&hostcache, hostname); if (host_ip == (unsigned long int) UINT_MAX) { jlog(1, "Could not resolve %s: %s", hostname, strerror(errno)); perror("Could not resolve the hostname"); return -1; } sin.sin_addr.s_addr = host_ip; } } else { /* okay, HOSTNAME was a valid dot-notation IP */ sin.sin_addr.s_addr = inetaddr; } sin.sin_port = htons(port); /* become root again - use our function instead of plain * setuid() for the logging */ if (changeid(PRIV, UID, "Changing ID to root (socket(), bind())")) { return -1; } shandle = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (shandle < 0) { jlog(1, "Error creating socket to bind: %s", strerror(errno)); perror("Error creating socket to bind to"); return -1; } if (setsockopt(shandle, SOL_SOCKET, SO_REUSEADDR, (void*) &one, sizeof(one)) < 0) { jlog(3, "Error setting socket to SO_REUSEADDR"); } if (bind(shandle, (struct sockaddr *) &sin, sizeof(sin)) < 0) { jlog(1, "Error binding: %s", strerror(errno)); perror("Error binding"); return -1; } if (listen(shandle, 5) < 0) { jlog(1, "Error listening on the socket: %s", strerror(errno)); perror("Error listening on a bound socket"); return -1; } jlog(6, "Listening on %s, port %d, fd %d", hostname, port, shandle); if (changeid(UNPRIV, EUID, "Changing id back (socket(), bind())") < 0) { return -1; } return shandle; } /* waitclient() waits for a client to connect. It binds to the ports and * listens to them. If a connection comes it, jftpgw forks. The parent process * keeps on listening whereas the child process handles the connection * * Parameters: hostnames: Where to bind to * clntinfo: connection information * * Return value: -1 on error (if error message has been sent to the client) * -2 on error (if error message should be created by * strerror() * socket handle: on success (by the child process * * Note: The parent process never returns from this function, * it is terminated by a signal */ int waitclient(const char* hostnames, struct clientinfo* clntinfo) { int chldpid =0; const char* option = 0; int ahandle, i; struct sigaction sa; struct descriptor_set d_set; unsigned int peer_ip; struct sockaddr_in c_in; time_t now; if (srvinfo.multithread) { daemonize(); } if (changeid(UNPRIV, EUID, "Changing id back (socket(), bind())") < 0) { return -1; } d_set = listen_on_ifaces(hostnames, clntinfo); if (d_set.maxfd < 0) { jlog(8, "d_set.maxfd was negative: %d", d_set.maxfd); return -1; } /* we have successfully bound */ /* become root again - use our function instead of plain * setuid() for the logging */ if (changeid(PRIV, UID, "Changing ID to root (pidfile)") < 0) { return -1; } option = config_get_option("pidfile"); if (option) { FILE* pidf; umask(022); pidf = fopen(option, "w"); if (pidf) { fprintf(pidf, "%ld\n", (long) getpid()); fclose(pidf); /* if successful register function to remove the * pidfile */ atexit(removepidfile); } else { jlog(2, "Error creating pidfile %s", option); } } /* this has to be done for the daemonization. We do it now after * the pidfile has been created */ umask(0); srvinfo.ready_to_serve = SVR_LAUNCH_READY; if (stage_action("startsetup") < 0) { return -1; } sa.sa_handler = childterm; chlds_exited = 0; sigemptyset (&sa.sa_mask); #ifndef WINDOWS sa.sa_flags = SA_RESTART; #endif sigaction (SIGCHLD, &sa, 0); /* Close stdin,stdout,stderr */ for(i = 0; i <= 2 && srvinfo.multithread; i++) { close(i); } srvinfo.main_server_pid = getpid(); atexit(sayterminating); while(1) { ahandle = get_connecting_socket(d_set); if (ahandle == -1) { /* either select() or accept() failed */ /* I don't try resume here because we are in an * endless loop. The danger of the programm falling * into an infinite loop consuming all cpu time is * too big... */ jlog(8, "get_connecting_socket() returned error code"); return -1; } c_in = socketinfo_get_local_sin(ahandle); peer_ip = get_uint_peer_ip(ahandle); now = time(NULL); config_counter_increase(peer_ip, /* from ip */ c_in.sin_addr.s_addr, /* proxy_ip */ ntohs(c_in.sin_port), /* proxy_port */ now); /* specific_time */ if (config_check_limit_violation()) { say(ahandle, "500 Too many connections, sorry\r\n"); close(ahandle); config_counter_decrease(peer_ip, /* from ip */ c_in.sin_addr.s_addr, /* proxy_ip */ ntohs(c_in.sin_port), /* proxy_port */ now); /* specific_time */ continue; } if (srvinfo.multithread) { if ((chldpid = fork()) < 0) { jlog(1, "Error forking: %s", strerror(errno)); close(ahandle); return -1; } if (chldpid > 0) { /* parent process */ /* register the PID */ register_pid(chldpid, peer_ip, c_in.sin_addr.s_addr, /* proxy_ip */ ntohs(c_in.sin_port), /* proxy_port */ now); /* specific_time */ close(ahandle); } if (chldpid == 0) { /* child process */ jlog(8, "forked to pid %d", getpid()); } } if (!srvinfo.multithread || chldpid == 0) { return child_setup(ahandle, clntinfo); } } } static int get_connecting_socket(struct descriptor_set d_set) { #ifdef HAVE_SOCKLEN_T socklen_t size; #else int size; #endif static int nfd; int shandle, ahandle; fd_set backupset; struct sockaddr_in sin; size = sizeof(sin); memcpy(&backupset, &d_set.set, sizeof(fd_set)); /* is there no remaining ready fd from the last select() ? */ if (nfd == 0) { while (1) { /* eternal select() */ /* nfd returns the number of fds that are ready */ nfd = select(d_set.maxfd + 1, &d_set.set, 0, 0, 0); if (nfd > 0) { break; } if (errno == EINTR) { memcpy(&d_set.set, &backupset, sizeof(fd_set)); if (chlds_exited > 0) { get_chld_pid(); } if (should_read_config) { jlog(9, "Re-reading configuration"); reread_config(); } continue; } jlog(1, "select() failed: %s, nfd: %d", strerror(errno), nfd); return -1; } } /* a descriptor is ready */ shandle = 0; while (!FD_ISSET(shandle, &d_set.set)) { shandle++; } /* one descriptor less is ready in the set */ nfd--; while(1) { ahandle = accept(shandle, (struct sockaddr *) &sin, &size); if (ahandle < 0) { switch(errno) { case EINTR: /* signal */ case ECONNRESET: /* client quit before three-way-handshake */ case ENETDOWN: /* the others: see manpage */ #ifdef EPROTO case EPROTO: #endif case ENOPROTOOPT: case EHOSTDOWN: #ifdef ENONET case ENONET: #endif case EHOSTUNREACH: case EOPNOTSUPP: case ENETUNREACH: continue; } jlog(1, "accept() failed: %s", strerror(errno)); return -1; } break; } return ahandle; } static int child_setup(int sock_fd, struct clientinfo *clntinfo) { struct sockaddr_in t_in; struct sockaddr_in c_in; unsigned long int peer_ip = get_uint_peer_ip(sock_fd); struct sigaction sa; int i, ret; #ifdef HAVE_LIBWRAP struct request_info req; int libwrap_allow = 0; #endif if (peer_ip == (unsigned long int) UINT_MAX) { return -1; } if (stage_action("connect") < 0) { say(sock_fd, "421 Error setting up (see logfile)\r\n"); return -1; } /* The clients ignore the SIGHUP signal. Thus * the user can issue a killall -HUP jftpgw * and the master jftpgw process rereads its * configuration file without affecting the * child servers */ sa.sa_handler = SIG_IGN; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sigaction(SIGHUP, &sa, 0); /* And they just reap the status of exited children */ sa.sa_handler = reap_chld_info; sigemptyset(&sa.sa_mask); #ifndef WINDOWS sa.sa_flags = SA_RESTART; #endif sigaction(SIGCHLD, &sa, 0); for (i = 0; i < clntinfo->boundsocket_niface; i++) { close(clntinfo->boundsocket_list[i]); } free(clntinfo->boundsocket_list); clntinfo->boundsocket_list = (int*) 0; clntinfo->forward.passauth = 0; clntinfo->serversocket = -1; clntinfo->clientsocket = sock_fd; jlog(7, "Connection from %s", get_char_peer_ip(sock_fd)); c_in = socketinfo_get_local_sin(sock_fd); jlog(7, "Client tried to connect to %s on port %d", inet_ntoa(c_in.sin_addr), ntohs(c_in.sin_port)); clntinfo->addr_to_client = c_in.sin_addr.s_addr; clntinfo->proxy_ip = c_in.sin_addr.s_addr; clntinfo->proxy_port = ntohs(c_in.sin_port); /* see if we are in transparent mode */ t_in = socketinfo_get_transparent_target_sin(sock_fd); jlog(7, "Transparent target seems to be %s on port %d", inet_ntoa(t_in.sin_addr), ntohs(t_in.sin_port)); jlog(9, "Checking TAG_GLOBAL | TAG_FROM | TAG_PROXYIP | TAG_PROXYPORT | TAG_TIME | TAG_SERVERTYPE"); ret = config_shrink_config(peer_ip, /* source IP */ -1, /* dest IP */ (char*) 0, /* dest name */ 0, /* dest port */ (char*) 0, /* dest user */ -1, /* forwarded IP */ (char*) 0, /* forwarded destination */ 0, /* forwarded destinationport */ (char*) 0, /* forwarded username */ 0, /* set no specific time */ clntinfo->proxy_ip, /* ip of proxy if */ clntinfo->proxy_port, /* port of proxy if */ srvinfo.servertype, /* global variable */ &hostcache, TAG_CONNECTED ); if (ret != 0) { jlog(2, "Error shrinking config data"); return ret; } #ifndef HAVE_LIBWRAP srvinfo.tcp_wrapper = 0; #endif if (srvinfo.tcp_wrapper) { #ifdef HAVE_LIBWRAP /* use libwrap(tcp_wrapper) for access control * patch by . * It is useful to check both keywords "jftpgw" and "ftp-gw" * for TIS Gauntlet flabour. */ request_init(&req, RQ_DAEMON, "ftp-gw", RQ_CLIENT_ADDR, conv_ip_to_char(peer_ip), NULL); libwrap_allow = hosts_access(&req); request_init(&req, RQ_DAEMON, "jftpgw", RQ_CLIENT_ADDR, conv_ip_to_char(peer_ip), NULL); if (libwrap_allow || hosts_access(&req)) { jlog(5, "%s is allowed by libwrap to connect.", conv_ip_to_char(peer_ip)); } else { say(sock_fd, "500 You are not allowed by libwrap to " "connect. Goodbye.\r\n"); jlog(5, "%s was not allowed to connect.", conv_ip_to_char(peer_ip)); close(sock_fd); return -2; } #endif } else { if ( ! config_get_option("access") || ! strcmp(config_get_option("access"), "allow") == 0) { say(sock_fd, "500 You are not allowed to " "connect. Goodbye.\r\n"); jlog(5, "%s was not allowed to connect.", conv_ip_to_char(peer_ip)); close(sock_fd); return -2; } else { jlog(6, "%s is allowed to connect.", conv_ip_to_char(peer_ip)); } } if (config_get_bool("transparent-proxy") && get_uint_peer_ip(sock_fd) == t_in.sin_addr.s_addr) { jlog(4, "proxy loop detected - machine connects to itself, disabling transparent proxy support"); } jlog(9, "Proxy loop check: peer_ip: %s, t_in_ip: %s", get_char_peer_ip(sock_fd), inet_ntoa(t_in.sin_addr)); #ifdef HAVE_LINUX_NETFILTER_IPV4_H jlog(9, "HAVE_LINUX_NETFILTER_IPV4_H true, c_in_ip: %s, t_in_port: %d, " "c_in_port: %d", inet_ntoa(c_in.sin_addr), ntohs(t_in.sin_port), ntohs(c_in.sin_port)); #endif if (config_get_bool("transparent-proxy") && /* see if the proxy loops. It loops when the source has the * same IP as the destination. There is no need for such a * configuration "in the wild". Is there? */ (!(get_uint_peer_ip(sock_fd) == t_in.sin_addr.s_addr) #ifdef HAVE_LINUX_NETFILTER_IPV4_H && !(t_in.sin_addr.s_addr == c_in.sin_addr.s_addr && t_in.sin_port == c_in.sin_port) #endif )) { jlog(9, "Enabling transparent proxy"); clntinfo->transparent = TRANSPARENT_YES; clntinfo->transparent_destination = t_in; } else { jlog(9, "No transparent proxy support"); clntinfo->transparent = TRANSPARENT_NO; clntinfo->transparent_destination.sin_port = 0; clntinfo->transparent_destination.sin_addr.s_addr = 0; } if (stage_action("connectsetup") < 0) { say(sock_fd, "421 Error setting up (see logfile)\r\n"); return -1; } /* set the target for the transparent proxy */ if (clntinfo->transparent == TRANSPARENT_YES) { char* colon; long int dport; if (config_get_option("transparent-forward") && config_compare_option("logintime", "connect")) { clntinfo->destination = strdup(config_get_option("transparent-forward")); jlog(9, "Enabling transparent forward to %s", clntinfo->destination); } else { clntinfo->destination = socketinfo_get_transparent_target_char(sock_fd); } colon = strchr(clntinfo->destination, ':'); if (!colon) { /* abort, shouldnt happen */ jlog(9, "Could not get transparent target properly"); clntinfo->transparent = TRANSPARENT_NO; return say_welcome(sock_fd); } *colon = '\0'; /* terminate destination */ colon++; /* skip to port number */ dport = strtol(colon, NULL, 10); if (errno == ERANGE && (dport == LONG_MIN || dport == LONG_MAX)) { clntinfo->destinationport = config_get_ioption("serverport", DEFAULTSERVERPORT); /* reverse for logging */ colon--; *colon = ':'; jlog(6, "Could not parse dest/port to connect to: %s", clntinfo->destination); return -1; } else { clntinfo->destinationport = dport; } } if ( ! config_compare_option("logintime", "connect") ) { ret = say_welcome(sock_fd); } else { ret = login(clntinfo, LOGIN_ST_CONNECTED); } /* seed the random number generator */ srand(time(NULL)); return ret; } int inetd_connected(int sock, struct clientinfo* clntinfo) { if (stage_action("startsetup") < 0) { return -1; } return child_setup(sock, clntinfo); } static int say_welcome(int sock_fd) { const char* welcome = config_get_option("welcomeline"); if (config_get_option("welcomeline")) { char* welcmsg; welcmsg = prependcode(welcome, 220); enough_mem(welcmsg); jlog(9, "Saying this text as welcomeline: %s", welcmsg); say(sock_fd, welcmsg); free(welcmsg); } else { say(sock_fd, "220 Joe FTP Proxy Server/Gateway (v"JFTPGW_VERSION") ready\r\n"); } return 0; } static struct descriptor_set listen_on_ifaces(const char* hostnames, struct clientinfo* clntinfo) { char* part =0, *portdel =0; char* hostnames2; size_t hostnames2size; int offset, port, shandle, i; struct descriptor_set d_set; offset = 0; clntinfo->boundsocket_niface = 0; FD_ZERO(&d_set.set); d_set.maxfd = 0; /* we have to make the string suitable for quotstrtok by appending a * WHITESPACE character */ hostnames2size = strlen(hostnames) + 2; hostnames2 = (char*) malloc(hostnames2size); enough_mem(hostnames2); snprintf(hostnames2, hostnames2size, "%s ", hostnames); while ((part = quotstrtok(hostnames2, WHITESPACES, &offset))) { /* do some counting */ clntinfo->boundsocket_niface++; free(part); } clntinfo->boundsocket_list = (int*) malloc(sizeof(int) * clntinfo->boundsocket_niface); enough_mem(clntinfo->boundsocket_list); offset = 0; i = 0; while ((part = quotstrtok(hostnames2, WHITESPACES, &offset))) { portdel = strchr(part, ':'); if (!portdel) { jlog(3, "Invalid IP/Port specification: %s", part); free(part); continue; } *portdel = (char) 0; errno = 0; port = strtol(portdel+1, (char**) 0, 10); if (errno || port > 65535 || port <= 0) { jlog(4, "Invalid port specification: %s. " "Using default value %d", portdel, DEFAULTBINDPORT); port = DEFAULTBINDPORT; } portdel = (char*) 0; jlog(9, "binding to %s, port %d", part, port); shandle = bindport(part, port); free(part); part = (char*) 0; if (shandle < 0) { jlog(8, "Could not bind: %s", strerror(errno)); free(hostnames2); FD_ZERO(&d_set.set); d_set.maxfd = -1; return d_set; } FD_SET(shandle, &d_set.set); d_set.maxfd = MAX_VAL(d_set.maxfd, shandle); clntinfo->boundsocket_list[ i++ ] = shandle; } free(hostnames2); hostnames2 = (char*) 0; if (clntinfo->boundsocket_niface == 0) { jlog(2, "No interfaces found to bind to"); FD_ZERO(&d_set.set); d_set.maxfd = -1; return d_set; } return d_set; } static char* prependcode(const char* s, int code) { char* sdup = strdup(s); char* nextline = sdup; char* buffer; char* end, *replaceend, *bufferend, *bufferstart; char dashp = '-'; const char* const linefeed = "\r\n"; const char* const empty = ""; const char* append = linefeed; int count = 0; int i, length; size_t bufsize; enough_mem(sdup); replace_not_larger(sdup, "\\r", "\r"); replace_not_larger(sdup, "\\n", "\n"); if (code < 0) { code = -code; } if (code > 999) { code = code % 1000; } if (code < 100) { code += 100; } /* count the newlines */ while ((nextline = strstr(nextline, "\r\n"))) { count++; nextline++; /* thus this position does not match again */ } /* memory: (count + 1) * strlen("xxx ") + 4 (to terminate for sure * with "\r\n") + 1 (Terminator) * * memory = (count + 1) * 4 + 1 */ bufsize = strlen(sdup) + (count + 1) * 4 + 4 + 1; bufferstart = buffer = (char*) malloc(bufsize); enough_mem(buffer); bufferend = buffer + bufsize - 1; end = nextline = sdup; while (strlen(end)) { end = strstr(nextline, "\r\n"); if (!end) { end = nextline + strlen(nextline); replaceend = end; dashp = ' '; append = linefeed; } else { /* don't replace the first '\' */ replaceend = end - 1; end += strlen("\r\n"); if (end == nextline + strlen(nextline)) { /* at the very end */ dashp = ' '; } else { dashp = '-'; } append = empty; } i = 0; while ((nextline + i) < replaceend) { if (nextline[i] == '%') { /* Remove the '%' */ nextline[i] = '/'; } /* remove any special characters between nextline * and end */ if ((unsigned char) nextline[i] < 32) { nextline[i] = '_'; } i++; } length = MIN_VAL( bufferend - bufferstart, strlen("xxx ") + (end - nextline) + strlen(append) + 1 ); snprintf(bufferstart, length, "%d%c%s%s", code, dashp, nextline, append); bufferstart += strlen(bufferstart); if (!*nextline) { /* end */ nextline = 0; } nextline = strstr(nextline, "\r\n") + strlen("\r\n"); } free(sdup); return buffer; }