/*****************************************************************************\ * 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); }