1 /*
2 libutil -- miscellaneous stuff
3 
4 Written by Arnt Gulbrandsen <agulbra@troll.no> and copyright 1995
5 Troll Tech AS, Postboks 6133 Etterstad, 0602 Oslo, Norway, fax +47
6 22646949.
7 Modified by Cornelius Krasel <krasel@wpxx02.toxi.uni-wuerzburg.de>
8 and Randolf Skerka <Randolf.Skerka@gmx.de>.
9 Copyright of the modifications 1997.
10 Modified by Kent Robotti <robotti@erols.com>. Copyright of the
11 modifications 1998.
12 Modified by Markus Enzenberger <enz@cip.physik.uni-muenchen.de>.
13 Copyright of the modifications 1998.
14 Modified by Cornelius Krasel <krasel@wpxx02.toxi.uni-wuerzburg.de>.
15 Copyright of the modifications 1998, 1999.
16 Modified by Matthias Andree <matthias.andree@gmx.de>.
17 Copyright of the modifications 1999 - 2002.
18 Modified and copyright of the modifications 2002 by Ralf Wildenhues
19 <ralf.wildenhues@gmx.de>.
20 
21 See file COPYING for restrictions on the use of this software.
22 */
23 
24 #include "leafnode.h"
25 #include "validatefqdn.h"
26 #include "strlcpy.h"
27 #include "mastring.h"
28 #include "ln_log.h"
29 
30 #include <fcntl.h>
31 #include <sys/uio.h>
32 #include <sys/param.h>
33 #include <ctype.h>
34 #include <errno.h>
35 #include <limits.h>
36 #include <stdlib.h>
37 #include <netdb.h>
38 #include <stdio.h>
39 #include <string.h>
40 #include <syslog.h>
41 #include <pwd.h>
42 #include <sys/stat.h>
43 #include <sys/types.h>
44 #include <unistd.h>
45 #include "system.h"
46 #include <signal.h>
47 #include <stdarg.h>
48 
49 static void whoami(void);
50 
suicide(void)51 static void suicide(void) {
52     /* just in case */
53     fflush(stdout);
54     fflush(stderr);
55     raise(SIGKILL);
56 }
57 
58 char fqdn[FQDNLEN + 1] = "";
59 
60 static const mode_t default_umask = 0002;
61 
62 /* xoverutil global vars */
63 struct xoverinfo *xoverinfo;
64 unsigned long xfirst, xlast;
65 
66 /* kludge around C89 const not being a compile-time constant */
67 enum { hashsize = 1000 };
68 
69 static int
createmsgiddir(void)70 createmsgiddir(void) {
71     mastr *dir = mastr_new(1024);
72     mastr *mid = mastr_new(1024);
73     DIR *d;
74     int rc = 0;
75     int havedir[hashsize] = {0};
76 
77     mastr_vcat(dir, spooldir, "/message.id", NULL);
78     d = opendir(mastr_str(dir));
79     if (d) {
80 	struct dirent *de;
81 	unsigned long u;
82 	const char *t;
83 	char *e;
84 
85 	/* read directory - should be faster than stat */
86 	while(errno = 0, de = readdir(d)) {
87 	    t = de->d_name;
88 	    if (isdigit((unsigned char)*t)) {
89 		u = strtoul(t, &e, 10);
90 		if (e > t && u < hashsize)
91 		    havedir[u] = 1;
92 	    }
93 	}
94 
95 	if (errno)
96 	    ln_log(LNLOG_SERR, LNLOG_CTOP, "error reading directory %s: %m",
97 		    mastr_str(dir));
98 
99 	closedir(d);
100 
101 	/* create missing */
102 	for(u = 0; u < hashsize; u++) {
103 	    char b[4];
104 
105 	    snprintf(b, sizeof(b), "%03lu", u);
106 	    mastr_clear(mid);
107 	    if (!havedir[u]) {
108 		mastr_vcat(mid, spooldir, "/message.id/", b, NULL);
109 		if (mkdir(mastr_str(mid), 02755)) {
110 		    ln_log(LNLOG_SERR, LNLOG_CTOP, "error creating directory %s: %m",
111 			    mastr_str(mid));
112 		    break;
113 		}
114 	    }
115 	}
116     } else {
117 	ln_log(LNLOG_SERR, LNLOG_CTOP, "cannot read %s: %m", mastr_str(dir));
118 	rc = -1;
119     }
120 
121     mastr_delete(mid);
122     mastr_delete(dir);
123     return rc;
124 }
125 
126 static struct { const char* name; mode_t mode; } dirs[] = {
127     {"", 04755 },
128     {"interesting.groups", 02775 },
129     {"leaf.node", 0755 },
130     {"failed.postings", 02775 },
131     {"message.id", 0755 },
132     {"out.going", 0755 },
133     {"temp.files", 0755 },
134 };
135 
136 static const int dirs_count = sizeof(dirs)/sizeof(dirs[0]);
137 
138 /*
139  * initialize all global variables
140  */
141 /*@-globstate@*/
142 int
initvars(char * progname)143 initvars(char *progname)
144 {
145 #ifndef TESTMODE
146     struct passwd *pw;
147 #endif
148     char s[SIZE_s+1];
149     int i;
150     char *t;
151 
152     active = NULL;
153     xoverinfo = NULL;
154     xfirst = 0;
155     xlast = 0;
156 
157     /* config.c stuff does not have to be initialized */
158 
159     expire_base = NULL;
160     servers = NULL;
161 
162     t = getenv("LN_DEBUG");
163     if (t)
164 	debugmode = atoi(t);
165 
166     (void)umask(default_umask);
167     if (strlen(spooldir) != strspn(spooldir, PORTFILENAMECSET "/")) {
168 	/* verrecke! */
169 	syslog(LOG_CRIT, "Fatal: spooldir contains illegal characters. "
170 	       "Recompile leafnode with a proper spooldir setting.");
171 	suicide();
172     }
173 
174 #ifndef TESTMODE
175     pw = getpwnam(NEWS_USER);
176     if (!pw) {
177 	fprintf(stderr, "no such user: %s\n", NEWS_USER);
178 	return FALSE;
179     }
180 #endif
181 
182     /* These directories should exist anyway */
183     for (i = 0 ; i < dirs_count ; i++) {
184 	xsnprintf(s, SIZE_s, "%s/%s", spooldir, dirs[i].name);
185 	if ((mkdir(s, dirs[i].mode) && errno != EEXIST)
186 		|| chmod(s, dirs[i].mode)
187 #ifndef TESTMODE
188 	|| chown(s, pw->pw_uid, pw->pw_gid)
189 #endif
190 	   ) {
191 	    int e = errno;
192 	    struct stat st;
193 	    if (stat(s, &st)
194 #ifndef TESTMODE
195 		    || st.st_uid != pw->pw_uid
196 #endif
197 	       ) {
198 		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),
199 			NEWS_USER);
200 		syslog(LOG_WARNING, "Warning: cannot create %s with proper ownership: %s", s, strerror(e));
201 		suicide();
202 	    }
203 	}
204     }
205 
206     whoami();
207 
208 #ifndef TESTMODE
209     if (progname) {
210 	int e;
211 
212 #ifdef HAVE_SETGID
213 	e = setgid(pw->pw_gid);
214 #else
215 	e = setregid(pw->pw_gid, pw->pw_gid);
216 #endif
217 
218 	if (e) {
219 	    syslog(LOG_ERR, "%s: setting group id to %ld failed: %s",
220 		    progname, (long)pw->pw_gid, strerror(errno));
221 	    fprintf(stderr, "%s: setting group id to %ld failed: %s\n",
222 		    progname, (long)pw->pw_gid, strerror(errno));
223 	}
224 
225 #ifdef HAVE_SETUID
226 	e = setuid(pw->pw_uid);
227 #else
228 	e = setreuid(pw->pw_uid, pw->pw_uid);
229 #endif
230 	if (e) {
231 	    syslog(LOG_ERR, "%s: setting user id to %ld failed: %s. (Real uid is %ld, effective %ld)",
232 		    progname, (long)pw->pw_uid, strerror(errno), (long)getuid(), (long)geteuid());
233 	    fprintf(stderr, "%s: setting user id to %ld failed: %s. (Real uid is %ld, effective %ld)\n",
234 		    progname, (long)pw->pw_uid, strerror(errno), (long)getuid(), (long)geteuid());
235 	}
236 
237 	/* extra sanity check - read back the new values */
238 	if (getuid() != pw->pw_uid || getgid() != pw->pw_gid) {
239 	    syslog(LOG_ERR, "%s: must be run as %s or root", progname, NEWS_USER);
240 	    fprintf(stderr, "%s: must be run as %s or root\n", progname, NEWS_USER);
241 	    endpwent();
242 	    return FALSE;
243 	}
244     }
245 #endif				/* not TESTMODE */
246     endpwent();
247 
248     if (chdir(spooldir) || (i = open(".", O_RDONLY)) < 0) {
249 	int e = errno;
250 	syslog(LOG_CRIT, "Fatal: cannot change to or open spooldir: %m");
251 	fprintf(stderr, "Fatal: cannot change or open spooldir: %s\n",
252 		strerror(e));
253 	suicide();
254     }
255     (void)close(i);
256 
257     /* create missing message.id directories */
258     if (createmsgiddir())
259 	return FALSE;
260 
261     return TRUE;
262 }
263 
264 /*@=globstate@*/
265 
266 /*
267  * check whether "groupname" is represented in interesting.groups without
268  * touching the file
269  */
270 int
isinteresting(const char * groupname)271 isinteresting(const char *groupname)
272 {
273     DIR *d;
274     struct dirent *de;
275     char s[SIZE_s+1];
276 
277     xsnprintf(s, SIZE_s, "%s/interesting.groups", spooldir);
278     d = opendir(s);
279     if (!d) {
280 	syslog(LOG_ERR, "Unable to open directory %s: %m", s);
281 	printf("Unable to open directory %s\n", s);
282 	return FALSE;
283     }
284 
285     while ((de = readdir(d)) != NULL) {
286 	if (strcasecmp(de->d_name, groupname) == 0) {
287 	    closedir(d);
288 	    return TRUE;
289 	}
290     }
291     closedir(d);
292     return FALSE;
293 }
294 
295 void
overrun(void)296 overrun(void)
297 /* report buffer overrun */
298 {
299     syslog(LOG_CRIT, "buffer size too small, cannot continue, aborting program");
300     abort();
301     kill(getpid(), SIGKILL);	/* really die! */
302 }
303 
304 /** construct file name for message.id/NNN/ in spooldir from message ID */
305 const char *
lookup(const char * msgid)306 lookup(const char *msgid /** if LOOKUP_FREE, release static resources */)
307 {
308     static char *name = NULL;
309     static size_t namelen = 0;
310     unsigned int r;
311     size_t i;
312 
313     if (msgid == LOOKUP_FREE) {
314 	namelen = 0;
315 	if (name)
316 	    free(name);
317 	return NULL;
318     }
319 
320     if (!msgid || !*msgid)
321 	return NULL;
322 
323     i = strlen(msgid) + strlen(spooldir) + 30;
324 
325     if (!name) {
326 	name = (char *)critmalloc(i, "lookup");
327 	namelen = i;
328     } else if (i > namelen) {
329 	free(name);
330 	name = (char *)critmalloc(i, "lookup");
331 	namelen = i;
332     }
333 
334     strcpy(name, spooldir);	/* RATS: ignore */
335     strcat(name, "/message.id/000/");	/* RATS: ignore */
336     i = strlen(name);
337     strcat(name, msgid);	/* RATS: ignore */
338 
339     r = 0;
340     do {
341 	if (name[i] == '/')
342 	    name[i] = '@';
343 	else if (name[i] == '>')
344 	    name[i + 1] = '\0';
345 	r += (int)(name[i]);
346 	r += ++i;
347     } while (name[i]);
348 
349     i = strlen(spooldir) + 14;	/* to the last digit */
350     r = (r % 999) + 1;
351     name[i--] = '0' + (char)(r % 10);
352     r /= 10;
353     name[i--] = '0' + (char)(r % 10);
354     r /= 10;
355     name[i] = '0' + (char)(r);
356     return name;
357 }
358 
359 #define LM_SIZE 65536
360 
361 static int
makedir(char * d)362 makedir(char *d)
363 {
364     char *p;
365     char *q;
366 
367     if (verbose > 3)
368 	printf("makedir(%s)\n", d);
369     if (!d || *d != '/' || chdir("/"))
370 	return 0;
371     q = d;
372     do {
373 	*q = '/';
374 	p = q;
375 	q = strchr(++p, '/');
376 	if (q)
377 	    *q = '\0';
378 	if (!chdir(p))
379 	    continue;		/* ok, I do use it sometimes :) */
380 	if (errno == ENOENT)
381 	    if (mkdir(p, 0775)) {
382 		syslog(LOG_ERR, "mkdir %s: %m", d);
383 		exit(1);
384 	    }
385 	if (chdir(p)) {
386 	    syslog(LOG_ERR, "chdir %s: %m", d);
387 	    exit(1);
388 	}
389     } while (q);
390     return 1;
391 }
392 
393 /* prefix numeric group name components with a minus */
migrate(const char * name)394 static int migrate(const char *name) {
395     char *p = critstrdup(name, "dogroup"), *q, *t = NULL;
396 
397     /* shortcut: don't call into chdir() excessively */
398     for(q = strtok(p, "."); q; q = strtok(NULL, ".")) {
399 	if (strspn(q, "0123456789") == strlen(q)) break;
400     }
401     if (!q) {
402 	free(p);
403 	return 0;
404     }
405 
406     if (chdir(spooldir)) goto barf;
407 
408     for(q = strtok(p, "."); q; q = strtok(NULL, ".")) {
409 	t = critmalloc(strlen(q)+2, "dogroup");
410 	t[0] = '-';
411 	strcpy(t+1, q);
412 	if (strspn(q, "0123456789") == strlen(q)) {
413 	    struct stat st;
414 	    if (0 == chdir(t)) {
415 		free(t);
416 		continue;
417 	    }
418 	    if (0 == stat(q, &st) && S_ISDIR(st.st_mode)) {
419 		if (rename(q, t)) {
420 		    ln_log(LNLOG_SERR, LNLOG_CTOP, "cannot rename %s to %s: %m",
421 			    q, t);
422 		    goto barf;
423 		}
424 		if (0 == chdir(t)) {
425 		    free(t);
426 		    continue;
427 		}
428 	    }
429 	    goto barf;
430 	}
431 	if (chdir(q)) {
432 	    goto barf;
433 	}
434 	free(t);
435     }
436     free(p);
437     return 0;
438 barf:
439     free(p);
440     free(t);
441     return -1;
442 }
443 
444 /* chdir to the directory of the argument if it's a valid group */
445 int
chdirgroup(const char * group,int creatdir)446 chdirgroup(const char *group, int creatdir)
447 {
448     char *p;
449     const char *c;
450 
451     if (group && *group) {
452 	int dots = 0;
453 	char *nam, *q;
454 	mastr *d = mastr_new(1024);
455 
456 	migrate(group);
457 	mastr_vcat(d, spooldir, "/", group, NULL);
458 	p = mastr_modifyable_str(d) + strlen(spooldir) + 1;
459 	while (*p) {
460 	    if (*p == '.') {
461 		*p = '/';
462 	    } else
463 		*p = tolower((unsigned char)*p);
464 	    p++;
465 	}
466 	for (c = mastr_str(d);*c;c++) {
467 	    if (*c == '/' && c[1] && strspn(c+1, "0123456789") == strcspn(c+1, "/"))
468 		dots++;
469 	}
470 	nam = critmalloc(mastr_len(d) + dots + 1, "chdirgroup");
471 	for (c = mastr_str(d), q=nam;*c;c++) {
472 	    *(q++) = *c;
473 	    if (*c == '/' && c[1] && strspn(c+1, "0123456789") == strcspn(c+1, "/"))
474 		*(q++) = '-';
475 	}
476 	*q = 0;
477 	mastr_delete(d);
478 
479 	if (!chdir(nam)) {
480 	    free(nam);
481 	    return 1;		/* chdir successful */
482 	}
483 	if (creatdir) {
484 	    int r = makedir(nam);
485 	    free(nam);
486 	    return r;
487 	}
488 	free(nam);
489     }
490     return 0;
491 }
492 
493 #ifdef HAVE_IPV6
494 /* get the fully qualified domain name of this box into fqdn */
495 /* IPv6-capable version by Boris Manojlovic <boris@steki.net> */
496 static void
whoami(void)497 whoami(void)
498 {
499     struct addrinfo hints, *res;
500     int debugqual = 0;
501     char *x;
502 
503     if ((x = getenv("LN_DEBUG_QUALIFICATION")) != NULL
504 	&& *x)
505 	debugqual = 1;
506     memset(&hints, 0, sizeof hints);
507     hints.ai_flags = AI_CANONNAME;
508 
509     if (!gethostname(fqdn, sizeof(fqdn)) && !getaddrinfo(fqdn, NULL, &hints, &res)) {
510 	if (res->ai_canonname != NULL) {
511             xstrlcpy(fqdn,res->ai_canonname, sizeof(fqdn));
512             if (debugqual) syslog(LOG_DEBUG, "canonical hostname: %s", fqdn);
513         }
514         freeaddrinfo(res);
515     }
516 }
517 #else
518 /* get the fully qualified domain name of this box into fqdn */
519 /* legacy version, uses legacy (IPv4) gethostby*() interfaces */
520 static void
whoami(void)521 whoami(void)
522 {
523     struct hostent *he;
524     int debugqual = 0;
525     char *x;
526 
527     if ((x = getenv("LN_DEBUG_QUALIFICATION")) != NULL
528 	&& *x)
529 	debugqual = 1;
530 
531     if (!gethostname(fqdn, sizeof(fqdn)) && (he = gethostbyname(fqdn)) != NULL) {
532 	xstrlcpy(fqdn, he->h_name, sizeof(fqdn));
533 	if (debugqual) syslog(LOG_DEBUG, "canonical hostname: %s", fqdn);
534 	if (!is_validfqdn(fqdn)) {
535 	    char **alias;
536 	    alias = he->h_aliases;
537 	    while (alias && *alias) {
538 		if (debugqual) {
539 		    syslog(LOG_DEBUG, "alias for my hostname: %s", *alias);
540 		}
541 		if (is_validfqdn(*alias)) {
542 		    xstrlcpy(fqdn, *alias, sizeof(fqdn));
543 		    break;
544 		} else {
545 		    alias++;
546 		}
547 	    }
548 	}
549 	endhostent();
550     }
551 }
552 #endif
553 
554 /*
555  * prepend string "newentry" to stringlist "list".
556  */
557 void
prependtolist(struct stringlist ** list,const char * newentry)558 prependtolist(struct stringlist **list, /*@unique@*/ const char *newentry)
559 {
560 
561     struct stringlist *ptr;
562 
563     ptr = (struct stringlist *)critmalloc(sizeof(struct stringlist) +
564 					  strlen(newentry),
565 					  "Allocating space in stringlist");
566     strcpy(ptr->string, newentry);	/* RATS: ignore */
567     ptr->next = *list;
568     *list = ptr;
569 }
570 
571 /*
572  * find a string in a stringlist
573  * return pointer to string if found, NULL otherwise
574  */
575 char *
findinlist(struct stringlist * haystack,char * needle)576 findinlist(struct stringlist *haystack, char *needle)
577 {
578     struct stringlist *a;
579 
580     a = haystack;
581     while (a && a->string) {
582 	if (strncmp(needle, a->string, strlen(needle)) == 0)
583 	    return a->string;
584 	a = a->next;
585     }
586     return NULL;
587 }
588 
589 /*
590  * find a string in a stringlist
591  * return pointer to string if found, NULL otherwise
592  */
593 struct stringlist **
lfindinlist(struct stringlist ** haystack,char * needle,size_t len)594 lfindinlist(struct stringlist **haystack, char *needle, size_t len)
595 {
596     struct stringlist **a;
597 
598     a = haystack;
599     while (a && *a && (*a)->string) {
600 	if (strncmp(needle, (*a)->string, len) == 0)
601 	    return a;
602 	a = &(*a)->next;
603     }
604     return NULL;
605 }
606 
replaceinlist(struct stringlist ** haystack,char * needle,size_t len)607 void replaceinlist(struct stringlist **haystack, char *needle, size_t len)
608 {
609     struct stringlist **f = lfindinlist(haystack, needle, len);
610     struct stringlist *n;
611     if (!f) prependtolist(haystack, needle);
612     else {
613 	n = (*f)->next;
614     	free(*f);
615     	*f = (struct stringlist *)critmalloc(sizeof(struct stringlist) +
616 			strlen(needle), "Allocating space in stringlist");
617 	strcpy((*f)->string, needle); /* RATS: ignore */
618 	(*f)->next = n;
619     }
620 }
621 
622 /*
623  * free a list
624  */
625 void
freelist(struct stringlist * list)626 freelist( /*@only@*/ struct stringlist *list)
627 {
628     struct stringlist *a;
629 
630     while (list) {
631 	a = list->next;
632 	free(list);
633 	list = a;
634     }
635 }
636 
637 /* next few routines implement a mapping from message-id to article
638    number, and clearing the entire space */
639 
640 struct msgidtree {
641     struct msgidtree *left;
642     struct msgidtree *right;
643     char msgid[1]; /* RATS: ignore */
644 };
645 
646 static struct msgidtree *head;	/* starts as NULL */
647 
648 static int
comparemsgid(const char * id1,const char * id2)649 comparemsgid(const char *id1, const char *id2)
650 {
651     int c;
652 
653     /* comparing only by msgid is uncool because the tree becomes
654        very unbalanced */
655     c = strcmp(strchr(id1, '@'), strchr(id2, '@'));
656     if (!c)
657 	c = strcmp(id1, id2);
658     return c;
659 }
660 
661 void
insertmsgid(const char * msgid)662 insertmsgid( /*@unique@*/ const char *msgid)
663 {
664     struct msgidtree **a;
665     int c;
666 
667     if (strchr(msgid, '@') == 0)
668 	return;
669 
670     a = &head;
671     while (a) {
672 	if (*a) {
673 	    c = comparemsgid((*a)->msgid, msgid);
674 	    if (c < 0)
675 		a = &((*a)->left);
676 	    else if (c > 0)
677 		a = &((*a)->right);
678 	    else {
679 		return;
680 	    }
681 	} else {
682 	    *a = (struct msgidtree *)
683 		critmalloc(sizeof(struct msgidtree) + strlen(msgid),
684 			   "Building expiry database");
685 	    (*a)->left = NULL;
686 	    (*a)->right = NULL;
687 	    strcpy((*a)->msgid, msgid);	/* RATS: ignore */
688 	    return;
689 	}
690     }
691 }
692 
693 /* returns 0 if not found, 1 otherwise */
694 int
findmsgid(const char * msgid)695 findmsgid(const char *msgid)
696 {
697     struct msgidtree *a;
698     int c;
699 
700     /* domain part differs more than local-part, so try it first */
701 
702     if (NULL == strchr(msgid, '@'))
703 	return 0;
704 
705     a = head;
706     while (a) {
707 	c = comparemsgid(a->msgid, msgid);
708 	if (c < 0)
709 	    a = a->left;
710 	else if (c > 0)
711 	    a = a->right;
712 	else
713 	    return 1;
714     }
715     return 0;
716 }
717 
718 static void
begone(struct msgidtree * m)719 begone( /*@null@*//*@only@*/ struct msgidtree *m)
720 {
721     if (m) {
722 	begone(m->right);
723 	begone(m->left);
724 	free((char *)m);
725     }
726 }
727 
728 void
clearidtree(void)729 clearidtree(void)
730 {
731     begone(head);
732     head = NULL;
733 }
734 
735 static int
xtraverseidtree(struct msgidtree * m,tmihook h)736 xtraverseidtree(struct msgidtree *m, tmihook h)
737 {
738     int e = 0;
739     if (!m) return 0;
740     e |= xtraverseidtree(m->left, h);
741     e |= h(m->msgid);
742     e |= xtraverseidtree(m->right, h);
743     return e;
744 }
745 
746 int
traverseidtree(tmihook h)747 traverseidtree(tmihook h) {
748     return xtraverseidtree(head, h);
749 }
750 
751 /*@dependent@*/ const char *
rfctime(void)752 rfctime(void)
753 {
754     static char date[128]; /* RATS: ignore */
755     const char *months[] = { "Jan", "Feb", "Mar", "Apr",
756 	"May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
757     };
758     const char *days[] = { "Sun", "Mon", "Tue", "Wed",
759 	"Thu", "Fri", "Sat"
760     };
761     time_t now;
762     struct tm gm;
763 
764     now = time(NULL);
765 #ifdef HAVE_STRUCT_TM_TM_GMTOFF
766     {
767 	char sign;
768 	long off;
769 
770 	gm = *(localtime(&now));
771 	/* fiddle a bit to make sure we don't get negative a%b results:
772 	 * make sure operands are non-negative */
773 	off = gm.tm_gmtoff/60;
774 	sign = off < 0 ? '-' : '+';
775 	off = labs(off);
776 	xsnprintf(date, sizeof(date), "%3s, %d %3s %4d %02d:%02d:%02d %c%02ld%02ld",
777 		days[gm.tm_wday], gm.tm_mday, months[gm.tm_mon],
778 		gm.tm_year + 1900, gm.tm_hour, gm.tm_min, gm.tm_sec,
779 		sign, off / 60, off % 60);
780     }
781 #else
782     gm = *(gmtime(&now));
783     xsnprintf(date, sizeof(date), "%3s, %d %3s %4d %02d:%02d:%02d -0000",
784 	      days[gm.tm_wday], gm.tm_mday, months[gm.tm_mon],
785 	      gm.tm_year + 1900, gm.tm_hour, gm.tm_min, gm.tm_sec);
786 #endif
787 
788     return (date);
789 }
790 
791 int
ngmatch(const char * pattern,const char * str)792 ngmatch(const char *pattern, const char *str)
793 {
794     return !wildmat(str, pattern);
795 }
796 
797 int
xsnprintf(char * str,size_t n,const char * format,...)798 xsnprintf(char *str, size_t n, const char *format, ...)
799 {
800     int r;
801     va_list ap;
802 
803     va_start(ap, format);
804     r = vsnprintf(str, n, format, ap);
805     va_end(ap);
806 
807     if ((size_t) r >= n || r < 0)
808 	overrun();
809     return r;
810 }
811 
xstrlcpy(char * dst,const char * src,size_t size)812 size_t xstrlcpy(char *dst, const char *src, size_t size)
813 {
814     size_t s;
815     s = strlcpy(dst, src, size);
816     if (s >= size)
817 	overrun();
818     return s;
819 }
820