/* pam_rsbac module */

/*
 * $Id: pam_permit.c,v 1.2 2000/12/04 19:02:34 baggins Exp $
 * Written by Andrew Morgan <morgan@parc.power.net> 1996/3/11
 *
 * Several functions and some other code copied from 
 * Copyright (c) Jan Rêkorajski 1999.
 * Copyright (c) Andrew G. Morgan 1996-8.
 * Copyright (c) Alex O. Yuriev, 1996.
 * Copyright (c) Cristian Gafton 1996.
 *
 * Modified and glued together for RSBAC authentication by
 * Amon Ott <ao@rsbac.org>
 * Copyright (c) Amon Ott, 2004-2017
 *
 * This product may be distributed under the terms of
 * the GNU Public License.
 *
 * THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#define DEFAULT_USER "nobody"

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <utmp.h>
#include <sys/types.h>
#include <sys/syslog.h>
#include <linux/types.h>
#include <linux/kdev_t.h>
#include <rsbac/types.h>
#include <rsbac/syscalls.h>

#include "nls.h"

/*
 * here, we make definitions for the externally accessible functions
 * in this file (these definitions are required for static modules
 * but strongly encouraged generally) they are used to instruct the
 * modules include file to define their prototypes.
 */

#define PAM_SM_PASSWORD

#include <security/pam_modules.h>
#include <security/_pam_macros.h>
#include <rsbac/types.h>
#include <rsbac/syscalls.h>

#define _pam_delete(xx)		\
{				\
	_pam_overwrite(xx);	\
	_pam_drop(xx);		\
}

void _log_err(int err, pam_handle_t *pamh, const char *format,...)
{
	va_list args;
	const char tag[] = "(pam_rsbac_oldpw) ";
	char *mod_format;
	int free_mod_format = 1;

	mod_format = malloc( 1 + sizeof(tag) + strlen(format));
	if(mod_format == NULL) {
	  free_mod_format = 0;
	  mod_format = (char *) format;
	} else {
	  strcpy(mod_format, tag);
	  strcat( mod_format, format);
	}

	va_start(args, format);
	vsyslog(err | LOG_AUTH, mod_format, args);
	va_end(args);

	if (free_mod_format) free(mod_format);
}

static int converse(pam_handle_t * pamh, int ctrl, int nargs
		    ,struct pam_message **message
		    ,struct pam_response **response)
{
	int retval;
	struct pam_conv *conv;

	D(("begin to converse"));

	retval = pam_get_item(pamh, PAM_CONV, (const void **) &conv);
	if (retval == PAM_SUCCESS) {

		retval = conv->conv(nargs, (const struct pam_message **) message
				    ,response, conv->appdata_ptr);

		D(("returned from application's conversation function"));

		if (retval != PAM_SUCCESS) {
			_log_err(LOG_DEBUG, pamh, "conversation failure [%s]"
				 ,pam_strerror(pamh, retval));
		}
	} else if (retval != PAM_CONV_AGAIN) {
		_log_err(LOG_ERR, pamh
		         ,"couldn't obtain coversation function [%s]"
			 ,pam_strerror(pamh, retval));
	}
	D(("ready to return from module conversation"));

	return retval;		/* propagate error status */
}

int _make_remark(pam_handle_t * pamh, unsigned int ctrl
		       ,int type, const char *text)
{
	int retval = PAM_SUCCESS;

	{
		struct pam_message *pmsg[1], msg[1];
		struct pam_response *resp;
		char remark[RSBAC_MAXNAMELEN];

		sprintf(remark, "pam_rsbac_oldpw.so: %s", text);
		pmsg[0] = &msg[0];
		msg[0].msg = remark;
		msg[0].msg_style = type;

		resp = NULL;
		retval = converse(pamh, 0, 1, pmsg, &resp);

		if (resp) {
			_pam_drop_reply(resp, 1);
		}
	}
	return retval;
}

  /*
   * Beacause getlogin() is braindead and sometimes it just
   * doesn't work, we reimplement it here.
   */
char *PAM_getlogin(void)
{
	struct utmp *ut, line;
	char *curr_tty, *retval;
	static char curr_user[sizeof(ut->ut_user) + 4];

	retval = NULL;

	curr_tty = ttyname(0);
	if (curr_tty != NULL && (strlen(curr_tty) > 5)) {
		D(("PAM_getlogin ttyname: %s", curr_tty));
		curr_tty += 5;
		setutent();
		strncpy(line.ut_line, curr_tty, sizeof(line.ut_line));
		if ((ut = getutline(&line)) != NULL) {
			strncpy(curr_user, ut->ut_user, sizeof(ut->ut_user));
			curr_user[sizeof(curr_user) - 1] = '\0';
			retval = curr_user;
		}
		endutent();
	}
	if(retval)
          D(("PAM_getlogin retval: %s", retval));

	return retval;
}


/* --- password management --- */

PAM_EXTERN
int pam_sm_chauthtok(pam_handle_t *pamh,int flags,int argc
		     ,const char **argv)
{
    int retval;
    const char *user=NULL;
    rsbac_uid_t uid = RSBAC_GEN_UID(RSBAC_UM_VIRTUAL_KEEP, RSBAC_NO_USER);
    char * p_old;
    unsigned int ctrl = 0;
    struct pam_message msg[3], *pmsg[3];
    struct pam_response *resp;
    int i, replies;
    char prompt1[RSBAC_MAXNAMELEN];
    char * token = NULL;

    retval = pam_get_user(pamh, &user, NULL);
    if (retval != PAM_SUCCESS) {
	D(("get user returned error: %s", pam_strerror(pamh,retval)));
	return retval;
    }
    if(!user)
      return PAM_SERVICE_ERR;
    if(!user[0])
      return PAM_USER_UNKNOWN;

    if (flags & PAM_PRELIM_CHECK)
      {
        retval = rsbac_um_get_uid(0, user, &uid);
        if(!retval)
          return PAM_SUCCESS;
        if (retval == -1 && errno != 0)
          retval = -errno;
        switch(retval)
          {
            case -RSBAC_EEXPIRED:
              return PAM_ACCT_EXPIRED;
            default:
              return PAM_TRY_AGAIN;
          }
      }
    if (flags & PAM_CHANGE_EXPIRED_AUTHTOK)
      {
        retval = rsbac_um_check_account_name(user);
        if(!retval)
          return PAM_SUCCESS;
        if (retval == -1 && errno != 0)
          retval = -errno;
        switch(retval)
          {
            case -RSBAC_EEXPIRED:
            case -RSBAC_EMUSTCHANGE:
              break;
            default:
              return PAM_TRY_AGAIN;
          }
      }

    retval = pam_get_item (pamh, PAM_OLDAUTHTOK, (void *) &p_old);
    if(retval != PAM_SUCCESS || !p_old)
      {
        /* prepare to converse */
        snprintf(prompt1, RSBAC_MAXNAMELEN - 1, _("Old password for user %s: "), user);
        prompt1[RSBAC_MAXNAMELEN - 1] = 0;
        i = 0;

        pmsg[i] = &msg[i];
        msg[i].msg_style = PAM_PROMPT_ECHO_OFF;
        msg[i++].msg = prompt1;
        replies = 1;
        /* so call the conversation expecting i responses */
        resp = NULL;
        retval = converse(pamh, ctrl, i, pmsg, &resp);

        if (resp != NULL) {
                /* interpret the response */
                if (retval == PAM_SUCCESS) {        /* a good conversation */

                        token = x_strdup(resp[i - replies].resp);
                        if (token != NULL) {
                                    p_old = token;
                        } else {
                                _log_err(LOG_NOTICE, pamh
                                         ,"could not recover authentication token");
                        }
                }
                /*
                 * tidy up the conversation (resp_retcode) is ignored
                 * -- what is it for anyway? AGM
                 */

                _pam_drop_reply(resp, i);
                if(!token)
                  return PAM_AUTHTOK_RECOVER_ERR;

        } else {
                retval = (retval == PAM_SUCCESS)
                    ? PAM_AUTHTOK_RECOVER_ERR : retval;
                    return retval;
        }
        retval = pam_set_item(pamh, PAM_OLDAUTHTOK, (const void *) p_old);
        if (retval != PAM_SUCCESS) {
                _log_err(LOG_CRIT, pamh,
                         "failed to set PAM_OLDAUTHTOK");
        }
      }
    retval = rsbac_um_auth_name(user, p_old);
    if (!retval)
      return PAM_SUCCESS;
    if (retval == -1 && errno != 0)
      retval = -errno;
    switch(retval)
        {
          case -RSBAC_EMUSTCHANGE:
            break;

          case -EPERM:
            _log_err(LOG_NOTICE, pamh,
                     "could not authenticate user %s",
                     user);
            pam_set_item (pamh, PAM_OLDAUTHTOK, NULL);
            return PAM_PERM_DENIED;
          case -RSBAC_ENOTFOUND:
          case -ENOENT:
            _log_err(LOG_NOTICE, pamh,
                     "could not authenticate user %s",
                     user);
            return PAM_USER_UNKNOWN;

          case -RSBAC_EINVALIDMODULE:
          case -ENOSYS:
            _log_err(LOG_NOTICE, pamh,
                     "RSBAC user management not available");
            _make_remark(pamh, ctrl, PAM_ERROR_MSG,
                       _("RSBAC user management not available"));
            return PAM_USER_UNKNOWN;

          case -RSBAC_EINVALIDVERSION:
            _log_err(LOG_NOTICE, pamh,
                     "Incompatible RSBAC API version, this PAM module was compiled for %s",
                     RSBAC_API_VERSION);
            _make_remark(pamh, ctrl, PAM_ERROR_MSG,
                         _("Incompatible RSBAC version"));
            return PAM_USER_UNKNOWN;

          case -RSBAC_EEXPIRED:
            _log_err(LOG_NOTICE, pamh,
                     "account %s or its password has expired (account expired)",
                     user);
            _make_remark(pamh, ctrl, PAM_ERROR_MSG,
                        _("Your account or password has expired, please contact system administrator"));
            pam_set_item (pamh, PAM_AUTHTOK, NULL);
            return PAM_PERM_DENIED;

          default:
            {
              char tmp[RSBAC_MAXNAMELEN];
              char tmp2[RSBAC_MAXNAMELEN];

              _log_err(LOG_NOTICE, pamh,
                       "error not handled: %s",
                       get_error_name(tmp, retval));
              snprintf(tmp2, RSBAC_MAXNAMELEN - 1, _("Unhandled error %s, please contact system administrator!"),
                       get_error_name(tmp, retval));
              tmp2[RSBAC_MAXNAMELEN - 1] = 0;
              _make_remark(pamh, ctrl, PAM_TEXT_INFO, tmp2);
              return PAM_USER_UNKNOWN;
            }
        }
    return PAM_SUCCESS;
}

/* end of module definition */

#ifdef PAM_STATIC

/* static module data */

struct pam_module _pam_permit_modstruct = {
    "pam_permit",
    pam_sm_chauthtok
};

#endif
