/*
 *	(c) Copyright 1990, Kim Fabricius Storm.  All rights reserved.
 *      Copyright (c) 1996-2026 Michael T Pins.  All rights reserved.
 *
 *	Global declarations and auxiliary functions.
 */

#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <time.h>
#include <fcntl.h>
#include <stdarg.h>
#include <signal.h>
#include <pwd.h>
#include <sys/mman.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include "config.h"
#include "global.h"
#include "execute.h"
#include "nn.h"
#include "patchlevel.h"
#include "nn_term.h"

#ifdef HAVE_SYSLOG
#include <syslog.h>
#endif				/* HAVE_SYSLOG */

/* global.c */

static sig_type catch_keyboard(int n);
static sig_type catch_pipe(int n);
static sig_type catch_suspend(int n);
static void     mail_sys_error(char *err, int isfatal);
static void     mem_error(int t, int32 bytes);

#ifdef NEED_STRERROR
char           *strerror(int);
#endif

char           *home_directory;
char           *nn_directory;	/* ~/.nn as default */
char           *news_directory;	/* /usr/spool/news */
char           *news_lib_directory;	/* /usr/lib/news */
char           *lib_directory;	/* /usr/local/lib/nn */
char           *master_directory;	/* = lib */
char           *help_directory;	/* = lib/help */
char           *bin_directory = BIN_DIRECTORY;

char           *db_directory;	/* /usr/spool/nn or NEWS_DIR/.nn */
char           *db_data_directory;	/* ..../DATA	 or undefined	 */
int             db_data_subdirs = 0;	/* set if DATA/[0-9]/ exist	 */

char           *news_active;	/* NLIB/active or DB/ACTIVE */

char           *pager;
char           *organization;

char           *log_file;	/* = lib/Log */
char           *log_entry_filter = NULL;

char           *temp_file;

#ifndef TMP_DIRECTORY
#define TMP_DIRECTORY "/var/tmp"
#endif

char           *tmp_directory = TMP_DIRECTORY;

char            version_id[100];

int             use_nntp = 0;	/* bool: t iff we use nntp */

uid_t           user_id;
static gid_t    group_id;
int             process_id;
int             who_am_i;
int             dont_write_console = 0;
int             mail_errors_mode = 2;

extern int      errno;
extern char    *getlogin(), *getenv(const char *);


#ifdef HAVE_MULTIGROUP

#ifndef NGROUPS
#include <sys/param.h>
#endif

static uid_t    user_eid;
static int      ngroups;
static gid_t    gidset[NGROUPS];

static int
in_grplist(gid_t gid)
{
    int             n;

    if (gid == group_id)
	return 1;

    for (n = 0; n < ngroups; ++n)
	if (gidset[n] == gid)
	    return 1;

    return 0;
}

#define group_access(gpid)	in_grplist(gpid)
#else
#define group_access(gid)	((gid) == group_id)
#endif

/* signal handler interface */

int             s_hangup = 0;	/* hangup signal */
int             s_keyboard = 0;	/* keyboard interrupt */
int             s_pipe = 0;	/* broken pipe */
int             s_redraw = 0;	/* redraw signal (if job control) */

#ifdef RESIZING
int             s_resized = 0;	/* screen resized */
#endif

#ifdef FAKE_INTERRUPT
#include <setjmp.h>

jmp_buf         fake_keyb_sig;
int             arm_fake_keyb_sig = 0;
char            fake_keyb_siglist[NSIG];
#endif

sig_type
catch_hangup(int n)
{
    s_hangup = 1;
    signal(n, SIG_IGN);

#ifdef FAKE_INTERRUPT
    if (fake_keyb_siglist[n] && arm_fake_keyb_sig)
	longjmp(fake_keyb_sig, 1);
#endif
}

static          sig_type
catch_keyboard(int n)
{

#ifdef RESET_SIGNAL_WHEN_CAUGHT
    signal(n, catch_keyboard);
#endif

#ifdef FAKE_INTERRUPT
    if (fake_keyb_siglist[n] && arm_fake_keyb_sig)
	longjmp(fake_keyb_sig, 1);
#endif

    s_keyboard++;
}

static          sig_type
catch_pipe(int n)
{
    s_pipe++;

#ifdef RESET_SIGNAL_WHEN_CAUGHT
    signal(n, catch_pipe);
#endif

#ifdef FAKE_INTERRUPT
    if (fake_keyb_siglist[n] && arm_fake_keyb_sig)
	longjmp(fake_keyb_sig, 1);
#endif
}

#ifdef HAVE_JOBCONTROL
static          sig_type
catch_suspend(int n)
{
    s_redraw++;

#ifdef RESET_SIGNAL_WHEN_CAUGHT
    signal(n, catch_suspend);
#endif

    suspend_nn();

#ifdef FAKE_INTERRUPT
    if (fake_keyb_siglist[n] && arm_fake_keyb_sig) {

#ifdef RESIZING
	s_resized++;
#endif

	longjmp(fake_keyb_sig, 1);
    }
#endif
}

#endif


int
init_global(void)
{

#ifdef NOV
    char           *nov_id = " (NOV)";
#else
    char           *nov_id = "";
#endif

#ifdef FAKE_INTERRUPT
    for (i = 0; i < NSIG; i++)
	fake_keyb_siglist[i] = i == 2 ? 1 : 0;
#endif

    if (who_am_i != I_AM_NN) {
	signal(SIGINT, SIG_IGN);
	signal(SIGQUIT, SIG_IGN);
    }
    signal(SIGTERM, catch_hangup);
    signal(SIGHUP, catch_hangup);
    signal(SIGPIPE, catch_pipe);
    signal(SIGALRM, SIG_IGN);

#ifdef SIGPWR
    signal(SIGPWR, catch_hangup);
#endif

#ifdef CONFIG_NUM_IN_VERSION
    sprintf(version_id, "%s.%s #%d%s", RELEASE, PATCHLEVEL,
#include "update.h"
	    ,nov_id);
#else
    sprintf(version_id, "%s.%s%s", RELEASE, PATCHLEVEL, nov_id);
#endif

    user_id = getuid();

#ifdef HAVE_MULTIGROUP
    ngroups = getgroups(NGROUPS, gidset);	/* Get users's group set */
#endif

    group_id = getegid();
    user_eid = geteuid();

    process_id = getpid();

#ifdef CLIENT_DIRECTORY
    lib_directory = CLIENT_DIRECTORY;
#else
    lib_directory = LIB_DIRECTORY;
#endif

#ifdef NEWS_DIRECTORY
    news_directory = NEWS_DIRECTORY;
#else
    news_directory = "/usr/spool/news";
#endif

#ifdef DB_DIRECTORY
    db_directory = DB_DIRECTORY;
#else
    db_directory = mk_file_name(lib_directory, ".nn");
#endif

#ifdef ACCOUNTING
    if (who_am_i == I_AM_ACCT)
	return 0;
#endif

#ifdef DB_DATA_DIRECTORY
    db_data_directory = DB_DATA_DIRECTORY;
#else

#ifdef DB_DIRECTORY
    db_data_directory = mk_file_name(db_directory, "DATA");
#else
    db_data_directory = NULL;
#endif

#endif

#ifndef DB_LONG_NAMES
    if (db_data_directory != NULL)
	db_data_subdirs = file_exist(relative(db_data_directory, "0"), "dx");
#endif

#ifdef NEWS_LIB_DIRECTORY
    news_lib_directory = NEWS_LIB_DIRECTORY;
#else
    news_lib_directory = "/usr/lib/news";
#endif

    /* this may later be changed by nntp_check */
    news_active = mk_file_name(news_lib_directory, "active");

#ifdef MASTER_DIRECTORY
    master_directory = MASTER_DIRECTORY;
#else
    master_directory = LIB_DIRECTORY;
#endif

#ifdef HELP_DIRECTORY
    help_directory = HELP_DIRECTORY;
#else
    help_directory = mk_file_name(lib_directory, "help");
#endif

#ifdef LOG_FILE
    log_file = LOG_FILE;
#else
    log_file = mk_file_name(LIB_DIRECTORY, "Log");
#endif

    if (who_am_i == I_AM_MASTER || who_am_i == I_AM_SPEW)
	return 0;

    signal(SIGINT, catch_keyboard);
    signal(SIGQUIT, catch_keyboard);

#ifdef HAVE_JOBCONTROL
    signal(SIGTSTP, catch_suspend);
#endif

    if ((home_directory = getenv("HOME")) == NULL)
	nn_exitmsg(1, "No HOME environment variable");

    if ((pager = getenv("PAGER")) == NULL)
	pager = DEFAULT_PAGER;

    if (!nn_directory)		/* if not set on command line */
	nn_directory = mk_file_name(home_directory, ".nn");

    organization = getenv("ORGANIZATION");

    return 0;
}

int
init_global2(void)
{
    if (who_am_i != I_AM_ADMIN && !file_exist(nn_directory, "drwx")) {
	if (who_am_i != I_AM_NN)
	    return -1;
	if (mkdir(nn_directory, 0755) < 0)
	    nn_exitmsg(1, "Cannot create %s directory", nn_directory);
	return 1;
    }
    return 0;
}

void
new_temp_file(void)
{
    static char     buf[FILENAME];
    static char    *temp_dir = NULL;

    if (temp_dir == NULL) {
	if ((temp_dir = getenv("TMPDIR")) == NULL)
	    temp_dir = tmp_directory;	/* just to make test above false */
	else
	    tmp_directory = temp_dir;
    }

    sprintf(buf, "%s/nn.XXXXXX", tmp_directory);
    mktemp(buf);
    temp_file = buf;
}


FILE           *
open_file(char *name, int mode)
{
    FILE           *f = NULL;
    int             fd;

    if ((mode & DONT_CREATE) && !file_exist(name, (char *) NULL))
	return NULL;

    switch (mode & 0x0f) {

	case OPEN_READ:

	    f = fopen(name, "r");
	    break;

	case OPEN_UPDATE:

/*	f = fopen(name, "r+");	-- not reliable on many systems (sigh) */

	    if ((fd = open(name, O_WRONLY)) >= 0) {
		if ((f = fdopen(fd, "w")) != NULL)
		    return f;
		close(fd);
	    }
	    /* FALLTHROUGH */
	case OPEN_CREATE:

	    f = fopen(name, "w");
	    break;

	case OPEN_APPEND:

	    f = fopen(name, "a");
	    break;

	case OPEN_CREATE_RW:

	    f = fopen(name, "w+");	/* not safe on all systems -- beware */
	    break;

	default:

	    sys_error("Illegal mode: open_file(%s, 0x%x)", name, mode);
    }

    if (f) {
	if (mode & OPEN_UNLINK)
	    unlink(name);
	return f;
    }
    if ((mode & MUST_EXIST) == 0)
	return NULL;

    sys_error("Cannot open file %s, mode=0x%x, errno=%d", name, mode, errno);

    return NULL;
}

FILE           *
open_file_search_path(char *name, int mode)
{
    FILE           *f;

    if (name == NULL)
	return NULL;

    if (*name == '/')
	return open_file(name, mode);

    f = NULL;

    if (!use_nntp)
	f = open_file(relative(news_lib_directory, name), OPEN_READ);
    if (f == NULL)
	f = open_file(relative(lib_directory, name), OPEN_READ);
    if (f == NULL)
	f = open_file(relative(db_directory, name), OPEN_READ);

    return f;
}

int
fgets_multi(char *buf, int size, register FILE * f)
{
    register char  *s = buf;
    register int    c, n = size;

    while (--n > 0) {
	c = getc(f);
	if (c == '\\') {
	    if ((c = getc(f)) == NL)
		continue;
	    *s++ = '\\';
	    if (--n < 0)
		break;
	}
	if (c == EOF) {
	    *s = NUL;
	    return s != buf;
	}
	if (c == NL) {
	    *s = NUL;
	    return 1;
	}
	*s++ = c;
    }
    buf[30] = NUL;
    sys_error("Line too long \"%s...\" (max %d)", buf, size);
    return 0;
}

/*
 *	relative -- concat directory name and file name
 */

char           *
relative(char *dir, char *name)
{
    static char     concat_path[FILENAME];

    sprintf(concat_path, "%s/%s", dir, name);
    return concat_path;
}


char           *
mk_file_name(char *dir, char *name)
{
    char           *buf;

    buf = newstr(strlen(dir) + strlen(name) + 2);
    sprintf(buf, "%s/%s", dir, name);

    return buf;
}


char           *
home_relative(char *dir)
{
    if (dir) {
	if (*dir == '/')
	    return copy_str(dir);
	else {
	    if (*dir == '~' && *++dir == '/')
		dir++;
	    return mk_file_name(home_directory, dir);
	}
    }
    return NULL;
}


char           *
substchr(char *str, char c1, char c2)
{
    char           *p;

    if ((p = strchr(str, c1)) != NULL)
	*p = c2;
    return p;
}

char           *
copy_str(char *str)
{
    char           *new;

    new = newstr(strlen(str) + 1);
    if (new)
	strcpy(new, str);

    return new;
}

time_t
m_time(FILE * f)
{
    struct stat     st;

    if (fstat(fileno(f), &st) < 0)
	return 0;
    return st.st_mtime;
}


time_t
file_exist(char *name, char *mode)
{
    static struct stat statb;
    int             mask;

    if (name != NULL && stat(name, &statb))
	return 0;

    if (mode == NULL)
	return statb.st_mtime;

    if (statb.st_uid == user_eid)
	mask = 0700;
    else if (group_access(statb.st_gid))
	mask = 0070;
    else
	mask = 0007;

    while (*mode) {
	switch (*mode++) {
	    case 'd':
		if ((statb.st_mode & S_IFMT) == S_IFDIR)
		    continue;
		errno = ENOTDIR;
		return 0;
	    case 'f':
		if ((statb.st_mode & S_IFMT) == S_IFREG)
		    continue;
		if ((statb.st_mode & S_IFMT) == 0000000)
		    continue;
		if ((statb.st_mode & S_IFMT) == S_IFDIR) {
		    errno = EISDIR;
		    return 0;
		}
		break;
	    case 'r':
		if (statb.st_mode & mask & 0444)
		    continue;
		break;
	    case 'w':
		if (statb.st_mode & mask & 0222)
		    continue;
		break;
	    case 'x':
		if (statb.st_mode & mask & 0111)
		    continue;
		break;
	}
	errno = EACCES;
	return 0;
    }

    /* all modes are ok */
    return statb.st_mtime;
}

/*
 *	cmp_file: compare two files
 *
 *	Returns		0: The files are identical.
 *			1: The files are different.
 *			2: An error occurred.
 */

int
cmp_file(char *work, char *copy)
{
    int             fd1, fd2;
    struct stat     sb1, sb2;
    u_char         *p1, *p2;
    off_t           len1, len2;

    if ((fd1 = open(work, O_RDONLY, 0)) < 0) {
	msg("%s %s", work, strerror(errno));
	return (2);
    }
    if ((fd2 = open(copy, O_RDONLY, 0)) < 0) {
	msg("%s %s", copy, strerror(errno));
	return (2);
    }
    if (fstat(fd1, &sb1)) {
	msg("%s %s", work, strerror(errno));
	return (2);
    }
    if (fstat(fd2, &sb2)) {
	msg("%s %s", copy, strerror(errno));
	return (2);
    }
    len1 = sb1.st_size;
    len2 = sb2.st_size;

    if (len1 != len2)
	return (1);

    if ((p1 = (u_char *) mmap(NULL, (size_t) len1, PROT_READ, MAP_SHARED, fd1, (off_t) 0)) == (u_char *) - 1) {
	msg("work=%s %s", work, strerror(errno));
	sleep(5);
	return (2);
    }
    if ((p2 = (u_char *) mmap(NULL, (size_t) len2, PROT_READ, MAP_SHARED, fd2, (off_t) 0)) == (u_char *) - 1) {
	msg("copy=%s %s", copy, strerror(errno));
	sleep(5);
	return (2);
    }
    for (; len1--; ++p1, ++p2) {
	if (*p1 != *p2)
	    return (1);
    }
    return (0);
}

/*
 * copy_file: copy (or append) src file to dest file.
 *
 * Returns number of characters copied or an error code:
 *  -1: source file not found
 *  -2: cannot create destination
 *  -3: write error
 */

int
copy_file(char *src, char *dest, int append)
{
    register FILE  *s, *d;
    register int    n;
    register int    c;

    s = open_file(src, OPEN_READ);
    if (s == NULL)
	return -1;

    d = open_file(dest, append ? OPEN_APPEND : OPEN_CREATE);
    if (d == NULL) {
	fclose(s);
	return -2;
    }
    n = 0;
    while ((c = getc(s)) != EOF) {
	putc(c, d);
	n++;
    }

    fclose(s);
    if (fclose(d) == EOF) {
	if (!append)
	    unlink(dest);
	return -3;
    }
    return n;
}

/*
 * move_file: move old file to new file, linking if possible.
 *
 * The third arg determines what is acceptable if the old file cannot be
 * removed after copying to the new file:
 *   0: must remove old, else remove new and fail,
 *   1: must remove or truncate old, else remove new and fail,
 *   2: just leave old if it cannot be removed or truncated.
 *
 * Returns positive value for success, negative for failure:
 *   0: file renamed (link)
 *   1: file copied, old removed
 *   2: file copied, but old file is only truncated.
 *   3: file copied, but old file still exist.
 *  -1: source file not found
 *  -2: cannot create destination
 *  -3: write error
 *  -4: cannot unlink/truncate old
 *  -5: cannot unlink new
 *  -6: cannot link old to new
 *  -9: messy situation: old and new linked on return (cannot happen?)
 */

int
move_file(char *old, char *new, int may_keep_old)
{
    int             n;

    if (file_exist(new, (char *) NULL)) {
	if (file_exist((char *) NULL, "d"))
	    return -5;
	if (unlink(new) < 0)	/* careful - new may be directory ? */
	    switch (errno) {
		case ENOENT:
		    break;
		case EACCES:
		    if (file_exist((char *) NULL, "w"))
			goto do_copy;
		default:
		    return -5;
	    }
    }
    if (link(old, new) < 0)
	switch (errno) {
	    case EACCES:	/* can just as well try to copy */
	    case EXDEV:
		goto do_copy;
	    default:
		return -6;
	}

    if (unlink(old) == 0)
	return 0;

    /* we were able to link but not unlink old	 */
    /* remove new, and attempt a copy instead	 */
    if (unlink(new) < 0)
	return -9;		/* cannot happen? */

do_copy:
    if ((n = copy_file(old, new, 0)) < 0)
	return n;
    if (unlink(old) == 0)
	return 1;
    if (may_keep_old)
	if (n == 0 || nn_truncate(old, (off_t) 0) == 0)
	    return 2;
    if (may_keep_old == 2)
	return 3;
    unlink(new);
    return -4;
}

int
save_old_file(char *name, char *suffix)
{
    char            buf[FILENAME];
    sprintf(buf, "%s%s", name, suffix);
    return move_file(name, buf, 0);
}


static int
enter_log(int type, char *fmt, va_list ap)
{
    FILE           *log;
    char            buf[512];

    vsprintf(buf, fmt, ap);

    /* cannot use relative: one of the args may be generated by it */

    log = open_file(log_file, OPEN_APPEND);
    if (log == NULL)
	return 0;

    fprintf(log, "%c: %s (%s): %s\n", type,
	    date_time((time_t) 0), user_name(), buf);

    fclose(log);
    return 1;
}

static void
mail_sys_error(char *err, int isfatal)
{
    FILE           *f;
    char            cmd[FILENAME * 2];

    switch (mail_errors_mode) {
	case 0:
	    return;
	case 1:
	    if (!isfatal)
		return;
	default:
	    break;
    }

#ifdef FATAL_ERROR_MAIL_CMD
    strcpy(cmd, FATAL_ERROR_MAIL_CMD);
#else

#ifdef MAILX
    sprintf(cmd, "%s -s 'nnmaster %s' %s", MAILX,
	    isfatal ? "fatal error" : "warning", OWNER);
#else
    sprintf(cmd, "mail %s", OWNER);
#endif

#endif

    if ((f = popen(cmd, "w")) == NULL)
	return;
    fprintf(f, "nnmaster %s\n\n%system error:\n%s\n",
	    isfatal ? "terminated" : "warning",
	    isfatal ? "Fatal s" : "S", err);
    pclose(f);
}

void
sys_error(char *fmt,...)
{
    va_list         ap;
    int             rval;
    char            buf[512];

#ifndef HAVE_SYSLOG
    FILE           *f;
#endif

    va_start(ap, fmt);
    rval = enter_log('E', fmt, ap);
    va_end(ap);

    va_start(ap, fmt);
    vsprintf(buf, fmt, ap);
    va_end(ap);

    if (who_am_i == I_AM_MASTER) {
	mail_sys_error(buf, 1);
	if (dont_write_console)
	    nn_exit(7);

#ifndef HAVE_SYSLOG
	f = open_file("/dev/console", OPEN_CREATE);
	if (f == NULL)
	    nn_exit(8);
	fprintf(f, "\n\rNNMASTER FATAL ERROR\n\r%s\n\n\r", buf);
	fclose(f);
#else				/* HAVE_SYSLOG */
	openlog("nnmaster", LOG_CONS, LOG_DAEMON);
	syslog(LOG_ALERT, "%s", buf);
	closelog();
#endif				/* HAVE_SYSLOG */

	nn_exit(7);
    }
    if (rval)
	nn_exitmsg(1, "%s", buf);
    else
	nn_exitmsg(1, "Can't open log file!\n%s", buf);
}

/*
 *	sys_warning: like sys_error but MASTER will return with -1
 *	instead of exit.  Clients still terminate!
 */

int
sys_warning(char *fmt,...)
{
    va_list         ap;
    int             rval;
    char            buf[512];

#ifndef HAVE_SYSLOG
    FILE           *f;
#endif

    static char    *last_err = NULL;

    va_start(ap, fmt);
    vsprintf(buf, fmt, ap);
    va_end(ap);

    if (last_err != NULL) {
	if (strcmp(last_err, buf) == 0)
	    return -1;
	free(last_err);
    }
    last_err = copy_str(buf);

    va_start(ap, fmt);
    rval = enter_log('R', fmt, ap);
    va_end(ap);

    if (who_am_i != I_AM_MASTER) {
	if (rval)
	    nn_exitmsg(1, "%s", buf);
	else
	    nn_exitmsg(1, "Can't open log file!\n%s", buf);
    }
    mail_sys_error(buf, 0);
    if (dont_write_console)
	return -1;

#ifndef HAVE_SYSLOG
    if ((f = open_file("/dev/console", OPEN_CREATE)) != NULL) {
	fprintf(f, "\n\rNNMASTER WARNING\n\r%s\n\n\r", buf);
	fclose(f);
    }
#else				/* HAVE_SYSLOG */
    openlog("nnmaster", LOG_CONS, LOG_DAEMON);
    syslog(LOG_ALERT, "%s", buf);
    closelog();
#endif				/* HAVE_SYSLOG */

    return -1;
}

int
log_entry(int type, char *fmt,...)
{
    va_list         ap;
    int             rval;

    va_start(ap, fmt);
    rval = enter_log(type, fmt, ap);
    va_end(ap);

    return rval;
}

char           *
user_name(void)
{
    static char    *user = NULL;
    struct passwd  *pw;

    if (who_am_i == I_AM_MASTER)
	return "M";
    if (who_am_i == I_AM_EXPIRE)
	return "X";

    if (user == NULL) {
	user = getlogin();
	if (user != NULL && *user != NUL)
	    goto out;

	pw = getpwuid(user_id);
	if (pw != NULL && pw->pw_name[0] != NUL) {
	    user = copy_str(pw->pw_name);
	    goto out;
	}
	user = getenv("LOGNAME");
	if (user != NULL && *user != NUL)
	    goto out;
	user = getenv("USER");
	if (user != NULL && *user != NUL)
	    goto out;
	user = "?";
    }
out:
    return user;
}

time_t
cur_time(void)
{
    time_t          t;

    time(&t);
    return t;
}

char           *
date_time(time_t t)
{
    char           *str;

    if (t == (time_t) 0)
	t = cur_time();
    str = ctime(&t);

    str[16] = 0;
    return str + 4;
}

char           *
plural(long n)
{
    return n != 1 ? "s" : "";
}

/*
 *	memory management
 */

 /* #define MEM_DEBUG *//* trace memory usage */

static void
mem_error(int t, int32 bytes)
{
    char            buf[200];

    if (t == 1) {
	sprintf(buf, "Alloc failed: unsigned too short to represent %ld bytes",
		(long) bytes);
    } else {
	sprintf(buf, "Out of memory - cannot allocate %ld bytes",
		(long) bytes);
    }

    sys_error(buf);
}

char           *
mem_obj(unsigned size, int32 nelt)
{
    unsigned        n;
    char           *obj;

    n = nelt;
    if (n != nelt)
	mem_error(1, nelt);

    obj = calloc(n, size);

#ifdef MEM_DEBUG
    printf("CALLOC(%u,%u) => %lx\n", n, size, (long) obj);
#endif

    if (obj == NULL)
	mem_error(2, (int32) (size * nelt));
    return obj;
}

char           *
mem_str(int32 nelt)
{
    unsigned        n;
    char           *obj;

    n = nelt;
    if (n != nelt)
	mem_error(1, nelt);

    obj = malloc(n);

#ifdef MEM_DEBUG
    printf("MALLOC(%u) => %lx\n", n, (long) obj);
#endif

    if (obj == NULL)
	mem_error(2, nelt);
    return obj;
}

char           *
mem_resize(char *obj, unsigned size, int32 nelt)
{
    unsigned        n;
    char           *obj1;

    if (obj == NULL)
	return mem_obj(size, nelt);

    nelt *= size;

    n = nelt;
    if (n != nelt)
	mem_error(1, nelt);

    obj1 = realloc(obj, n);

#ifdef MEM_DEBUG
    printf("REALLOC(%lx, %u) => %lx\n", (long) obj, n, (long) obj1);
#endif

    if (obj1 == NULL)
	mem_error(2, (int32) size);
    return obj1;
}


void
mem_clear(register char *obj, unsigned size, register int32 nelt)
{
    nelt *= size;
    while (--nelt >= 0)
	*obj++ = NUL;
}

void
mem_free(char *obj)
{

#ifdef MEM_DEBUG
    printf("FREE(%lx)\n", (long) obj);
#endif

    if (obj != NULL)
	free(obj);
}

#ifndef HAVE_MKDIR
mkdir(char *path, int mode)
{
    char            command[FILENAME * 2 + 20];

    sprintf(command, "{ mkdir %s && chmod %o %s ; } > /dev/null 2>&1",
	    path, mode, path);
    return system(command) != 0 ? -1 : 0;
}

#endif


int
nn_truncate(char *path, off_t len)
{

#ifdef HAVE_TRUNCATE
    /* Easy... we already have it... */
    return truncate(path, len);

#else				/* HAVE_TRUNCATE */

    int             fd;

#ifndef O_TRUNC
    struct stat     st;
#endif

    if (len != 0)
	sys_error("nn_truncate(%s,%ld): non-zero length", path, (long) len);

#ifdef O_TRUNC
    fd = open(path, O_WRONLY | O_TRUNC);
#else
    if (stat(path, &st) < 0)
	return -1;
    fd = creat(path, st.st_mode & 07777);
#endif

    if (fd < 0)
	return -1;
    close(fd);
    return 0;
#endif
}

/* Should really have some sort of configure file and use strdup().. */
char           *
strsave(char *s)
{
    int             l;
    char           *buf = NULL;

    if (s) {
	l = strlen(s);
	buf = malloc(l + 1);
	if (buf) {
	    if (l)
		strcpy(buf, s);
	    else
		buf[0] = '\0';
	}
    }
    return buf;
}

char           *
str3save(char *s1, char *s2, char *s3)
{
    int             l;
    char           *buf;

    if (!s1)
	s1 = "";
    if (!s2)
	s2 = "";
    if (!s3)
	s3 = "";

    l = strlen(s1) + strlen(s2) + strlen(s3);

    buf = malloc(l + 1);
    if (buf) {
	strcpy(buf, s1);
	strcat(buf, s2);
	strcat(buf, s3);
    }
    return buf;
}

/*
 * This quick function is a hack of a replacement for fgets, but is
 * unlimited in line length.  It returns a pointer to a buffer which
 * just happens to be malloc()ed and is reused next time.  IE: you had
 * best strdup() it.
 *
 * This was inspired by an article I saw in alt.sources but forgot to
 * save.
 *
 * -Peter Wemm <peter@DIALix.oz.au>
 */

static char    *buf = NULL;	/* buffer for line */
static int      size = 0;	/* size of buffer */

#define INITIAL_CHUNK	256	/* Initial malloc size */
#define GROW_CHUNK	256	/* extend with realloc in units if this */

/*
 * Get a LONG line.
 * Returns a pointer to a '\0' terminated string which will be
 * overwritten on the next call.
 * Returns NULL if error or eof, or if nothing could be read at all.
 *
 * Note: It does not react very well to '\0' characters in the byte stream
 */

char           *
fgetstr(FILE * f)
{
    int             len;	/* # of chars stored into buf before '\0' */
    char           *s;

    if (size == 0 && buf == NULL) {
	size = INITIAL_CHUNK;
	buf = malloc(size);
	if (buf == NULL) {
	    size = 0;
	    nn_exitmsg(50, "cant allocate initial chunk\n");
	    return NULL;
	}
	clearobj(buf, size, 1);
    }
    len = 0;

    while (fgets(buf + len, size - len, f) != NULL) {
	len += strlen(len + buf);
	if (len > 0 && buf[len - 1] == '\n')
	    break;		/* the whole line has been read */

	size += GROW_CHUNK;
	s = realloc(buf, size);

	if (!s) {		/* Malloc failure */
	    size = 0;		/* panic... */
	    buf = NULL;
	    nn_exitmsg(51, "cant realloc chunk\n");
	    return NULL;
	}
	buf = s;
	clearobj(buf + len, size - len, 1);
    }

    if (len == 0) {
	return NULL;		/* nothing read (eof or error) */
    }
    /* For when we are reading from a NNTP stream. */
    if (len > 1 && buf[len - 1] == '\n')
	--len;
    if (len > 1 && buf[len - 1] == '\r')
	--len;
    buf[len] = '\0';		/* unconditionally terminate string, */
    /* possibly overwriting CR and/or NL */
    return buf;
}

/*
 *	nn_getline - gets a line from stdin
 *			returns the length of the line
 */

int
nn_getline(char *line, int max)
{
    if (fgets(line, max, stdin) == NULL)
	return 0;
    else
	return strlen(line);
}

#ifdef NEED_STRERROR
/* strerror for old OSs that don't include it */

extern int      sys_nerr;
extern char    *sys_errlist[];

char           *
strerror(int num)
{
    static char     mesg[40];

    if (num < 0 || num > sys_nerr) {
	sprintf(mesg, "Unknown error (%d)", num);
	return mesg;
    } else
	return sys_errlist[num];
}

#endif
