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