/*
 * Copyright (C) 1994-2018 Altair Engineering, Inc.
 * For more information, contact Altair at www.altair.com.
 *
 * This file is part of the PBS Professional ("PBS Pro") software.
 *
 * Open Source License Information:
 *
 * PBS Pro is free software. You can redistribute it and/or modify it under the
 * terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any
 * later version.
 *
 * PBS Pro 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Commercial License Information:
 *
 * For a copy of the commercial license terms and conditions,
 * go to: (http://www.pbspro.com/UserArea/agreement.html)
 * or contact the Altair Legal Department.
 *
 * Altair’s dual-license business model allows companies, individuals, and
 * organizations to create proprietary derivative works of PBS Pro and
 * distribute them - whether embedded or bundled with other software -
 * under a commercial license agreement.
 *
 * Use of Altair’s trademarks, including but not limited to "PBS™",
 * "PBS Professional®", and "PBS Pro™" and Altair’s logos is subject to Altair's
 * trademark licensing policies.
 *
 */
/**
 * @file    user_func.c
 *
 *@brief
 * 		user_func.c - Functions which provide basic operation on the user concept
 *
 * Included public functions are:
 *
 *   user_write_password  saves a user password to a file
 *   user_read_password   reads user's saved password from a file
 *   req_usercredential   receive save per user/per server password request
 *   req_user_migrate	  receive migrate users info request to a dest. host
 */

#include <pbs_config.h>   /* the master config generated by configure */

#ifndef WIN32
#include <unistd.h>
#include <sys/param.h>
#include <dirent.h>
#endif
#ifdef NAS /* localmod 005 */
#ifdef	linux
#include <netdb.h>		/* for ruserok */
#endif
#endif /* localmod 005 */

#include <sys/types.h>
#include <sys/stat.h>
#include <ctype.h>
#include <errno.h>
#include <assert.h>

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef WIN32
#include <direct.h>
#include <windows.h>
#include "win.h"
#else
#include <netdb.h>
#endif

#include "pbs_ifl.h"
#include "log.h"
#include "user.h"
#include "list_link.h"
#include "server_limits.h"
#include "attribute.h"
#include "credential.h"
#include "ticket.h"
#include "libpbs.h"
#include "batch_request.h"
#include "server.h"
#include "net_connect.h"
#include "pbs_nodes.h"
#include "svrfunc.h"


#define MIG_RETRY_LIMIT 3

#ifdef	WIN32
extern int decrypt_pwd(char *crypted, size_t len, char **passwd);
#endif
extern struct server server;
extern char *path_users;
extern int pbs_server_port_dis;

/* External functions */
extern  int     should_retry_route(int err);

/* Local Private Functions */

/* Global Data items */

/**
 * @brief
 *  	user_write_password - Output password into user password file.
 *
 * @param[in]	user	-	The user name.
 * @param[in]	cred	-	Credential
 * @param[in]	len	-	length of cred.
 *
 * @return	int
 * @retval	0	: success
 * @retval	-2	: if a request was made to delete a non-existent user password file.
 * @retval	-1	: on all other errors.
 * @note
 *      NOTE: The well known 'log_buffer' array will be overwritten as to what
 *		action took place.
 *      Also, if cred is "" and len is 0, then the user password file is
 *      deleted!
 */
int
user_write_password(char *user, char *cred, size_t len)
{
	extern char     *path_users;
	char		name_buf[MAXPATHLEN+1];
	int		cred_fd;
	int		ret = -1;

	assert(user != NULL);
	assert(cred != NULL);

	(void)strcpy(name_buf, path_users);
	(void)strcat(name_buf, user);
	(void)strcat(name_buf, USER_PASSWORD_SUFFIX);


	if (len == 0) {
		struct stat sbuf;
#ifdef WIN32
		if( (lstat(name_buf, &sbuf) == -1) && \
                       (GetLastError() == ERROR_FILE_NOT_FOUND) ) {
			sprintf(log_buffer, "user %s has no password file!",
				user);
			return (-2);
		}

		if (DeleteFile(name_buf) == 0) {
			sprintf(log_buffer, "Deleting user %s failed: error %d",
				user, GetLastError());
			return (-1);
		}
#else
		if( (stat(name_buf, &sbuf) == -1) && \
                       			(errno == ENOENT) ) {
			sprintf(log_buffer, "user %s has no password file!",
				user);
			return (-2);
		}
		if (unlink(name_buf) == -1) {
			sprintf(log_buffer, "Deleting user %s failed: error %d",
				user, errno);
			return (-1);
		}
#endif
		return (0);
	}


	if ((cred_fd = open(name_buf, O_RDWR|O_CREAT|O_TRUNC, 0600)) == -1) {
		sprintf(log_buffer,
			"open of user password file %s failed: errno %d",
			name_buf, errno);
		return -1;
	}

#ifdef WIN32
	secure_file(name_buf, "Administrators",
		READS_MASK|WRITES_MASK|STANDARD_RIGHTS_REQUIRED);
	setmode(cred_fd, O_BINARY);
#endif
	if (write(cred_fd, cred, len) != len) {
		sprintf(log_buffer,
			"write to file %s incomplete: errno %d", name_buf, errno);
		goto done;
	}
	sprintf(log_buffer, "saved user %s's per server password", user);
#ifdef	WIN32
	/* read/cache user password */
	cache_usertoken_and_homedir(user, NULL, 0, user_read_password,
		(char *)user, pbs_decrypt_pwd, 1);
#endif

	ret = 0;

done:
	if (cred_fd > 0)
		close(cred_fd);
	return ret;
}

/**
 * @brief
 * 		user_read_password
 *		Check if this user has an associated user password file.  If it does,
 *		the user password file is opened and the password is read into
 *		malloc'ed memory.
 *
 * @param[in]	user	-	The user name.
 * @param[out]	cred	-	Credential
 * @param[out]	len	-	length of cred.
 *
 * @return	int
 * @retval	1	: if there is no password
 * @retval	0	: if there is a password
 * @retval	-1	: error.
 */
int
user_read_password(char *user, char **cred, size_t *len)
{
	extern char     *path_users;
	char		name_buf[MAXPATHLEN+1];
	char		*hold = NULL;
	struct	stat	sbuf;
	int		fd;
	int		ret = -1;

	assert(user != NULL);
	assert(cred != NULL);

	(void)strcpy(name_buf, path_users);
	(void)strcat(name_buf, user);
	(void)strcat(name_buf, USER_PASSWORD_SUFFIX);

	if ((fd = open(name_buf, O_RDONLY)) == -1) {
		if (errno == ENOENT)
			return 1;

		sprintf(log_buffer, "failed to open %s errno", name_buf);
		log_err(errno, __func__, log_buffer);
		return ret;
	}

	if (fstat(fd, &sbuf) == -1) {
		sprintf(log_buffer, "failed to fstat %s", name_buf);
		log_err(errno, __func__, log_buffer);
		goto done;
	}

	hold = malloc(sbuf.st_size);
	assert(hold != NULL);

#ifdef WIN32
	setmode(fd, O_BINARY);
#endif
	if (read(fd, hold, sbuf.st_size) != sbuf.st_size) {
		sprintf(log_buffer, "read %s is incomplete", name_buf);
		log_err(errno, __func__, log_buffer);
		goto done;
	}
	*len = sbuf.st_size;
	*cred = hold;
	hold = NULL;
	ret = 0;

done:
	if (fd > 0)
		close(fd);

	if (hold != NULL)
		free(hold);

	return ret;
}

/**
 * @brief
 * 		req_usercredential - receive password credential of a user that
 *                      is to be saved by the server.
 *
 * @param[in,out]	preq	-	ptr to the decoded request
 */
void
req_usercredential(struct batch_request *preq)
{
	char	*user;
	int	type;
	char	*cred;
	size_t	len;
	char	info[PBS_MAXUSER + PBS_MAXHOSTNAME + 2];
	int	rval;

	DBPRT(("%s: entered\n", __func__))
	user = preq->rq_ind.rq_usercred.rq_user;
	type = preq->rq_ind.rq_usercred.rq_type;
	cred = preq->rq_ind.rq_usercred.rq_data;
	len = (size_t)preq->rq_ind.rq_usercred.rq_size;

	if( (preq->rq_host == NULL) || (preq->rq_user == NULL) || \
		     			user == NULL ) { /* no user */
		req_reject(PBSE_INTERNAL, 0, preq);
		return;
	}

	if (!(server.sv_attr[SRV_ATR_ssignon_enable].at_flags & ATR_VFLAG_SET) \
	    || \
            (server.sv_attr[SRV_ATR_ssignon_enable].at_val.at_long == 0) ) {
		req_reject(PBSE_SSIGNON_UNSET_REJECT, 0, preq);
		return;
	}

	snprintf(info, sizeof(info), "%s@%s", preq->rq_user, preq->rq_host);

#ifdef WIN32
	if (strcmpi(preq->rq_user, user) != 0) {
		/* ok if request coming from another server */
		if( !preq->rq_fromsvr && !isAdminPrivilege(preq->rq_user) && \
			(ruserok(preq->rq_host, isAdminPrivilege(user),
			preq->rq_user, user) != 0)) {
			req_reject(PBSE_PERM, 0, preq);
			return;
		}
	}
#else
	if (strcasecmp(preq->rq_user, user) != 0) {
		/* ok if request coming from another server */
		if (!preq->rq_fromsvr &&
			(strcasecmp(preq->rq_user, PBS_DEFAULT_ADMIN) != 0) &&
			(ruserok(preq->rq_host, 0, preq->rq_user, user) != 0)) {
			req_reject(PBSE_PERM, 0, preq);
			return;
		}
	}
#endif


	if (type != PBS_CREDTYPE_AES) {
		req_reject(PBSE_SYSTEM, 0, preq);
		return;
	}

	strcpy(log_buffer, "");

	rval = user_write_password(user, cred, len);
	if (rval == -2) {
		if (strlen(log_buffer)  > 0)
			log_err(-1, info, log_buffer);
		req_reject(PBSE_BADUSER, 0, preq);
	} else if (rval == -1) {
		if (strlen(log_buffer)  > 0)
			log_err(-1, info, log_buffer);
		req_reject(PBSE_SYSTEM, 0, preq);
	} else {
		if (strlen(log_buffer) > 0)
			log_event(PBSEVENT_ADMIN, PBS_EVENTCLASS_SERVER,
				LOG_INFO, info , log_buffer);
		reply_ack(preq);
	}

	return;
}

/**
 * @brief
 * 		receive request to copy all users info on the
 *		the current server to some remote server.
 *		Currently, the only user info  is password.
 *
 * @param[in]	preq	-	ptr to the decoded batch request
 */
void
req_user_migrate(struct batch_request *preq)
{
	char	*tohost;
	DIR	*dir = 0;
	pbs_net_t hostaddr = 0;
	unsigned int port = pbs_server_port_dis;
	char	*hostname;
	int	i;
	int	con = -1;
	char	*cred = NULL;
	size_t  len = 0;
	int	ret;
	int	rc = 0;
	int	pbs_ret = PBSE_NONE;
	struct dirent *pdirent;

	DBPRT(("%s: entered\n", __func__))
	tohost = preq->rq_ind.rq_user_migrate.rq_tohost;

	if (tohost == NULL) {
		pbs_ret = PBSE_INTERNAL;
		goto migrate_exit;
	}

	if (!(server.sv_attr[SRV_ATR_ssignon_enable].at_flags & ATR_VFLAG_SET) \
	    || \
            (server.sv_attr[SRV_ATR_ssignon_enable].at_val.at_long == 0) ) {
		pbs_ret = PBSE_SSIGNON_UNSET_REJECT;
		goto migrate_exit;
	}

	/* ok if request coming from another server */
#ifdef WIN32
	if (!preq->rq_fromsvr && !isAdminPrivilege(preq->rq_user)) {
		pbs_ret = PBSE_PERM;
		goto migrate_exit;
	}
#else
	if( !preq->rq_fromsvr && \
		(strcasecmp(preq->rq_user, PBS_DEFAULT_ADMIN) != 0) ) {
		pbs_ret = PBSE_PERM;
		goto migrate_exit;
	}
#endif

	hostname = parse_servername(tohost, &port);
	hostaddr = get_hostaddr(hostname);

	for (i=0; i < MIG_RETRY_LIMIT; i++) {

		pbs_errno = 0;
		/* connect to receiving server with retries */

		if (i > 0) {	/* recycle after an error */
			if (con >= 0)
				svr_disconnect(con);

			if (should_retry_route(pbs_errno) == -1) {
				sprintf(log_buffer,
					"couldn't retry route to host %s",
					tohost);
				log_err(pbs_errno,
					"req_usercredential_migrate",
					log_buffer);
				pbs_ret = PBSE_SSIGNON_NOCONNECT_DEST;
				goto migrate_exit;

			}
			sleep(1<<i);
		}
		if ((con = svr_connect(hostaddr, port, 0, ToServerDIS, 0)) ==
			PBS_NET_RC_FATAL) {
			(void)sprintf(log_buffer, "connect to %s port %d",
				tohost, port);
			log_err(pbs_errno, __func__, log_buffer);
			pbs_ret = PBSE_SSIGNON_NOCONNECT_DEST;
			goto migrate_exit;

		} else if (con == PBS_NET_RC_RETRY) {
#ifdef WIN32
			pbs_errno = WSAECONNREFUSED;	/* should retry */
#else
			pbs_errno = ECONNREFUSED;	/* should retry */
#endif
		} else {
			break;
		}
	}

	if (i == MIG_RETRY_LIMIT) {
		(void)sprintf(log_buffer,
			"retry limit exceeded connecting to host %s port %d",
			tohost, port);
		log_err(pbs_errno, __func__, log_buffer);
		pbs_ret = PBSE_SSIGNON_NOCONNECT_DEST;
		goto migrate_exit;
	}

	rc = 0;
	dir = opendir(path_users);
	if (dir == NULL) {
		sprintf(log_buffer, "could not opendir %s", path_users);
		log_err(errno, __func__, log_buffer);
		pbs_ret = PBSE_SYSTEM;
		goto migrate_exit;
	}
	while (errno = 0, (pdirent = readdir(dir)) != NULL) {
		char	*p = NULL;

		if (pdirent->d_name[0] == '.') {
			if (pdirent->d_name[1] == '\0' ||
				(pdirent->d_name[1] == '.' &&
				pdirent->d_name[2] == '\0'))
				continue;
		}

		/* has a password */
		if ((p = strstr(pdirent->d_name, USER_PASSWORD_SUFFIX)))
			*p = '\0';

		if ((rc=user_read_password(pdirent->d_name, &cred, &len)) == 0) {
			ret = PBSD_ucred(con, pdirent->d_name, PBS_CREDTYPE_AES,
				cred, len);

			if (ret != 0) {
				sprintf(log_buffer, "Failed to migrate user %s",
					pdirent->d_name);
				log_err(pbs_errno, __func__, log_buffer);
				pbs_ret = pbs_errno;
				if (p != NULL)
					*p = USER_PASSWORD_SUFFIX[0];
				goto migrate_exit;
			}
		} else if (rc < 0) {
			sprintf(log_buffer,
				"Problem getting password for user %s",
				pdirent->d_name);
			log_err(-1, "req_user_migrate", log_buffer);
			pbs_ret = PBSE_SYSTEM;
			if (p != NULL)
				*p = USER_PASSWORD_SUFFIX[0];
			goto migrate_exit;
		}
		if (p != NULL)
			*p = USER_PASSWORD_SUFFIX[0];
	}
	if (errno != 0 && errno != ENOENT) {
		sprintf(log_buffer, "could not readdir %s", path_users);
		log_err(errno, __func__, log_buffer);
		pbs_ret = PBSE_SYSTEM;
		goto migrate_exit;
	}

migrate_exit:
	if (cred)
		(void)free(cred);

	if (con >= 0)
		svr_disconnect(con);

	if (dir != NULL)
		(void) closedir(dir);

	if (pbs_ret != PBSE_NONE)
		req_reject(pbs_ret, 0, preq);
	else
		reply_ack(preq);
	return;
}
