/* - Ftp Server * Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994, 2002 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #if 0 static char sccsid[] = "@(#)ftpd.c 8.5 (Berkeley) 4/28/95"; #endif /* * FTP server. */ /* #define WITH_SPLICE */ #ifdef WITH_SPLICE #define _GNU_SOURCE /* for splice */ #error write to ntfs-3g partitions using splice() is very very slow #endif #ifdef HAVE_CONFIG_H # include #endif #if !defined (__GNUC__) && defined (_AIX) #pragma alloca #endif #ifndef alloca /* Make alloca work the best possible way. */ # ifdef __GNUC__ # define alloca __builtin_alloca # else /* not __GNUC__ */ # if HAVE_ALLOCA_H # include # else /* not __GNUC__ or HAVE_ALLOCA_H */ # ifndef _AIX /* Already did AIX, up at the top. */ char *alloca (); # endif /* not _AIX */ # endif /* not HAVE_ALLOCA_H */ # endif /* not __GNUC__ */ #endif /* not alloca */ #include #include #include #include #ifdef HAVE_SYS_WAIT_H # include #endif #include #include /* flock */ #include #ifdef HAVE_NETINET_IN_SYSTM_H # include #endif #ifdef HAVE_NETINET_IP_H # include #endif #define FTP_NAMES #include #include #include #include #include #include // TCP_CORK #include #include #include #include #include #include #include #include #include #include #if defined (HAVE_STDARG_H) && defined (__STDC__) && __STDC__ # include #else # include #endif #include #include #include #include #ifdef TIME_WITH_SYS_TIME # include # include #else # ifdef HAVE_SYS_TIME_H # include # else # include # endif #endif #include #ifdef HAVE_MMAP #include #endif #include #include #include "extern.h" #include "xgetcwd.h" /* libinetutils */ #include "avm_acl.h" #include "charencoding.h" #include "virtual.h" #include "usermap.h" #include "port_open.h" #include "pcplisten.h" #include "route_tool.h" /* route_tool_get_ipv6_dev route_tool_get_dev */ #include "logincontrol.h" /* LoginControlLogout */ #ifdef PUMA6_ARM #include "proxy.h" #endif #ifdef PUMA6_ATOM #include "dataconn_proxy.h" #endif /* Include glob.h last, because it may define "const" which breaks system headers on some platforms. */ #include typedef void (*sighandler_t)(int); #ifndef LINE_MAX # define LINE_MAX 2048 #endif #ifndef LOG_FTP # define LOG_FTP LOG_DAEMON /* Use generic facility. */ #endif #ifndef MAP_FAILED # define MAP_FAILED (void*)-1 #endif #if !HAVE_DECL_FCLOSE /* Some systems don't declare fclose in , so do it ourselves. */ extern int fclose __P ((FILE *)); #endif extern char *__progname; /* Exported to ftpcmd.h. */ struct sockaddr_storage data_dest; /* Data port. */ struct sockaddr_storage his_addr; /* Peer address. */ #ifdef PUMA6_ATOM static short his_addr_is_from_puma6_arm_proxy = 0; /* control connection coming from PUMA6 ARM proxy ? */ #endif int g_extended_passive_all = 0; /* EPSV ALL received ? */ int logging; /* Enable log to syslog. */ int type = TYPE_A; /* Default TYPE_A. */ int form = FORM_N; /* Default FORM_N. */ int debug; /* Enable debug mode if 1. */ int timeout = 900; /* Timeout after 15 minutes of inactivity. */ int maxtimeout = 7200; /* Don't allow idle time to be set beyond 2 hours. */ int pdata = -1; /* For passive mode. */ char *hostname = 0; /* Who we are. */ int usedefault = 1; /* For data transfers. */ char tmpline[7]; /* Temp buffer use in OOB. */ #if 1 /* FRITZBOX */ int max_clients = 0; /* restrict max clients. */ #endif #ifndef NO_RFC2640_SUPPORT int g_utf8 = 0; #endif #ifndef NO_FTPS_SUPPORT int g_PBSZ_Received = 0; #endif /* Requester credentials. */ struct credentials cred; static struct sockaddr_storage ctrl_addr; /* Control address. */ static struct sockaddr_storage data_source; /* Port address. */ static struct sockaddr_storage pasv_addr; /* Pasv address. */ static int data = -1; /* Port data connection socket. */ static FILE *g_data_fp =0 ; static jmp_buf urgcatch; static int stru = STRU_F; /* Avoid C keyword. */ static int stru_mode = MODE_S; /* Default STRU mode stru_mode = MODE_S. */ static int anon_only; /* Allow only anonymous login. */ static int no_version; /* Don't print version to client. */ static int daemon_mode; /* Start in daemon mode. */ static off_t file_size; static off_t byte_count; static sig_atomic_t transflag; /* Flag where in a middle of transfer. */ static const char *pid_file = PATH_FTPDPID; #if !defined (CMASK) || CMASK == 0 #undef CMASK // #define CMASK 027 #define CMASK 0 /* AVM */ #endif static int defumask = CMASK; /* Default umask value. */ static int login_attempts; /* Number of failed login attempts. */ static int askpasswd; /* Had user command, ask for passwd. */ static char curname[10]; /* Current USER name. */ static char ttyline[20]; /* Line to log in utmp. */ static int opt_use_sendfile = 1; #ifndef NO_FTPS_SUPPORT struct SecuredConnection *g_pControlConnectionSecurity = 0; int g_bInternetFTPSOnly = 0; int g_bSecureDataConnection = 0; struct SecuredConnection *g_pDataConnectionSecurity = 0; unsigned short g_OpenedIPv4Port = 0; static short g_old_key = 0; static struct SecuredConnection *SecureServerConnection(int in_fd, int out_fd); #endif #ifdef USE_IPV6 unsigned short g_OpenedIPv6Port = 0; #endif #if 1 /* FRITZBOX */ #define AVM_EVENT_ID_FTP_LOGIN_OK 580 #define AVM_EVENT_ID_FTP_LOGIN_FAILED 581 #define AVM_EVENT_ID_FTP_APP_LOGIN_OK 584 #define AVM_EVENT_ID_FTP_APP_LOGIN_FAILED 585 #endif /* AVM */ static int is_access_from_internet(void); static ssize_t control_write(const void *buf, size_t count); static char *tmpstring(size_t siz) { #define NTMPSTRING 16 static char *tmps[NTMPSTRING] = { 0, }; static int index = 0; char *p; if (tmps[index]) free(tmps[index]); p = tmps[index] = malloc(siz); index++; if (index == NTMPSTRING) index = 0; return p; } #ifdef FTPD_DEBUG void _Log(char *fmt, ...) { time_t t; struct tm *tm; t = time(0); tm = localtime(&t); if (!tm) return; FILE *fp = fopen("/dev/console", "a"); if (fp) { va_list ap; fprintf(fp, "ftpd(%d): %02u:%02u:%02u ", getpid(), tm->tm_hour, tm->tm_min, tm->tm_sec); va_start(ap, fmt); vfprintf(fp, fmt, ap); va_end(ap); fprintf(fp, "\n"); fclose(fp); } mode_t old = umask(S_IWOTH); // let the root group have write access fp = fopen("/var/tmp/ftpd.log", "a"); umask(old); if (fp) { va_list ap; fprintf(fp, "ftpd(%d): %02u:%02u:%02u ", getpid(), tm->tm_hour, tm->tm_min, tm->tm_sec); va_start(ap, fmt); vfprintf(fp, fmt, ap); va_end(ap); fprintf(fp, "\n"); fclose(fp); } } void _hex_dump(const char *hint, unsigned char *data, size_t siz) { FILE *fp = fopen("/dev/console", "a"); if (fp) { fprintf(fp, "%s:", hint); while(siz) { fprintf(fp, "%02x", *data++); siz--; } fprintf(fp, "\n"); fclose(fp); } } static char *sockaddr_storage_to_string(struct sockaddr_storage *addr) { static buf[128]; unsigned port = 0; char addr_str[INET6_ADDRSTRLEN]; char *p = 0; switch(addr->ss_family) { case AF_INET: port = ntohs(((struct sockaddr_in *)addr)->sin_port); p = inet_ntop(AF_INET, &(((struct sockaddr_in *)addr)->sin_addr), addr_str, sizeof(addr_str)); break; case AF_INET6: port = ntohs(((struct sockaddr_in6 *)addr)->sin6_port); p = inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)addr)->sin6_addr), addr_str, sizeof(addr_str)); break; default: p = "illegal family"; } if (!p) p = "inet_ntop failed"; snprintf(buf, sizeof(buf), "%s :%u", p, port); buf[sizeof(buf)-1] = '\0'; return buf; } #endif unsigned short sockaddr_port(struct sockaddr_storage *ss) { switch(ss->ss_family) { case AF_INET: return ntohs( ((struct sockaddr_in *)ss)->sin_port); case AF_INET6: return ntohs( ((struct sockaddr_in6 *)ss)->sin6_port); default: return 0; } } char *sockaddr_addr2string(struct sockaddr_storage *ss) { switch(ss->ss_family) { case AF_INET: return inet_ntoa(((struct sockaddr_in *)ss)->sin_addr); case AF_INET6: { static char buf[8*5]; unsigned char *a; a = (unsigned char *)&((struct sockaddr_in6 *)ss)->sin6_addr; snprintf(buf, sizeof(buf), "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15]); buf[sizeof(buf)-1] = '\0'; return buf; } default: return ""; } } static int GetMappedIPv4(struct in6_addr *ipv6, unsigned char *ipv4) { if (ipv6->s6_addr16[0]) return -1; if (ipv6->s6_addr16[1]) return -1; if (ipv6->s6_addr16[2]) return -1; if (ipv6->s6_addr16[3]) return -1; if (ipv6->s6_addr16[4]) return -1; if (ipv6->s6_addr16[5] != 0xffff) return -1; ipv4[0] = ipv6->s6_addr[12]; ipv4[1] = ipv6->s6_addr[13]; ipv4[2] = ipv6->s6_addr[14]; ipv4[3] = ipv6->s6_addr[15]; return 0; } static void ConvertMappedIPv4(struct sockaddr_storage *addr) { if (addr->ss_family == AF_INET6) { unsigned char ipv4[4]; if (0 == GetMappedIPv4(&((struct sockaddr_in6 *)addr)->sin6_addr, ipv4)) { memcpy(&((struct sockaddr_in *)addr)->sin_addr, ipv4, 4); addr->ss_family = AF_INET; } } } static void GetFamilyAndIPv4(struct sockaddr_storage *ss, int *pfam, unsigned char *ipv4, unsigned short *pport) { *pfam = ss->ss_family; if (ss->ss_family == AF_INET) { memcpy(ipv4, &((struct sockaddr_in *)ss)->sin_addr, 4); if (pport) *pport = ((struct sockaddr_in *)ss)->sin_port; } else if (ss->ss_family == AF_INET6) { if (0 == GetMappedIPv4(&((struct sockaddr_in6 *)ss)->sin6_addr, ipv4)) { *pfam = AF_INET; if (pport) *pport = ((struct sockaddr_in6 *)ss)->sin6_port; } } } static int GetAddressAndPortStrings(struct sockaddr_storage *ss, char *addr_str, size_t addr_size, char *port_str, size_t port_size) { unsigned short port; int fam; unsigned char ipv4[4]; GetFamilyAndIPv4(ss, &fam, ipv4, &port); if (fam == AF_INET) { snprintf(addr_str, addr_size, "%u.%u.%u.%u", ipv4[0], ipv4[1], ipv4[2], ipv4[3]); } else if (fam == AF_INET6) { const char *p = inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)ss)->sin6_addr), addr_str, addr_size); if (p != addr_str) return -1; port = ((struct sockaddr_in6 *)ss)->sin6_port; } else return -1; snprintf(port_str, port_size, "%u", ntohs(port)); return 0; } int is_same_addr(struct sockaddr_storage *ss1, struct sockaddr_storage *ss2) { if (ss1->ss_family != ss2->ss_family) { if (ss1->ss_family == AF_INET6 && ss2->ss_family == AF_INET) { struct sockaddr_storage *h = ss1; ss1 = ss2; ss2 = h; } if (ss1->ss_family == AF_INET && ss2->ss_family == AF_INET6) { unsigned char ipv4[4]; // network order if (GetMappedIPv4(&((struct sockaddr_in6 *)ss2)->sin6_addr, ipv4)) return 0; if (memcmp( &((struct sockaddr_in *)ss1)->sin_addr, &ipv4, 4)) { Log(("is_same_addr: NO AF_INET sin_addr differ to mapped ipv4")); return 0; } return 1; } Log(("is_same_addr: NO fam1=%d fam2=%d", ss1->ss_family, ss2->ss_family)); return 0; } switch(ss1->ss_family) { case AF_INET: if (memcmp( &((struct sockaddr_in *)ss1)->sin_addr, &((struct sockaddr_in *)ss2)->sin_addr, sizeof(((struct sockaddr_in *)ss1)->sin_addr))) { Log(("is_same_addr: NO AF_INET sin_addr differ")); return 0; } break; case AF_INET6: if (memcmp( &((struct sockaddr_in6 *)ss1)->sin6_addr, &((struct sockaddr_in6 *)ss2)->sin6_addr, sizeof(((struct sockaddr_in6 *)ss1)->sin6_addr))) { Log(("is_same_addr: NO AF_INET6 sin6_addr differ")); hex_dump("addr1", &((struct sockaddr_in6 *)ss1)->sin6_addr, sizeof(((struct sockaddr_in6 *)ss1)->sin6_addr)); hex_dump("addr2", &((struct sockaddr_in6 *)ss2)->sin6_addr, sizeof(((struct sockaddr_in6 *)ss2)->sin6_addr)); return 0; } break; default: Log(("is_same_addr: NO unknown family")); return 0; } return 1; } static char * off_to_str (off_t off) { char *buf = tmpstring(80); if (!buf) return ""; if (sizeof (off) > sizeof (long)) sprintf (buf, "%qd", off); else if (sizeof (off) == sizeof (long)) sprintf (buf, "%ld", off); else sprintf (buf, "%d", off); return buf; } static unsigned count_running_processes(char *argv0) { DIR *d = opendir("/proc"); if (!d) return 0; unsigned n = 0; struct dirent *de; while(0 != (de = readdir(d))) { if (isdigit(de->d_name[0])) { char buf[256]; snprintf(buf, sizeof(buf), "/proc/%s/cmdline", de->d_name); buf[sizeof(buf)-1] = '\0'; FILE *fp = fopen(buf, "r"); if (fp) { if (buf == fgets(buf, sizeof(buf), fp)) { if (!strcmp(argv0, buf)) n++; } fclose(fp); } } } closedir(d); return n; } #ifdef PUMA6_ARM static int get_puma6_atom_addr_of_family(sa_family_t family, struct sockaddr_storage *addr) { int ok = 0; FILE *fp = 0; switch(family) { case AF_INET: fp = popen("avmipc_state ATOM_IPV4_LAN", "r"); break; #if 0 { char *s = getenv("AVMIPC_REMOTE_IP"); if (s && s[0]) { if (0 == string_to_sockaddr_storage(s, addr)) ok = 1; } } return (0 == ok); #endif case AF_INET6: fp = popen("avmipc_state ATOM_IPV6_GUA ATOM_IPV6_ULA", "r"); break; } if (!fp) return -1; char buf[80]; while(!ok && buf == fgets(buf, sizeof(buf)-1, fp)) { buf[sizeof(buf)-1] = '\0'; char *p = &buf[strlen(buf)-1]; while(p >= buf && (*p == '\r' || *p == '\n')) { *p-- = '\0'; } if (0 == string_to_sockaddr_storage(buf, addr)) { ok = 1; } } pclose(fp); return (0 == ok); } #endif /* ------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------ */ #define UNAUTH_CLIENTS_FILE "/var/tmp/ftpd_unauth_clients.dat" static int open_unauth_clients_file(void) { int fd; fd = open(UNAUTH_CLIENTS_FILE, O_RDWR | O_CREAT, 0666); if (fd < 0) return -1; if (0 != flock(fd, LOCK_EX)) { close(fd); return -1; } return fd; } static void close_unauth_clients_file(int fd) { (void)flock(fd, LOCK_UN); close(fd); } static int is_tail(const char *str, const char *tail) { const char *p; size_t str_len = strlen(str); size_t tail_len = strlen(tail); if (str_len < tail_len) return 0; p = str + str_len - tail_len; return 0 == strcmp(p, tail); } static int is_running_ftpd(unsigned pid) { char buf[256]; FILE *fp; int ret = 0; snprintf(buf, sizeof(buf), "/proc/%u/cmdline", pid); fp = fopen(buf, "r"); if (fp) { if (buf == fgets(buf, sizeof(buf), fp)) { if (is_tail(buf, "/ftpd")) { ret = 1; } } fclose(fp); } return ret; } static unsigned count_unauth_clients(int fd, pid_t del_pid, pid_t search_pid, /*OUT*/int *pfound, /*OUT*/off_t *pfree_pos) { unsigned n = 0; while(1) { unsigned pid; char buf[10+1]; ssize_t nread; off_t pos = lseek(fd, 0, SEEK_CUR); if (pos == (off_t)-1) break; do { nread = read(fd, buf, sizeof(buf)-1); } while (0 == nread && EINTR == errno); if (nread != sizeof(buf)-1) break; buf[sizeof(buf)-1] = '\0'; if (pfree_pos && (off_t)-1 == *pfree_pos && buf[0] == '#') *pfree_pos = pos; if (1 == sscanf(buf, "%u", &pid)) { if (-1 != search_pid && pid == (unsigned)search_pid) { // dont count or del searched pid if (pfound) *pfound = 1; } else { /* check if its a still running ftpd * may have been killed and pid reused for something else */ if ((-1 != del_pid && pid == (unsigned)del_pid) || !is_running_ftpd(pid)) { // remove ssize_t nwritten; if (pfree_pos && (off_t)-1 == *pfree_pos) *pfree_pos = pos; if ((off_t)-1 == lseek(fd, pos, SEEK_SET)) break; memset(buf, '#', sizeof(buf)-1); do { nwritten = write(fd, buf, sizeof(buf)-1); } while (-1 == nwritten && EINTR == errno); if (sizeof(buf)-1 != nwritten) break; } else { n++; } } } } return n; } /* * Add current process to the shared list of ftpd processes with * unauthorized client connection. * * return * 0: ok * <0: failed or max_allowed ftpd processes with unauthorized client connection * already running */ static int add_unauthorized_client(unsigned max_allowed) { int ret = -1; unsigned n; int fd = open_unauth_clients_file(); off_t free_pos = (off_t)-1; int found = 0; if (fd < 0) return -1; n = count_unauth_clients(fd, -1, getpid(), &found, &free_pos); if (n >= max_allowed) { ret = -1; } else { if (!found) { ssize_t nwritten; char buf[10+1]; if ((off_t)-1 != free_pos) { if ((off_t)-1 == lseek(fd, free_pos, SEEK_SET)) goto end; } snprintf(buf, sizeof(buf), "%010u", (unsigned)getpid()); do { nwritten = write(fd, buf, sizeof(buf)-1); } while (-1 == nwritten && EINTR == errno); } ret = 0; } end: close_unauth_clients_file(fd); return ret; } /* * Remove current process from the list of ftpd processes with * unauthorized client connection. */ static void remove_unauthorized_client(void) { int fd = open_unauth_clients_file(); if (fd < 0) return; (void)count_unauth_clients(fd, getpid(), -1, 0, 0); close_unauth_clients_file(fd); } /* ------------------------------------------------------------------------ */ /* * Timeout intervals for retrying connections * to hosts that don't accept PORT cmds. This * is a kludge, but given the problems with TCP... */ #define SWAITMAX 90 /* wait at most 90 seconds */ #define SWAITINT 5 /* interval between retries */ static int swaitmax = SWAITMAX; static int swaitint = SWAITINT; #ifdef HAVE_SETPROCTITLE char proctitle[LINE_MAX]; /* initial part of title */ #endif /* SETPROCTITLE */ #define LOGCMD(cmd, file) \ if (logging > 1) \ syslog(LOG_INFO,"%s %s%s", cmd, \ *(file) == '/' ? "" : curdir(), file); #define LOGCMD2(cmd, file1, file2) \ if (logging > 1) \ syslog(LOG_INFO,"%s %s%s %s%s", cmd, \ *(file1) == '/' ? "" : curdir(), file1, \ *(file2) == '/' ? "" : curdir(), file2); #define LOGBYTES(cmd, file, cnt) \ if (logging > 1) { \ if (cnt == (off_t)-1) \ syslog(LOG_INFO,"%s %s%s", cmd, \ *(file) == '/' ? "" : curdir(), file);\ else \ syslog(LOG_INFO, "%s %s%s = %s bytes", \ cmd, (*(file) == '/') ? "" : curdir(), file, \ off_to_str (cnt));\ } static void ack __P ((const char *)); #ifdef HAVE_LIBWRAP static int check_host __P ((struct sockaddr *sa)); #endif static void complete_login __P ((struct credentials *)); static char *curdir __P ((void)); static int dataconn __P ((const char *, off_t, const char *)); static void dolog __P ((struct sockaddr_storage *, struct credentials *)); static void end_login __P ((struct credentials *)); static FILE *getdatasock __P ((const char *)); static char *gunique __P ((const char *, int *pcount)); static void lostconn __P ((int)); static void reconfig __P ((int)); static void myoob __P ((int)); static int receive_data __P ((FILE *)); static void send_data __P ((FILE *, off_t)); #if 0 // FRITZBOX buildin_ls static void send_data_ls_filter __P ((FILE *, FILE *, off_t, int, int)); #endif static void send_data_buildin_ls __P ((FILE *instr)); static void sigquit __P ((int)); static void usage __P ((int)); #if 1 /* FRITZBOX */ static const char *short_options = "Aa:Ddlp:qt:T:u:m:h:Usr"; #else static const char *short_options = "Aa:Ddlp:qt:T:u:"; #endif static struct option long_options[] = { { "anonymous-only", no_argument, 0, 'A' }, { "auth", required_argument, 0, 'a' }, { "daemon", no_argument, 0, 'D' }, { "debug", no_argument, 0, 'd' }, { "help", no_argument, 0, '&' }, { "logging", no_argument, 0, 'l' }, { "pidfile", required_argument, 0, 'p' }, { "no-version", no_argument, 0, 'q' }, { "timeout", required_argument, 0, 't' }, { "max-timeout", required_argument, 0, 'T' }, { "umask", required_argument, 0, 'u' }, { "version", no_argument, 0, 'V' }, #if 1 /* FRITZBOX */ { "max-clients", required_argument, 0, 'm' }, { "hostname", required_argument, 0, 'h' }, { "Users", no_argument, 0, 'U' }, { "sendfile", no_argument, 0, 's' }, { "readonly", no_argument, 0, 'r' }, #endif { 0, 0, 0, 0 } }; static void usage (int err) { if (err != 0) { fprintf (stderr, "Usage: %s [OPTION] ...\n", __progname); fprintf (stderr, "Try `%s --help' for more information.\n", __progname); } else { fprintf (stdout, "Usage: %s [OPTION] ...\n", __progname); puts ("Internet File Transfer Protocol server.\n\n\ -A, --anonymous-only Server configure for anonymous service only\n\ -D, --daemon Start the ftpd standalone\n\ -d, --debug Debug mode\n\ -l, --logging Increase verbosity of syslog messages\n\ -p, --pidfile=[PIDFILE] Change default location of pidfile\n\ -q, --no-version Do not display version in banner\n\ -t, --timeout=[TIMEOUT] Set default idle timeout\n\ -T, --max-timeout Reset maximum value of timeout allowed\n\ -u, --umask Set default umask(base 8)\n\ --help Print this message\n\ -V, --version Print version\n\ -a, --auth=[AUTH] Use AUTH for authentication, it can be:\n\ default passwd authentication." ); #ifdef WITH_PAM puts ("\ pam using pam 'ftp' module." ); #endif #ifdef WITH_KERBEROS puts ("\ kerberos" ); #endif #ifdef WITH_KERBEROS5 puts ("\ kderberos5" ); #endif #ifdef WITH_OPIE puts ("\ opie" ); #endif #if 1 /* FRITZBOX */ fprintf(stdout,"\ -m, --max-clients Restrict max. number of connected clients\n\ -h, --hostname Set hostname\n\ -s, --sendfile DON'T use sendfile() but read/write loop\n"); #endif fprintf (stdout, "\nSubmit bug reports to %s.\n", PACKAGE_BUGREPORT); } exit (err); } int main(int argc, char *argv[], char **envp) { extern char *localhost __P ((void)); int option; #ifndef NO_FTPS_SUPPORT SSL_load_error_strings(); SSL_library_init(); #endif (void) signal (SIGTERM, SIG_DFL); setpriority(PRIO_PROCESS, 0, 0); /* set normal prio */ #ifndef HAVE___PROGNAME __progname = argv[0]; #endif #ifdef HAVE_TZSET tzset(); /* In case no timezone database in ~ftp. */ #endif #ifdef HAVE_INITSETPROCTITLE /* Save start and extent of argv for setproctitle. */ initsetproctitle (argc, argv, envp); #endif /* HAVE_INITSETPROCTITLE */ while ((option = getopt_long (argc, argv, short_options, long_options, NULL)) != EOF) { switch (option) { case 'A': /* Anonymous ftp only. */ anon_only = 1; break; case 'a': /* Authentification method. */ if (strcasecmp (optarg, "default") == 0) cred.auth_type = AUTH_TYPE_PASSWD; #ifdef WITH_PAM else if (strcasecmp (optarg, "pam") == 0) cred.auth_type = AUTH_TYPE_PAM; #endif #ifdef WITH_KERBEROS else if (stracasecmp (optarg, "kerberos") == 0) cred.auth_type = AUTH_TYPE_KERBEROS; #endif #ifdef WITH_KERBEROS5 else if (stracasecmp (optarg, "kerberos5") == 0) cred.auth_type = AUTH_TYPE_KERBEROS5; #endif #ifdef WITH_OPIE else if (stracasecmp (optarg, "opie") == 0) cred.auth_type = AUTH_TYPE_OPIE; #endif break; case 'D': /* Run ftpd as daemon. */ daemon_mode = 1; break; case 'd': /* Enable debug mode. */ debug = 1; break; case 'l': /* Increase logging level. */ logging++; /* > 1 == Extra logging. */ break; case 'p': /* Override pid file */ pid_file = optarg; break; case 'q': /* Don't include version number in banner. */ no_version = 1; break; case 't': /* Set default timeout value. */ timeout = atoi (optarg); if (maxtimeout < timeout) maxtimeout = timeout; break; case 'T': /* Maximum timeout allowed. */ maxtimeout = atoi (optarg); if (timeout > maxtimeout) timeout = maxtimeout; break; case 'u': /* Set umask. */ { long val = 0; val = strtol (optarg, &optarg, 8); if (*optarg != '\0' || val < 0) fprintf (stderr, "%s: bad value for -u", argv[0]); else defumask = val; break; } case '&': /* Usage. */ usage (0); /* Not reached. */ case 'V': /* Version. */ printf ("ftpd (%s) %s\n", PACKAGE_NAME, PACKAGE_VERSION); exit (0); #if 1 /* FRITZBOX */ case 'm': /* Max Clients */ max_clients = atoi(optarg); break; case 'h': /* hostname */ hostname = optarg; break; case 's': /* don't use_sendfile */ opt_use_sendfile = 0; break; #endif case '?': default: usage (1); /* Not reached. */ } } /* Bail out, wrong usage */ argc -= optind; if (argc != 0) usage (1); /* LOG_NDELAY sets up the logging connection immediately, necessary for anonymous ftp's that chroot and can't do it later. */ openlog ("ftpd", LOG_PID | LOG_NDELAY, LOG_FTP); (void) freopen (PATH_DEVNULL, "w", stderr); /* If not running via inetd, we detach and dup(fd, 0), dup(fd, 1) the fd = accept(). tcpd is check if compile with the support */ if (daemon_mode) { if (server_mode (pid_file, &his_addr) < 0) exit (1); } else { if (0 != max_clients) { int ret; char *s = 0; ret = add_unauthorized_client(2/* max allowed */); if (ret < 0) { s = "Unauthorized client limit reached. Try again later."; } else { unsigned n; n = count_running_processes(argv[0]); if (n > max_clients) { s = "Client limit reached. Try again later."; } } if (s) { /* Too many clients */ reply(421, s); remove_unauthorized_client(); exit(1); } } socklen_t addrlen = sizeof(his_addr); if (getpeername (STDIN_FILENO, (struct sockaddr *)&his_addr, &addrlen) < 0) { syslog (LOG_ERR, "getpeername (%s): %m", __progname); remove_unauthorized_client(); exit (1); } ConvertMappedIPv4(&his_addr); } (void) signal (SIGUSR1, reconfig); (void) signal (SIGHUP, sigquit); (void) signal (SIGINT, sigquit); (void) signal (SIGQUIT, sigquit); (void) signal (SIGTERM, sigquit); (void) signal (SIGPIPE, lostconn); (void) signal (SIGCHLD, SIG_IGN); if (signal (SIGURG, myoob) == SIG_ERR) syslog (LOG_ERR, "signal: %m"); /* Get info on the ctrl connection. */ { int addrlen = sizeof (ctrl_addr); if (getsockname (STDIN_FILENO, (struct sockaddr *)&ctrl_addr, &addrlen) < 0) { syslog (LOG_ERR, "getsockname (%s): %m", __progname); remove_unauthorized_client(); exit (1); } ConvertMappedIPv4(&ctrl_addr); } #if defined (IP_TOS) && defined (IPTOS_LOWDELAY) && defined (IPPROTO_IP) /* To minimize delays for interactive traffic. */ { int tos = IPTOS_LOWDELAY; if (setsockopt (STDIN_FILENO, IPPROTO_IP, IP_TOS, (char *)&tos, sizeof(int)) < 0) syslog (LOG_WARNING, "setsockopt (IP_TOS): %m"); } #endif #ifdef SO_OOBINLINE /* Try to handle urgent data inline. */ { int on = 1; if (setsockopt (STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE, (char *)&on, sizeof (on)) < 0) syslog (LOG_ERR, "setsockopt: %m"); } #endif #ifdef SO_KEEPALIVE /* Set keepalives on the socket to detect dropped connections. */ { int keepalive = 1; if (setsockopt (STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, (char *)&keepalive, sizeof (keepalive)) < 0) syslog (LOG_WARNING, "setsockopt (SO_KEEPALIVE): %m"); } #endif #ifdef F_SETOWN if (fcntl (STDIN_FILENO, F_SETOWN, getpid ()) == -1) syslog (LOG_ERR, "fcntl F_SETOWN: %m"); #endif dolog (&his_addr, &cred); #ifndef NO_FTPS_SUPPORT g_bInternetFTPSOnly = acl_force_secured_internet_access(); #endif #ifdef PUMA6_ARM if (!is_access_from_internet()) { // On PUMA6 ARM the ftpd is just a proxy/gateway to the ATOM is accessed from the homenetwork // We could do that if accessed from the internet also, but we would have to create some // NAT/Firewall rules for the ATOM. // we are a proxy for the control connection struct sockaddr_storage atom_ftpd; #if 0 // OLD TEST CODE memset(&atom_ftpd, 0, sizeof(atom_ftpd)); struct sockaddr_in *sin = (struct sockaddr_in *)&atom_ftpd; sin->sin_family = AF_INET; sin->sin_port = htons(21); inet_aton("169.254.1.2", &sin->sin_addr); #endif if (get_puma6_atom_addr_of_family(his_addr.ss_family, &atom_ftpd)) { remove_unauthorized_client(); exit(-1); } switch(atom_ftpd.ss_family) { case AF_INET: ((struct sockaddr_in *)&atom_ftpd)->sin_port = htons(21); break; case AF_INET6: default: ((struct sockaddr_in6 *)&atom_ftpd)->sin6_port = htons(21); } Log(("do_proxy atom_ftpd=%s", sockaddr_storage_to_string(&atom_ftpd))); Log(("do_proxy his_addr=%s", sockaddr_storage_to_string(&his_addr))); int ret = do_proxy(STDIN_FILENO, &atom_ftpd, &his_addr); Log(("do_proxy ret=%d", ret)); remove_unauthorized_client(); exit(0); } #endif /* Deal with login disable. */ if (display_file (PATH_NOLOGIN, 530) == 0) { reply (530, "System not available."); remove_unauthorized_client(); exit (0); } /* Display a Welcome message if exists, N.B. reply(220,) must follow. */ (void) display_file (PATH_FTPWELCOME, 220); #if 1 /* FRITZBOX */ if (!hostname) { #endif hostname = localhost (); if (!hostname) perror_reply (550, "Local resource failure: malloc"); #if 1 /* FRITZBOX */ } #endif /* Tell them we're ready to roll. */ if (!no_version) reply (220, "%s FTP server (%s %s) ready.", hostname, PACKAGE_NAME, PACKAGE_VERSION); else reply (220, "%s FTP server ready.", hostname); /* Set the jump, if we have an error parsing, come here and start fresh. */ (void) setjmp (errcatch); /* Roll. */ for (;;) (void) yyparse (); /* NOTREACHED */ } static char * curdir (void) { static char *path = 0; if (path) free (path); path = xgetcwd (); if (!path) return (char *)""; if (path[1] != '\0') /* special case for root dir. */ { char *tmp = realloc (path, strlen (path) + 2); /* '/' + '\0' */ if (!tmp) { free(path); return (char *)""; } strcat(tmp, "/"); path = tmp; } /* For guest account, skip / since it's chrooted */ return (cred.guest ? path+1 : path); } static void sigquit (int signo) { syslog (LOG_ERR, "got signal %s", strsignal (signo)); dologout (-1); } static void reconfig(int signo) { acl_reconfig(); } static void lostconn (int signo) { (void)signo; if (debug) syslog (LOG_DEBUG, "lost connection"); dologout (-1); } /* Helper function. */ char * sgetsave (const char *s) { char *string; size_t len; if (s == NULL) s = ""; len = strlen (s) + 1; string = malloc (len); if (string == NULL) { perror_reply (421, "Local resource failure: malloc"); dologout (1); /* NOTREACHED */ } /* (void) strcpy (string, s); */ memcpy (string, s, len); return string; } #if 1 /* FRITZBOX */ static void LogLogin(uid_t uid) { char *cmd; size_t len; char peer_addr[48], peer_port[8]; if (0 != GetAddressAndPortStrings(&his_addr, peer_addr, sizeof(peer_addr), peer_port, sizeof(peer_port))) { strcpy(peer_addr, "?"); } len = 80 + strlen(peer_addr); cmd = (char *)malloc(len); if (!cmd) return; // assert (0 == strchr(peer_addr, '\'')); snprintf(cmd, len, "msgsend ctlmgr login_linuxuid ftp %u '%s'", uid, peer_addr); (void)system(cmd); free(cmd); } /* dont allow any ' in '%s'-arg of system() */ static char *make_safe_arg_for_system(const char *arg) { char *s = (char*)arg; /* free() not allowed on return for this case! */ if (strchr(s, '\'')) { s = strdup(arg); if (s) { char *p; for (p = s; *p; p++) { if (*p == '\'') *p = '.'; } } } return s; } static void AccessEventLog(unsigned event_id, const char *name) { char *safe_name; char peer_addr[48], peer_port[8]; char *cmd; size_t len; if ('@' == name[0]) return; if (0 != GetAddressAndPortStrings(&his_addr, peer_addr, sizeof(peer_addr), peer_port, sizeof(peer_port))) { return; } // assert (0 == strchr(peer_addr, '\'')); safe_name = make_safe_arg_for_system(name); if (!safe_name) return; len = 80 + strlen(safe_name) + strlen(peer_addr); cmd = (char *)malloc(len); if (cmd) { snprintf(cmd, len, "/sbin/eventadd %u '%s' '%s' >/dev/null 2>/dev/null", event_id, safe_name, peer_addr); (void)system(cmd); free(cmd); } if (safe_name != name) free(safe_name); } static char *GetAppDisplayName(const char *username) { char buf[512]; char *app_name = 0; FILE *fp = fopen("/var/tmp/apps.map", "r"); if (fp) { while (!app_name && (buf == fgets(buf, sizeof(buf), fp))) { char *p; while((p = strrchr(buf, '\n')) || (p = strrchr(buf, '\r'))) { *p = '\0'; } p = strchr(buf, '='); if (p) { *p = 0; if (0 == strcmp(buf, username)) { app_name = strdup(p + 1); } } } fclose(fp); } return app_name; } #endif /* FRITZBOX */ static void complete_login (struct credentials *pcred) { if (setegid ((gid_t)pcred->gid) < 0) { reply (550, "Can't set gid."); return; } #ifdef HAVE_INITGROUPS (void) initgroups (pcred->name, pcred->gid); #endif /* open wtmp before chroot */ (void)snprintf (ttyline, sizeof (ttyline), "ftp%d", getpid ()); logwtmp_keep_open (ttyline, pcred->name, pcred->remotehost); Log(("calling acl_set_user_context=%p", &acl_set_user_context)); char *acl_name; if (pcred->is_anonymous) acl_name = "ftpuser"; else acl_name = pcred->display_name; if (acl_set_user_context(acl_name, pcred->access_from_internet)) { Log(("acl_set_user_context failed name=%s from_internet=%d", acl_name, pcred->access_from_internet)); reply (550, "Can't change to home directory."); goto bad; } Log(("acl_set_user_context ok")); char *root = acl_get_user_common_real_dir(); int write_access; if (root && acl_is_allowed_path(root, &write_access)) { free(pcred->rootdir); pcred->rootdir = sgetsave(root); virtual_set_root(root); } else { if (pcred->access_from_internet) { reply (550, "Access to home directory from internet not allowed."); } else { reply (550, "Access to home directory not allowed."); } goto bad; } if (pcred->guest) { /* We MUST do a chdir () after the chroot. Otherwise the old current directory will be accessible as "." outside the new root! */ if (chroot (pcred->rootdir) < 0 || chdir (pcred->homedir) < 0) { reply (550, "Can't set guest privileges."); goto bad; } } else if (pcred->dochroot) { if (chroot (pcred->rootdir) < 0 || chdir(pcred->homedir) < 0) { reply (550, "Can't change root."); goto bad; } setenv ("HOME", pcred->homedir, 1); } else if (chdir (pcred->rootdir) < 0) { #if 1 /* FRITZBOX */ reply (550, "Can't change to home directory."); goto bad; #else if (chdir ("/") < 0) { reply (530, "User %s: can't change directory to %s.", pcred->display_name, pcred->homedir); goto bad; } else lreply (230, "No directory! Logging in with home=/"); #endif } #if 1 /* FRITZBOX */ char *app_name = GetAppDisplayName(acl_name); if (app_name) { AccessEventLog(AVM_EVENT_ID_FTP_APP_LOGIN_OK, app_name); free(app_name); } else { AccessEventLog(AVM_EVENT_ID_FTP_LOGIN_OK, acl_name); } LogLogin(pcred->uid); remove_unauthorized_client(); #endif Log(("%s %d seteuid %u", __FUNCTION__, __LINE__, pcred->uid)); if (seteuid ((uid_t)pcred->uid) < 0) { reply (550, "Can't set uid."); goto bad; } /* Display a login message, if it exists. N.B. reply(230,) must follow the message. */ (void) display_file (PATH_FTPLOGINMESG, 230); if (pcred->guest) { reply (230, "Guest login ok, access restrictions apply."); #ifdef HAVE_SETPROCTITLE snprintf (proctitle, sizeof (proctitle), "%s: anonymous", pcred->remotehost); setproctitle ("%s",proctitle); #endif /* HAVE_SETPROCTITLE */ if (logging) syslog (LOG_INFO, "ANONYMOUS FTP LOGIN FROM %s", pcred->remotehost); } else { reply (230, "User %s logged in.", pcred->display_name); #ifdef HAVE_SETPROCTITLE snprintf (proctitle, sizeof (proctitle), "%s: %s", pcred->remotehost, pcred->name); setproctitle ("%s",proctitle); #endif /* HAVE_SETPROCTITLE */ if (logging) syslog (LOG_INFO, "FTP LOGIN FROM %s as %s", pcred->remotehost, pcred->name); } (void) umask(defumask); return; bad: /* Forget all about it... */ end_login (pcred); } /* ---------- taken from kdsld_misc_ioctl.h --------- */ #pragma pack(1) struct kdsld_vpn_tcpconn_route_check { uint32_t saddr; // network order uint32_t daddr; // network order uint16_t sport; // network order uint16_t dport; // network order }; #pragma pack() #define KDSLD_VPN_TCPCONN_ROUTE_CHECK _IOW('D', 0x05, struct kdsld_vpn_tcpconn_route_check) /* -------------------------------------------------- */ static int is_tcpconn_routed_via_vpn(unsigned src_addr, unsigned src_port, unsigned dest_addr, unsigned dest_port) { int fd = open("/dev/kdsld_misc", O_RDWR | O_NONBLOCK); if (fd < 0) return 0; struct kdsld_vpn_tcpconn_route_check buf; buf.saddr = htonl(src_addr); buf.sport = htons(src_port); buf.daddr = htonl(dest_addr); buf.dport = htons(dest_port); int ret = ioctl(fd, KDSLD_VPN_TCPCONN_ROUTE_CHECK, (void *)&buf); close(fd); if (ret < 0 ) return 0; // ioctl error return ret ? 1 : 0; } static void reset_login_pause(void) { unlink("/var/ftpd_login_fails"); } static void login_pause(void) { unsigned fail_counter = 0; FILE *fp = fopen("/var/ftpd_login_fails", "r"); if (fp) { if (1 != fscanf(fp, "%u", &fail_counter)) fail_counter = 0; fclose(fp); } if (fail_counter) { if (fail_counter > 6) sleep(6 * 2); else sleep(fail_counter * 2); } fail_counter++; fp = fopen("/var/ftpd_login_fails", "w"); if (fp) { fprintf(fp, "%u", fail_counter); fclose(fp); } } static void _pass (const char *passwd, int skip_auth) { #ifndef NO_FTPS_SUPPORT if (cred.access_from_internet && g_bInternetFTPSOnly && !g_pControlConnectionSecurity) { reply (530, "Must use AUTH TLS"); return; } #endif if (cred.logged_in || askpasswd == 0) { reply(503, "Login with USER first."); return; } askpasswd = 0; if (!skip_auth) { #if 1 /* FRITZBOX */ if (!cred.name) { // have to call auth_user() now // note that cred.display_name can't be used as a parameter to auth_user(). // it may be freed and set anew before the parameter is then accessed again. // --> use a temporary copy named display_name char *display_name = sgetsave(cred.display_name); int bad = auth_user(display_name, display_name, &cred); free(display_name); if (bad) { char *app_name = GetAppDisplayName(cred.display_name); if (app_name) { AccessEventLog(AVM_EVENT_ID_FTP_APP_LOGIN_FAILED, app_name); free(app_name); } else { AccessEventLog(AVM_EVENT_ID_FTP_LOGIN_FAILED, cred.display_name); } login_pause(); /* SECURITY: dont transport cred.message to client to avoid username guessing */ if (cred.message) { free(cred.message); cred.message = 0; } reply (530, "Login incorrect."); return; } } #endif if (!cred.guest) /* "ftp" is the only account allowed no password. */ { /* Try to authenticate the user. Failed if != 0. */ if (auth_pass (passwd, &cred) != 0) { char *app_name = GetAppDisplayName(cred.display_name); if (app_name) { AccessEventLog(AVM_EVENT_ID_FTP_APP_LOGIN_FAILED, app_name); free(app_name); } else { AccessEventLog(AVM_EVENT_ID_FTP_LOGIN_FAILED, cred.display_name); } login_pause(); /* Any particular reasons. */ /* SECURITY: dont transport cred.message to client to avoid username guessing */ if (cred.message) { free(cred.message); cred.message = 0; } reply (530, "Login incorrect."); if (logging) syslog (LOG_NOTICE, "FTP LOGIN FAILED FROM %s, %s", cred.remotehost, curname); if (login_attempts++ >= 5) { syslog(LOG_NOTICE, "repeated login failures from %s", cred.remotehost); remove_unauthorized_client(); exit(0); } return; } } } // skip_auth reset_login_pause(); cred.logged_in = 1; /* Everything seems to be allright. */ complete_login (&cred); login_attempts = 0; /* This time successful. */ } #ifdef USE_IPV6 static int get_wan_ipv6_address(unsigned char *pipv6) { FILE *fp = popen("showdsldstat", "r"); if (!fp) return -1; char *trigger = "0: IPv6: address "; int trigger_len = strlen(trigger); char buf[128]; int ret = -1; while(buf == fgets(buf, sizeof(buf), fp)) { if (strlen(buf) < trigger_len || memcmp(buf, trigger, trigger_len)) continue; char *addr_str = buf + trigger_len; char *s = strchr(addr_str, '/'); if (s) { *s = '\0'; struct in6_addr ipv6; if (1 == inet_pton(AF_INET6, addr_str, (void *)&ipv6)) { memcpy(pipv6, ipv6.s6_addr, 16); ret = 0; } } break; } pclose(fp); return ret; } #endif static int run_program(const char *program, char *av[]) { int exitcode = -1; pid_t child; sighandler_t old_handler = signal(SIGCHLD, SIG_DFL); switch((child = fork())) { case 0: /* child */ { int i; for (i = 0; i < FD_SETSIZE; i++) close(i); execv(program, av); } exit(-1); case -1: signal(SIGCHLD, old_handler); return exitcode; default: { int status; pid_t ret; ret = waitpid(child, &status, 0); (void)signal(SIGCHLD, old_handler); if (ret == child) { if(WIFEXITED(status)) exitcode = WEXITSTATUS(status); } } return exitcode; } return exitcode; } static int is_access_from_internet(void) { unsigned dev; unsigned inet_dev; int ret; unsigned char ipv4[4]; /* network order */ unsigned short peer_port; /* network order */ int family; short access_from_internet = 0; #ifdef PUMA6_ATOM // the code below using the route_tool_... does not work correctly on PUMA6 ATOM // in the meantime we assume the PUMA6 ATOM ftpd can only be reached from the homenetwork // (even using IPV6). return 0; #if 0 // we known that to PUMA6 ARM proxy only forwards connections from the homenetwork to us if (his_addr_is_from_puma6_arm_proxy) return 0; #endif #endif if (0 == access("/bin/tcppeerlocation", X_OK)) { char own_addr[48], own_port[8]; char peer_addr[48], peer_port2[8]; int exitcode; if (0 != GetAddressAndPortStrings(&ctrl_addr, own_addr, sizeof(own_addr), own_port, sizeof(own_port))) return 1; if (0 != GetAddressAndPortStrings(&his_addr, peer_addr, sizeof(peer_addr), peer_port2, sizeof(peer_port2))) return 1; char *av[6]; av[0] = "/bin/tcppeerlocation"; av[1] = own_addr; av[2] = own_port; av[3] = peer_addr; av[4] = peer_port2; av[5] = 0; exitcode = run_program("/bin/tcppeerlocation", av); return (1 == exitcode) ? 0 : 1; } GetFamilyAndIPv4(&his_addr, &family, ipv4, &peer_port); #ifdef USE_IPV6 if (family == AF_INET6) { // check routing to DNS root server 2001:503:BA3E::2:30 unsigned char root_dns[16] = { 0x20,0x01, 0x05,0x03, 0xba,0x3e, 0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x02, 0x00,0x30 }; unsigned char inet_gateway[16]; unsigned char inet_dst[16]; unsigned char gateway[16]; unsigned char dst[16]; ret = route_tool_get_ipv6_dev(&root_dns[0], &inet_dev, &inet_gateway[0], &inet_dst[0]); Log(("route_tool_get_dev IPv6 DNS-Root ret=%d inet_dev=%d inet_gateway=%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", ret, inet_dev, inet_gateway[0],inet_gateway[1],inet_gateway[2],inet_gateway[3],inet_gateway[4],inet_gateway[5],inet_gateway[6],inet_gateway[7], inet_gateway[8],inet_gateway[9],inet_gateway[10],inet_gateway[11],inet_gateway[12],inet_gateway[13],inet_gateway[14],inet_gateway[15] )); if (0 == ret) { // FIX - Kernel liefert bei IPv6 u.U. als Gateway die angefragte Adresse if (!memcmp(inet_gateway, root_dns, 16)) memset(inet_gateway, 0, sizeof(inet_gateway)); // access to internet exists (default route) -> ftp client may be from internet ret = route_tool_get_ipv6_dev(((struct sockaddr_in6 *)&his_addr)->sin6_addr.s6_addr, &dev, &gateway[0], &dst[0]); Log(("route_tool_get_dev IPv6 peer ret=%d inet_dev=%d gateway=%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", ret, dev, gateway[0],gateway[1],gateway[2],gateway[3],gateway[4],gateway[5],gateway[6],gateway[7], gateway[8],gateway[9],gateway[10],gateway[11],gateway[12],gateway[13],gateway[14],gateway[15] )); if (0 == ret) { // FIX - Kernel liefert bei IPv6 u.U. als Gateway die angefragte Adresse if (!memcmp(gateway, ((struct sockaddr_in6 *)&his_addr)->sin6_addr.s6_addr, 16)) memset(gateway, 0, sizeof(gateway)); if (dev == inet_dev && !memcmp(inet_gateway, gateway, 16)) { // access from internet access_from_internet = 1; } } } // TL10019 - alle IPv6-Zugriffe aus dem Heimnetz auf die externe IPv6 der Box als // aus dem Internet kommend betrachten. if (!access_from_internet) { unsigned char wan_ipv6[16]; if (0 == get_wan_ipv6_address(wan_ipv6)) { unsigned char ipv4addr[4]; int fam; GetFamilyAndIPv4(&ctrl_addr, &fam, ipv4addr, 0); if (fam == AF_INET6) { if (!memcmp(wan_ipv6, ((struct sockaddr_in6 *)&ctrl_addr)->sin6_addr.s6_addr, 16)) { access_from_internet = 1; } } } } } else #endif if (family == AF_INET) { unsigned long inet_gateway; unsigned long inet_dst; unsigned long gateway; unsigned long dst; // check routing to DNS root server 207.46.19.190 ret = route_tool_get_dev((207 << 24) | (46 << 16) | (19 << 8) | 190, &inet_dev, &inet_gateway, &inet_dst); Log(("route_tool_get_dev DNS-Root ret=%d inet_dev=%d inet_gateway=%u.%u.%u.%u", ret, inet_dev, (inet_gateway>>24)&0xff, (inet_gateway>>16)&0xff, (inet_gateway>>8)&0xff, inet_gateway&0xff )); if (0 == ret) { // access to internet exists (default route) -> ftp client may be from internet ret = route_tool_get_dev(ntohl(*((unsigned *)&ipv4)), &dev, &gateway, &dst); Log(("route_tool_get_dev peer ret=%d dev=%d gateway=%u.%u.%u.%u", ret, dev, (gateway>>24)&0xff, (gateway>>16)&0xff, (gateway>>8)&0xff, gateway&0xff )); if (0 == ret && dev == inet_dev && gateway == inet_gateway) { // access from internet unsigned char own_ipv4[4]; /* network order */ int own_family; unsigned short own_port; /* network order */ GetFamilyAndIPv4(&ctrl_addr, &own_family, own_ipv4, &own_port); if (AF_INET == own_family && !is_tcpconn_routed_via_vpn(ntohl(*((unsigned *)&own_ipv4)), ntohs(own_port), ntohl(*((unsigned *)&ipv4)), ntohs(peer_port))) { access_from_internet = 1; } } } } return access_from_internet; } /* USER command. Sets global passwd pointer pw if named account exists and is acceptable; sets askpasswd if a PASS command is expected. If logged in previously, need to reset state. */ void user (const char *name) { if (cred.logged_in) { int ret; if (cred.guest || cred.dochroot) { reply (530, "Can't change user from guest login."); return; } end_login (&cred); ret = add_unauthorized_client(2/* max allowed */); if (ret < 0) { /* Too many clients */ reply (421, "Unauthorized client limit reached. Try again later."); exit(1); } } { unsigned char ipv4[4]; int family; GetFamilyAndIPv4(&his_addr, &family, ipv4, 0); #ifdef USE_IPV6 if (family == AF_INET6) { unsigned char *a = (unsigned char*)&((struct sockaddr_in6 *)&his_addr)->sin6_addr.s6_addr; snprintf(cred.remote_addr_string, sizeof(cred.remote_addr_string), "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15]); } else #endif if (family == AF_INET) { snprintf(cred.remote_addr_string, sizeof(cred.remote_addr_string), "%u.%u.%u.%u", ipv4[0], ipv4[1], ipv4[2], ipv4[3]); } else { reply (530, "Protocol not supported"); return; } } cred.access_from_internet = is_access_from_internet(); Log(("access_from_internet=%d", cred.access_from_internet)); #ifndef NO_FTPS_SUPPORT if (cred.access_from_internet && g_bInternetFTPSOnly && !g_pControlConnectionSecurity) { reply (530, "Must use AUTH TLS"); return; } #endif /* FRITZBOX: 29.1.07 * Der MS IE7 probiert immer einen anonymous login. * Wenn er hier schon ein "530 access denied" ohne Kennwortabfrage erhaelt, * scheitert der Zugriff!! * Daher wird *immer* mit dem linux-User "ftpuser" authentifiziert!!! * Als positiver Nebeneffekt ist jetzt der ftp-Username vom Client total egal. */ /* Non zero means failed. */ int bad; int skip_auth = 0; if (!usermap_is_anonymous_allowed() || !IsPrePERF12CompatibilityMode() ) { char *skip_username = 0; if (!cred.access_from_internet && IsSkipAuthenticationFromHomenetwork(&skip_username)) { // Authentication komplett ueberspringen Log(("SkipAuthenticationFromHomenetwork skip_username=%s", skip_username)); bad = auth_user(skip_username, skip_username, &cred); free(skip_username); skip_auth = 1; if (cred.message) free(cred.message); cred.message = 0; Log(("SkipAuthenticationFromHomenetwork bad=%d cred.uid=%u", bad, cred.uid)); } else { // hier wg. IE7 auch bei anonymous oder ftp ein kennwort abfragen. if (cred.message) free(cred.message); cred.message = 0; if (cred.display_name) free(cred.display_name); cred.display_name = sgetsave(name); if (cred.name) free(cred.name); cred.name = 0; // this is a marker, that we have to call auth_user() after the password is entered bad = 0; cred.is_anonymous = 0; } } else { cred.is_anonymous = 1; bad = auth_user(name, "ftpuser", &cred); } if (bad) { /* If they gave us a reason. */ if (cred.message) { reply (530, "%s", cred.message); free (cred.message); cred.message = NULL; } else reply (530, "User %s access denied.", name); if (logging) syslog(LOG_NOTICE, "FTP LOGIN REFUSED FROM %s, %s", cred.remotehost, name); return; } /* If the server is set to serve anonymous service only the request have to come from a guest or a chrooted. */ if (anon_only && !cred.guest && !cred.dochroot) { reply (530, "Sorry, only anonymous ftp allowed"); return; } if (logging) { strncpy (curname, name, sizeof (curname) - 1); curname [sizeof (curname) - 1] = '\0'; /* Make sure null terminated. */ } if (cred.message) { reply (331, "%s", cred.message); free (cred.message); cred.message = NULL; } else { if (!skip_auth) reply (331, "Password required for %s.", name); } askpasswd = 1; if (skip_auth) { _pass("", 1); } /* Delay before reading passwd after first failed attempt to slow down passwd-guessing programs. */ // if (login_attempts) // sleep ((unsigned) login_attempts); } /* Terminate login as previous user, if any, resetting state; used when USER command is given or login fails. */ static void end_login (struct credentials *pcred) { char *remotehost = pcred->remotehost; int atype = pcred->auth_type; acl_set_user_context(0, 0), virtual_set_root(0); (void) seteuid ((uid_t)0); if (pcred->logged_in) logwtmp_keep_open (ttyline, "", ""); if (pcred->name) free (pcred->name); if (pcred->display_name) free (pcred->display_name); if (pcred->passwd) { memset (pcred->passwd, 0, strlen (pcred->passwd)); free (pcred->passwd); } if (pcred->homedir) free (pcred->homedir); if (pcred->rootdir) free (pcred->rootdir); if (pcred->shell) free (pcred->shell); if (pcred->pass) /* ??? */ { memset (pcred->pass, 0, strlen (pcred->pass)); free (pcred->pass); } if (pcred->message) free (pcred->message); memset (pcred, 0, sizeof (*pcred)); pcred->remotehost = remotehost; pcred->auth_type = atype; remove_unauthorized_client(); } void pass (const char *passwd) { _pass(passwd, 0/*dont skip auth*/); } /* ------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------ */ #if 0 /* AVM */ static char *filter_ls_options(const char *options) { char *s = tmpstring(strlen(options)+1); if (!s) return ""; // only allow options 1 l R a A char *p = s; while(*options != '\0') { switch(*options) { case '-': case '1': case 'l': case 'R': case 'a': case 'A': *p++ = *options; default: ; } options++; } *p = '\0'; if (!strcmp(s, "-")) *s = '\0'; return s; } #endif static char get_unescaped(const char *p) { if (*p == '\\') return *(p+1); return *p; } static char get_next_unescaped(char **pp) { if (**pp == '\0') return '\0'; if (**pp == '\\') *pp += 2; else *pp += 1; return get_unescaped(*pp); } // remove any options that are not l a d R // buf is assumed to be 'escaped' by ftpd_popen_escape() static void remove_unallowed_ls_options(char *buf, int *pis_long, int *pis_recursive) { *pis_long = 0; *pis_recursive = 0; char *p = buf; while(*p != '\0') { char c = get_unescaped(p); while(c == ' ' || c == '\t') { c = get_next_unescaped(&p); } if (c == '\0') break; if (c == '-') { char *start = p; c = get_next_unescaped(&p); while(c != ' ' && c != '\t' && c != '\0') { switch(c) { case 'l': c = get_next_unescaped(&p); *pis_long = 1; break; case 'R': c = get_next_unescaped(&p); *pis_recursive = 1; break; case 'a': case 'd': c = get_next_unescaped(&p); break; default: // remove option { char *d = p; char *s = p; c = get_next_unescaped(&s); if (d != s) { do { *d = *s; if (*d == '\0') break; s++; d++; } while(1); } } } } } else { while(c != ' ' && c != '\t' && c != '\0') { c = get_next_unescaped(&p); } } } } void retrieve (const char *cmd, const char *name) { FILE *fin; struct stat st; size_t buffer_size = 0; char line[BUFSIZ]; int is_long = 0; int is_recursive = 0; int is_popen = 0; sighandler_t old_sigpipe_handler; Log(("retrieve cmd=%s name=%s", cmd ? cmd : "?", name ? name : "?")); if (cmd == 0) { fin = virtual_fopen (name, "r"); st.st_size = 0; } else { char real_line[BUFSIZ]; /* note that name can be '-l' or other options for cmd="/bin/ls" */ char *real_name; /* use instead of given name, if given name not starting with '-' */ char *esc; if (name[0] == '-') { /* real_name = name; */ } else if (!virtual_is_read_access(name, &real_name)) { perror_reply (550, name); return; } if (name[0] == '-') { esc = ftpd_popen_escape(name); } else { esc = ftpd_popen_escape(real_name); } if (!esc) { perror_reply (451, "Local resource failure: malloc"); return; } snprintf (real_line, sizeof real_line, cmd, esc); free(esc); snprintf (line, sizeof line, cmd, name); name = line; /* should not contain the real_name */ remove_unallowed_ls_options(real_line, &is_long, &is_recursive); fin = ftpd_popen (real_line, "r"); is_popen = 1; st.st_size = -1; buffer_size = BUFSIZ; } if (fin == NULL) { if (errno != 0) { perror_reply (550, name); if (cmd == 0) { LOGCMD("get", name); } } return; } byte_count = -1; if (cmd == 0 && (virtual_stat(name, &st) < 0 || !S_ISREG (st.st_mode) || !(buffer_size = ST_BLKSIZE (st)))) { reply(550, "%s: not a plain file.", name); goto done; } if (buffer_size < 32*1024) buffer_size = 32*1024; // more speed if (restart_point) { if (type == TYPE_A) { off_t i, n; int c; n = restart_point; i = 0; while (i++ < n) { c = getc (fin); if (c == EOF) { perror_reply (550, name); goto done; } if (c == '\n') i++; } } else if (lseek (fileno (fin), restart_point, SEEK_SET) < 0) { perror_reply (550, name); goto done; } } if (dataconn (name, st.st_size, "w")) goto done; // Fix JZ-21203 // Die Control-Connection sollte bei vorzeitigem Schliessen der Daten-Connection durch // den Cienten offen bleiben. Durch das Schreiben auf den Socket wurde ein SIGPIPE ausgeloest, // und der Prozess in lostconn() beendet. old_sigpipe_handler = signal(SIGPIPE, SIG_IGN); if (is_popen) { // assume its ls command #if 1 // FRITZBOX ls is buildin send_data_buildin_ls(fin); #else send_data_ls_filter(fin, buffer_size, is_long, is_recursive); #endif } else { send_data(fin, buffer_size); } (void) data_fclose (); data = -1; pdata = -1; (void)signal(SIGPIPE, old_sigpipe_handler); done: if (cmd == 0) { LOGBYTES ("get", name, byte_count); fclose (fin); } else { ftpd_pclose (fin); } } void store (const char *name, const char *mode, int unique) { FILE *fout, *din; struct stat st; int (*closefunc)__P ((FILE *)); Log(("store name=%s mode=%d", name, mode)); if (unique && virtual_stat(name, &st) == 0) { int count; char *name2; const char *name_unique = gunique (name, &count); if (name_unique == NULL) { LOGCMD (*mode == 'w' ? "put" : "append", name); return; } else { name = name_unique; } /* FRITZBOX - gunique adjust name also */ name2 = tmpstring(strlen(name) + 8); if (!name2) { LOGCMD (*mode == 'w' ? "put" : "append", name); return; } strcpy(name2, name); name = name2; sprintf(name2 + strlen(name2), ".%d", count); } if (restart_point) mode = "r+"; fout = virtual_fopen (name, mode); closefunc = fclose; if (fout == NULL) { perror_reply (553, name); LOGCMD (*mode == 'w' ? "put" : "append", name); return; } byte_count = -1; if (restart_point) { if (type == TYPE_A) { off_t i, n; int c; n = restart_point; i = 0; while (i++ < n) { c = getc (fout); if (c == EOF) { perror_reply (550, name); goto done; } if (c == '\n') i++; } /* We must do this seek to "current" position because we are changing from reading to writing. */ if (fseek (fout, 0L, SEEK_CUR) < 0) { perror_reply (550, name); goto done; } } else if (lseek (fileno(fout), restart_point, SEEK_SET) < 0) { perror_reply (550, name); goto done; } } if (dataconn (name, (off_t)-1, "r")) goto done; if (receive_data (fout) == 0) { if (unique) reply (226, "Transfer complete (unique file name:%s).", name); else reply (226, "Transfer complete."); } (void) data_fclose (); data = -1; pdata = -1; done: LOGBYTES (*mode == 'w' ? "put" : "append", name, byte_count); (*closefunc)(fout); } static FILE * getdatasock (const char *mode) { int s, t, tries; if (data >= 0) return fdopen (data, mode); (void) seteuid ((uid_t)0); unsigned char ipv4[4]; int family; GetFamilyAndIPv4(&ctrl_addr, &family, ipv4, 0); s = socket (family /*AF_INET*/, SOCK_STREAM, 0); if (s < 0) goto bad; /* Enables local reuse address. */ { int on = 1; if (setsockopt (s, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on)) < 0) goto bad; } /* anchor socket to avoid multi-homing problems */ memset(&data_source, 0, sizeof(data_source)); data_source.ss_family = family; switch(data_source.ss_family) { case AF_INET: memcpy(&((struct sockaddr_in *)&data_source)->sin_addr, ipv4, sizeof(ipv4)); break; #ifdef USE_IPV6 case AF_INET6: memcpy(&((struct sockaddr_in6 *)&data_source)->sin6_addr, &((struct sockaddr_in6 *)&ctrl_addr)->sin6_addr, sizeof((struct sockaddr_in6 *)&data_source)->sin6_addr); break; #endif default: goto bad; } //data_source.ss_family = AF_INET; //data_source.sin_addr = ctrl_addr.sin_addr; for (tries = 1; ; tries++) { if (bind (s, (struct sockaddr *)&data_source, sizeof(data_source)) >= 0) break; if (errno != EADDRINUSE || tries > 10) goto bad; sleep (tries); } Log(("%s %d seteuid %u", __FUNCTION__, __LINE__, cred.uid)); (void) seteuid ((uid_t)cred.uid); #if defined (IP_TOS) && defined (IPTOS_THROUGHPUT) && defined (IPPROTO_IP) { int on = IPTOS_THROUGHPUT; if (setsockopt(s, IPPROTO_IP, IP_TOS, (char *)&on, sizeof(int)) < 0) syslog(LOG_WARNING, "setsockopt (IP_TOS): %m"); } #endif return (fdopen(s, mode)); bad: /* Return the real value of errno (close may change it) */ t = errno; Log(("%s %d seteuid %u", __FUNCTION__, __LINE__, cred.uid)); (void) seteuid ((uid_t)cred.uid); (void) close(s); errno = t; return NULL; } /* close the firewall ports that where opened for passive mode */ void passive_firewall_close(void) { #ifndef NO_FTPS_SUPPORT if (g_OpenedIPv4Port) { CloseIPv4TCPPort(g_OpenedIPv4Port); g_OpenedIPv4Port = 0; } #endif #ifdef USE_IPV6 if (g_OpenedIPv6Port) { CloseIPv6TCPPort(g_OpenedIPv6Port); g_OpenedIPv6Port = 0; } #endif } int dataconn (const char *name, off_t size, const char *mode) { char sizebuf[32]; FILE *file; int retry = 0; #ifndef NO_FTPS_SUPPORT if (cred.access_from_internet && g_bInternetFTPSOnly && !g_bSecureDataConnection) { reply(425, "Only secured data connection (PROT P) allowed."); #ifdef PUMA6_ATOM dataconn_proxy_destroy(); #endif if (pdata >= 0) (void) close (pdata); pdata = -1; passive_firewall_close(); return -1; } #endif file_size = size; byte_count = 0; if (size != (off_t) -1) (void) snprintf(sizebuf, sizeof(sizebuf), " (%s bytes)", off_to_str (size)); else *sizebuf = '\0'; if (pdata >= 0) { struct sockaddr_storage from; int s, fromlen = sizeof (from); (void) signal (SIGALRM, toolong); Log(("%s waiting for %u sec for dataconn", __FUNCTION__, timeout)); (void) alarm ((unsigned) timeout); s = accept (pdata, (struct sockaddr *)&from, &fromlen); (void) alarm (0); Log(("%s got dataconn", __FUNCTION__)); passive_firewall_close(); if (s < 0) { reply(425, "Can't open data connection."); #ifdef PUMA6_ATOM dataconn_proxy_destroy(); #endif (void) close (pdata); pdata = -1; return -1; } (void) close (pdata); pdata = s; #if defined (IP_TOS) && defined (IPTOS_THROUGHPUT) && defined (IPPROTO_IP) /* Optimize throughput. */ { int tos = IPTOS_THROUGHPUT; (void) setsockopt (s, IPPROTO_IP, IP_TOS, (char *)&tos, sizeof (int)); } #endif #ifdef SO_KEEPALIVE /* Set keepalives on the socket to detect dropped conns. */ { int keepalive = 1; (void) setsockopt (s, SOL_SOCKET, SO_KEEPALIVE, (char *)&keepalive, sizeof (int)); } #endif file = fdopen (pdata, mode); if (file == NULL) { reply (425, "Can't create data socket (%s,%d): %s.", sockaddr_addr2string(&data_source) /*inet_ntoa (data_source.sin_addr)*/, sockaddr_port(&data_source), strerror(errno)); return -1; } data = fileno (file); goto secure; //reply (150, "Opening %s mode data connection for '%s'%s.", // type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf); //return 0; } if (data >= 0) { reply (125, "Using existing data connection for '%s'%s.", name, sizebuf); usedefault = 1; return 0; // return fdopen (data, mode); } if (usedefault) data_dest = his_addr; usedefault = 1; file = getdatasock (mode); if (file == NULL) { reply (425, "Can't create data socket (%s,%d): %s.", sockaddr_addr2string(&data_source) /*inet_ntoa (data_source.sin_addr)*/, sockaddr_port(&data_source), strerror(errno)); return -1; } data = fileno (file); while (connect (data, (struct sockaddr *)&data_dest, sizeof (data_dest)) < 0) { if (errno == EADDRINUSE && retry < swaitmax) { sleep ((unsigned) swaitint); retry += swaitint; continue; } perror_reply (425, "Can't build data connection"); (void) fclose (file); data = -1; return -1; } secure: // for the curl client we have to send 150 before securing the data connection reply (150, "Opening %s mode data connection for '%s'%s.", type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf); #ifndef NO_FTPS_SUPPORT if (g_bSecureDataConnection) { g_pDataConnectionSecurity = SecureServerConnection(data, data); if (!g_pDataConnectionSecurity) { // RFC4217 10.2 states, the we should // send a 522 after the 150 in case // TLS failed. reply (522, "Can't secure data connection"); (void) fclose (file); data = -1; return -1; } // for filezilla we have to send a "close notify" before closing // the TCP connection, so we cant do SSL_set_quiet_shutdown() } #endif g_data_fp = file; return 0; // ok } /* Tranfer the contents of "instr" to "outstr" peer using the appropriate encapsulation of the data subject * to Mode, Structure, and Type. NB: Form isn't handled. */ static void send_data (FILE *instr, off_t blksize) { int c, cnt, filefd; // int netfd; char *buf, *bp; off_t curpos; off_t len, filesize; Log(("send_data")); transflag++; if (setjmp (urgcatch)) { transflag = 0; return; } // netfd = fileno (outstr); filefd = fileno (instr); #ifdef HAVE_MMAP #error HAVE_MMAP is somewhat slower on F!Box 7270 if (file_size > 0) { curpos = lseek (filefd, 0, SEEK_CUR); if (curpos >= 0) { filesize = file_size - curpos; buf = mmap (0, filesize, PROT_READ, MAP_SHARED, filefd, curpos); } } #endif switch (type) { case TYPE_A: #ifdef HAVE_MMAP if (file_size > 0 && curpos >= 0 && buf != MAP_FAILED) { len = 0; while (len < filesize) { byte_count++; if (buf[len] == '\n') { if (data_ferror ()) break; (void) data_putc ('\r'); } (void) data_putc (buf[len]); len++; } data_flush (); transflag = 0; munmap (buf, filesize); if (data_ferror ()) goto data_err; reply (226, "Transfer complete."); return; } #endif while ((c = getc (instr)) != EOF) { byte_count++; if (c == '\n') { if (data_ferror ()) { Log(("send_data TYPE_A data_ferror()")); goto data_err; } (void) data_putc ('\r'); } (void) data_putc (c); } data_flush(); transflag = 0; if (ferror (instr)) goto file_err; if (data_ferror ()) { Log(("send_data TYPE_A data_ferror() 2")); goto data_err; } reply (226, "Transfer complete."); return; case TYPE_I: case TYPE_L: #ifdef HAVE_MMAP #error HAVE_MMAP if (file_size > 0 && curpos >= 0 && buf != MAP_FAILED) { bp = buf; len = filesize; do { cnt = write (netfd, bp, len); len -= cnt; bp += cnt; if (cnt > 0) byte_count += cnt; } while (cnt > 0 && len > 0); transflag = 0; munmap (buf, (size_t)filesize); if (cnt < 0) { goto data_err; } reply (226, "Transfer complete."); return; } #endif if (opt_use_sendfile #ifndef NO_FTPS_SUPPORT && !g_pDataConnectionSecurity /* cant sendfile() over SSL */ #endif ) { Log(("send_data using sendfile")); if (file_size > 0) { curpos = lseek (filefd, 0, SEEK_CUR); if (curpos >= 0) { filesize = file_size - curpos; } else { filesize = 0; } } else { curpos = 0; filesize = 0; } off_t offset = curpos; { int val = 1; setsockopt(data_get_fd(), IPPROTO_TCP, TCP_CORK, &val, sizeof(val)); } while(filesize > 0) { size_t part_size = (size_t)0x7fff0000; // be sure to use a positive "ssize_t" value if (filesize < (off_t)part_size) part_size = (size_t)filesize; ssize_t size = sendfile(data_get_fd(), filefd, &offset, part_size); if (size == -1) { int sav_errno = errno; switch (sav_errno) { case EAGAIN: break; case EINVAL: case ENOSYS: goto fallback; // use read/write-loop instead case EIO: case ENOMEM: goto file_err; default: Log(("sendfile failed fd=%d goto data_err sav_errno=%d", data_get_fd(), sav_errno)); goto data_err; } } else { filesize -= (size_t)size; } } // while } else { fallback: buf = malloc ((u_int)blksize); if (buf == NULL) { transflag = 0; perror_reply (451, "Local resource failure: malloc"); return; } { int val = 1; setsockopt(data_get_fd(), IPPROTO_TCP, TCP_CORK, &val, sizeof(val)); } #if 0 // TEST DUMMY DATA while (byte_count < file_size) { cnt = file_size - byte_count; if (cnt > blksize) cnt = blksize; if (data_write(/*netfd,*/ buf, cnt) != cnt) break; #else while ((cnt = read (filefd, buf, (u_int)blksize)) > 0 && data_write(/*netfd,*/ buf, cnt) == cnt) { #endif byte_count += cnt; // loops++; } Log(("send_data fallback cnt=%u byte_count=%u", cnt, byte_count)); transflag = 0; (void)free (buf); if (cnt != 0) { if (cnt < 0) goto file_err; goto data_err; } } // opt_use_sendfile reply (226, "Transfer complete."); return; default: transflag = 0; reply (550, "Unimplemented TYPE %d in send_data", type); return; } data_err: transflag = 0; perror_reply (426, "Data connection"); return; file_err: transflag = 0; perror_reply (551, "Error on input file"); } #if 0 // FRITZBOX buildin_ls static void send_data_ls_filter(FILE *instr, FILE *outstr, off_t blksize, int is_long, int is_recursive) { int c, cnt, filefd, netfd; char *buf, *bp; off_t curpos; size_t len, filesize; transflag++; if (setjmp (urgcatch)) { transflag = 0; return; } netfd = fileno (outstr); filefd = fileno (instr); if (type != TYPE_I && type != TYPE_L && type != TYPE_A) { transflag = 0; reply (550, "Unimplemented TYPE %d", type); return; } char skip_dir = 0; char expect_dirline = 0; char line[2048]; char current_dir[2048]; // for not skipped is_recursive if (is_recursive) expect_dirline = 1; int n = 0; current_dir[0] = '\0'; while ((c = getc (instr)) != EOF) { if (ferror (outstr)) goto data_err; if (c != '\n') { if (n < sizeof(line)-1) line[n++] = c; } else { // komplette zeile line[n] = '\0'; int skip = 0; if (0 == n && is_recursive) { expect_dirline = 1; } else if (expect_dirline && line[n-1] == ':') { // assert n != 0 // scan dirline and set skip_dir accordingly char *real; line[n-1] = '\0'; if (!virtual_is_read_access(line, &real)) skip_dir = 1; else { strcpy(current_dir, line); skip_dir = 0; } expect_dirline = 1; line[n-1] = ':'; } else if (!skip_dir) { // scan line and set skip accordingly char *real; char *p = line; if (is_long) { // the filename is the 9th column int col = 1; while(col != 9 && *p != '\0') { while(*p != ' ' && *p != '\t' && *p != '\0') p++; if (*p == '\0') break; while(*p == ' ' || *p == '\t') p++; if (*p == '\0') break; col++; } Log(("long listing col=%d p=%s", col, p)); } if (is_recursive) { // check with current_dir int len = strlen(current_dir); if (len && current_dir[len-1] != '/' && len < sizeof(current_dir)-1) { current_dir[len++] = '/'; current_dir[len] = '\0'; } if (sizeof(current_dir) > len+1) strncat(current_dir, p, sizeof(current_dir)-len-1); if (!virtual_is_read_access(current_dir, &real)) skip = 1; current_dir[len] = 0; } else { if (!virtual_is_read_access(p, &real)) skip = 1; } } if (!skip_dir && !skip) { // line ausgeben fputs(line, outstr); byte_count += n; if (type == TYPE_A) { (void) data_putc ('\r'); byte_count++; } (void) data_putc ('\n'); byte_count++; } n = 0; } } // while data_flush (); transflag = 0; if (ferror (instr)) goto file_err; if (data_ferror ()) goto data_err; reply (226, "Transfer complete."); return; data_err: transflag = 0; perror_reply (426, "Data connection"); return; file_err: transflag = 0; perror_reply (551, "Error on input file"); } #else static void send_data_buildin_ls(FILE *instr) { int c; while ((c = getc (instr)) != EOF) { byte_count++; #if 0 if (c == '\n') { // if (ferror (outstr)) // goto data_err; (void) data_putc ('\r'); } #endif (void) data_putc (c); } data_flush(); reply (226, "Transfer complete."); } #endif // buildin_ls #if defined(WITH_SPLICE) && defined(_GNU_SOURCE) // receive without usermode buffer ("zero-copy") // see http://yarchive.net/comp/linux/splice.html // does not work with encryption static int my_recvfile(int in_fd, int out_fd) { const int SPLICE_PART_SIZE = 2 * 4096; int pipefd[2]; int ret; ssize_t n; Log(("%s calling pipe2", __FUNCTION__)); ret = pipe2(pipefd, 0); if (ret) { Log(("%s pipe2 failed", __FUNCTION__)); return -1; } do { Log(("%s calling splice in", __FUNCTION__)); n = splice(in_fd, 0, pipefd[1], 0, SPLICE_PART_SIZE, SPLICE_F_MORE | SPLICE_F_MOVE); Log(("%s splice in_fd returned %d", __FUNCTION__, n)); if (n > 0) { Log(("%s calling splice out", __FUNCTION__)); ssize_t n2 = splice(pipefd[0], 0, out_fd, 0, n, SPLICE_F_MORE | SPLICE_F_MOVE); Log(("%s splice out_fd returned %d", __FUNCTION__, n2)); if (n2 != n) { ret = -2; goto end; } // write error } } while(n > 0); if (n < 0) ret = -1; // read error else ret = 0; // success end: close(pipefd[0]); close(pipefd[1]); Log(("%s ret=%d", __FUNCTION__, ret)); return ret; } #endif // _GNU_SOURCE /* Transfer data from peer to "outstr" using the appropriate encapulation of the data subject to Mode, Structure, and Type. N.B.: Form isn't handled. */ static int receive_data (FILE *outstr) { int c; int cnt, bare_lfs = 0; Log(("receive_data type=%d\n", type)); transflag++; if (setjmp (urgcatch)) { transflag = 0; return -1; } switch (type) { case TYPE_I: case TYPE_L: #if defined(WITH_SPLICE) && defined(_GNU_SOURCE) if (opt_use_sendfile #ifndef NO_FTPS_SUPPORT && !g_pDataConnectionSecurity /* cant "splice" over SSL */ #endif ) { Log(("receive_data using splice\n")); cnt = my_recvfile(fileno(g_data_fp), fileno (outstr)); if (cnt < 0) { Log(("recvfile ERROR cnt[%d] < 0\n", cnt)); if (-2 == cnt) { goto file_err; } else { goto data_err; } } else { Log(("recvfile OK\n")); } } else #endif // _GNU_SOURCE { #define RECEIVE_BUF_SIZE 32768*3 char buf[RECEIVE_BUF_SIZE]; int buf_size = 0; int buf_cnt = 0; int remaining = 0; buf_size = sizeof(buf); remaining = buf_size; Log(("receive_data using a read/write loop\n")); while ((cnt = data_read (buf+buf_cnt,remaining)) > 0) { buf_cnt+=cnt; remaining-=cnt; if( remaining == 0) { if (write (fileno (outstr), buf, buf_cnt) != buf_cnt){ Log(("receive_data write error cnt:%d buf_cnt:%d\n",cnt,buf_cnt)); goto file_err; } buf_cnt =0; remaining = buf_size; } byte_count += cnt; } if(buf_cnt > 0 ){ if (write (fileno (outstr), buf, buf_cnt) != buf_cnt){ Log(("receive_data write error cnt:%d buf_cnt:%d\n",cnt,buf_cnt)); goto file_err; } } if (cnt < 0){ Log(("receive_data ERROR cnt[%d] < 0\n",cnt)); goto data_err; } } // opt_use_sendfile transflag = 0; return 0; case TYPE_E: reply (553, "TYPE E not implemented."); transflag = 0; return -1; case TYPE_A: while ((c = data_getc ()) != EOF) { byte_count++; if (c == '\n') bare_lfs++; while (c == '\r') { if (ferror (outstr)) goto data_err; c = data_getc (); if (c != '\n') { (void) putc ('\r', outstr); if (c == '\0' || c == EOF) goto contin2; } } (void) putc (c, outstr); contin2: ; } fflush(outstr); if (data_ferror ()) goto data_err; if (ferror (outstr)) goto file_err; transflag = 0; if (bare_lfs) { lreply (226, "WARNING! %d bare linefeeds received in ASCII mode", bare_lfs); (void)control_printf (" File may not have transferred correctly.\r\n"); } return (0); default: reply (550, "Unimplemented TYPE %d in receive_data", type); transflag = 0; return -1; } data_err: transflag = 0; perror_reply (426, "Data Connection"); return -1; file_err: transflag = 0; perror_reply (452, "Error writing file"); return -1; } void sizecmd(const char *filename) { switch (type) { case TYPE_L: case TYPE_I: { struct stat stbuf; if (virtual_stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) reply(550, "%s: not a plain file.", filename); else reply(213, (sizeof (stbuf.st_size) > sizeof(long) ? "%qu" : "%lu"), stbuf.st_size); break; } case TYPE_A: { FILE *fin; int c; off_t count; struct stat stbuf; fin = virtual_fopen(filename, "r"); if (fin == NULL) { perror_reply(550, filename); return; } if (virtual_stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) { reply(550, "%s: not a plain file.", filename); (void) fclose(fin); return; } count = 0; while((c=getc(fin)) != EOF) { if (c == '\n') /* will get expanded to \r\n */ count++; count++; } (void) fclose(fin); reply(213, sizeof(count) > sizeof(long) ? "%qd" : "%ld", count); break; } default: reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]); } } void statfilecmd (const char *filename) { FILE *fin; int c; char line[LINE_MAX]; char *real_name, *esc; if (!virtual_is_read_access(filename, &real_name)) { perror_reply (550, filename); return; } esc = ftpd_popen_escape(real_name); if (!esc) { perror_reply (451, "Local resource failure: malloc"); return; } (void)snprintf (line, sizeof(line), "/bin/ls -lA %s", esc); free(esc); fin = ftpd_popen (line, "r"); if (fin == NULL) { perror_reply (551, filename); return; } lreply (211, "status of %s:", filename); while ((c = getc (fin)) != EOF) { // TODO -- Filter ls result if (c == '\n') { if (control_ferror ()) { perror_reply (421, "control connection"); (void) ftpd_pclose (fin); dologout (1); /* NOTREACHED */ } if (ferror (fin)) { perror_reply (551, filename); (void) ftpd_pclose (fin); return; } (void) control_putc ('\r'); } (void) control_putc (c); } (void) ftpd_pclose (fin); reply (211, "End of Status"); } #ifndef NO_FTPS_SUPPORT static int get_internet_ipv4(unsigned char ipv4[4]) { FILE *fp = fopen("/var/run/onlinestat", "r"); if (!fp) return -1; unsigned a1 = 0, a2 = 0, a3 = 0, a4 = 256; char buf[80]; while(fgets(buf, sizeof(buf), fp)) { if (4 == sscanf(buf, "IPADDR %u.%u.%u.%u", &a1, &a2, &a3, &a4)) break; } fclose(fp); if (a4 > 255) return -1; ipv4[0] = a1 & 0xff; ipv4[1] = a2 & 0xff; ipv4[2] = a3 & 0xff; ipv4[3] = a4 & 0xff; return 0; } #endif void statcmd (void) { struct sockaddr_storage *sin = 0; lreply (211, "%s FTP server status:", hostname); if (!no_version) control_printf (" ftpd (%s) %s\r\n", PACKAGE_NAME, PACKAGE_VERSION); control_printf (" Connected to %s", cred.remotehost); if (!isdigit (cred.remotehost[0])) control_printf (" (%s)", sockaddr_addr2string(&his_addr)); control_printf ("\r\n"); if (cred.logged_in) { if (cred.guest) control_printf (" Logged in anonymously\r\n"); else control_printf (" Logged in as %s\r\n", cred.name); } else if (askpasswd) control_printf (" Waiting for password\r\n"); else control_printf (" Waiting for user name\r\n"); control_printf (" TYPE: %s", typenames[type]); if (type == TYPE_A || type == TYPE_E) control_printf (", FORM: %s", formnames[form]); if (type == TYPE_L) #ifdef CHAR_BIT control_printf (" %d", CHAR_BIT); #else #if NBBY == 8 control_printf (" %d", NBBY); #else control_printf (" %d", bytesize); /* need definition! */ #endif #endif control_printf ("; STRUcture: %s; transfer MODE: %s\r\n", strunames[stru], modenames[stru_mode]); if (data != -1) control_printf (" Data connection open\r\n"); else if (pdata != -1) { control_printf (" in Passive mode"); sin = &pasv_addr; } else if (usedefault == 0) { control_printf (" PORT"); sin = &data_dest; } if (sin) { unsigned short port = sockaddr_port(sin); int fam; unsigned char ipv4[4]; GetFamilyAndIPv4(sin, &fam, ipv4, 0); switch (fam) { case AF_INET: #ifndef NO_FTPS_SUPPORT if (cred.access_from_internet && g_pControlConnectionSecurity && sin == &pasv_addr) { // due to masq and encryption we have to send the internet ipv4 addr get_internet_ipv4(ipv4); } #endif control_printf (" (%u,%u,%u,%u,%u,%u)\r\n", ipv4[0],ipv4[1],ipv4[2],ipv4[3], (port >> 8) & 0xff, port & 0xff); break; #ifdef USE_IPV6 case AF_INET6: // not sure if this is the expeceted format for ipv6 address control_printf (" |2|%s|%u|\r\n", sockaddr_addr2string(sin), port); break; #endif } } else control_printf (" No data connection\r\n"); reply (211, "End of status"); } void fatal (const char *s) { reply (451, "Error in server: %s\n", s); reply (221, "Closing connection due to server error."); dologout (0); /* NOTREACHED */ } void reply (int n, const char *fmt, ...) { va_list ap; #if defined (HAVE_STDARG_H) && defined (__STDC__) && __STDC__ va_start (ap, fmt); #else va_start (ap); #endif // due to a BUG in FireFtp FTPS-client we have to send the whole line with one SSL_write() char *buf = 0; int siz = 40; again: siz *= 2; if (buf) free(buf); buf = (char *)malloc(siz); if (!buf) return; int len = siz; int ret; char *p = buf; ret = snprintf(p, len, "%d ", n); if (ret >= len) goto again; p += ret; len -= ret; ret = vsnprintf(p, len, fmt, ap); if (ret >= len) goto again; p += ret; len -= ret; ret = snprintf(p, len, "\r\n"); if (ret >= len) goto again; p += ret; len -= ret; control_write(buf, siz - len); free(buf); control_flush(); if (debug) { syslog (LOG_DEBUG, "<--- %d ", n); vsyslog (LOG_DEBUG, fmt, ap); } } void lreply (int n, const char *fmt, ...) { va_list ap; #if defined (HAVE_STDARG_H) && defined (__STDC__) && __STDC__ va_start (ap, fmt); #else va_start (ap); #endif // due to a BUG in FireFtp FTPS-client we have to send the whole line with one SSL_write() char *buf = 0; int siz = 40; again: siz *= 2; if (buf) free(buf); buf = (char *)malloc(siz); if (!buf) return; int len = siz; int ret; char *p = buf; ret = snprintf(p, len, "%d- ", n); if (ret >= len) goto again; p += ret; len -= ret; ret = vsnprintf(p, len, fmt, ap); if (ret >= len) goto again; p += ret; len -= ret; ret = snprintf(p, len, "\r\n"); if (ret >= len) goto again; p += ret; len -= ret; control_write(buf, siz - len); free(buf); control_flush(); if (debug) { syslog (LOG_DEBUG, "<--- %d- ", n); vsyslog (LOG_DEBUG, fmt, ap); } } static void ack (const char *s) { reply (250, "%s command successful.", s); } void nack (const char *s) { reply (502, "%s command not implemented.", s); } void delete (const char *name) { struct stat st; LOGCMD ("delete", name); if (virtual_stat(name, &st) < 0) { perror_reply (550, name); return; } if (S_ISDIR (st.st_mode)) { if (virtual_rmdir(name) < 0) { perror_reply (550, name); return; } goto done; } if (virtual_unlink(name) < 0) { perror_reply (550, name); return; } done: ack ("DELE"); } void cwd (const char *path) { if (virtual_chdir(path) < 0) perror_reply (550, path); else ack ("CWD"); } void makedir (const char *name) { LOGCMD ("mkdir", name); if (virtual_mkdir (name, 0777) < 0) { perror_reply (550, name); return; } reply (257, "\"%s\" new directory created.", name); } void removedir (const char *name) { LOGCMD ("rmdir", name); if (virtual_rmdir (name) < 0) perror_reply (550, name); else ack("RMD"); } void pwd (void) { char *path = xgetcwd (); if (path) { char *virt_path = path; virtual_make_from_real_path(&virt_path); reply (257, "\"%s\" is current directory.", virt_path); free(path); } else reply (550, "%s.", strerror (errno)); } char * renamefrom (const char *name) { struct stat st; if (virtual_stat(name, &st) < 0) { perror_reply (550, name); return ((char *)0); } reply (350, "File exists, ready for destination name"); return (char *)(name); } void renamecmd (const char *from, const char *to) { LOGCMD2 ("rename", from, to); if (virtual_rename (from, to) < 0) perror_reply (550, "rename"); else ack ("RNTO"); } static void dolog (struct sockaddr_storage *sin, struct credentials *pcred) { const char *name; #if 1 /* FRITZBOX */ struct hostent *hp = 0; #else struct hostent *hp = gethostbyaddr ((char *)&sin->sin_addr, sizeof (struct in_addr), AF_INET); #endif if (hp) name = hp->h_name; else name = sockaddr_addr2string(sin); if (pcred->remotehost) free (pcred->remotehost); pcred->remotehost = sgetsave (name); #ifdef HAVE_SETPROCTITLE snprintf (proctitle, sizeof (proctitle), "%s: connected", pcred->remotehost); setproctitle ("%s",proctitle); #endif /* HAVE_SETPROCTITLE */ if (logging) syslog (LOG_INFO, "connection from %s", pcred->remotehost); } /* Record logout in wtmp file and exit with supplied status. */ void dologout (int status) { /* Racing condition with SIGURG: If SIGURG is receive here, it will jump back has root in the main loop David Greenman:dg@root.com. */ transflag = 0; if (cred.logged_in) { (void) seteuid ((uid_t)0); logwtmp_keep_open (ttyline, "", ""); if (cred.display_name) LoginControlLogout(cred.display_name, cred.access_from_internet); } passive_firewall_close(); remove_unauthorized_client(); /* beware of flushing buffers after a SIGPIPE */ _exit (status); } static void myoob (int signo) { char *cp; (void)signo; /* only process if transfer occurring */ if (!transflag) return; cp = tmpline; if (get_controlconnection_line(cp, 7) == NULL) { reply (221, "You could at least say goodbye."); dologout (0); } upper (cp); if (strcmp (cp, "ABOR\r\n") == 0) { tmpline[0] = '\0'; reply (426, "Transfer aborted. Data connection closed."); reply (226, "Abort successful"); longjmp (urgcatch, 1); } if (strcmp (cp, "STAT\r\n") == 0) { if (file_size != (off_t) -1) reply (213, "Status: %s of %s bytes transferred", off_to_str (byte_count), off_to_str (file_size)); else reply (213, "Status: %s bytes transferred", off_to_str (byte_count)); } } struct sockaddr_storage *numericaladdr2sockaddr_storage(const int family, const char *addr) { struct addrinfo hints, *res; int ret; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = family; hints.ai_flags = AI_NUMERICHOST; ret = getaddrinfo(addr, 0, &hints, &res); if (ret != 0) { Log(("getaddrinfo of addr='%s' failed fam=%d errno=%d", addr, family, errno)); return 0; } switch(res->ai_family) { case AF_INET: if (res->ai_addrlen != sizeof(struct sockaddr_in)) { Log(("wrong size of ipv4 addr")); freeaddrinfo(res); return 0; } break; #ifdef USE_IPV6 case AF_INET6: if (res->ai_addrlen != sizeof(struct sockaddr_in6)) { Log(("wrong size of ipv6 addr")); freeaddrinfo(res); return 0; } break; #endif default: freeaddrinfo(res); return 0; }; // take first result struct sockaddr_storage *ss = (struct sockaddr_storage *)calloc(sizeof(struct sockaddr_storage), 1); if (!ss) { freeaddrinfo(res); return 0; } switch(res->ai_family) { case AF_INET: memcpy(ss, res->ai_addr, res->ai_addrlen); break; #ifdef USE_IPV6 case AF_INET6: memcpy(ss, res->ai_addr, res->ai_addrlen); ConvertMappedIPv4(ss); break; #endif } freeaddrinfo(res); return ss; } /* param from RFC 2428 e.g.: * |1|132.235.1.2|6275| * |2|1080::8:800:200C:417A|5282| */ void extended_port(char *param) { struct sockaddr_storage *ss = 0; char delim = *param++; if (delim < 33 || delim > 126) goto err; char net_prot = *param++; if (net_prot != '1' && net_prot != '2') goto err_prot; int family = AF_INET; #ifdef USE_IPV6 if (net_prot == '2') family = AF_INET6; #else if (net_prot == '2') goto err_prot; #endif if (*param++ != delim) goto err; char *addr_string = param; while(*param != '\0' && *param != delim) param++; if (*param != delim) goto err; *param++ = '\0'; char *port_string = param; while(*param != '\0' && *param != delim) param++; if (*param != delim) goto err; *param++ = '\0'; if (*param != '\0') goto err; ss = numericaladdr2sockaddr_storage(family, addr_string); if (!ss) goto err; unsigned port; if (1 != sscanf(port_string, "%u", &port)) goto err; Log(("port_string='%s' port=%u, IPPORT_RESERVED=%u", port_string, port, IPPORT_RESERVED)); if (port > 0xffff || port <= IPPORT_RESERVED) { Log(("port %u is illegal", port)); goto illegal; } // check for same dest ip address of data and control connection if (!is_same_addr(&his_addr, ss)) { Log(("EPRT his_addr and req addr do not match")); goto illegal; } memcpy(&data_dest, ss, sizeof(data_dest)); // data_dest.ss_family = family; switch(family) { case AF_INET: ((struct sockaddr_in *)&data_dest)->sin_port = htons(port); #ifdef USE_IPV6 case AF_INET6: ((struct sockaddr_in6 *)&data_dest)->sin6_port = htons(port); #endif } free(ss); reply (200, "EPRT command sucessful."); return; illegal: free(ss); reply(500, "Illegal EPRT command"); return; err: if (ss) free(ss); reply (501, "Illegal extended address"); return; err_prot: if (ss) free(ss); #ifdef USE_IPV6 reply(522, "Network protocol not supported, use (1,2)"); #else reply(522, "Network protocol not supported, use (1)"); #endif return; } // liefert zufaelligen Port im Bereich 1024 < port < 65536 static unsigned get_random_port(void) { FILE *fp = fopen("/dev/urandom", "r"); unsigned port; if (fp) { port = (((unsigned)fgetc(fp) & 0xff) << 8) | ((unsigned)fgetc(fp) & 0xff); fclose(fp); } else { port = ((rand() & 0xff) << 8) | (rand() & 0xff); } if (port <= 1024) port += 1024 + 1; // nicht mehr gleichverteilt, hier aber unwichtig return port; } #ifdef PUMA6_ATOM static struct sockaddr_storage puma6_arm_lan_addr; static int get_puma6_arm_ipv4(unsigned char *ipv4) { int family; GetFamilyAndIPv4(&puma6_arm_lan_addr, &family, ipv4, 0); if (family != AF_INET) return -1; if (0 == (ipv4[0] | ipv4[1] | ipv4[2] | ipv4[3])) return -1; return 0; } #endif // PUMA6_ATOM void passive_ex(int extended, int family) { int len; int err = 0; char *errstr = 0; #ifdef PUMA6_ATOM int with_pcplisten = 0; // not tested yet -> not activated #else int with_pcplisten = pcplisten_available(); #endif if (pdata >= 0) { /* AVM Nessus Check (Nessus ID 10085)*/ /* wg. Zugriff von Firefox 2.0 aus dem Internet duerfen wir hier kein 'goto done;' machen */ /* (Firefox 2.0 macht mehrfaches PASV) */ #ifdef PUMA6_ATOM dataconn_proxy_destroy(); #endif (void) close(pdata); pdata = -1; passive_firewall_close(); } #ifdef USE_IPV6 if (family == AF_INET6 && !extended) { Log(("passive_ex extended=%d family=%d: not possible", extended, family)); goto pasv_error; } #endif int fam; unsigned char ipv4[4]; GetFamilyAndIPv4(&ctrl_addr, &fam, ipv4, 0); if (family != fam) { Log(("passive_ex extended=%d family=%d fam=%d: not possible", extended, family, fam)); goto pasv_error; } #ifndef NO_FTPS_SUPPORT unsigned retry = 0; // retry done only when !with_pcplisten again: #endif pdata = socket (family, SOCK_STREAM, 0); if (pdata < 0) { err = errno; Log(("passive_ex extended=%d family=%d sizeof(pasv_addr)=%u : socket failed errno=%d", extended, family, sizeof(pasv_addr), err)); goto pasv_error; } memset(&pasv_addr, 0, sizeof(pasv_addr)); pasv_addr.ss_family = family; switch(pasv_addr.ss_family) { case AF_INET: memcpy(&((struct sockaddr_in *)&pasv_addr)->sin_addr, ipv4, 4); #ifndef NO_FTPS_SUPPORT if (retry != 0) ((struct sockaddr_in *)&pasv_addr)->sin_port = htons(get_random_port()); #endif break; #ifdef USE_IPV6 case AF_INET6: memcpy(&((struct sockaddr_in6 *)&pasv_addr)->sin6_addr, &((struct sockaddr_in6 *)&ctrl_addr)->sin6_addr, sizeof(((struct sockaddr_in6 *)&pasv_addr)->sin6_addr)); #ifndef NO_FTPS_SUPPORT if (retry != 0) ((struct sockaddr_in6 *)&pasv_addr)->sin6_port = htons(get_random_port()); #endif break; #endif } (void) seteuid ((uid_t)0); if (bind (pdata, (struct sockaddr *)&pasv_addr, sizeof (pasv_addr)) < 0) { err = errno; Log(("passive_ex extended=%d family=%d sizeof(pasv_addr)=%u : bind failed errno=%d", extended, family, sizeof(pasv_addr), err)); (void) seteuid ((uid_t)cred.uid); goto pasv_error; } (void) seteuid ((uid_t)cred.uid); len = sizeof(pasv_addr); if (getsockname (pdata, (struct sockaddr *) &pasv_addr, &len) < 0) { err = errno; Log(("passive_ex extended=%d family=%d sizeof(pasv_addr)=%u : getsockname failed errno=%d", extended, family, sizeof(pasv_addr), err)); goto pasv_error; } ConvertMappedIPv4(&pasv_addr); if (listen (pdata, 1) < 0) { err = errno; Log(("passive_ex extended=%d family=%d sizeof(pasv_addr)=%u : listen failed errno=%d", extended, family, sizeof(pasv_addr), err)); goto pasv_error; } if (cred.access_from_internet && with_pcplisten) { // pasv_addr may be modified here! (void) seteuid ((uid_t)0); int ret = pcplisten_start(&pasv_addr, timeout, &errstr); (void) seteuid ((uid_t)cred.uid); if (0 != ret) goto pasv_error; if (errstr) { free(errstr); errstr = 0; } } else { #ifdef PUMA6_ATOM if (his_addr_is_from_puma6_arm_proxy) { /* Fix for Defect 2496 */ /* * In passive mode we cant direct the client to connect to us (ATOM). * instead we have to craete a proxy (ARM<->ATOM) for the data connection. * The client will have to connect to the ARM. */ unsigned arm_port; (void) seteuid ((uid_t)0); if (dataconn_proxy_create(&pasv_addr, &arm_port)) { (void) seteuid ((uid_t)cred.uid); Log(("passive_ex extended=%d dataconn_proxy_create failed", extended)); goto pasv_error; } (void) seteuid ((uid_t)cred.uid); if (extended) { reply (229, "Entering Extended Passive Mode (|||%u|)", arm_port); } else { /* for IPv6 extented must be used, so we have IPv4 here */ unsigned char arm_ipv4[4]; if (get_puma6_arm_ipv4(arm_ipv4)) { Log(("passive_ex extended=%d get_puma6_arm_ipv4 failed", extended)); goto pasv_error; } reply (227, "Entering Passive Mode (%u,%u,%u,%u,%u,%u)", arm_ipv4[0], arm_ipv4[1], arm_ipv4[2], arm_ipv4[3], (arm_port >> 8) & 0xff, arm_port & 0xff); } return; } #endif #if defined(USE_IPV6) || !defined(NO_FTPS_SUPPORT) if (cred.access_from_internet && (family == AF_INET6 #ifndef NO_FTPS_SUPPORT || g_pControlConnectionSecurity #endif )) { // IP Masqerading can't learn the data port, so we have to open in manually // IPv6 connection tracking doesnt learn the port even if not encrypted! int ret; switch(family) { case AF_INET: ret = OpenIPv4TCPPort(sockaddr_port(&pasv_addr)); if (0 == ret) g_OpenedIPv4Port = sockaddr_port(&pasv_addr); break; #ifdef USE_IPV6 case AF_INET6: ret = OpenIPv6TCPPort(sockaddr_port(&pasv_addr)); Log(("OpenIPv6TCPPort %u ret=%d", sockaddr_port(&pasv_addr), ret)); if (0 == ret) g_OpenedIPv6Port = sockaddr_port(&pasv_addr); break; #endif default: ret = -EBADF; } if (-EBUSY == ret && ++retry < 100) { // retry with other port close(pdata); goto again; } if (ret != 0) goto pasv_error; // TODO: retry with different port } #endif } // if with_pcplisten if (extended) { #ifdef PUMA6_ATOM // assert(!his_addr_is_from_puma6_arm_proxy); #endif reply (229, "Entering Extended Passive Mode (|||%u|)", sockaddr_port(&pasv_addr)); } else { char *p, *a; // assert (pasv_addr.ss_family == AF_INET); a = (char *) &((struct sockaddr_in *)&pasv_addr)->sin_addr; #ifndef NO_FTPS_SUPPORT if (cred.access_from_internet && !with_pcplisten && g_pControlConnectionSecurity) { if (0 == get_internet_ipv4(ipv4)) a = ipv4; } #endif p = (char *) &((struct sockaddr_in *)&pasv_addr)->sin_port; #define UC(b) (((int) b) & 0xff) reply (227, "Entering Passive Mode (%d,%d,%d,%d,%d,%d)", UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1])); } return; pasv_error: if (pdata >= 0) { #ifdef PUMA6_ATOM dataconn_proxy_destroy(); #endif (void)close(pdata); pdata = -1; } if (0 != errstr) { reply (425, "Can't open passive connection (%s)", errstr); free(errstr); } else { if (0 != err) { errno = err; perror_reply (425, "Can't open passive connection"); } else { reply (425, "Can't open passive connection"); } } } /* * net port 0: auto * 1: IPv4 * 2: IPv6 */ void extended_passive(unsigned net_prot) { int family; switch(net_prot) { case 0: { unsigned char ipv4[4]; GetFamilyAndIPv4(&ctrl_addr, &family, ipv4, 0); } break; case 1: family = AF_INET; break; case 2: #ifdef USE_IPV6 family = AF_INET6; #else goto err; #endif break; default: goto err; } passive_ex(1, family); return; err: reply (425, "Can't open passive connection"); } /* Note: a response of 425 is not mentioned as a possible response to the PASV command in RFC959. However, it has been blessed as a legitimate response by Jon Postel in a telephone conversation with Rick Adams on 25 Jan 89. */ void passive (void) { passive_ex(0, AF_INET); } #if defined(PUMA6_ARM) || defined(PUMA6_ATOM) int string_to_sockaddr_storage(const char *str, struct sockaddr_storage *addr) { Log(("%s str=%s", __FUNCTION__, str)); memset(addr, 0, sizeof(*addr)); #if 0 if (strchr(str, '.') && strchr(str, ':')) { // inet_pton has a 'bug' regarding mapped ipv4 addresses -> we want an AF_INET result for these str = strrchr(str, ':') + 1; if (str == (void *)1) return -1; // fail } #endif if (1 == inet_pton(AF_INET, str, (void *)&((struct sockaddr_in *)(addr))->sin_addr)) { addr->ss_family = AF_INET; } else if (1 == inet_pton(AF_INET6, str, (void *)&((struct sockaddr_in6 *)(addr))->sin6_addr)) { addr->ss_family = AF_INET6; ConvertMappedIPv4(addr); } else { return -1; // fail } return 0; // ok } #endif #ifdef PUMA6_ATOM // port may differ static int is_same_ipaddr_in_sockaddr_storage(struct sockaddr_storage *a1, struct sockaddr_storage *a2) { if (a1->ss_family != a2->ss_family) return 0; switch(a1->ss_family) { case AF_INET: return 0 == memcmp(&(((struct sockaddr_in *)a1)->sin_addr), &(((struct sockaddr_in *)a2)->sin_addr), sizeof(struct in_addr)); case AF_INET6: return 0 == memcmp(&(((struct sockaddr_in6 *)a1)->sin6_addr), &(((struct sockaddr_in6 *)a2)->sin6_addr), sizeof(struct in6_addr)); } return 0; } static int his_addr_is_puma6_arm(void) { int is = 0; FILE *fp = 0; if (AF_INET == his_addr.ss_family) { fp = popen("avmipc_state ARM_IPV4_LAN", "r"); } else if (AF_INET6 == his_addr.ss_family) { fp = popen("avmipc_state ARM_IPV6_GUA ARM_IPV6_ULA", "r"); } if (!fp) return -1; char buf[80]; while(!is && buf == fgets(buf, sizeof(buf)-1, fp)) { buf[sizeof(buf)-1] = '\0'; char *p = &buf[strlen(buf)-1]; while(p >= buf && (*p == '\r' || *p == '\n')) { *p-- = '\0'; } if (0 == string_to_sockaddr_storage(buf, &puma6_arm_lan_addr)) { if (is_same_ipaddr_in_sockaddr_storage(&puma6_arm_lan_addr, &his_addr)) is = 1; } } pclose(fp); return is; } int avmclient(const char *client_addr) { Log(("%s client_addr=%s", __FUNCTION__, client_addr)); if (his_addr_is_from_puma6_arm_proxy) { // cant set twice return 0; // ok } // Security: only the PUMA6 ARM (current his_addr) is allowed to issue this command if (!his_addr_is_puma6_arm()) return -1; // security violation struct sockaddr_storage addr; if (string_to_sockaddr_storage(client_addr, &addr)) return -1; // fail memcpy(&his_addr, &addr, sizeof(his_addr)); his_addr_is_from_puma6_arm_proxy = 1; Log(("%s ok", __FUNCTION__)); return 0; // ok } #endif // PUMA6_ATOM /* Generate unique name for file with basename "local". The file named "local" is already known to exist. Generates failure reply on error. */ static char * gunique (const char *local, int *pcount) { static char *string = 0; struct stat st; int count; char *cp; cp = strrchr (local, '/'); if (cp) *cp = '\0'; if (virtual_stat(cp ? local : ".", &st) < 0) { perror_reply (553, cp ? local : "."); return ((char *) 0); } if (cp) *cp = '/'; if (string) free (string); string = malloc (strlen (local) + 5); /* '.' + DIG + DIG + '\0' */ if (string) { strcpy (string, local); cp = string + strlen (string); *cp++ = '.'; for (count = 1; count < 100; count++) { (void)sprintf (cp, "%d", count); if (stat (string, &st) < 0) { if (pcount) *pcount = count; return string; } } } reply (452, "Unique file name cannot be created."); return NULL; } /* * Format and send reply containing system error number. */ void perror_reply (int code, const char *string) { reply (code, "%s: %s.", string, strerror (errno)); } static char *onefile[] = { "", 0 }; void send_file_list (const char *whichf) { struct stat st; DIR *dirp = NULL; struct dirent *dir; char **dirlist, *dirname; int simple = 0; int freeglob = 0; glob_t gl; char *p = NULL; if (strpbrk(whichf, "~{[*?") != NULL) { int flags = GLOB_NOCHECK; #ifdef GLOB_BRACE flags |= GLOB_BRACE; #endif #ifdef GLOB_QUOTE flags |= GLOB_QUOTE; #endif #ifdef GLOB_TILDE flags |= GLOB_TILDE; #endif memset (&gl, 0, sizeof (gl)); freeglob = 1; if (pattern_too_complex(whichf) || glob (whichf, flags, 0, &gl)) { reply (550, "not found"); goto out; } else if (gl.gl_pathc == 0) { errno = ENOENT; perror_reply (550, whichf); goto out; } dirlist = gl.gl_pathv; } else { p = strdup (whichf); onefile[0] = p; dirlist = onefile; simple = 1; } if (setjmp (urgcatch)) { transflag = 0; goto out; } while ((dirname = *dirlist++)) { // char *real_dirname; /* If user typed "ls -l", etc, and the client used NLST, do what the user meant. */ if (dirname[0] == '-' && *dirlist == NULL && transflag == 0) { retrieve ("/bin/ls %s", dirname); /*dirname is option, no file */ goto out; } // real_dirname = make_real_path(dirname); // if (!real_dirname) continue; if (virtual_stat (dirname, &st) < 0) { perror_reply (550, whichf); if (g_data_fp != NULL) { (void) data_fclose (); transflag = 0; data = -1; pdata = -1; } goto out; } if (S_ISREG(st.st_mode)) { if (g_data_fp == NULL) { if (dataconn ("file list", (off_t)-1, "w")) goto out; transflag++; } #ifndef NO_RFC2640_SUPPORT { char *s; #ifdef FULL_UTF8 if (!g_utf8) { s = strdup(dirname); if (s) { ConvertStringFromUTF8ToISO8859_1_With_Fallback(s, '.'); data_printf ("%s%s\n", s, type == TYPE_A ? "\r" : ""); byte_count += strlen (s) + 1; free(s); } } else { data_printf ("%s%s\n", dirname, type == TYPE_A ? "\r" : ""); byte_count += strlen (dirname) + 1; } #else if (g_utf8) s = ConvertStringFromISO8859_1ToUTF8_WithAlloc(dirname); else s = 0; if (s) { data_printf ("%s%s\n", s, type == TYPE_A ? "\r" : ""); byte_count += strlen (s) + 1; free(s); } else { data_printf ("%s%s\n", dirname, type == TYPE_A ? "\r" : ""); byte_count += strlen (dirname) + 1; } #endif } #else data_printf ("%s%s\n", dirname, type == TYPE_A ? "\r" : ""); byte_count += strlen (dirname) + 1; #endif continue; } else if (!S_ISDIR (st.st_mode)) continue; dirp = virtual_opendir(dirname); if (dirp == NULL) continue; while ((dir = readdir (dirp)) != NULL) { char *virt_nbuf; // char *real_nbuf; if (dir->d_name[0] == '.' && dir->d_name[1] == '\0') continue; if (dir->d_name[0] == '.' && dir->d_name[1] == '.' && dir->d_name[2] == '\0') continue; virt_nbuf = (char *) alloca (strlen (dirname) + 1 + strlen (dir->d_name) + 1); if (!strlen(dirname) || !strcmp(dirname,"/")) { sprintf (virt_nbuf, "%s%s", dirname, dir->d_name); } else { sprintf (virt_nbuf, "%s/%s", dirname, dir->d_name); } // real_nbuf = (char *) alloca (strlen (real_dirname) + 1 + // strlen (dir->d_name) + 1); // sprintf (real_nbuf, "%s/%s", real_dirname, dir->d_name); /* We have to do a stat to insure it's not a directory or special file. */ if (simple || (virtual_stat (virt_nbuf, &st) == 0 && S_ISREG(st.st_mode))) { if (g_data_fp == NULL) { if (dataconn ("file list", (off_t)-1, "w")) goto out; transflag++; } char *real; if (virtual_is_read_access(virt_nbuf, &real)) { char *name = virt_nbuf; #ifndef NO_RFC2640_SUPPORT #ifdef FULL_UTF8 if (!g_utf8) { name = strdup(virt_nbuf); if (!name) continue; ConvertStringFromUTF8ToISO8859_1_With_Fallback(name, '.'); } #else if (g_utf8) { name = ConvertStringFromISO8859_1ToUTF8_WithAlloc(virt_nbuf); if (!name) continue; } #endif #else name = virt_nbuf; #endif #if 1 // for safari, conforming to RFC959, NLST and LIST command if (virt_nbuf[0] == '.' && virt_nbuf[1] == '/') { data_printf ("%s\r\n", &virt_nbuf[2]); byte_count += strlen(&virt_nbuf[2]) + 2; } else { data_printf ("%s\r\n", virt_nbuf); byte_count += strlen(virt_nbuf) + 2; } #else if (virt_nbuf[0] == '.' && virt_nbuf[1] == '/') data_printf ("%s%s\n", &virt_nbuf[2], type == TYPE_A ? "\r" : ""); else data_printf ("%s%s\n", virt_nbuf, type == TYPE_A ? "\r" : ""); byte_count += strlen (virt_nbuf) + 1; #endif #ifndef NO_RFC2640_SUPPORT #ifdef FULL_UTF8 if (!g_utf8) free(name); #else if (g_utf8) free(name); #endif #endif } } } (void) closedir (dirp); } if (g_data_fp == NULL) reply (550, "No files found."); else if (data_ferror () != 0) perror_reply (550, "Data connection"); else reply (226, "Transfer complete."); transflag = 0; if (g_data_fp != NULL) (void) data_fclose (); data = -1; pdata = -1; out: if (p) free (p); if (freeglob) { freeglob = 0; globfree (&gl); } } #ifndef NO_FTPS_SUPPORT void clear_ssl_errors(void) { while(ERR_get_error() != 0); } void Log_SSL_Errors(void) { #ifdef FTPD_DEBUG char buf[256]; #endif const char *file,*linedata; int line,flags; unsigned long es, e; es=CRYPTO_thread_id(); while ((e = ERR_get_error_line_data(&file,&line,&linedata,&flags)) != 0) { // unsigned long lib, func, reason; // lib = ERR_GET_LIB(e); // func = ERR_GET_FUNC(e); // freason = ERR_GET_REASON(e); Log(("%lu:%s:%s:%d:", es, ERR_error_string(e, buf), file, line)); } } #define FTPS_CERTFILE_OLD "/var/tmp/ftps_ssl_cert.pem" #define FTPS_KEYFILE_OLD "/var/tmp/ftps_ssl_key.pem" #define FTPS_CERTFILE "/var/tmp/websrv_ssl_cert.pem" #define FTPS_KEYFILE "/var/flash/websrv_ssl_key.pem" static time_t current_secs(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return ts.tv_sec; } // Generate or update server certificate for FTPS static int FixServerCertificate(void) { char complete_file[80]; char cmd[80]; sighandler_t old_handler = signal(SIGCHLD, SIG_DFL); snprintf(complete_file, sizeof(complete_file), "/var/tmp/ftps_cert_gen%d.complete", getpid()); unlink(complete_file); snprintf(cmd, sizeof(cmd), "msgsend ctlmgr ftps_cert_gen %d", getpid()); if (-1 == system(cmd)) { int errn = errno; Log(("%s system() failed with -1 errno=%d", __FUNCTION__, errn)); signal(SIGCHLD, old_handler); return -1; } signal(SIGCHLD, old_handler); time_t tstart = current_secs(); short ok = 0; do { usleep(100 * 1000); // 100ms if (0 == access(complete_file, R_OK)) { ok = 1; break; } } while((current_secs() - tstart) < 2*60); unlink(complete_file); return ok ? 0 : -1; } static int password_callback(char *buf, int size, int rwflag, void *userdata) { if (rwflag) return 0; if (g_old_key) { unsigned char pw[] = "\"G&mL"; unsigned char key = 0x55; unsigned char *s = pw; unsigned char *d = buf; while(*s) { *d = *s++; *d ^= key; key = key ^ *d++; } *d = '\0'; } else { char cmd[128]; unsigned key = (unsigned)getpid(); snprintf(cmd, sizeof(cmd), "/bin/getprivkeypass %u", key); cmd[sizeof(cmd)-1] = '\0'; FILE *fp = popen(cmd, "r"); if (!fp) return 0; size_t n = fread(buf, 1, size-1, fp); pclose(fp); if (n < 1) return 0; int i; for (i = 0 ; i < n; i++) { int bit = (key & 0x80000000); buf[i] ^= (key & 0xff); key <<= 1; if (bit) key |= 1; } buf[n] = '\0'; } return strlen(buf); } static DH *get_dh_params(void) { static unsigned char dh1536_p[] = { 0xF5,0xF5,0xD4,0x01,0xA6,0xD8,0x7F,0x8A,0x3E,0xEB,0xE2,0x6D, 0xA8,0xD0,0x67,0xF0,0x3B,0xDA,0x47,0x6F,0x7F,0xF4,0x1C,0x99, 0xA7,0x14,0x63,0x74,0x10,0xC1,0x93,0x58,0xE9,0xA3,0x19,0xF9, 0xDA,0x47,0xED,0x45,0x5C,0x12,0xAD,0xEC,0x7E,0xBF,0x51,0xB8, 0x0A,0x21,0xF4,0x77,0x13,0x17,0x88,0x71,0xE5,0x42,0xD1,0xC8, 0xC3,0x5C,0x9B,0xFC,0xA4,0x8E,0xB8,0x22,0x3E,0x75,0x23,0x41, 0x65,0xA1,0x74,0x1F,0x58,0x9E,0x0B,0xC2,0xAC,0x20,0x9E,0x5F, 0x33,0xB0,0x65,0xC1,0x2D,0x9B,0xCF,0x97,0x9E,0xB1,0xD7,0x49, 0x48,0x8C,0x81,0xE6,0x96,0x82,0xB3,0xC8,0x3A,0xFA,0xD7,0x53, 0xB4,0x6F,0x94,0xD0,0xA0,0xC4,0x11,0x80,0xEA,0x8B,0x3C,0x8B, 0xCB,0x98,0x3E,0x6C,0xC6,0x15,0x74,0x8C,0x2C,0x53,0x0F,0x84, 0x00,0x90,0x18,0x1A,0x82,0x87,0x53,0xCD,0x8F,0xD3,0x18,0x49, 0x80,0x39,0xB7,0x28,0xAD,0xBA,0xC7,0x6E,0x74,0x9F,0x9E,0xAB, 0xD4,0x59,0x65,0x76,0xF2,0xA4,0x22,0x5C,0x7F,0x46,0x95,0xE7, 0x76,0x19,0x87,0x30,0x3C,0x40,0xD1,0x65,0x95,0x16,0x09,0x41, 0xE5,0x6F,0x14,0xAD,0x29,0x3F,0x7E,0x67,0x04,0x7C,0xC0,0x9B, }; static unsigned char dh1536_g[] = { 0x02, }; DH *dhparams; if ((dhparams = DH_new()) == 0) return 0; dhparams->p = BN_bin2bn(dh1536_p,sizeof(dh1536_p), 0); dhparams->g = BN_bin2bn(dh1536_g,sizeof(dh1536_g), 0); if (!dhparams->p || !dhparams->g) { DH_free(dhparams); return 0; } return dhparams; } #if (OPENSSL_VERSION_NUMBER < 0x10000000L) #error OPENSSL version 0.x not supported #endif static struct SecuredConnection *SecureServerConnection(int in_fd, int out_fd) { struct SecuredConnection *p = (struct SecuredConnection *)calloc(sizeof(struct SecuredConnection), 1); DH *dhparams; if (!p) return 0; SSL_METHOD *meth = 0; meth=SSLv23_server_method(); p->ctx = SSL_CTX_new(meth); // alway use TLS server mode, even if server initiates TCP connection if (!p->ctx) { Log(("SSL_CTX_new failed meth=%p", meth)); goto err; } SSL_CTX_set_options(p->ctx, SSL_OP_ALL); // SSL_CTX_set_quiet_shutdown(p->ctx, 1); SSL_CTX_set_mode(p->ctx, SSL_MODE_AUTO_RETRY); SSL_CTX_set_verify(p->ctx, SSL_VERIFY_NONE, 0); g_old_key = 0; if (access("/bin/getprivkeypass", X_OK)) g_old_key = 1; // getprivkeypass does not exist if (1 != SSL_CTX_use_certificate_chain_file(p->ctx, g_old_key ? FTPS_CERTFILE_OLD : FTPS_CERTFILE)) { goto err; } SSL_CTX_set_default_passwd_cb(p->ctx, password_callback); if (SSL_CTX_use_PrivateKey_file(p->ctx, g_old_key ? FTPS_KEYFILE_OLD : FTPS_KEYFILE, SSL_FILETYPE_PEM) <= 0) { goto err; } /* DH parameters */ if ((dhparams = get_dh_params()) == 0) { Log(("unable to set dh params")); goto err; } SSL_CTX_set_options(p->ctx, SSL_OP_SINGLE_DH_USE); SSL_CTX_set_tmp_dh(p->ctx, dhparams); /* ECDH parameters - use curve NID_X9_62_prime256v1 which is widely used */ { EC_KEY *ecdh; /* use default */ ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); if (ecdh != NULL) { SSL_CTX_set_tmp_ecdh(p->ctx, ecdh); EC_KEY_free(ecdh); } else { /* unable to create curve NID_X9_62_prime256v1 */ goto err; } } /* AVM: we use a crafted default cipher - see Story 10505 */ const char *cipher = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:" "ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-SHA:" "DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA:AES256-SHA:" "AES128-SHA256:AES256-SHA256"; if (!SSL_CTX_set_cipher_list(p->ctx, cipher)) { Log(("can't set default cipher list \"%s\"", cipher)); goto err; } p->ssl = SSL_new(p->ctx); if (!p->ssl) { Log(("SSL_new failed")); goto err; } SSL_set_rfd(p->ssl, in_fd); SSL_set_wfd(p->ssl, out_fd); int ret, err = 0; do { ret = SSL_accept(p->ssl); if (ret < 0) { err = SSL_get_error(p->ssl, ret); } } while(ret < 0 && (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE)); if (ret != 1) { err = SSL_get_error(p->ssl, ret); Log(("SSL_accept failed ret=%d err=0x%x", ret, err)); Log_SSL_Errors(); goto err; } Log(("SecureServerConnection ok")); return p; err: Log(("SecureServerConnection failed")); if (p->ssl) SSL_free(p->ssl); if (p->ctx) SSL_CTX_free(p->ctx); free(p); return 0; } // Keep g_pControlConnectionSecurity !! static void ReinitSession(void) { end_login(&cred); chdir("/"); g_bSecureDataConnection = 0; g_PBSZ_Received = 0; g_extended_passive_all = 0; #ifndef NO_RFC2640_SUPPORT g_utf8 = 0; #endif } void SetControlConnectionSecurity(void) { if (!g_pControlConnectionSecurity) { if (0 != FixServerCertificate()) { reply(421, "Certificate creation failed"); remove_unauthorized_client(); exit(0); } } reply(234, "Authentication method accepted"); /* * Reset transfer parameters after the AUTH command, including (but * are not limited to): user identity, default data ports, TYPE, * STRU, MODE, and current working directory. */ ReinitSession(); if (!g_pControlConnectionSecurity) { g_pControlConnectionSecurity = SecureServerConnection(fileno(stdin), fileno(stdout)); if (!g_pControlConnectionSecurity) { // fail - terminate FTP control connection remove_unauthorized_client(); exit(0); } } } void SetDataConnectionSecurity(void) { reply(200, "Data channel will be secured"); g_bSecureDataConnection = 1; } static int secure_vprintf(char *fmt, va_list ap, struct SecuredConnection *sec) { // Due to a bug in fireftp client we have to send it within a single SSL_write() call !! int siz = 60; char *buf = (char *)malloc(siz); if (!buf) return -1; int n = vsnprintf(buf, siz, fmt, ap); if (n >= siz) { free(buf); siz = n+1; if (siz > 8192) return -1; buf = (char *)malloc(siz); if (!buf) return -1; n = vsnprintf(buf, siz, fmt, ap); if (n >= siz) { free(buf); return -1; } } clear_ssl_errors(); int ret = SSL_write(sec->ssl, buf, n); if (ret != n) { Log(("SSL_write failed n=%d ret=%d", n, ret)); Log_SSL_Errors(); } free(buf); return n; } #endif // NO_FTPS_SUPPORT // ----------- void control_putc(char c) { #ifndef NO_FTPS_SUPPORT if (g_pControlConnectionSecurity) { clear_ssl_errors(); int ret = SSL_write(g_pControlConnectionSecurity->ssl, &c, 1); if (ret != 1) { Log(("SSL_write control putc failed ret=%d", ret)); Log_SSL_Errors(); } } else #endif putc(c, stdout); } void control_putchar(char c) { control_putc(c); } int control_vprintf(char *fmt, va_list ap) { #ifndef NO_FTPS_SUPPORT if (g_pControlConnectionSecurity) { return secure_vprintf(fmt, ap, g_pControlConnectionSecurity); } else #endif return vprintf(fmt, ap); } int control_printf(char *fmt, ...) { va_list ap; va_start(ap, fmt); int ret = control_vprintf(fmt, ap); va_end(ap); return ret; } static ssize_t control_write(const void *buf, size_t count) { #ifndef NO_FTPS_SUPPORT if (g_pControlConnectionSecurity) { clear_ssl_errors(); return SSL_write(g_pControlConnectionSecurity->ssl, buf, count); } else #endif return fwrite(buf, 1, count, stdout); } void control_flush(void) { #ifndef NO_FTPS_SUPPORT if (g_pControlConnectionSecurity) { BIO *bio = SSL_get_wbio(g_pControlConnectionSecurity->ssl); if (bio) BIO_flush(bio); } else #endif fflush(stdout); } int control_ferror(void) { return ferror(stdin); } // ----------- void data_putc(char c) { #ifndef NO_FTPS_SUPPORT if (g_pDataConnectionSecurity) { clear_ssl_errors(); SSL_write(g_pDataConnectionSecurity->ssl, &c, 1); } else #endif putc(c, g_data_fp); } ssize_t data_write(const void *buf, size_t count) { #ifndef NO_FTPS_SUPPORT if (g_pDataConnectionSecurity) { clear_ssl_errors(); return SSL_write(g_pDataConnectionSecurity->ssl, buf, count); } #endif int fd = fileno(g_data_fp); int ret = write(fd, buf, count); // if (ret != count) { //Log(("data_write failed g_data_fp=%p fd=%d ret=%d count=%u", g_data_fp, fd, ret, count)); // } return ret; } ssize_t data_read(void *buf, size_t count) { #ifndef NO_FTPS_SUPPORT if (g_pDataConnectionSecurity) { clear_ssl_errors(); int ret = SSL_read(g_pDataConnectionSecurity->ssl, buf, count); Log(("data_read secure count=%u ret=%d", count, ret)); if (ret <= 0) { Log_SSL_Errors(); } return (ssize_t)ret; } else #endif return read(fileno(g_data_fp), buf, count); } void data_flush(void) { #ifndef NO_FTPS_SUPPORT if (g_pDataConnectionSecurity) { BIO *bio = SSL_get_wbio(g_pDataConnectionSecurity->ssl); if (bio) BIO_flush(bio); } else #endif fflush(g_data_fp); } void data_fclose(void) { Log(("data_fclose g_data_fp=%d", g_data_fp)); if (!g_data_fp) return; #ifndef NO_FTPS_SUPPORT if (g_pDataConnectionSecurity) { Log(("data_fclose calling SSL_shutdown")); int ret = SSL_shutdown(g_pDataConnectionSecurity->ssl); Log(("data_fclose SSL_shutdown ret=%d", ret)); // for fireftp we cant wait for "close notify" from peer // we would wait forever, so we must not call SSL_shutdown() // again if 0 == ret. // its ok to not wait, cause we close the TCP session anyway. #if 0 if (0 == ret) { ret = SSL_shutdown(g_pDataConnectionSecurity->ssl); // see manpage of SSL_shutdown() Log(("data_fclose SSL_shutdown second ret=%d", ret)); } #endif SSL_free(g_pDataConnectionSecurity->ssl); SSL_CTX_free(g_pDataConnectionSecurity->ctx); free(g_pDataConnectionSecurity); g_pDataConnectionSecurity = 0; } #endif Log(("data_fclose calling fclose")); fclose(g_data_fp); g_data_fp = 0; Log(("data_fclose ret")); } int data_get_fd(void) { if (data < 0 || 0 == g_data_fp || data != fileno(g_data_fp)) { Log(("data_get_fd ERROR data=%u g_data_fp=%p", data, g_data_fp)); } return data; } int data_ferror(void) { return ferror(g_data_fp); } int data_getc(void) { #ifndef NO_FTPS_SUPPORT if (g_pDataConnectionSecurity) { unsigned char c; int ret = SSL_read(g_pDataConnectionSecurity->ssl, &c ,1); if (ret != 1) return EOF; return (int)c; } else #endif return getc(g_data_fp); } int data_vprintf(char *fmt, va_list ap) { #ifndef NO_FTPS_SUPPORT if (g_pDataConnectionSecurity) { return secure_vprintf(fmt, ap, g_pDataConnectionSecurity); } else #endif return vfprintf(g_data_fp, fmt, ap); } int data_printf(char *fmt, ...) { va_list ap; va_start(ap, fmt); int ret = data_vprintf(fmt, ap); va_end(ap); return ret; } int pattern_too_complex(const char *pattern) { int prev_star = 0; int star = 0; int escape = 0; const char *p = pattern; while(*p != '\0') { int c = *p++; if (escape) { escape = 0; } else if (c == '\\') { escape = 1; } else if (c == '*') { if (prev_star) { Log(("pattern %s is BAD", pattern)); return (0 == 0); } star = 1; } else if (c == '/') { if (star) prev_star = 1; star = 0; } } Log(("pattern %s is ok", pattern)); return 0; } static unsigned long long BYTE_TO_MiB(unsigned long long b, int roundup) { if (roundup) b += 1024*1024-1; b /= 1024; b /= 1024; return b; } void quota(void) { unsigned long long avail = 0, total = 0; struct statvfs sb; int ret = statvfs(".", &sb); if (ret == 0) { total = (unsigned long long)sb.f_frsize * (unsigned long long)sb.f_blocks; if (0 == (sb.f_flag & ST_RDONLY)) { char *real; if (virtual_is_write_access(".", &real)) { avail = (unsigned long long)sb.f_bsize * (unsigned long long)sb.f_bavail; } } } reply(200, "Available: %llu (%lluMiB) Limit: %llu (%lluMiB)", avail, BYTE_TO_MiB(avail, 0), total, BYTE_TO_MiB(total, 1)); }