#include #include #include #include #include #include #include #include #include #include #include #define FTP_NAMES #include #include #include #include #include #include #include #include #include #include #include #ifdef SYSSYSLOG #include #else #include #endif #include #include "config.h" #include "extensions.h" #ifdef SHADOW_PASSWORD #include #endif #include "pathnames.h" #ifndef MAXHOSTNAMELEN #define MAXHOSTNAMELEN 64 #endif char * eft_access = _PATH_FTPACCESS; char remoteaddr [MAXHOSTNAMELEN]; char remotehost [MAXHOSTNAMELEN]; /* nameserved==1: remoteaddr!=remotehost */ /* nameserved==0: remoteaddr==remotehost */ int nameserved = 1; char guestpw[MAXHOSTNAMELEN]; char privatepw[MAXHOSTNAMELEN]; int log_commands = 0; int logging = 0; /* File transfer logging */ int xferlog = 0; int log_outbound_xfers = 0; int log_incoming_xfers = 0; /* --he char logfile[MAXPATHLEN];*/ char * logfile = _PATH_XFERLOG; int guest; int anonymous = 1; /* Make use of /etc/eftaccess ? */ int use_accessfile = 0; /* How many attempts for the user to login ? */ int lgi_failure_threshold = 5; /* The password structure of the logged in user */ struct passwd *pw; /* The options, if (un)compressing etc. is allowed */ int mangleopts = 0; char autherrmsg [256]; #define SPT_NONE 0 /* don't use it at all */ #define SPT_REUSEARGV 1 /* cover argv with title information */ #define SPT_BUILTIN 2 /* use libc builtin */ #define SPT_PSTAT 3 /* use pstat(PSTAT_SETCMD, ...) */ #define SPT_PSSTRINGS 4 /* use PS_STRINGS->... */ #define SPT_SYSMIPS 5 /* use sysmips() supported by NEWS-OS 6 */ #define SPT_SCO 6 /* write kernel u. area */ #define SPACELEFT(buf, ptr) (sizeof buf - ((ptr) - buf)) #ifdef HAVE_DIRENT #include #else #include #endif #ifndef MAXHOSTNAMELEN #define MAXHOSTNAMELEN 64 /* may be too big */ #endif #ifndef TRUE #define TRUE 1 #endif #ifndef FALSE #define FALSE !TRUE #endif extern int errno; extern int pidfd ; extern char *ctime(const time_t *); #ifndef NO_CRYPT_PROTO extern char *crypt(const char *, const char *); #endif extern FILE *ftpd_popen(char *program, char *type, int closestderr), *fopen(const char *, const char *), *freopen(const char *, const char *, FILE *); extern int ftpd_pclose(FILE *iop), fclose(FILE *); extern char *wu_getline(), *realpath(const char *pathname, char *result); extern char version[]; extern char *home; /* pointer to home directory for glob */ extern char cbuf[]; extern off_t restart_point; #ifdef VIRTUAL int virtual_mode=0; char virtual_root[MAXPATHLEN]; char virtual_banner[MAXPATHLEN]; #endif #ifdef LOG_FAILED #define MAXUSERNAMELEN 32 char the_user[MAXUSERNAMELEN]; #endif SIGNAL_TYPE lostconn(int sig); SIGNAL_TYPE randomsig(int sig); SIGNAL_TYPE myoob(int sig); FILE *getdatasock(char *mode), *dataconn(char *name, off_t size, char *mode); void reply(int, char *fmt, ...); void lreply(int, char *fmt, ...); #ifdef NEED_SIGFIX extern sigset_t block_sigmask; /* defined in sigfix.c */ #endif struct aclmember *entry = NULL; void end_login(void); void send_data(FILE *, FILE *, off_t); void dolog(struct sockaddr_in *); static char ttyline[20]; int checkuser(char *name) { register FILE *fd; register char *p; char line[BUFSIZ]; if ((fd = fopen(_PATH_FTPUSERS, "r")) != NULL) { while (fgets(line, sizeof(line), fd) != NULL) if ((p = strchr(line, '\n')) != NULL) { *p = '\0'; if (line[0] == '#') continue; if (strcmp(line, name) == 0) { (void) fclose(fd); return (1); } } (void) fclose(fd); } return (0); } /* Helper function for sgetpwnam(). */ char * sgetsave(char *s) { char *new; new = (char *) malloc(strlen(s) + 1); if (new == NULL) { strcpy (autherrmsg, "Local resource failure: malloc"); return NULL; /* NOTREACHED */ } (void) strcpy(new, s); return (new); } /* Save the result of a getpwnam. Used for USER command, since the data * returned must not be clobbered by any other command (e.g., globbing). */ struct passwd * sgetpwnam(char *name) { static struct passwd save; register struct passwd *p; char *sgetsave(char *s); if ((p = getpwnam(name)) == NULL) return p; if (save.pw_name) free(save.pw_name); if (save.pw_gecos) free(save.pw_gecos); if (save.pw_dir) free(save.pw_dir); if (save.pw_shell) free(save.pw_shell); if (save.pw_passwd) free(save.pw_passwd); save = *p; save.pw_name = sgetsave(p->pw_name); if (save.pw_name==NULL) return NULL; save.pw_passwd = sgetsave(p->pw_passwd); if (save.pw_passwd==NULL) return NULL; #ifdef SHADOW_PASSWORD if (p) { struct spwd *spw; setspent(); if ((spw = getspnam(p->pw_name)) != NULL) { int expired = 0; /*XXX Does this work on all Shadow Password Implementations? */ /* it is supposed to work on Solaris 2.x*/ time_t now; long today; now = time((time_t*) 0); today = now / (60*60*24); if ((spw->sp_expire > 0) && (spw->sp_expire < today)) expired++; if ((spw->sp_max > 0) && (spw->sp_lstchg > 0) && (spw->sp_lstchg + spw->sp_max < today)) expired++; free(save.pw_passwd); save.pw_passwd = sgetsave(expired?"":spw->sp_pwdp); if (save.pw_passwd==NULL) return NULL; } /* Don't overwrite the password if the shadow read fails, getpwnam() is NIS aware but getspnam() is not. */ /* Shadow passwords are optional on Linux. --marekm */ #if !defined(LINUX) && !defined(UNIXWARE) else{ free(save.pw_passwd); save.pw_passwd = sgetsave(""); if (save.pw_passwd==NULL) return NULL; } #endif /* marekm's fix for linux proc file system shadow passwd exposure problem */ endspent(); } #endif save.pw_gecos = sgetsave(p->pw_gecos); if (save.pw_gecos==NULL) return NULL; save.pw_dir = sgetsave(p->pw_dir); if (save.pw_dir==NULL) return NULL; save.pw_shell = sgetsave(p->pw_shell); if (save.pw_shell==NULL) return NULL; #ifdef M_UNIX ret = &save; DONE: endpwent(); #endif return(&save); } #ifdef SKEY /* * From Wietse Venema, Eindhoven University of Technology. */ /* skey_challenge - additional password prompt stuff */ char *skey_challenge(char *name, struct passwd *pwd, int pwok) { static char buf[128]; char sbuf[40]; struct skey skey; /* Display s/key challenge where appropriate. */ if (pwd == NULL || skeychallenge(&skey, pwd->pw_name, sbuf)) sprintf(buf, "Password required for %s.", name); else sprintf(buf, "%s %s for %s.", sbuf, pwok ? "allowed" : "required", name); return (buf); } #endif int login_attempts; /* number of failed login attempts */ int askpasswd; /* had user command, ask for passwd */ int wuftp_check_user (char *user, char *passw, char *isdnno) { char *shell; char *getusershell(); int passwarn = 0; int rval = 1; char *xpasswd, *salt; if(access_init()) return 0; strcpy (remotehost, isdnno); strcpy (remoteaddr, isdnno); nameserved = 0; anonymous = 0; acl_remove (); if (!strcasecmp(user, "ftp") || !strcasecmp(user, "anonymous") || !user[0]) { struct aclmember *entry = NULL; int machineok=1; if (checkuser("ftp") || checkuser("anonymous")) { sprintf (autherrmsg, "User %s access denied.", user); syslog(LOG_NOTICE, "EFT LOGIN REFUSED (eft in %s) FROM %s, %s", _PATH_FTPUSERS, remotehost, user); return 0; /* ** Algorithm used: ** - if no "guestserver" directive is present, ** anonymous access is allowed, for backward compatibility. ** - if a "guestserver" directive is present, ** anonymous access is restricted to the isdn-numbers listed. ** ** the format of the "guestserver" line is ** guestserver [ []] ** that is, "guestserver" will forbid anonymous access on all machines ** while "guestserver 40* 30*" will allow anonymous access on ** all callers coming from Hamburg or Berlin. ** */ } else if (getaclentry("guestserver", &entry) && entry->arg[0] && (int)strlen(entry->arg[0]) > 0) { int machinecount = 0; machineok=0; for (machinecount=0; entry->arg[machinecount] && (entry->arg[machinecount])[0]; machinecount++) { if (!fnmatch(entry->arg[machinecount], isdnno,0)) { machineok++; break; } } } if (!machineok) { strcpy (autherrmsg, "Guest login not allowed from given number."); syslog(LOG_NOTICE, "EFT LOGIN REFUSED (number not in guestservers) FROM %s, %s", remotehost, user); return 0; } else if ((pw = sgetpwnam("ftp")) != NULL) { anonymous = 1; /* for the access_ok call */ if (access_ok(530) < 1) { sprintf (autherrmsg, "User %s access denied.", user); syslog(LOG_NOTICE, "EFT LOGIN REFUSED (access denied) FROM %s, %s", remotehost, user); return 0; } else { askpasswd = 1; if (use_accessfile) acl_setfunctions(); } } else { sprintf (autherrmsg, "User %s unknown.", user); syslog(LOG_NOTICE, "EFT LOGIN REFUSED (ftp not in /etc/passwd) FROM %s, %s", remotehost, user); return 0; } } #ifdef ANON_ONLY /* H* fix: define the above to completely DISABLE logins by real users, despite ftpusers, shells, or any of that rot. You can always hang your "real" server off some other port, and access-control it. */ else { /* "ftp" or "anon" -- MARK your conditionals, okay?! */ sprintf (autherrmsg, "User %s unknown.", user); syslog (LOG_NOTICE, "EFT LOGIN REFUSED (not anonymous) FROM %s, %s", remotehost, user); return 0; } /* fall here if username okay in any case */ #endif /* ANON_ONLY */ if ((pw = sgetpwnam(user)) != NULL) { char *cp; if ((shell = pw->pw_shell) == NULL || *shell == 0) shell = _PATH_BSHELL; while ((cp = getusershell()) != NULL) if (strcmp(cp, shell) == 0) break; endusershell(); if (cp == NULL || checkuser(user)) { sprintf (autherrmsg, "User %s access denied.", user); syslog(LOG_NOTICE, "EFT LOGIN REFUSED (bad shell or username in %s) FROM %s, %s", _PATH_FTPUSERS, remotehost, user); pw = (struct passwd *) NULL; return 0; } /* if user is a member of any of the guestgroups, cause a chroot() */ /* after they log in successfully */ if (use_accessfile) /* see above. _H*/ guest = acl_guestgroup(pw); } if (access_ok(530) < 1) { sprintf (autherrmsg, "User %s access denied.", user); syslog(LOG_NOTICE, "EFT LOGIN REFUSED (access denied) FROM %s, %s", remotehost, user); return 0; } else if (use_accessfile) /* see above. _H*/ acl_setfunctions(); #ifdef SKEY #ifdef SKEY_NAME /* this is the old way, but freebsd uses it */ pwok = skeyaccess(user, NULL, remotehost, remoteaddr); #else /* this is the new way */ pwok = skeyaccess(pw, NULL, remotehost, remoteaddr); #endif #else #endif askpasswd = 1; /* Delay before reading passwd after first failed attempt to slow down * passwd-guessing programs. */ if (login_attempts) sleep((unsigned) login_attempts); if (!askpasswd) return 1; /* Hey man, we got it! */ if (!anonymous) { /* "ftp" is only account allowed no password */ *guestpw = '\0'; if (pw == NULL) salt = "xx"; else salt = pw->pw_passwd; xpasswd = crypt(passw, salt); /* The strcmp does not catch null passwords! */ if (pw !=NULL && *pw->pw_passwd != '\0' && strcmp(xpasswd, pw->pw_passwd) == 0) { rval = 0; } if(rval){ strcpy (autherrmsg, "Login incorrect."); #ifdef LOG_FAILED if (! strcmp (passw, "NULL")) syslog(LOG_NOTICE, "REFUSED \"NULL\" from %s, %s", remotehost, the_user); else syslog(LOG_INFO, "failed login from %s, %s", remotehost, the_user); #endif acl_remove(); pw = NULL; if (++login_attempts >= lgi_failure_threshold) { syslog(LOG_NOTICE, "repeated login failures from %s", remotehost); exit(0); } return 0; } /* ANONYMOUS USER PROCESSING STARTS HERE */ } else { char *pwin, *pwout = guestpw; struct aclmember *entry = NULL; int valid; if (!*passw) { strcpy(guestpw, "[none_given]"); } else { int cnt = sizeof(guestpw) - 2; for (pwin = passw; *pwin && cnt--; pwin++) if (!isgraph(*pwin)) *pwout++ = '_'; else *pwout++ = *pwin; } } /* if logging is enabled, open logfile before chroot or set group ID */ if (log_outbound_xfers || log_incoming_xfers) { xferlog = open(logfile, O_WRONLY | O_APPEND | O_CREAT, 0660); if (xferlog < 0) { syslog(LOG_ERR, "cannot open logfile %s: %s", logfile, strerror(errno)); xferlog = 0; } } enable_signaling(); /* we can allow signals once again: kinch */ /* if autogroup command applies to user's class change pw->pw_gid */ if (anonymous && use_accessfile) { /* see above. _H*/ (void) acl_autogroup(pw); guest = acl_guestgroup(pw); /* the new group may be a guest */ anonymous=!guest; } /* END AUTHENTICATION */ login_attempts = 0; /* this time successful */ /* SET GROUP ID STARTS HERE */ (void) setegid((gid_t) pw->pw_gid); (void) initgroups(pw->pw_name, pw->pw_gid); expand_id(); if (anonymous || guest) { char *sp; /* We MUST do a chdir() after the chroot. Otherwise the old current * directory will be accessible as "." outside the new root! */ #ifdef VIRTUAL if (virtual_mode && !guest) { if (pw->pw_dir) free(pw->pw_dir); pw->pw_dir = sgetsave(virtual_root); } #endif /* determine root and home directory */ if ((sp = strstr(pw->pw_dir, "/./")) == NULL) { if (chroot(pw->pw_dir) < 0 || chdir("/") < 0) { strcpy (autherrmsg, "Can't set guest privileges."); goto bad; } } else{ *sp++ = '\0'; if (chroot(pw->pw_dir) < 0 || chdir(++sp) < 0) { strcpy (autherrmsg, "Can't set guest privileges."); goto bad; } } } #ifdef HAVE_SETREUID if (setreuid(-1, (uid_t) pw->pw_uid) < 0) { #else if (seteuid((uid_t) pw->pw_uid) < 0) { #endif strcpy (autherrmsg, "Can't set uid."); goto bad; } if (!anonymous && !guest) { if (chdir(pw->pw_dir) < 0) { if (chdir("/") < 0) { sprintf(autherrmsg, "User %s: can't change directory to %s.", pw->pw_name, pw->pw_dir); goto bad; } else strcpy(autherrmsg, "No directory! Logging in with home=/"); } } if (anonymous) { strcpy (autherrmsg, "Guest login ok, access restrictions apply."); if (logging) syslog(LOG_INFO, "ANONYMOUS FTP LOGIN FROM %s, %s", remotehost, passw); } else { sprintf (autherrmsg, "User %s logged in.%s", pw->pw_name, guest ? " Access restrictions apply." : ""); if (logging) syslog(LOG_INFO, "FTP LOGIN FROM %s [%s], %s", remotehost, remoteaddr, pw->pw_name); } /* anonymous */ /* home = pw->pw_dir; */ /* home dir for globbing */ return 1; bad: /* Forget all about it... */ if (xferlog) close(xferlog); xferlog = 0; delay_signaling(); /* we can't allow any signals while euid==0: kinch */ (void) seteuid((uid_t) 0); pw = NULL; anonymous = 0; guest = 0; return 0; }