NEWS: v14.8.13 ("Cyrustrupidae")
[s-mailx.git] / nam_a_grp.c
blobcfeb04f29ef60a9ed5e2efdfe5e4edb6dd1490ab
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 - 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
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_MASK = GT_ALIAS | GT_MLIST | GT_SHORTCUT,
49 /* Subtype bits and flags */
50 GT_SUBSCRIBE = 1<< 4,
51 GT_REGEX = 1<< 5,
53 /* Extended type mask to be able to reflect what we really have; i.e., mlist
54 * can have GT_REGEX if they are subscribed or not, but `mlsubscribe' should
55 * print out only GT_MLIST which have the GT_SUBSCRIBE attribute set */
56 GT_PRINT_MASK = GT_MASK | GT_SUBSCRIBE
59 struct group {
60 struct group *g_next;
61 size_t g_subclass_off; /* of "subclass" in .g_id (if any) */
62 ui8_t g_type; /* enum group_type */
63 /* Identifying name, of variable size. Dependent on actual "subtype" more
64 * data follows thereafter, but note this is always used (i.e., for regular
65 * expression entries this is still set to the plain string) */
66 char g_id[VFIELD_SIZE(-1)];
68 #define GP_TO_SUBCLASS(X,G) \
69 do {\
70 union __group_subclass {void *gs_vp; char *gs_cp;} __gs__;\
71 __gs__.gs_cp = (char*)UNCONST(G) + (G)->g_subclass_off;\
72 (X) = __gs__.gs_vp;\
73 } while (0)
75 struct grp_names_head {
76 struct grp_names *gnh_head;
79 struct grp_names {
80 struct grp_names *gn_next;
81 char gn_id[VFIELD_SIZE(0)];
84 #ifdef HAVE_REGEX
85 struct grp_regex {
86 struct grp_regex *gr_last;
87 struct grp_regex *gr_next;
88 struct group *gr_mygroup; /* xxx because lists use grp_regex*! ?? */
89 size_t gr_hits; /* Number of times this group matched */
90 regex_t gr_regex;
92 #endif
94 struct group_lookup {
95 struct group **gl_htable;
96 struct group **gl_slot;
97 struct group *gl_slot_last;
98 struct group *gl_group;
101 /* List of alternate names of user */
102 static char **_altnames;
104 /* `alias' */
105 static struct group *_alias_heads[HSHSIZE]; /* TODO dynamic hash */
107 /* `mlist', `mlsubscribe'. Anything is stored in the hashmap.. */
108 static struct group *_mlist_heads[HSHSIZE]; /* TODO dynamic hash */
110 /* ..but entries which have GT_REGEX set are false lookups and will really be
111 * accessed via sequential lists instead, which are type-specific for better
112 * performance, but also to make it possible to have ".*@xy.org" as a mlist
113 * and "(one|two)@xy.org" as a mlsubscription.
114 * These lists use a bit of QOS optimization in that a matching group will
115 * become relinked as the new list head if its hit count is
116 * (>= ((xy_hits / _xy_size) >> 2))
117 * Note that the hit counts only count currently linked in nodes.. */
118 #ifdef HAVE_REGEX
119 static struct grp_regex *_mlist_regex, *_mlsub_regex;
120 static size_t _mlist_size, _mlist_hits, _mlsub_size, _mlsub_hits;
121 #endif
123 /* `shortcut' */
124 static struct group *_shortcut_heads[HSHSIZE]; /* TODO dynamic hash */
126 /* Same name, while taking care for *allnet*? */
127 static bool_t _same_name(char const *n1, char const *n2);
129 /* Delete the given name from a namelist */
130 static struct name * delname(struct name *np, char const *name);
132 /* Put another node onto a list of names and return the list */
133 static struct name * put(struct name *list, struct name *node);
135 /* Grab a single name (liberal name) */
136 static char const * yankname(char const *ap, char *wbuf,
137 char const *separators, int keepcomms);
139 /* Extraction multiplexer that splits an input line to names */
140 static struct name * _extract1(char const *line, enum gfield ntype,
141 char const *separators, bool_t keepcomms);
143 /* Recursively expand a alias name. Limit expansion to some fixed level.
144 * Direct recursion is not expanded for convenience */
145 static struct name * _gexpand(size_t level, struct name *nlist,
146 struct group *gp, bool_t metoo, int ntype);
148 /* Lookup a group, return it or NULL, fill in glp anyway */
149 static struct group * _group_lookup(enum group_type gt,
150 struct group_lookup *glp, char const *id);
152 /* Easier-to-use wrapper around _group_lookup() */
153 static struct group * _group_find(enum group_type gt, char const *id);
155 /* Iteration: go to the first group, which also inits the iterator. A valid
156 * iterator can be stepped via _next(). A NULL return means no (more) groups
157 * to be iterated exist, in which case only glp->gl_group is set (NULL) */
158 static struct group * _group_go_first(enum group_type gt,
159 struct group_lookup *glp);
160 static struct group * _group_go_next(struct group_lookup *glp);
162 /* Fetch the group id, create it as necessary */
163 static struct group * _group_fetch(enum group_type gt, char const *id,
164 size_t addsz);
166 /* "Intelligent" delete which handles a "*" id, too;
167 * returns a true boolean if a group was deleted, and always succeeds for "*" */
168 static bool_t _group_del(enum group_type gt, char const *id);
170 static struct group * __group_del(struct group_lookup *glp);
171 static void __names_del(struct group *gp);
173 /* Print all groups of the given type, alphasorted */
174 static void _group_print_all(enum group_type gt);
176 static int __group_print_qsorter(void const *a, void const *b);
178 /* Really print a group, actually */
179 static void _group_print(struct group const *gp, FILE *fo);
181 /* Multiplexers for list and subscribe commands */
182 static int _mlmux(enum group_type gt, char **argv);
183 static int _unmlmux(enum group_type gt, char **argv);
185 /* Relinkers for the sequential match lists */
186 #ifdef HAVE_REGEX
187 static void _mlmux_linkin(struct group *gp);
188 static void _mlmux_linkout(struct group *gp);
189 # define _MLMUX_LINKIN(GP) \
190 do if ((GP)->g_type & GT_REGEX) _mlmux_linkin(GP); while (0)
191 # define _MLMUX_LINKOUT(GP) \
192 do if ((GP)->g_type & GT_REGEX) _mlmux_linkout(GP); while (0)
193 #else
194 # define _MLMUX_LINKIN(GP)
195 # define _MLMUX_LINKOUT(GP)
196 #endif
198 static bool_t
199 _same_name(char const *n1, char const *n2)
201 bool_t rv = FAL0;
202 char c1, c2;
203 NYD_ENTER;
205 if (ok_blook(allnet)) {
206 do {
207 c1 = *n1++;
208 c2 = *n2++;
209 c1 = lowerconv(c1);
210 c2 = lowerconv(c2);
211 if (c1 != c2)
212 goto jleave;
213 } while (c1 != '\0' && c2 != '\0' && c1 != '@' && c2 != '@');
214 rv = 1;
215 } else
216 rv = !asccasecmp(n1, n2);
217 jleave:
218 NYD_LEAVE;
219 return rv;
222 static struct name *
223 delname(struct name *np, char const *name)
225 struct name *p;
226 NYD_ENTER;
228 for (p = np; p != NULL; p = p->n_flink)
229 if (_same_name(p->n_name, name)) {
230 if (p->n_blink == NULL) {
231 if (p->n_flink != NULL)
232 p->n_flink->n_blink = NULL;
233 np = p->n_flink;
234 continue;
236 if (p->n_flink == NULL) {
237 if (p->n_blink != NULL)
238 p->n_blink->n_flink = NULL;
239 continue;
241 p->n_blink->n_flink = p->n_flink;
242 p->n_flink->n_blink = p->n_blink;
244 NYD_LEAVE;
245 return np;
248 static struct name *
249 put(struct name *list, struct name *node)
251 NYD_ENTER;
252 node->n_flink = list;
253 node->n_blink = NULL;
254 if (list != NULL)
255 list->n_blink = node;
256 NYD_LEAVE;
257 return node;
260 static char const *
261 yankname(char const *ap, char *wbuf, char const *separators, int keepcomms)
263 char const *cp;
264 char *wp, c, inquote, lc, lastsp;
265 NYD_ENTER;
267 *(wp = wbuf) = '\0';
269 /* Skip over intermediate list trash, as in ".org> , <xy@zz.org>" */
270 for (c = *ap; blankchar(c) || c == ','; c = *++ap)
272 if (c == '\0') {
273 cp = NULL;
274 goto jleave;
277 /* Parse a full name: TODO RFC 5322
278 * - Keep everything in quotes, liberal handle *quoted-pair*s therein
279 * - Skip entire (nested) comments
280 * - In non-quote, non-comment, join adjacent space to a single SP
281 * - Understand separators only in non-quote, non-comment context,
282 * and only if not part of a *quoted-pair* (XXX too liberal) */
283 cp = ap;
284 for (inquote = lc = lastsp = 0;; lc = c, ++cp) {
285 c = *cp;
286 if (c == '\0')
287 break;
288 if (c == '\\') {
289 lastsp = 0;
290 continue;
292 if (c == '"') {
293 if (lc != '\\')
294 inquote = !inquote;
295 #if 0 /* TODO when doing real RFC 5322 parsers - why have i done this? */
296 else
297 --wp;
298 #endif
299 goto jwpwc;
301 if (inquote || lc == '\\') {
302 jwpwc:
303 *wp++ = c;
304 lastsp = 0;
305 continue;
307 if (c == '(') {
308 ap = cp;
309 cp = skip_comment(cp + 1);
310 if (keepcomms)
311 while (ap < cp)
312 *wp++ = *ap++;
313 --cp;
314 lastsp = 0;
315 continue;
317 if (strchr(separators, c) != NULL)
318 break;
320 lc = lastsp;
321 lastsp = blankchar(c);
322 if (!lastsp || !lc)
323 *wp++ = c;
325 if (blankchar(lc))
326 --wp;
328 *wp = '\0';
329 jleave:
330 NYD_LEAVE;
331 return cp;
334 static struct name *
335 _extract1(char const *line, enum gfield ntype, char const *separators,
336 bool_t keepcomms)
338 struct name *topp, *np, *t;
339 char const *cp;
340 char *nbuf;
341 NYD_ENTER;
343 topp = NULL;
344 if (line == NULL || *line == '\0')
345 goto jleave;
347 np = NULL;
348 cp = line;
349 nbuf = smalloc(strlen(line) +1);
350 while ((cp = yankname(cp, nbuf, separators, keepcomms)) != NULL) {
351 t = nalloc(nbuf, ntype);
352 if (topp == NULL)
353 topp = t;
354 else
355 np->n_flink = t;
356 t->n_blink = np;
357 np = t;
359 free(nbuf);
360 jleave:
361 NYD_LEAVE;
362 return topp;
365 static struct name *
366 _gexpand(size_t level, struct name *nlist, struct group *gp, bool_t metoo,
367 int ntype)
369 struct grp_names_head *gnhp;
370 struct grp_names *gnp;
371 NYD_ENTER;
373 if (UICMP(z, level++, >, MAXEXP)) {
374 n_err(_("Expanding alias to depth larger than %d\n"), MAXEXP);
375 goto jleave;
378 GP_TO_SUBCLASS(gnhp, gp);
379 for (gnp = gnhp->gnh_head; gnp != NULL; gnp = gnp->gn_next) {
380 char *cp;
381 struct group *ngp;
383 /* FIXME we do not really support leading backslash quoting do we??? */
384 if (*(cp = gnp->gn_id) == '\\' || !strcmp(cp, gp->g_id))
385 goto jquote;
387 if ((ngp = _group_find(GT_ALIAS, cp)) != NULL) {
388 /* For S-nail(1), the "alias" may *be* the sender in that a name maps
389 * to a full address specification; aliases cannot be empty */
390 struct grp_names_head *ngnhp;
391 GP_TO_SUBCLASS(ngnhp, ngp);
393 assert(ngnhp->gnh_head != NULL);
394 if (metoo || ngnhp->gnh_head->gn_next != NULL ||
395 !_same_name(cp, myname))
396 nlist = _gexpand(level, nlist, ngp, metoo, ntype);
397 continue;
400 /* Here we should allow to expand to itself if only person in alias */
401 jquote:
402 if (metoo || gnhp->gnh_head->gn_next == NULL || !_same_name(cp, myname))
403 nlist = put(nlist, nalloc(cp, ntype | GFULL));
405 jleave:
406 NYD_LEAVE;
407 return nlist;
410 static struct group *
411 _group_lookup(enum group_type gt, struct group_lookup *glp, char const *id)
413 struct group *lgp, *gp;
414 NYD_ENTER;
416 gt &= GT_MASK;
417 lgp = NULL;
418 gp = *(glp->gl_htable = glp->gl_slot =
419 ((gt & GT_ALIAS ? _alias_heads :
420 (gt & GT_MLIST ? _mlist_heads : _shortcut_heads)) +
421 torek_hash(id) % HSHSIZE));
423 for (; gp != NULL; lgp = gp, gp = gp->g_next)
424 if ((gp->g_type & gt) && *gp->g_id == *id && !strcmp(gp->g_id, id))
425 break;
427 glp->gl_slot_last = lgp;
428 glp->gl_group = gp;
429 NYD_LEAVE;
430 return gp;
433 static struct group *
434 _group_find(enum group_type gt, char const *id)
436 struct group_lookup gl;
437 struct group *gp;
438 NYD_ENTER;
440 gp = _group_lookup(gt, &gl, id);
441 NYD_LEAVE;
442 return gp;
445 static struct group *
446 _group_go_first(enum group_type gt, struct group_lookup *glp)
448 struct group **gpa, *gp;
449 size_t i;
450 NYD_ENTER;
452 for (glp->gl_htable = gpa = (gt & GT_ALIAS ? _alias_heads :
453 (gt & GT_MLIST ? _mlist_heads : _shortcut_heads)), i = 0;
454 i < HSHSIZE; ++gpa, ++i)
455 if ((gp = *gpa) != NULL) {
456 glp->gl_slot = gpa;
457 glp->gl_group = gp;
458 goto jleave;
461 glp->gl_group = gp = NULL;
462 jleave:
463 glp->gl_slot_last = NULL;
464 NYD_LEAVE;
465 return gp;
468 static struct group *
469 _group_go_next(struct group_lookup *glp)
471 struct group *gp, **gpa;
472 NYD_ENTER;
474 if ((gp = glp->gl_group->g_next) != NULL)
475 glp->gl_slot_last = glp->gl_group;
476 else {
477 glp->gl_slot_last = NULL;
478 for (gpa = glp->gl_htable + HSHSIZE; ++glp->gl_slot < gpa;)
479 if ((gp = *glp->gl_slot) != NULL)
480 break;
482 glp->gl_group = gp;
483 NYD_LEAVE;
484 return gp;
487 static struct group *
488 _group_fetch(enum group_type gt, char const *id, size_t addsz)
490 struct group_lookup gl;
491 struct group *gp;
492 size_t l, i;
493 NYD_ENTER;
495 if ((gp = _group_lookup(gt, &gl, id)) != NULL)
496 goto jleave;
498 l = strlen(id) +1;
499 i = n_ALIGN(sizeof(*gp) - VFIELD_SIZEOF(struct group, g_id) + l);
500 switch (gt & GT_MASK) {
501 case GT_ALIAS:
502 addsz = sizeof(struct grp_names_head);
503 break;
504 case GT_MLIST:
505 #ifdef HAVE_REGEX
506 if (is_maybe_regex(id)) {
507 addsz = sizeof(struct grp_regex);
508 gt |= GT_REGEX;
509 } else
510 #endif
511 case GT_SHORTCUT:
512 default:
513 break;
516 gp = smalloc(i + addsz);
517 gp->g_subclass_off = i;
518 gp->g_type = gt;
519 memcpy(gp->g_id, id, l);
521 if (gt & GT_ALIAS) {
522 struct grp_names_head *gnhp;
523 GP_TO_SUBCLASS(gnhp, gp);
524 gnhp->gnh_head = NULL;
526 #ifdef HAVE_REGEX
527 else if (/*(gt & GT_MLIST) &&*/ gt & GT_REGEX) {
528 struct grp_regex *grp;
529 GP_TO_SUBCLASS(grp, gp);
531 if (regcomp(&grp->gr_regex, id, REG_EXTENDED | REG_ICASE | REG_NOSUB)) {
532 n_err(_("Invalid regular expression: \"%s\"\n"), id);
533 free(gp);
534 gp = NULL;
535 goto jleave;
537 grp->gr_mygroup = gp;
538 _mlmux_linkin(gp);
540 #endif
542 gp->g_next = *gl.gl_slot;
543 *gl.gl_slot = gp;
544 jleave:
545 NYD_LEAVE;
546 return gp;
549 static bool_t
550 _group_del(enum group_type gt, char const *id)
552 enum group_type xgt = gt & GT_MASK;
553 struct group_lookup gl;
554 struct group *gp;
555 NYD_ENTER;
557 /* Delete 'em all? */
558 if (id[0] == '*' && id[1] == '\0') {
559 for (gp = _group_go_first(gt, &gl); gp != NULL;)
560 gp = (gp->g_type & xgt) ? __group_del(&gl) : _group_go_next(&gl);
561 gp = (struct group*)TRU1;
562 } else if ((gp = _group_lookup(gt, &gl, id)) != NULL) {
563 if (gp->g_type & xgt)
564 __group_del(&gl);
565 else
566 gp = NULL;
568 NYD_LEAVE;
569 return (gp != NULL);
572 static struct group *
573 __group_del(struct group_lookup *glp)
575 struct group *x, *gp;
576 NYD_ENTER;
578 /* Overly complicated: link off this node, step ahead to next.. */
579 x = glp->gl_group;
580 if ((gp = glp->gl_slot_last) != NULL) {
581 gp = (gp->g_next = x->g_next);
582 } else {
583 glp->gl_slot_last = NULL;
584 gp = (*glp->gl_slot = x->g_next);
586 if (gp == NULL) {
587 struct group **gpa = glp->gl_htable + HSHSIZE;
589 while (++glp->gl_slot < gpa)
590 if ((gp = *glp->gl_slot) != NULL)
591 break;
594 glp->gl_group = gp;
596 if (x->g_type & GT_ALIAS)
597 __names_del(x);
598 #ifdef HAVE_REGEX
599 else if (/*(x->g_type & GT_MLIST) &&*/ x->g_type & GT_REGEX) {
600 struct grp_regex *grp;
601 GP_TO_SUBCLASS(grp, x);
603 regfree(&grp->gr_regex);
604 _mlmux_linkout(x);
606 #endif
608 free(x);
609 NYD_LEAVE;
610 return gp;
613 static void
614 __names_del(struct group *gp)
616 struct grp_names_head *gnhp;
617 struct grp_names *gnp;
618 NYD_ENTER;
620 GP_TO_SUBCLASS(gnhp, gp);
621 for (gnp = gnhp->gnh_head; gnp != NULL;) {
622 struct grp_names *x = gnp;
623 gnp = gnp->gn_next;
624 free(x);
626 NYD_LEAVE;
629 static void
630 _group_print_all(enum group_type gt)
632 enum group_type xgt;
633 struct group **gpa;
634 struct group const *gp;
635 ui32_t h, i;
636 char const **ida;
637 FILE *fp;
638 size_t lines;
639 NYD_ENTER;
641 xgt = gt & GT_PRINT_MASK;
642 gpa = (xgt & GT_ALIAS ? _alias_heads
643 : (xgt & GT_MLIST ? _mlist_heads : _shortcut_heads));
645 for (h = 0, i = 1; h < HSHSIZE; ++h)
646 for (gp = gpa[h]; gp != NULL; gp = gp->g_next)
647 if ((gp->g_type & xgt) == xgt)
648 ++i;
649 ida = salloc(i * sizeof *ida);
651 for (i = h = 0; h < HSHSIZE; ++h)
652 for (gp = gpa[h]; gp != NULL; gp = gp->g_next)
653 if ((gp->g_type & xgt) == xgt)
654 ida[i++] = gp->g_id;
655 ida[i] = NULL;
657 if (i > 1)
658 qsort(ida, i, sizeof *ida, &__group_print_qsorter);
660 if ((fp = Ftmp(NULL, "prgroup", OF_RDWR | OF_UNLINK | OF_REGISTER, 0600)) ==
661 NULL)
662 fp = stdout;
663 lines = 0;
665 for (i = 0; ida[i] != NULL; ++lines, ++i)
666 _group_print(_group_find(gt, ida[i]), fp);
667 #ifdef HAVE_REGEX
668 if (gt & GT_MLIST) {
669 if (gt & GT_SUBSCRIBE)
670 i = (ui32_t)_mlsub_size, h = (ui32_t)_mlsub_hits;
671 else
672 i = (ui32_t)_mlist_size, h = (ui32_t)_mlist_hits;
673 if (i > 0)
674 fprintf(fp, _("%s list total: %u entries, %u hits\n"),
675 (gt & GT_SUBSCRIBE ? _("Subscribed") : _("Non-subscribed")),
676 i, h);
678 #endif
680 if (fp != stdout) {
681 page_or_print(fp, lines);
682 Fclose(fp);
684 NYD_LEAVE;
687 static int
688 __group_print_qsorter(void const *a, void const *b)
690 int rv;
691 NYD_ENTER;
693 rv = strcmp(*(char**)UNCONST(a), *(char**)UNCONST(b));
694 NYD_LEAVE;
695 return rv;
698 static void
699 _group_print(struct group const *gp, FILE *fo)
701 char const *sep;
702 NYD_ENTER;
704 if (gp->g_type & GT_ALIAS) {
705 struct grp_names_head *gnhp;
706 struct grp_names *gnp;
708 fprintf(fo, "alias %s", gp->g_id);
710 GP_TO_SUBCLASS(gnhp, gp);
711 if ((gnp = gnhp->gnh_head) != NULL) { /* xxx always 1+ entries */
712 do {
713 struct grp_names *x = gnp;
714 gnp = gnp->gn_next;
715 fprintf(fo, " \"%s\"", string_quote(x->gn_id));
716 } while (gnp != NULL);
718 } else if (gp->g_type & GT_MLIST) {
719 fprintf(fo, "%-42s", gp->g_id);
721 sep = " [";
722 if (gp->g_type & GT_SUBSCRIBE) {
723 fprintf(fo, "%ssub", sep);
724 sep = NULL;
726 #ifdef HAVE_REGEX
727 if (gp->g_type & GT_REGEX) {
728 size_t i;
729 struct grp_regex *grp,
730 *lp = (gp->g_type & GT_SUBSCRIBE ? _mlsub_regex : _mlist_regex);
732 GP_TO_SUBCLASS(grp, gp);
733 for (i = 1; lp != grp; lp = lp->gr_next)
734 ++i;
736 fprintf(fo, "%srx: %" PRIuZ "/%" PRIuZ ". hits/sort",
737 (sep != NULL ? sep : ", "), grp->gr_hits, i);
738 sep = NULL;
740 #endif
741 if (sep == NULL)
742 putc(']', fo);
743 } else if (gp->g_type & GT_SHORTCUT) {
744 char const *cp;
745 GP_TO_SUBCLASS(cp, gp);
746 fprintf(fo, "shortcut %s \"%s\"", gp->g_id, string_quote(cp));
749 putc('\n', fo);
750 NYD_LEAVE;
753 static int
754 _mlmux(enum group_type gt, char **argv)
756 struct group *gp;
757 int rv = 0;
758 NYD_ENTER;
760 if (*argv == NULL)
761 _group_print_all(gt);
762 else do {
763 if ((gp = _group_find(gt, *argv)) != NULL) {
764 if (gt & GT_SUBSCRIBE) {
765 if (!(gp->g_type & GT_SUBSCRIBE)) {
766 _MLMUX_LINKOUT(gp);
767 gp->g_type |= GT_SUBSCRIBE;
768 _MLMUX_LINKIN(gp);
769 } else {
770 n_err(_("Mailing-list already `mlsubscribe'd: %s\n"),
771 *argv);
772 rv = 1;
774 } else {
775 n_err(_("Mailing-list already `mlist'ed: %s\n"), *argv);
776 rv = 1;
778 } else
779 _group_fetch(gt, *argv, 0);
780 } while (*++argv != NULL);
782 NYD_LEAVE;
783 return rv;
786 static int
787 _unmlmux(enum group_type gt, char **argv)
789 struct group *gp;
790 int rv = 0;
791 NYD_ENTER;
793 for (; *argv != NULL; ++argv) {
794 if (gt & GT_SUBSCRIBE) {
795 struct group_lookup gl;
796 bool_t isaster;
798 if (!(isaster = (**argv == '*')))
799 gp = _group_find(gt, *argv);
800 else if ((gp = _group_go_first(gt, &gl)) == NULL)
801 continue;
802 else if (gp != NULL && !(gp->g_type & GT_SUBSCRIBE))
803 goto jaster_entry;
805 if (gp != NULL) {
806 jaster_redo:
807 if (gp->g_type & GT_SUBSCRIBE) {
808 _MLMUX_LINKOUT(gp);
809 gp->g_type &= ~GT_SUBSCRIBE;
810 _MLMUX_LINKIN(gp);
811 if (isaster) {
812 jaster_entry:
813 while ((gp = _group_go_next(&gl)) != NULL &&
814 !(gp->g_type & GT_SUBSCRIBE))
816 if (gp != NULL)
817 goto jaster_redo;
819 } else {
820 n_err(_("Mailing-list not `mlsubscribe'd: \"%s\"\n"), *argv);
821 rv = 1;
823 continue;
825 } else if (_group_del(gt, *argv))
826 continue;
827 n_err(_("No such mailing-list: \"%s\"\n"), *argv);
828 rv = 1;
830 NYD_LEAVE;
831 return rv;
834 #ifdef HAVE_REGEX
835 static void
836 _mlmux_linkin(struct group *gp)
838 struct grp_regex **lpp, *grp, *lhp;
839 NYD_ENTER;
841 if (gp->g_type & GT_SUBSCRIBE) {
842 lpp = &_mlsub_regex;
843 ++_mlsub_size;
844 } else {
845 lpp = &_mlist_regex;
846 ++_mlist_size;
849 GP_TO_SUBCLASS(grp, gp);
850 if ((lhp = *lpp) != NULL) {
851 (grp->gr_last = lhp->gr_last)->gr_next = grp;
852 (grp->gr_next = lhp)->gr_last = grp;
853 } else
854 *lpp = grp->gr_last = grp->gr_next = grp;
855 grp->gr_hits = 0;
856 NYD_LEAVE;
859 static void
860 _mlmux_linkout(struct group *gp)
862 struct grp_regex *grp, **lpp;
863 NYD_ENTER;
865 GP_TO_SUBCLASS(grp, gp);
867 if (gp->g_type & GT_SUBSCRIBE) {
868 lpp = &_mlsub_regex;
869 --_mlsub_size;
870 _mlsub_hits -= grp->gr_hits;
871 } else {
872 lpp = &_mlist_regex;
873 --_mlist_size;
874 _mlist_hits -= grp->gr_hits;
877 if (grp->gr_next == grp)
878 *lpp = NULL;
879 else {
880 (grp->gr_last->gr_next = grp->gr_next)->gr_last = grp->gr_last;
881 if (*lpp == grp)
882 *lpp = grp->gr_next;
884 NYD_LEAVE;
886 #endif /* HAVE_REGEX */
888 FL struct name *
889 nalloc(char const *str, enum gfield ntype)
891 struct addrguts ag;
892 struct str in, out;
893 struct name *np;
894 NYD_ENTER;
895 assert(!(ntype & GFULLEXTRA) || (ntype & GFULL) != 0);
897 addrspec_with_guts(((ntype & (GFULL | GSKIN | GREF)) != 0), str, &ag);
898 if (!(ag.ag_n_flags & NAME_NAME_SALLOC)) {
899 ag.ag_n_flags |= NAME_NAME_SALLOC;
900 np = salloc(sizeof(*np) + ag.ag_slen +1);
901 memcpy(np + 1, ag.ag_skinned, ag.ag_slen +1);
902 ag.ag_skinned = (char*)(np + 1);
903 } else
904 np = salloc(sizeof *np);
906 np->n_flink = NULL;
907 np->n_blink = NULL;
908 np->n_type = ntype;
909 np->n_flags = 0;
911 np->n_fullname = np->n_name = ag.ag_skinned;
912 np->n_fullextra = NULL;
913 np->n_flags = ag.ag_n_flags;
915 if (ntype & GFULL) {
916 if (ag.ag_ilen == ag.ag_slen
917 #ifdef HAVE_IDNA
918 && !(ag.ag_n_flags & NAME_IDNA)
919 #endif
921 goto jleave;
922 if (ag.ag_n_flags & NAME_ADDRSPEC_ISFILEORPIPE)
923 goto jleave;
925 /* n_fullextra is only the complete name part without address.
926 * Beware of "-r '<abc@def>'", don't treat that as FULLEXTRA */
927 if ((ntype & GFULLEXTRA) && ag.ag_ilen > ag.ag_slen + 2) {
928 size_t s = ag.ag_iaddr_start, e = ag.ag_iaddr_aend, i;
929 char const *cp;
931 if (s == 0 || str[--s] != '<' || str[e++] != '>')
932 goto jskipfullextra;
933 i = ag.ag_ilen - e;
934 in.s = ac_alloc(s + i +1);
935 memcpy(in.s, str, s);
936 if (i > 0)
937 memcpy(in.s + s, str + e, i);
938 s += i;
939 in.s[in.l = s] = '\0';
940 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
942 for (cp = out.s, i = out.l; i > 0 && spacechar(*cp); --i, ++cp)
944 while (i > 0 && spacechar(cp[i - 1]))
945 --i;
946 np->n_fullextra = savestrbuf(cp, i);
948 free(out.s);
949 ac_free(in.s);
951 jskipfullextra:
953 /* n_fullname depends on IDNA conversion */
954 #ifdef HAVE_IDNA
955 if (!(ag.ag_n_flags & NAME_IDNA)) {
956 #endif
957 in.s = UNCONST(str);
958 in.l = ag.ag_ilen;
959 #ifdef HAVE_IDNA
960 } else {
961 /* The domain name was IDNA and has been converted. We also have to
962 * ensure that the domain name in .n_fullname is replaced with the
963 * converted version, since MIME doesn't perform encoding of addrs */
964 size_t l = ag.ag_iaddr_start,
965 lsuff = ag.ag_ilen - ag.ag_iaddr_aend;
966 in.s = ac_alloc(l + ag.ag_slen + lsuff +1);
967 memcpy(in.s, str, l);
968 memcpy(in.s + l, ag.ag_skinned, ag.ag_slen);
969 l += ag.ag_slen;
970 memcpy(in.s + l, str + ag.ag_iaddr_aend, lsuff);
971 l += lsuff;
972 in.s[l] = '\0';
973 in.l = l;
975 #endif
976 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
977 np->n_fullname = savestr(out.s);
978 free(out.s);
979 #ifdef HAVE_IDNA
980 if (ag.ag_n_flags & NAME_IDNA)
981 ac_free(in.s);
982 #endif
983 np->n_flags |= NAME_FULLNAME_SALLOC;
985 jleave:
986 NYD_LEAVE;
987 return np;
990 FL struct name *
991 ndup(struct name *np, enum gfield ntype)
993 struct name *nnp;
994 NYD_ENTER;
996 if ((ntype & (GFULL | GSKIN)) && !(np->n_flags & NAME_SKINNED)) {
997 nnp = nalloc(np->n_name, ntype);
998 goto jleave;
1001 nnp = salloc(sizeof *np);
1002 nnp->n_flink = nnp->n_blink = NULL;
1003 nnp->n_type = ntype;
1004 nnp->n_flags = (np->n_flags & ~(NAME_NAME_SALLOC | NAME_FULLNAME_SALLOC)) |
1005 NAME_NAME_SALLOC;
1006 nnp->n_name = savestr(np->n_name);
1007 if (np->n_name == np->n_fullname || !(ntype & (GFULL | GSKIN))) {
1008 nnp->n_fullname = nnp->n_name;
1009 nnp->n_fullextra = NULL;
1010 } else {
1011 nnp->n_flags |= NAME_FULLNAME_SALLOC;
1012 nnp->n_fullname = savestr(np->n_fullname);
1013 nnp->n_fullextra = (np->n_fullextra == NULL) ? NULL
1014 : savestr(np->n_fullextra);
1016 jleave:
1017 NYD_LEAVE;
1018 return nnp;
1021 FL struct name *
1022 cat(struct name *n1, struct name *n2)
1024 struct name *tail;
1025 NYD_ENTER;
1027 tail = n2;
1028 if (n1 == NULL)
1029 goto jleave;
1030 tail = n1;
1031 if (n2 == NULL)
1032 goto jleave;
1034 while (tail->n_flink != NULL)
1035 tail = tail->n_flink;
1036 tail->n_flink = n2;
1037 n2->n_blink = tail;
1038 tail = n1;
1039 jleave:
1040 NYD_LEAVE;
1041 return tail;
1044 FL struct name *
1045 namelist_dup(struct name const *np, enum gfield ntype)
1047 struct name *nnp;
1048 NYD_ENTER;
1050 for (nnp = NULL; np != NULL; np = np->n_flink) {
1051 struct name *x = ndup(UNCONST(np), (np->n_type & ~GMASK) | ntype);
1052 x->n_flink = nnp;
1053 nnp = x;
1055 NYD_LEAVE;
1056 return nnp;
1059 FL ui32_t
1060 count(struct name const *np)
1062 ui32_t c;
1063 NYD_ENTER;
1065 for (c = 0; np != NULL; np = np->n_flink)
1066 if (!(np->n_type & GDEL))
1067 ++c;
1068 NYD_LEAVE;
1069 return c;
1072 FL ui32_t
1073 count_nonlocal(struct name const *np)
1075 ui32_t c;
1076 NYD_ENTER;
1078 for (c = 0; np != NULL; np = np->n_flink)
1079 if (!(np->n_type & GDEL) && !(np->n_flags & NAME_ADDRSPEC_ISFILEORPIPE))
1080 ++c;
1081 NYD_LEAVE;
1082 return c;
1085 FL struct name *
1086 extract(char const *line, enum gfield ntype)
1088 struct name *rv;
1089 NYD_ENTER;
1091 rv = _extract1(line, ntype, " \t,", 0);
1092 NYD_LEAVE;
1093 return rv;
1096 FL struct name *
1097 lextract(char const *line, enum gfield ntype)
1099 struct name *rv;
1100 NYD_ENTER;
1102 rv = ((line != NULL && strpbrk(line, ",\"\\(<|"))
1103 ? _extract1(line, ntype, ",", 1) : extract(line, ntype));
1104 NYD_LEAVE;
1105 return rv;
1108 FL char *
1109 detract(struct name *np, enum gfield ntype)
1111 char *topp, *cp;
1112 struct name *p;
1113 int comma, s;
1114 NYD_ENTER;
1116 topp = NULL;
1117 if (np == NULL)
1118 goto jleave;
1120 comma = ntype & GCOMMA;
1121 ntype &= ~GCOMMA;
1122 s = 0;
1123 if ((options & OPT_DEBUG) && comma)
1124 n_err(_("detract() asked to insert commas\n"));
1125 for (p = np; p != NULL; p = p->n_flink) {
1126 if (ntype && (p->n_type & GMASK) != ntype)
1127 continue;
1128 s += strlen(p->n_fullname) +1;
1129 if (comma)
1130 s++;
1132 if (s == 0)
1133 goto jleave;
1135 s += 2;
1136 topp = salloc(s);
1137 cp = topp;
1138 for (p = np; p != NULL; p = p->n_flink) {
1139 if (ntype && (p->n_type & GMASK) != ntype)
1140 continue;
1141 cp = sstpcpy(cp, p->n_fullname);
1142 if (comma && p->n_flink != NULL)
1143 *cp++ = ',';
1144 *cp++ = ' ';
1146 *--cp = 0;
1147 if (comma && *--cp == ',')
1148 *cp = 0;
1149 jleave:
1150 NYD_LEAVE;
1151 return topp;
1154 FL struct name *
1155 grab_names(char const *field, struct name *np, int comma, enum gfield gflags)
1157 struct name *nq;
1158 NYD_ENTER;
1160 jloop:
1161 np = lextract(n_input_cp_addhist(field, detract(np, comma), TRU1), gflags);
1162 for (nq = np; nq != NULL; nq = nq->n_flink)
1163 if (is_addr_invalid(nq, EACM_NONE))
1164 goto jloop;
1165 NYD_LEAVE;
1166 return np;
1169 FL bool_t
1170 name_is_same_domain(struct name const *n1, struct name const *n2)
1172 char const *d1, *d2;
1173 bool_t rv;
1174 NYD_ENTER;
1176 d1 = strrchr(n1->n_name, '@');
1177 d2 = strrchr(n2->n_name, '@');
1179 rv = (d1 != NULL && d2 != NULL) ? !asccasecmp(++d1, ++d2) : FAL0;
1181 NYD_LEAVE;
1182 return rv;
1185 FL struct name *
1186 checkaddrs(struct name *np, enum expand_addr_check_mode eacm,
1187 si8_t *set_on_error)
1189 struct name *n;
1190 NYD_ENTER;
1192 for (n = np; n != NULL;) {
1193 si8_t rv;
1195 if ((rv = is_addr_invalid(n, eacm)) != 0) {
1196 if (set_on_error != NULL)
1197 *set_on_error |= rv; /* don't loose -1! */
1198 if (n->n_blink)
1199 n->n_blink->n_flink = n->n_flink;
1200 if (n->n_flink)
1201 n->n_flink->n_blink = n->n_blink;
1202 if (n == np)
1203 np = n->n_flink;
1205 n = n->n_flink;
1207 NYD_LEAVE;
1208 return np;
1211 FL struct name *
1212 namelist_vaporise_head(struct header *hp, enum expand_addr_check_mode eacm,
1213 bool_t metoo, si8_t *set_on_error)
1215 struct name *tolist, *np, **npp;
1216 NYD_ENTER;
1218 tolist = usermap(cat(hp->h_to, cat(hp->h_cc, hp->h_bcc)), metoo);
1219 hp->h_to = hp->h_cc = hp->h_bcc = NULL;
1221 tolist = elide(checkaddrs(tolist, eacm, set_on_error));
1223 for (np = tolist; np != NULL; np = np->n_flink) {
1224 switch (np->n_type & (GDEL | GMASK)) {
1225 case GTO: npp = &hp->h_to; break;
1226 case GCC: npp = &hp->h_cc; break;
1227 case GBCC: npp = &hp->h_bcc; break;
1228 default: continue;
1230 *npp = cat(*npp, ndup(np, np->n_type | GFULL));
1232 NYD_LEAVE;
1233 return tolist;
1236 FL struct name *
1237 usermap(struct name *names, bool_t force_metoo)
1239 struct name *new, *np, *cp;
1240 struct group *gp;
1241 int metoo;
1242 NYD_ENTER;
1244 new = NULL;
1245 np = names;
1246 metoo = (force_metoo || ok_blook(metoo));
1247 while (np != NULL) {
1248 assert(!(np->n_type & GDEL)); /* TODO legacy */
1249 if (is_fileorpipe_addr(np) || np->n_name[0] == '\\') {
1250 cp = np->n_flink;
1251 new = put(new, np);
1252 np = cp;
1253 continue;
1255 gp = _group_find(GT_ALIAS, np->n_name);
1256 cp = np->n_flink;
1257 if (gp != NULL)
1258 new = _gexpand(0, new, gp, metoo, np->n_type);
1259 else
1260 new = put(new, np);
1261 np = cp;
1263 NYD_LEAVE;
1264 return new;
1267 FL struct name *
1268 elide(struct name *names)
1270 struct name *np, *t, *newn, *x;
1271 NYD_ENTER;
1273 newn = NULL;
1274 if (names == NULL)
1275 goto jleave;
1277 /* Throw away all deleted nodes (XXX merge with plain sort below?) */
1278 for (np = NULL; names != NULL; names = names->n_flink)
1279 if (!(names->n_type & GDEL)) {
1280 names->n_blink = np;
1281 if (np)
1282 np->n_flink = names;
1283 else
1284 newn = names;
1285 np = names;
1287 if (newn == NULL)
1288 goto jleave;
1290 np = newn->n_flink;
1291 if (np != NULL)
1292 np->n_blink = NULL;
1293 newn->n_flink = NULL;
1295 while (np != NULL) {
1296 int cmpres;
1298 t = newn;
1299 while ((cmpres = asccasecmp(t->n_name, np->n_name)) < 0) {
1300 if (t->n_flink == NULL)
1301 break;
1302 t = t->n_flink;
1305 /* If we ran out of t's, put new entry after the current value of t */
1306 if (cmpres < 0) {
1307 t->n_flink = np;
1308 np->n_blink = t;
1309 t = np;
1310 np = np->n_flink;
1311 t->n_flink = NULL;
1312 continue;
1315 /* Otherwise, put the new entry in front of the current t. If at the
1316 * front of the list, the new guy becomes the new head of the list */
1317 if (t == newn) {
1318 t = np;
1319 np = np->n_flink;
1320 t->n_flink = newn;
1321 newn->n_blink = t;
1322 t->n_blink = NULL;
1323 newn = t;
1324 continue;
1327 /* The normal case -- we are inserting into the middle of the list */
1328 x = np;
1329 np = np->n_flink;
1330 x->n_flink = t;
1331 x->n_blink = t->n_blink;
1332 t->n_blink->n_flink = x;
1333 t->n_blink = x;
1336 /* Now the list headed up by new is sorted. Remove duplicates */
1337 np = newn;
1338 while (np != NULL) {
1339 t = np;
1340 while (t->n_flink != NULL && !asccasecmp(np->n_name, t->n_flink->n_name))
1341 t = t->n_flink;
1342 if (t == np) {
1343 np = np->n_flink;
1344 continue;
1347 /* Now t points to the last entry with the same name as np.
1348 * Make np point beyond t */
1349 np->n_flink = t->n_flink;
1350 if (t->n_flink != NULL)
1351 t->n_flink->n_blink = np;
1352 np = np->n_flink;
1354 jleave:
1355 NYD_LEAVE;
1356 return newn;
1359 FL int
1360 c_alternates(void *v)
1362 size_t l;
1363 char **namelist = v, **ap, **ap2, *cp;
1364 NYD_ENTER;
1366 l = argcount(namelist) +1;
1368 if (l == 1) {
1369 if (_altnames != NULL) {
1370 printf("alternates ");
1371 for (ap = _altnames; *ap != NULL; ++ap)
1372 printf("%s ", *ap);
1373 printf("\n");
1375 goto jleave;
1378 if (_altnames != NULL) {
1379 for (ap = _altnames; *ap != NULL; ++ap)
1380 free(*ap);
1381 free(_altnames);
1384 _altnames = smalloc(l * sizeof(*_altnames));
1385 for (ap = namelist, ap2 = _altnames; *ap != NULL; ++ap, ++ap2) {
1386 l = strlen(*ap) +1;
1387 cp = smalloc(l);
1388 memcpy(cp, *ap, l);
1389 *ap2 = cp;
1391 *ap2 = NULL;
1392 jleave:
1393 NYD_LEAVE;
1394 return 0;
1397 FL struct name *
1398 delete_alternates(struct name *np)
1400 struct name *xp;
1401 char **ap;
1402 NYD_ENTER;
1404 np = delname(np, myname);
1405 if (_altnames != NULL)
1406 for (ap = _altnames; *ap != '\0'; ++ap)
1407 np = delname(np, *ap);
1409 if ((xp = lextract(ok_vlook(from), GEXTRA | GSKIN)) != NULL)
1410 while (xp != NULL) {
1411 np = delname(np, xp->n_name);
1412 xp = xp->n_flink;
1415 if ((xp = lextract(ok_vlook(replyto), GEXTRA | GSKIN)) != NULL)
1416 while (xp != NULL) {
1417 np = delname(np, xp->n_name);
1418 xp = xp->n_flink;
1421 if ((xp = extract(ok_vlook(sender), GEXTRA | GSKIN)) != NULL)
1422 while (xp != NULL) {
1423 np = delname(np, xp->n_name);
1424 xp = xp->n_flink;
1426 NYD_LEAVE;
1427 return np;
1430 FL int
1431 is_myname(char const *name)
1433 int rv = 1;
1434 struct name *xp;
1435 char **ap;
1436 NYD_ENTER;
1438 if (_same_name(myname, name))
1439 goto jleave;
1440 if (_altnames != NULL)
1441 for (ap = _altnames; *ap != NULL; ++ap)
1442 if (_same_name(*ap, name))
1443 goto jleave;
1445 if ((xp = lextract(ok_vlook(from), GEXTRA | GSKIN)) != NULL)
1446 while (xp != NULL) {
1447 if (_same_name(xp->n_name, name))
1448 goto jleave;
1449 xp = xp->n_flink;
1452 if ((xp = lextract(ok_vlook(replyto), GEXTRA | GSKIN)) != NULL)
1453 while (xp != NULL) {
1454 if (_same_name(xp->n_name, name))
1455 goto jleave;
1456 xp = xp->n_flink;
1459 if ((xp = extract(ok_vlook(sender), GEXTRA | GSKIN)) != NULL)
1460 while (xp != NULL) {
1461 if (_same_name(xp->n_name, name))
1462 goto jleave;
1463 xp = xp->n_flink;
1465 rv = 0;
1466 jleave:
1467 NYD_LEAVE;
1468 return rv;
1471 FL int
1472 c_alias(void *v)
1474 char **argv = v;
1475 struct group *gp;
1476 int rv = 0;
1477 NYD_ENTER;
1479 if (*argv == NULL)
1480 _group_print_all(GT_ALIAS);
1481 else if (argv[1] == NULL) {
1482 if ((gp = _group_find(GT_ALIAS, *argv)) != NULL)
1483 _group_print(gp, stdout);
1484 else {
1485 n_err(_("No such alias: \"%s\"\n"), *argv);
1486 rv = 1;
1488 } else {
1489 struct grp_names_head *gnhp;
1491 gp = _group_fetch(GT_ALIAS, *argv, 0);
1492 GP_TO_SUBCLASS(gnhp, gp);
1494 for (++argv; *argv != NULL; ++argv) {
1495 size_t l = strlen(*argv) +1;
1496 struct grp_names *gnp = smalloc(sizeof(*gnp) -
1497 VFIELD_SIZEOF(struct grp_names, gn_id) + l);
1498 gnp->gn_next = gnhp->gnh_head;
1499 gnhp->gnh_head = gnp;
1500 memcpy(gnp->gn_id, *argv, l);
1502 assert(gnhp->gnh_head != NULL);
1504 NYD_LEAVE;
1505 return rv;
1508 FL int
1509 c_unalias(void *v)
1511 char **argv = v;
1512 int rv = 0;
1513 NYD_ENTER;
1515 do if (!_group_del(GT_ALIAS, *argv)) {
1516 n_err(_("No such alias: \"%s\"\n"), *argv);
1517 rv = 1;
1518 } while (*++argv != NULL);
1519 NYD_LEAVE;
1520 return rv;
1523 FL int
1524 c_mlist(void *v)
1526 int rv;
1527 NYD_ENTER;
1529 rv = _mlmux(GT_MLIST, v);
1530 NYD_LEAVE;
1531 return rv;
1534 FL int
1535 c_unmlist(void *v)
1537 int rv;
1538 NYD_ENTER;
1540 rv = _unmlmux(GT_MLIST, v);
1541 NYD_LEAVE;
1542 return rv;
1545 FL int
1546 c_mlsubscribe(void *v)
1548 int rv;
1549 NYD_ENTER;
1551 rv = _mlmux(GT_MLIST | GT_SUBSCRIBE, v);
1552 NYD_LEAVE;
1553 return rv;
1556 FL int
1557 c_unmlsubscribe(void *v)
1559 int rv;
1560 NYD_ENTER;
1562 rv = _unmlmux(GT_MLIST | GT_SUBSCRIBE, v);
1563 NYD_LEAVE;
1564 return rv;
1567 FL enum mlist_state
1568 is_mlist(char const *name, bool_t subscribed_only)
1570 struct group *gp;
1571 #ifdef HAVE_REGEX
1572 struct grp_regex **lpp, *grp;
1573 bool_t re2;
1574 #endif
1575 enum mlist_state rv;
1576 NYD_ENTER;
1578 gp = _group_find(GT_MLIST, name);
1579 rv = (gp != NULL) ? MLIST_KNOWN : MLIST_OTHER;
1580 if (rv == MLIST_KNOWN) {
1581 if (gp->g_type & GT_SUBSCRIBE)
1582 rv = MLIST_SUBSCRIBED;
1583 else if (subscribed_only)
1584 rv = MLIST_OTHER;
1585 /* Of course, if that is a regular expression it doesn't mean a thing */
1586 #ifdef HAVE_REGEX
1587 if (gp->g_type & GT_REGEX)
1588 rv = MLIST_OTHER;
1589 else
1590 #endif
1591 goto jleave;
1594 /* Not in the hashmap (as something matchable), walk the lists */
1595 #ifdef HAVE_REGEX
1596 re2 = FAL0;
1597 lpp = &_mlsub_regex;
1598 jregex_redo:
1599 if ((grp = *lpp) != NULL) {
1600 do if (regexec(&grp->gr_regex, name, 0,NULL, 0) != REG_NOMATCH) {
1601 /* Relink as the head of this list if the hit count of this group is
1602 * >= 25% of the average hit count */
1603 size_t i;
1604 if (!re2)
1605 i = ++_mlsub_hits / _mlsub_size;
1606 else
1607 i = ++_mlist_hits / _mlist_size;
1608 i >>= 2;
1610 if (++grp->gr_hits >= i && *lpp != grp && grp->gr_next != grp) {
1611 grp->gr_last->gr_next = grp->gr_next;
1612 grp->gr_next->gr_last = grp->gr_last;
1613 (grp->gr_last = (*lpp)->gr_last)->gr_next = grp;
1614 (grp->gr_next = *lpp)->gr_last = grp;
1615 *lpp = grp;
1617 rv = !re2 ? MLIST_SUBSCRIBED : MLIST_KNOWN;
1618 goto jleave;
1619 } while ((grp = grp->gr_next) != *lpp);
1621 if (!re2 && !subscribed_only) {
1622 re2 = TRU1;
1623 lpp = &_mlist_regex;
1624 goto jregex_redo;
1626 assert(rv == MLIST_OTHER);
1627 #endif
1629 jleave:
1630 NYD_LEAVE;
1631 return rv;
1634 FL int
1635 c_shortcut(void *v)
1637 char **argv = v;
1638 int rv = 0;
1639 NYD_ENTER;
1641 if (*argv == NULL)
1642 _group_print_all(GT_SHORTCUT);
1643 else for (; *argv != NULL; argv += 2) {
1644 /* Because one hardly ever redefines, anything is stored in one chunk */
1645 size_t l;
1646 struct group *gp;
1647 char *cp;
1649 if (argv[1] == NULL) {
1650 n_err(_("Shortcut expansion is missing: \"%s\"\n"), *argv);
1651 rv = 1;
1652 break;
1654 if (_group_find(GT_SHORTCUT, *argv) != NULL)
1655 _group_del(GT_SHORTCUT, *argv);
1657 l = strlen(argv[1]) +1;
1658 gp = _group_fetch(GT_SHORTCUT, *argv, l);
1659 GP_TO_SUBCLASS(cp, gp);
1660 memcpy(cp, argv[1], l);
1662 NYD_LEAVE;
1663 return rv;
1666 FL int
1667 c_unshortcut(void *v)
1669 char **argv = v;
1670 int rv = 0;
1671 NYD_ENTER;
1673 do if (!_group_del(GT_SHORTCUT, *argv)) {
1674 n_err(_("No such shortcut: \"%s\"\n"), *argv);
1675 rv = 1;
1676 } while (*++argv != NULL);
1677 NYD_LEAVE;
1678 return rv;
1681 FL char const *
1682 shortcut_expand(char const *str)
1684 struct group *gp;
1685 NYD_ENTER;
1687 if ((gp = _group_find(GT_SHORTCUT, str)) != NULL)
1688 GP_TO_SUBCLASS(str, gp);
1689 else
1690 str = NULL;
1691 NYD_LEAVE;
1692 return str;
1695 /* s-it-mode */