/*	$CoreSDI: resadmin.c,v 1.18 2001/10/05 19:38:29 claudio Exp $	*/

/*
 *   Copyright (c) 2000, 2001, Core SDI S.A., Argentina
 *   All rights reserved
 *  
 *   Redistribution and use in source and binary forms, with or without
 *   modification, are permitted provided that the following conditions
 *   are met:
 *  
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *   2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *   3. Neither name of the Core SDI S.A. nor the names of its contributors
 *      may be used to endorse or promote products derived from this software
 *      without specific prior written permission.
 *  
 *   THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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.
 */

/*
 * Resources administration commands
 * Author: Claudio Castiglia
 */

#include <sys/types.h>
#include <sys/param.h>

#ifdef __linux__
#include <netinet/in.h>		/* in_addr_t */
#endif

#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>

#include "sysdep.h"
#include "packet.h"
#include "resource.h"
#include "modtypes.h"
#include "log.h"
#include "commands.h"

#define RESPERM_RNAME		"ResPerm"
#define RESADMIN_RNAME		"ResAdmin"

#define PERM_SET		0x01
#define PERM_FIND		0x02
#define	PERM_REMOVE		0x04
#define CAN_SET(x)		(x & PERM_SET)
#define CAN_FIND(x)		(x & PERM_FIND)
#define CAN_REMOVE(x)		(x & PERM_REMOVE)

extern void send_error(PACKET *, const char *, ...);
extern void internal_error(PACKET *);


/*
 * _resperms():
 *	Return permissions for specific resource.
 */
static int
_resperms(RESLIST *rlist, const char *rname)
{
	char	  full_rname[MAXPATHLEN];
	int	  i, perms;
	RESOURCE *r;

	/* Search 'RESPERM_RNAME_<rname>' resource */
	snprintf(full_rname, sizeof(full_rname), "%s_%s", RESPERM_RNAME, rname);
	r = res_find(rlist, full_rname);
	if (r == NULL) {
		/* Search 'RESPERM_RNAME' resource */
		r = res_find(rlist, RESPERM_RNAME);
		if (r == NULL)
			return (0);
	}

	/* Decode permissions */
	perms = 0;
	for (i = 0; i < r->dsize; i++)
		switch (*(char *) (r->data + i)) {
			case 'S': /* Set perm */
			case 's':
				perms |= PERM_SET;
				break;
			case 'F': /* Find perm */
			case 'f':
				perms |= PERM_FIND;
				break;
			case 'R': /* Remove perm */
			case 'r':
				perms |= PERM_REMOVE;
				break;
			default: ;
		}
	return (perms);
}


/*
 * _isadmin():
 *	Search for user administration permissions;
 *	return 1 if current auditor has permissions
 *	to administrate some given user; 0 otherwise.
 * XXX optimize
 */
static int
_isadmin(RESLIST *rlist, const char *user)
{
	RESOURCE *r;
	char	 *user_list, full_user[BUFSIZ];
	int	  isadmin;
	
	/* Search for 'RESADMIN_RNAME' resource */
	isadmin = 0;
	r = res_find(rlist, RESADMIN_RNAME);
	if (r != NULL) {
		/* Load managed users */
		user_list = (char *) malloc(r->dsize + 1);
		if (user_list == NULL)
			fatal(EX_OSERR, "_is_admin(): %s.", strerror(errno));
		memcpy(user_list, r->data, r->dsize);
		user_list[r->dsize] = '\0';

		/* Is the user specified in the list ? */
		snprintf(full_user, sizeof(full_user), "%s\n", user);
		if (!strcmp(user_list, "*") ||
		    strstr(user_list, full_user) != NULL)
			isadmin = 1;
		free(user_list);
	}
	return (isadmin);
}


/*
 * resadmin_adduser():
 *	Create a new user.
 */
void
resadmin_adduser(SESSION *s)
{
	char	 user[MAXPATHLEN];
	char	 *errstr;
	RESLIST *newuser;

	/* Get username */
	packet_get_string(s->s_packet, user, sizeof(user));
	log_debug("Adding user '%s'.", user);

	/* Add user only if client is its administrator */
	errstr = NULL;
	if (_isadmin(s->s_rlist, user)) {
		/* Do not create user resources file if it exists */
		if (res_check_list(user))
			errstr = "User exists";
		else {
			/* This forces local resources file to be created */
			/* XXX: We shouldn't depend on resource modules */
			newuser = res_open_list(user, RES_NOTREAD);
			if (newuser == NULL) {
				log_cmd(s->s_peername, strerror(errno),
				    "ADDUSER '%s'", user);
				internal_error(s->s_packet);
				return;
			}
			res_release_list(newuser);
			packet_put_int32(s->s_packet, CMD_OK);
			log_cmd(s->s_peername, "Ok", "ADDUSER '%s'", user);
			return;
		}
	} else
		errno = EACCES;

	if (errstr == NULL)
		errstr = strerror(errno);

	log_cmd(s->s_peername, errstr, "ADDUSER '%s'", user);
	send_error(s->s_packet, "Can't add user '%s': %s.", user, errstr);
}


/*
 * resadmin_rmuser():
 *	Remove an existing user.
 */
void
resadmin_rmuser(SESSION *s)
{
	char	 user[MAXPATHLEN];
	char	*errstr;

	/* Get username and remove if client is admin */
	packet_get_string(s->s_packet, user, sizeof(user));
	log_debug("Removing user '%s'.", user);

	errstr = NULL;
	if (_isadmin(s->s_rlist, user)) {
		if (!res_remove_list(user)) {
			packet_put_int32(s->s_packet, CMD_OK);
			log_cmd(s->s_peername, "Ok", "REMOVE '%s'", user);
			return;
		}
		if (errno == ENOENT)
			errstr = "User not found";
	} else
		errno = EACCES;

	if (errstr == NULL)
		errstr = strerror(errno);

	log_cmd(s->s_peername, errstr, "REMOVE '%s'", user);
	send_error(s->s_packet, "Can't remove user '%s': %s.", user, errstr);
}


/*
 * resadmin_set():
 *	Crate/modify resource.
 */
void
resadmin_set(SESSION *s)
{
	char	  user[MAXPATHLEN], rname[MAXPATHLEN];
	void	 *value;
	ssize_t	  vsize;
	int	  err, rperms;
	RESLIST  *ulist;
	RESOURCE *r;

	/* Get user name, resource name, and resource value */
	packet_get_string(s->s_packet, user, sizeof(user));
	packet_get_string(s->s_packet, rname, sizeof(rname));

	log_debug("Setting resource '%s' for user '%s'.", rname, user);

	packet_get_int32(s->s_packet, &vsize);
	if (vsize <= 0) {
		log_cmd(s->s_peername, strerror(EINVAL),
		    "SET RESOURCE '%s' FOR '%s'", rname, user);
		send_error(s->s_packet, "Can't set resource '%s' "
		    "for user '%s': %s.", rname, user, strerror(EINVAL));
		return;
	}

	if ( (value = malloc(vsize)) == NULL) {
		log_cmd(s->s_peername, strerror(errno),
		    "SET RESOURCE '%s' FOR '%s'", rname, user);
		internal_error(s->s_packet);
		return;
	}

	packet_get_raw(s->s_packet, value, vsize);

	/* Fail command if client isn't admin or hasn't SET permissions */
	err = 0;
	rperms = _resperms(s->s_rlist, rname);
	if (_isadmin(s->s_rlist, user) ||
	    (!strcmp(user, s->s_peername) && CAN_SET(rperms))) {

		/* Load user resource list */
		if (strcmp(user, s->s_peername))
			ulist = res_open_list(user, RES_FORCEREAD);
		else
			ulist = s->s_rlist;

		/* Set resource */
		r = res_find(ulist, rname);
		if (r == NULL)
			err = res_add(ulist, rname, value, vsize);
		else
			err = res_replace(ulist, r, value, vsize);
		if (!err) {
			res_save_list(ulist);
			packet_put_int32(s->s_packet, CMD_OK);
		} else
			err = errno;
	} else
		err = EACCES;

	free(value);
	if (err) {
		log_cmd(s->s_peername, strerror(err),
		    "SET RESOURCE '%s' FOR '%s'", rname, user);
		send_error(s->s_packet, "Can't set resource '%s' for "
		    "user '%s': %s.", rname, user, strerror(err));
	} else
		log_cmd(s->s_peername, "Ok", 
		    "SET RESOURCE '%s' FOR '%s'", rname, user);
}


/*
 * resadmin_find():
 *	Find a resource.
 */
void
resadmin_find(SESSION *s)
{
	char	  user[MAXPATHLEN], rname[MAXPATHLEN];
	int	  rperms;
	RESLIST  *ulist;
	RESOURCE *r;

	/* Get user and resource names */
	packet_get_string(s->s_packet, user, sizeof(user));
	packet_get_string(s->s_packet, rname, sizeof(rname));
	log_debug("Searching resource '%s' for user '%s'.", rname, user);

	/* Fail if client is not admin or has not FIND permissions */
	rperms = _resperms(s->s_rlist, rname);
	if (_isadmin(s->s_rlist, user) ||
	    (!strcmp(user, s->s_peername) && CAN_FIND(rperms))) {

		/* Load user resource list */
		if (strcmp(user, s->s_peername))
			ulist = res_open_list(user, RES_FORCEREAD);
		else
			ulist = s->s_rlist;

		/* Find resource */
		if ( (r = res_find(ulist, rname)) == NULL) {
			log_cmd(s->s_peername, "Not found", "FIND RESOURCE '%s'"
			    " FOR '%s'", rname, user);
			send_error(s->s_packet, "Can't find resource '%s' for "
			    "user '%s': Not found.", rname, user);
		} else {
			/* Send data */
			log_cmd(s->s_peername, "Ok", "FIND RESOURCE '%s' "
			    "FOR '%s'", rname, user);
			packet_put_int32(s->s_packet, CMD_OK);
			packet_put_int32(s->s_packet, r->dsize);
			packet_put_raw(s->s_packet, r->data, r->dsize);
		}
	} else  {
		log_cmd(s->s_peername, strerror(EACCES), "FIND RESOURCE '%s' "
		    "FOR '%s'", rname, user);
		send_error(s->s_packet, "Can't find resource '%s' for user "
		    "'%s': %s.", rname, user, strerror(EACCES));
	}
}


/*
 * resadmin_remove()
 *	Remove a resource.
 */
void
resadmin_remove(SESSION *s)
{
	char	 user[MAXPATHLEN], rname[MAXPATHLEN];
	int	 rperms;
	RESLIST *ulist;

	/* Get user and resource names */
	packet_get_string(s->s_packet, user, sizeof(user));
	packet_get_string(s->s_packet, rname, sizeof(rname));
	log_debug("Removing resource '%s' for '%s'.", rname, user);

	/* Fail if client is not admin or has not REMOVE permissions */
	rperms = _resperms(s->s_rlist, rname);
	if (_isadmin(s->s_rlist, user) ||
	    (!strcmp(user, s->s_peername) && CAN_REMOVE(rperms))) {

		/* Load user resource list */
		if (strcmp(user, s->s_peername))
			ulist = res_open_list(user, RES_FORCEREAD);
		else
			ulist = s->s_rlist;

		/* Remove resource */
		res_remove(ulist, rname);
		res_save_list(ulist);
		log_cmd(s->s_peername, "Ok", "REMOVE '%s' FOR '%s'", rname,
		    user);
		packet_put_int32(s->s_packet, CMD_OK);
	} else {
		log_cmd(s->s_peername, strerror(EACCES), "REMOVE '%s' FOR '%s'",
		    rname, user);
		send_error(s->s_packet, "Can't remove resource '%s' for user "
		    "'%s': %s.", rname, user, strerror(EACCES));
	}
}

