/*
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