db-passwd-file.c revision 229e473f71a96ce61585730b165275a93a311e4e
f6d57a2c182f63cd52819f0abb3c3d9f828afe19Timo Sirainen/* Copyright (C) 2002-2003 Timo Sirainen */
b9481bb7735c0335aa58f4749a989c2f07ab5f3bTimo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen#include "common.h"
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
34e7467e72d8a2e6b91ee6b2a50dd44570e12567Timo Sirainen#if defined (USERDB_PASSWD_FILE) || defined(PASSDB_PASSWD_FILE)
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
0df9428baed48afaff90b4d4f03792d2fd756a43Timo Sirainen#include "userdb.h"
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen#include "db-passwd-file.h"
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
cbc61fcb33b370d049c16a3c44568b4deb4e2b33Timo Sirainen#include "buffer.h"
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen#include "istream.h"
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen#include "hash.h"
f6d57a2c182f63cd52819f0abb3c3d9f828afe19Timo Sirainen#include "str.h"
f6d57a2c182f63cd52819f0abb3c3d9f828afe19Timo Sirainen#include "var-expand.h"
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
a966016e605eea27e02d73ff1412632cd684d770Timo Sirainen#include <stdlib.h>
f6d57a2c182f63cd52819f0abb3c3d9f828afe19Timo Sirainen#include <unistd.h>
9d1526ac8bcec9aff3c3a32f092ee2f3da2760b7Timo Sirainen#include <fcntl.h>
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen#include <sys/stat.h>
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
9d1526ac8bcec9aff3c3a32f092ee2f3da2760b7Timo Sirainenstatic struct db_passwd_file *passwd_files;
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen
9d1526ac8bcec9aff3c3a32f092ee2f3da2760b7Timo Sirainenstatic void passwd_file_add(struct passwd_file *pw, const char *username,
f6d57a2c182f63cd52819f0abb3c3d9f828afe19Timo Sirainen const char *pass, const char *const *args)
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen{
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen /* args = uid, gid, user info, home dir, shell, flags, mail */
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen struct passwd_user *pu;
f6d57a2c182f63cd52819f0abb3c3d9f828afe19Timo Sirainen const char *p;
8d3278a82b964217d95c340ec6f82037cdc59d19Timo Sirainen
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen if (hash_lookup(pw->users, username) != NULL) {
f6d57a2c182f63cd52819f0abb3c3d9f828afe19Timo Sirainen i_error("passwd-file %s: User %s exists more than once",
f6d57a2c182f63cd52819f0abb3c3d9f828afe19Timo Sirainen pw->path, username);
cf7164ece50797a67fc4bfb5889022ac93a36a8aTimo Sirainen return;
f6d57a2c182f63cd52819f0abb3c3d9f828afe19Timo Sirainen }
f6d57a2c182f63cd52819f0abb3c3d9f828afe19Timo Sirainen
pu = p_new(pw->pool, struct passwd_user, 1);
pu->user_realm = p_strdup(pw->pool, username);
pu->realm = strchr(pu->user_realm, '@');
if (pu->realm != NULL)
pu->realm++;
p = pass == NULL ? NULL : strchr(pass, '[');
if (p == NULL) {
pu->password = p_strdup(pw->pool, pass);
} else {
/* password[type] - we're being libpam-pwdfile compatible
here. it uses 13 = DES and 34 = MD5. For backwards
comaptibility with ourself, we have also 56 = Digest-MD5. */
pass = t_strdup_until(pass, p);
if (p[1] == '3' && p[2] == '4') {
pu->password = p_strconcat(pw->pool, "{PLAIN-MD5}",
pass, NULL);
} else if (p[1] == '5' && p[2] == '6') {
pu->password = p_strconcat(pw->pool, "{DIGEST-MD5}",
pass, NULL);
if (strlen(pu->password) != 32 + 12) {
i_error("passwd-file %s: User %s "
"has invalid password",
pw->path, username);
return;
}
} else {
pu->password = p_strconcat(pw->pool, "{CRYPT}",
pass, NULL);
}
}
if (*args != NULL) {
pu->uid = userdb_parse_uid(NULL, *args);
if (pu->uid == 0 || pu->uid == (uid_t)-1) {
i_error("passwd-file %s: User %s has invalid UID %s",
pw->path, username, *args);
return;
}
args++;
}
if (*args != NULL) {
pu->gid = userdb_parse_gid(NULL, *args);
if (pu->gid == 0 || pu->gid == (gid_t)-1) {
i_error("passwd-file %s: User %s has invalid GID %s",
pw->path, username, *args);
return;
}
args++;
}
/* user info */
if (*args != NULL)
args++;
/* home */
if (*args != NULL) {
pu->home = p_strdup_empty(pw->pool, *args);
args++;
}
/* shell */
if (*args != NULL)
args++;
/* flags */
if (*args != NULL) {
/* no flags currently */
args++;
}
/* rest is MAIL environment */
if (*args != NULL) {
string_t *str = t_str_new(100);
str_append(str, *args);
args++;
while (*args != NULL) {
str_append_c(str, ':');
str_append(str, *args);
args++;
}
pu->mail = p_strdup_empty(pw->pool, str_c(str));
}
hash_insert(pw->users, pu->user_realm, pu);
}
static struct passwd_file *
passwd_file_new(struct db_passwd_file *db, const char *expanded_path)
{
struct passwd_file *pw;
pw = i_new(struct passwd_file, 1);
pw->db = db;
pw->path = i_strdup(expanded_path);
pw->fd = -1;
hash_insert(db->files, pw->path, pw);
return pw;
}
static bool passwd_file_open(struct passwd_file *pw)
{
const char *no_args = NULL;
struct istream *input;
const char *const *args;
const char *line;
struct stat st;
int fd;
fd = open(pw->path, O_RDONLY);
if (fd == -1) {
i_error("passwd-file %s: Can't open file: %m", pw->path);
return FALSE;
}
if (fstat(fd, &st) != 0) {
i_error("passwd-file %s: fstat() failed: %m", pw->path);
(void)close(fd);
return FALSE;
}
pw->fd = fd;
pw->stamp = st.st_mtime;
pw->pool = pool_alloconly_create("passwd_file", 10240);;
pw->users = hash_create(default_pool, pw->pool, 100,
str_hash, (hash_cmp_callback_t *)strcmp);
input = i_stream_create_file(pw->fd, default_pool, 4096, FALSE);
while ((line = i_stream_read_next_line(input)) != NULL) {
if (*line == '\0' || *line == ':' || *line == '#')
continue; /* no username or comment */
t_push();
args = t_strsplit(line, ":");
if (args[1] != NULL) {
/* at least username+password */
passwd_file_add(pw, args[0], args[1],
pw->db->userdb ? args+2 : &no_args);
} else {
/* only username */
passwd_file_add(pw, args[0], NULL, &no_args);
}
t_pop();
}
i_stream_unref(&input);
if (pw->db->debug) {
i_info("passwd-file %s: Read %u users",
pw->path, hash_size(pw->users));
}
return TRUE;
}
static void passwd_file_close(struct passwd_file *pw)
{
if (pw->fd != -1) {
if (close(pw->fd) < 0)
i_error("passwd-file %s: close() failed: %m", pw->path);
pw->fd = -1;
}
if (pw->users != NULL) {
hash_destroy(pw->users);
pw->users = NULL;
}
if (pw->pool != NULL) {
pool_unref(pw->pool);
pw->pool = NULL;
}
}
static void passwd_file_free(struct passwd_file *pw)
{
hash_remove(pw->db->files, pw->path);
passwd_file_close(pw);
i_free(pw->path);
i_free(pw);
}
static bool passwd_file_sync(struct passwd_file *pw)
{
struct stat st;
if (stat(pw->path, &st) < 0) {
/* with variables don't give hard errors, or errors about
nonexisting files */
if (errno != ENOENT)
i_error("passwd-file %s: stat() failed: %m", pw->path);
passwd_file_free(pw);
return FALSE;
}
if (st.st_mtime != pw->stamp) {
passwd_file_close(pw);
return passwd_file_open(pw);
}
return TRUE;
}
static struct db_passwd_file *db_passwd_file_find(const char *path)
{
struct db_passwd_file *f;
for (f = passwd_files; f != NULL; f = f->next) {
if (strcmp(f->path, path) == 0)
return f;
}
return NULL;
}
struct db_passwd_file *
db_passwd_file_parse(const char *path, bool userdb, bool debug)
{
struct db_passwd_file *db;
const char *p;
bool percents = FALSE;
db = db_passwd_file_find(path);
if (db != NULL) {
db->refcount++;
if (userdb && !db->userdb) {
db->userdb = TRUE;
if (db->default_file != NULL) {
/* resync */
db->default_file->stamp = 0;
}
}
return db;
}
db = i_new(struct db_passwd_file, 1);
db->refcount = 1;
db->userdb = userdb;
db->debug = debug;
db->files = hash_create(default_pool, default_pool, 100,
str_hash, (hash_cmp_callback_t *)strcmp);
for (p = path; *p != '\0'; p++) {
if (*p == '%' && p[1] != '\0') {
p++;
if (*p == 'd') {
/* drop domains out only if %d is given
without modifiers */
db->domain_var = TRUE;
}
if (var_get_key(p) == '%')
percents = TRUE;
else
db->vars = TRUE;
}
}
if (percents && !db->vars) {
/* just extra escaped % chars. remove them. */
struct var_expand_table empty_table[1];
string_t *dest;
empty_table[0].key = '\0';
dest = t_str_new(256);
var_expand(dest, path, empty_table);
path = str_c(dest);
}
db->path = i_strdup(path);
if (!db->vars) {
/* no variables, open the file immediately */
db->default_file = passwd_file_new(db, path);
if (!passwd_file_open(db->default_file))
exit(FATAL_DEFAULT);
}
db->next = passwd_files;
passwd_files = db;
return db;
}
void db_passwd_file_unref(struct db_passwd_file **_db)
{
struct db_passwd_file *db = *_db;
struct db_passwd_file **p;
struct hash_iterate_context *iter;
void *key, *value;
*_db = NULL;
i_assert(db->refcount >= 0);
if (--db->refcount > 0)
return;
for (p = &passwd_files; *p != NULL; p = &(*p)->next) {
if (*p == db) {
*p = db->next;
break;
}
}
iter = hash_iterate_init(db->files);
while (hash_iterate(iter, &key, &value))
passwd_file_free(value);
hash_iterate_deinit(iter);
hash_destroy(db->files);
i_free(db->path);
i_free(db);
}
static const char *path_fix(const char *path)
{
const char *p;
p = strchr(path, '/');
if (p == NULL)
return path;
/* most likely this is an invalid request. just cut off the '/' and
everything after it. */
return t_strdup_until(path, p);
}
struct passwd_user *
db_passwd_file_lookup(struct db_passwd_file *db, struct auth_request *request)
{
struct passwd_file *pw;
struct passwd_user *pu;
if (!db->vars)
pw = db->default_file;
else {
const struct var_expand_table *table;
string_t *dest;
t_push();
table = auth_request_get_var_expand_table(request, path_fix);
dest = t_str_new(256);
var_expand(dest, db->path, table);
pw = hash_lookup(db->files, str_c(dest));
if (pw == NULL) {
/* doesn't exist yet. create lookup for it. */
pw = passwd_file_new(db, str_c(dest));
}
t_pop();
}
if (!passwd_file_sync(pw)) {
auth_request_log_info(request, "passwd-file",
"no passwd file");
return NULL;
}
t_push();
pu = hash_lookup(pw->users, !db->domain_var ? request->user :
t_strcut(request->user, '@'));
if (pu == NULL)
auth_request_log_info(request, "passwd-file", "unknown user");
t_pop();
return pu;
}
#endif