/* $Id: auth.c,v 1.13 2004/03/31 20:25:21 doug Exp $
*
* This file is part of EXACT.
*
* EXACT 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.
*
* EXACT 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 EXACT; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
/* These functions manage the authentication database.
*
* The database is an array of hostname and time pairs. The array is
* dynamically sized. A shadow array is used when cleaning the primary.
* Entries in the primary are checked, and if they are still live are copied to
* the secondary.
*
*/
#include "config.h"
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef WITH_DB
#include <db.h>
#include <fcntl.h>
#endif
#if TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# if HAVE_SYS_TIME_H
# include <sys/time.h>
# else
# include <time.h>
# endif
#endif
#include "logger.h"
#include "conffile.h"
#include "match.h"
// an entry in the authentication database
typedef struct auth_entry_str {
char hostname[MATCH_LOGIN_HOSTNAME_MAX];
time_t t;
} auth_entry;
// the authentication database
auth_entry *auth;
// the shadow authentication database
auth_entry *shadow_auth;
#ifdef WITH_DB
// The Berkeley Database
// this may not be used, optionally
DB *db;
#endif
// auth_max used to be 1024, but that's really quite low. we allocate
// MATCH_LOGIN_HOSTNAME_MAX + 4 bytes per auth structure, which is about 64K
// for 16,384 structures.
int auth_max=16384;
int auth_cur=0;
int auth_alarm=0;
/* auth_dump: dump the current state table to the dump file.
*
* this is triggered by the receipt of a SIGUSR1
*/
void auth_text_dump() {
int i;
FILE *f=fopen(conffile_param("dumpfile"),"w");
if(!f) {
logger(LOG_ERR, "Unable to write to dump file %s\n", conffile_param("dumpfile"));
return;
}
chmod(conffile_param("dumpfile"),0640);
logger(LOG_NOTICE, "dumping state\n");
for(i=0;i<auth_cur;i++) {
char tbuff[1024];
strftime(tbuff, 1023, "%Y-%m-%d %H:%M:%S", localtime(&auth[i].t));
fprintf(f,"%s\t\t%d (%s)\n", auth[i].hostname,(int)auth[i].t, tbuff);
}
fclose(f);
}
#ifdef WITH_DB
void auth_db_dump() {
DBC *dbc;
DBT key, data;
int ret;
char hostname[MATCH_LOGIN_HOSTNAME_MAX];
FILE *f=fopen(conffile_param("dumpfile"),"w");
if(!f) {
logger(LOG_ERR, "Unable to write to dump file %s\n", conffile_param("dumpfile"));
return;
}
chmod(conffile_param("dumpfile"),0640);
logger(LOG_NOTICE, "dumping state\n");
if((ret = db->cursor(db, NULL, &dbc, 0)) != 0) {
db->err(db, ret, "opening cursor");
exit(22);
}
memset(&key, 0, sizeof(key));
memset(&data, 0, sizeof(data));
while((ret = dbc->c_get(dbc, &key, &data, DB_NEXT)) ==0) {
char tbuff[1024];
time_t *time=data.data;
strncpy(hostname, key.data, MATCH_LOGIN_HOSTNAME_MAX-1);
hostname[key.size]=0;
strftime(tbuff, 1023, "%Y-%m-%d %H:%M:%S", localtime(time));
fprintf(f, "%s\t%s\n", hostname, tbuff);
}
fclose(f);
}
#endif
void auth_dump(int sig) {
#ifdef WITH_DB
if(!strcmp(conffile_param("authtype"), "db"))
auth_db_dump();
else
auth_text_dump();
#else
auth_text_dump();
#endif
}
/* auth_cmp: qsort/bsearch comparison function
* doesn't matter what is used really, it's just used to speed up the frequent
* checks for presence. in this case a string comparison is used.
*/
int auth_cmp(const void *a, const void *b) {
return(strcmp(((auth_entry *)a)->hostname,((auth_entry *)b)->hostname));
}
/* auth_init_mem: this reallocates the memory requirements based on the current
* auth_max value. auth_max may be changed if auth_cur reaches it
*/
void auth_init_mem() {
auth=realloc(auth, sizeof(auth_entry)*auth_max);
shadow_auth=realloc(shadow_auth, sizeof(auth_entry)*auth_max);
if(!auth || !shadow_auth) {
logger(LOG_ERR, "Fatal error: out of memory in auth\n");
exit(20);
}
}
/* auth_present: check if a host is present in the authentication database
*/
auth_entry *auth_present(char *hostname) {
auth_entry e;
strncpy(e.hostname,hostname, MATCH_LOGIN_HOSTNAME_MAX);
e.t=0;
return((auth_entry *)bsearch(&e,auth,auth_cur, sizeof(auth_entry), auth_cmp));
}
/* auth_write_text: write the current live hostnames to the relay temp file, and then
* move it to the relay file
*/
void auth_write_text() {
int i;
FILE *f;
f = fopen(conffile_param("authtemp"),"w");
if(!f) {
logger(LOG_ERR, "Fatal Error: cannot write to %s\n", conffile_param("authtemp"));
exit(21);
}
chmod(conffile_param("authtemp"),0640);
for(i=0;i<auth_cur;i++) {
fprintf(f,"%s\n",auth[i].hostname);
}
fclose(f);
if(rename(conffile_param("authtemp"),conffile_param("authfile"))!=0) {
logger(LOG_ERR, "Fatal Error: rename( \"%s\".\"%s\" ) failed\n",
conffile_param("authtemp"),conffile_param("authfile"));
exit(22);
}
}
/* auth_text_add: update the internal state tables with the hostname
*/
void auth_text_add(char *username, char *hostname) {
auth_entry *e;
e=auth_present(hostname);
if(e) {
logger(LOG_NOTICE, "updating timeout for %s at %s\n", username, hostname);
e->t=time(NULL);
} else {
logger(LOG_NOTICE, "authorising %s at %s\n", username, hostname);
if(auth_cur==auth_max) {
auth_max+=1024;
auth_init_mem();
}
strncpy(auth[auth_cur].hostname, hostname, MATCH_LOGIN_HOSTNAME_MAX);
auth[auth_cur].t=time(NULL);
auth_cur++;
qsort(auth, auth_cur, sizeof(auth_entry), auth_cmp);
// we write immediately, so that they can immediately send mail
auth_write_text();
}
}
#ifdef WITH_DB
/* auth_write_db: update the berkeley database with the current live hostnames
* This ONLY supports the 3.x and later interfaces
*/
void db_errcall_fn(const char *errpfx, char *msg) {
logger(LOG_ERR, "Berkeley Database: %s\n", msg);
}
void opendb() {
int ret;
int db_flags = DB_CREATE | DB_INIT_CDB;
int dbtype = DB_HASH;
char *db_path = conffile_param("authfile");
if(db_create(&db,0,0)) {
logger(LOG_ERR, "Fatal Error: unable to create berkeley database\n");
exit(22);
}
db->set_errcall(db, db_errcall_fn);
#if (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR > 0)
if((ret = db->open(db, 0, db_path, 0, dbtype, db_flags, 0644)) != 0) {
db->err(db, ret, "Opening Database");
exit(22);
}
logger(LOG_DEBUG, "Database %s Opened\n", db_path);
#elif (DB_VERSION_MAJOR ==3 || DB_VERSION_MAJOR == 4)
if((ret = db->open(db, db_path, 0, dbtype, db_flags, 0644)) != 0) {
db->err(db, ret, "Opening Database");
exit(22);
}
logger(LOG_DEBUG, "Database %s Opened\n", db_path);
#else
#error "Unsupported Berkeley DB version"
#endif
}
DBT hostname_key(char *hn) {
DBT key;
memset(&key, 0, sizeof(key));
key.data = hn;
key.size = strlen(hn);
return key;
}
void auth_db_add(char *username, char *hostname) {
DBT key, data;
int ret;
time_t now;
memset(&data, 0, sizeof(data));
key = hostname_key(hostname);
now = time(NULL);
data.data = &now;
data.size = sizeof(time_t);
logger(LOG_DEBUG, "Berkeley DB: %s -> %d\n", hostname, now);
logger(LOG_NOTICE, "authorising %s at %s\n", username, hostname);
if((ret = db->put(db, NULL, &key, &data, 0)) != 0) {
db->err(db, ret, "writing hostname");
exit(22);
}
if((ret = db->sync(db, 0)) != 0) {
db->err(db, ret, "syncing database");
exit(23);
}
}
void auth_db_delete(char *hostname) {
int ret;
DBT key;
key = hostname_key(hostname);
if((ret = db->del(db, NULL, &key, 0)) != 0) {
db->err(db, ret, "deleting hostname");
exit(22);
}
db->close(db, 0);
}
#endif
/* auth_add: add an entry to the database. the username isn't stored, it's
* used just for logging purposes, to make debugging easier for the
* administrator. the database is written after each entry is added.
*/
void auth_add(char *username, char *hostname) {
#ifdef WITH_DB
if(!strcmp(conffile_param("authtype"), "db"))
auth_db_add(username, hostname);
else
auth_text_add(username, hostname);
#else
auth_text_add(username, hostname);
#endif
}
#ifdef WITH_DB
void auth_db_clean(int sig) {
DBC *dbc;
DBT key, data;
int ret;
time_t now=time(NULL);
time_t max=(time_t)conffile_param_int("timeout");
logger(LOG_NOTICE, "cleaning db file\n");
// apparently i should use DB_WRITECURSOR as a flag here
// but the version 3 db barfs on me when i do that
if((ret = db->cursor(db, NULL, &dbc, 0)) != 0) {
db->err(db, ret, "opening cursor");
exit(22);
}
memset(&key, 0, sizeof(key));
memset(&data, 0, sizeof(data));
while((ret = dbc->c_get(dbc, &key, &data, DB_NEXT)) ==0) {
time_t then = (time_t)data.data;
if(now - then > max) {
if((ret = dbc->c_del(dbc, 0)) != 0) {
db->err(db, ret, "deleting key");
exit(22);
}
}
}
logger(LOG_DEBUG,"Finished cleaning cycle\n");
}
#endif
/* auth_clean_text: remove entries that have expired. this is done by selectively
* copying entries to the shadow buffer, then swapping buffers.
*
* this process is triggered by the reception of a SIGALRM.
*/
void auth_text_clean(int sig) {
int i;
auth_entry *tmp;
int n=0;
time_t t=time(NULL);
time_t max=(time_t)conffile_param_int("timeout");
logger(LOG_NOTICE, "cleaning state tables\n");
logger(LOG_DEBUG,"Starting cleaning cycle\n");
for(i=0;i<auth_cur;++i) {
if(t-auth[i].t<max) {
strncpy(shadow_auth[n].hostname, auth[i].hostname,
MATCH_LOGIN_HOSTNAME_MAX);
shadow_auth[n].t=auth[i].t;
n++;
} else {
logger(LOG_NOTICE, "flushing %s\n", auth[i].hostname);
}
}
// swap them
tmp=auth;
auth=shadow_auth;
shadow_auth=tmp;
auth_cur=n;
auth_write_text();
logger(LOG_DEBUG,"Finished cleaning cycle\n");
}
void auth_clean(int sig) {
#ifdef WITH_DB
if(!strcmp(conffile_param("authtype"), "db"))
auth_db_clean(sig);
else
auth_text_clean(sig);
#else
auth_text_clean(sig);
#endif
signal(14, auth_clean);
alarm(auth_alarm);
}
/* auth_init: set up the auth tables.
*/
void auth_init() {
logger(LOG_DEBUG, "initialising authentication tables\n");
auth_init_mem();
auth_alarm=conffile_param_int("flush");
signal(14, auth_clean);
alarm(auth_alarm);
// create the empty output files, so our SMTP server doesn't blow up
#ifdef WITH_DB
if(!strcmp(conffile_param("authtype"), "db"))
opendb();
else
auth_write_text(); // so that the file exists
#else
auth_write_text(); // so that the file exists
#endif
logger(LOG_DEBUG, "authentication tables initialised\n");
}
void auth_exit() {
#ifdef WITH_DB
db->close(db, 0);
#endif
}
syntax highlighted by Code2HTML, v. 0.9.1