/*****************************************************************************\
* 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: com_login.c 1245 2004-12-09 12:01:59Z morth $ */

#include "system.h"

#include "commands.h"

#include "connection.h"
#include "main.h"
#include "utf8fs/memory.h"
#include "defaults.h"
#include "accounter.h"
#include "events.h"

#ifdef HAVE_LIBPAM
static const char *tmp_passwd;
static int tmp_passwd_reqed;

int conv_fun (int num_msg, PAM_CONST struct pam_message **msg,
      struct pam_response **resp, void *appdata_ptr)
{
  //connection_t *conn = appdata_ptr;
  int i;
  struct pam_response *res;
  
  res = malloc (sizeof (*res) * num_msg);
  if (!res)
    return PAM_CONV_ERR;
  
  for (i = 0; i < num_msg; i++)
  {
    switch (msg[i]->msg_style)
    {
    case PAM_PROMPT_ECHO_OFF:
      if (tmp_passwd)
	res[i].resp = strdup (tmp_passwd);
      else
      {
	tmp_passwd_reqed = 1;
	free (res);
	return PAM_CONV_ERR;
      }
      break;
    default:
      free (res);
      return PAM_CONV_ERR;
    }
    res[i].resp_retcode = PAM_SUCCESS;
  }
  *resp = res;
  return PAM_SUCCESS;
}
#endif

int ext_login_read (int fd, void *user, int urgent)
{
  char buf[4097], *bp, *nbp;
  int l;
  connection_t *conn = user;
  
  if (conn->sock == -1)
    return 0;
  l = read (fd, buf, sizeof (buf) - 1);
  if (l < 0)
  {
    if (errno == EINTR || errno == EAGAIN)
      return 0;
    reply (conn, "530 %s.", strerror (errno));
    unauthenticated (conn);
    return 0;
  }
  if (!l)
  {
    remove_read_fd (fd);
    close (conn->extRFd);
    conn->extRFd = -1;
    close (conn->extWFd);
    conn->extWFd = -1;
    return 0;
  }
  for (bp = buf; (nbp = strchr (bp, '\n')); bp = nbp)
  {
    *nbp++ = 0;
    if (!strncmp (bp, "230 ", 4))
    {
      if (authenticated (conn))
      {
	reply (conn, "530 Failed to authenticate: %s (root dir inaccessible?).", strerror (errno));
	break;
      }
    }
    else if (!strncmp (bp, "331 ", 4))
      conn->expect = "PASS";
    else if (!strncmp (bp, "332 ", 4))
      conn->expect = "ACCT";
    else if (!strncmp (bp, "421 ", 4))
    {
      reply (conn, "%s", bp);
      disconnected (conn);
      break;
    }
    else if (!strncmp (bp, "530 ", 4))
      unauthenticated (conn);
    reply (conn, "%s", bp);
  }
  return 0;
}

/* Login commands */

int command_user(connection_t *conn, const char *arg, int expected)
{
#ifdef HAVE_LIBPAM
  int err;
  PAM_CONST void *parg;
#endif
  
  if (conn->authed && conn->user && !strcmp (arg, conn->user->name))
  {
    reply (conn, "230 Already logged in as %s.", conn->user->name);
    return 0;
  }
  
  if (
#ifdef USE_TLS
      (conn->tlsControl && conn->server->loginTLS && conn->server->tlsNoLogout) ||
#endif
      super_privs (1))
  {
    reply(conn, "503 Can't relogin.");
    return 0;
  }
  
  unauthenticated(conn);
  
  if (!conn->inLookUp)
  {
    conn->inLookUp = 1;
#ifdef USE_SQL
    conn->server->sqlRefs++;
#endif
  }
  conn->user = find_user (conn, arg,
#ifdef USE_TLS
	conn->tlsControl? 2 :
#endif
	0, conn);
  
#ifdef HAVE_LIBPAM
  pam_set_item (conn->pamh, PAM_USER, arg);
#endif
  
  if (conn->user && conn->user->external_login)
  {
    int ipipe[2], opipe[2], i;
    const char *args[3], **ap = args;
    const char *envs[] = { NULL };
    const char *prog = conn->user->external_login;
    
    if (pipe (ipipe))
    {
      reply (conn, "530 %s.", strerror (errno));
      return 0;
    }
    if (pipe (opipe))
    {
      reply (conn, "530 %s.", strerror (errno));
      close (ipipe[0]);
      close (ipipe[1]);
      return 0;
    }
    
    *ap++ = prog;
    *ap++ = arg;
    *ap = NULL;
    switch (vfork ())
    {
    case -1:
      // Error
      reply (conn, "530 %s.", strerror (errno));
      close (ipipe[0]);
      close (ipipe[1]);
      close (opipe[0]);
      close (opipe[1]);
      return 0;
    case 0:
      // Child
      if (dup2 (ipipe[1], 1) == -1)
	_exit (1);
      if (dup2 (opipe[0], 0) == -1)
	_exit (1);
      for (i = 2; i < getdtablesize (); i++)
	close (i);
      execve (prog, (char**)args, (char**)envs);
      _exit (1);
    default:
      close (ipipe[1]);
      close (opipe[0]);
      break;
    }
    fcntl (ipipe[0], F_SETFL, O_NONBLOCK);
    add_read_fd (ipipe[0], ext_login_read, conn);
    conn->extRFd = ipipe[0];
    conn->extWFd = opipe[1];
    return 0;
  }
  
#ifdef HAVE_LIBPAM
  tmp_passwd_reqed = 0;
  err = pam_authenticate (conn->pamh, PAM_SILENT);
  if (pam_get_item (conn->pamh, PAM_USER, &parg) == PAM_SUCCESS)
  {
    arg = parg;
    pfree (conn->user, conn);
    conn->user = find_user (conn, arg,
#ifdef USE_TLS
	  conn->tlsControl? 2 :
#endif
	  0, conn);
  }
  if (err != PAM_SUCCESS || !conn->user)
  {
    if (!tmp_passwd_reqed || !conn->user || conn->user->passwordNeeded)
    {
      if (conn->server->passIfInvalid || (conn->user && (tmp_passwd_reqed || conn->user->anonymous || conn->user->password)))
      {
	if (conn->user && conn->user->anonymous)
	  reply_msg (conn, "331 %s", conn->server->anonPassMsg? conn->server->anonPassMsg
		: defAnonPassMsg);
	else
	  reply_msg (conn, "331 %s", conn->server->passRequestMsg?
		conn->server->passRequestMsg : defPassRequestMsg);
	conn->expect = "PASS";
      }
      else
      {
	if (err == PAM_MAXTRIES)
	{
	  syslog (LOG_NOTICE, "%d: Unknown user %s.", conn->id, arg);
	  syslog (LOG_AUTHPRIV | LOG_NOTICE, "%d: Unknown user %s.",
		conn->id, arg);
	  reply_msg (conn, "421 %s", conn->server->userInvalidMsg?
		conn->server->userInvalidMsg : defUserInvalidMsg);
	  disconnected (conn);
	  return 1;
	}
	if (err == PAM_AUTH_ERR)
	{
	  syslog (LOG_NOTICE, "%d: Unknown user %s.", conn->id, arg);
	  syslog (LOG_AUTHPRIV | LOG_NOTICE, "%d: Unknown user %s.",
		conn->id, arg);
	  reply_msg (conn, "530 %s", conn->server->userInvalidMsg?
		conn->server->userInvalidMsg : defUserInvalidMsg);
	  if (conn->server->sleepOnFail)
	  {
	    time (&conn->sleepUntil);
	    conn->sleepUntil += conn->server->sleepOnFail;
	  }
	}
	else
	  reply (conn, "530 %s.", pam_strerror (conn->pamh, err));
	unauthenticated (conn);
      }
      return 0;
    }
  }
  
  err = pam_acct_mgmt (conn->pamh, PAM_SILENT);
  if (err != PAM_SUCCESS)
  {
    reply (conn, "530 %s.", pam_strerror (conn->pamh, err));
    unauthenticated (conn);
    return 0;
  }
  
  accounter (conn->accSock, "LOGIN %s - - %d\n", conn->user->name, conn->user->maxLogins);
  conn->accWait = acLogin;
#else /*HAVE_LIBPAM*/
  if(conn->user && !conn->user->passwordNeeded)
  {
    accounter (conn->accWait, "LOGIN %s - - %d\n", conn->user->name, conn->user->maxLogins);
    conn->accWait = acLogin;
  }
  else if(!conn->user && !conn->server->passIfInvalid)
  {
    syslog (LOG_NOTICE, "%d: Unknown user %s.", conn->id, arg);
    syslog (LOG_AUTHPRIV | LOG_NOTICE, "%d: Unknown user %s.",
	  conn->id, arg);
    reply_msg (conn, "530 %s", conn->server->userInvalidMsg?
	  conn->server->userInvalidMsg : defUserInvalidMsg);
    unauthenticated (conn);
    if (conn->server->sleepOnFail)
    {
      time (&conn->sleepUntil);
      conn->sleepUntil += conn->server->sleepOnFail;
    }
  }
  else
  {
    if (!conn->user)
    {
      syslog (LOG_NOTICE, "%d: Unknwon user %s.", conn->id, arg);
      syslog (LOG_AUTHPRIV | LOG_NOTICE, "%d: Unknown user %s.",
	    conn->id, arg);
    }
    if(conn->user && conn->user->anonymous)
      reply_msg (conn, "331 %s", conn->server->anonPassMsg? conn->server->anonPassMsg
	    : defAnonPassMsg);
    else
      reply_msg (conn, "331 %s", conn->server->passRequestMsg?
	    conn->server->passRequestMsg : defPassRequestMsg);
    conn->expect = "PASS";
  }
#endif /*!HAVE_LIBPAM*/
  return 0;
}

int command_pass(connection_t *conn, const char *arg, int expected)
{
  const char *pass;
#ifdef HAVE_LIBPAM
  const char *user;
  PAM_CONST void *puser;
#endif
  
  if (!expected)
  {
    reply(conn, "503 PASS not expected here.");
    return 0;
  }
  
  if (conn->user && conn->user->external_login)
  {
    char *warg = talloc (strlen (arg) + 2);
    
    strcpy (warg, arg);
    strcat (warg, "\n");
    if (write (conn->extWFd, warg, strlen (warg)) < 0)
    {
      reply (conn, "530 %s.", strerror (errno));
      unauthenticated (conn);
    }
    return 0;
  }
  
  pass = user_pass (conn->user);
  
  if (conn->user && conn->user->anonymous)
  {
    const char *ap;
    
    if (!strlen (arg))
      arg = "-";
    else
    {
      for (ap = arg; *ap; ap++)
      {
	if (isspace (*ap & 0xFF))
	{
	  unauthenticated (conn);
	  reply (conn, "530 Invalid email.");
	  return 0;
	}
      }
    }
  }
#ifdef HAVE_LIBPAM
  else if (!pass)
  {
    int err;
    
    tmp_passwd = arg;
    err = pam_authenticate (conn->pamh, PAM_SILENT);
    tmp_passwd = NULL;
    if (err != PAM_SUCCESS)
    {
      if (err == PAM_MAXTRIES)
      {
	if (conn->user)
	{
	  syslog (LOG_NOTICE, "%d: User %s failed login.", conn->id,
		conn->user->name);
	  syslog (LOG_AUTHPRIV | LOG_NOTICE, "%d: User %s failed login.",
		conn->id, conn->user->name);
	}
	reply_msg (conn, "421 %s", conn->server->loginFailedMsg?
	      conn->server->loginFailedMsg : defLoginFailedMsg);
	disconnected (conn);
	return 1;
      }
      if (err == PAM_AUTH_ERR)
      {
	if (conn->user)
	{
	  syslog (LOG_NOTICE, "%d: User %s failed login.", conn->id,
		conn->user->name);
	  syslog (LOG_AUTHPRIV | LOG_NOTICE, "%d: User %s failed login.",
		conn->id, conn->user->name);
	}
	reply_msg (conn, "530 %s", conn->server->loginFailedMsg?
	      conn->server->loginFailedMsg : defLoginFailedMsg);
	if (conn->server->sleepOnFail)
	{
	  time (&conn->sleepUntil);
	  conn->sleepUntil += conn->server->sleepOnFail;
	}
      }
      else
	reply (conn, "530 %s.", pam_strerror (conn->pamh, err));
      unauthenticated (conn);
      return 0;
    }
    
    err = pam_acct_mgmt (conn->pamh, PAM_SILENT);
    if (err != PAM_SUCCESS)
    {
      reply (conn, "530 %s.", pam_strerror (conn->pamh, err));
      unauthenticated (conn);
      return 0;
    }
  }
  else if (strcmp (crypt (arg, pass), pass))
#else /*HAVE_LIBPAM*/
  else if (!pass || strcmp (crypt (arg, pass), pass))
#endif
  {
    if (conn->user)
    {
      syslog (LOG_NOTICE, "%d: User %s failed login.", conn->id,
	    conn->user->name);
      syslog (LOG_AUTHPRIV | LOG_NOTICE, "%d: User %s failed login.",
	    conn->id, conn->user->name);
    }
    if (conn->server->maxLoginAttempts && ++conn->failedLogins >=
	  conn->server->maxLoginAttempts)
    {
      reply_msg (conn, "421 %s", conn->server->loginFailedMsg?
	    conn->server->loginFailedMsg : defLoginFailedMsg);
      disconnected (conn);
      return 1;
    }
    unauthenticated (conn);
    reply_msg (conn, "530 %s", conn->server->loginFailedMsg?
	  conn->server->loginFailedMsg : defLoginFailedMsg);
    if (conn->server->sleepOnFail)
    {
      time (&conn->sleepUntil);
      conn->sleepUntil += conn->server->sleepOnFail;
    }
    return 0;
  }
#ifdef HAVE_LIBPAM
  if (pam_get_item (conn->pamh, PAM_USER, &puser) == PAM_SUCCESS)
  {
    user = puser;
    pfree (conn->user, conn);
    conn->user = find_user (conn, user,
#ifdef USE_TLS
	  conn->tlsControl? 2 :
#endif
	  0, conn);
  }
  else
    user = "";
  if (!conn->user)
  {
    syslog (LOG_NOTICE, "%d: Unknown user %s.", conn->id, user);
    syslog (LOG_AUTHPRIV | LOG_NOTICE, "%d: Unknown user %s.",
	  conn->id, user);
    reply_msg (conn, "530 %s", conn->server->loginFailedMsg?
	  conn->server->loginFailedMsg : defLoginFailedMsg);
    if (conn->server->sleepOnFail)
    {
      time (&conn->sleepUntil);
      conn->sleepUntil += conn->server->sleepOnFail;
    }
    return 0;
  }
#endif
  if (conn->user->anonymous)
  {
    conn->email = pstring (arg, conn);
    accounter (conn->accSock, "LOGIN %s - %s %d\n", conn->user->name, conn->email, conn->user->maxLogins);
  }
  else
    accounter (conn->accSock, "LOGIN %s - - %d\n", conn->user->name, conn->user->maxLogins);
  conn->accWait = acLogin;
  return 0;
}

int command_acct(connection_t *conn, const char *arg, int expected)
{
  const char *ap;
  
  if (!expected)
  {
    reply(conn, "503 ACCT not expected here.");
    return 0;
  }
  
  for (ap = arg; *ap; ap++)
  {
    if (isspace (*ap & 0xFF))
    {
      unauthenticated (conn);
      reply (conn, "530 Invalid account.");
      return 0;
    }
  }
  conn->account = pstring (arg, conn);
  
  if (conn->user && conn->user->external_login)
  {
    char *warg = talloc (strlen (arg) + 2);
    
    strcpy (warg, arg);
    strcat (warg, "\n");
    if (write (conn->extWFd, warg, strlen (warg)) < 0)
    {
      reply (conn, "530 %s.", strerror (errno));
      unauthenticated (conn);
    }
    return 0;
  }
  if (conn->user)
  {
    accounter (conn->accSock, "LOGIN %s %s - %d\n", conn->user->name, conn->account, conn->user->maxLogins);
    conn->accWait = acLogin;
  }
  else
  {
    reply (conn, "530 Not logged in.");
    unauthenticated (conn);
  }
  return 0;
}

/* Logout commands */

int command_rein(connection_t *conn, const char *arg, int expected)
{
  if (super_privs (1))
  {
    reply(conn, "503 Can't relogin.");
    return 0;
  }
  
  if (conn->user)
    syslog (LOG_INFO, "%d: User %s logged out", conn->id, conn->user->name);
  unauthenticated(conn);
  pfree (conn->currLang, conn);
  conn->currLang = NULL;
  if (conn->langFd >= 0)
    close_shared (conn->langFd);
  conn->langFd = -1;
  conn->protLevel = 0;
  conn->mlstTags = tfType | tfUnique | tfModify | tfPerm | tfSize;
  reply (conn, "220 You have been logged out.");
#ifdef USE_TLS
  if (conn->tlsControl)
  {
    tls_stop (conn->tlsControl);
    conn->tlsState = 2;
  }
#endif
  accounter (conn->accSock, "LOGOUT\n");
  return 0;
}

int command_quit(connection_t *conn, const char *arg, int expected)
{
  if (conn->working)
    reply_spont(conn, "221 Good bye.", 1);
  else
  {
    reply(conn, "221 Good bye.");
    disconnected(conn);
  }
  return 1;
}


syntax highlighted by Code2HTML, v. 0.9.1