Only support sh(1)-style quoting for some new commands..
[s-mailx.git] / nam_a_grp.c
blob2ce6b0db722ba4f34cfc52b78dee642e080795e4
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_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[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*)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[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 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 lastsp = 0;
294 continue;
296 if (c == '"') {
297 if (lc != '\\')
298 inquote = !inquote;
299 #if 0 /* TODO when doing real RFC 5322 parsers - why have i done this? */
300 else
301 --wp;
302 #endif
303 goto jwpwc;
305 if (inquote || lc == '\\') {
306 jwpwc:
307 *wp++ = c;
308 lastsp = 0;
309 continue;
311 if (c == '(') {
312 ap = cp;
313 cp = skip_comment(cp + 1);
314 if (keepcomms)
315 while (ap < cp)
316 *wp++ = *ap++;
317 --cp;
318 lastsp = 0;
319 continue;
321 if (strchr(separators, c) != NULL)
322 break;
324 lc = lastsp;
325 lastsp = blankchar(c);
326 if (!lastsp || !lc)
327 *wp++ = c;
329 if (blankchar(lc))
330 --wp;
332 *wp = '\0';
333 jleave:
334 NYD_LEAVE;
335 return cp;
338 static struct name *
339 _extract1(char const *line, enum gfield ntype, char const *separators,
340 bool_t keepcomms)
342 struct name *topp, *np, *t;
343 char const *cp;
344 char *nbuf;
345 NYD_ENTER;
347 topp = NULL;
348 if (line == NULL || *line == '\0')
349 goto jleave;
351 np = NULL;
352 cp = line;
353 nbuf = smalloc(strlen(line) +1);
354 while ((cp = yankname(cp, nbuf, separators, keepcomms)) != NULL) {
355 t = nalloc(nbuf, ntype);
356 if (topp == NULL)
357 topp = t;
358 else
359 np->n_flink = t;
360 t->n_blink = np;
361 np = t;
363 free(nbuf);
364 jleave:
365 NYD_LEAVE;
366 return topp;
369 static struct name *
370 _gexpand(size_t level, struct name *nlist, struct group *gp, bool_t metoo,
371 int ntype)
373 struct grp_names_head *gnhp;
374 struct grp_names *gnp;
375 NYD_ENTER;
377 if (UICMP(z, level++, >, MAXEXP)) {
378 n_err(_("Expanding alias to depth larger than %d\n"), MAXEXP);
379 goto jleave;
382 GP_TO_SUBCLASS(gnhp, gp);
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, myname))
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, myname))
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 : _customhdr_heads))) +
426 torek_hash(id) % HSHSIZE));
428 for (; gp != NULL; lgp = gp, gp = gp->g_next)
429 if ((gp->g_type & gt) && *gp->g_id == *id && !strcmp(gp->g_id, id))
430 break;
432 glp->gl_slot_last = lgp;
433 glp->gl_group = gp;
434 NYD_LEAVE;
435 return gp;
438 static struct group *
439 _group_find(enum group_type gt, char const *id)
441 struct group_lookup gl;
442 struct group *gp;
443 NYD_ENTER;
445 gp = _group_lookup(gt, &gl, id);
446 NYD_LEAVE;
447 return gp;
450 static struct group *
451 _group_go_first(enum group_type gt, struct group_lookup *glp)
453 struct group **gpa, *gp;
454 size_t i;
455 NYD_ENTER;
457 for (glp->gl_htable = gpa = (gt & GT_ALIAS ? _alias_heads :
458 (gt & GT_MLIST ? _mlist_heads :
459 (gt & GT_SHORTCUT ? _shortcut_heads : _customhdr_heads))), i = 0;
460 i < HSHSIZE; ++gpa, ++i)
461 if ((gp = *gpa) != NULL) {
462 glp->gl_slot = gpa;
463 glp->gl_group = gp;
464 goto jleave;
467 glp->gl_group = gp = NULL;
468 jleave:
469 glp->gl_slot_last = NULL;
470 NYD_LEAVE;
471 return gp;
474 static struct group *
475 _group_go_next(struct group_lookup *glp)
477 struct group *gp, **gpa;
478 NYD_ENTER;
480 if ((gp = glp->gl_group->g_next) != NULL)
481 glp->gl_slot_last = glp->gl_group;
482 else {
483 glp->gl_slot_last = NULL;
484 for (gpa = glp->gl_htable + HSHSIZE; ++glp->gl_slot < gpa;)
485 if ((gp = *glp->gl_slot) != NULL)
486 break;
488 glp->gl_group = gp;
489 NYD_LEAVE;
490 return gp;
493 static struct group *
494 _group_fetch(enum group_type gt, char const *id, size_t addsz)
496 struct group_lookup gl;
497 struct group *gp;
498 size_t l, i;
499 NYD_ENTER;
501 if ((gp = _group_lookup(gt, &gl, id)) != NULL)
502 goto jleave;
504 l = strlen(id) +1;
505 i = n_ALIGN(sizeof(*gp) - VFIELD_SIZEOF(struct group, g_id) + l);
506 switch (gt & GT_MASK) {
507 case GT_ALIAS:
508 addsz = sizeof(struct grp_names_head);
509 break;
510 case GT_MLIST:
511 #ifdef HAVE_REGEX
512 if (is_maybe_regex(id)) {
513 addsz = sizeof(struct grp_regex);
514 gt |= GT_REGEX;
515 } else
516 #endif
517 case GT_SHORTCUT:
518 default:
519 break;
522 gp = smalloc(i + addsz);
523 gp->g_subclass_off = i;
524 gp->g_type = gt;
525 memcpy(gp->g_id, id, l);
527 if (gt & GT_ALIAS) {
528 struct grp_names_head *gnhp;
529 GP_TO_SUBCLASS(gnhp, gp);
530 gnhp->gnh_head = NULL;
532 #ifdef HAVE_REGEX
533 else if (/*(gt & GT_MLIST) &&*/ gt & GT_REGEX) {
534 struct grp_regex *grp;
535 GP_TO_SUBCLASS(grp, gp);
537 if (regcomp(&grp->gr_regex, id, REG_EXTENDED | REG_ICASE | REG_NOSUB)) {
538 n_err(_("Invalid regular expression: \"%s\"\n"), id);
539 free(gp);
540 gp = NULL;
541 goto jleave;
543 grp->gr_mygroup = gp;
544 _mlmux_linkin(gp);
546 #endif
548 gp->g_next = *gl.gl_slot;
549 *gl.gl_slot = gp;
550 jleave:
551 NYD_LEAVE;
552 return gp;
555 static bool_t
556 _group_del(enum group_type gt, char const *id)
558 enum group_type xgt = gt & GT_MASK;
559 struct group_lookup gl;
560 struct group *gp;
561 NYD_ENTER;
563 /* Delete 'em all? */
564 if (id[0] == '*' && id[1] == '\0') {
565 for (gp = _group_go_first(gt, &gl); gp != NULL;)
566 gp = (gp->g_type & xgt) ? __group_del(&gl) : _group_go_next(&gl);
567 gp = (struct group*)TRU1;
568 } else if ((gp = _group_lookup(gt, &gl, id)) != NULL) {
569 if (gp->g_type & xgt)
570 __group_del(&gl);
571 else
572 gp = NULL;
574 NYD_LEAVE;
575 return (gp != NULL);
578 static struct group *
579 __group_del(struct group_lookup *glp)
581 struct group *x, *gp;
582 NYD_ENTER;
584 /* Overly complicated: link off this node, step ahead to next.. */
585 x = glp->gl_group;
586 if ((gp = glp->gl_slot_last) != NULL) {
587 gp = (gp->g_next = x->g_next);
588 } else {
589 glp->gl_slot_last = NULL;
590 gp = (*glp->gl_slot = x->g_next);
592 if (gp == NULL) {
593 struct group **gpa = glp->gl_htable + HSHSIZE;
595 while (++glp->gl_slot < gpa)
596 if ((gp = *glp->gl_slot) != NULL)
597 break;
600 glp->gl_group = gp;
602 if (x->g_type & GT_ALIAS)
603 __names_del(x);
604 #ifdef HAVE_REGEX
605 else if (/*(x->g_type & GT_MLIST) &&*/ x->g_type & GT_REGEX) {
606 struct grp_regex *grp;
607 GP_TO_SUBCLASS(grp, x);
609 regfree(&grp->gr_regex);
610 _mlmux_linkout(x);
612 #endif
614 free(x);
615 NYD_LEAVE;
616 return gp;
619 static void
620 __names_del(struct group *gp)
622 struct grp_names_head *gnhp;
623 struct grp_names *gnp;
624 NYD_ENTER;
626 GP_TO_SUBCLASS(gnhp, gp);
627 for (gnp = gnhp->gnh_head; gnp != NULL;) {
628 struct grp_names *x = gnp;
629 gnp = gnp->gn_next;
630 free(x);
632 NYD_LEAVE;
635 static void
636 _group_print_all(enum group_type gt)
638 enum group_type xgt;
639 struct group **gpa;
640 struct group const *gp;
641 ui32_t h, i;
642 char const **ida;
643 FILE *fp;
644 size_t lines;
645 NYD_ENTER;
647 xgt = gt & GT_PRINT_MASK;
648 gpa = (xgt & GT_ALIAS ? _alias_heads
649 : (xgt & GT_MLIST ? _mlist_heads
650 : (xgt & GT_SHORTCUT ? _shortcut_heads : _customhdr_heads)));
652 for (h = 0, i = 1; h < HSHSIZE; ++h)
653 for (gp = gpa[h]; gp != NULL; gp = gp->g_next)
654 if ((gp->g_type & xgt) == xgt)
655 ++i;
656 ida = salloc(i * sizeof *ida);
658 for (i = h = 0; h < HSHSIZE; ++h)
659 for (gp = gpa[h]; gp != NULL; gp = gp->g_next)
660 if ((gp->g_type & xgt) == xgt)
661 ida[i++] = gp->g_id;
662 ida[i] = NULL;
664 if (i > 1)
665 qsort(ida, i, sizeof *ida, &__group_print_qsorter);
667 if ((fp = Ftmp(NULL, "prgroup", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL)
668 fp = stdout;
669 lines = 0;
671 for (i = 0; ida[i] != NULL; ++i)
672 lines += _group_print(_group_find(gt, ida[i]), fp);
673 #ifdef HAVE_REGEX
674 if (gt & GT_MLIST) {
675 if (gt & GT_SUBSCRIBE)
676 i = (ui32_t)_mlsub_size, h = (ui32_t)_mlsub_hits;
677 else
678 i = (ui32_t)_mlist_size, h = (ui32_t)_mlist_hits;
679 if (i > 0 && (options & OPT_D_V)){
680 fprintf(fp, _("# %s list regex(7) total: %u entries, %u hits\n"),
681 (gt & GT_SUBSCRIBE ? _("Subscribed") : _("Non-subscribed")),
682 i, h);
683 ++lines;
686 #endif
688 if (fp != stdout) {
689 page_or_print(fp, lines);
690 Fclose(fp);
692 NYD_LEAVE;
695 static int
696 __group_print_qsorter(void const *a, void const *b)
698 int rv;
699 NYD_ENTER;
701 rv = strcmp(*(char**)UNCONST(a), *(char**)UNCONST(b));
702 NYD_LEAVE;
703 return rv;
706 static size_t
707 _group_print(struct group const *gp, FILE *fo)
709 char const *cp;
710 size_t rv;
711 NYD_ENTER;
713 rv = 1;
715 if (gp->g_type & GT_ALIAS) {
716 struct grp_names_head *gnhp;
717 struct grp_names *gnp;
719 fprintf(fo, "alias %s", gp->g_id);
721 GP_TO_SUBCLASS(gnhp, gp);
722 if ((gnp = gnhp->gnh_head) != NULL) { /* xxx always 1+ entries */
723 do {
724 struct grp_names *x = gnp;
725 gnp = gnp->gn_next;
726 fprintf(fo, " \"%s\"", string_quote(x->gn_id));
727 } while (gnp != NULL);
729 } else if (gp->g_type & GT_MLIST) {
730 #ifdef HAVE_REGEX
731 if ((gp->g_type & GT_REGEX) && (options & OPT_D_V)){
732 size_t i;
733 struct grp_regex *grp,
734 *lp = (gp->g_type & GT_SUBSCRIBE ? _mlsub_regex : _mlist_regex);
736 GP_TO_SUBCLASS(grp, gp);
737 for (i = 1; lp != grp; lp = lp->gr_next)
738 ++i;
739 fprintf(fo, "# regex(7): hits %" PRIuZ ", sort %" PRIuZ ".\n ",
740 grp->gr_hits, i);
741 ++rv;
743 #endif
745 fprintf(fo, "wysh %s %s",
746 (gp->g_type & GT_SUBSCRIBE ? "mlsubscribe" : "mlist"),
747 n_shell_quote_cp(gp->g_id));
748 } else if (gp->g_type & GT_SHORTCUT) {
749 GP_TO_SUBCLASS(cp, gp);
750 fprintf(fo, "wysh shortcut %s %s", gp->g_id, n_shell_quote_cp(cp));
751 } else if (gp->g_type & GT_CUSTOMHDR) {
752 GP_TO_SUBCLASS(cp, gp);
753 fprintf(fo, "customhdr %s %s", gp->g_id, n_shell_quote_cp(cp));
756 putc('\n', fo);
757 NYD_LEAVE;
758 return rv;
761 static int
762 _mlmux(enum group_type gt, char **argv)
764 struct group *gp;
765 int rv = 0;
766 NYD_ENTER;
768 if (*argv == NULL)
769 _group_print_all(gt);
770 else do {
771 if ((gp = _group_find(gt, *argv)) != NULL) {
772 if (gt & GT_SUBSCRIBE) {
773 if (!(gp->g_type & GT_SUBSCRIBE)) {
774 _MLMUX_LINKOUT(gp);
775 gp->g_type |= GT_SUBSCRIBE;
776 _MLMUX_LINKIN(gp);
777 } else {
778 n_err(_("Mailing-list already `mlsubscribe'd: %s\n"),
779 *argv);
780 rv = 1;
782 } else {
783 n_err(_("Mailing-list already `mlist'ed: %s\n"), *argv);
784 rv = 1;
786 } else
787 _group_fetch(gt, *argv, 0);
788 } while (*++argv != NULL);
790 NYD_LEAVE;
791 return rv;
794 static int
795 _unmlmux(enum group_type gt, char **argv)
797 struct group *gp;
798 int rv = 0;
799 NYD_ENTER;
801 for (; *argv != NULL; ++argv) {
802 if (gt & GT_SUBSCRIBE) {
803 struct group_lookup gl;
804 bool_t isaster;
806 if (!(isaster = (**argv == '*')))
807 gp = _group_find(gt, *argv);
808 else if ((gp = _group_go_first(gt, &gl)) == NULL)
809 continue;
810 else if (gp != NULL && !(gp->g_type & GT_SUBSCRIBE))
811 goto jaster_entry;
813 if (gp != NULL) {
814 jaster_redo:
815 if (gp->g_type & GT_SUBSCRIBE) {
816 _MLMUX_LINKOUT(gp);
817 gp->g_type &= ~GT_SUBSCRIBE;
818 _MLMUX_LINKIN(gp);
819 if (isaster) {
820 jaster_entry:
821 while ((gp = _group_go_next(&gl)) != NULL &&
822 !(gp->g_type & GT_SUBSCRIBE))
824 if (gp != NULL)
825 goto jaster_redo;
827 } else {
828 n_err(_("Mailing-list not `mlsubscribe'd: \"%s\"\n"), *argv);
829 rv = 1;
831 continue;
833 } else if (_group_del(gt, *argv))
834 continue;
835 n_err(_("No such mailing-list: \"%s\"\n"), *argv);
836 rv = 1;
838 NYD_LEAVE;
839 return rv;
842 #ifdef HAVE_REGEX
843 static void
844 _mlmux_linkin(struct group *gp)
846 struct grp_regex **lpp, *grp, *lhp;
847 NYD_ENTER;
849 if (gp->g_type & GT_SUBSCRIBE) {
850 lpp = &_mlsub_regex;
851 ++_mlsub_size;
852 } else {
853 lpp = &_mlist_regex;
854 ++_mlist_size;
857 GP_TO_SUBCLASS(grp, gp);
858 if ((lhp = *lpp) != NULL) {
859 (grp->gr_last = lhp->gr_last)->gr_next = grp;
860 (grp->gr_next = lhp)->gr_last = grp;
861 } else
862 *lpp = grp->gr_last = grp->gr_next = grp;
863 grp->gr_hits = 0;
864 NYD_LEAVE;
867 static void
868 _mlmux_linkout(struct group *gp)
870 struct grp_regex *grp, **lpp;
871 NYD_ENTER;
873 GP_TO_SUBCLASS(grp, gp);
875 if (gp->g_type & GT_SUBSCRIBE) {
876 lpp = &_mlsub_regex;
877 --_mlsub_size;
878 _mlsub_hits -= grp->gr_hits;
879 } else {
880 lpp = &_mlist_regex;
881 --_mlist_size;
882 _mlist_hits -= grp->gr_hits;
885 if (grp->gr_next == grp)
886 *lpp = NULL;
887 else {
888 (grp->gr_last->gr_next = grp->gr_next)->gr_last = grp->gr_last;
889 if (*lpp == grp)
890 *lpp = grp->gr_next;
892 NYD_LEAVE;
894 #endif /* HAVE_REGEX */
896 FL struct name *
897 nalloc(char *str, enum gfield ntype)
899 struct addrguts ag;
900 struct str in, out;
901 struct name *np;
902 NYD_ENTER;
903 assert(!(ntype & GFULLEXTRA) || (ntype & GFULL) != 0);
905 addrspec_with_guts(((ntype & (GFULL | GSKIN | GREF)) != 0), str, &ag);
906 if (!(ag.ag_n_flags & NAME_NAME_SALLOC)) {
907 ag.ag_n_flags |= NAME_NAME_SALLOC;
908 np = salloc(sizeof(*np) + ag.ag_slen +1);
909 memcpy(np + 1, ag.ag_skinned, ag.ag_slen +1);
910 ag.ag_skinned = (char*)(np + 1);
911 } else
912 np = salloc(sizeof *np);
914 np->n_flink = NULL;
915 np->n_blink = NULL;
916 np->n_type = ntype;
917 np->n_flags = 0;
919 np->n_fullname = np->n_name = ag.ag_skinned;
920 np->n_fullextra = NULL;
921 np->n_flags = ag.ag_n_flags;
923 if (ntype & GFULL) {
924 if (ag.ag_ilen == ag.ag_slen
925 #ifdef HAVE_IDNA
926 && !(ag.ag_n_flags & NAME_IDNA)
927 #endif
929 goto jleave;
930 if (ag.ag_n_flags & NAME_ADDRSPEC_ISFILEORPIPE)
931 goto jleave;
933 /* n_fullextra is only the complete name part without address.
934 * Beware of "-r '<abc@def>'", don't treat that as FULLEXTRA */
935 if ((ntype & GFULLEXTRA) && ag.ag_ilen > ag.ag_slen + 2) {
936 size_t s = ag.ag_iaddr_start, e = ag.ag_iaddr_aend, i;
937 char const *cp;
939 if (s == 0 || str[--s] != '<' || str[e++] != '>')
940 goto jskipfullextra;
941 i = ag.ag_ilen - e;
942 in.s = ac_alloc(s + i +1);
943 memcpy(in.s, str, s);
944 if (i > 0)
945 memcpy(in.s + s, str + e, i);
946 s += i;
947 in.s[in.l = s] = '\0';
948 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
950 for (cp = out.s, i = out.l; i > 0 && spacechar(*cp); --i, ++cp)
952 while (i > 0 && spacechar(cp[i - 1]))
953 --i;
954 np->n_fullextra = savestrbuf(cp, i);
956 free(out.s);
957 ac_free(in.s);
959 jskipfullextra:
961 /* n_fullname depends on IDNA conversion */
962 #ifdef HAVE_IDNA
963 if (!(ag.ag_n_flags & NAME_IDNA)) {
964 #endif
965 in.s = str;
966 in.l = ag.ag_ilen;
967 #ifdef HAVE_IDNA
968 } else {
969 /* The domain name was IDNA and has been converted. We also have to
970 * ensure that the domain name in .n_fullname is replaced with the
971 * converted version, since MIME doesn't perform encoding of addrs */
972 size_t l = ag.ag_iaddr_start,
973 lsuff = ag.ag_ilen - ag.ag_iaddr_aend;
974 in.s = ac_alloc(l + ag.ag_slen + lsuff +1);
975 memcpy(in.s, str, l);
976 memcpy(in.s + l, ag.ag_skinned, ag.ag_slen);
977 l += ag.ag_slen;
978 memcpy(in.s + l, str + ag.ag_iaddr_aend, lsuff);
979 l += lsuff;
980 in.s[l] = '\0';
981 in.l = l;
983 #endif
984 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
985 np->n_fullname = savestr(out.s);
986 free(out.s);
987 #ifdef HAVE_IDNA
988 if (ag.ag_n_flags & NAME_IDNA)
989 ac_free(in.s);
990 #endif
991 np->n_flags |= NAME_FULLNAME_SALLOC;
993 jleave:
994 NYD_LEAVE;
995 return np;
998 FL struct name *
999 ndup(struct name *np, enum gfield ntype)
1001 struct name *nnp;
1002 NYD_ENTER;
1004 if ((ntype & (GFULL | GSKIN)) && !(np->n_flags & NAME_SKINNED)) {
1005 nnp = nalloc(np->n_name, ntype);
1006 goto jleave;
1009 nnp = salloc(sizeof *np);
1010 nnp->n_flink = nnp->n_blink = NULL;
1011 nnp->n_type = ntype;
1012 nnp->n_flags = (np->n_flags & ~(NAME_NAME_SALLOC | NAME_FULLNAME_SALLOC)) |
1013 NAME_NAME_SALLOC;
1014 nnp->n_name = savestr(np->n_name);
1015 if (np->n_name == np->n_fullname || !(ntype & (GFULL | GSKIN))) {
1016 nnp->n_fullname = nnp->n_name;
1017 nnp->n_fullextra = NULL;
1018 } else {
1019 nnp->n_flags |= NAME_FULLNAME_SALLOC;
1020 nnp->n_fullname = savestr(np->n_fullname);
1021 nnp->n_fullextra = (np->n_fullextra == NULL) ? NULL
1022 : savestr(np->n_fullextra);
1024 jleave:
1025 NYD_LEAVE;
1026 return nnp;
1029 FL struct name *
1030 cat(struct name *n1, struct name *n2)
1032 struct name *tail;
1033 NYD_ENTER;
1035 tail = n2;
1036 if (n1 == NULL)
1037 goto jleave;
1038 tail = n1;
1039 if (n2 == NULL)
1040 goto jleave;
1042 while (tail->n_flink != NULL)
1043 tail = tail->n_flink;
1044 tail->n_flink = n2;
1045 n2->n_blink = tail;
1046 tail = n1;
1047 jleave:
1048 NYD_LEAVE;
1049 return tail;
1052 FL struct name *
1053 namelist_dup(struct name const *np, enum gfield ntype)
1055 struct name *nnp;
1056 NYD_ENTER;
1058 for (nnp = NULL; np != NULL; np = np->n_flink) {
1059 struct name *x = ndup(UNCONST(np), (np->n_type & ~GMASK) | ntype);
1060 x->n_flink = nnp;
1061 nnp = x;
1063 NYD_LEAVE;
1064 return nnp;
1067 FL ui32_t
1068 count(struct name const *np)
1070 ui32_t c;
1071 NYD_ENTER;
1073 for (c = 0; np != NULL; np = np->n_flink)
1074 if (!(np->n_type & GDEL))
1075 ++c;
1076 NYD_LEAVE;
1077 return c;
1080 FL ui32_t
1081 count_nonlocal(struct name const *np)
1083 ui32_t c;
1084 NYD_ENTER;
1086 for (c = 0; np != NULL; np = np->n_flink)
1087 if (!(np->n_type & GDEL) && !(np->n_flags & NAME_ADDRSPEC_ISFILEORPIPE))
1088 ++c;
1089 NYD_LEAVE;
1090 return c;
1093 FL struct name *
1094 extract(char const *line, enum gfield ntype)
1096 struct name *rv;
1097 NYD_ENTER;
1099 rv = _extract1(line, ntype, " \t,", 0);
1100 NYD_LEAVE;
1101 return rv;
1104 FL struct name *
1105 lextract(char const *line, enum gfield ntype)
1107 struct name *rv;
1108 NYD_ENTER;
1110 rv = ((line != NULL && strpbrk(line, ",\"\\(<|"))
1111 ? _extract1(line, ntype, ",", 1) : extract(line, ntype));
1112 NYD_LEAVE;
1113 return rv;
1116 FL char *
1117 detract(struct name *np, enum gfield ntype)
1119 char *topp, *cp;
1120 struct name *p;
1121 int flags, s;
1122 NYD_ENTER;
1124 topp = NULL;
1125 if (np == NULL)
1126 goto jleave;
1128 flags = ntype & (GCOMMA | GNAMEONLY);
1129 ntype &= ~(GCOMMA | GNAMEONLY);
1130 s = 0;
1132 for (p = np; p != NULL; p = p->n_flink) {
1133 if (ntype && (p->n_type & GMASK) != ntype)
1134 continue;
1135 s += strlen(flags & GNAMEONLY ? p->n_name : p->n_fullname) +1;
1136 if (flags & GCOMMA)
1137 ++s;
1139 if (s == 0)
1140 goto jleave;
1142 s += 2;
1143 topp = salloc(s);
1144 cp = topp;
1145 for (p = np; p != NULL; p = p->n_flink) {
1146 if (ntype && (p->n_type & GMASK) != ntype)
1147 continue;
1148 cp = sstpcpy(cp, (flags & GNAMEONLY ? p->n_name : p->n_fullname));
1149 if ((flags & GCOMMA) && p->n_flink != NULL)
1150 *cp++ = ',';
1151 *cp++ = ' ';
1153 *--cp = 0;
1154 if ((flags & GCOMMA) && *--cp == ',')
1155 *cp = 0;
1156 jleave:
1157 NYD_LEAVE;
1158 return topp;
1161 FL struct name *
1162 grab_names(char const *field, struct name *np, int comma, enum gfield gflags)
1164 struct name *nq;
1165 NYD_ENTER;
1167 jloop:
1168 np = lextract(n_lex_input_cp_addhist(field, detract(np, comma), TRU1),
1169 gflags);
1170 for (nq = np; nq != NULL; nq = nq->n_flink)
1171 if (is_addr_invalid(nq, EACM_NONE))
1172 goto jloop;
1173 NYD_LEAVE;
1174 return np;
1177 FL bool_t
1178 name_is_same_domain(struct name const *n1, struct name const *n2)
1180 char const *d1, *d2;
1181 bool_t rv;
1182 NYD_ENTER;
1184 d1 = strrchr(n1->n_name, '@');
1185 d2 = strrchr(n2->n_name, '@');
1187 rv = (d1 != NULL && d2 != NULL) ? !asccasecmp(++d1, ++d2) : FAL0;
1189 NYD_LEAVE;
1190 return rv;
1193 FL struct name *
1194 checkaddrs(struct name *np, enum expand_addr_check_mode eacm,
1195 si8_t *set_on_error)
1197 struct name *n;
1198 NYD_ENTER;
1200 for (n = np; n != NULL;) {
1201 si8_t rv;
1203 if ((rv = is_addr_invalid(n, eacm)) != 0) {
1204 if (set_on_error != NULL)
1205 *set_on_error |= rv; /* don't loose -1! */
1206 if (n->n_blink)
1207 n->n_blink->n_flink = n->n_flink;
1208 if (n->n_flink)
1209 n->n_flink->n_blink = n->n_blink;
1210 if (n == np)
1211 np = n->n_flink;
1213 n = n->n_flink;
1215 NYD_LEAVE;
1216 return np;
1219 FL struct name *
1220 namelist_vaporise_head(struct header *hp, enum expand_addr_check_mode eacm,
1221 bool_t metoo, si8_t *set_on_error)
1223 struct name *tolist, *np, **npp;
1224 NYD_ENTER;
1226 tolist = usermap(cat(hp->h_to, cat(hp->h_cc, hp->h_bcc)), metoo);
1227 hp->h_to = hp->h_cc = hp->h_bcc = NULL;
1229 tolist = elide(checkaddrs(tolist, eacm, set_on_error));
1231 for (np = tolist; np != NULL; np = np->n_flink) {
1232 switch (np->n_type & (GDEL | GMASK)) {
1233 case GTO: npp = &hp->h_to; break;
1234 case GCC: npp = &hp->h_cc; break;
1235 case GBCC: npp = &hp->h_bcc; break;
1236 default: continue;
1238 *npp = cat(*npp, ndup(np, np->n_type | GFULL));
1240 NYD_LEAVE;
1241 return tolist;
1244 FL struct name *
1245 usermap(struct name *names, bool_t force_metoo)
1247 struct name *new, *np, *cp;
1248 struct group *gp;
1249 int metoo;
1250 NYD_ENTER;
1252 new = NULL;
1253 np = names;
1254 metoo = (force_metoo || ok_blook(metoo));
1255 while (np != NULL) {
1256 assert(!(np->n_type & GDEL)); /* TODO legacy */
1257 if (is_fileorpipe_addr(np) || np->n_name[0] == '\\') {
1258 cp = np->n_flink;
1259 new = put(new, np);
1260 np = cp;
1261 continue;
1263 gp = _group_find(GT_ALIAS, np->n_name);
1264 cp = np->n_flink;
1265 if (gp != NULL)
1266 new = _gexpand(0, new, gp, metoo, np->n_type);
1267 else
1268 new = put(new, np);
1269 np = cp;
1271 NYD_LEAVE;
1272 return new;
1275 FL struct name *
1276 elide(struct name *names)
1278 struct name *np, *t, *newn, *x;
1279 NYD_ENTER;
1281 newn = NULL;
1282 if (names == NULL)
1283 goto jleave;
1285 /* Throw away all deleted nodes (XXX merge with plain sort below?) */
1286 for (np = NULL; names != NULL; names = names->n_flink)
1287 if (!(names->n_type & GDEL)) {
1288 names->n_blink = np;
1289 if (np)
1290 np->n_flink = names;
1291 else
1292 newn = names;
1293 np = names;
1295 if (newn == NULL)
1296 goto jleave;
1298 np = newn->n_flink;
1299 if (np != NULL)
1300 np->n_blink = NULL;
1301 newn->n_flink = NULL;
1303 while (np != NULL) {
1304 int cmpres;
1306 t = newn;
1307 while ((cmpres = asccasecmp(t->n_name, np->n_name)) < 0) {
1308 if (t->n_flink == NULL)
1309 break;
1310 t = t->n_flink;
1313 /* If we ran out of t's, put new entry after the current value of t */
1314 if (cmpres < 0) {
1315 t->n_flink = np;
1316 np->n_blink = t;
1317 t = np;
1318 np = np->n_flink;
1319 t->n_flink = NULL;
1320 continue;
1323 /* Otherwise, put the new entry in front of the current t. If at the
1324 * front of the list, the new guy becomes the new head of the list */
1325 if (t == newn) {
1326 t = np;
1327 np = np->n_flink;
1328 t->n_flink = newn;
1329 newn->n_blink = t;
1330 t->n_blink = NULL;
1331 newn = t;
1332 continue;
1335 /* The normal case -- we are inserting into the middle of the list */
1336 x = np;
1337 np = np->n_flink;
1338 x->n_flink = t;
1339 x->n_blink = t->n_blink;
1340 t->n_blink->n_flink = x;
1341 t->n_blink = x;
1344 /* Now the list headed up by new is sorted. Remove duplicates */
1345 np = newn;
1346 while (np != NULL) {
1347 t = np;
1348 while (t->n_flink != NULL && !asccasecmp(np->n_name, t->n_flink->n_name))
1349 t = t->n_flink;
1350 if (t == np) {
1351 np = np->n_flink;
1352 continue;
1355 /* Now t points to the last entry with the same name as np.
1356 * Make np point beyond t */
1357 np->n_flink = t->n_flink;
1358 if (t->n_flink != NULL)
1359 t->n_flink->n_blink = np;
1360 np = np->n_flink;
1362 jleave:
1363 NYD_LEAVE;
1364 return newn;
1367 FL int
1368 c_alternates(void *v)
1370 size_t l;
1371 char **namelist = v, **ap, **ap2, *cp;
1372 NYD_ENTER;
1374 l = argcount(namelist) +1;
1376 if (l == 1) {
1377 if (_altnames != NULL) {
1378 printf("alternates ");
1379 for (ap = _altnames; *ap != NULL; ++ap)
1380 printf("%s ", *ap);
1381 printf("\n");
1383 goto jleave;
1386 if (_altnames != NULL) {
1387 for (ap = _altnames; *ap != NULL; ++ap)
1388 free(*ap);
1389 free(_altnames);
1392 _altnames = smalloc(l * sizeof(*_altnames));
1393 for (ap = namelist, ap2 = _altnames; *ap != NULL; ++ap, ++ap2) {
1394 l = strlen(*ap) +1;
1395 cp = smalloc(l);
1396 memcpy(cp, *ap, l);
1397 *ap2 = cp;
1399 *ap2 = NULL;
1400 jleave:
1401 NYD_LEAVE;
1402 return 0;
1405 FL struct name *
1406 delete_alternates(struct name *np)
1408 struct name *xp;
1409 char **ap;
1410 NYD_ENTER;
1412 np = delname(np, myname);
1413 if (_altnames != NULL)
1414 for (ap = _altnames; *ap != '\0'; ++ap)
1415 np = delname(np, *ap);
1417 if ((xp = lextract(ok_vlook(from), GEXTRA | GSKIN)) != NULL)
1418 while (xp != NULL) {
1419 np = delname(np, xp->n_name);
1420 xp = xp->n_flink;
1423 if ((xp = lextract(ok_vlook(replyto), GEXTRA | GSKIN)) != NULL)
1424 while (xp != NULL) {
1425 np = delname(np, xp->n_name);
1426 xp = xp->n_flink;
1429 if ((xp = extract(ok_vlook(sender), GEXTRA | GSKIN)) != NULL)
1430 while (xp != NULL) {
1431 np = delname(np, xp->n_name);
1432 xp = xp->n_flink;
1434 NYD_LEAVE;
1435 return np;
1438 FL int
1439 is_myname(char const *name)
1441 int rv = 1;
1442 struct name *xp;
1443 char **ap;
1444 NYD_ENTER;
1446 if (_same_name(myname, name))
1447 goto jleave;
1448 if (_altnames != NULL)
1449 for (ap = _altnames; *ap != NULL; ++ap)
1450 if (_same_name(*ap, name))
1451 goto jleave;
1453 if ((xp = lextract(ok_vlook(from), GEXTRA | GSKIN)) != NULL)
1454 while (xp != NULL) {
1455 if (_same_name(xp->n_name, name))
1456 goto jleave;
1457 xp = xp->n_flink;
1460 if ((xp = lextract(ok_vlook(replyto), GEXTRA | GSKIN)) != NULL)
1461 while (xp != NULL) {
1462 if (_same_name(xp->n_name, name))
1463 goto jleave;
1464 xp = xp->n_flink;
1467 if ((xp = extract(ok_vlook(sender), GEXTRA | GSKIN)) != NULL)
1468 while (xp != NULL) {
1469 if (_same_name(xp->n_name, name))
1470 goto jleave;
1471 xp = xp->n_flink;
1473 rv = 0;
1474 jleave:
1475 NYD_LEAVE;
1476 return rv;
1479 FL int
1480 c_alias(void *v)
1482 char **argv = v;
1483 struct group *gp;
1484 int rv = 0;
1485 NYD_ENTER;
1487 if (*argv == NULL)
1488 _group_print_all(GT_ALIAS);
1489 else if (argv[1] == NULL) {
1490 if ((gp = _group_find(GT_ALIAS, *argv)) != NULL)
1491 _group_print(gp, stdout);
1492 else {
1493 n_err(_("No such alias: \"%s\"\n"), *argv);
1494 rv = 1;
1496 } else {
1497 struct grp_names_head *gnhp;
1499 gp = _group_fetch(GT_ALIAS, *argv, 0);
1500 GP_TO_SUBCLASS(gnhp, gp);
1502 for (++argv; *argv != NULL; ++argv) {
1503 size_t l = strlen(*argv) +1;
1504 struct grp_names *gnp = smalloc(sizeof(*gnp) -
1505 VFIELD_SIZEOF(struct grp_names, gn_id) + l);
1506 gnp->gn_next = gnhp->gnh_head;
1507 gnhp->gnh_head = gnp;
1508 memcpy(gnp->gn_id, *argv, l);
1510 assert(gnhp->gnh_head != NULL);
1512 NYD_LEAVE;
1513 return rv;
1516 FL int
1517 c_unalias(void *v)
1519 char **argv = v;
1520 int rv = 0;
1521 NYD_ENTER;
1523 do if (!_group_del(GT_ALIAS, *argv)) {
1524 n_err(_("No such alias: \"%s\"\n"), *argv);
1525 rv = 1;
1526 } while (*++argv != NULL);
1527 NYD_LEAVE;
1528 return rv;
1531 FL int
1532 c_mlist(void *v)
1534 int rv;
1535 NYD_ENTER;
1537 rv = _mlmux(GT_MLIST, v);
1538 NYD_LEAVE;
1539 return rv;
1542 FL int
1543 c_unmlist(void *v)
1545 int rv;
1546 NYD_ENTER;
1548 rv = _unmlmux(GT_MLIST, v);
1549 NYD_LEAVE;
1550 return rv;
1553 FL int
1554 c_mlsubscribe(void *v)
1556 int rv;
1557 NYD_ENTER;
1559 rv = _mlmux(GT_MLIST | GT_SUBSCRIBE, v);
1560 NYD_LEAVE;
1561 return rv;
1564 FL int
1565 c_unmlsubscribe(void *v)
1567 int rv;
1568 NYD_ENTER;
1570 rv = _unmlmux(GT_MLIST | GT_SUBSCRIBE, v);
1571 NYD_LEAVE;
1572 return rv;
1575 FL enum mlist_state
1576 is_mlist(char const *name, bool_t subscribed_only)
1578 struct group *gp;
1579 #ifdef HAVE_REGEX
1580 struct grp_regex **lpp, *grp;
1581 bool_t re2;
1582 #endif
1583 enum mlist_state rv;
1584 NYD_ENTER;
1586 gp = _group_find(GT_MLIST, name);
1587 rv = (gp != NULL) ? MLIST_KNOWN : MLIST_OTHER;
1588 if (rv == MLIST_KNOWN) {
1589 if (gp->g_type & GT_SUBSCRIBE)
1590 rv = MLIST_SUBSCRIBED;
1591 else if (subscribed_only)
1592 rv = MLIST_OTHER;
1593 /* Of course, if that is a regular expression it doesn't mean a thing */
1594 #ifdef HAVE_REGEX
1595 if (gp->g_type & GT_REGEX)
1596 rv = MLIST_OTHER;
1597 else
1598 #endif
1599 goto jleave;
1602 /* Not in the hashmap (as something matchable), walk the lists */
1603 #ifdef HAVE_REGEX
1604 re2 = FAL0;
1605 lpp = &_mlsub_regex;
1606 jregex_redo:
1607 if ((grp = *lpp) != NULL) {
1608 do if (regexec(&grp->gr_regex, name, 0,NULL, 0) != REG_NOMATCH) {
1609 /* Relink as the head of this list if the hit count of this group is
1610 * >= 25% of the average hit count */
1611 size_t i;
1612 if (!re2)
1613 i = ++_mlsub_hits / _mlsub_size;
1614 else
1615 i = ++_mlist_hits / _mlist_size;
1616 i >>= 2;
1618 if (++grp->gr_hits >= i && *lpp != grp && grp->gr_next != grp) {
1619 grp->gr_last->gr_next = grp->gr_next;
1620 grp->gr_next->gr_last = grp->gr_last;
1621 (grp->gr_last = (*lpp)->gr_last)->gr_next = grp;
1622 (grp->gr_next = *lpp)->gr_last = grp;
1623 *lpp = grp;
1625 rv = !re2 ? MLIST_SUBSCRIBED : MLIST_KNOWN;
1626 goto jleave;
1627 } while ((grp = grp->gr_next) != *lpp);
1629 if (!re2 && !subscribed_only) {
1630 re2 = TRU1;
1631 lpp = &_mlist_regex;
1632 goto jregex_redo;
1634 assert(rv == MLIST_OTHER);
1635 #endif
1637 jleave:
1638 NYD_LEAVE;
1639 return rv;
1642 FL int
1643 c_shortcut(void *v)
1645 char **argv = v;
1646 int rv = 0;
1647 NYD_ENTER;
1649 if (*argv == NULL)
1650 _group_print_all(GT_SHORTCUT);
1651 else for (; *argv != NULL; argv += 2) {
1652 /* Because one hardly ever redefines, anything is stored in one chunk */
1653 size_t l;
1654 struct group *gp;
1655 char *cp;
1657 if (argv[1] == NULL) {
1658 n_err(_("Shortcut expansion is missing: \"%s\"\n"), *argv);
1659 rv = 1;
1660 break;
1662 if (_group_find(GT_SHORTCUT, *argv) != NULL)
1663 _group_del(GT_SHORTCUT, *argv);
1665 l = strlen(argv[1]) +1;
1666 gp = _group_fetch(GT_SHORTCUT, *argv, l);
1667 GP_TO_SUBCLASS(cp, gp);
1668 memcpy(cp, argv[1], l);
1670 NYD_LEAVE;
1671 return rv;
1674 FL int
1675 c_unshortcut(void *v)
1677 char **argv = v;
1678 int rv = 0;
1679 NYD_ENTER;
1681 do if (!_group_del(GT_SHORTCUT, *argv)) {
1682 n_err(_("No such shortcut: \"%s\"\n"), *argv);
1683 rv = 1;
1684 } while (*++argv != NULL);
1685 NYD_LEAVE;
1686 return rv;
1689 FL char const *
1690 shortcut_expand(char const *str)
1692 struct group *gp;
1693 NYD_ENTER;
1695 if ((gp = _group_find(GT_SHORTCUT, str)) != NULL)
1696 GP_TO_SUBCLASS(str, gp);
1697 else
1698 str = NULL;
1699 NYD_LEAVE;
1700 return str;
1703 FL int
1704 c_customhdr(void *v){
1705 struct group *gp;
1706 char const **argv, *hcp;
1707 int rv;
1708 NYD_ENTER;
1710 rv = 0;
1712 if((hcp = *(argv = v)) == NULL){
1713 _group_print_all(GT_CUSTOMHDR);
1714 goto jleave;
1717 /* Verify the header field name */
1718 for(rv = 1;; ++hcp){
1719 if(fieldnamechar(*hcp))
1720 continue;
1721 if(*hcp == '\0'){
1722 if(hcp == *argv){
1723 n_err(_("Cannot use nameless custom header\n"));
1724 goto jleave;
1726 hcp = *argv;
1727 break;
1729 n_err(_("Invalid custom header name: \"%s\"\n"), *argv);
1730 goto jleave;
1732 rv = 0;
1734 if(*++argv == NULL){
1735 if((gp = _group_find(GT_CUSTOMHDR, hcp)) != NULL)
1736 _group_print(gp, stdout);
1737 else{
1738 n_err(_("No such custom header: \"%s\"\n"), hcp);
1739 rv = 1;
1741 }else{
1742 /* Because one hardly ever redefines, anything is stored in one chunk */
1743 size_t i, l;
1744 char *cp;
1746 if(_group_find(GT_CUSTOMHDR, hcp) != NULL)
1747 _group_del(GT_CUSTOMHDR, hcp);
1749 for(l = i = 0; argv[i] != NULL; ++i)
1750 l += strlen(argv[i]) + 1;
1751 gp = _group_fetch(GT_CUSTOMHDR, hcp, l +1);
1752 GP_TO_SUBCLASS(cp, gp);
1753 for(i = 0; argv[i] != NULL; ++i){
1754 if(i > 0)
1755 *cp++ = ' ';
1756 l = strlen(argv[i]);
1757 memcpy(cp, argv[i], l);
1758 cp += l;
1760 *cp = '\0';
1762 jleave:
1763 NYD_LEAVE;
1764 return rv;
1767 FL int
1768 c_uncustomhdr(void *v){
1769 int rv;
1770 char **argv;
1771 NYD_ENTER;
1773 rv = 0;
1775 argv = v;
1777 if(!_group_del(GT_CUSTOMHDR, *argv)){
1778 n_err(_("No such custom header: \"%s\"\n"), *argv);
1779 rv = 1;
1781 }while(*++argv != NULL);
1782 NYD_LEAVE;
1783 return rv;
1786 FL struct n_header_field *
1787 n_customhdr_query(void){ /* XXX Uses salloc()! */
1788 struct group const *gp;
1789 ui32_t h;
1790 struct n_header_field *rv, **tail, *hfp;
1791 NYD_ENTER;
1793 rv = NULL;
1794 tail = &rv;
1796 for(h = 0; h < HSHSIZE; ++h)
1797 for(gp = _customhdr_heads[h]; gp != NULL; gp = gp->g_next){
1798 char const *cp;
1799 ui32_t nl, bl;
1801 GP_TO_SUBCLASS(cp, gp);
1802 nl = (ui32_t)strlen(gp->g_id) +1;
1803 bl = (ui32_t)strlen(cp) +1;
1805 *tail = hfp = salloc(VSTRUCT_SIZEOF(struct n_header_field, hf_dat) +
1806 nl + bl);
1807 tail = &hfp->hf_next;
1808 hfp->hf_next = NULL;
1809 hfp->hf_nl = nl - 1;
1810 hfp->hf_bl = bl - 1;
1811 memcpy(hfp->hf_dat, gp->g_id, nl);
1812 memcpy(hfp->hf_dat + nl, cp, bl);
1815 /* TODO We have no copy-on-write environments yet, and since custom headers
1816 * TODO are a perfect subject for localization, add the OBSOLETE variable
1817 * TODO *customhdr*, too */
1819 char const *vp = ok_vlook(customhdr);
1821 if(vp != NULL){
1822 char *buf = savestr(vp);
1823 jch_outer:
1824 while((vp = n_strescsep(&buf, ',', TRU1)) != NULL){
1825 ui32_t nl, bl;
1826 char const *nstart, *cp;
1828 for(nstart = cp = vp;; ++cp){
1829 if(fieldnamechar(*cp))
1830 continue;
1831 if(*cp == '\0'){
1832 if(cp == nstart){
1833 n_err(_("Invalid nameless *customhdr* entry\n"));
1834 goto jch_outer;
1836 }else if(*cp != ':' && !blankchar(*cp)){
1837 jch_badent:
1838 n_err(_("Invalid *customhdr* entry: \"%s\"\n"), vp);
1839 goto jch_outer;
1841 break;
1843 nl = (ui32_t)PTR2SIZE(cp - nstart);
1845 while(blankchar(*cp))
1846 ++cp;
1847 if(*cp++ != ':')
1848 goto jch_badent;
1849 while(blankchar(*cp))
1850 ++cp;
1851 bl = (ui32_t)strlen(cp) +1;
1853 *tail = hfp = salloc(VSTRUCT_SIZEOF(struct n_header_field, hf_dat) +
1854 nl +1 + bl);
1855 tail = &hfp->hf_next;
1856 hfp->hf_next = NULL;
1857 hfp->hf_nl = nl;
1858 hfp->hf_bl = bl - 1;
1859 memcpy(hfp->hf_dat, nstart, nl);
1860 hfp->hf_dat[nl++] = '\0';
1861 memcpy(hfp->hf_dat + nl, cp, bl);
1865 NYD_LEAVE;
1866 return rv;
1869 /* s-it-mode */