# include <kernel/kernel.h>
# include <kernel/access.h>
# include <type.h>

mapping uaccess;		/* user access */
mapping gaccess;		/* read access under /usr for everyone */

/*
 * NAME:	create()
 * DESCRIPTION:	initialize the access daemon
 */
static void create()
{
    uaccess = ([ ]);
    gaccess = ([ ]);
    catch {
	restore_object(ACCESSDATA);
    }
}

/*
 * NAME:	filter_from_file()
 * DESCRIPTION:	filter access from a file
 */
private mapping filter_from_file(mapping access, string file)
{
    if (file == "/") {
	return access;
    }
    return access[file .. file] +
	   (access[file + "/" .. file + "0"] - ({ file + "0" }));
}

/*
 * NAME:	filter_to_file()
 * DESCRIPTION:	filter access to a file
 */
private mapping filter_to_file(mapping access, string file)
{
    mapping result;
    string *path;
    int i, sz, type;

    if (file == "/") {
	/* special case */
	return ([ "/" : access["/"] ]);
    }

    result = ([ ]);
    path = explode(file, "/");
    file = "";
    for (i = 0, sz = sizeof(path); i < sz; i++) {
	file += "/" + path[i];
	result[file] = access[file];
    }

    return result;
}

/*
 * NAME:	access()
 * DESCRIPTION:	check access
 */
int access(string user, string file, int type)
{
    if (KERNEL()) {
	string dir, str, *path;
	mixed access;
	int i, sz;

	sscanf(file, USR + "/%s/%s/", dir, str);
	if (type == READ_ACCESS && (!dir || gaccess[dir] || str == "open") &&
	    sscanf(file, "/kernel/data/%*s") == 0) {
	    /*
	     * read access outside /usr, in /usr/foo/open and in selected
	     * other directories in /usr
	     */
	    return TRUE;
	}
	if (user == dir ||
	    (user && sscanf(user, USR + "/%s/", str) != 0 && str == dir)) {
	    /*
	     * full access to own/owner directory
	     */
	    return TRUE;
	}
	if (user == "admin") {
	    /*
	     * admin always has access (hardcoded)
	     */
	    return TRUE;
	}

	/*
	 * check special access
	 */
	access = uaccess[user];
	if (typeof(access) == T_MAPPING) {
	    if (access["/"] && access["/"] >= type) {
		return TRUE;
	    }

	    path = explode(file, "/");
	    file = "";
	    for (i = 0, sz = sizeof(path) - 1; i < sz; i++) {
		file += "/" + path[i];
		if (access[file] && access[file] >= type) {
		    return TRUE;
		}
	    }
	}
    }
    return FALSE;
}

/*
 * NAME:	add_user()
 * DESCRIPTION:	add a new user
 */
void add_user(string user)
{
    if (previous_program() == API_ACCESS && !uaccess[user]) {
	rlimits (-1; -1) {
	    uaccess[user] = TRUE;
# ifndef SYS_PERSISTENT
	    save_object(ACCESSDATA);
# endif
	}
    }
}

/*
 * NAME:	remove_user()
 * DESCRIPTION:	remove a user
 */
void remove_user(string user)
{
    if (previous_program() == API_ACCESS) {
	if (uaccess[user]) {
	    rlimits (-1; -1) {
		string *users;
		mixed *values, access;
		int i;

		uaccess[user] = nil;
		users = map_indices(uaccess);
		values = map_values(uaccess);
		user = USR + "/" + user;
		for (i = sizeof(values); --i >= 0; ) {
		    access = values[i];
		    if (typeof(access) == T_MAPPING) {
			access = filter_from_file(access, user);
			if (map_sizeof(access) != 0) {
			    uaccess[users[i]] -= map_indices(access);
			}
		    }
		}
# ifndef SYS_PERSISTENT
		save_object(ACCESSDATA);
# endif
	    }
	}
    }
}

/*
 * NAME:	query_users()
 * DESCRIPTION:	return list of users
 */
string *query_users()
{
    if (KERNEL()) {
	return map_indices(uaccess);
    }
}

/*
 * NAME:	set_access()
 * DESCRIPTION:	set access
 */
void set_access(string user, string file, int type)
{
    if (previous_program() == API_ACCESS) {
	mixed access, *indices;
	int i;

	access = uaccess[user];
	if (!access) {
	    error("No such user");
	}

	rlimits (-1; -1) {
	    if (type != 0) {
		/*
		 * add access
		 */
		if (access(user, file + "/", type)) {
		    return;	/* access already exists */
		}

		if (typeof(access) != T_MAPPING) {
		    /* first special access for this user */
		    uaccess[user] = ([ file : type ]);
		} else {
		    /* remove existing lesser access */
		    indices = map_indices(filter_from_file(access, file));
		    for (i = sizeof(indices); --i >= 0; ) {
			if (access[indices[i]] <= type) {
			    access[indices[i]] = nil;
			}
		    }
		    /* set access */
		    access[file] = (type != 0) ? type : nil;
		}
	    } else if (typeof(access) == T_MAPPING) {
		/*
		 * remove access
		 */
		if (!access[file]) {
		    /* remove all subdir access */
		    indices = map_indices(filter_from_file(access, file));
		    for (i = sizeof(indices); --i >= 0; ) {
			access[indices[i]] = nil;
		    }
		} else {
		    /* remove specific access */
		    access[file] = nil;
		}
		if (map_sizeof(access) == 0) {
		    uaccess[user] = TRUE;
		}
	    }
# ifndef SYS_PERSISTENT
	    save_object(ACCESSDATA);
# endif
	}
    }
}

/*
 * NAME:	query_user_access()
 * DESCRIPTION:	get all access for a user
 */
mapping query_user_access(string user)
{
    if (previous_program() == API_ACCESS) {
	mixed access;

	access = uaccess[user];
	return (typeof(access) == T_MAPPING) ? access[..] : ([ ]);
    }
}

/*
 * NAME:	query_file_access()
 * DESCRIPTION:	get all access to a file
 */
mapping query_file_access(string file)
{
    if (previous_program() == API_ACCESS) {
	mapping result;
	mixed *values, access;
	string *users;
	int i, sz;

	result = ([ ]);
	users = map_indices(uaccess);
	values = map_values(uaccess);
	for (i = 0, sz = sizeof(values); i < sz; i++) {
	    access = values[i];
	    if (typeof(access) == T_MAPPING) {
		access = filter_to_file(access, file);
		if (map_sizeof(access) != 0) {
		    result[users[i]] = access;
		}
	    }
	}
	return result;
    }
}

/*
 * NAME:	set_global_access()
 * DESCRIPTION:	set global read access for a directory
 */
void set_global_access(string dir, int flag)
{
    if (previous_program() == API_ACCESS) {
	rlimits (-1; -1) {
	    gaccess[dir] = (flag != 0) ? flag : nil;
# ifndef SYS_PERSISTENT
	    save_object(ACCESSDATA);
# endif
	}
    }
}

/*
 * NAME:	query_global_access()
 * DESCRIPTION:	return the directories under /usr where everyone has read
 *		access
 */
string *query_global_access()
{
    if (previous_program() == API_ACCESS) {
	return map_indices(gaccess);
    }
}
