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>.
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
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
36 #define n_FILE nam_a_grp
38 #ifndef HAVE_AMALGAMATION
43 /* Main types (bits not values for easier testing only) */
47 GT_MASK
= GT_ALIAS
| GT_MLIST
| GT_SHORTCUT
,
49 /* Subtype bits and flags */
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
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) \
70 union __group_subclass {void *gs_vp; char *gs_cp;} __gs__;\
71 __gs__.gs_cp = (char*)UNCONST(G) + (G)->g_subclass_off;\
75 struct grp_names_head
{
76 struct grp_names
*gnh_head
;
80 struct grp_names
*gn_next
;
81 char gn_id
[VFIELD_SIZE(0)];
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 */
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
;
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.. */
119 static struct grp_regex
*_mlist_regex
, *_mlsub_regex
;
120 static size_t _mlist_size
, _mlist_hits
, _mlsub_size
, _mlsub_hits
;
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
,
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 */
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)
194 # define _MLMUX_LINKIN(GP)
195 # define _MLMUX_LINKOUT(GP)
199 _same_name(char const *n1
, char const *n2
)
205 if (ok_blook(allnet
)) {
213 } while (c1
!= '\0' && c2
!= '\0' && c1
!= '@' && c2
!= '@');
216 rv
= !asccasecmp(n1
, n2
);
223 delname(struct name
*np
, char const *name
)
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
;
236 if (p
->n_flink
== NULL
) {
237 if (p
->n_blink
!= NULL
)
238 p
->n_blink
->n_flink
= NULL
;
241 p
->n_blink
->n_flink
= p
->n_flink
;
242 p
->n_flink
->n_blink
= p
->n_blink
;
249 put(struct name
*list
, struct name
*node
)
252 node
->n_flink
= list
;
253 node
->n_blink
= NULL
;
255 list
->n_blink
= node
;
261 yankname(char const *ap
, char *wbuf
, char const *separators
, int keepcomms
)
264 char *wp
, c
, inquote
, lc
, lastsp
;
269 /* Skip over intermediate list trash, as in ".org> , <xy@zz.org>" */
270 for (c
= *ap
; blankchar(c
) || c
== ','; c
= *++ap
)
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) */
284 for (inquote
= lc
= lastsp
= 0;; lc
= c
, ++cp
) {
295 #if 0 /* TODO when doing real RFC 5322 parsers - why have i done this? */
301 if (inquote
|| lc
== '\\') {
309 cp
= skip_comment(cp
+ 1);
317 if (strchr(separators
, c
) != NULL
)
321 lastsp
= blankchar(c
);
335 _extract1(char const *line
, enum gfield ntype
, char const *separators
,
338 struct name
*topp
, *np
, *t
;
344 if (line
== NULL
|| *line
== '\0')
349 nbuf
= smalloc(strlen(line
) +1);
350 while ((cp
= yankname(cp
, nbuf
, separators
, keepcomms
)) != NULL
) {
351 t
= nalloc(nbuf
, ntype
);
366 _gexpand(size_t level
, struct name
*nlist
, struct group
*gp
, bool_t metoo
,
369 struct grp_names_head
*gnhp
;
370 struct grp_names
*gnp
;
373 if (UICMP(z
, level
++, >, MAXEXP
)) {
374 n_err(_("Expanding alias to depth larger than %d\n"), MAXEXP
);
378 GP_TO_SUBCLASS(gnhp
, gp
);
379 for (gnp
= gnhp
->gnh_head
; gnp
!= NULL
; gnp
= gnp
->gn_next
) {
383 /* FIXME we do not really support leading backslash quoting do we??? */
384 if (*(cp
= gnp
->gn_id
) == '\\' || !strcmp(cp
, gp
->g_id
))
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
);
400 /* Here we should allow to expand to itself if only person in alias */
402 if (metoo
|| gnhp
->gnh_head
->gn_next
== NULL
|| !_same_name(cp
, myname
))
403 nlist
= put(nlist
, nalloc(cp
, ntype
| GFULL
));
410 static struct group
*
411 _group_lookup(enum group_type gt
, struct group_lookup
*glp
, char const *id
)
413 struct group
*lgp
, *gp
;
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
))
427 glp
->gl_slot_last
= lgp
;
433 static struct group
*
434 _group_find(enum group_type gt
, char const *id
)
436 struct group_lookup gl
;
440 gp
= _group_lookup(gt
, &gl
, id
);
445 static struct group
*
446 _group_go_first(enum group_type gt
, struct group_lookup
*glp
)
448 struct group
**gpa
, *gp
;
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
) {
461 glp
->gl_group
= gp
= NULL
;
463 glp
->gl_slot_last
= NULL
;
468 static struct group
*
469 _group_go_next(struct group_lookup
*glp
)
471 struct group
*gp
, **gpa
;
474 if ((gp
= glp
->gl_group
->g_next
) != NULL
)
475 glp
->gl_slot_last
= glp
->gl_group
;
477 glp
->gl_slot_last
= NULL
;
478 for (gpa
= glp
->gl_htable
+ HSHSIZE
; ++glp
->gl_slot
< gpa
;)
479 if ((gp
= *glp
->gl_slot
) != NULL
)
487 static struct group
*
488 _group_fetch(enum group_type gt
, char const *id
, size_t addsz
)
490 struct group_lookup gl
;
495 if ((gp
= _group_lookup(gt
, &gl
, id
)) != NULL
)
499 i
= n_ALIGN(sizeof(*gp
) - VFIELD_SIZEOF(struct group
, g_id
) + l
);
500 switch (gt
& GT_MASK
) {
502 addsz
= sizeof(struct grp_names_head
);
506 if (is_maybe_regex(id
)) {
507 addsz
= sizeof(struct grp_regex
);
516 gp
= smalloc(i
+ addsz
);
517 gp
->g_subclass_off
= i
;
519 memcpy(gp
->g_id
, id
, l
);
522 struct grp_names_head
*gnhp
;
523 GP_TO_SUBCLASS(gnhp
, gp
);
524 gnhp
->gnh_head
= NULL
;
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
);
537 grp
->gr_mygroup
= gp
;
542 gp
->g_next
= *gl
.gl_slot
;
550 _group_del(enum group_type gt
, char const *id
)
552 enum group_type xgt
= gt
& GT_MASK
;
553 struct group_lookup gl
;
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
)
572 static struct group
*
573 __group_del(struct group_lookup
*glp
)
575 struct group
*x
, *gp
;
578 /* Overly complicated: link off this node, step ahead to next.. */
580 if ((gp
= glp
->gl_slot_last
) != NULL
) {
581 gp
= (gp
->g_next
= x
->g_next
);
583 glp
->gl_slot_last
= NULL
;
584 gp
= (*glp
->gl_slot
= x
->g_next
);
587 struct group
**gpa
= glp
->gl_htable
+ HSHSIZE
;
589 while (++glp
->gl_slot
< gpa
)
590 if ((gp
= *glp
->gl_slot
) != NULL
)
596 if (x
->g_type
& GT_ALIAS
)
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
);
614 __names_del(struct group
*gp
)
616 struct grp_names_head
*gnhp
;
617 struct grp_names
*gnp
;
620 GP_TO_SUBCLASS(gnhp
, gp
);
621 for (gnp
= gnhp
->gnh_head
; gnp
!= NULL
;) {
622 struct grp_names
*x
= gnp
;
630 _group_print_all(enum group_type gt
)
634 struct group
const *gp
;
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
)
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
)
658 qsort(ida
, i
, sizeof *ida
, &__group_print_qsorter
);
660 if ((fp
= Ftmp(NULL
, "prgroup", OF_RDWR
| OF_UNLINK
| OF_REGISTER
, 0600)) ==
665 for (i
= 0; ida
[i
] != NULL
; ++lines
, ++i
)
666 _group_print(_group_find(gt
, ida
[i
]), fp
);
669 if (gt
& GT_SUBSCRIBE
)
670 i
= (ui32_t
)_mlsub_size
, h
= (ui32_t
)_mlsub_hits
;
672 i
= (ui32_t
)_mlist_size
, h
= (ui32_t
)_mlist_hits
;
674 fprintf(fp
, _("%s list total: %u entries, %u hits\n"),
675 (gt
& GT_SUBSCRIBE
? _("Subscribed") : _("Non-subscribed")),
681 page_or_print(fp
, lines
);
688 __group_print_qsorter(void const *a
, void const *b
)
693 rv
= strcmp(*(char**)UNCONST(a
), *(char**)UNCONST(b
));
699 _group_print(struct group
const *gp
, FILE *fo
)
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 */
713 struct grp_names
*x
= gnp
;
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
);
722 if (gp
->g_type
& GT_SUBSCRIBE
) {
723 fprintf(fo
, "%ssub", sep
);
727 if (gp
->g_type
& GT_REGEX
) {
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
)
736 fprintf(fo
, "%srx: %" PRIuZ
"/%" PRIuZ
". hits/sort",
737 (sep
!= NULL
? sep
: ", "), grp
->gr_hits
, i
);
743 } else if (gp
->g_type
& GT_SHORTCUT
) {
745 GP_TO_SUBCLASS(cp
, gp
);
746 fprintf(fo
, "shortcut %s \"%s\"", gp
->g_id
, string_quote(cp
));
754 _mlmux(enum group_type gt
, char **argv
)
761 _group_print_all(gt
);
763 if ((gp
= _group_find(gt
, *argv
)) != NULL
) {
764 if (gt
& GT_SUBSCRIBE
) {
765 if (!(gp
->g_type
& GT_SUBSCRIBE
)) {
767 gp
->g_type
|= GT_SUBSCRIBE
;
770 n_err(_("Mailing-list already `mlsubscribe'd: %s\n"),
775 n_err(_("Mailing-list already `mlist'ed: %s\n"), *argv
);
779 _group_fetch(gt
, *argv
, 0);
780 } while (*++argv
!= NULL
);
787 _unmlmux(enum group_type gt
, char **argv
)
793 for (; *argv
!= NULL
; ++argv
) {
794 if (gt
& GT_SUBSCRIBE
) {
795 struct group_lookup gl
;
798 if (!(isaster
= (**argv
== '*')))
799 gp
= _group_find(gt
, *argv
);
800 else if ((gp
= _group_go_first(gt
, &gl
)) == NULL
)
802 else if (gp
!= NULL
&& !(gp
->g_type
& GT_SUBSCRIBE
))
807 if (gp
->g_type
& GT_SUBSCRIBE
) {
809 gp
->g_type
&= ~GT_SUBSCRIBE
;
813 while ((gp
= _group_go_next(&gl
)) != NULL
&&
814 !(gp
->g_type
& GT_SUBSCRIBE
))
820 n_err(_("Mailing-list not `mlsubscribe'd: \"%s\"\n"), *argv
);
825 } else if (_group_del(gt
, *argv
))
827 n_err(_("No such mailing-list: \"%s\"\n"), *argv
);
836 _mlmux_linkin(struct group
*gp
)
838 struct grp_regex
**lpp
, *grp
, *lhp
;
841 if (gp
->g_type
& GT_SUBSCRIBE
) {
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
;
854 *lpp
= grp
->gr_last
= grp
->gr_next
= grp
;
860 _mlmux_linkout(struct group
*gp
)
862 struct grp_regex
*grp
, **lpp
;
865 GP_TO_SUBCLASS(grp
, gp
);
867 if (gp
->g_type
& GT_SUBSCRIBE
) {
870 _mlsub_hits
-= grp
->gr_hits
;
874 _mlist_hits
-= grp
->gr_hits
;
877 if (grp
->gr_next
== grp
)
880 (grp
->gr_last
->gr_next
= grp
->gr_next
)->gr_last
= grp
->gr_last
;
886 #endif /* HAVE_REGEX */
889 nalloc(char const *str
, enum gfield ntype
)
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);
904 np
= salloc(sizeof *np
);
911 np
->n_fullname
= np
->n_name
= ag
.ag_skinned
;
912 np
->n_fullextra
= NULL
;
913 np
->n_flags
= ag
.ag_n_flags
;
916 if (ag
.ag_ilen
== ag
.ag_slen
918 && !(ag
.ag_n_flags
& NAME_IDNA
)
922 if (ag
.ag_n_flags
& NAME_ADDRSPEC_ISFILEORPIPE
)
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
;
931 if (s
== 0 || str
[--s
] != '<' || str
[e
++] != '>')
934 in
.s
= ac_alloc(s
+ i
+1);
935 memcpy(in
.s
, str
, s
);
937 memcpy(in
.s
+ s
, str
+ e
, 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]))
946 np
->n_fullextra
= savestrbuf(cp
, i
);
953 /* n_fullname depends on IDNA conversion */
955 if (!(ag
.ag_n_flags
& NAME_IDNA
)) {
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
);
970 memcpy(in
.s
+ l
, str
+ ag
.ag_iaddr_aend
, lsuff
);
976 mime_fromhdr(&in
, &out
, TD_ISPR
| TD_ICONV
);
977 np
->n_fullname
= savestr(out
.s
);
980 if (ag
.ag_n_flags
& NAME_IDNA
)
983 np
->n_flags
|= NAME_FULLNAME_SALLOC
;
991 ndup(struct name
*np
, enum gfield ntype
)
996 if ((ntype
& (GFULL
| GSKIN
)) && !(np
->n_flags
& NAME_SKINNED
)) {
997 nnp
= nalloc(np
->n_name
, ntype
);
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
)) |
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
;
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
);
1022 cat(struct name
*n1
, struct name
*n2
)
1034 while (tail
->n_flink
!= NULL
)
1035 tail
= tail
->n_flink
;
1045 namelist_dup(struct name
const *np
, enum gfield ntype
)
1050 for (nnp
= NULL
; np
!= NULL
; np
= np
->n_flink
) {
1051 struct name
*x
= ndup(UNCONST(np
), (np
->n_type
& ~GMASK
) | ntype
);
1060 count(struct name
const *np
)
1065 for (c
= 0; np
!= NULL
; np
= np
->n_flink
)
1066 if (!(np
->n_type
& GDEL
))
1073 count_nonlocal(struct name
const *np
)
1078 for (c
= 0; np
!= NULL
; np
= np
->n_flink
)
1079 if (!(np
->n_type
& GDEL
) && !(np
->n_flags
& NAME_ADDRSPEC_ISFILEORPIPE
))
1086 extract(char const *line
, enum gfield ntype
)
1091 rv
= _extract1(line
, ntype
, " \t,", 0);
1097 lextract(char const *line
, enum gfield ntype
)
1102 rv
= ((line
!= NULL
&& strpbrk(line
, ",\"\\(<|"))
1103 ? _extract1(line
, ntype
, ",", 1) : extract(line
, ntype
));
1109 detract(struct name
*np
, enum gfield ntype
)
1120 comma
= ntype
& GCOMMA
;
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
)
1128 s
+= strlen(p
->n_fullname
) +1;
1138 for (p
= np
; p
!= NULL
; p
= p
->n_flink
) {
1139 if (ntype
&& (p
->n_type
& GMASK
) != ntype
)
1141 cp
= sstpcpy(cp
, p
->n_fullname
);
1142 if (comma
&& p
->n_flink
!= NULL
)
1147 if (comma
&& *--cp
== ',')
1155 grab_names(char const *field
, struct name
*np
, int comma
, enum gfield gflags
)
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
))
1170 name_is_same_domain(struct name
const *n1
, struct name
const *n2
)
1172 char const *d1
, *d2
;
1176 d1
= strrchr(n1
->n_name
, '@');
1177 d2
= strrchr(n2
->n_name
, '@');
1179 rv
= (d1
!= NULL
&& d2
!= NULL
) ? !asccasecmp(++d1
, ++d2
) : FAL0
;
1186 checkaddrs(struct name
*np
, enum expand_addr_check_mode eacm
,
1187 si8_t
*set_on_error
)
1192 for (n
= np
; n
!= NULL
;) {
1195 if ((rv
= is_addr_invalid(n
, eacm
)) != 0) {
1196 if (set_on_error
!= NULL
)
1197 *set_on_error
|= rv
; /* don't loose -1! */
1199 n
->n_blink
->n_flink
= n
->n_flink
;
1201 n
->n_flink
->n_blink
= n
->n_blink
;
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
;
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;
1230 *npp
= cat(*npp
, ndup(np
, np
->n_type
| GFULL
));
1237 usermap(struct name
*names
, bool_t force_metoo
)
1239 struct name
*new, *np
, *cp
;
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] == '\\') {
1255 gp
= _group_find(GT_ALIAS
, np
->n_name
);
1258 new = _gexpand(0, new, gp
, metoo
, np
->n_type
);
1268 elide(struct name
*names
)
1270 struct name
*np
, *t
, *newn
, *x
;
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
;
1282 np
->n_flink
= names
;
1293 newn
->n_flink
= NULL
;
1295 while (np
!= NULL
) {
1299 while ((cmpres
= asccasecmp(t
->n_name
, np
->n_name
)) < 0) {
1300 if (t
->n_flink
== NULL
)
1305 /* If we ran out of t's, put new entry after the current value of t */
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 */
1327 /* The normal case -- we are inserting into the middle of the list */
1331 x
->n_blink
= t
->n_blink
;
1332 t
->n_blink
->n_flink
= x
;
1336 /* Now the list headed up by new is sorted. Remove duplicates */
1338 while (np
!= NULL
) {
1340 while (t
->n_flink
!= NULL
&& !asccasecmp(np
->n_name
, t
->n_flink
->n_name
))
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
;
1360 c_alternates(void *v
)
1363 char **namelist
= v
, **ap
, **ap2
, *cp
;
1366 l
= argcount(namelist
) +1;
1369 if (_altnames
!= NULL
) {
1370 printf("alternates ");
1371 for (ap
= _altnames
; *ap
!= NULL
; ++ap
)
1378 if (_altnames
!= NULL
) {
1379 for (ap
= _altnames
; *ap
!= NULL
; ++ap
)
1384 _altnames
= smalloc(l
* sizeof(*_altnames
));
1385 for (ap
= namelist
, ap2
= _altnames
; *ap
!= NULL
; ++ap
, ++ap2
) {
1398 delete_alternates(struct name
*np
)
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
);
1415 if ((xp
= lextract(ok_vlook(replyto
), GEXTRA
| GSKIN
)) != NULL
)
1416 while (xp
!= NULL
) {
1417 np
= delname(np
, xp
->n_name
);
1421 if ((xp
= extract(ok_vlook(sender
), GEXTRA
| GSKIN
)) != NULL
)
1422 while (xp
!= NULL
) {
1423 np
= delname(np
, xp
->n_name
);
1431 is_myname(char const *name
)
1438 if (_same_name(myname
, name
))
1440 if (_altnames
!= NULL
)
1441 for (ap
= _altnames
; *ap
!= NULL
; ++ap
)
1442 if (_same_name(*ap
, name
))
1445 if ((xp
= lextract(ok_vlook(from
), GEXTRA
| GSKIN
)) != NULL
)
1446 while (xp
!= NULL
) {
1447 if (_same_name(xp
->n_name
, name
))
1452 if ((xp
= lextract(ok_vlook(replyto
), GEXTRA
| GSKIN
)) != NULL
)
1453 while (xp
!= NULL
) {
1454 if (_same_name(xp
->n_name
, name
))
1459 if ((xp
= extract(ok_vlook(sender
), GEXTRA
| GSKIN
)) != NULL
)
1460 while (xp
!= NULL
) {
1461 if (_same_name(xp
->n_name
, name
))
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
);
1485 n_err(_("No such alias: \"%s\"\n"), *argv
);
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
);
1515 do if (!_group_del(GT_ALIAS
, *argv
)) {
1516 n_err(_("No such alias: \"%s\"\n"), *argv
);
1518 } while (*++argv
!= NULL
);
1529 rv
= _mlmux(GT_MLIST
, v
);
1540 rv
= _unmlmux(GT_MLIST
, v
);
1546 c_mlsubscribe(void *v
)
1551 rv
= _mlmux(GT_MLIST
| GT_SUBSCRIBE
, v
);
1557 c_unmlsubscribe(void *v
)
1562 rv
= _unmlmux(GT_MLIST
| GT_SUBSCRIBE
, v
);
1568 is_mlist(char const *name
, bool_t subscribed_only
)
1572 struct grp_regex
**lpp
, *grp
;
1575 enum mlist_state rv
;
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
)
1585 /* Of course, if that is a regular expression it doesn't mean a thing */
1587 if (gp
->g_type
& GT_REGEX
)
1594 /* Not in the hashmap (as something matchable), walk the lists */
1597 lpp
= &_mlsub_regex
;
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 */
1605 i
= ++_mlsub_hits
/ _mlsub_size
;
1607 i
= ++_mlist_hits
/ _mlist_size
;
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
;
1617 rv
= !re2
? MLIST_SUBSCRIBED
: MLIST_KNOWN
;
1619 } while ((grp
= grp
->gr_next
) != *lpp
);
1621 if (!re2
&& !subscribed_only
) {
1623 lpp
= &_mlist_regex
;
1626 assert(rv
== MLIST_OTHER
);
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 */
1649 if (argv
[1] == NULL
) {
1650 n_err(_("Shortcut expansion is missing: \"%s\"\n"), *argv
);
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
);
1667 c_unshortcut(void *v
)
1673 do if (!_group_del(GT_SHORTCUT
, *argv
)) {
1674 n_err(_("No such shortcut: \"%s\"\n"), *argv
);
1676 } while (*++argv
!= NULL
);
1682 shortcut_expand(char const *str
)
1687 if ((gp
= _group_find(GT_SHORTCUT
, str
)) != NULL
)
1688 GP_TO_SUBCLASS(str
, gp
);