/*
 * pam_sshauth: PAM module for authentication via a remote ssh server.
 * Copyright (C) 2010-2013 Scott Balneaves <sbalneav@ltsp.org>
 *
 * 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 of the License, 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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <syslog.h>
#include <config.h>
#include <security/pam_modules.h>
#include <security/pam_ext.h>

#include "pam_sshauth.h"

/*
 * pam_cleanup
 *
 * String cleanup function.
 */

void
pam_cleanup (pam_handle_t * pamh, void * data, int error_status)
{
  free (data);
}

/*
 * pam_debug
 *
 * Log a message if debug active.
 */

void
pam_debug (pam_handle_t * pamh, const char *format, ...)
{
  char msgbuf[BUFSIZ];
  va_list ap;

  if (psadebug)
    {
      va_start (ap, format);
      if (vsnprintf (msgbuf, sizeof msgbuf, format, ap) >= sizeof msgbuf)
        {
          /*
           * Message was truncated.  Make sure we're NULL terminated
           */

          msgbuf[(sizeof msgbuf) - 1] = '\0';
	}
      pam_syslog (pamh, LOG_INFO, "%s", msgbuf);
      va_end (ap);
    }
}


/*
 * Send a message through pam's conv stack.
 *
 * for PAM_TEXT_INFO, and PAM_ERROR_MSG messages.
 */

int
send_pam_msg (pam_handle_t * pamh, int style, const char *format, ...)
{
  char msgbuf[BUFSIZ];
  const struct pam_message mymsg =
    {
      .msg_style = style,
      .msg = msgbuf,
    };

  const struct pam_message *msgp = &mymsg;
  const struct pam_conv *pc;
  struct pam_response *resp;
  int pam_result;
  va_list ap;

  /*
   * Start our varargs list, so we can format our message.
   */

  va_start (ap, format);
  if (vsnprintf (msgbuf, sizeof msgbuf, format, ap) >= sizeof msgbuf)
    {
      /*
       * Message was truncated.  Make sure we're NULL terminated,
       * and log an error.
       */

       msgbuf[(sizeof msgbuf) - 1] = '\0';
       pam_syslog (pamh, LOG_ERR, "send_pam_msg () truncated a message.");
    }

  va_end (ap);

  pam_result = pam_get_item (pamh, PAM_CONV, (const void **) &pc);
  if (pam_result != PAM_SUCCESS)
    {
      return pam_result;
    }

  if (!pc || !pc->conv)
    {
      return PAM_CONV_ERR;
    }

  return pc->conv (1, &msgp, &resp, pc->appdata_ptr);
}

/*
 * pam_process_args
 *
 * Process pam module command line arguments, and set global flags.
 */

void
pam_process_args (pam_handle_t * pamh, int argc, const char **argv, char **host, char **port)
{
  int i;
  psadebug = 0;
  nostrict = 0;
  askpass = 0;
  try_first_pass = 0;
  for (i = 0; i < argc; i++)
    {
      if (!strcmp (argv[i], "debug"))
        {
          psadebug++;
        }
      else if (!strcmp (argv[i], "nostrict"))
        {
          nostrict++;
        }
      else if (!strcmp (argv[i], "shm_askpass"))
        {
          askpass++;
        }
      else if (!strcmp (argv[i], "try_first_pass"))
        {
          try_first_pass++;
        }
      else if (!strncmp (argv[i], "authtries=", 10))
        {
          authtries = atoi ((char *)(argv[i] + 10)); /* authtries=x */
        }
      else if (!strncmp (argv[i], "host=", 5))
        {
          *host = (char *)(argv[i] + 5);   /* host=foo.bar.com */
        }
      else if (!strncmp (argv[i], "port=", 5))
        {
          *port = (char *)(argv[i] + 5);   /* port=x */
        }
    }
}

int
sshauth_pam_env (pam_handle_t * pamh, char *envname, char *envvalue, int cleanup)
{
  /*
   * Process the var
   */

  if (envvalue && *envvalue != '\0')
    {
      char *new_env;
      size_t len;
      int pam_result;

      if (cleanup)
	{
	  pam_result = pam_set_data (pamh, envname, envvalue, pam_cleanup);
	}
      else
	{
	  pam_result = pam_set_data (pamh, envname, envvalue, NULL);
	}

      if (pam_result != PAM_SUCCESS)
        {
          pam_syslog (pamh, LOG_ERR, "Couldn't store %s in pam handle.", envname);
          return pam_result;
        }

      /*
       * Export envname as pam environment variable for scripts to use.
       */

      len = strlen (envname) + strlen (envvalue) + 2; /* Length of env name + value + 2 for =, NULL */
      if ((new_env = (char *) malloc (len)) == NULL)
        {
          pam_syslog (pamh, LOG_ERR, "Could not allocate memory for %s variable.", envname);
          return PAM_BUF_ERR;
        }

      snprintf (new_env, len, "%s=%s", envname, envvalue);
      pam_result = pam_putenv (pamh, new_env);
      free (new_env);

      if (pam_result != PAM_SUCCESS)
        {
          pam_syslog (pamh, LOG_ERR, "Could not set %s in pam environment.", envname);
        }

      return pam_result;
    }
  else
    {
      return PAM_SUCCESS;
    }
}
