/*****************************************************************************\
* 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