1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Name lists, alternates and groups: aliases, mailing lists, shortcuts.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2017 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
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_CHARSETALIAS
= 1<< 3,
48 GT_MASK
= GT_ALIAS
| GT_MLIST
| GT_SHORTCUT
| GT_CHARSETALIAS
,
50 /* Subtype bits and flags */
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
62 size_t g_subclass_off
; /* of "subclass" in .g_id (if any) */
63 ui8_t g_type
; /* enum group_type */
64 /* Identifying name, of variable size. Dependent on actual "subtype" more
65 * data follows thereafter, but note this is always used (i.e., for regular
66 * expression entries this is still set to the plain string) */
67 char g_id
[n_VFIELD_SIZE(-1)];
69 #define GP_TO_SUBCLASS(X,G) \
71 union __group_subclass {void *gs_vp; char *gs_cp;} __gs__;\
72 __gs__.gs_cp = (char*)n_UNCONST(G) + (G)->g_subclass_off;\
76 struct grp_names_head
{
77 struct grp_names
*gnh_head
;
81 struct grp_names
*gn_next
;
82 char gn_id
[n_VFIELD_SIZE(0)];
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 */
96 struct group
**gl_htable
;
97 struct group
**gl_slot
;
98 struct group
*gl_slot_last
;
99 struct group
*gl_group
;
102 /* List of alternate names of user */
103 static char **a_nag_altnames
;
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.. */
120 static struct grp_regex
*_mlist_regex
, *_mlsub_regex
;
121 static size_t _mlist_size
, _mlist_hits
, _mlsub_size
, _mlsub_hits
;
125 static struct group
*_shortcut_heads
[HSHSIZE
]; /* TODO dynamic hash */
128 static struct group
*_charsetalias_heads
[HSHSIZE
];
130 /* Same name, while taking care for *allnet*? */
131 static bool_t
_same_name(char const *n1
, char const *n2
);
133 /* Delete the given name from a namelist */
134 static struct name
* delname(struct name
*np
, char const *name
);
136 /* Put another node onto a list of names and return the list */
137 static struct name
* put(struct name
*list
, struct name
*node
);
139 /* Grab a single name (liberal name) */
140 static char const * yankname(char const *ap
, char *wbuf
,
141 char const *separators
, int keepcomms
);
143 /* Extraction multiplexer that splits an input line to names */
144 static struct name
* _extract1(char const *line
, enum gfield ntype
,
145 char const *separators
, bool_t keepcomms
);
147 /* Recursively expand a alias name. Limit expansion to some fixed level.
148 * Direct recursion is not expanded for convenience */
149 static struct name
* _gexpand(size_t level
, struct name
*nlist
,
150 struct group
*gp
, bool_t metoo
, int ntype
);
152 /* Lookup a group, return it or NULL, fill in glp anyway */
153 static struct group
* _group_lookup(enum group_type gt
,
154 struct group_lookup
*glp
, char const *id
);
156 /* Easier-to-use wrapper around _group_lookup() */
157 static struct group
* _group_find(enum group_type gt
, char const *id
);
159 /* Iteration: go to the first group, which also inits the iterator. A valid
160 * iterator can be stepped via _next(). A NULL return means no (more) groups
161 * to be iterated exist, in which case only glp->gl_group is set (NULL) */
162 static struct group
* _group_go_first(enum group_type gt
,
163 struct group_lookup
*glp
);
164 static struct group
* _group_go_next(struct group_lookup
*glp
);
166 /* Fetch the group id, create it as necessary */
167 static struct group
* _group_fetch(enum group_type gt
, char const *id
,
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 */
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)
198 # define _MLMUX_LINKIN(GP)
199 # define _MLMUX_LINKOUT(GP)
203 _same_name(char const *n1
, char const *n2
)
209 if (ok_blook(allnet
)) {
217 } while (c1
!= '\0' && c2
!= '\0' && c1
!= '@' && c2
!= '@');
220 rv
= !asccasecmp(n1
, n2
);
227 delname(struct name
*np
, char const *name
)
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
;
240 if (p
->n_flink
== NULL
) {
241 if (p
->n_blink
!= NULL
)
242 p
->n_blink
->n_flink
= NULL
;
245 p
->n_blink
->n_flink
= p
->n_flink
;
246 p
->n_flink
->n_blink
= p
->n_blink
;
253 put(struct name
*list
, struct name
*node
)
256 node
->n_flink
= list
;
257 node
->n_blink
= NULL
;
259 list
->n_blink
= node
;
265 yankname(char const *ap
, char *wbuf
, char const *separators
, int keepcomms
)
268 char *wp
, c
, inquote
, lc
, lastsp
;
273 /* Skip over intermediate list trash, as in ".org> , <xy@zz.org>" */
274 for (c
= *ap
; blankchar(c
) || c
== ','; c
= *++ap
)
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) */
288 for (inquote
= lc
= lastsp
= 0;; lc
= c
, ++cp
) {
297 #if 0 /* TODO when doing real RFC 5322 parsers - why have i done this? */
303 if (inquote
|| lc
== '\\') {
311 cp
= skip_comment(cp
+ 1);
319 if (strchr(separators
, c
) != NULL
)
323 lastsp
= blankchar(c
);
337 _extract1(char const *line
, enum gfield ntype
, char const *separators
,
340 struct name
*topp
, *np
, *t
;
346 if (line
== NULL
|| *line
== '\0')
351 nbuf
= smalloc(strlen(line
) +1);
352 while ((cp
= yankname(cp
, nbuf
, separators
, keepcomms
)) != NULL
) {
353 t
= nalloc(nbuf
, ntype
);
368 _gexpand(size_t level
, struct name
*nlist
, struct group
*gp
, bool_t metoo
,
372 struct grp_names_head
*gnhp
;
373 struct grp_names
*gnp
;
376 if (UICMP(z
, level
++, >, MAXEXP
)) {
377 n_err(_("Expanding alias to depth larger than %d\n"), MAXEXP
);
381 GP_TO_SUBCLASS(gnhp
, gp
);
382 logname
= ok_vlook(LOGNAME
);
383 for (gnp
= gnhp
->gnh_head
; gnp
!= NULL
; gnp
= gnp
->gn_next
) {
387 /* FIXME we do not really support leading backslash quoting do we??? */
388 if (*(cp
= gnp
->gn_id
) == '\\' || !strcmp(cp
, gp
->g_id
))
391 if ((ngp
= _group_find(GT_ALIAS
, cp
)) != NULL
) {
392 /* For S-nail(1), the "alias" may *be* the sender in that a name maps
393 * to a full address specification; aliases cannot be empty */
394 struct grp_names_head
*ngnhp
;
395 GP_TO_SUBCLASS(ngnhp
, ngp
);
397 assert(ngnhp
->gnh_head
!= NULL
);
398 if (metoo
|| ngnhp
->gnh_head
->gn_next
!= NULL
||
399 !_same_name(cp
, logname
))
400 nlist
= _gexpand(level
, nlist
, ngp
, metoo
, ntype
);
404 /* Here we should allow to expand to itself if only person in alias */
406 if (metoo
|| gnhp
->gnh_head
->gn_next
== NULL
|| !_same_name(cp
, logname
))
407 nlist
= put(nlist
, nalloc(cp
, ntype
| GFULL
));
414 static struct group
*
415 _group_lookup(enum group_type gt
, struct group_lookup
*glp
, char const *id
)
417 struct group
*lgp
, *gp
;
422 gp
= *(glp
->gl_htable
= glp
->gl_slot
=
423 &(gt
& GT_ALIAS
? _alias_heads
:
424 (gt
& GT_MLIST
? _mlist_heads
:
425 (gt
& GT_SHORTCUT
? _shortcut_heads
:
426 (gt
& GT_CHARSETALIAS
? _charsetalias_heads
: NULL
)))
427 )[torek_hash(id
) % HSHSIZE
]);
429 for (; gp
!= NULL
; lgp
= gp
, gp
= gp
->g_next
)
430 if ((gp
->g_type
& gt
) && *gp
->g_id
== *id
) {
431 if(!strcmp(gp
->g_id
, id
))
433 if(gt
== GT_CHARSETALIAS
){
434 if(!asccasecmp(gp
->g_id
, id
))
439 glp
->gl_slot_last
= lgp
;
445 static struct group
*
446 _group_find(enum group_type gt
, char const *id
)
448 struct group_lookup gl
;
452 gp
= _group_lookup(gt
, &gl
, id
);
457 static struct group
*
458 _group_go_first(enum group_type gt
, struct group_lookup
*glp
)
460 struct group
**gpa
, *gp
;
464 for (glp
->gl_htable
= gpa
= (gt
& GT_ALIAS
? _alias_heads
:
465 (gt
& GT_MLIST
? _mlist_heads
:
466 (gt
& GT_SHORTCUT
? _shortcut_heads
:
467 (gt
& GT_CHARSETALIAS
? _charsetalias_heads
: NULL
)))), i
= 0;
468 i
< HSHSIZE
; ++gpa
, ++i
)
469 if ((gp
= *gpa
) != NULL
) {
475 glp
->gl_group
= gp
= NULL
;
477 glp
->gl_slot_last
= NULL
;
482 static struct group
*
483 _group_go_next(struct group_lookup
*glp
)
485 struct group
*gp
, **gpa
;
488 if ((gp
= glp
->gl_group
->g_next
) != NULL
)
489 glp
->gl_slot_last
= glp
->gl_group
;
491 glp
->gl_slot_last
= NULL
;
492 for (gpa
= glp
->gl_htable
+ HSHSIZE
; ++glp
->gl_slot
< gpa
;)
493 if ((gp
= *glp
->gl_slot
) != NULL
)
501 static struct group
*
502 _group_fetch(enum group_type gt
, char const *id
, size_t addsz
)
504 struct group_lookup gl
;
509 if ((gp
= _group_lookup(gt
, &gl
, id
)) != NULL
)
513 i
= n_ALIGN(n_VSTRUCT_SIZEOF(struct group
, g_id
) + l
);
514 switch (gt
& GT_MASK
) {
516 addsz
= sizeof(struct grp_names_head
);
520 if (n_is_maybe_regex(id
)) {
521 addsz
= sizeof(struct grp_regex
);
526 case GT_CHARSETALIAS
:
531 gp
= smalloc(i
+ addsz
);
532 gp
->g_subclass_off
= i
;
534 memcpy(gp
->g_id
, id
, l
);
537 struct grp_names_head
*gnhp
;
539 GP_TO_SUBCLASS(gnhp
, gp
);
540 gnhp
->gnh_head
= NULL
;
543 else if (/*(gt & GT_MLIST) &&*/ gt
& GT_REGEX
) {
545 struct grp_regex
*grp
;
546 GP_TO_SUBCLASS(grp
, gp
);
548 if((s
= regcomp(&grp
->gr_regex
, id
,
549 REG_EXTENDED
| REG_ICASE
| REG_NOSUB
)) != 0){
550 n_err(_("Invalid regular expression: %s: %s\n"),
551 n_shexp_quote_cp(id
, FAL0
), n_regex_err_to_str(&grp
->gr_regex
, s
));
556 grp
->gr_mygroup
= gp
;
561 gp
->g_next
= *gl
.gl_slot
;
569 _group_del(enum group_type gt
, char const *id
)
571 enum group_type xgt
= gt
& GT_MASK
;
572 struct group_lookup gl
;
576 /* Delete 'em all? */
577 if (id
[0] == '*' && id
[1] == '\0') {
578 for (gp
= _group_go_first(gt
, &gl
); gp
!= NULL
;)
579 gp
= (gp
->g_type
& xgt
) ? __group_del(&gl
) : _group_go_next(&gl
);
580 gp
= (struct group
*)TRU1
;
581 } else if ((gp
= _group_lookup(gt
, &gl
, id
)) != NULL
) {
582 if (gp
->g_type
& xgt
)
591 static struct group
*
592 __group_del(struct group_lookup
*glp
)
594 struct group
*x
, *gp
;
597 /* Overly complicated: link off this node, step ahead to next.. */
599 if ((gp
= glp
->gl_slot_last
) != NULL
) {
600 gp
= (gp
->g_next
= x
->g_next
);
602 glp
->gl_slot_last
= NULL
;
603 gp
= (*glp
->gl_slot
= x
->g_next
);
606 struct group
**gpa
= glp
->gl_htable
+ HSHSIZE
;
608 while (++glp
->gl_slot
< gpa
)
609 if ((gp
= *glp
->gl_slot
) != NULL
)
615 if (x
->g_type
& GT_ALIAS
)
618 else if (/*(x->g_type & GT_MLIST) &&*/ x
->g_type
& GT_REGEX
) {
619 struct grp_regex
*grp
;
620 GP_TO_SUBCLASS(grp
, x
);
622 regfree(&grp
->gr_regex
);
633 __names_del(struct group
*gp
)
635 struct grp_names_head
*gnhp
;
636 struct grp_names
*gnp
;
639 GP_TO_SUBCLASS(gnhp
, gp
);
640 for (gnp
= gnhp
->gnh_head
; gnp
!= NULL
;) {
641 struct grp_names
*x
= gnp
;
649 _group_print_all(enum group_type gt
)
653 struct group
const *gp
;
660 xgt
= gt
& GT_PRINT_MASK
;
661 gpa
= (xgt
& GT_ALIAS
? _alias_heads
662 : (xgt
& GT_MLIST
? _mlist_heads
663 : (xgt
& GT_SHORTCUT
? _shortcut_heads
664 : (xgt
& GT_CHARSETALIAS
? _charsetalias_heads
: NULL
))));
666 for (h
= 0, i
= 1; h
< HSHSIZE
; ++h
)
667 for (gp
= gpa
[h
]; gp
!= NULL
; gp
= gp
->g_next
)
668 if ((gp
->g_type
& xgt
) == xgt
)
670 ida
= salloc(i
* sizeof *ida
);
672 for (i
= h
= 0; h
< HSHSIZE
; ++h
)
673 for (gp
= gpa
[h
]; gp
!= NULL
; gp
= gp
->g_next
)
674 if ((gp
->g_type
& xgt
) == xgt
)
679 qsort(ida
, i
, sizeof *ida
, &__group_print_qsorter
);
681 if ((fp
= Ftmp(NULL
, "prgroup", OF_RDWR
| OF_UNLINK
| OF_REGISTER
)) == NULL
)
685 for (i
= 0; ida
[i
] != NULL
; ++i
)
686 lines
+= _group_print(_group_find(gt
, ida
[i
]), fp
);
689 if (gt
& GT_SUBSCRIBE
)
690 i
= (ui32_t
)_mlsub_size
, h
= (ui32_t
)_mlsub_hits
;
692 i
= (ui32_t
)_mlist_size
, h
= (ui32_t
)_mlist_hits
;
693 if (i
> 0 && (n_poption
& n_PO_D_V
)){
694 fprintf(fp
, _("# %s list regex(7) total: %u entries, %u hits\n"),
695 (gt
& GT_SUBSCRIBE
? _("Subscribed") : _("Non-subscribed")),
702 if (fp
!= n_stdout
) {
703 page_or_print(fp
, lines
);
710 __group_print_qsorter(void const *a
, void const *b
)
715 rv
= strcmp(*(char**)n_UNCONST(a
), *(char**)n_UNCONST(b
));
721 _group_print(struct group
const *gp
, FILE *fo
)
729 if (gp
->g_type
& GT_ALIAS
) {
730 struct grp_names_head
*gnhp
;
731 struct grp_names
*gnp
;
733 fprintf(fo
, "alias %s ", gp
->g_id
);
735 GP_TO_SUBCLASS(gnhp
, gp
);
736 if ((gnp
= gnhp
->gnh_head
) != NULL
) { /* xxx always 1+ entries */
738 struct grp_names
*x
= gnp
;
740 fprintf(fo
, " \"%s\"", string_quote(x
->gn_id
)); /* TODO shexp */
741 } while (gnp
!= NULL
);
744 } else if (gp
->g_type
& GT_MLIST
) {
746 if ((gp
->g_type
& GT_REGEX
) && (n_poption
& n_PO_D_V
)){
748 struct grp_regex
*grp
,
749 *lp
= (gp
->g_type
& GT_SUBSCRIBE
? _mlsub_regex
: _mlist_regex
);
751 GP_TO_SUBCLASS(grp
, gp
);
752 for (i
= 1; lp
!= grp
; lp
= lp
->gr_next
)
754 fprintf(fo
, "# regex(7): hits %" PRIuZ
", sort %" PRIuZ
".\n ",
760 fprintf(fo
, "wysh %s %s\n",
761 (gp
->g_type
& GT_SUBSCRIBE
? "mlsubscribe" : "mlist"),
762 n_shexp_quote_cp(gp
->g_id
, TRU1
));
763 } else if (gp
->g_type
& GT_SHORTCUT
) {
764 GP_TO_SUBCLASS(cp
, gp
);
765 fprintf(fo
, "wysh shortcut %s %s\n",
766 gp
->g_id
, n_shexp_quote_cp(cp
, TRU1
));
767 } else if (gp
->g_type
& GT_CHARSETALIAS
) {
768 GP_TO_SUBCLASS(cp
, gp
);
769 fprintf(fo
, "charsetalias %s %s\n",
770 n_shexp_quote_cp(gp
->g_id
, TRU1
), n_shexp_quote_cp(cp
, TRU1
));
778 _mlmux(enum group_type gt
, char **argv
)
785 _group_print_all(gt
);
787 if ((gp
= _group_find(gt
, *argv
)) != NULL
) {
788 if (gt
& GT_SUBSCRIBE
) {
789 if (!(gp
->g_type
& GT_SUBSCRIBE
)) {
791 gp
->g_type
|= GT_SUBSCRIBE
;
794 n_err(_("Mailing-list already `mlsubscribe'd: %s\n"),
799 n_err(_("Mailing-list already `mlist'ed: %s\n"), *argv
);
803 _group_fetch(gt
, *argv
, 0);
804 } while (*++argv
!= NULL
);
811 _unmlmux(enum group_type gt
, char **argv
)
817 for (; *argv
!= NULL
; ++argv
) {
818 if (gt
& GT_SUBSCRIBE
) {
819 struct group_lookup gl
;
822 if (!(isaster
= (**argv
== '*')))
823 gp
= _group_find(gt
, *argv
);
824 else if ((gp
= _group_go_first(gt
, &gl
)) == NULL
)
826 else if (gp
!= NULL
&& !(gp
->g_type
& GT_SUBSCRIBE
))
831 if (gp
->g_type
& GT_SUBSCRIBE
) {
833 gp
->g_type
&= ~GT_SUBSCRIBE
;
837 while ((gp
= _group_go_next(&gl
)) != NULL
&&
838 !(gp
->g_type
& GT_SUBSCRIBE
))
844 n_err(_("Mailing-list not `mlsubscribe'd: %s\n"),
845 n_shexp_quote_cp(*argv
, FAL0
));
850 } else if (_group_del(gt
, *argv
))
852 n_err(_("No such mailing-list: %s\n"), n_shexp_quote_cp(*argv
, FAL0
));
861 _mlmux_linkin(struct group
*gp
)
863 struct grp_regex
**lpp
, *grp
, *lhp
;
866 if (gp
->g_type
& GT_SUBSCRIBE
) {
874 GP_TO_SUBCLASS(grp
, gp
);
875 if ((lhp
= *lpp
) != NULL
) {
876 (grp
->gr_last
= lhp
->gr_last
)->gr_next
= grp
;
877 (grp
->gr_next
= lhp
)->gr_last
= grp
;
879 *lpp
= grp
->gr_last
= grp
->gr_next
= grp
;
885 _mlmux_linkout(struct group
*gp
)
887 struct grp_regex
*grp
, **lpp
;
890 GP_TO_SUBCLASS(grp
, gp
);
892 if (gp
->g_type
& GT_SUBSCRIBE
) {
895 _mlsub_hits
-= grp
->gr_hits
;
899 _mlist_hits
-= grp
->gr_hits
;
902 if (grp
->gr_next
== grp
)
905 (grp
->gr_last
->gr_next
= grp
->gr_next
)->gr_last
= grp
->gr_last
;
911 #endif /* HAVE_REGEX */
914 nalloc(char const *str
, enum gfield ntype
)
916 struct n_addrguts ag
;
920 assert(!(ntype
& GFULLEXTRA
) || (ntype
& GFULL
) != 0);
922 str
= n_addrspec_with_guts(&ag
, str
,
923 ((ntype
& (GFULL
| GSKIN
| GREF
)) != 0));
926 np = NULL; TODO We cannot return NULL,
927 goto jleave; TODO thus handle failures in here!
932 if (!(ag
.ag_n_flags
& NAME_NAME_SALLOC
)) {
933 ag
.ag_n_flags
|= NAME_NAME_SALLOC
;
934 np
= salloc(sizeof(*np
) + ag
.ag_slen
+1);
935 memcpy(np
+ 1, ag
.ag_skinned
, ag
.ag_slen
+1);
936 ag
.ag_skinned
= (char*)(np
+ 1);
938 np
= salloc(sizeof *np
);
945 np
->n_fullname
= np
->n_name
= ag
.ag_skinned
;
946 np
->n_fullextra
= NULL
;
947 np
->n_flags
= ag
.ag_n_flags
;
950 if (ag
.ag_ilen
== ag
.ag_slen
952 && !(ag
.ag_n_flags
& NAME_IDNA
)
956 if (ag
.ag_n_flags
& NAME_ADDRSPEC_ISFILEORPIPE
)
959 /* n_fullextra is only the complete name part without address.
960 * Beware of "-r '<abc@def>'", don't treat that as FULLEXTRA */
961 if ((ntype
& GFULLEXTRA
) && ag
.ag_ilen
> ag
.ag_slen
+ 2) {
962 size_t s
= ag
.ag_iaddr_start
, e
= ag
.ag_iaddr_aend
, i
;
965 if (s
== 0 || str
[--s
] != '<' || str
[e
++] != '>')
968 in
.s
= n_lofi_alloc(s
+ 1 + i
+1);
969 while(s
> 0 && blankchar(str
[s
- 1]))
971 memcpy(in
.s
, str
, s
);
974 while (blankchar(str
[e
])) {
980 memcpy(&in
.s
[s
], &str
[e
], i
);
983 in
.s
[in
.l
= s
] = '\0';
984 mime_fromhdr(&in
, &out
, TD_ISPR
| TD_ICONV
);
986 for (cp
= out
.s
, i
= out
.l
; i
> 0 && spacechar(*cp
); --i
, ++cp
)
988 while (i
> 0 && spacechar(cp
[i
- 1]))
990 np
->n_fullextra
= savestrbuf(cp
, i
);
997 /* n_fullname depends on IDNA conversion */
999 if (!(ag
.ag_n_flags
& NAME_IDNA
)) {
1001 in
.s
= n_UNCONST(str
);
1005 /* The domain name was IDNA and has been converted. We also have to
1006 * ensure that the domain name in .n_fullname is replaced with the
1007 * converted version, since MIME doesn't perform encoding of addrs */
1008 /* TODO This definetely doesn't belong here! */
1009 size_t l
= ag
.ag_iaddr_start
,
1010 lsuff
= ag
.ag_ilen
- ag
.ag_iaddr_aend
;
1011 in
.s
= ac_alloc(l
+ ag
.ag_slen
+ lsuff
+1);
1012 memcpy(in
.s
, str
, l
);
1013 memcpy(in
.s
+ l
, ag
.ag_skinned
, ag
.ag_slen
);
1015 memcpy(in
.s
+ l
, str
+ ag
.ag_iaddr_aend
, lsuff
);
1021 mime_fromhdr(&in
, &out
, TD_ISPR
| TD_ICONV
);
1022 np
->n_fullname
= savestr(out
.s
);
1025 if (ag
.ag_n_flags
& NAME_IDNA
)
1028 np
->n_flags
|= NAME_FULLNAME_SALLOC
;
1036 ndup(struct name
*np
, enum gfield ntype
)
1041 if ((ntype
& (GFULL
| GSKIN
)) && !(np
->n_flags
& NAME_SKINNED
)) {
1042 nnp
= nalloc(np
->n_name
, ntype
);
1046 nnp
= salloc(sizeof *np
);
1047 nnp
->n_flink
= nnp
->n_blink
= NULL
;
1048 nnp
->n_type
= ntype
;
1049 nnp
->n_flags
= (np
->n_flags
& ~(NAME_NAME_SALLOC
| NAME_FULLNAME_SALLOC
)) |
1051 nnp
->n_name
= savestr(np
->n_name
);
1052 if (np
->n_name
== np
->n_fullname
|| !(ntype
& (GFULL
| GSKIN
))) {
1053 nnp
->n_fullname
= nnp
->n_name
;
1054 nnp
->n_fullextra
= NULL
;
1056 nnp
->n_flags
|= NAME_FULLNAME_SALLOC
;
1057 nnp
->n_fullname
= savestr(np
->n_fullname
);
1058 nnp
->n_fullextra
= (np
->n_fullextra
== NULL
) ? NULL
1059 : savestr(np
->n_fullextra
);
1067 cat(struct name
*n1
, struct name
*n2
)
1079 while (tail
->n_flink
!= NULL
)
1080 tail
= tail
->n_flink
;
1090 namelist_dup(struct name
const *np
, enum gfield ntype
)
1095 for (nnp
= NULL
; np
!= NULL
; np
= np
->n_flink
) {
1096 struct name
*x
= ndup(n_UNCONST(np
), (np
->n_type
& ~GMASK
) | ntype
);
1105 count(struct name
const *np
)
1110 for (c
= 0; np
!= NULL
; np
= np
->n_flink
)
1111 if (!(np
->n_type
& GDEL
))
1118 count_nonlocal(struct name
const *np
)
1123 for (c
= 0; np
!= NULL
; np
= np
->n_flink
)
1124 if (!(np
->n_type
& GDEL
) && !(np
->n_flags
& NAME_ADDRSPEC_ISFILEORPIPE
))
1131 extract(char const *line
, enum gfield ntype
)
1136 rv
= _extract1(line
, ntype
, " \t,", 0);
1142 lextract(char const *line
, enum gfield ntype
)
1147 rv
= ((line
!= NULL
&& strpbrk(line
, ",\"\\(<|"))
1148 ? _extract1(line
, ntype
, ",", 1) : extract(line
, ntype
));
1154 detract(struct name
*np
, enum gfield ntype
)
1165 flags
= ntype
& (GCOMMA
| GNAMEONLY
);
1166 ntype
&= ~(GCOMMA
| GNAMEONLY
);
1169 for (p
= np
; p
!= NULL
; p
= p
->n_flink
) {
1170 if (ntype
&& (p
->n_type
& GMASK
) != ntype
)
1172 s
+= strlen(flags
& GNAMEONLY
? p
->n_name
: p
->n_fullname
) +1;
1182 for (p
= np
; p
!= NULL
; p
= p
->n_flink
) {
1183 if (ntype
&& (p
->n_type
& GMASK
) != ntype
)
1185 cp
= sstpcpy(cp
, (flags
& GNAMEONLY
? p
->n_name
: p
->n_fullname
));
1186 if ((flags
& GCOMMA
) && p
->n_flink
!= NULL
)
1191 if ((flags
& GCOMMA
) && *--cp
== ',')
1199 grab_names(enum n_lexinput_flags lif
, char const *field
, struct name
*np
,
1200 int comma
, enum gfield gflags
)
1206 np
= lextract(n_lex_input_cp(lif
, field
, detract(np
, comma
)), gflags
);
1207 for (nq
= np
; nq
!= NULL
; nq
= nq
->n_flink
)
1208 if (is_addr_invalid(nq
, EACM_NONE
))
1215 name_is_same_domain(struct name
const *n1
, struct name
const *n2
)
1217 char const *d1
, *d2
;
1221 d1
= strrchr(n1
->n_name
, '@');
1222 d2
= strrchr(n2
->n_name
, '@');
1224 rv
= (d1
!= NULL
&& d2
!= NULL
) ? !asccasecmp(++d1
, ++d2
) : FAL0
;
1231 checkaddrs(struct name
*np
, enum expand_addr_check_mode eacm
,
1232 si8_t
*set_on_error
)
1237 for (n
= np
; n
!= NULL
; n
= n
->n_flink
) {
1240 if ((rv
= is_addr_invalid(n
, eacm
)) != 0) {
1241 if (set_on_error
!= NULL
)
1242 *set_on_error
|= rv
; /* don't loose -1! */
1243 else if (eacm
& EAF_MAYKEEP
) /* TODO HACK! See definition! */
1246 n
->n_blink
->n_flink
= n
->n_flink
;
1248 n
->n_flink
->n_blink
= n
->n_blink
;
1258 namelist_vaporise_head(struct header
*hp
, enum expand_addr_check_mode eacm
,
1259 bool_t metoo
, si8_t
*set_on_error
)
1261 struct name
*tolist
, *np
, **npp
;
1264 tolist
= usermap(cat(hp
->h_to
, cat(hp
->h_cc
, hp
->h_bcc
)), metoo
);
1265 hp
->h_to
= hp
->h_cc
= hp
->h_bcc
= NULL
;
1267 tolist
= elide(checkaddrs(tolist
, eacm
, set_on_error
));
1269 for (np
= tolist
; np
!= NULL
; np
= np
->n_flink
) {
1270 switch (np
->n_type
& (GDEL
| GMASK
)) {
1271 case GTO
: npp
= &hp
->h_to
; break;
1272 case GCC
: npp
= &hp
->h_cc
; break;
1273 case GBCC
: npp
= &hp
->h_bcc
; break;
1276 *npp
= cat(*npp
, ndup(np
, np
->n_type
| GFULL
));
1283 usermap(struct name
*names
, bool_t force_metoo
)
1285 struct name
*new, *np
, *cp
;
1292 metoo
= (force_metoo
|| ok_blook(metoo
));
1293 while (np
!= NULL
) {
1294 assert(!(np
->n_type
& GDEL
)); /* TODO legacy */
1295 if (is_fileorpipe_addr(np
) || np
->n_name
[0] == '\\') {
1301 gp
= _group_find(GT_ALIAS
, np
->n_name
);
1304 new = _gexpand(0, new, gp
, metoo
, np
->n_type
);
1314 elide(struct name
*names
)
1316 struct name
*np
, *t
, *newn
, *x
;
1323 /* Throw away all deleted nodes (XXX merge with plain sort below?) */
1324 for (np
= NULL
; names
!= NULL
; names
= names
->n_flink
)
1325 if (!(names
->n_type
& GDEL
)) {
1326 names
->n_blink
= np
;
1328 np
->n_flink
= names
;
1339 newn
->n_flink
= NULL
;
1341 while (np
!= NULL
) {
1345 while ((cmpres
= asccasecmp(t
->n_name
, np
->n_name
)) < 0) {
1346 if (t
->n_flink
== NULL
)
1351 /* If we ran out of t's, put new entry after the current value of t */
1361 /* Otherwise, put the new entry in front of the current t. If at the
1362 * front of the list, the new guy becomes the new head of the list */
1373 /* The normal case -- we are inserting into the middle of the list */
1377 x
->n_blink
= t
->n_blink
;
1378 t
->n_blink
->n_flink
= x
;
1382 /* Now the list headed up by new is sorted. Remove duplicates */
1384 while (np
!= NULL
) {
1386 while (t
->n_flink
!= NULL
&& !asccasecmp(np
->n_name
, t
->n_flink
->n_name
))
1393 /* Now t points to the last entry with the same name as np.
1394 * Make np point beyond t */
1395 np
->n_flink
= t
->n_flink
;
1396 if (t
->n_flink
!= NULL
)
1397 t
->n_flink
->n_blink
= np
;
1406 c_alternates(void *v
){
1407 char **namelist
, **ap
, **ap2
, *cp
;
1415 for(namelist
= v
; namelist
[l
] != NULL
; ++l
)
1419 if(a_nag_altnames
!= NULL
){
1420 fprintf(n_stdout
, "alternates ");
1421 for(ap
= a_nag_altnames
; *ap
!= NULL
; ++ap
)
1422 fprintf(n_stdout
, "%s ", *ap
);
1423 putc('\n', n_stdout
);
1426 if(a_nag_altnames
!= NULL
){
1427 for(ap
= a_nag_altnames
; *ap
!= NULL
; ++ap
)
1429 free(a_nag_altnames
);
1434 a_nag_altnames
= smalloc(l
* sizeof(*a_nag_altnames
));
1437 for(ap
= namelist
; *ap
!= NULL
; ++ap
)
1441 if((np
= lextract(*ap
, GSKIN
)) == NULL
|| np
->n_flink
!= NULL
||
1442 (np
= checkaddrs(np
, EACM_STRICT
, NULL
)) == NULL
){
1443 n_err(_("Invalid `alternates' argument: %s\n"),
1444 n_shexp_quote_cp(*ap
, FAL0
));
1448 l
= strlen(np
->n_name
) +1;
1451 memcpy(cp
, np
->n_name
, l
);
1456 /* And put it into *-alternates* */
1459 for(sl
= 0, ap
= a_nag_altnames
; *ap
!= NULL
; ++ap
)
1460 if((l
= strlen(*ap
)) > 0){
1461 memcpy(&cp
[sl
], *ap
, l
);
1469 free(a_nag_altnames
);
1470 a_nag_altnames
= NULL
;
1473 n_PS_ROOT_BLOCK(sl
> 0 ? ok_vset(_alternates
, cp
)
1474 : ok_vclear(_alternates
));
1481 delete_alternates(struct name
*np
)
1487 np
= delname(np
, ok_vlook(LOGNAME
));
1488 if (a_nag_altnames
!= NULL
)
1489 for (ap
= a_nag_altnames
; *ap
!= '\0'; ++ap
)
1490 np
= delname(np
, *ap
);
1492 if ((xp
= lextract(ok_vlook(from
), GEXTRA
| GSKIN
)) != NULL
)
1493 while (xp
!= NULL
) {
1494 np
= delname(np
, xp
->n_name
);
1498 if ((xp
= lextract(ok_vlook(replyto
), GEXTRA
| GSKIN
)) != NULL
)
1499 while (xp
!= NULL
) {
1500 np
= delname(np
, xp
->n_name
);
1504 if ((xp
= extract(ok_vlook(sender
), GEXTRA
| GSKIN
)) != NULL
)
1505 while (xp
!= NULL
) {
1506 np
= delname(np
, xp
->n_name
);
1514 is_myname(char const *name
)
1521 if (_same_name(ok_vlook(LOGNAME
), name
))
1523 if (a_nag_altnames
!= NULL
)
1524 for (ap
= a_nag_altnames
; *ap
!= NULL
; ++ap
)
1525 if (_same_name(*ap
, name
))
1528 if ((xp
= lextract(ok_vlook(from
), GEXTRA
| GSKIN
)) != NULL
)
1529 while (xp
!= NULL
) {
1530 if (_same_name(xp
->n_name
, name
))
1535 if ((xp
= lextract(ok_vlook(replyto
), GEXTRA
| GSKIN
)) != NULL
)
1536 while (xp
!= NULL
) {
1537 if (_same_name(xp
->n_name
, name
))
1542 if ((xp
= extract(ok_vlook(sender
), GEXTRA
| GSKIN
)) != NULL
)
1543 while (xp
!= NULL
) {
1544 if (_same_name(xp
->n_name
, name
))
1555 c_addrcodec(void *v
){
1556 struct n_addrguts ag
;
1557 struct n_string s_b
, *sp
;
1558 char const **argv
, *varname
, *varres
, *cp
;
1562 sp
= n_string_creat_auto(&s_b
);
1565 varname
= (n_pstate
& n_PS_ARGMOD_VPUT
) ? *argv
++ : NULL
;
1567 for(; *argv
!= NULL
; ++argv
){
1569 sp
= n_string_push_c(sp
, ' ');
1570 sp
= n_string_push_cp(sp
, *argv
);
1574 /* TODO nalloc() cannot yet fail, thus need to do the work twice!!
1575 * TODO I.e. later on this could be a simple nalloc() wrapper.. */
1576 for(cp
= n_string_cp(sp
); blankchar(*cp
); ++cp
)
1579 sp
= n_string_cut(sp
, 0, PTR2SIZE(cp
- sp
->s_dat
));
1580 for(varres
= cp
= &sp
->s_dat
[sp
->s_len
];
1581 cp
> sp
->s_dat
&& blankchar(cp
[-1]); --cp
)
1584 sp
= n_string_trunc(sp
, sp
->s_len
- (ui32_t
)PTR2SIZE(varres
- cp
));
1589 /* However, the difference for this command is that the user enters what
1590 * she wants to have, and we should make something of it. Therefore any
1591 * quotes are necessarily to be turned to quoted-pair! */
1593 for(i
= 0; i
< sp
->s_len
; ++i
)
1594 if(sp
->s_dat
[i
] == '"' || sp
->s_dat
[i
] == '\\')
1595 sp
= n_string_insert_c(sp
, i
++, '\\');
1598 if(n_addrspec_with_guts(&ag
, n_string_cp(sp
), TRU1
) == NULL
||
1599 (ag
.ag_n_flags
& (NAME_ADDRSPEC_ISADDR
| NAME_ADDRSPEC_INVALID
)
1600 ) != NAME_ADDRSPEC_ISADDR
){
1606 np
= nalloc(n_string_cp(sp
), GTO
| GFULL
| GSKIN
);
1607 varres
= np
->n_fullname
;
1610 if(varname
== NULL
){
1611 if(fprintf(n_stdout
, "%s\n", varres
) < 0)
1613 }else if(!n_var_vset(varname
, (uintptr_t)varres
)){
1619 n_pstate_var__em
= n_0
;
1625 n_alias_is_valid_name(char const *name
){
1631 for(rv
= TRU1
, cp
= name
++; (c
= *cp
++) != '\0';)
1632 /* User names, plus things explicitly mentioned in Postfix aliases(5).
1633 * As an extension, allow period: [[:alnum:]_#:@.-]+$? */
1634 if(!alnumchar(c
) && c
!= '_' && c
!= '-' &&
1635 c
!= '#' && c
!= ':' && c
!= '@' &&
1637 if(c
== '$' && cp
!= name
&& *cp
== '\0')
1655 _group_print_all(GT_ALIAS
);
1656 else if (!n_alias_is_valid_name(*argv
)) {
1657 n_err(_("Not a valid alias name: %s\n"), *argv
);
1659 } else if (argv
[1] == NULL
) {
1660 if ((gp
= _group_find(GT_ALIAS
, *argv
)) != NULL
)
1661 _group_print(gp
, n_stdout
);
1663 n_err(_("No such alias: %s\n"), *argv
);
1667 struct grp_names_head
*gnhp
;
1669 gp
= _group_fetch(GT_ALIAS
, *argv
, 0);
1670 GP_TO_SUBCLASS(gnhp
, gp
);
1672 for (++argv
; *argv
!= NULL
; ++argv
) {
1673 size_t l
= strlen(*argv
) +1;
1674 struct grp_names
*gnp
= smalloc(n_VSTRUCT_SIZEOF(struct grp_names
,
1676 gnp
->gn_next
= gnhp
->gnh_head
;
1677 gnhp
->gnh_head
= gnp
;
1678 memcpy(gnp
->gn_id
, *argv
, l
);
1680 assert(gnhp
->gnh_head
!= NULL
);
1693 do if (!_group_del(GT_ALIAS
, *argv
)) {
1694 n_err(_("No such alias: %s\n"), *argv
);
1696 } while (*++argv
!= NULL
);
1707 rv
= _mlmux(GT_MLIST
, v
);
1718 rv
= _unmlmux(GT_MLIST
, v
);
1724 c_mlsubscribe(void *v
)
1729 rv
= _mlmux(GT_MLIST
| GT_SUBSCRIBE
, v
);
1735 c_unmlsubscribe(void *v
)
1740 rv
= _unmlmux(GT_MLIST
| GT_SUBSCRIBE
, v
);
1746 is_mlist(char const *name
, bool_t subscribed_only
)
1750 struct grp_regex
**lpp
, *grp
;
1753 enum mlist_state rv
;
1756 gp
= _group_find(GT_MLIST
, name
);
1757 rv
= (gp
!= NULL
) ? MLIST_KNOWN
: MLIST_OTHER
;
1758 if (rv
== MLIST_KNOWN
) {
1759 if (gp
->g_type
& GT_SUBSCRIBE
)
1760 rv
= MLIST_SUBSCRIBED
;
1761 else if (subscribed_only
)
1763 /* Of course, if that is a regular expression it doesn't mean a thing */
1765 if (gp
->g_type
& GT_REGEX
)
1772 /* Not in the hashmap (as something matchable), walk the lists */
1775 lpp
= &_mlsub_regex
;
1777 if ((grp
= *lpp
) != NULL
) {
1778 do if (regexec(&grp
->gr_regex
, name
, 0,NULL
, 0) != REG_NOMATCH
) {
1779 /* Relink as the head of this list if the hit count of this group is
1780 * >= 25% of the average hit count */
1783 i
= ++_mlsub_hits
/ _mlsub_size
;
1785 i
= ++_mlist_hits
/ _mlist_size
;
1788 if (++grp
->gr_hits
>= i
&& *lpp
!= grp
&& grp
->gr_next
!= grp
) {
1789 grp
->gr_last
->gr_next
= grp
->gr_next
;
1790 grp
->gr_next
->gr_last
= grp
->gr_last
;
1791 (grp
->gr_last
= (*lpp
)->gr_last
)->gr_next
= grp
;
1792 (grp
->gr_next
= *lpp
)->gr_last
= grp
;
1795 rv
= !re2
? MLIST_SUBSCRIBED
: MLIST_KNOWN
;
1797 } while ((grp
= grp
->gr_next
) != *lpp
);
1799 if (!re2
&& !subscribed_only
) {
1801 lpp
= &_mlist_regex
;
1804 assert(rv
== MLIST_OTHER
);
1820 _group_print_all(GT_SHORTCUT
);
1821 else for (; *argv
!= NULL
; argv
+= 2) {
1822 /* Because one hardly ever redefines, anything is stored in one chunk */
1827 if (argv
[1] == NULL
) {
1828 n_err(_("Synopsis: shortcut: <shortcut> <expansion>\n"));
1832 if (_group_find(GT_SHORTCUT
, *argv
) != NULL
)
1833 _group_del(GT_SHORTCUT
, *argv
);
1835 l
= strlen(argv
[1]) +1;
1836 gp
= _group_fetch(GT_SHORTCUT
, *argv
, l
);
1837 GP_TO_SUBCLASS(cp
, gp
);
1838 memcpy(cp
, argv
[1], l
);
1845 c_unshortcut(void *v
)
1851 do if (!_group_del(GT_SHORTCUT
, *argv
)) {
1852 n_err(_("No such shortcut: %s\n"), *argv
);
1854 } while (*++argv
!= NULL
);
1860 shortcut_expand(char const *str
)
1865 if ((gp
= _group_find(GT_SHORTCUT
, str
)) != NULL
)
1866 GP_TO_SUBCLASS(str
, gp
);
1874 c_charsetalias(void *vp
){
1883 _group_print_all(GT_CHARSETALIAS
);
1884 else for(; *argv
!= NULL
; argv
+= 2){
1885 /* Because one hardly ever redefines, anything is stored in one chunk */
1891 if(argv
[1] == NULL
){
1892 n_err(_("Synopsis: charsetalias: <charset> <charset-alias>\n"));
1898 if(ccp
[0] != '*' || ccp
[1] != '\0')
1899 _group_del(GT_CHARSETALIAS
, ccp
);
1901 /* Lowercase it all (for display purposes) */
1904 while((c
= *cp
) != '\0')
1905 *cp
++ = lowerconv(c
);
1907 l
= strlen(argv
[1]) +1;
1908 gp
= _group_fetch(GT_CHARSETALIAS
, ccp
, l
);
1909 GP_TO_SUBCLASS(cp
, gp
);
1910 for(ccp
= argv
[1]; (c
= *ccp
++) != '\0';)
1911 *cp
++ = lowerconv(c
);
1919 c_uncharsetalias(void *vp
){
1927 do if(!_group_del(GT_CHARSETALIAS
, *argv
)){
1928 n_err(_("No such `charsetalias': %s\n"), n_shexp_quote_cp(*argv
, FAL0
));
1930 }while(*++argv
!= NULL
);
1936 n_charsetalias_expand(char const *cp
){
1939 char const *cp_orig
;
1944 for(i
= 0; (gp
= _group_find(GT_CHARSETALIAS
, cp
)) != NULL
;){
1945 GP_TO_SUBCLASS(cp
, gp
);
1946 if(++i
== 8) /* XXX Magic (same as for `ghost' expansion) */