/* qmail-users.c - qmail users/cdb lookup routines
 * Copyright (C) 2010  Bruce Guenter <bruce@untroubled.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
#include <bglibs/sysdeps.h>
#include <errno.h>
#include <fcntl.h>
#include <pwd.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

#include <bglibs/cdb.h>
#include <bglibs/cdb.h>
#include <bglibs/iobuf.h>
#include <bglibs/str.h>

#include "qmail.h"

static str users_path;
static int users_fd = -1;
static struct cdb users_cdb;
static struct stat users_stat;

int qmail_users_reinit(void)
{
  struct stat s;
  /* If we can see the users CDB file... */
  if (stat(users_path.s, &s) != -1) {
    /* If it was either not open or has changed since the last open... */
    if (users_fd == -1 ||
	s.st_ino != users_stat.st_ino ||
	s.st_mtime != users_stat.st_mtime ||
	s.st_size != users_stat.st_size) {
      /* If it was previously open, close it */
      if (users_fd != -1) {
	close(users_fd);
	cdb_free(&users_cdb);
      }
      /* And re-open it */
      if ((users_fd = open(users_path.s, O_RDONLY)) != -1) {
	fstat(users_fd, &users_stat);
	cdb_init(&users_cdb, users_fd);
      }
    }
  }
  else if (users_fd != -1) {
    close(users_fd);
    cdb_free(&users_cdb);
    users_fd = -1;
  }
  return 0;
}

int qmail_users_init(void)
{
  if (!str_copy2s(&users_path, qmail_root, "/users/cdb"))
    return -1;
  return qmail_users_reinit();
}

static int lookup_userscdb(struct qmail_user* u,
			   str* name, char dash)
{
  char* ptr;
  const char* end;
  const char* user;
  const char* home;
  int i;

  if (!str_spliceb(name, 0, 0, "!", 1)
      || (name->len > 1 && !str_catc(name, dash))) {
    errno = ENOMEM;
    return -1;
  }

  if ((i = cdb_get(&users_cdb, name, name)) <= 0)
    return i;

  /* name now contains:
   * user NUL uid NUL gid NUL home NUL dash NUL ext
   */
  errno = EDOM;
  ptr = name->s;
  end = name->s + name->len;
  user = ptr;
  if ((ptr += strlen(ptr) + 1) >= end) return -1;
  u->uid = strtoul(ptr, &ptr, 10);
  if (*ptr++ != 0 || ptr >= end) return -1;
  u->gid = strtoul(ptr, &ptr, 10);
  if (*ptr++ != 0 || ptr >= end) return -1;
  home = ptr;
  if ((ptr += strlen(ptr) + 1) >= end) return -1;
  if ((u->dash = *ptr) != 0) ++ptr;
  if (*ptr++ != 0 || ptr > end) return -1;

  if (!str_copys(&u->user, user)
      || !str_copys(&u->homedir, home)
      || !str_copyb(&u->ext, ptr, end-ptr)) {
    errno = ENOMEM;
    return -1;
  }

  return 1;
}

static int lookup_passwd(struct qmail_user* u, const str* namestr, char dash)
{
  const struct passwd* pw;
  const char* name;

  if (*(name = namestr->s) == 0)
    name = "alias";
  if ((pw = getpwnam(name)) == 0)
    return (errno == ETXTBSY) ? -1 : 0;

  if (!str_copys(&u->user, pw->pw_name)
      || !str_copys(&u->homedir, pw->pw_dir)
      || !str_copys(&u->ext, "")) {
    errno = ENOMEM;
    return -1;
  }
  u->uid = pw->pw_uid;
  u->gid = pw->pw_gid;
  u->dash = dash;
  return 1;
}

int qmail_users_lookup(struct qmail_user* u, const char* name, char dash)
{
  static str lname;
  if (!str_copys(&lname, name)){
    errno = ENOMEM;
    return -1;
  }
  str_lower(&lname);
  if (users_fd != -1) {
    switch (lookup_userscdb(u, &lname, dash)) {
    case -1: return -1;
    case 0: break;
    default: return 1;
    }
    if (!str_copys(&lname, name)){
      errno = ENOMEM;
      return -1;
    }
  }
  return lookup_passwd(u, &lname, dash);
}

int qmail_users_lookup_split(struct qmail_user* u, const char* name,
			     str* local, str* ext)
{
  static str account;
  int i;

  /* Check if the name is a base UNIX user. */
  if (!str_copys(local, name)) return -1;
  if (!str_copys(ext, "")) return -1;
  switch (qmail_users_lookup(u, name, 0)) {
  case -1: return -1;
  case 0:  break;
  default: return 1;
  }

  /* Now, look for increasingly shorter base-ext pairs */
  if (!str_copy(&account, local)) return -1;
  i = account.len;
  while (i > 0 && (i = str_findprev(&account, '-', i-1)) != -1) {
    if (!str_copyb(local, account.s, i)) return -1;
    if (!str_copyb(ext, account.s+i+1, account.len-i-1)) return -1;
    switch (qmail_users_lookup(u, local->s, '-')) {
    case -1: return -1;
    case 0: continue;
    default: return 1;
    }
  }

  switch (qmail_users_lookup(u, "", '-')) {
  case -1: return -1;
  case 0: return 0;
  }
  str_copyb(local, "", 0);
  if (!str_copy(ext, &account)) return -1;
  return 1;
}
