Fix _CLOEXEC_SET() fallback implementation
[s-mailx.git] / nam-a-grp.c
blobf08815028397f03803ef68a9e2e3855447e07067
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Name lists, alternates and groups: aliases, mailing lists, shortcuts.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2017 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
6 */
7 /*
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
35 #undef n_FILE
36 #define n_FILE nam_a_grp
38 #ifndef HAVE_AMALGAMATION
39 # include "nail.h"
40 #endif
42 enum group_type {
43 /* Main types (bits not values for easier testing only) */
44 GT_ALIAS = 1<< 0,
45 GT_MLIST = 1<< 1,
46 GT_SHORTCUT = 1<< 2,
47 GT_CHARSETALIAS = 1<< 3,
48 GT_MASK = GT_ALIAS | GT_MLIST | GT_SHORTCUT | GT_CHARSETALIAS,
50 /* Subtype bits and flags */
51 GT_SUBSCRIBE = 1<< 4,
52 GT_REGEX = 1<< 5,
54 /* Extended type mask to be able to reflect what we really have; i.e., mlist
55 * can have GT_REGEX if they are subscribed or not, but `mlsubscribe' should
56 * print out only GT_MLIST which have the GT_SUBSCRIBE attribute set */
57 GT_PRINT_MASK = GT_MASK | GT_SUBSCRIBE
60 struct group {
61 struct group *g_next;
62 size_t g_subclass_off; /* of "subclass" in .g_id (if any) */
63 ui8_t g_type; /* enum group_type */
64 /* Identifying name, of variable size. Dependent on actual "subtype" more
65 * data follows thereafter, but note this is always used (i.e., for regular
66 * expression entries this is still set to the plain string) */
67 char g_id[n_VFIELD_SIZE(-1)];
69 #define GP_TO_SUBCLASS(X,G) \
70 do {\
71 union __group_subclass {void *gs_vp; char *gs_cp;} __gs__;\
72 __gs__.gs_cp = (char*)n_UNCONST(G) + (G)->g_subclass_off;\
73 (X) = __gs__.gs_vp;\
74 } while (0)
76 struct grp_names_head {
77 struct grp_names *gnh_head;
80 struct grp_names {
81 struct grp_names *gn_next;
82 char gn_id[n_VFIELD_SIZE(0)];
85 #ifdef HAVE_REGEX
86 struct grp_regex {
87 struct grp_regex *gr_last;
88 struct grp_regex *gr_next;
89 struct group *gr_mygroup; /* xxx because lists use grp_regex*! ?? */
90 size_t gr_hits; /* Number of times this group matched */
91 regex_t gr_regex;
93 #endif
95 struct group_lookup {
96 struct group **gl_htable;
97 struct group **gl_slot;
98 struct group *gl_slot_last;
99 struct group *gl_group;
102 /* List of alternate names of user */
103 static char **a_nag_altnames;
105 /* `alias' */
106 static struct group *_alias_heads[HSHSIZE]; /* TODO dynamic hash */
108 /* `mlist', `mlsubscribe'. Anything is stored in the hashmap.. */
109 static struct group *_mlist_heads[HSHSIZE]; /* TODO dynamic hash */
111 /* ..but entries which have GT_REGEX set are false lookups and will really be
112 * accessed via sequential lists instead, which are type-specific for better
113 * performance, but also to make it possible to have ".*@xy.org" as a mlist
114 * and "(one|two)@xy.org" as a mlsubscription.
115 * These lists use a bit of QOS optimization in that a matching group will
116 * become relinked as the new list head if its hit count is
117 * (>= ((xy_hits / _xy_size) >> 2))
118 * Note that the hit counts only count currently linked in nodes.. */
119 #ifdef HAVE_REGEX
120 static struct grp_regex *_mlist_regex, *_mlsub_regex;
121 static size_t _mlist_size, _mlist_hits, _mlsub_size, _mlsub_hits;
122 #endif
124 /* `shortcut' */
125 static struct group *_shortcut_heads[HSHSIZE]; /* TODO dynamic hash */
127 /* `charsetalias' */
128 static struct group *_charsetalias_heads[HSHSIZE];
130 /* Same name, while taking care for *allnet*? */
131 static bool_t _same_name(char const *n1, char const *n2);
133 /* Delete the given name from a namelist */
134 static struct name * delname(struct name *np, char const *name);
136 /* Put another node onto a list of names and return the list */
137 static struct name * put(struct name *list, struct name *node);
139 /* Grab a single name (liberal name) */
140 static char const * yankname(char const *ap, char *wbuf,
141 char const *separators, int keepcomms);
143 /* Extraction multiplexer that splits an input line to names */
144 static struct name * _extract1(char const *line, enum gfield ntype,
145 char const *separators, bool_t keepcomms);
147 /* Recursively expand a alias name. Limit expansion to some fixed level.
148 * Direct recursion is not expanded for convenience */
149 static struct name * _gexpand(size_t level, struct name *nlist,
150 struct group *gp, bool_t metoo, int ntype);
152 /* Lookup a group, return it or NULL, fill in glp anyway */
153 static struct group * _group_lookup(enum group_type gt,
154 struct group_lookup *glp, char const *id);
156 /* Easier-to-use wrapper around _group_lookup() */
157 static struct group * _group_find(enum group_type gt, char const *id);
159 /* Iteration: go to the first group, which also inits the iterator. A valid
160 * iterator can be stepped via _next(). A NULL return means no (more) groups
161 * to be iterated exist, in which case only glp->gl_group is set (NULL) */
162 static struct group * _group_go_first(enum group_type gt,
163 struct group_lookup *glp);
164 static struct group * _group_go_next(struct group_lookup *glp);
166 /* Fetch the group id, create it as necessary */
167 static struct group * _group_fetch(enum group_type gt, char const *id,
168 size_t addsz);
170 /* "Intelligent" delete which handles a "*" id, too;
171 * returns a true boolean if a group was deleted, and always succeeds for "*" */
172 static bool_t _group_del(enum group_type gt, char const *id);
174 static struct group * __group_del(struct group_lookup *glp);
175 static void __names_del(struct group *gp);
177 /* Print all groups of the given type, alphasorted */
178 static void _group_print_all(enum group_type gt);
180 static int __group_print_qsorter(void const *a, void const *b);
182 /* Really print a group, actually. Return number of written lines */
183 static size_t _group_print(struct group const *gp, FILE *fo);
185 /* Multiplexers for list and subscribe commands */
186 static int _mlmux(enum group_type gt, char **argv);
187 static int _unmlmux(enum group_type gt, char **argv);
189 /* Relinkers for the sequential match lists */
190 #ifdef HAVE_REGEX
191 static void _mlmux_linkin(struct group *gp);
192 static void _mlmux_linkout(struct group *gp);
193 # define _MLMUX_LINKIN(GP) \
194 do if ((GP)->g_type & GT_REGEX) _mlmux_linkin(GP); while (0)
195 # define _MLMUX_LINKOUT(GP) \
196 do if ((GP)->g_type & GT_REGEX) _mlmux_linkout(GP); while (0)
197 #else
198 # define _MLMUX_LINKIN(GP)
199 # define _MLMUX_LINKOUT(GP)
200 #endif
202 static bool_t
203 _same_name(char const *n1, char const *n2)
205 bool_t rv = FAL0;
206 char c1, c2;
207 NYD_ENTER;
209 if (ok_blook(allnet)) {
210 do {
211 c1 = *n1++;
212 c2 = *n2++;
213 c1 = lowerconv(c1);
214 c2 = lowerconv(c2);
215 if (c1 != c2)
216 goto jleave;
217 } while (c1 != '\0' && c2 != '\0' && c1 != '@' && c2 != '@');
218 rv = 1;
219 } else
220 rv = !asccasecmp(n1, n2);
221 jleave:
222 NYD_LEAVE;
223 return rv;
226 static struct name *
227 delname(struct name *np, char const *name)
229 struct name *p;
230 NYD_ENTER;
232 for (p = np; p != NULL; p = p->n_flink)
233 if (_same_name(p->n_name, name)) {
234 if (p->n_blink == NULL) {
235 if (p->n_flink != NULL)
236 p->n_flink->n_blink = NULL;
237 np = p->n_flink;
238 continue;
240 if (p->n_flink == NULL) {
241 if (p->n_blink != NULL)
242 p->n_blink->n_flink = NULL;
243 continue;
245 p->n_blink->n_flink = p->n_flink;
246 p->n_flink->n_blink = p->n_blink;
248 NYD_LEAVE;
249 return np;
252 static struct name *
253 put(struct name *list, struct name *node)
255 NYD_ENTER;
256 node->n_flink = list;
257 node->n_blink = NULL;
258 if (list != NULL)
259 list->n_blink = node;
260 NYD_LEAVE;
261 return node;
264 static char const *
265 yankname(char const *ap, char *wbuf, char const *separators, int keepcomms)
267 char const *cp;
268 char *wp, c, inquote, lc, lastsp;
269 NYD_ENTER;
271 *(wp = wbuf) = '\0';
273 /* Skip over intermediate list trash, as in ".org> , <xy@zz.org>" */
274 for (c = *ap; blankchar(c) || c == ','; c = *++ap)
276 if (c == '\0') {
277 cp = NULL;
278 goto jleave;
281 /* Parse a full name: TODO RFC 5322
282 * - Keep everything in quotes, liberal handle *quoted-pair*s therein
283 * - Skip entire (nested) comments
284 * - In non-quote, non-comment, join adjacent space to a single SP
285 * - Understand separators only in non-quote, non-comment context,
286 * and only if not part of a *quoted-pair* (XXX too liberal) */
287 cp = ap;
288 for (inquote = lc = lastsp = 0;; lc = c, ++cp) {
289 c = *cp;
290 if (c == '\0')
291 break;
292 if (c == '\\')
293 goto jwpwc;
294 if (c == '"') {
295 if (lc != '\\')
296 inquote = !inquote;
297 #if 0 /* TODO when doing real RFC 5322 parsers - why have i done this? */
298 else
299 --wp;
300 #endif
301 goto jwpwc;
303 if (inquote || lc == '\\') {
304 jwpwc:
305 *wp++ = c;
306 lastsp = 0;
307 continue;
309 if (c == '(') {
310 ap = cp;
311 cp = skip_comment(cp + 1);
312 if (keepcomms)
313 while (ap < cp)
314 *wp++ = *ap++;
315 --cp;
316 lastsp = 0;
317 continue;
319 if (strchr(separators, c) != NULL)
320 break;
322 lc = lastsp;
323 lastsp = blankchar(c);
324 if (!lastsp || !lc)
325 *wp++ = c;
327 if (blankchar(lc))
328 --wp;
330 *wp = '\0';
331 jleave:
332 NYD_LEAVE;
333 return cp;
336 static struct name *
337 _extract1(char const *line, enum gfield ntype, char const *separators,
338 bool_t keepcomms)
340 struct name *topp, *np, *t;
341 char const *cp;
342 char *nbuf;
343 NYD_ENTER;
345 topp = NULL;
346 if (line == NULL || *line == '\0')
347 goto jleave;
349 np = NULL;
350 cp = line;
351 nbuf = smalloc(strlen(line) +1);
352 while ((cp = yankname(cp, nbuf, separators, keepcomms)) != NULL) {
353 t = nalloc(nbuf, ntype);
354 if (topp == NULL)
355 topp = t;
356 else
357 np->n_flink = t;
358 t->n_blink = np;
359 np = t;
361 free(nbuf);
362 jleave:
363 NYD_LEAVE;
364 return topp;
367 static struct name *
368 _gexpand(size_t level, struct name *nlist, struct group *gp, bool_t metoo,
369 int ntype)
371 char const *logname;
372 struct grp_names_head *gnhp;
373 struct grp_names *gnp;
374 NYD_ENTER;
376 if (UICMP(z, level++, >, MAXEXP)) {
377 n_err(_("Expanding alias to depth larger than %d\n"), MAXEXP);
378 goto jleave;
381 GP_TO_SUBCLASS(gnhp, gp);
382 logname = ok_vlook(LOGNAME);
383 for (gnp = gnhp->gnh_head; gnp != NULL; gnp = gnp->gn_next) {
384 char *cp;
385 struct group *ngp;
387 /* FIXME we do not really support leading backslash quoting do we??? */
388 if (*(cp = gnp->gn_id) == '\\' || !strcmp(cp, gp->g_id))
389 goto jquote;
391 if ((ngp = _group_find(GT_ALIAS, cp)) != NULL) {
392 /* For S-nail(1), the "alias" may *be* the sender in that a name maps
393 * to a full address specification; aliases cannot be empty */
394 struct grp_names_head *ngnhp;
395 GP_TO_SUBCLASS(ngnhp, ngp);
397 assert(ngnhp->gnh_head != NULL);
398 if (metoo || ngnhp->gnh_head->gn_next != NULL ||
399 !_same_name(cp, logname))
400 nlist = _gexpand(level, nlist, ngp, metoo, ntype);
401 continue;
404 /* Here we should allow to expand to itself if only person in alias */
405 jquote:
406 if (metoo || gnhp->gnh_head->gn_next == NULL || !_same_name(cp, logname))
407 nlist = put(nlist, nalloc(cp, ntype | GFULL));
409 jleave:
410 NYD_LEAVE;
411 return nlist;
414 static struct group *
415 _group_lookup(enum group_type gt, struct group_lookup *glp, char const *id)
417 struct group *lgp, *gp;
418 NYD_ENTER;
420 gt &= GT_MASK;
421 lgp = NULL;
422 gp = *(glp->gl_htable = glp->gl_slot =
423 &(gt & GT_ALIAS ? _alias_heads :
424 (gt & GT_MLIST ? _mlist_heads :
425 (gt & GT_SHORTCUT ? _shortcut_heads :
426 (gt & GT_CHARSETALIAS ? _charsetalias_heads : NULL)))
427 )[n_torek_hash(id) % HSHSIZE]);
429 for (; gp != NULL; lgp = gp, gp = gp->g_next)
430 if ((gp->g_type & gt) && *gp->g_id == *id) {
431 if(!strcmp(gp->g_id, id))
432 break;
433 if(gt == GT_CHARSETALIAS){
434 if(!asccasecmp(gp->g_id, id))
435 break;
439 glp->gl_slot_last = lgp;
440 glp->gl_group = gp;
441 NYD_LEAVE;
442 return gp;
445 static struct group *
446 _group_find(enum group_type gt, char const *id)
448 struct group_lookup gl;
449 struct group *gp;
450 NYD_ENTER;
452 gp = _group_lookup(gt, &gl, id);
453 NYD_LEAVE;
454 return gp;
457 static struct group *
458 _group_go_first(enum group_type gt, struct group_lookup *glp)
460 struct group **gpa, *gp;
461 size_t i;
462 NYD_ENTER;
464 for (glp->gl_htable = gpa = (gt & GT_ALIAS ? _alias_heads :
465 (gt & GT_MLIST ? _mlist_heads :
466 (gt & GT_SHORTCUT ? _shortcut_heads :
467 (gt & GT_CHARSETALIAS ? _charsetalias_heads : NULL)))), i = 0;
468 i < HSHSIZE; ++gpa, ++i)
469 if ((gp = *gpa) != NULL) {
470 glp->gl_slot = gpa;
471 glp->gl_group = gp;
472 goto jleave;
475 glp->gl_group = gp = NULL;
476 jleave:
477 glp->gl_slot_last = NULL;
478 NYD_LEAVE;
479 return gp;
482 static struct group *
483 _group_go_next(struct group_lookup *glp)
485 struct group *gp, **gpa;
486 NYD_ENTER;
488 if ((gp = glp->gl_group->g_next) != NULL)
489 glp->gl_slot_last = glp->gl_group;
490 else {
491 glp->gl_slot_last = NULL;
492 for (gpa = glp->gl_htable + HSHSIZE; ++glp->gl_slot < gpa;)
493 if ((gp = *glp->gl_slot) != NULL)
494 break;
496 glp->gl_group = gp;
497 NYD_LEAVE;
498 return gp;
501 static struct group *
502 _group_fetch(enum group_type gt, char const *id, size_t addsz)
504 struct group_lookup gl;
505 struct group *gp;
506 size_t l, i;
507 NYD_ENTER;
509 if ((gp = _group_lookup(gt, &gl, id)) != NULL)
510 goto jleave;
512 l = strlen(id) +1;
513 i = n_ALIGN(n_VSTRUCT_SIZEOF(struct group, g_id) + l);
514 switch (gt & GT_MASK) {
515 case GT_ALIAS:
516 addsz = sizeof(struct grp_names_head);
517 break;
518 case GT_MLIST:
519 #ifdef HAVE_REGEX
520 if (n_is_maybe_regex(id)) {
521 addsz = sizeof(struct grp_regex);
522 gt |= GT_REGEX;
524 #endif
525 case GT_SHORTCUT:
526 case GT_CHARSETALIAS:
527 default:
528 break;
531 gp = smalloc(i + addsz);
532 gp->g_subclass_off = i;
533 gp->g_type = gt;
534 memcpy(gp->g_id, id, l);
536 if (gt & GT_ALIAS) {
537 struct grp_names_head *gnhp;
539 GP_TO_SUBCLASS(gnhp, gp);
540 gnhp->gnh_head = NULL;
542 #ifdef HAVE_REGEX
543 else if (/*(gt & GT_MLIST) &&*/ gt & GT_REGEX) {
544 int s;
545 struct grp_regex *grp;
546 GP_TO_SUBCLASS(grp, gp);
548 if((s = regcomp(&grp->gr_regex, id,
549 REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0){
550 n_err(_("Invalid regular expression: %s: %s\n"),
551 n_shexp_quote_cp(id, FAL0), n_regex_err_to_doc(&grp->gr_regex, s));
552 free(gp);
553 gp = NULL;
554 goto jleave;
556 grp->gr_mygroup = gp;
557 _mlmux_linkin(gp);
559 #endif
561 gp->g_next = *gl.gl_slot;
562 *gl.gl_slot = gp;
563 jleave:
564 NYD_LEAVE;
565 return gp;
568 static bool_t
569 _group_del(enum group_type gt, char const *id)
571 enum group_type xgt = gt & GT_MASK;
572 struct group_lookup gl;
573 struct group *gp;
574 NYD_ENTER;
576 /* Delete 'em all? */
577 if (id[0] == '*' && id[1] == '\0') {
578 for (gp = _group_go_first(gt, &gl); gp != NULL;)
579 gp = (gp->g_type & xgt) ? __group_del(&gl) : _group_go_next(&gl);
580 gp = (struct group*)TRU1;
581 } else if ((gp = _group_lookup(gt, &gl, id)) != NULL) {
582 if (gp->g_type & xgt)
583 __group_del(&gl);
584 else
585 gp = NULL;
587 NYD_LEAVE;
588 return (gp != NULL);
591 static struct group *
592 __group_del(struct group_lookup *glp)
594 struct group *x, *gp;
595 NYD_ENTER;
597 /* Overly complicated: link off this node, step ahead to next.. */
598 x = glp->gl_group;
599 if ((gp = glp->gl_slot_last) != NULL) {
600 gp = (gp->g_next = x->g_next);
601 } else {
602 glp->gl_slot_last = NULL;
603 gp = (*glp->gl_slot = x->g_next);
605 if (gp == NULL) {
606 struct group **gpa = glp->gl_htable + HSHSIZE;
608 while (++glp->gl_slot < gpa)
609 if ((gp = *glp->gl_slot) != NULL)
610 break;
613 glp->gl_group = gp;
615 if (x->g_type & GT_ALIAS)
616 __names_del(x);
617 #ifdef HAVE_REGEX
618 else if (/*(x->g_type & GT_MLIST) &&*/ x->g_type & GT_REGEX) {
619 struct grp_regex *grp;
620 GP_TO_SUBCLASS(grp, x);
622 regfree(&grp->gr_regex);
623 _mlmux_linkout(x);
625 #endif
627 free(x);
628 NYD_LEAVE;
629 return gp;
632 static void
633 __names_del(struct group *gp)
635 struct grp_names_head *gnhp;
636 struct grp_names *gnp;
637 NYD_ENTER;
639 GP_TO_SUBCLASS(gnhp, gp);
640 for (gnp = gnhp->gnh_head; gnp != NULL;) {
641 struct grp_names *x = gnp;
642 gnp = gnp->gn_next;
643 free(x);
645 NYD_LEAVE;
648 static void
649 _group_print_all(enum group_type gt)
651 enum group_type xgt;
652 struct group **gpa;
653 struct group const *gp;
654 ui32_t h, i;
655 char const **ida;
656 FILE *fp;
657 size_t lines;
658 NYD_ENTER;
660 xgt = gt & GT_PRINT_MASK;
661 gpa = (xgt & GT_ALIAS ? _alias_heads
662 : (xgt & GT_MLIST ? _mlist_heads
663 : (xgt & GT_SHORTCUT ? _shortcut_heads
664 : (xgt & GT_CHARSETALIAS ? _charsetalias_heads : NULL))));
666 for (h = 0, i = 1; h < HSHSIZE; ++h)
667 for (gp = gpa[h]; gp != NULL; gp = gp->g_next)
668 if ((gp->g_type & xgt) == xgt)
669 ++i;
670 ida = salloc(i * sizeof *ida);
672 for (i = h = 0; h < HSHSIZE; ++h)
673 for (gp = gpa[h]; gp != NULL; gp = gp->g_next)
674 if ((gp->g_type & xgt) == xgt)
675 ida[i++] = gp->g_id;
676 ida[i] = NULL;
678 if (i > 1)
679 qsort(ida, i, sizeof *ida, &__group_print_qsorter);
681 if ((fp = Ftmp(NULL, "prgroup", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL)
682 fp = n_stdout;
683 lines = 0;
685 for (i = 0; ida[i] != NULL; ++i)
686 lines += _group_print(_group_find(gt, ida[i]), fp);
687 #ifdef HAVE_REGEX
688 if (gt & GT_MLIST) {
689 if (gt & GT_SUBSCRIBE)
690 i = (ui32_t)_mlsub_size, h = (ui32_t)_mlsub_hits;
691 else
692 i = (ui32_t)_mlist_size, h = (ui32_t)_mlist_hits;
693 if (i > 0 && (n_poption & n_PO_D_V)){
694 fprintf(fp, _("# %s list regex(7) total: %u entries, %u hits\n"),
695 (gt & GT_SUBSCRIBE ? _("Subscribed") : _("Non-subscribed")),
696 i, h);
697 ++lines;
700 #endif
702 if (fp != n_stdout) {
703 page_or_print(fp, lines);
704 Fclose(fp);
706 NYD_LEAVE;
709 static int
710 __group_print_qsorter(void const *a, void const *b)
712 int rv;
713 NYD_ENTER;
715 rv = strcmp(*(char**)n_UNCONST(a), *(char**)n_UNCONST(b));
716 NYD_LEAVE;
717 return rv;
720 static size_t
721 _group_print(struct group const *gp, FILE *fo)
723 char const *cp;
724 size_t rv;
725 NYD_ENTER;
727 rv = 1;
729 if (gp->g_type & GT_ALIAS) {
730 struct grp_names_head *gnhp;
731 struct grp_names *gnp;
733 fprintf(fo, "alias %s ", gp->g_id);
735 GP_TO_SUBCLASS(gnhp, gp);
736 if ((gnp = gnhp->gnh_head) != NULL) { /* xxx always 1+ entries */
737 do {
738 struct grp_names *x = gnp;
739 gnp = gnp->gn_next;
740 fprintf(fo, " \"%s\"", string_quote(x->gn_id)); /* TODO shexp */
741 } while (gnp != NULL);
743 putc('\n', fo);
744 } else if (gp->g_type & GT_MLIST) {
745 #ifdef HAVE_REGEX
746 if ((gp->g_type & GT_REGEX) && (n_poption & n_PO_D_V)){
747 size_t i;
748 struct grp_regex *grp,
749 *lp = (gp->g_type & GT_SUBSCRIBE ? _mlsub_regex : _mlist_regex);
751 GP_TO_SUBCLASS(grp, gp);
752 for (i = 1; lp != grp; lp = lp->gr_next)
753 ++i;
754 fprintf(fo, "# regex(7): hits %" PRIuZ ", sort %" PRIuZ ".\n ",
755 grp->gr_hits, i);
756 ++rv;
758 #endif
760 fprintf(fo, "wysh %s %s\n",
761 (gp->g_type & GT_SUBSCRIBE ? "mlsubscribe" : "mlist"),
762 n_shexp_quote_cp(gp->g_id, TRU1));
763 } else if (gp->g_type & GT_SHORTCUT) {
764 GP_TO_SUBCLASS(cp, gp);
765 fprintf(fo, "wysh shortcut %s %s\n",
766 gp->g_id, n_shexp_quote_cp(cp, TRU1));
767 } else if (gp->g_type & GT_CHARSETALIAS) {
768 GP_TO_SUBCLASS(cp, gp);
769 fprintf(fo, "charsetalias %s %s\n",
770 n_shexp_quote_cp(gp->g_id, TRU1), n_shexp_quote_cp(cp, TRU1));
773 NYD_LEAVE;
774 return rv;
777 static int
778 _mlmux(enum group_type gt, char **argv)
780 struct group *gp;
781 int rv = 0;
782 NYD_ENTER;
784 if (*argv == NULL)
785 _group_print_all(gt);
786 else do {
787 if ((gp = _group_find(gt, *argv)) != NULL) {
788 if (gt & GT_SUBSCRIBE) {
789 if (!(gp->g_type & GT_SUBSCRIBE)) {
790 _MLMUX_LINKOUT(gp);
791 gp->g_type |= GT_SUBSCRIBE;
792 _MLMUX_LINKIN(gp);
793 } else {
794 n_err(_("Mailing-list already `mlsubscribe'd: %s\n"),
795 *argv);
796 rv = 1;
798 } else {
799 n_err(_("Mailing-list already `mlist'ed: %s\n"), *argv);
800 rv = 1;
802 } else
803 _group_fetch(gt, *argv, 0);
804 } while (*++argv != NULL);
806 NYD_LEAVE;
807 return rv;
810 static int
811 _unmlmux(enum group_type gt, char **argv)
813 struct group *gp;
814 int rv = 0;
815 NYD_ENTER;
817 for (; *argv != NULL; ++argv) {
818 if (gt & GT_SUBSCRIBE) {
819 struct group_lookup gl;
820 bool_t isaster;
822 if (!(isaster = (**argv == '*')))
823 gp = _group_find(gt, *argv);
824 else if ((gp = _group_go_first(gt, &gl)) == NULL)
825 continue;
826 else if (gp != NULL && !(gp->g_type & GT_SUBSCRIBE))
827 goto jaster_entry;
829 if (gp != NULL) {
830 jaster_redo:
831 if (gp->g_type & GT_SUBSCRIBE) {
832 _MLMUX_LINKOUT(gp);
833 gp->g_type &= ~GT_SUBSCRIBE;
834 _MLMUX_LINKIN(gp);
835 if (isaster) {
836 jaster_entry:
837 while ((gp = _group_go_next(&gl)) != NULL &&
838 !(gp->g_type & GT_SUBSCRIBE))
840 if (gp != NULL)
841 goto jaster_redo;
843 } else {
844 n_err(_("Mailing-list not `mlsubscribe'd: %s\n"),
845 n_shexp_quote_cp(*argv, FAL0));
846 rv = 1;
848 continue;
850 } else if (_group_del(gt, *argv))
851 continue;
852 n_err(_("No such mailing-list: %s\n"), n_shexp_quote_cp(*argv, FAL0));
853 rv = 1;
855 NYD_LEAVE;
856 return rv;
859 #ifdef HAVE_REGEX
860 static void
861 _mlmux_linkin(struct group *gp)
863 struct grp_regex **lpp, *grp, *lhp;
864 NYD_ENTER;
866 if (gp->g_type & GT_SUBSCRIBE) {
867 lpp = &_mlsub_regex;
868 ++_mlsub_size;
869 } else {
870 lpp = &_mlist_regex;
871 ++_mlist_size;
874 GP_TO_SUBCLASS(grp, gp);
875 if ((lhp = *lpp) != NULL) {
876 (grp->gr_last = lhp->gr_last)->gr_next = grp;
877 (grp->gr_next = lhp)->gr_last = grp;
878 } else
879 *lpp = grp->gr_last = grp->gr_next = grp;
880 grp->gr_hits = 0;
881 NYD_LEAVE;
884 static void
885 _mlmux_linkout(struct group *gp)
887 struct grp_regex *grp, **lpp;
888 NYD_ENTER;
890 GP_TO_SUBCLASS(grp, gp);
892 if (gp->g_type & GT_SUBSCRIBE) {
893 lpp = &_mlsub_regex;
894 --_mlsub_size;
895 _mlsub_hits -= grp->gr_hits;
896 } else {
897 lpp = &_mlist_regex;
898 --_mlist_size;
899 _mlist_hits -= grp->gr_hits;
902 if (grp->gr_next == grp)
903 *lpp = NULL;
904 else {
905 (grp->gr_last->gr_next = grp->gr_next)->gr_last = grp->gr_last;
906 if (*lpp == grp)
907 *lpp = grp->gr_next;
909 NYD_LEAVE;
911 #endif /* HAVE_REGEX */
913 FL struct name *
914 nalloc(char const *str, enum gfield ntype)
916 struct n_addrguts ag;
917 struct str in, out;
918 struct name *np;
919 NYD_ENTER;
920 assert(!(ntype & GFULLEXTRA) || (ntype & GFULL) != 0);
922 str = n_addrspec_with_guts(&ag, str,
923 ((ntype & (GFULL | GSKIN | GREF)) != 0), FAL0);
924 if(str == NULL){
926 np = NULL; TODO We cannot return NULL,
927 goto jleave; TODO thus handle failures in here!
929 str = ag.ag_input;
932 if (!(ag.ag_n_flags & NAME_NAME_SALLOC)) {
933 ag.ag_n_flags |= NAME_NAME_SALLOC;
934 np = salloc(sizeof(*np) + ag.ag_slen +1);
935 memcpy(np + 1, ag.ag_skinned, ag.ag_slen +1);
936 ag.ag_skinned = (char*)(np + 1);
937 } else
938 np = salloc(sizeof *np);
940 np->n_flink = NULL;
941 np->n_blink = NULL;
942 np->n_type = ntype;
943 np->n_flags = 0;
945 np->n_fullname = np->n_name = ag.ag_skinned;
946 np->n_fullextra = NULL;
947 np->n_flags = ag.ag_n_flags;
949 if (ntype & GFULL) {
950 if (ag.ag_ilen == ag.ag_slen
951 #ifdef HAVE_IDNA
952 && !(ag.ag_n_flags & NAME_IDNA)
953 #endif
955 goto jleave;
956 if (ag.ag_n_flags & NAME_ADDRSPEC_ISFILEORPIPE)
957 goto jleave;
959 /* n_fullextra is only the complete name part without address.
960 * Beware of "-r '<abc@def>'", don't treat that as FULLEXTRA */
961 if ((ntype & GFULLEXTRA) && ag.ag_ilen > ag.ag_slen + 2) {
962 size_t s = ag.ag_iaddr_start, e = ag.ag_iaddr_aend, i;
963 char const *cp;
965 if (s == 0 || str[--s] != '<' || str[e++] != '>')
966 goto jskipfullextra;
967 i = ag.ag_ilen - e;
968 in.s = n_lofi_alloc(s + 1 + i +1);
969 while(s > 0 && blankchar(str[s - 1]))
970 --s;
971 memcpy(in.s, str, s);
972 if (i > 0) {
973 in.s[s++] = ' ';
974 while (blankchar(str[e])) {
975 ++e;
976 if (--i == 0)
977 break;
979 if (i > 0)
980 memcpy(&in.s[s], &str[e], i);
982 s += i;
983 in.s[in.l = s] = '\0';
984 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
986 for (cp = out.s, i = out.l; i > 0 && spacechar(*cp); --i, ++cp)
988 while (i > 0 && spacechar(cp[i - 1]))
989 --i;
990 np->n_fullextra = savestrbuf(cp, i);
992 n_lofi_free(in.s);
993 free(out.s);
995 jskipfullextra:
997 /* n_fullname depends on IDNA conversion */
998 #ifdef HAVE_IDNA
999 if (!(ag.ag_n_flags & NAME_IDNA)) {
1000 #endif
1001 in.s = n_UNCONST(str);
1002 in.l = ag.ag_ilen;
1003 #ifdef HAVE_IDNA
1004 } else {
1005 /* The domain name was IDNA and has been converted. We also have to
1006 * ensure that the domain name in .n_fullname is replaced with the
1007 * converted version, since MIME doesn't perform encoding of addrs */
1008 /* TODO This definetely doesn't belong here! */
1009 size_t l = ag.ag_iaddr_start,
1010 lsuff = ag.ag_ilen - ag.ag_iaddr_aend;
1011 in.s = ac_alloc(l + ag.ag_slen + lsuff +1);
1012 memcpy(in.s, str, l);
1013 memcpy(in.s + l, ag.ag_skinned, ag.ag_slen);
1014 l += ag.ag_slen;
1015 memcpy(in.s + l, str + ag.ag_iaddr_aend, lsuff);
1016 l += lsuff;
1017 in.s[l] = '\0';
1018 in.l = l;
1020 #endif
1021 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
1022 np->n_fullname = savestr(out.s);
1023 free(out.s);
1024 #ifdef HAVE_IDNA
1025 if (ag.ag_n_flags & NAME_IDNA)
1026 ac_free(in.s);
1027 #endif
1028 np->n_flags |= NAME_FULLNAME_SALLOC;
1030 jleave:
1031 NYD_LEAVE;
1032 return np;
1035 FL struct name *
1036 ndup(struct name *np, enum gfield ntype)
1038 struct name *nnp;
1039 NYD_ENTER;
1041 if ((ntype & (GFULL | GSKIN)) && !(np->n_flags & NAME_SKINNED)) {
1042 nnp = nalloc(np->n_name, ntype);
1043 goto jleave;
1046 nnp = salloc(sizeof *np);
1047 nnp->n_flink = nnp->n_blink = NULL;
1048 nnp->n_type = ntype;
1049 nnp->n_flags = (np->n_flags & ~(NAME_NAME_SALLOC | NAME_FULLNAME_SALLOC)) |
1050 NAME_NAME_SALLOC;
1051 nnp->n_name = savestr(np->n_name);
1052 if (np->n_name == np->n_fullname || !(ntype & (GFULL | GSKIN))) {
1053 nnp->n_fullname = nnp->n_name;
1054 nnp->n_fullextra = NULL;
1055 } else {
1056 nnp->n_flags |= NAME_FULLNAME_SALLOC;
1057 nnp->n_fullname = savestr(np->n_fullname);
1058 nnp->n_fullextra = (np->n_fullextra == NULL) ? NULL
1059 : savestr(np->n_fullextra);
1061 jleave:
1062 NYD_LEAVE;
1063 return nnp;
1066 FL struct name *
1067 cat(struct name *n1, struct name *n2)
1069 struct name *tail;
1070 NYD_ENTER;
1072 tail = n2;
1073 if (n1 == NULL)
1074 goto jleave;
1075 tail = n1;
1076 if (n2 == NULL)
1077 goto jleave;
1079 while (tail->n_flink != NULL)
1080 tail = tail->n_flink;
1081 tail->n_flink = n2;
1082 n2->n_blink = tail;
1083 tail = n1;
1084 jleave:
1085 NYD_LEAVE;
1086 return tail;
1089 FL struct name *
1090 namelist_dup(struct name const *np, enum gfield ntype)
1092 struct name *nnp;
1093 NYD_ENTER;
1095 for (nnp = NULL; np != NULL; np = np->n_flink) {
1096 struct name *x = ndup(n_UNCONST(np), (np->n_type & ~GMASK) | ntype);
1097 x->n_flink = nnp;
1098 nnp = x;
1100 NYD_LEAVE;
1101 return nnp;
1104 FL ui32_t
1105 count(struct name const *np)
1107 ui32_t c;
1108 NYD_ENTER;
1110 for (c = 0; np != NULL; np = np->n_flink)
1111 if (!(np->n_type & GDEL))
1112 ++c;
1113 NYD_LEAVE;
1114 return c;
1117 FL ui32_t
1118 count_nonlocal(struct name const *np)
1120 ui32_t c;
1121 NYD_ENTER;
1123 for (c = 0; np != NULL; np = np->n_flink)
1124 if (!(np->n_type & GDEL) && !(np->n_flags & NAME_ADDRSPEC_ISFILEORPIPE))
1125 ++c;
1126 NYD_LEAVE;
1127 return c;
1130 FL struct name *
1131 extract(char const *line, enum gfield ntype)
1133 struct name *rv;
1134 NYD_ENTER;
1136 rv = _extract1(line, ntype, " \t,", 0);
1137 NYD_LEAVE;
1138 return rv;
1141 FL struct name *
1142 lextract(char const *line, enum gfield ntype)
1144 struct name *rv;
1145 NYD_ENTER;
1147 rv = ((line != NULL && strpbrk(line, ",\"\\(<|"))
1148 ? _extract1(line, ntype, ",", 1) : extract(line, ntype));
1149 NYD_LEAVE;
1150 return rv;
1153 FL char *
1154 detract(struct name *np, enum gfield ntype)
1156 char *topp, *cp;
1157 struct name *p;
1158 int flags, s;
1159 NYD_ENTER;
1161 topp = NULL;
1162 if (np == NULL)
1163 goto jleave;
1165 flags = ntype & (GCOMMA | GNAMEONLY);
1166 ntype &= ~(GCOMMA | GNAMEONLY);
1167 s = 0;
1169 for (p = np; p != NULL; p = p->n_flink) {
1170 if (ntype && (p->n_type & GMASK) != ntype)
1171 continue;
1172 s += strlen(flags & GNAMEONLY ? p->n_name : p->n_fullname) +1;
1173 if (flags & GCOMMA)
1174 ++s;
1176 if (s == 0)
1177 goto jleave;
1179 s += 2;
1180 topp = salloc(s);
1181 cp = topp;
1182 for (p = np; p != NULL; p = p->n_flink) {
1183 if (ntype && (p->n_type & GMASK) != ntype)
1184 continue;
1185 cp = sstpcpy(cp, (flags & GNAMEONLY ? p->n_name : p->n_fullname));
1186 if ((flags & GCOMMA) && p->n_flink != NULL)
1187 *cp++ = ',';
1188 *cp++ = ' ';
1190 *--cp = 0;
1191 if ((flags & GCOMMA) && *--cp == ',')
1192 *cp = 0;
1193 jleave:
1194 NYD_LEAVE;
1195 return topp;
1198 FL struct name *
1199 grab_names(enum n_go_input_flags gif, char const *field, struct name *np,
1200 int comma, enum gfield gflags)
1202 struct name *nq;
1203 NYD_ENTER;
1205 jloop:
1206 np = lextract(n_go_input_cp(gif, field, detract(np, comma)), gflags);
1207 for (nq = np; nq != NULL; nq = nq->n_flink)
1208 if (is_addr_invalid(nq, EACM_NONE))
1209 goto jloop;
1210 NYD_LEAVE;
1211 return np;
1214 FL bool_t
1215 name_is_same_domain(struct name const *n1, struct name const *n2)
1217 char const *d1, *d2;
1218 bool_t rv;
1219 NYD_ENTER;
1221 d1 = strrchr(n1->n_name, '@');
1222 d2 = strrchr(n2->n_name, '@');
1224 rv = (d1 != NULL && d2 != NULL) ? !asccasecmp(++d1, ++d2) : FAL0;
1226 NYD_LEAVE;
1227 return rv;
1230 FL struct name *
1231 checkaddrs(struct name *np, enum expand_addr_check_mode eacm,
1232 si8_t *set_on_error)
1234 struct name *n;
1235 NYD_ENTER;
1237 for (n = np; n != NULL; n = n->n_flink) {
1238 si8_t rv;
1240 if ((rv = is_addr_invalid(n, eacm)) != 0) {
1241 if (set_on_error != NULL)
1242 *set_on_error |= rv; /* don't loose -1! */
1243 else if (eacm & EAF_MAYKEEP) /* TODO HACK! See definition! */
1244 continue;
1245 if (n->n_blink)
1246 n->n_blink->n_flink = n->n_flink;
1247 if (n->n_flink)
1248 n->n_flink->n_blink = n->n_blink;
1249 if (n == np)
1250 np = n->n_flink;
1253 NYD_LEAVE;
1254 return np;
1257 FL struct name *
1258 namelist_vaporise_head(struct header *hp, enum expand_addr_check_mode eacm,
1259 bool_t metoo, si8_t *set_on_error)
1261 struct name *tolist, *np, **npp;
1262 NYD_ENTER;
1264 tolist = usermap(cat(hp->h_to, cat(hp->h_cc, hp->h_bcc)), metoo);
1265 hp->h_to = hp->h_cc = hp->h_bcc = NULL;
1267 tolist = elide(checkaddrs(tolist, eacm, set_on_error));
1269 for (np = tolist; np != NULL; np = np->n_flink) {
1270 switch (np->n_type & (GDEL | GMASK)) {
1271 case GTO: npp = &hp->h_to; break;
1272 case GCC: npp = &hp->h_cc; break;
1273 case GBCC: npp = &hp->h_bcc; break;
1274 default: continue;
1276 *npp = cat(*npp, ndup(np, np->n_type | GFULL));
1278 NYD_LEAVE;
1279 return tolist;
1282 FL struct name *
1283 usermap(struct name *names, bool_t force_metoo)
1285 struct name *new, *np, *cp;
1286 struct group *gp;
1287 int metoo;
1288 NYD_ENTER;
1290 new = NULL;
1291 np = names;
1292 metoo = (force_metoo || ok_blook(metoo));
1293 while (np != NULL) {
1294 assert(!(np->n_type & GDEL)); /* TODO legacy */
1295 if (is_fileorpipe_addr(np) || np->n_name[0] == '\\') {
1296 cp = np->n_flink;
1297 new = put(new, np);
1298 np = cp;
1299 continue;
1301 gp = _group_find(GT_ALIAS, np->n_name);
1302 cp = np->n_flink;
1303 if (gp != NULL)
1304 new = _gexpand(0, new, gp, metoo, np->n_type);
1305 else
1306 new = put(new, np);
1307 np = cp;
1309 NYD_LEAVE;
1310 return new;
1313 FL struct name *
1314 elide(struct name *names)
1316 struct name *np, *t, *newn, *x;
1317 NYD_ENTER;
1319 newn = NULL;
1320 if (names == NULL)
1321 goto jleave;
1323 /* Throw away all deleted nodes (XXX merge with plain sort below?) */
1324 for (np = NULL; names != NULL; names = names->n_flink)
1325 if (!(names->n_type & GDEL)) {
1326 names->n_blink = np;
1327 if (np)
1328 np->n_flink = names;
1329 else
1330 newn = names;
1331 np = names;
1333 if (newn == NULL)
1334 goto jleave;
1336 np = newn->n_flink;
1337 if (np != NULL)
1338 np->n_blink = NULL;
1339 newn->n_flink = NULL;
1341 while (np != NULL) {
1342 int cmpres;
1344 t = newn;
1345 while ((cmpres = asccasecmp(t->n_name, np->n_name)) < 0) {
1346 if (t->n_flink == NULL)
1347 break;
1348 t = t->n_flink;
1351 /* If we ran out of t's, put new entry after the current value of t */
1352 if (cmpres < 0) {
1353 t->n_flink = np;
1354 np->n_blink = t;
1355 t = np;
1356 np = np->n_flink;
1357 t->n_flink = NULL;
1358 continue;
1361 /* Otherwise, put the new entry in front of the current t. If at the
1362 * front of the list, the new guy becomes the new head of the list */
1363 if (t == newn) {
1364 t = np;
1365 np = np->n_flink;
1366 t->n_flink = newn;
1367 newn->n_blink = t;
1368 t->n_blink = NULL;
1369 newn = t;
1370 continue;
1373 /* The normal case -- we are inserting into the middle of the list */
1374 x = np;
1375 np = np->n_flink;
1376 x->n_flink = t;
1377 x->n_blink = t->n_blink;
1378 t->n_blink->n_flink = x;
1379 t->n_blink = x;
1382 /* Now the list headed up by new is sorted. Remove duplicates */
1383 np = newn;
1384 while (np != NULL) {
1385 t = np;
1386 while (t->n_flink != NULL && !asccasecmp(np->n_name, t->n_flink->n_name))
1387 t = t->n_flink;
1388 if (t == np) {
1389 np = np->n_flink;
1390 continue;
1393 /* Now t points to the last entry with the same name as np.
1394 * Make np point beyond t */
1395 np->n_flink = t->n_flink;
1396 if (t->n_flink != NULL)
1397 t->n_flink->n_blink = np;
1398 np = np->n_flink;
1400 jleave:
1401 NYD_LEAVE;
1402 return newn;
1405 FL int
1406 c_alternates(void *v){
1407 char **namelist, **ap, **ap2, *cp;
1408 size_t l, sl;
1409 int rv;
1410 NYD_ENTER;
1412 rv = 0;
1414 l = 0;
1415 for(namelist = v; namelist[l] != NULL; ++l)
1418 if(l == 0){
1419 if(a_nag_altnames != NULL){
1420 fprintf(n_stdout, "alternates ");
1421 for(ap = a_nag_altnames; *ap != NULL; ++ap)
1422 fprintf(n_stdout, "%s ", *ap);
1423 putc('\n', n_stdout);
1425 }else{
1426 if(a_nag_altnames != NULL){
1427 for(ap = a_nag_altnames; *ap != NULL; ++ap)
1428 free(*ap);
1429 free(a_nag_altnames);
1432 ++l;
1433 ap2 =
1434 a_nag_altnames = smalloc(l * sizeof(*a_nag_altnames));
1436 sl = 0;
1437 for(ap = namelist; *ap != NULL; ++ap)
1438 if(*ap != '\0'){
1439 struct name *np;
1441 if((np = lextract(*ap, GSKIN)) == NULL || np->n_flink != NULL ||
1442 (np = checkaddrs(np, EACM_STRICT, NULL)) == NULL){
1443 n_err(_("Invalid `alternates' argument: %s\n"),
1444 n_shexp_quote_cp(*ap, FAL0));
1445 rv = 1;
1446 continue;
1448 l = strlen(np->n_name) +1;
1449 sl += l;
1450 cp = smalloc(l);
1451 memcpy(cp, np->n_name, l);
1452 *ap2++ = cp;
1454 *ap2 = NULL;
1456 /* And put it into *alternates* */
1457 if(sl > 0){
1458 cp = salloc(sl);
1459 for(sl = 0, ap = a_nag_altnames; *ap != NULL; ++ap)
1460 if((l = strlen(*ap)) > 0){
1461 memcpy(&cp[sl], *ap, l);
1462 cp[sl += l] = ' ';
1463 ++sl;
1465 if(sl > 0)
1466 --sl;
1467 cp[sl] = '\0';
1468 }else{
1469 free(a_nag_altnames);
1470 a_nag_altnames = NULL;
1473 n_PS_ROOT_BLOCK(sl > 0 ? ok_vset(alternates, cp) : ok_vclear(alternates));
1475 NYD_LEAVE;
1476 return rv;
1479 FL struct name *
1480 delete_alternates(struct name *np)
1482 struct name *xp;
1483 char **ap;
1484 NYD_ENTER;
1486 np = delname(np, ok_vlook(LOGNAME));
1487 if (a_nag_altnames != NULL)
1488 for (ap = a_nag_altnames; *ap != '\0'; ++ap)
1489 np = delname(np, *ap);
1491 if ((xp = lextract(ok_vlook(from), GEXTRA | GSKIN)) != NULL)
1492 while (xp != NULL) {
1493 np = delname(np, xp->n_name);
1494 xp = xp->n_flink;
1497 if ((xp = lextract(ok_vlook(replyto), GEXTRA | GSKIN)) != NULL)
1498 while (xp != NULL) {
1499 np = delname(np, xp->n_name);
1500 xp = xp->n_flink;
1503 if ((xp = extract(ok_vlook(sender), GEXTRA | GSKIN)) != NULL)
1504 while (xp != NULL) {
1505 np = delname(np, xp->n_name);
1506 xp = xp->n_flink;
1508 NYD_LEAVE;
1509 return np;
1512 FL int
1513 is_myname(char const *name)
1515 int rv = 1;
1516 struct name *xp;
1517 char **ap;
1518 NYD_ENTER;
1520 if (_same_name(ok_vlook(LOGNAME), name))
1521 goto jleave;
1522 if (a_nag_altnames != NULL)
1523 for (ap = a_nag_altnames; *ap != NULL; ++ap)
1524 if (_same_name(*ap, name))
1525 goto jleave;
1527 if ((xp = lextract(ok_vlook(from), GEXTRA | GSKIN)) != NULL)
1528 while (xp != NULL) {
1529 if (_same_name(xp->n_name, name))
1530 goto jleave;
1531 xp = xp->n_flink;
1534 if ((xp = lextract(ok_vlook(replyto), GEXTRA | GSKIN)) != NULL)
1535 while (xp != NULL) {
1536 if (_same_name(xp->n_name, name))
1537 goto jleave;
1538 xp = xp->n_flink;
1541 if ((xp = extract(ok_vlook(sender), GEXTRA | GSKIN)) != NULL)
1542 while (xp != NULL) {
1543 if (_same_name(xp->n_name, name))
1544 goto jleave;
1545 xp = xp->n_flink;
1547 rv = 0;
1548 jleave:
1549 NYD_LEAVE;
1550 return rv;
1553 FL int
1554 c_addrcodec(void *vp){
1555 struct n_addrguts ag;
1556 struct n_string s_b, *sp;
1557 size_t alen;
1558 int mode;
1559 char const **argv, *varname, *act, *cp;
1560 NYD_ENTER;
1562 sp = n_string_creat_auto(&s_b);
1563 argv = vp;
1564 varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
1566 act = *argv;
1567 for(cp = act; *cp != '\0' && !blankspacechar(*cp); ++cp)
1569 mode = 0;
1570 if(*act == '+')
1571 mode = 1, ++act;
1572 if(*act == '+')
1573 mode = 2, ++act;
1574 if(*act == '+')
1575 mode = 3, ++act;
1576 if(act >= cp)
1577 goto jesynopsis;
1578 alen = PTR2SIZE(cp - act);
1579 if(*cp != '\0')
1580 ++cp;
1582 /* C99 */{
1583 size_t i;
1585 i = strlen(cp);
1586 if(i <= UIZ_MAX / 4)
1587 i <<= 1;
1588 sp = n_string_reserve(sp, i);
1591 n_pstate_err_no = n_ERR_NONE;
1593 if(is_ascncaseprefix(act, "encode", alen)){
1594 /* This function cannot be a simple nalloc() wrapper even later on, since
1595 * we may need to turn any ", () or \ into quoted-pairs */
1596 char c;
1598 while((c = *cp++) != '\0'){
1599 if(((c == '(' || c == ')') && mode < 1) || (c == '"' && mode < 2) ||
1600 (c == '\\' && mode < 3))
1601 sp = n_string_push_c(sp, '\\');
1602 sp = n_string_push_c(sp, c);
1605 if(n_addrspec_with_guts(&ag, n_string_cp(sp), TRU1, TRU1) == NULL ||
1606 (ag.ag_n_flags & (NAME_ADDRSPEC_ISADDR | NAME_ADDRSPEC_INVALID)
1607 ) != NAME_ADDRSPEC_ISADDR){
1608 cp = sp->s_dat;
1609 n_pstate_err_no = n_ERR_INVAL;
1610 vp = NULL;
1611 }else{
1612 struct name *np;
1614 np = nalloc(ag.ag_input, GTO | GFULL | GSKIN);
1615 cp = np->n_fullname;
1617 }else if(mode == 0){
1618 if(is_ascncaseprefix(act, "decode", alen)){
1619 char c;
1621 while((c = *cp++) != '\0'){
1622 switch(c){
1623 case '(':
1624 sp = n_string_push_c(sp, '(');
1625 act = skip_comment(cp);
1626 if(--act > cp)
1627 sp = n_string_push_buf(sp, cp, PTR2SIZE(act - cp));
1628 sp = n_string_push_c(sp, ')');
1629 cp = ++act;
1630 break;
1631 case '"':
1632 while(*cp != '\0'){
1633 if((c = *cp++) == '"')
1634 break;
1635 if(c == '\\' && (c = *cp) != '\0')
1636 ++cp;
1637 sp = n_string_push_c(sp, c);
1639 break;
1640 default:
1641 if(c == '\\' && (c = *cp++) == '\0')
1642 break;
1643 sp = n_string_push_c(sp, c);
1644 break;
1647 cp = n_string_cp(sp);
1648 }else if(is_ascncaseprefix(act, "skin", alen)){
1649 /* Let's just use the is-single-address hack for this one, too.. */
1650 if(n_addrspec_with_guts(&ag, cp, TRU1, TRU1) == NULL ||
1651 (ag.ag_n_flags & (NAME_ADDRSPEC_ISADDR | NAME_ADDRSPEC_INVALID)
1652 ) != NAME_ADDRSPEC_ISADDR){
1653 n_pstate_err_no = n_ERR_INVAL;
1654 vp = NULL;
1655 }else{
1656 struct name *np;
1658 np = nalloc(ag.ag_input, GTO | GFULL | GSKIN);
1659 cp = np->n_name;
1661 }else
1662 goto jesynopsis;
1663 }else
1664 goto jesynopsis;
1666 if(varname == NULL){
1667 if(fprintf(n_stdout, "%s\n", cp) < 0){
1668 n_pstate_err_no = n_err_no;
1669 vp = NULL;
1671 }else if(!n_var_vset(varname, (uintptr_t)cp)){
1672 n_pstate_err_no = n_ERR_NOTSUP;
1673 vp = NULL;
1676 jleave:
1677 NYD_LEAVE;
1678 return (vp != NULL ? 0 : 1);
1679 jesynopsis:
1680 n_err(_("Synopsis: addrcodec: <[+[+[+]]]e[ncode]|d[ecode]|s[kin]> "
1681 "<rest-of-line>\n"));
1682 n_pstate_err_no = n_ERR_INVAL;
1683 vp = NULL;
1684 goto jleave;
1687 FL bool_t
1688 n_alias_is_valid_name(char const *name){
1689 char c;
1690 char const *cp;
1691 bool_t rv;
1692 NYD2_ENTER;
1694 for(rv = TRU1, cp = name++; (c = *cp++) != '\0';)
1695 /* User names, plus things explicitly mentioned in Postfix aliases(5).
1696 * As an extension, allow period: [[:alnum:]_#:@.-]+$? */
1697 if(!alnumchar(c) && c != '_' && c != '-' &&
1698 c != '#' && c != ':' && c != '@' &&
1699 c != '.'){
1700 if(c == '$' && cp != name && *cp == '\0')
1701 break;
1702 rv = FAL0;
1703 break;
1705 NYD2_LEAVE;
1706 return rv;
1709 FL int
1710 c_alias(void *v)
1712 char **argv = v;
1713 struct group *gp;
1714 int rv = 0;
1715 NYD_ENTER;
1717 if (*argv == NULL)
1718 _group_print_all(GT_ALIAS);
1719 else if (!n_alias_is_valid_name(*argv)) {
1720 n_err(_("Not a valid alias name: %s\n"), *argv);
1721 rv = 1;
1722 } else if (argv[1] == NULL) {
1723 if ((gp = _group_find(GT_ALIAS, *argv)) != NULL)
1724 _group_print(gp, n_stdout);
1725 else {
1726 n_err(_("No such alias: %s\n"), *argv);
1727 rv = 1;
1729 } else {
1730 struct grp_names_head *gnhp;
1732 gp = _group_fetch(GT_ALIAS, *argv, 0);
1733 GP_TO_SUBCLASS(gnhp, gp);
1735 for (++argv; *argv != NULL; ++argv) {
1736 size_t l = strlen(*argv) +1;
1737 struct grp_names *gnp = smalloc(n_VSTRUCT_SIZEOF(struct grp_names,
1738 gn_id) + l);
1739 gnp->gn_next = gnhp->gnh_head;
1740 gnhp->gnh_head = gnp;
1741 memcpy(gnp->gn_id, *argv, l);
1743 assert(gnhp->gnh_head != NULL);
1745 NYD_LEAVE;
1746 return rv;
1749 FL int
1750 c_unalias(void *v)
1752 char **argv = v;
1753 int rv = 0;
1754 NYD_ENTER;
1756 do if (!_group_del(GT_ALIAS, *argv)) {
1757 n_err(_("No such alias: %s\n"), *argv);
1758 rv = 1;
1759 } while (*++argv != NULL);
1760 NYD_LEAVE;
1761 return rv;
1764 FL int
1765 c_mlist(void *v)
1767 int rv;
1768 NYD_ENTER;
1770 rv = _mlmux(GT_MLIST, v);
1771 NYD_LEAVE;
1772 return rv;
1775 FL int
1776 c_unmlist(void *v)
1778 int rv;
1779 NYD_ENTER;
1781 rv = _unmlmux(GT_MLIST, v);
1782 NYD_LEAVE;
1783 return rv;
1786 FL int
1787 c_mlsubscribe(void *v)
1789 int rv;
1790 NYD_ENTER;
1792 rv = _mlmux(GT_MLIST | GT_SUBSCRIBE, v);
1793 NYD_LEAVE;
1794 return rv;
1797 FL int
1798 c_unmlsubscribe(void *v)
1800 int rv;
1801 NYD_ENTER;
1803 rv = _unmlmux(GT_MLIST | GT_SUBSCRIBE, v);
1804 NYD_LEAVE;
1805 return rv;
1808 FL enum mlist_state
1809 is_mlist(char const *name, bool_t subscribed_only)
1811 struct group *gp;
1812 #ifdef HAVE_REGEX
1813 struct grp_regex **lpp, *grp;
1814 bool_t re2;
1815 #endif
1816 enum mlist_state rv;
1817 NYD_ENTER;
1819 gp = _group_find(GT_MLIST, name);
1820 rv = (gp != NULL) ? MLIST_KNOWN : MLIST_OTHER;
1821 if (rv == MLIST_KNOWN) {
1822 if (gp->g_type & GT_SUBSCRIBE)
1823 rv = MLIST_SUBSCRIBED;
1824 else if (subscribed_only)
1825 rv = MLIST_OTHER;
1826 /* Of course, if that is a regular expression it doesn't mean a thing */
1827 #ifdef HAVE_REGEX
1828 if (gp->g_type & GT_REGEX)
1829 rv = MLIST_OTHER;
1830 else
1831 #endif
1832 goto jleave;
1835 /* Not in the hashmap (as something matchable), walk the lists */
1836 #ifdef HAVE_REGEX
1837 re2 = FAL0;
1838 lpp = &_mlsub_regex;
1839 jregex_redo:
1840 if ((grp = *lpp) != NULL) {
1841 do if (regexec(&grp->gr_regex, name, 0,NULL, 0) != REG_NOMATCH) {
1842 /* Relink as the head of this list if the hit count of this group is
1843 * >= 25% of the average hit count */
1844 size_t i;
1845 if (!re2)
1846 i = ++_mlsub_hits / _mlsub_size;
1847 else
1848 i = ++_mlist_hits / _mlist_size;
1849 i >>= 2;
1851 if (++grp->gr_hits >= i && *lpp != grp && grp->gr_next != grp) {
1852 grp->gr_last->gr_next = grp->gr_next;
1853 grp->gr_next->gr_last = grp->gr_last;
1854 (grp->gr_last = (*lpp)->gr_last)->gr_next = grp;
1855 (grp->gr_next = *lpp)->gr_last = grp;
1856 *lpp = grp;
1858 rv = !re2 ? MLIST_SUBSCRIBED : MLIST_KNOWN;
1859 goto jleave;
1860 } while ((grp = grp->gr_next) != *lpp);
1862 if (!re2 && !subscribed_only) {
1863 re2 = TRU1;
1864 lpp = &_mlist_regex;
1865 goto jregex_redo;
1867 assert(rv == MLIST_OTHER);
1868 #endif
1870 jleave:
1871 NYD_LEAVE;
1872 return rv;
1875 FL int
1876 c_shortcut(void *v)
1878 char **argv = v;
1879 int rv = 0;
1880 NYD_ENTER;
1882 if (*argv == NULL)
1883 _group_print_all(GT_SHORTCUT);
1884 else for (; *argv != NULL; argv += 2) {
1885 /* Because one hardly ever redefines, anything is stored in one chunk */
1886 size_t l;
1887 struct group *gp;
1888 char *cp;
1890 if (argv[1] == NULL) {
1891 n_err(_("Synopsis: shortcut: <shortcut> <expansion>\n"));
1892 rv = 1;
1893 break;
1895 if (_group_find(GT_SHORTCUT, *argv) != NULL)
1896 _group_del(GT_SHORTCUT, *argv);
1898 l = strlen(argv[1]) +1;
1899 gp = _group_fetch(GT_SHORTCUT, *argv, l);
1900 GP_TO_SUBCLASS(cp, gp);
1901 memcpy(cp, argv[1], l);
1903 NYD_LEAVE;
1904 return rv;
1907 FL int
1908 c_unshortcut(void *v)
1910 char **argv = v;
1911 int rv = 0;
1912 NYD_ENTER;
1914 do if (!_group_del(GT_SHORTCUT, *argv)) {
1915 n_err(_("No such shortcut: %s\n"), *argv);
1916 rv = 1;
1917 } while (*++argv != NULL);
1918 NYD_LEAVE;
1919 return rv;
1922 FL char const *
1923 shortcut_expand(char const *str)
1925 struct group *gp;
1926 NYD_ENTER;
1928 if ((gp = _group_find(GT_SHORTCUT, str)) != NULL)
1929 GP_TO_SUBCLASS(str, gp);
1930 else
1931 str = NULL;
1932 NYD_LEAVE;
1933 return str;
1936 FL int
1937 c_charsetalias(void *vp){
1938 char **argv;
1939 int rv;
1940 NYD_ENTER;
1942 rv = 0;
1943 argv = vp;
1945 if(*argv == NULL)
1946 _group_print_all(GT_CHARSETALIAS);
1947 else for(; *argv != NULL; argv += 2){
1948 /* Because one hardly ever redefines, anything is stored in one chunk */
1949 char const *ccp;
1950 char *cp, c;
1951 struct group *gp;
1952 size_t l;
1954 if(argv[1] == NULL){
1955 n_err(_("Synopsis: charsetalias: <charset> <charset-alias>\n"));
1956 rv = 1;
1957 break;
1960 /* Delete the old one, if any; don't get fooled to remove them all */
1961 ccp = argv[0];
1962 if(ccp[0] != '*' || ccp[1] != '\0')
1963 _group_del(GT_CHARSETALIAS, ccp);
1965 /* Lowercase it all (for display purposes) */
1966 cp = savestr(ccp);
1967 ccp = cp;
1968 while((c = *cp) != '\0')
1969 *cp++ = lowerconv(c);
1971 l = strlen(argv[1]) +1;
1972 gp = _group_fetch(GT_CHARSETALIAS, ccp, l);
1973 GP_TO_SUBCLASS(cp, gp);
1974 for(ccp = argv[1]; (c = *ccp++) != '\0';)
1975 *cp++ = lowerconv(c);
1976 *cp = '\0';
1978 NYD_LEAVE;
1979 return rv;
1982 FL int
1983 c_uncharsetalias(void *vp){
1984 char **argv;
1985 int rv;
1986 NYD_ENTER;
1988 rv = 0;
1989 argv = vp;
1991 do if(!_group_del(GT_CHARSETALIAS, *argv)){
1992 n_err(_("No such `charsetalias': %s\n"), n_shexp_quote_cp(*argv, FAL0));
1993 rv = 1;
1994 }while(*++argv != NULL);
1995 NYD_LEAVE;
1996 return rv;
1999 FL char const *
2000 n_charsetalias_expand(char const *cp){
2001 struct group *gp;
2002 size_t i;
2003 char const *cp_orig;
2004 NYD_ENTER;
2006 cp_orig = cp;
2008 for(i = 0; (gp = _group_find(GT_CHARSETALIAS, cp)) != NULL;){
2009 GP_TO_SUBCLASS(cp, gp);
2010 if(++i == 8) /* XXX Magic (same as for `ghost' expansion) */
2011 break;
2014 if(cp != cp_orig)
2015 cp = savestr(cp);
2016 NYD_LEAVE;
2017 return cp;
2020 /* s-it-mode */