/* auth.c Handle user authentication and setup of user options Copyright (C) 1999 Beau Kuiper This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "ftpd.h" #include "auth.h" extern FTPCMD mainftpcmd[]; extern FTPCMD siteftpcmd[]; PERMINFO authtypes[] = { { "unix", &unixauth_commands }, { "anonymous", &anonauth_commands }, { "internal", &internalauth_commands }, { "disabled", &disableauth_commands }, #ifdef HAVE_PAM_START { "pam", &pamauth_commands }, #endif { NULL, NULL }, }; /* this converts a config file string to discribe a multiline response to a real string. This does not need a new string to copy it to */ void converttorealstr(char *strbuf) { char *pos1, *pos2; pos1 = pos2 = strbuf; while(*pos1 != 0) { if (*pos1 == '/') { pos1++; switch(*pos1) { case 'n': *pos2 = '\n'; pos2++; break; case 's': *pos2 = ' '; pos2++; break; case 't': *pos2 = '\t'; pos2++; break; case '/': *pos2 = '/'; pos2++; break; default: *pos2 = '/'; pos2++; pos1--; break; } } else { *pos2 = *pos1; pos2++; } pos1++; } *pos2 = 0; } /* check an encrypted password against a plain text password */ int chkpassword(char *encrypass, char *password) { char *pass2; int result = FALSE; pass2 = crypt(password, encrypass); if (strcmp(pass2, encrypass) == 0) result = TRUE; /* destroy the passwords so it cannot be seen if muddleftpd crashes! */ memset(password, 0, strlen(password)); memset(pass2, 0, strlen(pass2)); return(result); } /* Make sure the user isn't trying to exploit the nature of the ftp server */ int checkexploits(FTPSTATE *peer) { int count; int namelen = strlen(peer->username); if (strlen(peer->username) > MAXNAMELEN) return(TRUE); for(count = 0; count < namelen; count++) { switch ((peer->username)[count]) { case '?': case '*': case '/': case '[': case ']': case '\\': case '!': case ':': case '^': return(TRUE); } } return(FALSE); } int authlogmsg(char *directive, char *groupname) { log_giveentry(MYLOG_INFO, NULL, safe_snprintf("directive %s is not correct in group %s", directive, groupname)); return(FALSE); } int checkabsdir(char *dir) { if (dir == NULL) return(TRUE); return(dir[0] == '/'); } void clearauth(FTPSTATE *peer) { freeifnotnull(peer->pwd); peer->pwd = NULL; freeifnotnull(peer->homedir); freeifnotnull(peer->basedir); if (peer->passiveport) select_delfd(peer->sel, peer->passiveport); peer->passiveport = 0; freeifnotnull(peer->renameoldname); peer->renameoldname = NULL; peer->restartpos = 0; peer->binary = TRUE; peer->remoteport = peer->connport - 1; freeifnotnull(peer->logindump); freeifnotnull(peer->cwddump); freeifnotnull(peer->busydump); freeifnotnull(peer->quitdump); freeifnotnull(peer->logindumpdata); freeifnotnull(peer->cwddumpdata); freeifnotnull(peer->busydumpdata); freeifnotnull(peer->quitdumpdata); freeifnotnull(peer->cmddisableset); freeifnotnull(peer->sitedisableset); peer->sitedisableset = disableset_create(); peer->cmddisableset = disableset_create(); peer->umask = peer->vserver->umask; if (peer->acldata) acllist_dest(peer->acldata); peer->gidt_asgid = config->gidt_nobodygid; peer->uidt_asuid = config->uidt_nobodyuid; peer->maxusers = peer->vserver->maxusers; peer->maxtimeout = peer->vserver->timeout; peer->timeout = peer->vserver->timeout; peer->chmodable = FALSE; peer->jailenabled = FALSE; peer->droproot = FALSE; peer->fakemode = -1; peer->accessdevices = FALSE; peer->fxpallow = FALSE; peer->maxtranspd = 0; freeifnotnull(peer->fakename); freeifnotnull(peer->fakegroup); if (peer->ratioinfo) ratio_finish(peer->ratioinfo); freeifnotnull(peer->supgids); } char *mkconfrealstr(int sectionid, char *item, char *defaul) { char *setting; loadstrfromconfig(config->configfile, sectionid, item, &(setting), defaul); if (setting == NULL) return(NULL); setting = strdupwrapper(setting); converttorealstr(setting); return(setting); return(setting); } char *mktokconfstr(TOKENSET *tset, int sectionid, char *item, char *defaul) { char *setting; loadstrfromconfig(config->configfile, sectionid, item, &(setting), defaul); if (setting == NULL) return(NULL); setting = strdupwrapper(setting); setting = tokenset_apply(tset, setting, FALSE); return(setting); } int mktokconfint(TOKENSET *tset, int sectionid, char *item, char *format, char *defaultstr, int defaultint) { char *setting; int ret; loadstrfromconfig(config->configfile, sectionid, item, &(setting), defaultstr); if (setting == NULL) return(defaultint); setting = strdupwrapper(setting); setting = tokenset_apply(tset, setting, FALSE); if (sscanf(setting, format, &ret) != 1) ret = defaultint; freewrapper(setting); return(ret); } void buildtokset(TOKENSET *tset, PERMSTRUCT *s, void *a) { gid_t *list; uid_t inuid; gid_t ingid; int result; char *str; if (s->getuseruid) { inuid = s->getuseruid(a); result = (int)inuid; str = safe_snprintf("%d", result); tokenset_settoken(tset, 'u', str); } if (s->getusergid) { ingid = s->getusergid(a); result = (int)ingid; str = safe_snprintf("%d", result); tokenset_settoken(tset, 'g', str); } if (s->gethomedir) tokenset_settoken(tset, 'h', strdupwrapper(s->gethomedir(a))); if (s->getrootdir) tokenset_settoken(tset, 'r', strdupwrapper(s->getrootdir(a))); if (s->getusersupgid) { list = s->getusersupgid(a); tokenset_settoken(tset, 'G', makegidliststr(list)); freewrapper(list); } } void setupacls(FTPSTATE *peer, TOKENSET *tset, int section, char *funcname, int aclfunc) { char *data; int occur = 1; int escapetoks = (aclfunc != 0); while((data = getconfigdata(config->configfile, section, funcname, occur))) { char *pos; data = tokenset_apply(tset, strdupwrapper(data), escapetoks); if ((pos = strrchr(data, ':')) != NULL) { /* this will temporaryly damage config data. But I fix it afterwards :) */ *pos = 0; acllist_add(peer->acldata, data, pos + 1, aclfunc); } freewrapper(data); occur++; } } int transferconfig(FTPSTATE *peer, TOKENSET *tset, int section) { int occur, result = TRUE; char *data; int intdata; peer->timeout = mktokconfint(tset, section, "timeout", "%d", NULL, peer->vserver->timeout); peer->maxtimeout = peer->timeout; /* get uid setting */ loadstrfromconfig(config->configfile, section, "uid", &(data), "%u"); data = strdupwrapper(data); data = tokenset_apply(tset, data, FALSE); if (sscanf(data, "%d", &intdata) != 1) { /* try for username then */ struct passwd *pwdent; pwdent = getpwnam(data); if (pwdent) peer->uidt_asuid = pwdent->pw_uid; else peer->uidt_asuid = config->uidt_nobodyuid; } else { peer->uidt_asuid = (uid_t)intdata; } freewrapper(data); /* get gid setting */ loadstrfromconfig(config->configfile, section, "gid", &(data), "%g"); data = strdupwrapper(data); data = tokenset_apply(tset, data, FALSE); if (sscanf(data, "%d", &intdata) != 1) { /* if it starts with a "!" mark, get gid of username */ if (data[0] == '!') { struct passwd *pwdent; pwdent = getpwnam(data+1); if (pwdent) peer->gidt_asgid = pwdent->pw_gid; else peer->gidt_asgid = config->gidt_nobodygid; } else { /* try for groupname then */ struct group *grent; grent = getgrnam(data); if (grent) peer->gidt_asgid = grent->gr_gid; else peer->gidt_asgid = config->gidt_nobodygid; } } else { peer->gidt_asgid = (gid_t)intdata; } freewrapper(data); if ((int)peer->uidt_asuid == 0) peer->uidt_asuid = config->uidt_nobodyuid; if ((int)peer->gidt_asgid == 0) peer->gidt_asgid = config->gidt_nobodygid; peer->homedir = mktokconfstr(tset, section, "homedir", "%h"); if (!checkabsdir(peer->homedir)) result = authlogmsg("homedir", peer->groupname); peer->basedir = mktokconfstr(tset, section, "rootdir", "%r"); if (!checkabsdir(peer->basedir)) result = authlogmsg("rootdir", peer->groupname); peer->umask = mktokconfint(tset, section, "umask", "%o", NULL, peer->vserver->umask) & 0777; peer->chmodable = mktokconfint(tset, section, "chmoding", "%d", NULL, 0); peer->jailenabled = mktokconfint(tset, section, "userjail", "%d", NULL, 0); peer->maxusers = mktokconfint(tset, section, "maxusers", "%d", NULL, config->defaults->maxusers); peer->maxtranspd = mktokconfint(tset, section, "maxspeed", "%d", NULL, 0); peer->maxtranspd_down = mktokconfint(tset, section, "maxspeeddown", "%d", NULL, 0); peer->maxtranspd_up = mktokconfint(tset, section, "maxspeedup", "%d", NULL, 0); peer->chroot = mktokconfint(tset, section, "chroot", "%d", NULL, FALSE); peer->droproot = mktokconfint(tset, section, "droproot", "%d", NULL, FALSE); peer->nicevalue = mktokconfint(tset, section, "nice", "%d", NULL, 0); peer->accessdevices = mktokconfint(tset, section, "devaccess", "%d", NULL, FALSE); peer->realdir = mktokconfint(tset, section, "realdir", "%d", NULL, FALSE); peer->fxpallow = mktokconfint(tset, section, "fxpallow", "%d", NULL, FALSE); peer->fakemode = mktokconfint(tset, section, "fakemode", "%o", NULL, -1); peer->fakename = mktokconfstr(tset, section, "fakename", NULL); peer->fakegroup = mktokconfstr(tset, section, "fakegroup", NULL); peer->cwddumpdata = mkconfrealstr(section, "cddumpdata", NULL); peer->logindumpdata = mkconfrealstr(section, "welcomedumpdata", NULL); peer->quitdumpdata = mkconfrealstr(section, "quitdumpdata", NULL); peer->busydumpdata = mkconfrealstr(section, "busydumpdata", NULL); if (!peer->cwddumpdata) peer->cwddump = mktokconfstr(tset, section, "cddump", NULL); if (!peer->logindumpdata) { peer->logindump = mktokconfstr(tset, section, "welcome", NULL); if (!checkabsdir(peer->logindump)) result = authlogmsg("welcome", peer->groupname); } if (!peer->quitdumpdata) { peer->quitdump = mktokconfstr(tset, section, "quitdump", NULL); if (!checkabsdir(peer->quitdump)) result = authlogmsg("quitdump", peer->groupname); } if (!peer->busydumpdata) { peer->busydump = mktokconfstr(tset, section, "busydump", NULL); if (!checkabsdir(peer->busydump)) result = authlogmsg("busydump", peer->groupname); } peer->acldata = acllist_create(); setupacls(peer, tset, section, "fnaccess", 1); setupacls(peer, tset, section, "pfnaccess", 2); setupacls(peer, tset, section, "access", 0); occur = 1; while((data = getconfigdata(config->configfile, section, "cmdoff", occur++))) disableset_disablecmd(peer->cmddisableset, mainftpcmd, data); occur = 1; while((data = getconfigdata(config->configfile, section, "sitecmdoff", occur++))) disableset_disablecmd(peer->sitedisableset, siteftpcmd, data); if (mktokconfint(tset, section, "ratios", "%d", NULL, FALSE)) peer->ratioinfo = ratio_loaduser(peer->username, section); else peer->ratioinfo = NULL; data = mktokconfstr(tset, section, "supgid", "%G"); peer->supgids = parsegidlist(data); freewrapper(data); return(result); } int auth_getcursectionid(FTPSTATE *peer) { return(getsectionid(config->configfile, peer->groupname)); } void *auth_getconfigcache(void) { return(config->configfile); } char *auth_getremotename(FTPSTATE *peer) { return(peer->hostname); } unsigned int *auth_getremoteip(FTPSTATE *peer) { return(&(peer->remoteip)); } PERMSTRUCT *configauthmethod(char *group, char *authmethod) { int count = 0; static PERMSTRUCT dp; #ifdef HAVE_DLOPEN if (authmethod[0] == '/') { /* assume it is a libary module */ #ifndef RTLD_NOW dp.handle = dlopen(authmethod, 1); #else dp.handle = dlopen(authmethod, RTLD_NOW); #endif if (dp.handle == NULL) { log_giveentry(MYLOG_INFO, NULL, safe_snprintf("could not open or link '%s', reason: %s", authmethod, dlerror())); return(NULL); } dp.gethandle = dlsym(dp.handle, "gethandle"); dp.freehandle = dlsym(dp.handle, "freehandle"); dp.chkpasswd = dlsym(dp.handle, "chkpasswd"); dp.gethomedir = dlsym(dp.handle, "gethomedir"); dp.getrootdir = dlsym(dp.handle, "getrootdir"); dp.getuseruid = dlsym(dp.handle, "getuseruid"); dp.getusergid = dlsym(dp.handle, "getusergid"); dp.getusersupgid = dlsym(dp.handle, "getusersupgid"); /* make sure the required functions exist and are defined. */ if (!dp.gethandle || !dp.freehandle || !dp.chkpasswd) { log_giveentry(MYLOG_INFO, NULL, safe_snprintf("linked module '%s' incomplete", authmethod)); dlclose(dp.handle); return(NULL); } return(&dp); } #endif while(authtypes[count].authname != NULL) { if (strcasecmp(authmethod, authtypes[count].authname) == 0) return(authtypes[count].authstruct); count++; } log_giveentry(MYLOG_INFO, NULL, safe_snprintf("authmethod '%s' does not exist, used by group %s", authmethod, group)); return(NULL); } /* this finds the section the user belongs to */ char *auth_getgroup(FTPSTATE *peer, TOKENSET *tset, int *section, PERMSTRUCT **rprog, void **rinf) { int count = 0; int sectionid, result; char *mode, *authuser; char **grouplist = peer->vserver->grouplist; PERMSTRUCT *am = NULL; void *handle = NULL; while(grouplist[count] != NULL) { sectionid = getsectionid(config->configfile, grouplist[count]); if (sectionid == -1) { /* stop authenticating if error occurs */ log_giveentry(MYLOG_INFO, NULL, safe_snprintf("group %s does not exist", grouplist[count])); return(NULL); } else { authuser = mktokconfstr(tset, sectionid, "authuser", peer->username); mode = getconfigdata(config->configfile, sectionid, "authmethod", 1); if (mode) am = configauthmethod(grouplist[count], mode); if (am) { /* Check ip */ IPACLLIST *clist; clist = ipacllist_new(config->configfile, sectionid, "ipacl"); if (!user_allowed(clist, peer->remoteip, peer->hostname)) am = NULL; ipacllist_destroy(clist); } else return(NULL); if (am) /* Check username */ if (!checknamelist(config->configfile, sectionid, peer->username)) am = NULL; if (am) { /* now see if valid user */ peer->groupname = grouplist[count]; handle = am->gethandle(peer, tset, authuser, &result); /* if the authentication module returns ERROR, return now! */ if (result == AUTH_ERROR) { freewrapper(authuser); return(NULL); } } if (handle) { *section = sectionid; *rprog = (void *)am; *rinf = handle; freewrapper(authuser); return(grouplist[count]); } freewrapper(authuser); } count++; } return(NULL); } char *setuseropts(FTPSTATE *peer, char *password) { int section; void *authhandle = NULL; char *errstr = NULL; PERMSTRUCT *authcmd; TOKENSET *tset = tokenset_new(); /* Check possible exploits here! */ if (checkexploits(peer)) goto authend; peer->loggedin = FALSE; tokenset_settoken(tset, 'U', strdupwrapper(peer->username)); tokenset_settoken(tset, 'v', strdupwrapper(peer->vserver->sectionname)); tokenset_settoken(tset, 'V', strdupwrapper(peer->vserver->vhostname)); peer->groupname = auth_getgroup(peer, tset, §ion, &authcmd, &authhandle); if (peer->groupname == NULL) goto authend; if (!authcmd->chkpasswd(authhandle, password, &errstr)) goto authend; clearauth(peer); buildtokset(tset, authcmd, authhandle); if (!transferconfig(peer, tset, section)) goto authend; peer->loggedin = TRUE; authend: tokenset_finish(tset); if (authhandle) { authcmd->freehandle(authhandle); #ifdef HAVE_DLOPEN if (authcmd->handle) dlclose(authcmd->handle); #endif } if (peer->loggedin) { freeifnotnull(errstr); return(NULL); } else { if (!errstr) errstr = strdupwrapper("Bad password"); return(errstr); } }