/*****************************************************************************\
* Copyright (c) 2002 Pelle Johansson. *
* All rights reserved. *
* *
* This file is part of the moftpd package. Use and distribution of *
* this software is governed by the terms in the file LICENCE, which *
* should have come with this package. *
\*****************************************************************************/
/* $moftpd: user.c 1264 2005-04-06 13:32:27Z morth $ */
#include "system.h"
#include "user.h"
#include "main.h"
#include "utf8fs/file.h"
#include "utf8fs/memory.h"
#include "config.tab.h"
#include "confparse.h"
extern int forkConnections, forker;
extern uid_t unprivUid;
extern gid_t unprivGid;
user_t *new_user(const char *name, void *parent)
{
user_t *res = palloc (sizeof (user_t), parent, NULL);
if (!res)
return NULL;
if (name)
res->name = pstring (name, res);
return res;
}
user_t *copy_user(const user_t *u, const char *newname, void *newparent)
{
user_t *res = palloc (sizeof(user_t), newparent, NULL);
if(!res)
return NULL;
memcpy(res, u, sizeof(user_t));
if (newname)
res->name = pstring (newname, res);
else
res->name = pattach (u->name, res);
res->password = pattach (u->password, res);
res->home = pattach (u->home, res);
res->chroot = pattach (u->chroot, res);
res->dirMsgFile = pattach (u->dirMsgFile, res);
res->access = pattach (u->access, res);
res->access_program = pattach (u->access_program, res);
res->external_login = pattach (u->external_login, res);
#ifdef USE_SQL
res->sqlDirQuery = pattach (u->sqlDirQuery, res);
#endif
res->next = NULL;
return res;
}
user_t *find_passwd_user(const char *name, void *newparent)
{
#ifdef HAVE_GETSPNAM
struct spwd *spwd;
#endif
struct passwd *pwd;
struct group *gr;
user_t *res;
char **gusr;
#ifdef HAVE_GETSPNAM
spwd = getspnam (name);
#endif
pwd = getpwnam (name);
if (!pwd)
return NULL;
res = palloc(sizeof(user_t), newparent, NULL);
if(!res)
{
syslog (LOG_ERR, "palloc: %m");
return NULL;
}
res->name = pstring (name, res);
if(!res->name)
{
syslog (LOG_ERR, "pstring: %m");
pfree(res, newparent);
return NULL;
}
#ifndef HAVE_LIBPAM // PAM is only used if password is NULL.
#ifdef HAVE_GETSPNAM
if (spwd)
res->password = pstring (spwd->sp_pwdp, res);
else
#endif
res->password = pstring (pwd->pw_passwd, res);
#endif
res->home = pstring (pwd->pw_dir, res);
res->uid = pwd->pw_uid;
res->gid = pwd->pw_gid;
setgrent();
while((gr = getgrent()) && res->ngroups < NGROUPS_MAX)
{
for(gusr = gr->gr_mem; *gusr; gusr++)
if(!strcmp(*gusr, name))
res->groups[res->ngroups++] = gr->gr_gid;
}
endgrent();
res->allowLogin = 1;
res->allowSecLogin = 1;
#ifdef HAVE_LIBPAM
res->passwordNeeded = 1;
#else
res->passwordNeeded = strlen(res->password) > 0;
#endif
return res;
}
user_t *find_server_user (const server_t *server, const char *name, void *newparent)
{
user_t *res;
for (res = server->users; res; res = res->next)
if (!strcmp (name, res->name))
{
if (newparent)
return copy_user (res, NULL, newparent);
else
return res;
}
return NULL;
}
#ifdef USE_SQL
user_t *find_sql_user (connection_t *conn, const char *name, void *newparent)
{
int res, b;
const char *field, *val;
user_t *user;
sql_arg_t args[6];
server_t *server = conn->server;
char rhost[NI_MAXHOST], lhost[NI_MAXHOST], rport[NI_MAXSERV], lport[NI_MAXSERV];
int l;
struct sockaddr_storage addr;
if (!server->sqlUserQuery || !server->sql.type)
return NULL;
if (sql_connect (&server->sql, server->sqlHost, server->sqlUser, server->sqlDB,
server->sqlPass, server->sqlCert, server->sqlKey))
return NULL;
l = sizeof(addr);
getsockname(conn->sock, (struct sockaddr*)&addr, &l);
if (getnameinfo ((struct sockaddr*)&addr, l, lhost, sizeof (lhost), lport,
sizeof (lport), NI_NUMERICHOST | NI_NUMERICSERV))
{
strcpy (lhost, "unknown");
strcpy (lport, "0");
}
l = sizeof(addr);
getpeername(conn->sock, (struct sockaddr*)&addr, &l);
if (getnameinfo ((struct sockaddr*)&addr, l, rhost, sizeof (rhost), rport,
sizeof (rport), NI_NUMERICHOST | NI_NUMERICSERV))
{
strcpy (rhost, "unknown");
strcpy (rport, "0");
}
args[0].ch = 'u';
args[0].str = tstring (sql_quote (name));
args[1].ch = 's';
args[1].str = tstring (sql_quote (server->name));
args[2].ch = 'l';
args[2].str = tstring (sql_quote (lhost));
args[3].ch = 'L';
args[3].str = lport;
args[4].ch = 'r';
args[4].str = tstring (sql_quote (rhost));
args[5].ch = 'R';
args[5].str = rport;
res = sql_query (&server->sql, server->sqlUserQuery, 6, args);
if (res < 0)
return NULL;
if (!res)
{
sql_free_result (&server->sql);
return NULL;
}
user = new_user (name, newparent);
if (!user)
{
sql_free_result (&server->sql);
return NULL;
}
user->allowLogin = server->allowLogin;
user->allowSecLogin = server->allowSecLogin;
user->access = pattach (server->access, user);
user->defHardLink = server->defHardLink;
user->maxIdle = server->maxIdle;
user->passwordNeeded = server->passwordNeeded;
user->dirMsgFile = pattach (server->dirMsgFile, user);
#ifdef USE_SQL
user->sqlDirQuery = pattach (server->sqlDirQuery, user);
#endif
// Reset uid and gid, but don't activate in spec_flags.
user->uid = unprivUid;
user->gid = unprivGid;
if (server->chroot)
{
user->chroot = pattach (server->chroot, user);
user->spec_flags |= ufChroot;
}
for (res = 0; (field = sql_fetch_cell (&server->sql, -1, res)); res++)
{
val = sql_fetch_cell (&server->sql, 0, res);
if (!val)
continue;
switch (find_identifier (field))
{
case I_ANONYMOUS:
b = parse_bool (val);
if (b != -1)
{
user->anonymous = b;
user->spec_flags |= ufAnonymous;
if (user->anonymous)
{
user->allowLogin = 1;
user->passwordNeeded = 1;
}
}
break;
case I_HOME:
pfree (user->home, user);
user->home = pstring(val, user);
user->spec_flags |= ufHome;
break;
case I_CHROOT:
pfree (user->chroot, user);
user->chroot = pstring (val, user);
user->spec_flags |= ufChroot;
break;
case I_PASSWORDNEEDED:
b = parse_bool (val);
if (b != -1)
user->passwordNeeded = b;
break;
case I_PASSWORD:
pfree (user->password, user);
user->password = pstring(val, user);
user->passwordNeeded = 1;
user->anonymous = 0;
user->spec_flags |= ufPassword | ufAnonymous;
break;
case I_UID:
b = parse_uid (val);
if (b != INT_MIN)
{
user->spec_flags |= ufUid;
user->uid = b;
}
break;
case I_GID:
b = parse_gid (val);
if (b != INT_MIN)
{
user->spec_flags |= ufGid;
user->gid = b;
}
break;
case I_ALLOWLOGIN:
b = parse_bool (val);
if (b != -1)
user->allowLogin = b;
break;
case I_ALLOWSECLOGIN:
b = parse_bool (val);
if (b != -1)
user->allowSecLogin = b;
break;
case I_ALLOWFOREIGN:
b = parse_bool (val);
if (b != -1)
user->allowForeign = b;
break;
case I_ALLOWOUTOFRANGE:
b = parse_bool (val);
if (b != -1)
user->allowOutOfRange = b;
break;
case I_MAXIDLE:
user->maxIdle = atoi (val);
break;
case I_EXTERNLOGIN:
user->external_login = pstring (full_path (val, NULL, user->home), user);
break;
case I_EXTERNACCESS:
user->access_program = pstring (full_path (val, NULL, user->home), user);
break;
case I_DEFAULTHARDLINK:
b = parse_bool (val);
if (b != -1)
user->defHardLink = b;
break;
case I_DIRECTORYMSGFILE:
pfree (user->dirMsgFile, user);
user->dirMsgFile = pstring (val, user);
break;
case I_SQLDIRQUERY:
#ifdef USE_SQL
pfree (user->sqlDirQuery, user);
user->sqlDirQuery = pstring (val, user);
#endif
break;
case I_ADMIN:
user->adminPrivs = parse_admin_list (val);
break;
default:
syslog (LOG_DEBUG, "Unknown user option from SQL: %s", field);
break;
}
}
sql_free_result (&server->sql);
if (user->chroot)
{
char *tmp = user->chroot;
char *home = user->home;
user_t *pwUser = NULL;
if (!home)
{
pwUser = find_passwd_user (user->name, NULL);
if (pwUser && pwUser->home)
home = pwUser->home;
}
user->chroot = pstring (full_path (user->chroot, NULL, home), user);
pfree (tmp, user);
pfree (pwUser, NULL);
}
return user;
}
void insert_sql_access (connection_t *conn, user_t *user)
{
int res, b, row, col;
const char *field, *val;
access_t *acc;
sql_arg_t args[6];
server_t *server = conn->server;
char rhost[NI_MAXHOST], lhost[NI_MAXHOST], rport[NI_MAXSERV], lport[NI_MAXSERV];
int l;
struct sockaddr_storage addr;
if (!user->sqlDirQuery || !server->sql.type)
return;
if (sql_connect (&server->sql, server->sqlHost, server->sqlUser, server->sqlDB,
server->sqlPass, server->sqlCert, server->sqlKey))
return;
l = sizeof(addr);
getsockname(conn->sock, (struct sockaddr*)&addr, &l);
if (getnameinfo ((struct sockaddr*)&addr, l, lhost, sizeof (lhost), lport,
sizeof (lport), NI_NUMERICHOST | NI_NUMERICSERV))
{
strcpy (lhost, "unknown");
strcpy (lport, "0");
}
l = sizeof(addr);
getpeername(conn->sock, (struct sockaddr*)&addr, &l);
if (getnameinfo ((struct sockaddr*)&addr, l, rhost, sizeof (rhost), rport,
sizeof (rport), NI_NUMERICHOST | NI_NUMERICSERV))
{
strcpy (rhost, "unknown");
strcpy (rport, "0");
}
args[0].ch = 'u';
args[0].str = tstring (sql_quote (user->name));
args[1].ch = 's';
args[1].str = tstring (sql_quote (server->name));
args[2].ch = 'l';
args[2].str = tstring (sql_quote (lhost));
args[3].ch = 'L';
args[3].str = lport;
args[4].ch = 'r';
args[4].str = tstring (sql_quote (rhost));
args[5].ch = 'R';
args[5].str = rport;
res = sql_query (&server->sql, user->sqlDirQuery, 6, args);
if (res < 0)
return;
if (!res)
{
sql_free_result (&server->sql);
return;
}
for (row = 0; row < res; row++)
{
acc = palloc (sizeof (access_t), NULL, NULL);
if (!acc)
continue;
for (col = 0; (field = sql_fetch_cell (&server->sql, -1, col)); col++)
{
val = sql_fetch_cell (&server->sql, row, col);
if (!val)
continue;
if (!strcasecmp (field, "directory"))
{
pfree (acc->path, acc);
acc->path = pstring (full_path (val, NULL, user->home), acc);
}
else switch (find_identifier (field))
{
case I_ALLOW:
b = parse_access_list (val);
acc->deny &= ~b;
acc->require &= ~b;
acc->allow |= b;
break;
case I_DENY:
b = parse_access_list (val);
acc->allow &= ~b;
acc->require &= ~b;
acc->deny |= b;
break;
case I_REQUIRE:
b = parse_access_list (val) & (acEncrypted | acSigned);
acc->allow &= ~b;
acc->deny &= ~b;
acc->require |= b;
break;
case I_HIDDEN:
b = parse_bool (val);
if (b != -1)
acc->hidden = b;
break;
case I_FAKEUSER:
pfree (acc->fakeUser, acc);
acc->fakeUser = pstring(val, acc);
break;
case I_FAKEGROUP:
pfree (acc->fakeGroup, acc);
acc->fakeGroup = pstring(val, acc);
break;
case I_FAKEFILEMODE:
pfree (acc->fakeFile, acc);
acc->fakeFile = pstring(val, acc);
break;
case I_FAKEDIRMODE:
pfree (acc->fakeDir, acc);
acc->fakeDir = pstring(val, acc);
break;
case I_HARDLINK:
b = parse_bool (val);
if (b != -1)
acc->hardlink = b? 1 : -1;
break;
case I_DIRECTORYMSGFILE:
pfree (acc->dirMsgFile, acc);
acc->dirMsgFile = pstring(val, acc);
break;
case I_MASK:
if (acc->numMasks)
acc->masks = prealloc (acc->masks, sizeof (*acc->masks) * (acc->numMasks + 1));
else
acc->masks = palloc (sizeof (*acc->masks), acc, NULL);
if (!acc->masks)
{
pfree (acc, NULL);
continue;
}
acc->masks[acc->numMasks++] = pstring (val, acc->masks);
break;
default:
syslog (LOG_DEBUG, "Unknown directory option from SQL: %s", field);
break;
}
}
if (acc->path)
{
if (acc->numMasks || !user->access || !user->access->numMasks)
{
acc->next = padopt (user->access, user, acc);
user->access = pattach (acc, user);
}
else
{
access_t *p = user->access, *a = NULL;
if (pnparents (p) > 1)
{
a = palloc (sizeof (access_t), user, NULL);
if (!a)
{
pfree (acc, NULL);
continue;
}
pfree (p, user);
user->access = a;
}
// We need to split up the access chain down to the place
// where we should insert.
while (1)
{
if (a)
{
memcpy (a, p, sizeof (*a));
a->path = pattach (p->path, a);
a->masks = pattach (p->masks, a);
a->fakeUser = pattach (p->fakeUser, a);
a->fakeGroup = pattach (p->fakeGroup, a);
a->fakeFile = pattach (p->fakeFile, a);
a->fakeDir = pattach (p->fakeDir, a);
a->next = pattach (p->next, a);
p = a;
}
if (!p->next || !p->next->numMasks)
break;
if (a)
{
a = palloc (sizeof (access_t), p, NULL);
if (!a)
{
pfree (acc, NULL);
continue;
}
p = p->next;
pfree (p->next, p);
p->next = a;
}
else
p = p->next;
}
acc->next = padopt (p->next, p, acc);
p->next = pattach (acc, p);
}
}
else
{
syslog (LOG_ERR, "No \"directory\" column from SQL.");
pfree (acc, user);
}
}
}
#endif
user_t *find_user (connection_t *conn, const char *name, int allowNoLogin, void *newparent)
{
user_t *res;
user_t *suser;
server_t *server = conn->server;
name = server_expand_alias(server, name);
res = find_passwd_user(name, newparent);
#ifdef USE_SQL
suser = find_sql_user (conn, name, newparent);
if (!suser)
#endif
suser = find_server_user(server, name, newparent);
if(!res && !suser)
return NULL;
if(res)
{
res->allowLogin = server->allowLogin;
if(server->chroot)
res->chroot = pattach (server->chroot, res);
if(!server->passwordNeeded)
res->passwordNeeded = 0;
res->access = server->access;
res->maxIdle = server->maxIdle;
}
if(!res)
res = suser;
else if(suser)
{
if(suser->spec_flags & ufPassword)
{
pfree (res->password, res);
res->password = pattach (suser->password, res);
}
if(suser->spec_flags & ufHome)
{
pfree (res->home, res);
res->home = pattach (suser->home, res);
}
if(suser->spec_flags & ufUid)
res->uid = suser->uid;
if(suser->spec_flags & ufGid)
res->gid = suser->gid;
if(suser->spec_flags & (ufUid | ufGid))
res->ngroups = 0;
if(suser->spec_flags & ufAnonymous)
res->anonymous = suser->anonymous;
if(suser->spec_flags & ufChroot)
{
pfree (res->chroot, res);
res->chroot = pattach (suser->chroot, res);
}
res->access = pattach (suser->access, res);
res->allowLogin = suser->allowLogin;
res->allowSecLogin = suser->allowSecLogin;
res->allowForeign = suser->allowForeign;
res->allowOutOfRange = suser->allowOutOfRange;
res->maxIdle = suser->maxIdle;
res->passwordNeeded = suser->passwordNeeded;
res->external_login = pattach (suser->external_login, res);
res->access_program = pattach (suser->access_program, res);
#ifdef USE_SQL
res->sqlDirQuery = pattach (suser->sqlDirQuery, res);
#endif
pfree (suser, newparent);
}
if(!allowNoLogin && !res->allowLogin)
{
pfree(res, newparent);
return NULL;
}
#ifdef USE_TLS
if (allowNoLogin == 2 && !res->allowSecLogin)
{
pfree (res, newparent);
return NULL;
}
#endif
if (res->chroot)
{
char *tmp = res->chroot;
res->chroot = pstring (full_path (tmp, NULL, res->home), res);
pfree (tmp, res);
}
#ifdef USE_SQL
insert_sql_access (conn, res);
#endif
return res;
}
const char *user_pass(const user_t *user)
{
if(!user)
return NULL;
return user->password;
}
void user_setup_environ(user_t *user, int authed, int rFd, int wFd)
{
int canDrop;
if(user->chroot && authed)
{
const char *new_root;
super_privs (0);
new_root = set_root(user->chroot);
if (new_root && strcmp (new_root, user->chroot))
{
pfree (user->chroot, user);
user->chroot = pstring (new_root, user);
}
}
else
set_root("/");
canDrop = (authed && forkConnections && forker != getpid ());
set_gid (user->gid, canDrop);
set_groups (user->ngroups, user->groups);
set_uid (user->uid, canDrop);
set_access (user->access, user->defHardLink, authed? rFd : -1, authed? wFd : -1);
}
syntax highlighted by Code2HTML, v. 0.9.1