/* 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);
}
}
syntax highlighted by Code2HTML, v. 0.9.1