/*
libutil -- miscellaneous stuff

Written by Arnt Gulbrandsen <agulbra@troll.no> and copyright 1995
Troll Tech AS, Postboks 6133 Etterstad, 0602 Oslo, Norway, fax +47
22646949.
Modified by Cornelius Krasel <krasel@wpxx02.toxi.uni-wuerzburg.de>
and Randolf Skerka <Randolf.Skerka@gmx.de>.
Copyright of the modifications 1997.
Modified by Kent Robotti <robotti@erols.com>. Copyright of the
modifications 1998.
Modified by Markus Enzenberger <enz@cip.physik.uni-muenchen.de>.
Copyright of the modifications 1998.
Modified by Cornelius Krasel <krasel@wpxx02.toxi.uni-wuerzburg.de>.
Copyright of the modifications 1998, 1999.
Modified by Matthias Andree <matthias.andree@gmx.de>.
Copyright of the modifications 1999 - 2002.
Modified and copyright of the modifications 2002 by Ralf Wildenhues
<ralf.wildenhues@gmx.de>.

See file COPYING for restrictions on the use of this software.
*/

#include "leafnode.h"
#include "validatefqdn.h"
#include "strlcpy.h"
#include "mastring.h"
#include "ln_log.h"

#include <fcntl.h>
#include <sys/uio.h>
#include <sys/param.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <pwd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "system.h"
#include <signal.h>
#include <stdarg.h>

static void whoami(void);

static void suicide(void) {
    /* just in case */
    fflush(stdout);
    fflush(stderr);
    raise(SIGKILL);
}

char fqdn[FQDNLEN + 1] = "";

static const mode_t default_umask = 0002;

/* xoverutil global vars */
struct xoverinfo *xoverinfo;
unsigned long xfirst, xlast;

static int
createmsgiddir(void) {
    mastr *dir = mastr_new(1024);
    mastr *mid = mastr_new(1024);
    DIR *d;
    int rc = 0;
    int havedir[1000] = {0};

    mastr_vcat(dir, spooldir, "/message.id", NULL);
    d = opendir(mastr_str(dir));
    if (d) {
	struct dirent *de;
	unsigned long u;
	const char *t;
	char *e;

	/* read directory - should be faster than stat */
	while(errno = 0, de = readdir(d)) {
	    t = de->d_name;
	    if (isdigit((unsigned char)*t)) {
		u = strtoul(t, &e, 10);
		if (e > t)
		    havedir[u] = 1;
	    }
	}

	if (errno)
	    ln_log(LNLOG_SERR, LNLOG_CTOP, "error reading directory %s: %m",
		    mastr_str(dir));

	closedir(d);

	/* create missing */
	for(u = 0; u < 1000; u++) {
	    char b[4];

	    snprintf(b, sizeof(b), "%03lu", u);
	    mastr_clear(mid);
	    if (!havedir[u]) {
		mastr_vcat(mid, spooldir, "/message.id/", b, NULL);
		if (mkdir(mastr_str(mid), 02755)) {
		    ln_log(LNLOG_SERR, LNLOG_CTOP, "error creating directory %s: %m",
			    mastr_str(mid));
		    break;
		}
	    }
	}
    } else {
	ln_log(LNLOG_SERR, LNLOG_CTOP, "cannot read %s: %m", mastr_str(dir));
	rc = -1;
    }

    mastr_delete(mid);
    mastr_delete(dir);
    return rc;
}

static struct { const char* name; mode_t mode; } dirs[] = {
    {"", 04755 },
    {"interesting.groups", 02775 },
    {"leaf.node", 0755 },
    {"failed.postings", 02775 },
    {"message.id", 0755 },
    {"out.going", 0755 },
    {"temp.files", 0755 },
};

static const int dirs_count = sizeof(dirs)/sizeof(dirs[0]);

/*
 * initialize all global variables
 */
/*@-globstate@*/
int
initvars(char *progname)
{
#ifndef TESTMODE
    struct passwd *pw;
#endif
    char s[SIZE_s+1];
    int i;
    char *t;

    active = NULL;
    xoverinfo = NULL;
    xfirst = 0;
    xlast = 0;

    /* config.c stuff does not have to be initialized */

    expire_base = NULL;
    servers = NULL;

    t = getenv("LN_DEBUG");
    if (t)
	debugmode = atoi(t);

    (void)umask(default_umask);
    if (strlen(spooldir) != strspn(spooldir, PORTFILENAMECSET "/")) {
	/* verrecke! */
	syslog(LOG_CRIT, "Fatal: spooldir contains illegal characters. "
	       "Recompile leafnode with a proper spooldir setting.");
	suicide();
    }

#ifndef TESTMODE
    pw = getpwnam(NEWS_USER);
    if (!pw) {
	fprintf(stderr, "no such user: %s\n", NEWS_USER);
	return FALSE;
    }
#endif

    /* These directories should exist anyway */
    for (i = 0 ; i < dirs_count ; i++) {
	xsnprintf(s, SIZE_s, "%s/%s", spooldir, dirs[i].name);
	if ((mkdir(s, dirs[i].mode) && errno != EEXIST)
		|| chmod(s, dirs[i].mode)
#ifndef TESTMODE
	|| chown(s, pw->pw_uid, pw->pw_gid)
#endif
	   ) {
	    int e = errno;
	    struct stat st;
	    if (stat(s, &st)
#ifndef TESTMODE
		    || st.st_uid != pw->pw_uid
#endif
	       ) {
		fprintf(stderr, "Warning: cannot create %s with proper ownership: %s\nMake sure you run this program as user root or %s.\n", s, strerror(e),
			NEWS_USER);
		syslog(LOG_WARNING, "Warning: cannot create %s with proper ownership: %s", s, strerror(e));
		suicide();
	    }
	}
    }

    whoami();

#ifndef TESTMODE
    if (progname) {
#ifdef HAVE_SETGID
	setgid(pw->pw_gid);
#else
	setregid(pw->pw_gid, pw->pw_gid);
#endif

#ifdef HAVE_SETUID
	setuid(pw->pw_uid);
#else
	setreuid(pw->pw_uid, pw->pw_uid);
#endif
	if (getuid() != pw->pw_uid || getgid() != pw->pw_gid) {
	    syslog(LOG_ERR, "%s: must be run as %s or root\n", progname, NEWS_USER);
	    fprintf(stderr, "%s: must be run as %s or root\n", progname, NEWS_USER);
	    endpwent();
	    return FALSE;
	}
    }
#endif				/* not TESTMODE */
    endpwent();

    if (chdir(spooldir) || (i = open(".", O_RDONLY)) < 0) {
	int e = errno;
	syslog(LOG_CRIT, "Fatal: cannot change to or open spooldir: %m");
	fprintf(stderr, "Fatal: cannot change or open spooldir: %s\n",
		strerror(e));
	suicide();
    }
    (void)close(i);

    /* create missing message.id directories */
    if (createmsgiddir())
	return FALSE;

    return TRUE;
}

/*@=globstate@*/

/*
 * check whether "groupname" is represented in interesting.groups without
 * touching the file
 */
int
isinteresting(const char *groupname)
{
    DIR *d;
    struct dirent *de;
    char s[SIZE_s+1];

    xsnprintf(s, SIZE_s, "%s/interesting.groups", spooldir);
    d = opendir(s);
    if (!d) {
	syslog(LOG_ERR, "Unable to open directory %s: %m", s);
	printf("Unable to open directory %s\n", s);
	return FALSE;
    }

    while ((de = readdir(d)) != NULL) {
	if (strcasecmp(de->d_name, groupname) == 0) {
	    closedir(d);
	    return TRUE;
	}
    }
    closedir(d);
    return FALSE;
}

void
overrun(void)
/* report buffer overrun */
{
    syslog(LOG_CRIT, "buffer size too small, cannot continue, aborting program");
    abort();
    kill(getpid(), SIGKILL);	/* really die! */
}

/* no good but this server isn't going to be scalable so shut up */
const char *
lookup(const char *msgid)
{
    static char *name = NULL;
    static size_t namelen = 0;
    unsigned int r;
    size_t i;

    if (msgid == LOOKUP_FREE) {
	namelen = 0;
	if (name)
	    free(name);
	return NULL;
    }

    if (!msgid || !*msgid)
	return NULL;

    i = strlen(msgid) + strlen(spooldir) + 30;

    if (!name) {
	name = (char *)critmalloc(i, "lookup");
	namelen = i;
    } else if (i > namelen) {
	free(name);
	name = (char *)critmalloc(i, "lookup");
	namelen = i;
    }

    strcpy(name, spooldir);	/* RATS: ignore */
    strcat(name, "/message.id/000/");	/* RATS: ignore */
    i = strlen(name);
    strcat(name, msgid);	/* RATS: ignore */

    r = 0;
    do {
	if (name[i] == '/')
	    name[i] = '@';
	else if (name[i] == '>')
	    name[i + 1] = '\0';
	r += (int)(name[i]);
	r += ++i;
    } while (name[i]);

    i = strlen(spooldir) + 14;	/* to the last digit */
    r = (r % 999) + 1;
    name[i--] = '0' + (char)(r % 10);
    r /= 10;
    name[i--] = '0' + (char)(r % 10);
    r /= 10;
    name[i] = '0' + (char)(r);
    return name;
}

#define LM_SIZE 65536

static int
makedir(char *d)
{
    char *p;
    char *q;

    if (verbose > 3)
	printf("makedir(%s)\n", d);
    if (!d || *d != '/' || chdir("/"))
	return 0;
    q = d;
    do {
	*q = '/';
	p = q;
	q = strchr(++p, '/');
	if (q)
	    *q = '\0';
	if (!chdir(p))
	    continue;		/* ok, I do use it sometimes :) */
	if (errno == ENOENT)
	    if (mkdir(p, 0775)) {
		syslog(LOG_ERR, "mkdir %s: %m", d);
		exit(1);
	    }
	if (chdir(p)) {
	    syslog(LOG_ERR, "chdir %s: %m", d);
	    exit(1);
	}
    } while (q);
    return 1;
}

/* prefix numeric group name components with a minus */
static int migrate(const char *name) {
    char *p = critstrdup(name, "dogroup"), *q, *t = NULL;

    /* shortcut: don't call into chdir() excessively */
    for(q = strtok(p, "."); q; q = strtok(NULL, ".")) {
	if (strspn(q, "0123456789") == strlen(q)) break;
    }
    if (!q) {
	free(p);
	return 0;
    }

    if (chdir(spooldir)) goto barf;

    for(q = strtok(p, "."); q; q = strtok(NULL, ".")) {
	t = critmalloc(strlen(q)+2, "dogroup");
	t[0] = '-';
	strcpy(t+1, q);
	if (strspn(q, "0123456789") == strlen(q)) {
	    struct stat st;
	    if (0 == chdir(t)) {
		free(t);
		continue;
	    }
	    if (0 == stat(q, &st) && S_ISDIR(st.st_mode)) {
		if (rename(q, t)) {
		    ln_log(LNLOG_SERR, LNLOG_CTOP, "cannot rename %s to %s: %m",
			    q, t);
		    goto barf;
		}
		if (0 == chdir(t)) {
		    free(t);
		    continue;
		}
	    }
	    goto barf;
	}
	if (chdir(q)) {
	    goto barf;
	}
	free(t);
    }
    free(p);
    return 0;
barf:
    free(p);
    free(t);
    return -1;
}

/* chdir to the directory of the argument if it's a valid group */
int
chdirgroup(const char *group, int creatdir)
{
    char *p;
    const char *c;

    if (group && *group) {
	int dots = 0;
	char *nam, *q;
	mastr *d = mastr_new(1024);

	migrate(group);
	mastr_vcat(d, spooldir, "/", group, NULL);
	p = mastr_modifyable_str(d) + strlen(spooldir) + 1;
	while (*p) {
	    if (*p == '.') {
		*p = '/';
	    } else
		*p = tolower((unsigned char)*p);
	    p++;
	}
	for (c = mastr_str(d);*c;c++) {
	    if (*c == '/' && c[1] && strspn(c+1, "0123456789") == strcspn(c+1, "/"))
		dots++;
	}
	nam = critmalloc(mastr_len(d) + dots + 1, "chdirgroup");
	for (c = mastr_str(d), q=nam;*c;c++) {
	    *(q++) = *c;
	    if (*c == '/' && c[1] && strspn(c+1, "0123456789") == strcspn(c+1, "/"))
		*(q++) = '-';
	}
	*q = 0;
	mastr_delete(d);

	if (!chdir(nam)) {
	    free(nam);
	    return 1;		/* chdir successful */
	}
	if (creatdir) {
	    int r = makedir(nam);
	    free(nam);
	    return r;
	}
	free(nam);
    }
    return 0;
}

/* get the fully qualified domain name of this box into fqdn */
static void
whoami(void)
{
    struct hostent *he;
    int debugqual = 0;
    char *x;

    if ((x = getenv("LN_DEBUG_QUALIFICATION")) != NULL
	&& *x)
	debugqual = 1;

    if (!gethostname(fqdn, sizeof(fqdn)) && (he = gethostbyname(fqdn)) != NULL) {
	xstrlcpy(fqdn, he->h_name, sizeof(fqdn));
	if (debugqual) syslog(LOG_DEBUG, "canonical hostname: %s", fqdn);
	if (!is_validfqdn(fqdn)) {
	    char **alias;
	    alias = he->h_aliases;
	    while (alias && *alias) {
		if (debugqual) {
		    syslog(LOG_DEBUG, "alias for my hostname: %s", *alias);
		}
		if (is_validfqdn(*alias)) {
		    xstrlcpy(fqdn, *alias, sizeof(fqdn));
		    break;
		} else {
		    alias++;
		}
	    }
	}
	endhostent();
    }
}

/*
 * prepend string "newentry" to stringlist "list".
 */
void
prependtolist(struct stringlist **list, /*@unique@*/ const char *newentry)
{

    struct stringlist *ptr;

    ptr = (struct stringlist *)critmalloc(sizeof(struct stringlist) +
					  strlen(newentry),
					  "Allocating space in stringlist");
    strcpy(ptr->string, newentry);	/* RATS: ignore */
    ptr->next = *list;
    *list = ptr;
}

/*
 * find a string in a stringlist
 * return pointer to string if found, NULL otherwise
 */
char *
findinlist(struct stringlist *haystack, char *needle)
{
    struct stringlist *a;

    a = haystack;
    while (a && a->string) {
	if (strncmp(needle, a->string, strlen(needle)) == 0)
	    return a->string;
	a = a->next;
    }
    return NULL;
}

/*
 * find a string in a stringlist
 * return pointer to string if found, NULL otherwise
 */
struct stringlist **
lfindinlist(struct stringlist **haystack, char *needle, size_t len)
{
    struct stringlist **a;

    a = haystack;
    while (a && *a && (*a)->string) {
	if (strncmp(needle, (*a)->string, len) == 0)
	    return a;
	a = &(*a)->next;
    }
    return NULL;
}

void replaceinlist(struct stringlist **haystack, char *needle, size_t len)
{
    struct stringlist **f = lfindinlist(haystack, needle, len);
    struct stringlist *n;
    if (!f) prependtolist(haystack, needle);
    else {
	n = (*f)->next;
    	free(*f);
    	*f = (struct stringlist *)critmalloc(sizeof(struct stringlist) + 
			strlen(needle), "Allocating space in stringlist");
	strcpy((*f)->string, needle); /* RATS: ignore */
	(*f)->next = n;
    }
}

/*
 * free a list
 */
void
freelist( /*@only@*/ struct stringlist *list)
{
    struct stringlist *a;

    while (list) {
	a = list->next;
	free(list);
	list = a;
    }
}

/* next few routines implement a mapping from message-id to article
   number, and clearing the entire space */

struct msgidtree {
    struct msgidtree *left;
    struct msgidtree *right;
    char msgid[1]; /* RATS: ignore */
};

static struct msgidtree *head;	/* starts as NULL */

static int
comparemsgid(const char *id1, const char *id2)
{
    int c;

    /* comparing only by msgid is uncool because the tree becomes
       very unbalanced */
    c = strcmp(strchr(id1, '@'), strchr(id1, '@'));
    if (!c)
	c = strcmp(id1, id2);
    return c;
}

void
insertmsgid( /*@unique@*/ const char *msgid)
{
    struct msgidtree **a;
    int c;

    if (strchr(msgid, '@') == 0)
	return;

    a = &head;
    while (a) {
	if (*a) {
	    c = comparemsgid((*a)->msgid, msgid);
	    if (c < 0)
		a = &((*a)->left);
	    else if (c > 0)
		a = &((*a)->right);
	    else {
		return;
	    }
	} else {
	    *a = (struct msgidtree *)
		critmalloc(sizeof(struct msgidtree) + strlen(msgid),
			   "Building expiry database");
	    (*a)->left = NULL;
	    (*a)->right = NULL;
	    strcpy((*a)->msgid, msgid);	/* RATS: ignore */
	    return;
	}
    }
}

/* returns 0 if not found, 1 otherwise */
int
findmsgid(const char *msgid)
{
    struct msgidtree *a;
    int c;

    /* domain part differs more than local-part, so try it first */

    if (NULL == strchr(msgid, '@'))
	return 0;

    a = head;
    while (a) {
	c = comparemsgid(a->msgid, msgid);
	if (c < 0)
	    a = a->left;
	else if (c > 0)
	    a = a->right;
	else
	    return 1;
    }
    return 0;
}

static void
begone( /*@null@*//*@only@*/ struct msgidtree *m)
{
    if (m) {
	begone(m->right);
	begone(m->left);
	free((char *)m);
    }
}

void
clearidtree(void)
{
    begone(head);
    head = NULL;
}

static int
xtraverseidtree(struct msgidtree *m, tmihook h)
{
    int e = 0;
    if (!m) return 0;
    e |= xtraverseidtree(m->left, h);
    e |= h(m->msgid);
    e |= xtraverseidtree(m->right, h);
    return e;
}

int
traverseidtree(tmihook h) {
    return xtraverseidtree(head, h);
}

/*@dependent@*/ const char *
rfctime(void)
{
    static char date[128]; /* RATS: ignore */
    const char *months[] = { "Jan", "Feb", "Mar", "Apr",
	"May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
    };
    const char *days[] = { "Sun", "Mon", "Tue", "Wed",
	"Thu", "Fri", "Sat"
    };
    time_t now;
    struct tm gm;

    now = time(NULL);
#ifdef HAVE_STRUCT_TM_TM_GMTOFF
    {
	char sign;
	long off;

	gm = *(localtime(&now));
	/* fiddle a bit to make sure we don't get negative a%b results:
	 * make sure operands are non-negative */
	off = gm.tm_gmtoff/60;
	sign = off < 0 ? '-' : '+';
	off = labs(off);
	xsnprintf(date, sizeof(date), "%3s, %d %3s %4d %02d:%02d:%02d %c%02ld%02ld",
		days[gm.tm_wday], gm.tm_mday, months[gm.tm_mon],
		gm.tm_year + 1900, gm.tm_hour, gm.tm_min, gm.tm_sec,
		sign, off / 60, off % 60);
    }
#else
    gm = *(gmtime(&now));
    xsnprintf(date, sizeof(date), "%3s, %d %3s %4d %02d:%02d:%02d -0000",
	      days[gm.tm_wday], gm.tm_mday, months[gm.tm_mon],
	      gm.tm_year + 1900, gm.tm_hour, gm.tm_min, gm.tm_sec);
#endif

    return (date);
}

int
ngmatch(const char *pattern, const char *str)
{
    return !wildmat(str, pattern);
}

int
xsnprintf(char *str, size_t n, const char *format, ...)
{
    int r;
    va_list ap;

    va_start(ap, format);
    r = vsnprintf(str, n, format, ap);
    va_end(ap);

    if ((size_t) r >= n || r < 0)
	overrun();
    return r;
}

size_t xstrlcpy(char *dst, const char *src, size_t size) 
{
    size_t s;
    s = strlcpy(dst, src, size);
    if (s >= size)
	overrun();
    return s;
}


syntax highlighted by Code2HTML, v. 0.9.1