nail.1: tweak `colour' docu a bit
[s-mailx.git] / nam_a_grp.c
blobc249b2c51c008dc169628e2fe217939a05eeaddb
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 - 2016 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_CUSTOMHDR = 1<< 3,
48 GT_MASK = GT_ALIAS | GT_MLIST | GT_SHORTCUT | GT_CUSTOMHDR,
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 **_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 /* `customhdr' */
128 static struct group *_customhdr_heads[HSHSIZE]; /* TODO dynamic hash */
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 /* TODO v15: drop *customhdr* (OR change syntax and use shell tokens) */
203 static char *a_nag_custom_sep(char **iolist);
205 static bool_t
206 _same_name(char const *n1, char const *n2)
208 bool_t rv = FAL0;
209 char c1, c2;
210 NYD_ENTER;
212 if (ok_blook(allnet)) {
213 do {
214 c1 = *n1++;
215 c2 = *n2++;
216 c1 = lowerconv(c1);
217 c2 = lowerconv(c2);
218 if (c1 != c2)
219 goto jleave;
220 } while (c1 != '\0' && c2 != '\0' && c1 != '@' && c2 != '@');
221 rv = 1;
222 } else
223 rv = !asccasecmp(n1, n2);
224 jleave:
225 NYD_LEAVE;
226 return rv;
229 static struct name *
230 delname(struct name *np, char const *name)
232 struct name *p;
233 NYD_ENTER;
235 for (p = np; p != NULL; p = p->n_flink)
236 if (_same_name(p->n_name, name)) {
237 if (p->n_blink == NULL) {
238 if (p->n_flink != NULL)
239 p->n_flink->n_blink = NULL;
240 np = p->n_flink;
241 continue;
243 if (p->n_flink == NULL) {
244 if (p->n_blink != NULL)
245 p->n_blink->n_flink = NULL;
246 continue;
248 p->n_blink->n_flink = p->n_flink;
249 p->n_flink->n_blink = p->n_blink;
251 NYD_LEAVE;
252 return np;
255 static struct name *
256 put(struct name *list, struct name *node)
258 NYD_ENTER;
259 node->n_flink = list;
260 node->n_blink = NULL;
261 if (list != NULL)
262 list->n_blink = node;
263 NYD_LEAVE;
264 return node;
267 static char const *
268 yankname(char const *ap, char *wbuf, char const *separators, int keepcomms)
270 char const *cp;
271 char *wp, c, inquote, lc, lastsp;
272 NYD_ENTER;
274 *(wp = wbuf) = '\0';
276 /* Skip over intermediate list trash, as in ".org> , <xy@zz.org>" */
277 for (c = *ap; blankchar(c) || c == ','; c = *++ap)
279 if (c == '\0') {
280 cp = NULL;
281 goto jleave;
284 /* Parse a full name: TODO RFC 5322
285 * - Keep everything in quotes, liberal handle *quoted-pair*s therein
286 * - Skip entire (nested) comments
287 * - In non-quote, non-comment, join adjacent space to a single SP
288 * - Understand separators only in non-quote, non-comment context,
289 * and only if not part of a *quoted-pair* (XXX too liberal) */
290 cp = ap;
291 for (inquote = lc = lastsp = 0;; lc = c, ++cp) {
292 c = *cp;
293 if (c == '\0')
294 break;
295 if (c == '\\') {
296 lastsp = 0;
297 continue;
299 if (c == '"') {
300 if (lc != '\\')
301 inquote = !inquote;
302 #if 0 /* TODO when doing real RFC 5322 parsers - why have i done this? */
303 else
304 --wp;
305 #endif
306 goto jwpwc;
308 if (inquote || lc == '\\') {
309 jwpwc:
310 *wp++ = c;
311 lastsp = 0;
312 continue;
314 if (c == '(') {
315 ap = cp;
316 cp = skip_comment(cp + 1);
317 if (keepcomms)
318 while (ap < cp)
319 *wp++ = *ap++;
320 --cp;
321 lastsp = 0;
322 continue;
324 if (strchr(separators, c) != NULL)
325 break;
327 lc = lastsp;
328 lastsp = blankchar(c);
329 if (!lastsp || !lc)
330 *wp++ = c;
332 if (blankchar(lc))
333 --wp;
335 *wp = '\0';
336 jleave:
337 NYD_LEAVE;
338 return cp;
341 static struct name *
342 _extract1(char const *line, enum gfield ntype, char const *separators,
343 bool_t keepcomms)
345 struct name *topp, *np, *t;
346 char const *cp;
347 char *nbuf;
348 NYD_ENTER;
350 topp = NULL;
351 if (line == NULL || *line == '\0')
352 goto jleave;
354 np = NULL;
355 cp = line;
356 nbuf = smalloc(strlen(line) +1);
357 while ((cp = yankname(cp, nbuf, separators, keepcomms)) != NULL) {
358 t = nalloc(nbuf, ntype);
359 if (topp == NULL)
360 topp = t;
361 else
362 np->n_flink = t;
363 t->n_blink = np;
364 np = t;
366 free(nbuf);
367 jleave:
368 NYD_LEAVE;
369 return topp;
372 static struct name *
373 _gexpand(size_t level, struct name *nlist, struct group *gp, bool_t metoo,
374 int ntype)
376 struct grp_names_head *gnhp;
377 struct grp_names *gnp;
378 NYD_ENTER;
380 if (UICMP(z, level++, >, MAXEXP)) {
381 n_err(_("Expanding alias to depth larger than %d\n"), MAXEXP);
382 goto jleave;
385 GP_TO_SUBCLASS(gnhp, gp);
386 for (gnp = gnhp->gnh_head; gnp != NULL; gnp = gnp->gn_next) {
387 char *cp;
388 struct group *ngp;
390 /* FIXME we do not really support leading backslash quoting do we??? */
391 if (*(cp = gnp->gn_id) == '\\' || !strcmp(cp, gp->g_id))
392 goto jquote;
394 if ((ngp = _group_find(GT_ALIAS, cp)) != NULL) {
395 /* For S-nail(1), the "alias" may *be* the sender in that a name maps
396 * to a full address specification; aliases cannot be empty */
397 struct grp_names_head *ngnhp;
398 GP_TO_SUBCLASS(ngnhp, ngp);
400 assert(ngnhp->gnh_head != NULL);
401 if (metoo || ngnhp->gnh_head->gn_next != NULL ||
402 !_same_name(cp, myname))
403 nlist = _gexpand(level, nlist, ngp, metoo, ntype);
404 continue;
407 /* Here we should allow to expand to itself if only person in alias */
408 jquote:
409 if (metoo || gnhp->gnh_head->gn_next == NULL || !_same_name(cp, myname))
410 nlist = put(nlist, nalloc(cp, ntype | GFULL));
412 jleave:
413 NYD_LEAVE;
414 return nlist;
417 static struct group *
418 _group_lookup(enum group_type gt, struct group_lookup *glp, char const *id)
420 struct group *lgp, *gp;
421 NYD_ENTER;
423 gt &= GT_MASK;
424 lgp = NULL;
425 gp = *(glp->gl_htable = glp->gl_slot =
426 ((gt & GT_ALIAS ? _alias_heads :
427 (gt & GT_MLIST ? _mlist_heads :
428 (gt & GT_SHORTCUT ? _shortcut_heads : _customhdr_heads))) +
429 torek_hash(id) % HSHSIZE));
431 for (; gp != NULL; lgp = gp, gp = gp->g_next)
432 if ((gp->g_type & gt) && *gp->g_id == *id && !strcmp(gp->g_id, id))
433 break;
435 glp->gl_slot_last = lgp;
436 glp->gl_group = gp;
437 NYD_LEAVE;
438 return gp;
441 static struct group *
442 _group_find(enum group_type gt, char const *id)
444 struct group_lookup gl;
445 struct group *gp;
446 NYD_ENTER;
448 gp = _group_lookup(gt, &gl, id);
449 NYD_LEAVE;
450 return gp;
453 static struct group *
454 _group_go_first(enum group_type gt, struct group_lookup *glp)
456 struct group **gpa, *gp;
457 size_t i;
458 NYD_ENTER;
460 for (glp->gl_htable = gpa = (gt & GT_ALIAS ? _alias_heads :
461 (gt & GT_MLIST ? _mlist_heads :
462 (gt & GT_SHORTCUT ? _shortcut_heads : _customhdr_heads))), i = 0;
463 i < HSHSIZE; ++gpa, ++i)
464 if ((gp = *gpa) != NULL) {
465 glp->gl_slot = gpa;
466 glp->gl_group = gp;
467 goto jleave;
470 glp->gl_group = gp = NULL;
471 jleave:
472 glp->gl_slot_last = NULL;
473 NYD_LEAVE;
474 return gp;
477 static struct group *
478 _group_go_next(struct group_lookup *glp)
480 struct group *gp, **gpa;
481 NYD_ENTER;
483 if ((gp = glp->gl_group->g_next) != NULL)
484 glp->gl_slot_last = glp->gl_group;
485 else {
486 glp->gl_slot_last = NULL;
487 for (gpa = glp->gl_htable + HSHSIZE; ++glp->gl_slot < gpa;)
488 if ((gp = *glp->gl_slot) != NULL)
489 break;
491 glp->gl_group = gp;
492 NYD_LEAVE;
493 return gp;
496 static struct group *
497 _group_fetch(enum group_type gt, char const *id, size_t addsz)
499 struct group_lookup gl;
500 struct group *gp;
501 size_t l, i;
502 NYD_ENTER;
504 if ((gp = _group_lookup(gt, &gl, id)) != NULL)
505 goto jleave;
507 l = strlen(id) +1;
508 i = n_ALIGN(sizeof(*gp) - n_VFIELD_SIZEOF(struct group, g_id) + l);
509 switch (gt & GT_MASK) {
510 case GT_ALIAS:
511 case GT_CUSTOMHDR:
512 addsz = sizeof(struct grp_names_head);
513 break;
514 case GT_MLIST:
515 #ifdef HAVE_REGEX
516 if (n_is_maybe_regex(id)) {
517 addsz = sizeof(struct grp_regex);
518 gt |= GT_REGEX;
520 #endif
521 case GT_SHORTCUT:
522 default:
523 break;
526 gp = smalloc(i + addsz);
527 gp->g_subclass_off = i;
528 gp->g_type = gt;
529 memcpy(gp->g_id, id, l);
531 if (gt & (GT_ALIAS | GT_CUSTOMHDR)) {
532 struct grp_names_head *gnhp;
534 GP_TO_SUBCLASS(gnhp, gp);
535 gnhp->gnh_head = NULL;
537 #ifdef HAVE_REGEX
538 else if (/*(gt & GT_MLIST) &&*/ gt & GT_REGEX) {
539 struct grp_regex *grp;
540 GP_TO_SUBCLASS(grp, gp);
542 if (regcomp(&grp->gr_regex, id, REG_EXTENDED | REG_ICASE | REG_NOSUB)) {
543 n_err(_("Invalid regular expression: %s\n"), id);
544 free(gp);
545 gp = NULL;
546 goto jleave;
548 grp->gr_mygroup = gp;
549 _mlmux_linkin(gp);
551 #endif
553 gp->g_next = *gl.gl_slot;
554 *gl.gl_slot = gp;
555 jleave:
556 NYD_LEAVE;
557 return gp;
560 static bool_t
561 _group_del(enum group_type gt, char const *id)
563 enum group_type xgt = gt & GT_MASK;
564 struct group_lookup gl;
565 struct group *gp;
566 NYD_ENTER;
568 /* Delete 'em all? */
569 if (id[0] == '*' && id[1] == '\0') {
570 for (gp = _group_go_first(gt, &gl); gp != NULL;)
571 gp = (gp->g_type & xgt) ? __group_del(&gl) : _group_go_next(&gl);
572 gp = (struct group*)TRU1;
573 } else if ((gp = _group_lookup(gt, &gl, id)) != NULL) {
574 if (gp->g_type & xgt)
575 __group_del(&gl);
576 else
577 gp = NULL;
579 NYD_LEAVE;
580 return (gp != NULL);
583 static struct group *
584 __group_del(struct group_lookup *glp)
586 struct group *x, *gp;
587 NYD_ENTER;
589 /* Overly complicated: link off this node, step ahead to next.. */
590 x = glp->gl_group;
591 if ((gp = glp->gl_slot_last) != NULL) {
592 gp = (gp->g_next = x->g_next);
593 } else {
594 glp->gl_slot_last = NULL;
595 gp = (*glp->gl_slot = x->g_next);
597 if (gp == NULL) {
598 struct group **gpa = glp->gl_htable + HSHSIZE;
600 while (++glp->gl_slot < gpa)
601 if ((gp = *glp->gl_slot) != NULL)
602 break;
605 glp->gl_group = gp;
607 if (x->g_type & (GT_ALIAS | GT_CUSTOMHDR))
608 __names_del(x);
609 #ifdef HAVE_REGEX
610 else if (/*(x->g_type & GT_MLIST) &&*/ x->g_type & GT_REGEX) {
611 struct grp_regex *grp;
612 GP_TO_SUBCLASS(grp, x);
614 regfree(&grp->gr_regex);
615 _mlmux_linkout(x);
617 #endif
619 free(x);
620 NYD_LEAVE;
621 return gp;
624 static void
625 __names_del(struct group *gp)
627 struct grp_names_head *gnhp;
628 struct grp_names *gnp;
629 NYD_ENTER;
631 GP_TO_SUBCLASS(gnhp, gp);
632 for (gnp = gnhp->gnh_head; gnp != NULL;) {
633 struct grp_names *x = gnp;
634 gnp = gnp->gn_next;
635 free(x);
637 NYD_LEAVE;
640 static void
641 _group_print_all(enum group_type gt)
643 enum group_type xgt;
644 struct group **gpa;
645 struct group const *gp;
646 ui32_t h, i;
647 char const **ida;
648 FILE *fp;
649 size_t lines;
650 NYD_ENTER;
652 xgt = gt & GT_PRINT_MASK;
653 gpa = (xgt & GT_ALIAS ? _alias_heads
654 : (xgt & GT_MLIST ? _mlist_heads
655 : (xgt & GT_SHORTCUT ? _shortcut_heads
656 : _customhdr_heads)));
658 for (h = 0, i = 1; h < HSHSIZE; ++h)
659 for (gp = gpa[h]; gp != NULL; gp = gp->g_next)
660 if ((gp->g_type & xgt) == xgt)
661 ++i;
662 ida = salloc(i * sizeof *ida);
664 for (i = h = 0; h < HSHSIZE; ++h)
665 for (gp = gpa[h]; gp != NULL; gp = gp->g_next)
666 if ((gp->g_type & xgt) == xgt)
667 ida[i++] = gp->g_id;
668 ida[i] = NULL;
670 if (i > 1)
671 qsort(ida, i, sizeof *ida, &__group_print_qsorter);
673 if ((fp = Ftmp(NULL, "prgroup", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL)
674 fp = stdout;
675 lines = 0;
677 for (i = 0; ida[i] != NULL; ++i)
678 lines += _group_print(_group_find(gt, ida[i]), fp);
679 #ifdef HAVE_REGEX
680 if (gt & GT_MLIST) {
681 if (gt & GT_SUBSCRIBE)
682 i = (ui32_t)_mlsub_size, h = (ui32_t)_mlsub_hits;
683 else
684 i = (ui32_t)_mlist_size, h = (ui32_t)_mlist_hits;
685 if (i > 0 && (options & OPT_D_V)){
686 fprintf(fp, _("# %s list regex(7) total: %u entries, %u hits\n"),
687 (gt & GT_SUBSCRIBE ? _("Subscribed") : _("Non-subscribed")),
688 i, h);
689 ++lines;
692 #endif
694 if (fp != stdout) {
695 page_or_print(fp, lines);
696 Fclose(fp);
698 NYD_LEAVE;
701 static int
702 __group_print_qsorter(void const *a, void const *b)
704 int rv;
705 NYD_ENTER;
707 rv = strcmp(*(char**)n_UNCONST(a), *(char**)n_UNCONST(b));
708 NYD_LEAVE;
709 return rv;
712 static size_t
713 _group_print(struct group const *gp, FILE *fo)
715 char const *cp;
716 size_t rv;
717 NYD_ENTER;
719 rv = 1;
721 if (gp->g_type & GT_ALIAS) {
722 struct grp_names_head *gnhp;
723 struct grp_names *gnp;
725 fprintf(fo, "alias %s ", gp->g_id);
727 GP_TO_SUBCLASS(gnhp, gp);
728 if ((gnp = gnhp->gnh_head) != NULL) { /* xxx always 1+ entries */
729 do {
730 struct grp_names *x = gnp;
731 gnp = gnp->gn_next;
732 fprintf(fo, " \"%s\"", string_quote(x->gn_id)); /* TODO shexp */
733 } while (gnp != NULL);
735 putc('\n', fo);
736 } else if (gp->g_type & GT_MLIST) {
737 #ifdef HAVE_REGEX
738 if ((gp->g_type & GT_REGEX) && (options & OPT_D_V)){
739 size_t i;
740 struct grp_regex *grp,
741 *lp = (gp->g_type & GT_SUBSCRIBE ? _mlsub_regex : _mlist_regex);
743 GP_TO_SUBCLASS(grp, gp);
744 for (i = 1; lp != grp; lp = lp->gr_next)
745 ++i;
746 fprintf(fo, "# regex(7): hits %" PRIuZ ", sort %" PRIuZ ".\n ",
747 grp->gr_hits, i);
748 ++rv;
750 #endif
752 fprintf(fo, "wysh %s %s\n",
753 (gp->g_type & GT_SUBSCRIBE ? "mlsubscribe" : "mlist"),
754 n_shexp_quote_cp(gp->g_id, TRU1));
755 } else if (gp->g_type & GT_SHORTCUT) {
756 GP_TO_SUBCLASS(cp, gp);
757 fprintf(fo, "wysh shortcut %s %s\n",
758 gp->g_id, n_shexp_quote_cp(cp, TRU1));
759 } else if (gp->g_type & GT_CUSTOMHDR) {
760 struct grp_names_head *gnhp;
761 struct grp_names *gnp;
763 GP_TO_SUBCLASS(gnhp, gp);
764 for(rv = 0, gnp = gnhp->gnh_head; gnp != NULL; ++rv, gnp = gnp->gn_next)
765 fprintf(fo, "customhdr %s %s\n",
766 gp->g_id, n_shexp_quote_cp(gnp->gn_id, TRU1));
769 NYD_LEAVE;
770 return rv;
773 static int
774 _mlmux(enum group_type gt, char **argv)
776 struct group *gp;
777 int rv = 0;
778 NYD_ENTER;
780 if (*argv == NULL)
781 _group_print_all(gt);
782 else do {
783 if ((gp = _group_find(gt, *argv)) != NULL) {
784 if (gt & GT_SUBSCRIBE) {
785 if (!(gp->g_type & GT_SUBSCRIBE)) {
786 _MLMUX_LINKOUT(gp);
787 gp->g_type |= GT_SUBSCRIBE;
788 _MLMUX_LINKIN(gp);
789 } else {
790 n_err(_("Mailing-list already `mlsubscribe'd: %s\n"),
791 *argv);
792 rv = 1;
794 } else {
795 n_err(_("Mailing-list already `mlist'ed: %s\n"), *argv);
796 rv = 1;
798 } else
799 _group_fetch(gt, *argv, 0);
800 } while (*++argv != NULL);
802 NYD_LEAVE;
803 return rv;
806 static int
807 _unmlmux(enum group_type gt, char **argv)
809 struct group *gp;
810 int rv = 0;
811 NYD_ENTER;
813 for (; *argv != NULL; ++argv) {
814 if (gt & GT_SUBSCRIBE) {
815 struct group_lookup gl;
816 bool_t isaster;
818 if (!(isaster = (**argv == '*')))
819 gp = _group_find(gt, *argv);
820 else if ((gp = _group_go_first(gt, &gl)) == NULL)
821 continue;
822 else if (gp != NULL && !(gp->g_type & GT_SUBSCRIBE))
823 goto jaster_entry;
825 if (gp != NULL) {
826 jaster_redo:
827 if (gp->g_type & GT_SUBSCRIBE) {
828 _MLMUX_LINKOUT(gp);
829 gp->g_type &= ~GT_SUBSCRIBE;
830 _MLMUX_LINKIN(gp);
831 if (isaster) {
832 jaster_entry:
833 while ((gp = _group_go_next(&gl)) != NULL &&
834 !(gp->g_type & GT_SUBSCRIBE))
836 if (gp != NULL)
837 goto jaster_redo;
839 } else {
840 n_err(_("Mailing-list not `mlsubscribe'd: %s\n"),
841 n_shexp_quote_cp(*argv, FAL0));
842 rv = 1;
844 continue;
846 } else if (_group_del(gt, *argv))
847 continue;
848 n_err(_("No such mailing-list: %s\n"), n_shexp_quote_cp(*argv, FAL0));
849 rv = 1;
851 NYD_LEAVE;
852 return rv;
855 #ifdef HAVE_REGEX
856 static void
857 _mlmux_linkin(struct group *gp)
859 struct grp_regex **lpp, *grp, *lhp;
860 NYD_ENTER;
862 if (gp->g_type & GT_SUBSCRIBE) {
863 lpp = &_mlsub_regex;
864 ++_mlsub_size;
865 } else {
866 lpp = &_mlist_regex;
867 ++_mlist_size;
870 GP_TO_SUBCLASS(grp, gp);
871 if ((lhp = *lpp) != NULL) {
872 (grp->gr_last = lhp->gr_last)->gr_next = grp;
873 (grp->gr_next = lhp)->gr_last = grp;
874 } else
875 *lpp = grp->gr_last = grp->gr_next = grp;
876 grp->gr_hits = 0;
877 NYD_LEAVE;
880 static void
881 _mlmux_linkout(struct group *gp)
883 struct grp_regex *grp, **lpp;
884 NYD_ENTER;
886 GP_TO_SUBCLASS(grp, gp);
888 if (gp->g_type & GT_SUBSCRIBE) {
889 lpp = &_mlsub_regex;
890 --_mlsub_size;
891 _mlsub_hits -= grp->gr_hits;
892 } else {
893 lpp = &_mlist_regex;
894 --_mlist_size;
895 _mlist_hits -= grp->gr_hits;
898 if (grp->gr_next == grp)
899 *lpp = NULL;
900 else {
901 (grp->gr_last->gr_next = grp->gr_next)->gr_last = grp->gr_last;
902 if (*lpp == grp)
903 *lpp = grp->gr_next;
905 NYD_LEAVE;
907 #endif /* HAVE_REGEX */
909 static char *
910 a_nag_custom_sep(char **iolist){
911 char *cp, c, *base;
912 bool_t isesc, anyesc;
913 NYD2_ENTER;
915 for(base = *iolist; base != NULL; base = *iolist){
916 while((c = *base) != '\0' && blankspacechar(c))
917 ++base;
919 for(isesc = anyesc = FAL0, cp = base;; ++cp){
920 if(n_UNLIKELY((c = *cp) == '\0')){
921 *iolist = NULL;
922 break;
923 }else if(!isesc){
924 if(c == ','){
925 *iolist = cp + 1;
926 break;
928 isesc = (c == '\\');
929 }else{
930 isesc = FAL0;
931 anyesc |= (c == ',');
935 while(cp > base && blankspacechar(cp[-1]))
936 --cp;
937 *cp = '\0';
939 if(*base != '\0'){
940 if(anyesc){
941 char *ins;
943 for(ins = cp = base;; ++ins)
944 if((c = *cp) == '\\' && cp[1] == ','){
945 *ins = ',';
946 cp += 2;
947 }else if((*ins = (++cp, c)) == '\0')
948 break;
950 break;
953 NYD2_LEAVE;
954 return base;
957 FL struct name *
958 nalloc(char const *str, enum gfield ntype)
960 struct addrguts ag;
961 struct str in, out;
962 struct name *np;
963 NYD_ENTER;
964 assert(!(ntype & GFULLEXTRA) || (ntype & GFULL) != 0);
966 addrspec_with_guts(((ntype & (GFULL | GSKIN | GREF)) != 0), str, &ag);
967 if (!(ag.ag_n_flags & NAME_NAME_SALLOC)) {
968 ag.ag_n_flags |= NAME_NAME_SALLOC;
969 np = salloc(sizeof(*np) + ag.ag_slen +1);
970 memcpy(np + 1, ag.ag_skinned, ag.ag_slen +1);
971 ag.ag_skinned = (char*)(np + 1);
972 } else
973 np = salloc(sizeof *np);
975 np->n_flink = NULL;
976 np->n_blink = NULL;
977 np->n_type = ntype;
978 np->n_flags = 0;
980 np->n_fullname = np->n_name = ag.ag_skinned;
981 np->n_fullextra = NULL;
982 np->n_flags = ag.ag_n_flags;
984 if (ntype & GFULL) {
985 if (ag.ag_ilen == ag.ag_slen
986 #ifdef HAVE_IDNA
987 && !(ag.ag_n_flags & NAME_IDNA)
988 #endif
990 goto jleave;
991 if (ag.ag_n_flags & NAME_ADDRSPEC_ISFILEORPIPE)
992 goto jleave;
994 /* n_fullextra is only the complete name part without address.
995 * Beware of "-r '<abc@def>'", don't treat that as FULLEXTRA */
996 if ((ntype & GFULLEXTRA) && ag.ag_ilen > ag.ag_slen + 2) {
997 size_t s = ag.ag_iaddr_start, e = ag.ag_iaddr_aend, i;
998 char const *cp;
1000 if (s == 0 || str[--s] != '<' || str[e++] != '>')
1001 goto jskipfullextra;
1002 i = ag.ag_ilen - e;
1003 in.s = ac_alloc(s + i +1);
1004 memcpy(in.s, str, s);
1005 if (i > 0)
1006 memcpy(in.s + s, str + e, i);
1007 s += i;
1008 in.s[in.l = s] = '\0';
1009 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
1011 for (cp = out.s, i = out.l; i > 0 && spacechar(*cp); --i, ++cp)
1013 while (i > 0 && spacechar(cp[i - 1]))
1014 --i;
1015 np->n_fullextra = savestrbuf(cp, i);
1017 free(out.s);
1018 ac_free(in.s);
1020 jskipfullextra:
1022 /* n_fullname depends on IDNA conversion */
1023 #ifdef HAVE_IDNA
1024 if (!(ag.ag_n_flags & NAME_IDNA)) {
1025 #endif
1026 in.s = n_UNCONST(str);
1027 in.l = ag.ag_ilen;
1028 #ifdef HAVE_IDNA
1029 } else {
1030 /* The domain name was IDNA and has been converted. We also have to
1031 * ensure that the domain name in .n_fullname is replaced with the
1032 * converted version, since MIME doesn't perform encoding of addrs */
1033 size_t l = ag.ag_iaddr_start,
1034 lsuff = ag.ag_ilen - ag.ag_iaddr_aend;
1035 in.s = ac_alloc(l + ag.ag_slen + lsuff +1);
1036 memcpy(in.s, str, l);
1037 memcpy(in.s + l, ag.ag_skinned, ag.ag_slen);
1038 l += ag.ag_slen;
1039 memcpy(in.s + l, str + ag.ag_iaddr_aend, lsuff);
1040 l += lsuff;
1041 in.s[l] = '\0';
1042 in.l = l;
1044 #endif
1045 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
1046 np->n_fullname = savestr(out.s);
1047 free(out.s);
1048 #ifdef HAVE_IDNA
1049 if (ag.ag_n_flags & NAME_IDNA)
1050 ac_free(in.s);
1051 #endif
1052 np->n_flags |= NAME_FULLNAME_SALLOC;
1054 jleave:
1055 NYD_LEAVE;
1056 return np;
1059 FL struct name *
1060 ndup(struct name *np, enum gfield ntype)
1062 struct name *nnp;
1063 NYD_ENTER;
1065 if ((ntype & (GFULL | GSKIN)) && !(np->n_flags & NAME_SKINNED)) {
1066 nnp = nalloc(np->n_name, ntype);
1067 goto jleave;
1070 nnp = salloc(sizeof *np);
1071 nnp->n_flink = nnp->n_blink = NULL;
1072 nnp->n_type = ntype;
1073 nnp->n_flags = (np->n_flags & ~(NAME_NAME_SALLOC | NAME_FULLNAME_SALLOC)) |
1074 NAME_NAME_SALLOC;
1075 nnp->n_name = savestr(np->n_name);
1076 if (np->n_name == np->n_fullname || !(ntype & (GFULL | GSKIN))) {
1077 nnp->n_fullname = nnp->n_name;
1078 nnp->n_fullextra = NULL;
1079 } else {
1080 nnp->n_flags |= NAME_FULLNAME_SALLOC;
1081 nnp->n_fullname = savestr(np->n_fullname);
1082 nnp->n_fullextra = (np->n_fullextra == NULL) ? NULL
1083 : savestr(np->n_fullextra);
1085 jleave:
1086 NYD_LEAVE;
1087 return nnp;
1090 FL struct name *
1091 cat(struct name *n1, struct name *n2)
1093 struct name *tail;
1094 NYD_ENTER;
1096 tail = n2;
1097 if (n1 == NULL)
1098 goto jleave;
1099 tail = n1;
1100 if (n2 == NULL)
1101 goto jleave;
1103 while (tail->n_flink != NULL)
1104 tail = tail->n_flink;
1105 tail->n_flink = n2;
1106 n2->n_blink = tail;
1107 tail = n1;
1108 jleave:
1109 NYD_LEAVE;
1110 return tail;
1113 FL struct name *
1114 namelist_dup(struct name const *np, enum gfield ntype)
1116 struct name *nnp;
1117 NYD_ENTER;
1119 for (nnp = NULL; np != NULL; np = np->n_flink) {
1120 struct name *x = ndup(n_UNCONST(np), (np->n_type & ~GMASK) | ntype);
1121 x->n_flink = nnp;
1122 nnp = x;
1124 NYD_LEAVE;
1125 return nnp;
1128 FL ui32_t
1129 count(struct name const *np)
1131 ui32_t c;
1132 NYD_ENTER;
1134 for (c = 0; np != NULL; np = np->n_flink)
1135 if (!(np->n_type & GDEL))
1136 ++c;
1137 NYD_LEAVE;
1138 return c;
1141 FL ui32_t
1142 count_nonlocal(struct name const *np)
1144 ui32_t c;
1145 NYD_ENTER;
1147 for (c = 0; np != NULL; np = np->n_flink)
1148 if (!(np->n_type & GDEL) && !(np->n_flags & NAME_ADDRSPEC_ISFILEORPIPE))
1149 ++c;
1150 NYD_LEAVE;
1151 return c;
1154 FL struct name *
1155 extract(char const *line, enum gfield ntype)
1157 struct name *rv;
1158 NYD_ENTER;
1160 rv = _extract1(line, ntype, " \t,", 0);
1161 NYD_LEAVE;
1162 return rv;
1165 FL struct name *
1166 lextract(char const *line, enum gfield ntype)
1168 struct name *rv;
1169 NYD_ENTER;
1171 rv = ((line != NULL && strpbrk(line, ",\"\\(<|"))
1172 ? _extract1(line, ntype, ",", 1) : extract(line, ntype));
1173 NYD_LEAVE;
1174 return rv;
1177 FL char *
1178 detract(struct name *np, enum gfield ntype)
1180 char *topp, *cp;
1181 struct name *p;
1182 int flags, s;
1183 NYD_ENTER;
1185 topp = NULL;
1186 if (np == NULL)
1187 goto jleave;
1189 flags = ntype & (GCOMMA | GNAMEONLY);
1190 ntype &= ~(GCOMMA | GNAMEONLY);
1191 s = 0;
1193 for (p = np; p != NULL; p = p->n_flink) {
1194 if (ntype && (p->n_type & GMASK) != ntype)
1195 continue;
1196 s += strlen(flags & GNAMEONLY ? p->n_name : p->n_fullname) +1;
1197 if (flags & GCOMMA)
1198 ++s;
1200 if (s == 0)
1201 goto jleave;
1203 s += 2;
1204 topp = salloc(s);
1205 cp = topp;
1206 for (p = np; p != NULL; p = p->n_flink) {
1207 if (ntype && (p->n_type & GMASK) != ntype)
1208 continue;
1209 cp = sstpcpy(cp, (flags & GNAMEONLY ? p->n_name : p->n_fullname));
1210 if ((flags & GCOMMA) && p->n_flink != NULL)
1211 *cp++ = ',';
1212 *cp++ = ' ';
1214 *--cp = 0;
1215 if ((flags & GCOMMA) && *--cp == ',')
1216 *cp = 0;
1217 jleave:
1218 NYD_LEAVE;
1219 return topp;
1222 FL struct name *
1223 grab_names(enum n_lexinput_flags lif, char const *field, struct name *np,
1224 int comma, enum gfield gflags)
1226 struct name *nq;
1227 NYD_ENTER;
1229 jloop:
1230 np = lextract(n_lex_input_cp(lif, field, detract(np, comma)), gflags);
1231 for (nq = np; nq != NULL; nq = nq->n_flink)
1232 if (is_addr_invalid(nq, EACM_NONE))
1233 goto jloop;
1234 NYD_LEAVE;
1235 return np;
1238 FL bool_t
1239 name_is_same_domain(struct name const *n1, struct name const *n2)
1241 char const *d1, *d2;
1242 bool_t rv;
1243 NYD_ENTER;
1245 d1 = strrchr(n1->n_name, '@');
1246 d2 = strrchr(n2->n_name, '@');
1248 rv = (d1 != NULL && d2 != NULL) ? !asccasecmp(++d1, ++d2) : FAL0;
1250 NYD_LEAVE;
1251 return rv;
1254 FL struct name *
1255 checkaddrs(struct name *np, enum expand_addr_check_mode eacm,
1256 si8_t *set_on_error)
1258 struct name *n;
1259 NYD_ENTER;
1261 for (n = np; n != NULL;) {
1262 si8_t rv;
1264 if ((rv = is_addr_invalid(n, eacm)) != 0) {
1265 if (set_on_error != NULL)
1266 *set_on_error |= rv; /* don't loose -1! */
1267 if (n->n_blink)
1268 n->n_blink->n_flink = n->n_flink;
1269 if (n->n_flink)
1270 n->n_flink->n_blink = n->n_blink;
1271 if (n == np)
1272 np = n->n_flink;
1274 n = n->n_flink;
1276 NYD_LEAVE;
1277 return np;
1280 FL struct name *
1281 namelist_vaporise_head(struct header *hp, enum expand_addr_check_mode eacm,
1282 bool_t metoo, si8_t *set_on_error)
1284 struct name *tolist, *np, **npp;
1285 NYD_ENTER;
1287 tolist = usermap(cat(hp->h_to, cat(hp->h_cc, hp->h_bcc)), metoo);
1288 hp->h_to = hp->h_cc = hp->h_bcc = NULL;
1290 tolist = elide(checkaddrs(tolist, eacm, set_on_error));
1292 for (np = tolist; np != NULL; np = np->n_flink) {
1293 switch (np->n_type & (GDEL | GMASK)) {
1294 case GTO: npp = &hp->h_to; break;
1295 case GCC: npp = &hp->h_cc; break;
1296 case GBCC: npp = &hp->h_bcc; break;
1297 default: continue;
1299 *npp = cat(*npp, ndup(np, np->n_type | GFULL));
1301 NYD_LEAVE;
1302 return tolist;
1305 FL struct name *
1306 usermap(struct name *names, bool_t force_metoo)
1308 struct name *new, *np, *cp;
1309 struct group *gp;
1310 int metoo;
1311 NYD_ENTER;
1313 new = NULL;
1314 np = names;
1315 metoo = (force_metoo || ok_blook(metoo));
1316 while (np != NULL) {
1317 assert(!(np->n_type & GDEL)); /* TODO legacy */
1318 if (is_fileorpipe_addr(np) || np->n_name[0] == '\\') {
1319 cp = np->n_flink;
1320 new = put(new, np);
1321 np = cp;
1322 continue;
1324 gp = _group_find(GT_ALIAS, np->n_name);
1325 cp = np->n_flink;
1326 if (gp != NULL)
1327 new = _gexpand(0, new, gp, metoo, np->n_type);
1328 else
1329 new = put(new, np);
1330 np = cp;
1332 NYD_LEAVE;
1333 return new;
1336 FL struct name *
1337 elide(struct name *names)
1339 struct name *np, *t, *newn, *x;
1340 NYD_ENTER;
1342 newn = NULL;
1343 if (names == NULL)
1344 goto jleave;
1346 /* Throw away all deleted nodes (XXX merge with plain sort below?) */
1347 for (np = NULL; names != NULL; names = names->n_flink)
1348 if (!(names->n_type & GDEL)) {
1349 names->n_blink = np;
1350 if (np)
1351 np->n_flink = names;
1352 else
1353 newn = names;
1354 np = names;
1356 if (newn == NULL)
1357 goto jleave;
1359 np = newn->n_flink;
1360 if (np != NULL)
1361 np->n_blink = NULL;
1362 newn->n_flink = NULL;
1364 while (np != NULL) {
1365 int cmpres;
1367 t = newn;
1368 while ((cmpres = asccasecmp(t->n_name, np->n_name)) < 0) {
1369 if (t->n_flink == NULL)
1370 break;
1371 t = t->n_flink;
1374 /* If we ran out of t's, put new entry after the current value of t */
1375 if (cmpres < 0) {
1376 t->n_flink = np;
1377 np->n_blink = t;
1378 t = np;
1379 np = np->n_flink;
1380 t->n_flink = NULL;
1381 continue;
1384 /* Otherwise, put the new entry in front of the current t. If at the
1385 * front of the list, the new guy becomes the new head of the list */
1386 if (t == newn) {
1387 t = np;
1388 np = np->n_flink;
1389 t->n_flink = newn;
1390 newn->n_blink = t;
1391 t->n_blink = NULL;
1392 newn = t;
1393 continue;
1396 /* The normal case -- we are inserting into the middle of the list */
1397 x = np;
1398 np = np->n_flink;
1399 x->n_flink = t;
1400 x->n_blink = t->n_blink;
1401 t->n_blink->n_flink = x;
1402 t->n_blink = x;
1405 /* Now the list headed up by new is sorted. Remove duplicates */
1406 np = newn;
1407 while (np != NULL) {
1408 t = np;
1409 while (t->n_flink != NULL && !asccasecmp(np->n_name, t->n_flink->n_name))
1410 t = t->n_flink;
1411 if (t == np) {
1412 np = np->n_flink;
1413 continue;
1416 /* Now t points to the last entry with the same name as np.
1417 * Make np point beyond t */
1418 np->n_flink = t->n_flink;
1419 if (t->n_flink != NULL)
1420 t->n_flink->n_blink = np;
1421 np = np->n_flink;
1423 jleave:
1424 NYD_LEAVE;
1425 return newn;
1428 FL int
1429 c_alternates(void *v)
1431 size_t l;
1432 char **namelist = v, **ap, **ap2, *cp;
1433 NYD_ENTER;
1435 for (namelist = v, l = 0; namelist[l] != NULL; ++l)
1438 if (l == 0) {
1439 if (_altnames != NULL) {
1440 printf("alternates ");
1441 for (ap = _altnames; *ap != NULL; ++ap)
1442 printf("%s ", *ap);
1443 printf("\n");
1445 goto jleave;
1448 if (_altnames != NULL) {
1449 for (ap = _altnames; *ap != NULL; ++ap)
1450 free(*ap);
1451 free(_altnames);
1454 ++l;
1455 _altnames = smalloc(l * sizeof(*_altnames));
1456 for (ap = namelist, ap2 = _altnames; *ap != NULL; ++ap, ++ap2) {
1457 l = strlen(*ap) +1;
1458 cp = smalloc(l);
1459 memcpy(cp, *ap, l);
1460 *ap2 = cp;
1462 *ap2 = NULL;
1463 jleave:
1464 NYD_LEAVE;
1465 return 0;
1468 FL struct name *
1469 delete_alternates(struct name *np)
1471 struct name *xp;
1472 char **ap;
1473 NYD_ENTER;
1475 np = delname(np, myname);
1476 if (_altnames != NULL)
1477 for (ap = _altnames; *ap != '\0'; ++ap)
1478 np = delname(np, *ap);
1480 if ((xp = lextract(ok_vlook(from), GEXTRA | GSKIN)) != NULL)
1481 while (xp != NULL) {
1482 np = delname(np, xp->n_name);
1483 xp = xp->n_flink;
1486 if ((xp = lextract(ok_vlook(replyto), GEXTRA | GSKIN)) != NULL)
1487 while (xp != NULL) {
1488 np = delname(np, xp->n_name);
1489 xp = xp->n_flink;
1492 if ((xp = extract(ok_vlook(sender), GEXTRA | GSKIN)) != NULL)
1493 while (xp != NULL) {
1494 np = delname(np, xp->n_name);
1495 xp = xp->n_flink;
1497 NYD_LEAVE;
1498 return np;
1501 FL int
1502 is_myname(char const *name)
1504 int rv = 1;
1505 struct name *xp;
1506 char **ap;
1507 NYD_ENTER;
1509 if (_same_name(myname, name))
1510 goto jleave;
1511 if (_altnames != NULL)
1512 for (ap = _altnames; *ap != NULL; ++ap)
1513 if (_same_name(*ap, name))
1514 goto jleave;
1516 if ((xp = lextract(ok_vlook(from), GEXTRA | GSKIN)) != NULL)
1517 while (xp != NULL) {
1518 if (_same_name(xp->n_name, name))
1519 goto jleave;
1520 xp = xp->n_flink;
1523 if ((xp = lextract(ok_vlook(replyto), GEXTRA | GSKIN)) != NULL)
1524 while (xp != NULL) {
1525 if (_same_name(xp->n_name, name))
1526 goto jleave;
1527 xp = xp->n_flink;
1530 if ((xp = extract(ok_vlook(sender), GEXTRA | GSKIN)) != NULL)
1531 while (xp != NULL) {
1532 if (_same_name(xp->n_name, name))
1533 goto jleave;
1534 xp = xp->n_flink;
1536 rv = 0;
1537 jleave:
1538 NYD_LEAVE;
1539 return rv;
1542 FL int
1543 c_alias(void *v)
1545 char **argv = v;
1546 struct group *gp;
1547 int rv = 0;
1548 NYD_ENTER;
1550 if (*argv == NULL)
1551 _group_print_all(GT_ALIAS);
1552 else if (argv[1] == NULL) {
1553 if ((gp = _group_find(GT_ALIAS, *argv)) != NULL)
1554 _group_print(gp, stdout);
1555 else {
1556 n_err(_("No such alias: %s\n"), *argv);
1557 rv = 1;
1559 } else {
1560 struct grp_names_head *gnhp;
1562 gp = _group_fetch(GT_ALIAS, *argv, 0);
1563 GP_TO_SUBCLASS(gnhp, gp);
1565 for (++argv; *argv != NULL; ++argv) {
1566 size_t l = strlen(*argv) +1;
1567 struct grp_names *gnp = smalloc(sizeof(*gnp) -
1568 n_VFIELD_SIZEOF(struct grp_names, gn_id) + l);
1569 gnp->gn_next = gnhp->gnh_head;
1570 gnhp->gnh_head = gnp;
1571 memcpy(gnp->gn_id, *argv, l);
1573 assert(gnhp->gnh_head != NULL);
1575 NYD_LEAVE;
1576 return rv;
1579 FL int
1580 c_unalias(void *v)
1582 char **argv = v;
1583 int rv = 0;
1584 NYD_ENTER;
1586 do if (!_group_del(GT_ALIAS, *argv)) {
1587 n_err(_("No such alias: %s\n"), *argv);
1588 rv = 1;
1589 } while (*++argv != NULL);
1590 NYD_LEAVE;
1591 return rv;
1594 FL int
1595 c_mlist(void *v)
1597 int rv;
1598 NYD_ENTER;
1600 rv = _mlmux(GT_MLIST, v);
1601 NYD_LEAVE;
1602 return rv;
1605 FL int
1606 c_unmlist(void *v)
1608 int rv;
1609 NYD_ENTER;
1611 rv = _unmlmux(GT_MLIST, v);
1612 NYD_LEAVE;
1613 return rv;
1616 FL int
1617 c_mlsubscribe(void *v)
1619 int rv;
1620 NYD_ENTER;
1622 rv = _mlmux(GT_MLIST | GT_SUBSCRIBE, v);
1623 NYD_LEAVE;
1624 return rv;
1627 FL int
1628 c_unmlsubscribe(void *v)
1630 int rv;
1631 NYD_ENTER;
1633 rv = _unmlmux(GT_MLIST | GT_SUBSCRIBE, v);
1634 NYD_LEAVE;
1635 return rv;
1638 FL enum mlist_state
1639 is_mlist(char const *name, bool_t subscribed_only)
1641 struct group *gp;
1642 #ifdef HAVE_REGEX
1643 struct grp_regex **lpp, *grp;
1644 bool_t re2;
1645 #endif
1646 enum mlist_state rv;
1647 NYD_ENTER;
1649 gp = _group_find(GT_MLIST, name);
1650 rv = (gp != NULL) ? MLIST_KNOWN : MLIST_OTHER;
1651 if (rv == MLIST_KNOWN) {
1652 if (gp->g_type & GT_SUBSCRIBE)
1653 rv = MLIST_SUBSCRIBED;
1654 else if (subscribed_only)
1655 rv = MLIST_OTHER;
1656 /* Of course, if that is a regular expression it doesn't mean a thing */
1657 #ifdef HAVE_REGEX
1658 if (gp->g_type & GT_REGEX)
1659 rv = MLIST_OTHER;
1660 else
1661 #endif
1662 goto jleave;
1665 /* Not in the hashmap (as something matchable), walk the lists */
1666 #ifdef HAVE_REGEX
1667 re2 = FAL0;
1668 lpp = &_mlsub_regex;
1669 jregex_redo:
1670 if ((grp = *lpp) != NULL) {
1671 do if (regexec(&grp->gr_regex, name, 0,NULL, 0) != REG_NOMATCH) {
1672 /* Relink as the head of this list if the hit count of this group is
1673 * >= 25% of the average hit count */
1674 size_t i;
1675 if (!re2)
1676 i = ++_mlsub_hits / _mlsub_size;
1677 else
1678 i = ++_mlist_hits / _mlist_size;
1679 i >>= 2;
1681 if (++grp->gr_hits >= i && *lpp != grp && grp->gr_next != grp) {
1682 grp->gr_last->gr_next = grp->gr_next;
1683 grp->gr_next->gr_last = grp->gr_last;
1684 (grp->gr_last = (*lpp)->gr_last)->gr_next = grp;
1685 (grp->gr_next = *lpp)->gr_last = grp;
1686 *lpp = grp;
1688 rv = !re2 ? MLIST_SUBSCRIBED : MLIST_KNOWN;
1689 goto jleave;
1690 } while ((grp = grp->gr_next) != *lpp);
1692 if (!re2 && !subscribed_only) {
1693 re2 = TRU1;
1694 lpp = &_mlist_regex;
1695 goto jregex_redo;
1697 assert(rv == MLIST_OTHER);
1698 #endif
1700 jleave:
1701 NYD_LEAVE;
1702 return rv;
1705 FL int
1706 c_shortcut(void *v)
1708 char **argv = v;
1709 int rv = 0;
1710 NYD_ENTER;
1712 if (*argv == NULL)
1713 _group_print_all(GT_SHORTCUT);
1714 else for (; *argv != NULL; argv += 2) {
1715 /* Because one hardly ever redefines, anything is stored in one chunk */
1716 size_t l;
1717 struct group *gp;
1718 char *cp;
1720 if (argv[1] == NULL) {
1721 n_err(_("Shortcut expansion is missing: %s\n"), *argv);
1722 rv = 1;
1723 break;
1725 if (_group_find(GT_SHORTCUT, *argv) != NULL)
1726 _group_del(GT_SHORTCUT, *argv);
1728 l = strlen(argv[1]) +1;
1729 gp = _group_fetch(GT_SHORTCUT, *argv, l);
1730 GP_TO_SUBCLASS(cp, gp);
1731 memcpy(cp, argv[1], l);
1733 NYD_LEAVE;
1734 return rv;
1737 FL int
1738 c_unshortcut(void *v)
1740 char **argv = v;
1741 int rv = 0;
1742 NYD_ENTER;
1744 do if (!_group_del(GT_SHORTCUT, *argv)) {
1745 n_err(_("No such shortcut: %s\n"), *argv);
1746 rv = 1;
1747 } while (*++argv != NULL);
1748 NYD_LEAVE;
1749 return rv;
1752 FL char const *
1753 shortcut_expand(char const *str)
1755 struct group *gp;
1756 NYD_ENTER;
1758 if ((gp = _group_find(GT_SHORTCUT, str)) != NULL)
1759 GP_TO_SUBCLASS(str, gp);
1760 else
1761 str = NULL;
1762 NYD_LEAVE;
1763 return str;
1766 FL int
1767 c_customhdr(void *v){
1768 struct group *gp;
1769 char const **argv, *hcp;
1770 int rv;
1771 NYD_ENTER;
1773 rv = 0;
1775 if((hcp = *(argv = v)) == NULL){
1776 _group_print_all(GT_CUSTOMHDR);
1777 goto jleave;
1780 /* Verify the header field name */
1781 for(rv = 1;; ++hcp){
1782 if(fieldnamechar(*hcp))
1783 continue;
1784 if(*hcp == '\0'){
1785 if(hcp == *argv){
1786 n_err(_("`customhdr': no name specified\n"));
1787 goto jleave;
1789 hcp = *argv;
1790 break;
1792 n_err(_("`customhdr': invalid name: %s\n"), *argv);
1793 goto jleave;
1795 rv = 0;
1797 if(*++argv == NULL){
1798 if((gp = _group_find(GT_CUSTOMHDR, hcp)) != NULL)
1799 _group_print(gp, stdout);
1800 else{
1801 n_err(_("No such `customhdr': %s\n"), hcp);
1802 rv = 1;
1804 }else{
1805 struct grp_names_head *gnhp;
1806 struct grp_names *gnp, *lgnp;
1807 size_t i, l;
1808 char *cp;
1810 if(pstate & PS_WYSHLIST_SAW_CONTROL){
1811 n_err(_("`customhdr': control characters not allowed: %s: %s%s\n"),
1812 hcp, n_shexp_quote_cp(*argv, FAL0),
1813 (argv[1] != NULL ? "..." : n_empty));
1814 rv = 1;
1815 goto jleave;
1818 if((gp = _group_find(GT_CUSTOMHDR, hcp)) == NULL)
1819 gp = _group_fetch(GT_CUSTOMHDR, hcp, 0);
1821 for(l = 1, i = 0; argv[i] != NULL; ++i)
1822 l += strlen(argv[i]) + 1;
1823 gnp = smalloc(sizeof *gnp - n_VFIELD_SIZEOF(struct grp_names, gn_id) + l);
1825 GP_TO_SUBCLASS(gnhp, gp);
1826 if((lgnp = gnhp->gnh_head) != NULL){
1827 while(lgnp->gn_next != NULL)
1828 lgnp = lgnp->gn_next;
1829 lgnp->gn_next = gnp;
1830 }else
1831 gnhp->gnh_head = gnp;
1833 gnp->gn_next = NULL;
1834 for(cp = &gnp->gn_id[0], i = 0; argv[i] != NULL; ++i){
1835 if(i > 0)
1836 *cp++ = ' ';
1837 l = strlen(argv[i]);
1838 memcpy(cp, argv[i], l);
1839 cp += l;
1841 *cp = '\0';
1843 jleave:
1844 NYD_LEAVE;
1845 return rv;
1848 FL int
1849 c_uncustomhdr(void *v){
1850 int rv;
1851 char **argv;
1852 NYD_ENTER;
1854 rv = 0;
1856 argv = v;
1858 if(!_group_del(GT_CUSTOMHDR, *argv)){
1859 n_err(_("No such custom header: %s\n"), *argv);
1860 rv = 1;
1862 }while(*++argv != NULL);
1863 NYD_LEAVE;
1864 return rv;
1867 FL struct n_header_field *
1868 n_customhdr_query(void){ /* XXX Uses salloc()! */
1869 struct group const *gp;
1870 ui32_t h;
1871 struct n_header_field *rv, **tail, *hfp;
1872 NYD_ENTER;
1874 rv = NULL;
1875 tail = &rv;
1877 for(h = 0; h < HSHSIZE; ++h)
1878 for(gp = _customhdr_heads[h]; gp != NULL; gp = gp->g_next){
1879 struct grp_names *gnp;
1880 struct grp_names_head *gnhp;
1882 GP_TO_SUBCLASS(gnhp, gp);
1884 for(gnp = gnhp->gnh_head; gnp != NULL; gnp = gnp->gn_next){
1885 ui32_t nl, bl;
1887 nl = (ui32_t)strlen(gp->g_id) +1;
1888 bl = (ui32_t)strlen(gnp->gn_id) +1;
1890 *tail =
1891 hfp = salloc(n_VSTRUCT_SIZEOF(struct n_header_field, hf_dat) +
1892 nl + bl);
1893 tail = &hfp->hf_next;
1894 hfp->hf_next = NULL;
1895 hfp->hf_nl = nl - 1;
1896 hfp->hf_bl = bl - 1;
1897 memcpy(hfp->hf_dat, gp->g_id, nl);
1898 memcpy(hfp->hf_dat + nl, gnp->gn_id, bl);
1902 /* TODO We have no copy-on-write environments yet, and since custom headers
1903 * TODO are a perfect subject for localization, add the OBSOLETE variable
1904 * TODO *customhdr*, too */
1906 char const *vp = ok_vlook(customhdr);
1908 if(vp != NULL){
1909 char *buf = savestr(vp);
1910 jch_outer:
1911 while((vp = a_nag_custom_sep(&buf)) != NULL){
1912 ui32_t nl, bl;
1913 char const *nstart, *cp;
1915 for(nstart = cp = vp;; ++cp){
1916 if(fieldnamechar(*cp))
1917 continue;
1918 if(*cp == '\0'){
1919 if(cp == nstart){
1920 n_err(_("Invalid nameless *customhdr* entry\n"));
1921 goto jch_outer;
1923 }else if(*cp != ':' && !blankchar(*cp)){
1924 jch_badent:
1925 n_err(_("Invalid *customhdr* entry: %s\n"), vp);
1926 goto jch_outer;
1928 break;
1930 nl = (ui32_t)PTR2SIZE(cp - nstart);
1932 while(blankchar(*cp))
1933 ++cp;
1934 if(*cp++ != ':')
1935 goto jch_badent;
1936 while(blankchar(*cp))
1937 ++cp;
1938 bl = (ui32_t)strlen(cp) +1;
1940 *tail =
1941 hfp = salloc(n_VSTRUCT_SIZEOF(struct n_header_field, hf_dat) +
1942 nl +1 + bl);
1943 tail = &hfp->hf_next;
1944 hfp->hf_next = NULL;
1945 hfp->hf_nl = nl;
1946 hfp->hf_bl = bl - 1;
1947 memcpy(hfp->hf_dat, nstart, nl);
1948 hfp->hf_dat[nl++] = '\0';
1949 memcpy(hfp->hf_dat + nl, cp, bl);
1953 NYD_LEAVE;
1954 return rv;
1957 /* s-it-mode */