/*
auth.c - MessageWall authentication definitions
Copyright (C) 2002 Ian Gulliver

This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as
published by the Free Software Foundation.

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 <stdio.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <firestring.h>
#include "dbase.h"
#include "auth.h"
#include "security.h"
#include "smtp.h"
#include "messagewall.h"

static const char tagstring[] = "$Id: auth.c,v 1.13 2002/07/16 18:37:20 ian Exp $";

int auth_reload;

void auth_clear() {
	int i;
	struct messagewall_relay_auth_t *relay_auth1, *relay_auth2;

	for (i = 0; i < 65536; i++) {
		relay_auth1 = relay_auth_hash[i];
		while (relay_auth1 != NULL) {
			relay_auth2 = relay_auth1;
			relay_auth1 = relay_auth1->next;
			firestring_estr_free(&relay_auth2->username);
			firestring_estr_free(&relay_auth2->hash);
			free(relay_auth2);
		}
		relay_auth_hash[i] = NULL;
	}
}

void auth_load_relay_auth() {
	struct messagewall_relay_auth_t *relay_auth1;
	char *slash;
	char line[1025];
	unsigned int u;
	FILE *f;

	if (relay_auth == NULL)
		return;

	/*
	 * we're inside the chroot; everything should be root controlled and fopen shouldn't be a problem
	 */
	f = fopen(relay_auth,"r"); /* ITS4: ignore fopen */
	if (f == NULL) {
		perror("Unable to open relay_auth file");
		exit(100);
	}
	/*
	 * line really does have 1025 bytes of space
	 */
	while (fgets(line,1025,f) != NULL) { /* ITS4: ignore fgets */
		/*
		 * check for comment
		 */
		if (line[0] == '#')
			continue;

		firestring_chomp(line);

		/* 
		 * check for blank line
		 */
		if (line[0] == '\0')
			continue;

		/*
		 * make sure it's delimited
		 */
		slash = strchr(line,':');
		if (slash == NULL)
			continue;

		*(slash++) = '\0';

		/*
		 * build new object to insert
		 */
		relay_auth1 = messagewall_malloc(struct messagewall_relay_auth_t);
		firestring_estr_alloc(&relay_auth1->username,strlen(line));
		firestring_estr_strcpy(&relay_auth1->username,line);
		firestring_estr_alloc(&relay_auth1->hash,strlen(slash));
		firestring_estr_strcpy(&relay_auth1->hash,slash);
		firestring_estr_0(&relay_auth1->hash);

		/* 
		 * calculate hash
		 */
		u = dbase_binary_hash(&relay_auth1->username,0);

		/*
		 * insert into table
		 */
		relay_auth1->next = relay_auth_hash[u];
		relay_auth_hash[u] = relay_auth1;
	}
	fclose(f);
}

void auth_load() {
	auth_clear();
	auth_load_relay_auth();
}

int auth_check_password(struct firestring_estr_t *username, struct firestring_estr_t *password) {
	int c;

	if (relay_auth == NULL)
		return 1;

	if (write(auth_writemap[process],&username->l,sizeof(username->l)) != sizeof(username->l)) {
		fprintf(stderr,"{%d} AUTH/FATAL: unable to write username length",process);
		exit(1);
	}
	if (write(auth_writemap[process],username->s,username->l) != username->l) {
		fprintf(stderr,"{%d} AUTH/FATAL: unable to write username\n",process);
		exit(1);
	}
	if (write(auth_writemap[process],&password->l,sizeof(password->l)) != sizeof(password->l)) {
		fprintf(stderr,"{%d} AUTH/FATAL: unable to write password length\n",process);
		exit(1);
	}
	if (write(auth_writemap[process],password->s,password->l) != password->l) {
		fprintf(stderr,"{%d} AUTH/FATAL: unable to write password\n",process);
		exit(1);
	}
	if (read(auth_readmap[process],&c,sizeof(c)) != sizeof(c)) {
		fprintf(stderr,"{%d} AUTH/FATAL: unable to read result\n",process);
		exit(1);
	}

	return c;
}

static int auth_internal_check_password(struct firestring_estr_t *username, struct firestring_estr_t *password) {
	struct messagewall_relay_auth_t *iter;
	unsigned int u;

	u = dbase_binary_hash(username,0);

	iter = relay_auth_hash[u];
	while (iter != NULL) {
		if (firestring_estr_estrcmp(&iter->username,username,0) == 0) {
			/*
			 * we've found the user
			 */
			firestring_estr_0(password);
			if (firestring_estr_strcmp(&iter->hash,crypt(password->s,iter->hash.s)) == 0)
				return 0;
			else
				return 1;
		}
		iter = iter->next;
	}

	/*
	 * user does not exist
	 */
	return 1;
}

int auth_main(struct firestring_conf_t *config, int r, int w, int j) {
	struct firestring_estr_t username, password;
	int c;
	int i;

	security_context(config,SECURITY_MWALLA);

	firestring_conf_free(config);

	firestring_estr_alloc(&username,SMTP_LINE_MAXLEN);
	firestring_estr_alloc(&password,SMTP_LINE_MAXLEN);

	for (i = 0; i < 65536; i++)
		relay_auth_hash[i] = NULL;
	
	auth_load_relay_auth();

	auth_reload = 0;

	signal(SIGUSR2,auth_handle_usr2);


	/*
	 * write out my pid file
	 */
	{
		FILE *f;
		char filename[512];

		firestring_snprintf(filename,512,"%s/mwalla.%d.pid",pid_dir,j);
		f = fopen(filename,"w");
		if (f == NULL) {
			perror("fopen(pid_dir/mwalla.pid)");
			exit(100);
		}
		fprintf(f,"%d\n",getpid());
		fclose(f);
	}

	while (1) {
username_length:
		i = read(r,&username.l,sizeof(username.l));
		if (i == -1 && errno == EINTR)
			goto username_length;
		if (i == 0) {
			fprintf(stderr,"{%d} AUTH/FATAL: auth process lost connection to main process\n",process);
			exit(1);
		}
		if (i != sizeof(username.l)) {
			fprintf(stderr,"{%d} AUTH/FATAL: failed reading username length\n",process);
			exit(1);
		}
		if (username.l > SMTP_LINE_MAXLEN) {
			fprintf(stderr,"{%d} AUTH/FATAL: username too long\n",process);
			exit(1);
		}
		username.a = username.l;
username:
		i = read(r,username.s,username.l);
		if (i == -1 && errno == EINTR)
			goto username;
		if (i != username.l) {
			fprintf(stderr,"{%d} AUTH/FATAL: failed reading username\n",process);
			exit(1);
		}
password_length:
		i = read(r,&password.l,sizeof(password.l));
		if (i == -1 && errno == EINTR)
			goto password_length;
		if (i != sizeof(password.l)) {
			fprintf(stderr,"{%d} AUTH/FATAL: failed reading password length\n",process);
			exit(1);
		}
		if (password.l > SMTP_LINE_MAXLEN) {
			fprintf(stderr,"{%d} AUTH/FATAL: password too long\n",process);
			exit(1);
		}
		password.a = password.l;
password:
		i = read(r,password.s,password.l);
		if (i == -1 && errno == EINTR)
			goto password;
		if (i != password.l) {
			fprintf(stderr,"{%d} AUTH/FATAL: failed reading password\n",process);
			exit(1);
		}
		if (auth_reload == 1) {
			auth_load();
			auth_reload = 0;
		}
		c = auth_internal_check_password(&username,&password);
		if (write(w,&c,sizeof(c)) != sizeof(c)) {
			fprintf(stderr,"{%d} AUTH/FATAL: failed to write result\n",process);
			exit(1);
		}
	}

	exit(0);
}

void auth_handle_usr2(int s) {
	auth_reload = 1;

	fprintf(stderr,"{%d} AUTH/STATUS: received USR2, reloading authentication database\n",process);

	signal(s,auth_handle_usr2);
}


syntax highlighted by Code2HTML, v. 0.9.1