/* libutil -- miscellaneous stuff Written by Arnt Gulbrandsen and copyright 1995 Troll Tech AS, Postboks 6133 Etterstad, 0602 Oslo, Norway, fax +47 22646949. Modified by Cornelius Krasel and Randolf Skerka . Copyright of the modifications 1997. Modified by Kent Robotti . Copyright of the modifications 1998. Modified by Markus Enzenberger . Copyright of the modifications 1998. Modified by Cornelius Krasel . Copyright of the modifications 1998, 1999. Modified by Matthias Andree . Copyright of the modifications 1999 - 2002. Modified and copyright of the modifications 2002 by Ralf Wildenhues . 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "system.h" #include #include 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; }