// SPDX-License-Identifier: GPL-3.0-or-later

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <errno.h>

#include "util.h"
#include "hashmap.h"
#include "chase.h"

#include "offline-password.h"

static OrderedHashmap *pwd_cache = NULL;
static OrderedHashmap *group_cache = NULL;

int name_to_uid_offline(const char *root, const char *user, uid_t *ret) {
	if (strcmp(user, "root") == 0) {
		*ret = 0;
		return 0;
	}
	if (!pwd_cache) {
		pwd_cache = ordered_hashmap_new();
		if (!pwd_cache)
			return -ENOMEM;
	}
	if (ordered_hashmap_contains(pwd_cache, user)) {
		*ret = (uid_t)((uintptr_t)ordered_hashmap_get(pwd_cache, user)-1);
		return 0;
	}

	_cleanup_close_ int passwd_fd = chase_and_open("/etc/passwd", root, CHASE_PREFIX_ROOT, O_CLOEXEC | O_RDONLY, NULL);
	if (passwd_fd < 0)
		return passwd_fd;
	_cleanup_fclose_ FILE *passwd = fdopen(passwd_fd, "r");
	if (!passwd)
		return -ENOMEM;
	passwd_fd = -1;

	struct passwd *result = fgetpwent(passwd);
	while(result && strcmp(result->pw_name, user) != 0) {
		ordered_hashmap_put(pwd_cache, result->pw_name, (void*)(uintptr_t)(result->pw_uid+1));
		result = fgetpwent(passwd);
	}
	if (!result)
		return -ESRCH;

	ordered_hashmap_put(pwd_cache, result->pw_name, (void*)(uintptr_t)(result->pw_uid+1));
	*ret = result->pw_uid;
	return 0;
}

int name_to_gid_offline(const char *root, const char *group, gid_t *ret) {
	if (strcmp(group, "root") == 0) {
		*ret = 0;
		return 0;
	}
	if (!group_cache) {
		group_cache = ordered_hashmap_new();
		if (!group_cache)
			return -ENOMEM;
	}
	if (ordered_hashmap_contains(group_cache, group)) {
		*ret = (gid_t)((uintptr_t)ordered_hashmap_get(group_cache, group)-1);
		return 0;
	}
	_cleanup_close_ int groupfd = chase_and_open("/etc/group", root, CHASE_PREFIX_ROOT, O_CLOEXEC | O_RDONLY, NULL);
	if (groupfd < 0)
		return groupfd;
	_cleanup_fclose_ FILE *groups = fdopen(groupfd, "r");
	if (!groups)
		return -ENOMEM;
	groupfd = -1;

	struct group *result = fgetgrent(groups);
	while(result && strcmp(result->gr_name, group) != 0) {
		ordered_hashmap_put(group_cache, result->gr_name, (void*)(uintptr_t)(result->gr_gid+1));
		result = fgetgrent(groups);
	}
	if (!result)
		return -ESRCH;

	ordered_hashmap_put(group_cache, result->gr_name, (void*)(uintptr_t)(result->gr_gid+1));
	*ret = result->gr_gid;
	return 0;
}

void offline_passwd_cache_clear(void) {
	if (group_cache) {
		ordered_hashmap_free(group_cache);
		group_cache = NULL;
	}
	if (pwd_cache) {
		ordered_hashmap_free(pwd_cache);
		pwd_cache = NULL;
	}
}
