view src/auth/userinfo-passwd-file.c @ 903:fd8888f6f037 HEAD

Naming style changes, finally got tired of most of the typedefs. Also the previous enum -> macro change reverted so that we don't use the highest bit anymore, that's incompatible with old indexes so they will be rebuilt.
author Timo Sirainen <tss@iki.fi>
date Sun, 05 Jan 2003 15:09:51 +0200
parents 5043e48c022f
children 2d6db119ca9a
line wrap: on
line source

/* Copyright (C) 2002 Timo Sirainen */

#include "config.h"
#undef HAVE_CONFIG_H

#ifdef USERINFO_PASSWD_FILE

#include "userinfo-passwd.h"

#include "buffer.h"
#include "istream.h"
#include "hash.h"
#include "hex-binary.h"
#include "md5.h"
#include "mycrypt.h"

#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>

struct passwd_file {
	pool_t pool;

	char *path;
	time_t stamp;
	int fd;

	struct hash_table *users;
};

enum password_type {
	PASSWORD_DES,
	PASSWORD_MD5,
	PASSWORD_DIGEST_MD5
};

struct passwd_user {
	char *user_realm; /* user:realm */
	const char *realm; /* NULL or points to user_realm */
	char *password;
	char *home;
	char *mail;

	uid_t uid;
	gid_t gid;

	enum password_type password_type;
	unsigned int chroot:1;
};

static struct passwd_file *passwd_file;

static void passwd_file_sync(void);

static int get_reply_data(struct passwd_user *pu,
			  struct auth_cookie_reply_data *reply)
{
	const char *user;
	struct passwd *pw;

	if (pu->uid == 0 || pu->gid == 0 ||
	    (pu->home == NULL && pu->mail == NULL)) {
		/* all required information was not set in passwd file,
		   check from system's passwd */
		user = pu->realm == NULL ? pu->user_realm :
			t_strndup(pu->user_realm, strlen(pu->user_realm) -
				  strlen(pu->realm) - 1);

		pw = getpwnam(user);
		if (pw == NULL)
			return FALSE;

		passwd_fill_cookie_reply(pw, reply);
	}

	if (pu->uid != 0)
		reply->uid = pu->uid;
	if (pu->gid != 0)
		reply->gid = pu->gid;

	if (pu->home != NULL) {
		if (strocpy(reply->home, pu->home, sizeof(reply->home)) < 0)
			i_panic("home overflow");
	}

	if (pu->mail != NULL) {
		if (strocpy(reply->mail, pu->mail, sizeof(reply->mail)) < 0)
			i_panic("mail overflow");
	}

	if (strocpy(reply->virtual_user, pu->user_realm,
		    sizeof(reply->virtual_user)) < 0)
		i_panic("virtual_user overflow");

	if (pu->realm != NULL) {
		/* @UNSAFE: ':' -> '@' to make it look prettier */
		size_t pos;

		pos = (size_t) (pu->realm - (const char *) pu->user_realm);
		reply->virtual_user[pos-1] = '@';
	}

	reply->chroot = pu->chroot;
	return TRUE;
}

static int passwd_file_verify_plain(const char *user, const char *password,
				    struct auth_cookie_reply_data *reply)
{
	struct passwd_user *pu;
	const char *const *tmp;
	unsigned char digest[16];
	const char *str;

	passwd_file_sync();

	/* find it from all realms */
	pu = hash_lookup(passwd_file->users, user);
	if (pu == NULL) {
		t_push();
		for (tmp = auth_realms; *tmp != NULL; tmp++) {
                        str = t_strconcat(user, ":", *tmp, NULL);
			pu = hash_lookup(passwd_file->users, str);
		}
		t_pop();
	}

	if (pu == NULL)
		return FALSE;

	/* verify that password matches */
	switch (pu->password_type) {
	case PASSWORD_DES:
		if (strcmp(mycrypt(password, pu->password), pu->password) != 0)
			return FALSE;
		break;
	case PASSWORD_MD5:
		md5_get_digest(password, strlen(password), digest);
		str = binary_to_hex(digest, sizeof(digest));

		if (strcmp(str, pu->password) != 0)
			return FALSE;
		break;
	case PASSWORD_DIGEST_MD5:
		/* user:realm:passwd */
		str = t_strconcat(pu->user_realm,
				  pu->realm == NULL ? ":" : "",  ":",
				  password, NULL);

		md5_get_digest(str, strlen(str), digest);
		str = binary_to_hex(digest, sizeof(digest));

		if (strcmp(str, pu->password) != 0)
			return FALSE;
		break;
	default:
                i_unreached();
	}

	/* found */
	return get_reply_data(pu, reply);
}

static int passwd_file_lookup_digest_md5(const char *user, const char *realm,
					 unsigned char digest[16],
					 struct auth_cookie_reply_data *reply)
{
	const char *id;
	struct passwd_user *pu;
	buffer_t *buf;

	passwd_file_sync();

	/* FIXME: we simply ignore UTF8 setting.. */

	id = realm == NULL || *realm == '\0' ? user :
		t_strconcat(user, ":", realm, NULL);

	pu = hash_lookup(passwd_file->users, id);
	if (pu == NULL)
		return FALSE;

	/* found */
	i_assert(strlen(pu->password) == 32);

	buf = buffer_create_data(data_stack_pool, digest, 16);
	if (!hex_to_binary(pu->password, buf))
		return FALSE;
	
	return get_reply_data(pu, reply);
}

static void passwd_file_add(struct passwd_file *pw, const char *username,
			    const char *pass, const char *const *args)
{
	/* args = uid, gid, user info, home dir, shell, realm, mail, chroot */
	struct passwd_user *pu;
	const char *p;

	if (strlen(username) >= AUTH_MAX_USER_LEN) {
		i_error("Username %s is too long (max. %d chars) in password "
			"file %s", username, AUTH_MAX_USER_LEN, pw->path);
		return;
	}

	pu = p_new(pw->pool, struct passwd_user, 1);

	p = strchr(pass, '[');
	if (p == NULL) {
		pu->password = p_strdup(pw->pool, pass);
		pu->password_type = PASSWORD_DES;
	} else {
		/* password[type] - we're being libpam-pwdfile compatible
		   here. it uses 13 = DES and 34 = MD5. We add
		   56 = Digest-MD5. */
		pu->password = p_strndup(pw->pool, pass, (size_t) (p-pass));
		if (p[1] == '3' && p[2] == '4') {
			pu->password_type = PASSWORD_MD5;
			str_lcase(pu->password);
		} else if (p[1] == '5' && p[2] == '6') {
			pu->password_type = PASSWORD_DIGEST_MD5;
			if (strlen(pu->password) != 32) {
				i_error("User %s has invalid password in "
					"file %s", username, pw->path);
				return;
			}
			str_lcase(pu->password);
		} else {
			pu->password_type = PASSWORD_DES;
		}
	}

	if (args[0] != NULL) {
		pu->uid = atoi(args[0]);
		if (pu->uid == 0) {
			i_error("User %s has UID 0 in password file %s",
				username, pw->path);
			return;
		}
		args++;
	}

	if (args[0] != NULL) {
		pu->gid = atoi(args[0]);
		if (pu->gid == 0) {
			i_error("User %s has GID 0 in password file %s",
				username, pw->path);
			return;
		}
		args++;
	}

	/* user info */
	if (args[0] != NULL)
		args++;

	/* home */
	if (args[0] != NULL) {
		if (strlen(args[0]) >= AUTH_MAX_HOME_LEN) {
			i_error("User %s has too long home directory in "
				"password file %s", username, pw->path);
			return;
		}

		pu->home = p_strdup(pw->pool, args[0]);
		args++;
	}

	/* shell */
	if (args[0] != NULL)
		args++;

	/* realm */
	if (args[0] == NULL || *args[0] == '\0') {
		pu->user_realm = p_strdup(pw->pool, username);
		if (hash_lookup(pw->users, username) != NULL) {
			i_error("User %s already exists in password file %s",
				username, pw->path);
			return;
		}
	} else {
		pu->user_realm = p_strconcat(pw->pool, username, ":",
					     args[0], NULL);
		pu->realm = pu->user_realm + strlen(username)+1;

		if (hash_lookup(pw->users, pu->user_realm) != NULL) {
			i_error("User %s already exists in realm %s in "
				"password file %s", username, args[0],
				pw->path);
			return;
		}
	}

	/* mail storage */
	if (args[0] != NULL) {
		if (strlen(args[0]) >= AUTH_MAX_MAIL_LEN) {
			i_error("User %s has too long mail storage in "
				"password file %s", username, pw->path);
			return;
		}

		pu->mail = p_strdup(pw->pool, args[0]);
		args++;
	}

	/* chroot */
	if (args[0] != NULL && strstr(args[0], "chroot") != NULL)
		pu->chroot = TRUE;

	hash_insert(pw->users, pu->user_realm, pu);
}

static void passwd_file_parse_file(struct passwd_file *pw)
{
	struct istream *input;
	const char *const *args;
	const char *line;

	input = i_stream_create_file(pw->fd, default_pool, 2048, FALSE);
	for (;;) {
		line = i_stream_next_line(input);
		if (line == NULL) {
			if (i_stream_read(input) <= 0)
				break;
                        continue;
		}

		if (*line == '\0' || *line == ':')
			continue; /* no username */

		t_push();
		args = t_strsplit(line, ":");
		if (args[1] != NULL && IS_VALID_PASSWD(args[1])) {
			/* valid user/pass */
			passwd_file_add(pw, args[0], args[1], args+2);
		}
		t_pop();
	}
	i_stream_unref(input);
}

static struct passwd_file *passwd_file_parse(const char *path)
{
	struct passwd_file *pw;
	pool_t pool;
	struct stat st;
	int fd;

	fd = open(path, O_RDONLY);
	if (fd == -1) {
		i_fatal("Can't open passwd-file %s: %m", path);
	}

	if (fstat(fd, &st) != 0)
		i_fatal("fstat() failed for passwd-file %s: %m", path);

	pool = pool_alloconly_create("passwd_file", 10240);
	pw = p_new(pool, struct passwd_file, 1);
	pw->pool = pool;
	pw->path = p_strdup(pool, path);
	pw->stamp = st.st_mtime;
	pw->fd = fd;
	pw->users = hash_create(pool, 100, str_hash, (HashCompareFunc) strcmp);

	passwd_file_parse_file(pw);
	return pw;
}

static void passwd_file_free(struct passwd_file *pw)
{
	pool_unref(pw->pool);
}

static void passwd_file_init(const char *args)
{
	passwd_file = passwd_file_parse(args);
}

static void passwd_file_deinit(void)
{
	passwd_file_free(passwd_file);
}

static void passwd_file_sync(void)
{
	const char *path;
	struct stat st;

	if (stat(passwd_file->path, &st) < 0)
		i_fatal("stat() failed for %s: %m", passwd_file->path);

	if (st.st_mtime != passwd_file->stamp) {
		path = t_strdup(passwd_file->path);
		passwd_file_free(passwd_file);
		passwd_file = passwd_file_parse(path);
	}
}

struct user_info_module userinfo_passwd_file = {
	passwd_file_init,
	passwd_file_deinit,

	passwd_file_verify_plain,
        passwd_file_lookup_digest_md5
};

#endif