1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Name lists, alternates and groups: aliases, mailing lists, shortcuts.
3 *@ TODO Dynamic hashmaps; names and (these) groups have _nothing_ in common!
5 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
6 * Copyright (c) 2012 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
9 * Copyright (c) 1980, 1993
10 * The Regents of the University of California. All rights reserved.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 * 3. Neither the name of the University nor the names of its contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 #define n_FILE nam_a_grp
39 #ifndef HAVE_AMALGAMATION
45 a_NAG_T_ALTERNATES
= 1,
54 /* Subtype bits and flags */
55 a_NAG_T_SUBSCRIBE
= 1u<<6,
56 a_NAG_T_REGEX
= 1u<<7,
58 /* Extended type mask to be able to reflect what we really have; i.e., mlist
59 * can have a_NAG_T_REGEX if they are subscribed or not, but `mlsubscribe'
60 * should print out only a_NAG_T_MLIST which have the a_NAG_T_SUBSCRIBE
62 a_NAG_T_PRINT_MASK
= a_NAG_T_MASK
| a_NAG_T_SUBSCRIBE
64 n_CTA(a_NAG_T_MASK
>= a_NAG_T_FILETYPE
, "Mask does not cover necessary bits");
67 struct a_nag_group
*ng_next
;
68 ui32_t ng_subclass_off
; /* of "subclass" in .ng_id (if any) */
69 ui16_t ng_id_len_sub
; /* length of .ng_id: _subclass_off - this */
70 ui8_t ng_type
; /* enum a_nag_type */
71 /* Identifying name, of variable size. Dependent on actual "subtype" more
72 * data follows thereafter, but note this is always used (i.e., for regular
73 * expression entries this is still set to the plain string) */
74 char ng_id
[n_VFIELD_SIZE(1)];
76 #define a_NAG_GP_TO_SUBCLASS(X,G) \
78 union a_nag_group_subclass {void *gs_vp; char *gs_cp;} a__gs__;\
79 a__gs__.gs_cp = &((char*)n_UNCONST(G))[(G)->ng_subclass_off];\
83 struct a_nag_grp_names_head
{
84 struct a_nag_grp_names
*ngnh_head
;
87 struct a_nag_grp_names
{
88 struct a_nag_grp_names
*ngn_next
;
89 char ngn_id
[n_VFIELD_SIZE(0)];
93 struct a_nag_grp_regex
{
94 struct a_nag_grp_regex
*ngr_last
;
95 struct a_nag_grp_regex
*ngr_next
;
96 struct a_nag_group
*ngr_mygroup
; /* xxx because lists use grp_regex*! ?? */
97 size_t ngr_hits
; /* Number of times this group matched */
102 struct a_nag_cmd_alias
{
103 struct str nca_expand
;
106 struct a_nag_file_type
{
111 struct a_nag_group_lookup
{
112 struct a_nag_group
**ngl_htable
;
113 struct a_nag_group
**ngl_slot
;
114 struct a_nag_group
*ngl_slot_last
;
115 struct a_nag_group
*ngl_group
;
118 static struct n_file_type
const a_nag_OBSOLETE_xz
= { /* TODO v15 compat */
119 "xz", 2, "xz -cd", sizeof("xz -cd") -1, "xz -cz", sizeof("xz -cz") -1
120 }, a_nag_OBSOLETE_gz
= {
121 "gz", 2, "gzip -cd", sizeof("gzip -cd") -1, "gzip -cz", sizeof("gzip -cz") -1
122 }, a_nag_OBSOLETE_bz2
= {
123 "bz2", 3, "bzip2 -cd", sizeof("bzip2 -cd") -1,
124 "bzip2 -cz", sizeof("bzip2 -cz") -1
128 static struct a_nag_group
*a_nag_alternates_heads
[HSHSIZE
];
131 static struct a_nag_group
*a_nag_commandalias_heads
[HSHSIZE
];
134 static struct a_nag_group
*a_nag_alias_heads
[HSHSIZE
];
136 /* `mlist', `mlsubscribe'. Anything is stored in the hashmap.. */
137 static struct a_nag_group
*a_nag_mlist_heads
[HSHSIZE
];
139 /* ..but entries which have a_NAG_T_REGEX set are false lookups and will really
140 * be accessed via sequential lists instead, which are type-specific for better
141 * performance, but also to make it possible to have ".*@xy.org" as a mlist
142 * and "(one|two)@xy.org" as a mlsubscription.
143 * These lists use a bit of QOS optimization in that a matching group will
144 * become relinked as the new list head if its hit count is
145 * (>= ((xy_hits / _xy_size) >> 2))
146 * Note that the hit counts only count currently linked in nodes.. */
148 static struct a_nag_grp_regex
*a_nag_mlist_regex
;
149 static struct a_nag_grp_regex
*a_nag_mlsub_regex
;
150 static size_t a_nag_mlist_size
;
151 static size_t a_nag_mlist_hits
;
152 static size_t a_nag_mlsub_size
;
153 static size_t a_nag_mlsub_hits
;
157 static struct a_nag_group
*a_nag_shortcut_heads
[HSHSIZE
];
160 static struct a_nag_group
*a_nag_charsetalias_heads
[HSHSIZE
];
163 static struct a_nag_group
*a_nag_filetype_heads
[HSHSIZE
];
165 /* Same name, while taking care for *allnet*? */
166 static bool_t
a_nag_is_same_name(char const *n1
, char const *n2
);
168 /* Mark all nodes with the given name */
169 static struct name
*a_nag_namelist_mark_name(struct name
*np
, char const *name
);
171 /* Grab a single name (liberal name) */
172 static char const *a_nag_yankname(char const *ap
, char *wbuf
,
173 char const *separators
, int keepcomms
);
175 /* Extraction multiplexer that splits an input line to names */
176 static struct name
*a_nag_extract1(char const *line
, enum gfield ntype
,
177 char const *separators
, bool_t keepcomms
);
179 /* Recursively expand a alias name. Limit expansion to some fixed level.
180 * Direct recursion is not expanded for convenience */
181 static struct name
*a_nag_gexpand(size_t level
, struct name
*nlist
,
182 struct a_nag_group
*ngp
, bool_t metoo
, int ntype
);
185 static int a_nag_elide_qsort(void const *s1
, void const *s2
);
187 /* Lookup a group, return it or NULL, fill in glp anyway */
188 static struct a_nag_group
*a_nag_group_lookup(enum a_nag_type nt
,
189 struct a_nag_group_lookup
*nglp
, char const *id
);
191 /* Easier-to-use wrapper around _group_lookup() */
192 static struct a_nag_group
*a_nag_group_find(enum a_nag_type nt
, char const *id
);
194 /* Iteration: go to the first group, which also inits the iterator. A valid
195 * iterator can be stepped via _next(). A NULL return means no (more) groups
196 * to be iterated exist, in which case only nglp->ngl_group is set (NULL) */
197 static struct a_nag_group
*a_nag_group_go_first(enum a_nag_type nt
,
198 struct a_nag_group_lookup
*nglp
);
199 static struct a_nag_group
*a_nag_group_go_next(struct a_nag_group_lookup
*nglp
);
201 /* Fetch the group id, create it as necessary, fail with NULL if impossible */
202 static struct a_nag_group
*a_nag_group_fetch(enum a_nag_type nt
, char const *id
,
205 /* "Intelligent" delete which handles a "*" id, too;
206 * returns a true boolean if a group was deleted, and always succeeds for "*" */
207 static bool_t
a_nag_group_del(enum a_nag_type nt
, char const *id
);
209 static struct a_nag_group
*a_nag__group_del(struct a_nag_group_lookup
*nglp
);
210 static void a_nag__names_del(struct a_nag_group
*ngp
);
212 /* Print all groups of the given type, alphasorted, or store in `vput' varname:
213 * only in this mode it can return failure */
214 static bool_t
a_nag_group_print_all(enum a_nag_type nt
,
215 char const *varname
);
217 static int a_nag__group_print_qsorter(void const *a
, void const *b
);
219 /* Really print a group, actually. Or store in vputsp, if set.
220 * Return number of written lines */
221 static size_t a_nag_group_print(struct a_nag_group
const *ngp
, FILE *fo
,
222 struct n_string
*vputsp
);
224 /* Multiplexers for list and subscribe commands */
225 static int a_nag_mlmux(enum a_nag_type nt
, char const **argv
);
226 static int a_nag_unmlmux(enum a_nag_type nt
, char const **argv
);
228 /* Relinkers for the sequential match lists */
230 static void a_nag_mlmux_linkin(struct a_nag_group
*ngp
);
231 static void a_nag_mlmux_linkout(struct a_nag_group
*ngp
);
232 # define a_NAG_MLMUX_LINKIN(GP) \
233 do if((GP)->ng_type & a_NAG_T_REGEX) a_nag_mlmux_linkin(GP); while(0)
234 # define a_NAG_MLMUX_LINKOUT(GP) \
235 do if((GP)->ng_type & a_NAG_T_REGEX) a_nag_mlmux_linkout(GP); while(0)
237 # define a_NAG_MLMUX_LINKIN(GP)
238 # define a_NAG_MLMUX_LINKOUT(GP)
242 a_nag_is_same_name(char const *n1
, char const *n2
){
244 char c1
, c2
, c1r
, c2r
;
247 if(ok_blook(allnet
)){
251 c1r
= (c1
== '\0' || c1
== '@');
254 c2r
= (c2
== '\0' || c2
== '@');
265 rv
= !asccasecmp(n1
, n2
);
271 a_nag_namelist_mark_name(struct name
*np
, char const *name
){
275 for(p
= np
; p
!= NULL
; p
= p
->n_flink
)
276 if(!(p
->n_type
& GDEL
) && !(p
->n_flags
& (ui32_t
)SI32_MIN
) &&
277 a_nag_is_same_name(p
->n_name
, name
))
278 p
->n_flags
|= (ui32_t
)SI32_MIN
;
284 a_nag_yankname(char const *ap
, char *wbuf
, char const *separators
,
288 char *wp
, c
, inquote
, lc
, lastsp
;
293 /* Skip over intermediate list trash, as in ".org> , <xy@zz.org>" */
294 for (c
= *ap
; blankchar(c
) || c
== ','; c
= *++ap
)
301 /* Parse a full name: TODO RFC 5322
302 * - Keep everything in quotes, liberal handle *quoted-pair*s therein
303 * - Skip entire (nested) comments
304 * - In non-quote, non-comment, join adjacent space to a single SP
305 * - Understand separators only in non-quote, non-comment context,
306 * and only if not part of a *quoted-pair* (XXX too liberal) */
308 for (inquote
= lc
= lastsp
= 0;; lc
= c
, ++cp
) {
317 #if 0 /* TODO when doing real RFC 5322 parsers - why have i done this? */
323 if (inquote
|| lc
== '\\') {
331 cp
= skip_comment(cp
+ 1);
339 if (strchr(separators
, c
) != NULL
)
343 lastsp
= blankchar(c
);
357 a_nag_extract1(char const *line
, enum gfield ntype
, char const *separators
,
360 struct name
*topp
, *np
, *t
;
366 if (line
== NULL
|| *line
== '\0')
371 nbuf
= n_alloc(strlen(line
) +1);
372 while ((cp
= a_nag_yankname(cp
, nbuf
, separators
, keepcomms
)) != NULL
) {
373 t
= nalloc(nbuf
, ntype
);
388 a_nag_gexpand(size_t level
, struct name
*nlist
, struct a_nag_group
*ngp
,
389 bool_t metoo
, int ntype
){
390 struct a_nag_grp_names
*ngnp
;
391 struct name
*nlist_tail
;
393 struct a_nag_grp_names_head
*ngnhp
;
396 if(UICMP(z
, level
++, >, n_ALIAS_MAXEXP
)){
397 n_err(_("Expanding alias to depth larger than %d\n"), n_ALIAS_MAXEXP
);
401 a_NAG_GP_TO_SUBCLASS(ngnhp
, ngp
);
402 logname
= ok_vlook(LOGNAME
);
404 for(ngnp
= ngnhp
->ngnh_head
; ngnp
!= NULL
; ngnp
= ngnp
->ngn_next
){
405 struct a_nag_group
*xngp
;
410 if(!strcmp(cp
, ngp
->ng_id
))
413 if((xngp
= a_nag_group_find(a_NAG_T_ALIAS
, cp
)) != NULL
){
414 /* For S-nail(1), the "alias" may *be* the sender in that a name maps
415 * to a full address specification; aliases cannot be empty */
416 struct a_nag_grp_names_head
*xngnhp
;
418 a_NAG_GP_TO_SUBCLASS(xngnhp
, xngp
);
420 assert(xngnhp
->ngnh_head
!= NULL
);
421 if(metoo
|| xngnhp
->ngnh_head
->ngn_next
!= NULL
||
422 !a_nag_is_same_name(cp
, logname
))
423 nlist
= a_nag_gexpand(level
, nlist
, xngp
, metoo
, ntype
);
427 /* Here we should allow to expand to itself if only person in alias */
429 if(metoo
|| ngnhp
->ngnh_head
->ngn_next
== NULL
||
430 !a_nag_is_same_name(cp
, logname
)){
433 np
= nalloc(cp
, ntype
| GFULL
);
434 if((nlist_tail
= nlist
) != NULL
){
435 while(nlist_tail
->n_flink
!= NULL
)
436 nlist_tail
= nlist_tail
->n_flink
;
437 nlist_tail
->n_flink
= np
;
438 np
->n_blink
= nlist_tail
;
449 a_nag_elide_qsort(void const *s1
, void const *s2
){
450 struct name
const * const *np1
, * const *np2
;
456 if(!(rv
= asccasecmp((*np1
)->n_name
, (*np2
)->n_name
))){
457 n_LCTAV(GTO
< GCC
&& GCC
< GBCC
);
458 rv
= ((*np1
)->n_type
& (GTO
| GCC
| GBCC
)) -
459 ((*np2
)->n_type
& (GTO
| GCC
| GBCC
));
465 static struct a_nag_group
*
466 a_nag_group_lookup(enum a_nag_type nt
, struct a_nag_group_lookup
*nglp
,
469 struct a_nag_group
*lngp
, *ngp
;
477 struct a_nag_group
**ngpa
;
479 switch((nt
&= a_NAG_T_MASK
)){
480 case a_NAG_T_ALTERNATES
:
481 ngpa
= a_nag_alternates_heads
;
485 case a_NAG_T_COMMANDALIAS
:
486 ngpa
= a_nag_commandalias_heads
;
489 ngpa
= a_nag_alias_heads
;
492 ngpa
= a_nag_mlist_heads
;
495 case a_NAG_T_SHORTCUT
:
496 ngpa
= a_nag_shortcut_heads
;
498 case a_NAG_T_CHARSETALIAS
:
499 ngpa
= a_nag_charsetalias_heads
;
502 case a_NAG_T_FILETYPE
:
503 ngpa
= a_nag_filetype_heads
;
508 nglp
->ngl_htable
= ngpa
;
509 h
= icase
? n_torek_ihash(id
) : n_torek_hash(id
);
510 ngp
= *(nglp
->ngl_slot
= &ngpa
[h
% HSHSIZE
]);
518 for(; ngp
!= NULL
; lngp
= ngp
, ngp
= ngp
->ng_next
)
519 if((ngp
->ng_type
& a_NAG_T_MASK
) == nt
&& *ngp
->ng_id
== c1
&&
520 !asccasecmp(&ngp
->ng_id
[1], id
))
523 for(; ngp
!= NULL
; lngp
= ngp
, ngp
= ngp
->ng_next
)
524 if((ngp
->ng_type
& a_NAG_T_MASK
) == nt
&& *ngp
->ng_id
== c1
&&
525 !strcmp(&ngp
->ng_id
[1], id
))
529 nglp
->ngl_slot_last
= lngp
;
530 nglp
->ngl_group
= ngp
;
535 static struct a_nag_group
*
536 a_nag_group_find(enum a_nag_type nt
, char const *id
){
537 struct a_nag_group_lookup ngl
;
538 struct a_nag_group
*ngp
;
541 ngp
= a_nag_group_lookup(nt
, &ngl
, id
);
546 static struct a_nag_group
*
547 a_nag_group_go_first(enum a_nag_type nt
, struct a_nag_group_lookup
*nglp
){
549 struct a_nag_group
**ngpa
, *ngp
;
552 switch((nt
&= a_NAG_T_MASK
)){
553 case a_NAG_T_ALTERNATES
:
554 ngpa
= a_nag_alternates_heads
;
557 case a_NAG_T_COMMANDALIAS
:
558 ngpa
= a_nag_commandalias_heads
;
561 ngpa
= a_nag_alias_heads
;
564 ngpa
= a_nag_mlist_heads
;
566 case a_NAG_T_SHORTCUT
:
567 ngpa
= a_nag_shortcut_heads
;
569 case a_NAG_T_CHARSETALIAS
:
570 ngpa
= a_nag_charsetalias_heads
;
572 case a_NAG_T_FILETYPE
:
573 ngpa
= a_nag_filetype_heads
;
577 nglp
->ngl_htable
= ngpa
;
579 for(i
= 0; i
< HSHSIZE
; ++ngpa
, ++i
)
580 if((ngp
= *ngpa
) != NULL
){
581 nglp
->ngl_slot
= ngpa
;
582 nglp
->ngl_group
= ngp
;
586 nglp
->ngl_group
= ngp
= NULL
;
588 nglp
->ngl_slot_last
= NULL
;
593 static struct a_nag_group
*
594 a_nag_group_go_next(struct a_nag_group_lookup
*nglp
){
595 struct a_nag_group
*ngp
, **ngpa
;
598 if((ngp
= nglp
->ngl_group
->ng_next
) != NULL
)
599 nglp
->ngl_slot_last
= nglp
->ngl_group
;
601 nglp
->ngl_slot_last
= NULL
;
602 for(ngpa
= &nglp
->ngl_htable
[HSHSIZE
]; ++nglp
->ngl_slot
< ngpa
;)
603 if((ngp
= *nglp
->ngl_slot
) != NULL
)
606 nglp
->ngl_group
= ngp
;
611 static struct a_nag_group
*
612 a_nag_group_fetch(enum a_nag_type nt
, char const *id
, size_t addsz
){
613 struct a_nag_group_lookup ngl
;
614 struct a_nag_group
*ngp
;
618 if((ngp
= a_nag_group_lookup(nt
, &ngl
, id
)) != NULL
)
622 if(UIZ_MAX
- n_ALIGN(l
) <=
623 n_ALIGN(n_VSTRUCT_SIZEOF(struct a_nag_group
, ng_id
)))
626 i
= n_ALIGN(n_VSTRUCT_SIZEOF(struct a_nag_group
, ng_id
) + l
);
627 switch(nt
& a_NAG_T_MASK
){
628 case a_NAG_T_ALTERNATES
:
629 case a_NAG_T_SHORTCUT
:
630 case a_NAG_T_CHARSETALIAS
:
633 case a_NAG_T_COMMANDALIAS
:
634 addsz
+= sizeof(struct a_nag_cmd_alias
);
637 addsz
+= sizeof(struct a_nag_grp_names_head
);
641 if(n_is_maybe_regex(id
)){
642 addsz
= sizeof(struct a_nag_grp_regex
);
647 case a_NAG_T_FILETYPE
:
648 addsz
+= sizeof(struct a_nag_file_type
);
651 if(UIZ_MAX
- i
< addsz
|| UI32_MAX
<= i
|| UI16_MAX
< i
- l
)
654 ngp
= n_alloc(i
+ addsz
);
655 memcpy(ngp
->ng_id
, id
, l
);
656 ngp
->ng_subclass_off
= (ui32_t
)i
;
657 ngp
->ng_id_len_sub
= (ui16_t
)(i
- --l
);
659 switch(nt
& a_NAG_T_MASK
){
660 case a_NAG_T_ALTERNATES
:
662 case a_NAG_T_CHARSETALIAS
:
663 case a_NAG_T_FILETYPE
:{
666 for(cp
= ngp
->ng_id
; (c
= *cp
) != '\0'; ++cp
)
673 if((nt
& a_NAG_T_MASK
) == a_NAG_T_ALIAS
){
674 struct a_nag_grp_names_head
*ngnhp
;
676 a_NAG_GP_TO_SUBCLASS(ngnhp
, ngp
);
677 ngnhp
->ngnh_head
= NULL
;
680 else if(nt
& a_NAG_T_REGEX
){
682 struct a_nag_grp_regex
*ngrp
;
684 a_NAG_GP_TO_SUBCLASS(ngrp
, ngp
);
686 if((s
= regcomp(&ngrp
->ngr_regex
, id
,
687 REG_EXTENDED
| REG_ICASE
| REG_NOSUB
)) != 0){
688 n_err(_("Invalid regular expression: %s: %s\n"),
689 n_shexp_quote_cp(id
, FAL0
), n_regex_err_to_doc(NULL
, s
));
694 ngrp
->ngr_mygroup
= ngp
;
695 a_nag_mlmux_linkin(ngp
);
697 #endif /* HAVE_REGEX */
699 ngp
->ng_next
= *ngl
.ngl_slot
;
707 a_nag_group_del(enum a_nag_type nt
, char const *id
){
708 struct a_nag_group_lookup ngl
;
709 struct a_nag_group
*ngp
;
713 xnt
= nt
& a_NAG_T_MASK
;
715 /* Delete 'em all? */
716 if(id
[0] == '*' && id
[1] == '\0'){
717 for(ngp
= a_nag_group_go_first(nt
, &ngl
); ngp
!= NULL
;)
718 ngp
= ((ngp
->ng_type
& a_NAG_T_MASK
) == xnt
) ? a_nag__group_del(&ngl
)
719 : a_nag_group_go_next(&ngl
);
720 ngp
= (struct a_nag_group
*)TRU1
;
721 }else if((ngp
= a_nag_group_lookup(nt
, &ngl
, id
)) != NULL
){
722 if(ngp
->ng_type
& xnt
)
723 a_nag__group_del(&ngl
);
728 return (ngp
!= NULL
);
731 static struct a_nag_group
*
732 a_nag__group_del(struct a_nag_group_lookup
*nglp
){
733 struct a_nag_group
*x
, *ngp
;
736 /* Overly complicated: link off this node, step ahead to next.. */
738 if((ngp
= nglp
->ngl_slot_last
) != NULL
)
739 ngp
= (ngp
->ng_next
= x
->ng_next
);
741 nglp
->ngl_slot_last
= NULL
;
742 ngp
= (*nglp
->ngl_slot
= x
->ng_next
);
745 struct a_nag_group
**ngpa
;
747 for(ngpa
= &nglp
->ngl_htable
[HSHSIZE
]; ++nglp
->ngl_slot
< ngpa
;)
748 if((ngp
= *nglp
->ngl_slot
) != NULL
)
752 nglp
->ngl_group
= ngp
;
754 if((x
->ng_type
& a_NAG_T_MASK
) == a_NAG_T_ALIAS
)
757 else if(x
->ng_type
& a_NAG_T_REGEX
){
758 struct a_nag_grp_regex
*ngrp
;
760 a_NAG_GP_TO_SUBCLASS(ngrp
, x
);
762 regfree(&ngrp
->ngr_regex
);
763 a_nag_mlmux_linkout(x
);
773 a_nag__names_del(struct a_nag_group
*ngp
){
774 struct a_nag_grp_names_head
*ngnhp
;
775 struct a_nag_grp_names
*ngnp
;
778 a_NAG_GP_TO_SUBCLASS(ngnhp
, ngp
);
780 for(ngnp
= ngnhp
->ngnh_head
; ngnp
!= NULL
;){
781 struct a_nag_grp_names
*x
;
784 ngnp
= ngnp
->ngn_next
;
791 a_nag_group_print_all(enum a_nag_type nt
, char const *varname
){
796 struct a_nag_group
const *ngp
;
798 struct a_nag_group
**ngpa
;
804 n_string_creat_auto(&s
);
806 xnt
= nt
& a_NAG_T_PRINT_MASK
;
808 switch(xnt
& a_NAG_T_MASK
){
809 case a_NAG_T_ALTERNATES
:
810 tname
= "alternates";
811 ngpa
= a_nag_alternates_heads
;
814 case a_NAG_T_COMMANDALIAS
:
815 tname
= "commandalias";
816 ngpa
= a_nag_commandalias_heads
;
820 ngpa
= a_nag_alias_heads
;
824 ngpa
= a_nag_mlist_heads
;
826 case a_NAG_T_SHORTCUT
:
828 ngpa
= a_nag_shortcut_heads
;
830 case a_NAG_T_CHARSETALIAS
:
831 tname
= "charsetalias";
832 ngpa
= a_nag_charsetalias_heads
;
834 case a_NAG_T_FILETYPE
:
836 ngpa
= a_nag_filetype_heads
;
841 for(i
= h
= 0; h
< HSHSIZE
; ++h
)
842 for(ngp
= ngpa
[h
]; ngp
!= NULL
; ngp
= ngp
->ng_next
)
843 if((ngp
->ng_type
& a_NAG_T_PRINT_MASK
) == xnt
)
847 fprintf(n_stdout
, _("# no %s registered\n"), tname
);
851 ida
= n_autorec_alloc(i
* sizeof *ida
);
853 /* Create alpha sorted array of entries */
854 for(i
= h
= 0; h
< HSHSIZE
; ++h
)
855 for(ngp
= ngpa
[h
]; ngp
!= NULL
; ngp
= ngp
->ng_next
)
856 if((ngp
->ng_type
& a_NAG_T_PRINT_MASK
) == xnt
)
857 ida
[i
++] = ngp
->ng_id
;
859 qsort(ida
, i
, sizeof *ida
, &a_nag__group_print_qsorter
);
864 else if((fp
= Ftmp(NULL
, "nagprint", OF_RDWR
| OF_UNLINK
| OF_REGISTER
)
868 /* Create visual result */
871 switch(xnt
& a_NAG_T_MASK
){
872 case a_NAG_T_ALTERNATES
:
882 for(i
= 0; ida
[i
] != NULL
; ++i
)
883 lines
+= a_nag_group_print(a_nag_group_find(nt
, ida
[i
]), fp
, &s
);
886 if(varname
== NULL
&& (nt
& a_NAG_T_MASK
) == a_NAG_T_MLIST
){
887 if(nt
& a_NAG_T_SUBSCRIBE
)
888 i
= (ui32_t
)a_nag_mlsub_size
, h
= (ui32_t
)a_nag_mlsub_hits
;
890 i
= (ui32_t
)a_nag_mlist_size
, h
= (ui32_t
)a_nag_mlist_hits
;
892 if(i
> 0 && (n_poption
& n_PO_D_V
)){
894 fprintf(fp
, _("# %s list regex(7) total: %u entries, %u hits\n"),
895 (nt
& a_NAG_T_SUBSCRIBE
? _("Subscribed") : _("Non-subscribed")),
902 switch(xnt
& a_NAG_T_MASK
){
903 case a_NAG_T_ALTERNATES
:
913 if(varname
== NULL
&& fp
!= n_stdout
){
915 page_or_print(fp
, lines
);
921 tname
= n_string_cp(&s
);
922 if(n_var_vset(varname
, (uintptr_t)tname
))
925 n_pstate_err_no
= n_ERR_NOTSUP
;
928 return (varname
== NULL
);
932 a_nag__group_print_qsorter(void const *a
, void const *b
){
936 rv
= strcmp(*(char**)n_UNCONST(a
), *(char**)n_UNCONST(b
));
942 a_nag_group_print(struct a_nag_group
const *ngp
, FILE *fo
,
943 struct n_string
*vputsp
){
950 switch(ngp
->ng_type
& a_NAG_T_MASK
){
951 case a_NAG_T_ALTERNATES
:{
953 fprintf(fo
, " %s", ngp
->ng_id
);
955 if(vputsp
->s_len
> 0)
956 vputsp
= n_string_push_c(vputsp
, ' ');
957 /*vputsp =*/ n_string_push_cp(vputsp
, ngp
->ng_id
);
961 case a_NAG_T_COMMANDALIAS
:{
962 struct a_nag_cmd_alias
*ncap
;
964 assert(fo
!= NULL
); /* xxx no vput yet */
965 a_NAG_GP_TO_SUBCLASS(ncap
, ngp
);
966 fprintf(fo
, "commandalias %s %s\n",
967 n_shexp_quote_cp(ngp
->ng_id
, TRU1
),
968 n_shexp_quote_cp(ncap
->nca_expand
.s
, TRU1
));
971 struct a_nag_grp_names_head
*ngnhp
;
972 struct a_nag_grp_names
*ngnp
;
974 assert(fo
!= NULL
); /* xxx no vput yet */
975 fprintf(fo
, "alias %s ", ngp
->ng_id
);
977 a_NAG_GP_TO_SUBCLASS(ngnhp
, ngp
);
978 if((ngnp
= ngnhp
->ngnh_head
) != NULL
) { /* xxx always 1+ entries */
980 struct a_nag_grp_names
*x
;
983 ngnp
= ngnp
->ngn_next
;
984 fprintf(fo
, " \"%s\"", string_quote(x
->ngn_id
)); /* TODO shexp */
985 }while(ngnp
!= NULL
);
990 assert(fo
!= NULL
); /* xxx no vput yet */
992 if((ngp
->ng_type
& a_NAG_T_REGEX
) && (n_poption
& n_PO_D_V
)){
994 struct a_nag_grp_regex
*lp
, *ngrp
;
996 lp
= (ngp
->ng_type
& a_NAG_T_SUBSCRIBE
? a_nag_mlsub_regex
997 : a_nag_mlist_regex
);
998 a_NAG_GP_TO_SUBCLASS(ngrp
, ngp
);
999 for(i
= 1; lp
!= ngrp
; lp
= lp
->ngr_next
)
1001 fprintf(fo
, "# regex(7): hits %" PRIuZ
", sort %" PRIuZ
".\n ",
1006 fprintf(fo
, "wysh %s %s\n",
1007 (ngp
->ng_type
& a_NAG_T_SUBSCRIBE
? "mlsubscribe" : "mlist"),
1008 n_shexp_quote_cp(ngp
->ng_id
, TRU1
));
1010 case a_NAG_T_SHORTCUT
:
1011 assert(fo
!= NULL
); /* xxx no vput yet */
1012 a_NAG_GP_TO_SUBCLASS(cp
, ngp
);
1013 fprintf(fo
, "wysh shortcut %s %s\n",
1014 ngp
->ng_id
, n_shexp_quote_cp(cp
, TRU1
));
1016 case a_NAG_T_CHARSETALIAS
:
1017 assert(fo
!= NULL
); /* xxx no vput yet */
1018 a_NAG_GP_TO_SUBCLASS(cp
, ngp
);
1019 fprintf(fo
, "charsetalias %s %s\n",
1020 n_shexp_quote_cp(ngp
->ng_id
, TRU1
), n_shexp_quote_cp(cp
, TRU1
));
1022 case a_NAG_T_FILETYPE
:{
1023 struct a_nag_file_type
*nftp
;
1025 assert(fo
!= NULL
); /* xxx no vput yet */
1026 a_NAG_GP_TO_SUBCLASS(nftp
, ngp
);
1027 fprintf(fo
, "filetype %s %s %s\n",
1028 n_shexp_quote_cp(ngp
->ng_id
, TRU1
),
1029 n_shexp_quote_cp(nftp
->nft_load
.s
, TRU1
),
1030 n_shexp_quote_cp(nftp
->nft_save
.s
, TRU1
));
1038 a_nag_mlmux(enum a_nag_type nt
, char const **argv
){
1039 struct a_nag_group
*ngp
;
1045 n_UNINIT(ecp
, NULL
);
1048 a_nag_group_print_all(nt
, NULL
);
1050 if((ngp
= a_nag_group_find(nt
, *argv
)) != NULL
){
1051 if(nt
& a_NAG_T_SUBSCRIBE
){
1052 if(!(ngp
->ng_type
& a_NAG_T_SUBSCRIBE
)){
1053 a_NAG_MLMUX_LINKOUT(ngp
);
1054 ngp
->ng_type
|= a_NAG_T_SUBSCRIBE
;
1055 a_NAG_MLMUX_LINKIN(ngp
);
1057 ecp
= N_("Mailing-list already `mlsubscribe'd: %s\n");
1061 ecp
= N_("Mailing-list already `mlist'ed: %s\n");
1064 }else if(a_nag_group_fetch(nt
, *argv
, 0) == NULL
){
1065 ecp
= N_("Failed to create storage for mailing-list: %s\n");
1067 n_err(V_(ecp
), n_shexp_quote_cp(*argv
, FAL0
));
1070 }while(*++argv
!= NULL
);
1077 a_nag_unmlmux(enum a_nag_type nt
, char const **argv
){
1078 struct a_nag_group
*ngp
;
1084 for(; *argv
!= NULL
; ++argv
){
1085 if(nt
& a_NAG_T_SUBSCRIBE
){
1086 struct a_nag_group_lookup ngl
;
1089 if(!(isaster
= (**argv
== '*')))
1090 ngp
= a_nag_group_find(nt
, *argv
);
1091 else if((ngp
= a_nag_group_go_first(nt
, &ngl
)) == NULL
)
1093 else if(ngp
!= NULL
&& !(ngp
->ng_type
& a_NAG_T_SUBSCRIBE
))
1098 if(ngp
->ng_type
& a_NAG_T_SUBSCRIBE
){
1099 a_NAG_MLMUX_LINKOUT(ngp
);
1100 ngp
->ng_type
&= ~a_NAG_T_SUBSCRIBE
;
1101 a_NAG_MLMUX_LINKIN(ngp
);
1105 while((ngp
= a_nag_group_go_next(&ngl
)) != NULL
&&
1106 !(ngp
->ng_type
& a_NAG_T_SUBSCRIBE
))
1112 n_err(_("Mailing-list not `mlsubscribe'd: %s\n"),
1113 n_shexp_quote_cp(*argv
, FAL0
));
1118 }else if(a_nag_group_del(nt
, *argv
))
1120 n_err(_("No such mailing-list: %s\n"), n_shexp_quote_cp(*argv
, FAL0
));
1129 a_nag_mlmux_linkin(struct a_nag_group
*ngp
){
1130 struct a_nag_grp_regex
**lpp
, *ngrp
, *lhp
;
1133 if(ngp
->ng_type
& a_NAG_T_SUBSCRIBE
){
1134 lpp
= &a_nag_mlsub_regex
;
1137 lpp
= &a_nag_mlist_regex
;
1141 a_NAG_GP_TO_SUBCLASS(ngrp
, ngp
);
1143 if((lhp
= *lpp
) != NULL
){
1144 (ngrp
->ngr_last
= lhp
->ngr_last
)->ngr_next
= ngrp
;
1145 (ngrp
->ngr_next
= lhp
)->ngr_last
= ngrp
;
1147 *lpp
= ngrp
->ngr_last
= ngrp
->ngr_next
= ngrp
;
1153 a_nag_mlmux_linkout(struct a_nag_group
*ngp
){
1154 struct a_nag_grp_regex
*ngrp
, **lpp
;
1157 a_NAG_GP_TO_SUBCLASS(ngrp
, ngp
);
1159 if(ngp
->ng_type
& a_NAG_T_SUBSCRIBE
){
1160 lpp
= &a_nag_mlsub_regex
;
1162 a_nag_mlsub_hits
-= ngrp
->ngr_hits
;
1164 lpp
= &a_nag_mlist_regex
;
1166 a_nag_mlist_hits
-= ngrp
->ngr_hits
;
1169 if(ngrp
->ngr_next
== ngrp
)
1172 (ngrp
->ngr_last
->ngr_next
= ngrp
->ngr_next
)->ngr_last
= ngrp
->ngr_last
;
1174 *lpp
= ngrp
->ngr_next
;
1178 #endif /* HAVE_REGEX */
1181 nalloc(char const *str
, enum gfield ntype
)
1183 struct n_addrguts ag
;
1187 assert(!(ntype
& GFULLEXTRA
) || (ntype
& GFULL
) != 0);
1189 str
= n_addrspec_with_guts(&ag
, str
,
1190 ((ntype
& (GFULL
| GSKIN
| GREF
)) != 0), FAL0
);
1193 np = NULL; TODO We cannot return NULL,
1194 goto jleave; TODO thus handle failures in here!
1199 if (!(ag
.ag_n_flags
& NAME_NAME_SALLOC
)) {
1200 ag
.ag_n_flags
|= NAME_NAME_SALLOC
;
1201 np
= n_autorec_alloc(sizeof(*np
) + ag
.ag_slen
+1);
1202 memcpy(np
+ 1, ag
.ag_skinned
, ag
.ag_slen
+1);
1203 ag
.ag_skinned
= (char*)(np
+ 1);
1205 np
= n_autorec_alloc(sizeof *np
);
1210 np
->n_fullname
= np
->n_name
= ag
.ag_skinned
;
1211 np
->n_fullextra
= NULL
;
1212 np
->n_flags
= ag
.ag_n_flags
;
1214 if (ntype
& GFULL
) {
1215 if (ag
.ag_ilen
== ag
.ag_slen
1217 && !(ag
.ag_n_flags
& NAME_IDNA
)
1221 if (ag
.ag_n_flags
& NAME_ADDRSPEC_ISFILEORPIPE
)
1224 /* n_fullextra is only the complete name part without address.
1225 * Beware of "-r '<abc@def>'", don't treat that as FULLEXTRA */
1226 if ((ntype
& GFULLEXTRA
) && ag
.ag_ilen
> ag
.ag_slen
+ 2) {
1227 size_t s
= ag
.ag_iaddr_start
, e
= ag
.ag_iaddr_aend
, i
;
1230 if (s
== 0 || str
[--s
] != '<' || str
[e
++] != '>')
1231 goto jskipfullextra
;
1233 in
.s
= n_lofi_alloc(s
+ 1 + i
+1);
1234 while(s
> 0 && blankchar(str
[s
- 1]))
1236 memcpy(in
.s
, str
, s
);
1239 while (blankchar(str
[e
])) {
1245 memcpy(&in
.s
[s
], &str
[e
], i
);
1248 in
.s
[in
.l
= s
] = '\0';
1249 mime_fromhdr(&in
, &out
, /* TODO TD_ISPR |*/ TD_ICONV
);
1251 for (cp
= out
.s
, i
= out
.l
; i
> 0 && spacechar(*cp
); --i
, ++cp
)
1253 while (i
> 0 && spacechar(cp
[i
- 1]))
1255 np
->n_fullextra
= savestrbuf(cp
, i
);
1262 /* n_fullname depends on IDNA conversion */
1264 if (!(ag
.ag_n_flags
& NAME_IDNA
)) {
1266 in
.s
= n_UNCONST(str
);
1270 /* The domain name was IDNA and has been converted. We also have to
1271 * ensure that the domain name in .n_fullname is replaced with the
1272 * converted version, since MIME doesn't perform encoding of addrs */
1273 /* TODO This definetely doesn't belong here! */
1274 size_t l
= ag
.ag_iaddr_start
,
1275 lsuff
= ag
.ag_ilen
- ag
.ag_iaddr_aend
;
1276 in
.s
= n_lofi_alloc(l
+ ag
.ag_slen
+ lsuff
+1);
1277 memcpy(in
.s
, str
, l
);
1278 memcpy(in
.s
+ l
, ag
.ag_skinned
, ag
.ag_slen
);
1280 memcpy(in
.s
+ l
, str
+ ag
.ag_iaddr_aend
, lsuff
);
1286 mime_fromhdr(&in
, &out
, /* TODO TD_ISPR |*/ TD_ICONV
);
1287 np
->n_fullname
= savestr(out
.s
);
1290 if (ag
.ag_n_flags
& NAME_IDNA
)
1300 ndup(struct name
*np
, enum gfield ntype
)
1305 if ((ntype
& (GFULL
| GSKIN
)) && !(np
->n_flags
& NAME_SKINNED
)) {
1306 nnp
= nalloc(np
->n_name
, ntype
);
1310 nnp
= n_autorec_alloc(sizeof *np
);
1311 nnp
->n_flink
= nnp
->n_blink
= NULL
;
1312 nnp
->n_type
= ntype
;
1313 nnp
->n_flags
= np
->n_flags
| NAME_NAME_SALLOC
;
1314 nnp
->n_name
= savestr(np
->n_name
);
1315 if (np
->n_name
== np
->n_fullname
|| !(ntype
& (GFULL
| GSKIN
))) {
1316 nnp
->n_fullname
= nnp
->n_name
;
1317 nnp
->n_fullextra
= NULL
;
1319 nnp
->n_fullname
= savestr(np
->n_fullname
);
1320 nnp
->n_fullextra
= (np
->n_fullextra
== NULL
) ? NULL
1321 : savestr(np
->n_fullextra
);
1329 cat(struct name
*n1
, struct name
*n2
){
1337 if(n2
== NULL
|| (n2
->n_type
& GDEL
))
1340 while(tail
->n_flink
!= NULL
)
1341 tail
= tail
->n_flink
;
1351 namelist_dup(struct name
const *np
, enum gfield ntype
){
1352 struct name
*nlist
, *xnp
;
1355 for(nlist
= xnp
= NULL
; np
!= NULL
; np
= np
->n_flink
){
1358 if(!(np
->n_type
& GDEL
)){
1359 x
= ndup(n_UNCONST(np
), (np
->n_type
& ~GMASK
) | ntype
);
1360 if((x
->n_blink
= xnp
) == NULL
)
1372 count(struct name
const *np
)
1377 for (c
= 0; np
!= NULL
; np
= np
->n_flink
)
1378 if (!(np
->n_type
& GDEL
))
1385 count_nonlocal(struct name
const *np
)
1390 for (c
= 0; np
!= NULL
; np
= np
->n_flink
)
1391 if (!(np
->n_type
& GDEL
) && !(np
->n_flags
& NAME_ADDRSPEC_ISFILEORPIPE
))
1398 extract(char const *line
, enum gfield ntype
)
1403 rv
= a_nag_extract1(line
, ntype
, " \t,", 0);
1409 lextract(char const *line
, enum gfield ntype
)
1414 rv
= ((line
!= NULL
&& strpbrk(line
, ",\"\\(<|"))
1415 ? a_nag_extract1(line
, ntype
, ",", 1) : extract(line
, ntype
));
1421 detract(struct name
*np
, enum gfield ntype
)
1432 flags
= ntype
& (GCOMMA
| GNAMEONLY
);
1433 ntype
&= ~(GCOMMA
| GNAMEONLY
);
1436 for (p
= np
; p
!= NULL
; p
= p
->n_flink
) {
1437 if (ntype
&& (p
->n_type
& GMASK
) != ntype
)
1439 s
+= strlen(flags
& GNAMEONLY
? p
->n_name
: p
->n_fullname
) +1;
1447 topp
= n_autorec_alloc(s
);
1449 for (p
= np
; p
!= NULL
; p
= p
->n_flink
) {
1450 if (ntype
&& (p
->n_type
& GMASK
) != ntype
)
1452 cp
= sstpcpy(cp
, (flags
& GNAMEONLY
? p
->n_name
: p
->n_fullname
));
1453 if ((flags
& GCOMMA
) && p
->n_flink
!= NULL
)
1458 if ((flags
& GCOMMA
) && *--cp
== ',')
1466 grab_names(enum n_go_input_flags gif
, char const *field
, struct name
*np
,
1467 int comma
, enum gfield gflags
)
1473 np
= lextract(n_go_input_cp(gif
, field
, detract(np
, comma
)), gflags
);
1474 for (nq
= np
; nq
!= NULL
; nq
= nq
->n_flink
)
1475 if (is_addr_invalid(nq
, EACM_NONE
))
1482 name_is_same_domain(struct name
const *n1
, struct name
const *n2
)
1484 char const *d1
, *d2
;
1488 d1
= strrchr(n1
->n_name
, '@');
1489 d2
= strrchr(n2
->n_name
, '@');
1491 rv
= (d1
!= NULL
&& d2
!= NULL
) ? !asccasecmp(++d1
, ++d2
) : FAL0
;
1498 checkaddrs(struct name
*np
, enum expand_addr_check_mode eacm
,
1499 si8_t
*set_on_error
)
1504 for (n
= np
; n
!= NULL
; n
= n
->n_flink
) {
1507 if ((rv
= is_addr_invalid(n
, eacm
)) != 0) {
1508 if (set_on_error
!= NULL
)
1509 *set_on_error
|= rv
; /* don't loose -1! */
1510 else if (eacm
& EAF_MAYKEEP
) /* TODO HACK! See definition! */
1513 n
->n_blink
->n_flink
= n
->n_flink
;
1515 n
->n_flink
->n_blink
= n
->n_blink
;
1525 namelist_vaporise_head(struct header
*hp
, enum expand_addr_check_mode eacm
,
1526 bool_t metoo
, si8_t
*set_on_error
)
1528 /* TODO namelist_vaporise_head() is incredibly expensive and redundant */
1529 struct name
*tolist
, *np
, **npp
;
1532 tolist
= cat(hp
->h_to
, cat(hp
->h_cc
, hp
->h_bcc
));
1533 hp
->h_to
= hp
->h_cc
= hp
->h_bcc
= NULL
;
1535 tolist
= usermap(tolist
, metoo
);
1536 tolist
= n_alternates_remove(tolist
, TRU1
);
1537 tolist
= elide(checkaddrs(tolist
, eacm
, set_on_error
));
1539 for (np
= tolist
; np
!= NULL
; np
= np
->n_flink
) {
1540 switch (np
->n_type
& (GDEL
| GMASK
)) {
1541 case GTO
: npp
= &hp
->h_to
; break;
1542 case GCC
: npp
= &hp
->h_cc
; break;
1543 case GBCC
: npp
= &hp
->h_bcc
; break;
1546 *npp
= cat(*npp
, ndup(np
, np
->n_type
| GFULL
));
1553 usermap(struct name
*names
, bool_t force_metoo
){
1554 struct a_nag_group
*ngp
;
1555 struct name
*nlist
, *nlist_tail
, *np
, *cp
;
1559 metoo
= (force_metoo
|| ok_blook(metoo
));
1560 nlist
= nlist_tail
= NULL
;
1563 for(; np
!= NULL
; np
= cp
){
1564 assert(!(np
->n_type
& GDEL
)); /* TODO legacy */
1567 if(is_fileorpipe_addr(np
) ||
1568 (ngp
= a_nag_group_find(a_NAG_T_ALIAS
, np
->n_name
)) == NULL
){
1569 if((np
->n_blink
= nlist_tail
) != NULL
)
1570 nlist_tail
->n_flink
= np
;
1576 nlist
= a_nag_gexpand(0, nlist
, ngp
, metoo
, np
->n_type
);
1577 if((nlist_tail
= nlist
) != NULL
)
1578 while(nlist_tail
->n_flink
!= NULL
)
1579 nlist_tail
= nlist_tail
->n_flink
;
1587 elide(struct name
*names
)
1590 struct name
*nlist
, *np
, **nparr
;
1598 /* Throw away all deleted nodes */
1599 for(np
= NULL
, i
= 0; names
!= NULL
; names
= names
->n_flink
)
1600 if(!(names
->n_type
& GDEL
)){
1601 names
->n_blink
= np
;
1603 np
->n_flink
= names
;
1609 if(nlist
== NULL
|| i
== 1)
1613 /* Create a temporay array and sort that */
1614 nparr
= n_lofi_alloc(sizeof(*nparr
) * i
);
1616 for(i
= 0, np
= nlist
; np
!= NULL
; np
= np
->n_flink
)
1619 qsort(nparr
, i
, sizeof *nparr
, &a_nag_elide_qsort
);
1621 /* Remove duplicates XXX speedup, or list_uniq()! */
1622 for(j
= 0, --i
; j
< i
;){
1623 if(asccasecmp(nparr
[j
]->n_name
, nparr
[k
= j
+ 1]->n_name
))
1627 nparr
[k
] = nparr
[k
+ 1];
1632 /* Throw away all list members which are not part of the array.
1633 * Note this keeps the original, possibly carefully crafted, order of the
1634 * addressees, thus */
1635 for(np
= nlist
; np
!= NULL
; np
= np
->n_flink
){
1636 for(j
= 0; j
<= i
; ++j
)
1643 nlist
= np
->n_flink
;
1646 np
->n_blink
->n_flink
= np
->n_flink
;
1647 if(np
->n_flink
!= NULL
)
1648 np
->n_flink
->n_blink
= np
->n_blink
;
1659 c_alternates(void *vp
){
1660 struct a_nag_group
*ngp
;
1661 char const *varname
, *ccp
;
1665 n_pstate_err_no
= n_ERR_NONE
;
1668 varname
= (n_pstate
& n_PS_ARGMOD_VPUT
) ? *argv
++ : NULL
;
1671 if(!a_nag_group_print_all(a_NAG_T_ALTERNATES
, varname
))
1675 n_err(_("`alternates': `vput' only supported for show mode\n"));
1677 /* Delete the old set to "declare a list", if *posix* */
1679 a_nag_group_del(a_NAG_T_ALTERNATES
, n_star
);
1681 while((ccp
= *argv
++) != NULL
){
1685 if((np
= lextract(ccp
, GSKIN
)) == NULL
|| np
->n_flink
!= NULL
||
1686 (np
= checkaddrs(np
, EACM_STRICT
, NULL
)) == NULL
){
1687 n_err(_("Invalid `alternates' argument: %s\n"),
1688 n_shexp_quote_cp(ccp
, FAL0
));
1689 n_pstate_err_no
= n_ERR_INVAL
;
1696 if((ngp
= a_nag_group_fetch(a_NAG_T_ALTERNATES
, ccp
, l
)) == NULL
){
1697 n_err(_("Failed to create storage for alternates: %s\n"),
1698 n_shexp_quote_cp(ccp
, FAL0
));
1699 n_pstate_err_no
= n_ERR_NOMEM
;
1705 return (vp
!= NULL
? 0 : 1);
1709 c_unalternates(void *vp
){
1717 do if(!a_nag_group_del(a_NAG_T_ALTERNATES
, *argv
)){
1718 n_err(_("No such `alternates': %s\n"), n_shexp_quote_cp(*argv
, FAL0
));
1720 }while(*++argv
!= NULL
);
1726 n_alternates_remove(struct name
*np
, bool_t keep_single
){
1727 /* XXX keep a single pointer, initial null, and immediate remove nodes
1728 * XXX on successful match unless keep single and that pointer null! */
1729 struct a_nag_group_lookup ngl
;
1730 struct a_nag_group
*ngp
;
1731 struct name
*xp
, *newnp
;
1734 /* Delete the temporary bit from all */
1735 for(xp
= np
; xp
!= NULL
; xp
= xp
->n_flink
)
1736 xp
->n_flags
&= ~(ui32_t
)SI32_MIN
;
1738 /* Mark all possible alternate names (xxx sic: instead walk over namelist
1739 * and hash-lookup alternate instead (unless *allnet*) */
1740 for(ngp
= a_nag_group_go_first(a_NAG_T_ALTERNATES
, &ngl
); ngp
!= NULL
;
1741 ngp
= a_nag_group_go_next(&ngl
))
1742 np
= a_nag_namelist_mark_name(np
, ngp
->ng_id
);
1744 np
= a_nag_namelist_mark_name(np
, ok_vlook(LOGNAME
));
1746 if((xp
= extract(ok_vlook(sender
), GEXTRA
| GSKIN
)) != NULL
){
1747 /* TODO check_from_and_sender(): drop; *sender*: only one name!
1748 * TODO At assignment time, as VIP var? */
1750 np
= a_nag_namelist_mark_name(np
, xp
->n_name
);
1751 while((xp
= xp
->n_flink
) != NULL
);
1752 }else for(xp
= lextract(ok_vlook(from
), GEXTRA
| GSKIN
); xp
!= NULL
;
1754 np
= a_nag_namelist_mark_name(np
, xp
->n_name
);
1757 char const *v15compat
;
1759 if((v15compat
= ok_vlook(replyto
)) != NULL
){
1760 n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
1761 for(xp
= lextract(v15compat
, GEXTRA
| GSKIN
); xp
!= NULL
;
1763 np
= a_nag_namelist_mark_name(np
, xp
->n_name
);
1767 for(xp
= lextract(ok_vlook(reply_to
), GEXTRA
| GSKIN
); xp
!= NULL
;
1769 np
= a_nag_namelist_mark_name(np
, xp
->n_name
);
1771 /* Clean the list by throwing away all deleted or marked (but one) nodes */
1772 for(xp
= newnp
= NULL
; np
!= NULL
; np
= np
->n_flink
){
1773 if(np
->n_type
& GDEL
)
1775 if(np
->n_flags
& (ui32_t
)SI32_MIN
){
1787 xp
->n_flags
&= ~(ui32_t
)SI32_MIN
;
1798 n_is_myname(char const *name
){
1799 struct a_nag_group_lookup ngl
;
1800 struct a_nag_group
*ngp
;
1804 if(a_nag_is_same_name(ok_vlook(LOGNAME
), name
))
1807 if(!ok_blook(allnet
)){
1808 if(a_nag_group_lookup(a_NAG_T_ALTERNATES
, &ngl
, name
) != NULL
)
1811 for(ngp
= a_nag_group_go_first(a_NAG_T_ALTERNATES
, &ngl
); ngp
!= NULL
;
1812 ngp
= a_nag_group_go_next(&ngl
))
1813 if(a_nag_is_same_name(ngp
->ng_id
, name
))
1817 for(xp
= lextract(ok_vlook(from
), GEXTRA
| GSKIN
); xp
!= NULL
;
1819 if(a_nag_is_same_name(xp
->n_name
, name
))
1823 char const *v15compat
;
1825 if((v15compat
= ok_vlook(replyto
)) != NULL
){
1826 n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
1827 for(xp
= lextract(v15compat
, GEXTRA
| GSKIN
); xp
!= NULL
;
1829 if(a_nag_is_same_name(xp
->n_name
, name
))
1834 for(xp
= lextract(ok_vlook(reply_to
), GEXTRA
| GSKIN
); xp
!= NULL
;
1836 if(a_nag_is_same_name(xp
->n_name
, name
))
1839 for(xp
= extract(ok_vlook(sender
), GEXTRA
| GSKIN
); xp
!= NULL
;
1841 if(a_nag_is_same_name(xp
->n_name
, name
))
1847 return (name
!= NULL
);
1851 c_addrcodec(void *vp
){
1852 struct n_addrguts ag
;
1854 struct n_string s_b
, *sp
;
1857 char const **argv
, *varname
, *act
, *cp
;
1860 sp
= n_string_creat_auto(&s_b
);
1862 varname
= (n_pstate
& n_PS_ARGMOD_VPUT
) ? *argv
++ : NULL
;
1865 for(cp
= act
; *cp
!= '\0' && !blankspacechar(*cp
); ++cp
)
1876 alen
= PTR2SIZE(cp
- act
);
1880 trims
.l
= strlen(trims
.s
= n_UNCONST(cp
));
1881 cp
= savestrbuf(n_str_trim(&trims
, n_STR_TRIM_BOTH
)->s
, trims
.l
);
1882 if(trims
.l
<= UIZ_MAX
/ 4)
1884 sp
= n_string_reserve(sp
, trims
.l
);
1886 n_pstate_err_no
= n_ERR_NONE
;
1888 if(is_ascncaseprefix(act
, "encode", alen
)){
1889 /* This function cannot be a simple nalloc() wrapper even later on, since
1890 * we may need to turn any ", () or \ into quoted-pairs */
1893 while((c
= *cp
++) != '\0'){
1894 if(((c
== '(' || c
== ')') && mode
< 1) || (c
== '"' && mode
< 2) ||
1895 (c
== '\\' && mode
< 3))
1896 sp
= n_string_push_c(sp
, '\\');
1897 sp
= n_string_push_c(sp
, c
);
1900 if(n_addrspec_with_guts(&ag
, n_string_cp(sp
), TRU1
, TRU1
) == NULL
||
1901 (ag
.ag_n_flags
& (NAME_ADDRSPEC_ISADDR
| NAME_ADDRSPEC_INVALID
)
1902 ) != NAME_ADDRSPEC_ISADDR
){
1904 n_pstate_err_no
= n_ERR_INVAL
;
1909 np
= nalloc(ag
.ag_input
, GTO
| GFULL
| GSKIN
);
1910 cp
= np
->n_fullname
;
1912 }else if(mode
== 0){
1913 if(is_ascncaseprefix(act
, "decode", alen
)){
1916 while((c
= *cp
++) != '\0'){
1919 sp
= n_string_push_c(sp
, '(');
1920 act
= skip_comment(cp
);
1922 sp
= n_string_push_buf(sp
, cp
, PTR2SIZE(act
- cp
));
1923 sp
= n_string_push_c(sp
, ')');
1928 if((c
= *cp
++) == '"')
1930 if(c
== '\\' && (c
= *cp
) != '\0')
1932 sp
= n_string_push_c(sp
, c
);
1936 if(c
== '\\' && (c
= *cp
++) == '\0')
1938 sp
= n_string_push_c(sp
, c
);
1942 cp
= n_string_cp(sp
);
1943 }else if(is_ascncaseprefix(act
, "skin", alen
) ||
1944 (mode
= 1, is_ascncaseprefix(act
, "skinlist", alen
))){
1945 /* Let's just use the is-single-address hack for this one, too.. */
1946 if(n_addrspec_with_guts(&ag
, cp
, TRU1
, TRU1
) == NULL
||
1947 (ag
.ag_n_flags
& (NAME_ADDRSPEC_ISADDR
| NAME_ADDRSPEC_INVALID
)
1948 ) != NAME_ADDRSPEC_ISADDR
){
1949 n_pstate_err_no
= n_ERR_INVAL
;
1954 np
= nalloc(ag
.ag_input
, GTO
| GFULL
| GSKIN
);
1957 if(mode
== 1 && is_mlist(cp
, FAL0
) != MLIST_OTHER
)
1958 n_pstate_err_no
= n_ERR_EXIST
;
1965 if(varname
== NULL
){
1966 if(fprintf(n_stdout
, "%s\n", cp
) < 0){
1967 n_pstate_err_no
= n_err_no
;
1970 }else if(!n_var_vset(varname
, (uintptr_t)cp
)){
1971 n_pstate_err_no
= n_ERR_NOTSUP
;
1977 return (vp
!= NULL
? 0 : 1);
1979 n_err(_("Synopsis: addrcodec: <[+[+[+]]]e[ncode]|d[ecode]|s[kin]> "
1980 "<rest-of-line>\n"));
1981 n_pstate_err_no
= n_ERR_INVAL
;
1987 c_commandalias(void *vp
){
1988 struct a_nag_group
*ngp
;
1989 char const **argv
, *ccp
;
1996 if((ccp
= *argv
) == NULL
){
1997 a_nag_group_print_all(a_NAG_T_COMMANDALIAS
, NULL
);
2001 /* Verify the name is a valid one, and not a command modifier.
2002 * NOTE: this list duplicates settings isolated somewhere else (go.c) */
2003 if(*ccp
== '\0' || *n_cmd_isolate(ccp
) != '\0' ||
2004 !asccasecmp(ccp
, "ignerr") || !asccasecmp(ccp
, "local") ||
2005 !asccasecmp(ccp
, "wysh") || !asccasecmp(ccp
, "vput") ||
2006 !asccasecmp(ccp
, "scope") || !asccasecmp(ccp
, "u")){
2007 n_err(_("`commandalias': not a valid command name: %s\n"),
2008 n_shexp_quote_cp(ccp
, FAL0
));
2013 if(argv
[1] == NULL
){
2014 if((ngp
= a_nag_group_find(a_NAG_T_COMMANDALIAS
, ccp
)) != NULL
)
2015 a_nag_group_print(ngp
, n_stdout
, NULL
);
2017 n_err(_("No such commandalias: %s\n"), n_shexp_quote_cp(ccp
, FAL0
));
2021 /* Because one hardly ever redefines, anything is stored in one chunk */
2025 /* Delete the old one, if any; don't get fooled to remove them all */
2026 if(ccp
[0] != '*' || ccp
[1] != '\0')
2027 a_nag_group_del(a_NAG_T_COMMANDALIAS
, ccp
);
2029 for(i
= len
= 0, ++argv
; argv
[i
] != NULL
; ++i
)
2030 len
+= strlen(argv
[i
]) + 1;
2034 if((ngp
= a_nag_group_fetch(a_NAG_T_COMMANDALIAS
, ccp
, len
)) == NULL
){
2035 n_err(_("Failed to create storage for commandalias: %s\n"),
2036 n_shexp_quote_cp(ccp
, FAL0
));
2039 struct a_nag_cmd_alias
*ncap
;
2041 a_NAG_GP_TO_SUBCLASS(ncap
, ngp
);
2042 a_NAG_GP_TO_SUBCLASS(cp
, ngp
);
2044 ncap
->nca_expand
.s
= cp
;
2045 ncap
->nca_expand
.l
= len
- 1;
2047 for(len
= 0; (ccp
= *argv
++) != NULL
;)
2048 if((i
= strlen(ccp
)) > 0){
2063 c_uncommandalias(void *vp
){
2071 do if(!a_nag_group_del(a_NAG_T_COMMANDALIAS
, *argv
)){
2072 n_err(_("No such `commandalias': %s\n"), n_shexp_quote_cp(*argv
, FAL0
));
2074 }while(*++argv
!= NULL
);
2080 n_commandalias_exists(char const *name
, struct str
const **expansion_or_null
){
2081 struct a_nag_group
*ngp
;
2084 if((ngp
= a_nag_group_find(a_NAG_T_COMMANDALIAS
, name
)) != NULL
){
2087 if(expansion_or_null
!= NULL
){
2088 struct a_nag_cmd_alias
*ncap
;
2090 a_NAG_GP_TO_SUBCLASS(ncap
, ngp
);
2091 *expansion_or_null
= &ncap
->nca_expand
;
2100 n_alias_is_valid_name(char const *name
){
2106 for(rv
= TRU1
, cp
= name
++; (c
= *cp
++) != '\0';)
2107 /* User names, plus things explicitly mentioned in Postfix aliases(5),
2108 * i.e., [[:alnum:]_#:@.-]+$?.
2109 * As extensions allow high-bit bytes, semicolon and period. */
2110 if(!alnumchar(c
) && c
!= '_' && c
!= '-' &&
2111 c
!= '#' && c
!= ':' && c
!= '@' &&
2112 !((ui8_t
)c
& 0x80) && c
!= '!' && c
!= '.'){
2113 if(c
== '$' && cp
!= name
&& *cp
== '\0')
2127 struct a_nag_group
*ngp
;
2133 n_UNINIT(ecp
, NULL
);
2136 a_nag_group_print_all(a_NAG_T_ALIAS
, NULL
);
2137 else if(!n_alias_is_valid_name(*argv
)){
2138 ecp
= N_("Not a valid alias name: %s\n");
2140 }else if(argv
[1] == NULL
){
2141 if((ngp
= a_nag_group_find(a_NAG_T_ALIAS
, *argv
)) != NULL
)
2142 a_nag_group_print(ngp
, n_stdout
, NULL
);
2144 ecp
= N_("No such alias: %s\n");
2147 }else if((ngp
= a_nag_group_fetch(a_NAG_T_ALIAS
, *argv
, 0)) == NULL
){
2148 ecp
= N_("Failed to create alias storage for: %s\n");
2150 n_err(V_(ecp
), n_shexp_quote_cp(*argv
, FAL0
));
2153 struct a_nag_grp_names
*ngnp_tail
, *ngnp
;
2154 struct a_nag_grp_names_head
*ngnhp
;
2156 a_NAG_GP_TO_SUBCLASS(ngnhp
, ngp
);
2158 if((ngnp_tail
= ngnhp
->ngnh_head
) != NULL
)
2159 while((ngnp
= ngnp_tail
->ngn_next
) != NULL
)
2162 for(++argv
; *argv
!= NULL
; ++argv
){
2165 i
= strlen(*argv
) +1;
2166 ngnp
= n_alloc(n_VSTRUCT_SIZEOF(struct a_nag_grp_names
, ngn_id
) + i
);
2167 if(ngnp_tail
!= NULL
)
2168 ngnp_tail
->ngn_next
= ngnp
;
2170 ngnhp
->ngnh_head
= ngnp
;
2172 ngnp
->ngn_next
= NULL
;
2173 memcpy(ngnp
->ngn_id
, *argv
, i
);
2189 do if(!a_nag_group_del(a_NAG_T_ALIAS
, *argv
)){
2190 n_err(_("No such alias: %s\n"), *argv
);
2192 }while(*++argv
!= NULL
);
2202 rv
= a_nag_mlmux(a_NAG_T_MLIST
, v
);
2212 rv
= a_nag_unmlmux(a_NAG_T_MLIST
, v
);
2218 c_mlsubscribe(void *v
){
2222 rv
= a_nag_mlmux(a_NAG_T_MLIST
| a_NAG_T_SUBSCRIBE
, v
);
2228 c_unmlsubscribe(void *v
){
2232 rv
= a_nag_unmlmux(a_NAG_T_MLIST
| a_NAG_T_SUBSCRIBE
, v
);
2238 is_mlist(char const *name
, bool_t subscribed_only
){
2239 struct a_nag_group
*ngp
;
2241 struct a_nag_grp_regex
**lpp
, *ngrp
;
2244 enum mlist_state rv
;
2247 ngp
= a_nag_group_find(a_NAG_T_MLIST
, name
);
2248 rv
= (ngp
!= NULL
) ? MLIST_KNOWN
: MLIST_OTHER
;
2250 if(rv
== MLIST_KNOWN
){
2251 if(ngp
->ng_type
& a_NAG_T_SUBSCRIBE
)
2252 rv
= MLIST_SUBSCRIBED
;
2253 else if(subscribed_only
)
2255 /* Of course, if that is a regular expression it doesn't mean a thing */
2257 if(ngp
->ng_type
& a_NAG_T_REGEX
)
2264 /* Not in the hashmap (as something matchable), walk the lists */
2267 lpp
= &a_nag_mlsub_regex
;
2270 if((ngrp
= *lpp
) != NULL
){
2271 do if(regexec(&ngrp
->ngr_regex
, name
, 0,NULL
, 0) != REG_NOMATCH
){
2272 /* Relink as the head of this list if the hit count of this group is
2273 * >= 25% of the average hit count */
2277 i
= ++a_nag_mlsub_hits
/ a_nag_mlsub_size
;
2279 i
= ++a_nag_mlist_hits
/ a_nag_mlist_size
;
2282 if(++ngrp
->ngr_hits
>= i
&& *lpp
!= ngrp
&& ngrp
->ngr_next
!= ngrp
){
2283 ngrp
->ngr_last
->ngr_next
= ngrp
->ngr_next
;
2284 ngrp
->ngr_next
->ngr_last
= ngrp
->ngr_last
;
2285 (ngrp
->ngr_last
= (*lpp
)->ngr_last
)->ngr_next
= ngrp
;
2286 (ngrp
->ngr_next
= *lpp
)->ngr_last
= ngrp
;
2289 rv
= !re2
? MLIST_SUBSCRIBED
: MLIST_KNOWN
;
2291 }while((ngrp
= ngrp
->ngr_next
) != *lpp
);
2294 if(!re2
&& !subscribed_only
){
2296 lpp
= &a_nag_mlist_regex
;
2299 assert(rv
== MLIST_OTHER
);
2300 #endif /* HAVE_REGEX */
2308 is_mlist_mp(struct message
*mp
, enum mlist_state what
){
2311 enum mlist_state rv
;
2317 np
= lextract(hfield1("to", mp
), GTO
| GSKIN
);
2319 for(; np
!= NULL
; np
= np
->n_flink
){
2320 switch(is_mlist(np
->n_name
, FAL0
)){
2324 if(what
== MLIST_KNOWN
|| what
== MLIST_OTHER
){
2325 if(rv
== MLIST_OTHER
)
2327 if(what
== MLIST_KNOWN
)
2331 case MLIST_SUBSCRIBED
:
2332 if(what
== MLIST_SUBSCRIBED
|| what
== MLIST_OTHER
){
2333 if(rv
!= MLIST_SUBSCRIBED
)
2334 rv
= MLIST_SUBSCRIBED
;
2343 np
= lextract(hfield1("cc", mp
), GCC
| GSKIN
);
2352 c_shortcut(void *vp
){
2353 struct a_nag_group
*ngp
;
2362 a_nag_group_print_all(a_NAG_T_SHORTCUT
, NULL
);
2363 else if(argv
[1] == NULL
){
2364 if((ngp
= a_nag_group_find(a_NAG_T_SHORTCUT
, *argv
)) != NULL
)
2365 a_nag_group_print(ngp
, n_stdout
, NULL
);
2367 n_err(_("No such shortcut: %s\n"), n_shexp_quote_cp(*argv
, FAL0
));
2370 }else for(; *argv
!= NULL
; argv
+= 2){
2371 /* Because one hardly ever redefines, anything is stored in one chunk */
2375 if(argv
[1] == NULL
){
2376 n_err(_("Synopsis: shortcut: <shortcut> <expansion>\n"));
2380 if(a_nag_group_find(a_NAG_T_SHORTCUT
, *argv
) != NULL
)
2381 a_nag_group_del(a_NAG_T_SHORTCUT
, *argv
);
2383 l
= strlen(argv
[1]) +1;
2384 if((ngp
= a_nag_group_fetch(a_NAG_T_SHORTCUT
, *argv
, l
)) == NULL
){
2385 n_err(_("Failed to create storage for shortcut: %s\n"),
2386 n_shexp_quote_cp(*argv
, FAL0
));
2389 a_NAG_GP_TO_SUBCLASS(cp
, ngp
);
2390 memcpy(cp
, argv
[1], l
);
2398 c_unshortcut(void *vp
){
2406 do if(!a_nag_group_del(a_NAG_T_SHORTCUT
, *argv
)){
2407 n_err(_("No such shortcut: %s\n"), *argv
);
2409 }while(*++argv
!= NULL
);
2415 shortcut_expand(char const *str
){
2416 struct a_nag_group
*ngp
;
2419 if((ngp
= a_nag_group_find(a_NAG_T_SHORTCUT
, str
)) != NULL
)
2420 a_NAG_GP_TO_SUBCLASS(str
, ngp
);
2428 c_charsetalias(void *vp
){
2429 struct a_nag_group
*ngp
;
2438 a_nag_group_print_all(a_NAG_T_CHARSETALIAS
, NULL
);
2439 else if(argv
[1] == NULL
){
2440 if((ngp
= a_nag_group_find(a_NAG_T_CHARSETALIAS
, *argv
)) != NULL
)
2441 a_nag_group_print(ngp
, n_stdout
, NULL
);
2443 n_err(_("No such charsetalias: %s\n"), n_shexp_quote_cp(*argv
, FAL0
));
2446 }else for(; *argv
!= NULL
; argv
+= 2){
2447 /* Because one hardly ever redefines, anything is stored in one chunk */
2450 char const *dst
, *src
;
2452 if((dst
= argv
[1]) == NULL
){
2453 n_err(_("Synopsis: charsetalias: <charset> <charset-alias>\n"));
2456 }else if((dst
= n_iconv_normalize_name(dst
)) == NULL
){
2457 n_err(_("charsetalias: invalid target charset %s\n"),
2458 n_shexp_quote_cp(argv
[1], FAL0
));
2461 }else if((src
= n_iconv_normalize_name(argv
[0])) == NULL
){
2462 n_err(_("charsetalias: invalid source charset %s\n"),
2463 n_shexp_quote_cp(argv
[0], FAL0
));
2468 /* Delete the old one, if any; don't get fooled to remove them all */
2469 if(src
[0] != '*' || src
[1] != '\0')
2470 a_nag_group_del(a_NAG_T_CHARSETALIAS
, src
);
2472 dstl
= strlen(dst
) +1;
2473 if((ngp
= a_nag_group_fetch(a_NAG_T_CHARSETALIAS
, src
, dstl
)) == NULL
){
2474 n_err(_("Failed to create storage for charsetalias: %s\n"),
2475 n_shexp_quote_cp(src
, FAL0
));
2478 a_NAG_GP_TO_SUBCLASS(cp
, ngp
);
2479 memcpy(cp
, dst
, dstl
);
2487 c_uncharsetalias(void *vp
){
2496 if((cp
= n_iconv_normalize_name(*argv
)) == NULL
||
2497 !a_nag_group_del(a_NAG_T_CHARSETALIAS
, cp
)){
2498 n_err(_("No such `charsetalias': %s\n"),
2499 n_shexp_quote_cp(*argv
, FAL0
));
2502 }while(*++argv
!= NULL
);
2508 n_charsetalias_expand(char const *cp
){
2509 struct a_nag_group
*ngp
;
2511 char const *cp_orig
;
2516 for(i
= 0; (ngp
= a_nag_group_find(a_NAG_T_CHARSETALIAS
, cp
)) != NULL
;){
2517 a_NAG_GP_TO_SUBCLASS(cp
, ngp
);
2518 if(++i
== 8) /* XXX Magic (same as for `ghost' expansion) */
2529 c_filetype(void *vp
){ /* TODO support automatic chains: .tar.gz -> .gz + .tar */
2530 struct a_nag_group
*ngp
;
2531 char **argv
; /* TODO While there: let ! prefix mean: direct execlp(2) */
2539 a_nag_group_print_all(a_NAG_T_FILETYPE
, NULL
);
2540 else if(argv
[1] == NULL
){
2541 if((ngp
= a_nag_group_find(a_NAG_T_FILETYPE
, *argv
)) != NULL
)
2542 a_nag_group_print(ngp
, n_stdout
, NULL
);
2544 n_err(_("No such filetype: %s\n"), n_shexp_quote_cp(*argv
, FAL0
));
2547 }else for(; *argv
!= NULL
; argv
+= 3){
2548 /* Because one hardly ever redefines, anything is stored in one chunk */
2553 if(argv
[1] == NULL
|| argv
[2] == NULL
){
2554 n_err(_("Synopsis: filetype: <extension> <load-cmd> <save-cmd>\n"));
2559 /* Delete the old one, if any; don't get fooled to remove them all */
2561 if(ccp
[0] != '*' || ccp
[1] != '\0')
2562 a_nag_group_del(a_NAG_T_FILETYPE
, ccp
);
2564 /* Lowercase it all (for display purposes) */
2567 while((c
= *cp
) != '\0')
2568 *cp
++ = lowerconv(c
);
2570 llc
= strlen(argv
[1]) +1;
2571 lsc
= strlen(argv
[2]) +1;
2572 if(UIZ_MAX
- llc
<= lsc
)
2575 if((ngp
= a_nag_group_fetch(a_NAG_T_FILETYPE
, ccp
, llc
+ lsc
)) == NULL
){
2577 n_err(_("Failed to create storage for filetype: %s\n"),
2578 n_shexp_quote_cp(argv
[0], FAL0
));
2581 struct a_nag_file_type
*nftp
;
2583 a_NAG_GP_TO_SUBCLASS(nftp
, ngp
);
2584 a_NAG_GP_TO_SUBCLASS(cp
, ngp
);
2586 memcpy(nftp
->nft_load
.s
= cp
, argv
[1], llc
);
2588 nftp
->nft_load
.l
= --llc
;
2589 memcpy(nftp
->nft_save
.s
= cp
, argv
[2], lsc
);
2591 nftp
->nft_save
.l
= --lsc
;
2599 c_unfiletype(void *vp
){
2607 do if(!a_nag_group_del(a_NAG_T_FILETYPE
, *argv
)){
2608 n_err(_("No such `filetype': %s\n"), n_shexp_quote_cp(*argv
, FAL0
));
2610 }while(*++argv
!= NULL
);
2616 n_filetype_trial(struct n_file_type
*res_or_null
, char const *file
){
2618 struct a_nag_group_lookup ngl
;
2619 struct n_string s
, *sp
;
2620 struct a_nag_group
const *ngp
;
2624 sp
= n_string_creat_auto(&s
);
2625 sp
= n_string_assign_cp(sp
, file
);
2626 sp
= n_string_push_c(sp
, '.');
2629 for(ngp
= a_nag_group_go_first(a_NAG_T_FILETYPE
, &ngl
); ngp
!= NULL
;
2630 ngp
= a_nag_group_go_next(&ngl
)){
2631 sp
= n_string_trunc(sp
, l
);
2632 sp
= n_string_push_buf(sp
, ngp
->ng_id
,
2633 ngp
->ng_subclass_off
- ngp
->ng_id_len_sub
);
2635 if(!stat(n_string_cp(sp
), &stb
) && S_ISREG(stb
.st_mode
)){
2636 if(res_or_null
!= NULL
){
2637 struct a_nag_file_type
*nftp
;
2639 a_NAG_GP_TO_SUBCLASS(nftp
, ngp
);
2640 res_or_null
->ft_ext_dat
= ngp
->ng_id
;
2641 res_or_null
->ft_ext_len
= ngp
->ng_subclass_off
- ngp
->ng_id_len_sub
;
2642 res_or_null
->ft_load_dat
= nftp
->nft_load
.s
;
2643 res_or_null
->ft_load_len
= nftp
->nft_load
.l
;
2644 res_or_null
->ft_save_dat
= nftp
->nft_save
.s
;
2645 res_or_null
->ft_save_len
= nftp
->nft_save
.l
;
2647 goto jleave
; /* TODO after v15 legacy drop: break; */
2651 /* TODO v15 legacy code: automatic file hooks for .{bz2,gz,xz},
2652 * TODO but NOT supporting *file-hook-{load,save}-EXTENSION* */
2653 ngp
= (struct a_nag_group
*)0x1;
2655 sp
= n_string_trunc(sp
, l
);
2656 sp
= n_string_push_buf(sp
, a_nag_OBSOLETE_xz
.ft_ext_dat
,
2657 a_nag_OBSOLETE_xz
.ft_ext_len
);
2658 if(!stat(n_string_cp(sp
), &stb
) && S_ISREG(stb
.st_mode
)){
2659 n_OBSOLETE(".xz support will vanish, please use the `filetype' command");
2660 if(res_or_null
!= NULL
)
2661 *res_or_null
= a_nag_OBSOLETE_xz
;
2665 sp
= n_string_trunc(sp
, l
);
2666 sp
= n_string_push_buf(sp
, a_nag_OBSOLETE_gz
.ft_ext_dat
,
2667 a_nag_OBSOLETE_gz
.ft_ext_len
);
2668 if(!stat(n_string_cp(sp
), &stb
) && S_ISREG(stb
.st_mode
)){
2669 n_OBSOLETE(".gz support will vanish, please use the `filetype' command");
2670 if(res_or_null
!= NULL
)
2671 *res_or_null
= a_nag_OBSOLETE_gz
;
2675 sp
= n_string_trunc(sp
, l
);
2676 sp
= n_string_push_buf(sp
, a_nag_OBSOLETE_bz2
.ft_ext_dat
,
2677 a_nag_OBSOLETE_bz2
.ft_ext_len
);
2678 if(!stat(n_string_cp(sp
), &stb
) && S_ISREG(stb
.st_mode
)){
2679 n_OBSOLETE(".bz2 support will vanish, please use the `filetype' command");
2680 if(res_or_null
!= NULL
)
2681 *res_or_null
= a_nag_OBSOLETE_bz2
;
2689 return (ngp
!= NULL
);
2693 n_filetype_exists(struct n_file_type
*res_or_null
, char const *file
){
2694 char const *ext
, *lext
;
2697 if((ext
= strrchr(file
, '/')) != NULL
)
2700 for(lext
= NULL
; (ext
= strchr(file
, '.')) != NULL
; lext
= file
= ext
){
2701 struct a_nag_group
const *ngp
;
2703 if((ngp
= a_nag_group_find(a_NAG_T_FILETYPE
, ++ext
)) != NULL
){
2705 if(res_or_null
!= NULL
){
2706 struct a_nag_file_type
*nftp
;
2708 a_NAG_GP_TO_SUBCLASS(nftp
, ngp
);
2709 res_or_null
->ft_ext_dat
= ngp
->ng_id
;
2710 res_or_null
->ft_ext_len
= ngp
->ng_subclass_off
- ngp
->ng_id_len_sub
;
2711 res_or_null
->ft_load_dat
= nftp
->nft_load
.s
;
2712 res_or_null
->ft_load_len
= nftp
->nft_load
.l
;
2713 res_or_null
->ft_save_dat
= nftp
->nft_save
.s
;
2714 res_or_null
->ft_save_len
= nftp
->nft_save
.l
;
2716 goto jleave
; /* TODO after v15 legacy drop: break; */
2720 /* TODO v15 legacy code: automatic file hooks for .{bz2,gz,xz},
2721 * TODO as well as supporting *file-hook-{load,save}-EXTENSION* */
2725 if(!asccasecmp(lext
, "xz")){
2726 n_OBSOLETE(".xz support will vanish, please use the `filetype' command");
2727 if(res_or_null
!= NULL
)
2728 *res_or_null
= a_nag_OBSOLETE_xz
;
2730 }else if(!asccasecmp(lext
, "gz")){
2731 n_OBSOLETE(".gz support will vanish, please use the `filetype' command");
2732 if(res_or_null
!= NULL
)
2733 *res_or_null
= a_nag_OBSOLETE_gz
;
2735 }else if(!asccasecmp(lext
, "bz2")){
2736 n_OBSOLETE(".bz2 support will vanish, please use the `filetype' command");
2737 if(res_or_null
!= NULL
)
2738 *res_or_null
= a_nag_OBSOLETE_bz2
;
2741 char const *cload
, *csave
;
2746 #define a_X1 "file-hook-load-"
2748 #define a_X2 "file-hook-save-"
2750 vbuf
= n_lofi_alloc(l
+ n_MAX(sizeof(a_X1
), sizeof(a_X2
)));
2752 memcpy(vbuf
, a_X1
, sizeof(a_X1
) -1);
2753 memcpy(&vbuf
[sizeof(a_X1
) -1], lext
, l
);
2754 vbuf
[sizeof(a_X1
) -1 + l
] = '\0';
2755 cload
= n_var_vlook(vbuf
, FAL0
);
2757 memcpy(vbuf
, a_X2
, sizeof(a_X2
) -1);
2758 memcpy(&vbuf
[sizeof(a_X2
) -1], lext
, l
);
2759 vbuf
[sizeof(a_X2
) -1 + l
] = '\0';
2760 csave
= n_var_vlook(vbuf
, FAL0
);
2766 if((csave
!= NULL
) | (cload
!= NULL
)){
2767 n_OBSOLETE("*file-hook-{load,save}-EXTENSION* will vanish, "
2768 "please use the `filetype' command");
2770 if(((csave
!= NULL
) ^ (cload
!= NULL
)) == 0){
2771 if(res_or_null
!= NULL
){
2772 res_or_null
->ft_ext_dat
= lext
;
2773 res_or_null
->ft_ext_len
= l
;
2774 res_or_null
->ft_load_dat
= cload
;
2775 res_or_null
->ft_load_len
= strlen(cload
);
2776 res_or_null
->ft_save_dat
= csave
;
2777 res_or_null
->ft_save_len
= strlen(csave
);
2781 n_alert(_("Incomplete *file-hook-{load,save}-EXTENSION* for: .%s"),
2790 return (lext
!= NULL
);