/* $OpenBSD: virtual.c,v 1.3 2002/03/27 14:08:43 camield Exp $ */

/*
 * Virtual domain support.
 */

#include "params.h"

#if POP_VIRTUAL

#define _XOPEN_SOURCE 4
#define _XOPEN_SOURCE_EXTENDED
#define _XOPEN_VERSION 4
#define _XPG4_2
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <errno.h>
#include <pwd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#ifndef NAME_MAX
#define NAME_MAX			255
#endif

extern int log_error(char *s);

char *virtual_domain;
char *virtual_spool;

int virtual_startup(void)
{
	return 0;
}

static char *lookup(void)
{
	struct sockaddr_in sin;
	int length;

	length = sizeof(sin);
	if (getsockname(0, (struct sockaddr *)&sin, &length)) {
		if (errno == ENOTSOCK) return "";
		log_error("getsockname");
		return NULL;
	}
	if (length != sizeof(sin) || sin.sin_family != AF_INET) return NULL;

	return inet_ntoa(sin.sin_addr);
}

static int is_valid_user(char *user)
{
	unsigned char *p;

/* This is pretty liberal, but we're going to use direct syscalls only,
 * and they have to accept all the printable characters */
	for (p = (unsigned char *)user; *p; p++)
		if (*p < ' ' || *p > 0x7E || *p == '.' || *p == '/') return 0;

	if (p - (unsigned char *)user > NAME_MAX) return 0;

	return 1;
}

struct passwd *virtual_userpass(char *user, char *pass, int *known)
{
	struct passwd *pw, *result;
	struct stat stat;
	char auth[VIRTUAL_AUTH_SIZE];
	char *address, *pathname;
	char *template, *passwd;
	int fail;
	int fd, size;

	*known = 0;

/* Make sure we don't try to authenticate globally if something fails
 * before we find out whether the virtual domain is known to us */
	virtual_domain = "UNKNOWN";
	virtual_spool = NULL;

	if (!(address = lookup())) return NULL;

/* Authenticate globally (if supported) if run on a non-socket */
	if (!*address) {
		virtual_domain = NULL;
		return NULL;
	}

	fail = 0;
	if (!is_valid_user(user)) {
		user = "INVALID";
		fail = 1;
	}

/* This "can't happen", but is just too critical to not check explicitly */
	if (strchr(address, '/') || strchr(user, '/'))
		return NULL;

	pathname = malloc(strlen(VIRTUAL_HOME_PATH) + strlen(address) +
		strlen(VIRTUAL_AUTH_PATH) + strlen(user) + 4);
	if (!pathname) return NULL;
	sprintf(pathname, "%s/%s", VIRTUAL_HOME_PATH, address);

	if (lstat(pathname, &stat)) {
		if (errno == ENOENT)
			virtual_domain = NULL;
		else
			log_error("lstat");
		free(pathname);
		return NULL;
	}

	if (!(address = strdup(address))) return NULL;
	virtual_domain = address;

	sprintf(pathname, "%s/%s/%s/%s", VIRTUAL_HOME_PATH, address,
		VIRTUAL_AUTH_PATH, user);

	if ((fd = open(pathname, O_RDONLY)) < 0 && errno != ENOENT) {
		log_error("open");
		fail = 1;
	}

	free(pathname);

	virtual_spool = malloc(strlen(VIRTUAL_HOME_PATH) +
		strlen(virtual_domain) +
		strlen(VIRTUAL_SPOOL_PATH) + 3);
	if (!virtual_spool) {
		close(fd);
		return NULL;
	}
	sprintf(virtual_spool, "%s/%s/%s", VIRTUAL_HOME_PATH, virtual_domain,
		VIRTUAL_SPOOL_PATH);

	size = 0;
	if (fd >= 0) {
		*known = !fail;

		if ((size = read(fd, auth, sizeof(auth))) < 0) {
			log_error("read");
			size = 0;
			fail = 1;
		}

		close(fd);
	}

	if (size >= sizeof(auth)) {
		size = 0;
		fail = 1;
	}

	auth[size] = 0;

	if (!(template = strtok(auth, ":")) || !*template) {
		template = "INVALID";
		fail = 1;
	}
	if (!(passwd = strtok(NULL, ":")) || !*passwd ||
	    *passwd == '*' || *passwd == '!') {
		passwd = AUTH_DUMMY_SALT;
		fail = 1;
	}
	if (!strtok(NULL, ":")) fail = 1;

	if ((pw = getpwnam(template)))
		memset(pw->pw_passwd, 0, strlen(pw->pw_passwd));
	endpwent();

	result = NULL;
	if (!strcmp(crypt(pass, passwd), passwd) && !fail)
		result = pw;

	memset(auth, 0, sizeof(auth));

	return result;
}

#endif