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 (!file, !pipe) 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
) &&
277 !(p
->n_flags
& (((ui32_t
)SI32_MIN
) | NAME_ADDRSPEC_ISFILE
|
278 NAME_ADDRSPEC_ISPIPE
)) &&
279 a_nag_is_same_name(p
->n_name
, name
))
280 p
->n_flags
|= (ui32_t
)SI32_MIN
;
286 a_nag_yankname(char const *ap
, char *wbuf
, char const *separators
,
290 char *wp
, c
, inquote
, lc
, lastsp
;
295 /* Skip over intermediate list trash, as in ".org> , <xy@zz.org>" */
296 for (c
= *ap
; blankchar(c
) || c
== ','; c
= *++ap
)
303 /* Parse a full name: TODO RFC 5322
304 * - Keep everything in quotes, liberal handle *quoted-pair*s therein
305 * - Skip entire (nested) comments
306 * - In non-quote, non-comment, join adjacent space to a single SP
307 * - Understand separators only in non-quote, non-comment context,
308 * and only if not part of a *quoted-pair* (XXX too liberal) */
310 for (inquote
= lc
= lastsp
= 0;; lc
= c
, ++cp
) {
319 #if 0 /* TODO when doing real RFC 5322 parsers - why have i done this? */
325 if (inquote
|| lc
== '\\') {
333 cp
= skip_comment(cp
+ 1);
341 if (strchr(separators
, c
) != NULL
)
345 lastsp
= blankchar(c
);
359 a_nag_extract1(char const *line
, enum gfield ntype
, char const *separators
,
362 struct name
*topp
, *np
, *t
;
368 if (line
== NULL
|| *line
== '\0')
373 nbuf
= n_alloc(strlen(line
) +1);
374 while ((cp
= a_nag_yankname(cp
, nbuf
, separators
, keepcomms
)) != NULL
) {
375 t
= nalloc(nbuf
, ntype
);
390 a_nag_gexpand(size_t level
, struct name
*nlist
, struct a_nag_group
*ngp
,
391 bool_t metoo
, int ntype
){
392 struct a_nag_grp_names
*ngnp
;
393 struct name
*nlist_tail
;
395 struct a_nag_grp_names_head
*ngnhp
;
398 if(UICMP(z
, level
++, >, n_ALIAS_MAXEXP
)){
399 n_err(_("Expanding alias to depth larger than %d\n"), n_ALIAS_MAXEXP
);
403 a_NAG_GP_TO_SUBCLASS(ngnhp
, ngp
);
404 logname
= ok_vlook(LOGNAME
);
406 for(ngnp
= ngnhp
->ngnh_head
; ngnp
!= NULL
; ngnp
= ngnp
->ngn_next
){
407 struct a_nag_group
*xngp
;
412 if(!strcmp(cp
, ngp
->ng_id
))
415 if((xngp
= a_nag_group_find(a_NAG_T_ALIAS
, cp
)) != NULL
){
416 /* For S-nail(1), the "alias" may *be* the sender in that a name maps
417 * to a full address specification; aliases cannot be empty */
418 struct a_nag_grp_names_head
*xngnhp
;
420 a_NAG_GP_TO_SUBCLASS(xngnhp
, xngp
);
422 assert(xngnhp
->ngnh_head
!= NULL
);
423 if(metoo
|| xngnhp
->ngnh_head
->ngn_next
!= NULL
||
424 !a_nag_is_same_name(cp
, logname
))
425 nlist
= a_nag_gexpand(level
, nlist
, xngp
, metoo
, ntype
);
429 /* Here we should allow to expand to itself if only person in alias */
431 if(metoo
|| ngnhp
->ngnh_head
->ngn_next
== NULL
||
432 !a_nag_is_same_name(cp
, logname
)){
435 np
= nalloc(cp
, ntype
| GFULL
);
436 if((nlist_tail
= nlist
) != NULL
){
437 while(nlist_tail
->n_flink
!= NULL
)
438 nlist_tail
= nlist_tail
->n_flink
;
439 nlist_tail
->n_flink
= np
;
440 np
->n_blink
= nlist_tail
;
451 a_nag_elide_qsort(void const *s1
, void const *s2
){
452 struct name
const * const *np1
, * const *np2
;
458 if(!(rv
= asccasecmp((*np1
)->n_name
, (*np2
)->n_name
))){
459 n_LCTAV(GTO
< GCC
&& GCC
< GBCC
);
460 rv
= ((*np1
)->n_type
& (GTO
| GCC
| GBCC
)) -
461 ((*np2
)->n_type
& (GTO
| GCC
| GBCC
));
467 static struct a_nag_group
*
468 a_nag_group_lookup(enum a_nag_type nt
, struct a_nag_group_lookup
*nglp
,
471 struct a_nag_group
*lngp
, *ngp
;
479 struct a_nag_group
**ngpa
;
481 switch((nt
&= a_NAG_T_MASK
)){
482 case a_NAG_T_ALTERNATES
:
483 ngpa
= a_nag_alternates_heads
;
487 case a_NAG_T_COMMANDALIAS
:
488 ngpa
= a_nag_commandalias_heads
;
491 ngpa
= a_nag_alias_heads
;
494 ngpa
= a_nag_mlist_heads
;
497 case a_NAG_T_SHORTCUT
:
498 ngpa
= a_nag_shortcut_heads
;
500 case a_NAG_T_CHARSETALIAS
:
501 ngpa
= a_nag_charsetalias_heads
;
504 case a_NAG_T_FILETYPE
:
505 ngpa
= a_nag_filetype_heads
;
510 nglp
->ngl_htable
= ngpa
;
511 h
= icase
? n_torek_ihash(id
) : n_torek_hash(id
);
512 ngp
= *(nglp
->ngl_slot
= &ngpa
[h
% HSHSIZE
]);
520 for(; ngp
!= NULL
; lngp
= ngp
, ngp
= ngp
->ng_next
)
521 if((ngp
->ng_type
& a_NAG_T_MASK
) == nt
&& *ngp
->ng_id
== c1
&&
522 !asccasecmp(&ngp
->ng_id
[1], id
))
525 for(; ngp
!= NULL
; lngp
= ngp
, ngp
= ngp
->ng_next
)
526 if((ngp
->ng_type
& a_NAG_T_MASK
) == nt
&& *ngp
->ng_id
== c1
&&
527 !strcmp(&ngp
->ng_id
[1], id
))
531 nglp
->ngl_slot_last
= lngp
;
532 nglp
->ngl_group
= ngp
;
537 static struct a_nag_group
*
538 a_nag_group_find(enum a_nag_type nt
, char const *id
){
539 struct a_nag_group_lookup ngl
;
540 struct a_nag_group
*ngp
;
543 ngp
= a_nag_group_lookup(nt
, &ngl
, id
);
548 static struct a_nag_group
*
549 a_nag_group_go_first(enum a_nag_type nt
, struct a_nag_group_lookup
*nglp
){
551 struct a_nag_group
**ngpa
, *ngp
;
554 switch((nt
&= a_NAG_T_MASK
)){
555 case a_NAG_T_ALTERNATES
:
556 ngpa
= a_nag_alternates_heads
;
559 case a_NAG_T_COMMANDALIAS
:
560 ngpa
= a_nag_commandalias_heads
;
563 ngpa
= a_nag_alias_heads
;
566 ngpa
= a_nag_mlist_heads
;
568 case a_NAG_T_SHORTCUT
:
569 ngpa
= a_nag_shortcut_heads
;
571 case a_NAG_T_CHARSETALIAS
:
572 ngpa
= a_nag_charsetalias_heads
;
574 case a_NAG_T_FILETYPE
:
575 ngpa
= a_nag_filetype_heads
;
579 nglp
->ngl_htable
= ngpa
;
581 for(i
= 0; i
< HSHSIZE
; ++ngpa
, ++i
)
582 if((ngp
= *ngpa
) != NULL
){
583 nglp
->ngl_slot
= ngpa
;
584 nglp
->ngl_group
= ngp
;
588 nglp
->ngl_group
= ngp
= NULL
;
590 nglp
->ngl_slot_last
= NULL
;
595 static struct a_nag_group
*
596 a_nag_group_go_next(struct a_nag_group_lookup
*nglp
){
597 struct a_nag_group
*ngp
, **ngpa
;
600 if((ngp
= nglp
->ngl_group
->ng_next
) != NULL
)
601 nglp
->ngl_slot_last
= nglp
->ngl_group
;
603 nglp
->ngl_slot_last
= NULL
;
604 for(ngpa
= &nglp
->ngl_htable
[HSHSIZE
]; ++nglp
->ngl_slot
< ngpa
;)
605 if((ngp
= *nglp
->ngl_slot
) != NULL
)
608 nglp
->ngl_group
= ngp
;
613 static struct a_nag_group
*
614 a_nag_group_fetch(enum a_nag_type nt
, char const *id
, size_t addsz
){
615 struct a_nag_group_lookup ngl
;
616 struct a_nag_group
*ngp
;
620 if((ngp
= a_nag_group_lookup(nt
, &ngl
, id
)) != NULL
)
624 if(UIZ_MAX
- n_ALIGN(l
) <=
625 n_ALIGN(n_VSTRUCT_SIZEOF(struct a_nag_group
, ng_id
)))
628 i
= n_ALIGN(n_VSTRUCT_SIZEOF(struct a_nag_group
, ng_id
) + l
);
629 switch(nt
& a_NAG_T_MASK
){
630 case a_NAG_T_ALTERNATES
:
631 case a_NAG_T_SHORTCUT
:
632 case a_NAG_T_CHARSETALIAS
:
635 case a_NAG_T_COMMANDALIAS
:
636 addsz
+= sizeof(struct a_nag_cmd_alias
);
639 addsz
+= sizeof(struct a_nag_grp_names_head
);
643 if(n_is_maybe_regex(id
)){
644 addsz
= sizeof(struct a_nag_grp_regex
);
649 case a_NAG_T_FILETYPE
:
650 addsz
+= sizeof(struct a_nag_file_type
);
653 if(UIZ_MAX
- i
< addsz
|| UI32_MAX
<= i
|| UI16_MAX
< i
- l
)
656 ngp
= n_alloc(i
+ addsz
);
657 memcpy(ngp
->ng_id
, id
, l
);
658 ngp
->ng_subclass_off
= (ui32_t
)i
;
659 ngp
->ng_id_len_sub
= (ui16_t
)(i
- --l
);
661 switch(nt
& a_NAG_T_MASK
){
662 case a_NAG_T_ALTERNATES
:
664 case a_NAG_T_CHARSETALIAS
:
665 case a_NAG_T_FILETYPE
:{
668 for(cp
= ngp
->ng_id
; (c
= *cp
) != '\0'; ++cp
)
675 if((nt
& a_NAG_T_MASK
) == a_NAG_T_ALIAS
){
676 struct a_nag_grp_names_head
*ngnhp
;
678 a_NAG_GP_TO_SUBCLASS(ngnhp
, ngp
);
679 ngnhp
->ngnh_head
= NULL
;
682 else if(nt
& a_NAG_T_REGEX
){
684 struct a_nag_grp_regex
*ngrp
;
686 a_NAG_GP_TO_SUBCLASS(ngrp
, ngp
);
688 if((s
= regcomp(&ngrp
->ngr_regex
, id
,
689 REG_EXTENDED
| REG_ICASE
| REG_NOSUB
)) != 0){
690 n_err(_("Invalid regular expression: %s: %s\n"),
691 n_shexp_quote_cp(id
, FAL0
), n_regex_err_to_doc(NULL
, s
));
696 ngrp
->ngr_mygroup
= ngp
;
697 a_nag_mlmux_linkin(ngp
);
699 #endif /* HAVE_REGEX */
701 ngp
->ng_next
= *ngl
.ngl_slot
;
709 a_nag_group_del(enum a_nag_type nt
, char const *id
){
710 struct a_nag_group_lookup ngl
;
711 struct a_nag_group
*ngp
;
715 xnt
= nt
& a_NAG_T_MASK
;
717 /* Delete 'em all? */
718 if(id
[0] == '*' && id
[1] == '\0'){
719 for(ngp
= a_nag_group_go_first(nt
, &ngl
); ngp
!= NULL
;)
720 ngp
= ((ngp
->ng_type
& a_NAG_T_MASK
) == xnt
) ? a_nag__group_del(&ngl
)
721 : a_nag_group_go_next(&ngl
);
722 ngp
= (struct a_nag_group
*)TRU1
;
723 }else if((ngp
= a_nag_group_lookup(nt
, &ngl
, id
)) != NULL
){
724 if(ngp
->ng_type
& xnt
)
725 a_nag__group_del(&ngl
);
730 return (ngp
!= NULL
);
733 static struct a_nag_group
*
734 a_nag__group_del(struct a_nag_group_lookup
*nglp
){
735 struct a_nag_group
*x
, *ngp
;
738 /* Overly complicated: link off this node, step ahead to next.. */
740 if((ngp
= nglp
->ngl_slot_last
) != NULL
)
741 ngp
= (ngp
->ng_next
= x
->ng_next
);
743 nglp
->ngl_slot_last
= NULL
;
744 ngp
= (*nglp
->ngl_slot
= x
->ng_next
);
747 struct a_nag_group
**ngpa
;
749 for(ngpa
= &nglp
->ngl_htable
[HSHSIZE
]; ++nglp
->ngl_slot
< ngpa
;)
750 if((ngp
= *nglp
->ngl_slot
) != NULL
)
754 nglp
->ngl_group
= ngp
;
756 if((x
->ng_type
& a_NAG_T_MASK
) == a_NAG_T_ALIAS
)
759 else if(x
->ng_type
& a_NAG_T_REGEX
){
760 struct a_nag_grp_regex
*ngrp
;
762 a_NAG_GP_TO_SUBCLASS(ngrp
, x
);
764 regfree(&ngrp
->ngr_regex
);
765 a_nag_mlmux_linkout(x
);
775 a_nag__names_del(struct a_nag_group
*ngp
){
776 struct a_nag_grp_names_head
*ngnhp
;
777 struct a_nag_grp_names
*ngnp
;
780 a_NAG_GP_TO_SUBCLASS(ngnhp
, ngp
);
782 for(ngnp
= ngnhp
->ngnh_head
; ngnp
!= NULL
;){
783 struct a_nag_grp_names
*x
;
786 ngnp
= ngnp
->ngn_next
;
793 a_nag_group_print_all(enum a_nag_type nt
, char const *varname
){
798 struct a_nag_group
const *ngp
;
800 struct a_nag_group
**ngpa
;
806 n_string_creat_auto(&s
);
808 xnt
= nt
& a_NAG_T_PRINT_MASK
;
810 switch(xnt
& a_NAG_T_MASK
){
811 case a_NAG_T_ALTERNATES
:
812 tname
= "alternates";
813 ngpa
= a_nag_alternates_heads
;
816 case a_NAG_T_COMMANDALIAS
:
817 tname
= "commandalias";
818 ngpa
= a_nag_commandalias_heads
;
822 ngpa
= a_nag_alias_heads
;
826 ngpa
= a_nag_mlist_heads
;
828 case a_NAG_T_SHORTCUT
:
830 ngpa
= a_nag_shortcut_heads
;
832 case a_NAG_T_CHARSETALIAS
:
833 tname
= "charsetalias";
834 ngpa
= a_nag_charsetalias_heads
;
836 case a_NAG_T_FILETYPE
:
838 ngpa
= a_nag_filetype_heads
;
843 for(i
= h
= 0; h
< HSHSIZE
; ++h
)
844 for(ngp
= ngpa
[h
]; ngp
!= NULL
; ngp
= ngp
->ng_next
)
845 if((ngp
->ng_type
& a_NAG_T_PRINT_MASK
) == xnt
)
849 fprintf(n_stdout
, _("# no %s registered\n"), tname
);
853 ida
= n_autorec_alloc(i
* sizeof *ida
);
855 /* Create alpha sorted array of entries */
856 for(i
= h
= 0; h
< HSHSIZE
; ++h
)
857 for(ngp
= ngpa
[h
]; ngp
!= NULL
; ngp
= ngp
->ng_next
)
858 if((ngp
->ng_type
& a_NAG_T_PRINT_MASK
) == xnt
)
859 ida
[i
++] = ngp
->ng_id
;
861 qsort(ida
, i
, sizeof *ida
, &a_nag__group_print_qsorter
);
866 else if((fp
= Ftmp(NULL
, "nagprint", OF_RDWR
| OF_UNLINK
| OF_REGISTER
)
870 /* Create visual result */
873 switch(xnt
& a_NAG_T_MASK
){
874 case a_NAG_T_ALTERNATES
:
884 for(i
= 0; ida
[i
] != NULL
; ++i
)
885 lines
+= a_nag_group_print(a_nag_group_find(nt
, ida
[i
]), fp
, &s
);
888 if(varname
== NULL
&& (nt
& a_NAG_T_MASK
) == a_NAG_T_MLIST
){
889 if(nt
& a_NAG_T_SUBSCRIBE
)
890 i
= (ui32_t
)a_nag_mlsub_size
, h
= (ui32_t
)a_nag_mlsub_hits
;
892 i
= (ui32_t
)a_nag_mlist_size
, h
= (ui32_t
)a_nag_mlist_hits
;
894 if(i
> 0 && (n_poption
& n_PO_D_V
)){
896 fprintf(fp
, _("# %s list regex(7) total: %u entries, %u hits\n"),
897 (nt
& a_NAG_T_SUBSCRIBE
? _("Subscribed") : _("Non-subscribed")),
904 switch(xnt
& a_NAG_T_MASK
){
905 case a_NAG_T_ALTERNATES
:
915 if(varname
== NULL
&& fp
!= n_stdout
){
917 page_or_print(fp
, lines
);
923 tname
= n_string_cp(&s
);
924 if(n_var_vset(varname
, (uintptr_t)tname
))
927 n_pstate_err_no
= n_ERR_NOTSUP
;
930 return (varname
== NULL
);
934 a_nag__group_print_qsorter(void const *a
, void const *b
){
938 rv
= strcmp(*(char**)n_UNCONST(a
), *(char**)n_UNCONST(b
));
944 a_nag_group_print(struct a_nag_group
const *ngp
, FILE *fo
,
945 struct n_string
*vputsp
){
952 switch(ngp
->ng_type
& a_NAG_T_MASK
){
953 case a_NAG_T_ALTERNATES
:{
955 fprintf(fo
, " %s", ngp
->ng_id
);
957 if(vputsp
->s_len
> 0)
958 vputsp
= n_string_push_c(vputsp
, ' ');
959 /*vputsp =*/ n_string_push_cp(vputsp
, ngp
->ng_id
);
963 case a_NAG_T_COMMANDALIAS
:{
964 struct a_nag_cmd_alias
*ncap
;
966 assert(fo
!= NULL
); /* xxx no vput yet */
967 a_NAG_GP_TO_SUBCLASS(ncap
, ngp
);
968 fprintf(fo
, "commandalias %s %s\n",
969 n_shexp_quote_cp(ngp
->ng_id
, TRU1
),
970 n_shexp_quote_cp(ncap
->nca_expand
.s
, TRU1
));
973 struct a_nag_grp_names_head
*ngnhp
;
974 struct a_nag_grp_names
*ngnp
;
976 assert(fo
!= NULL
); /* xxx no vput yet */
977 fprintf(fo
, "alias %s ", ngp
->ng_id
);
979 a_NAG_GP_TO_SUBCLASS(ngnhp
, ngp
);
980 if((ngnp
= ngnhp
->ngnh_head
) != NULL
) { /* xxx always 1+ entries */
982 struct a_nag_grp_names
*x
;
985 ngnp
= ngnp
->ngn_next
;
986 fprintf(fo
, " \"%s\"", string_quote(x
->ngn_id
)); /* TODO shexp */
987 }while(ngnp
!= NULL
);
992 assert(fo
!= NULL
); /* xxx no vput yet */
994 if((ngp
->ng_type
& a_NAG_T_REGEX
) && (n_poption
& n_PO_D_V
)){
996 struct a_nag_grp_regex
*lp
, *ngrp
;
998 lp
= (ngp
->ng_type
& a_NAG_T_SUBSCRIBE
? a_nag_mlsub_regex
999 : a_nag_mlist_regex
);
1000 a_NAG_GP_TO_SUBCLASS(ngrp
, ngp
);
1001 for(i
= 1; lp
!= ngrp
; lp
= lp
->ngr_next
)
1003 fprintf(fo
, "# regex(7): hits %" PRIuZ
", sort %" PRIuZ
".\n ",
1008 fprintf(fo
, "wysh %s %s\n",
1009 (ngp
->ng_type
& a_NAG_T_SUBSCRIBE
? "mlsubscribe" : "mlist"),
1010 n_shexp_quote_cp(ngp
->ng_id
, TRU1
));
1012 case a_NAG_T_SHORTCUT
:
1013 assert(fo
!= NULL
); /* xxx no vput yet */
1014 a_NAG_GP_TO_SUBCLASS(cp
, ngp
);
1015 fprintf(fo
, "wysh shortcut %s %s\n",
1016 ngp
->ng_id
, n_shexp_quote_cp(cp
, TRU1
));
1018 case a_NAG_T_CHARSETALIAS
:
1019 assert(fo
!= NULL
); /* xxx no vput yet */
1020 a_NAG_GP_TO_SUBCLASS(cp
, ngp
);
1021 fprintf(fo
, "charsetalias %s %s\n",
1022 n_shexp_quote_cp(ngp
->ng_id
, TRU1
), n_shexp_quote_cp(cp
, TRU1
));
1024 case a_NAG_T_FILETYPE
:{
1025 struct a_nag_file_type
*nftp
;
1027 assert(fo
!= NULL
); /* xxx no vput yet */
1028 a_NAG_GP_TO_SUBCLASS(nftp
, ngp
);
1029 fprintf(fo
, "filetype %s %s %s\n",
1030 n_shexp_quote_cp(ngp
->ng_id
, TRU1
),
1031 n_shexp_quote_cp(nftp
->nft_load
.s
, TRU1
),
1032 n_shexp_quote_cp(nftp
->nft_save
.s
, TRU1
));
1040 a_nag_mlmux(enum a_nag_type nt
, char const **argv
){
1041 struct a_nag_group
*ngp
;
1047 n_UNINIT(ecp
, NULL
);
1050 a_nag_group_print_all(nt
, NULL
);
1052 if((ngp
= a_nag_group_find(nt
, *argv
)) != NULL
){
1053 if(nt
& a_NAG_T_SUBSCRIBE
){
1054 if(!(ngp
->ng_type
& a_NAG_T_SUBSCRIBE
)){
1055 a_NAG_MLMUX_LINKOUT(ngp
);
1056 ngp
->ng_type
|= a_NAG_T_SUBSCRIBE
;
1057 a_NAG_MLMUX_LINKIN(ngp
);
1059 ecp
= N_("Mailing-list already `mlsubscribe'd: %s\n");
1063 ecp
= N_("Mailing-list already `mlist'ed: %s\n");
1066 }else if(a_nag_group_fetch(nt
, *argv
, 0) == NULL
){
1067 ecp
= N_("Failed to create storage for mailing-list: %s\n");
1069 n_err(V_(ecp
), n_shexp_quote_cp(*argv
, FAL0
));
1072 }while(*++argv
!= NULL
);
1079 a_nag_unmlmux(enum a_nag_type nt
, char const **argv
){
1080 struct a_nag_group
*ngp
;
1086 for(; *argv
!= NULL
; ++argv
){
1087 if(nt
& a_NAG_T_SUBSCRIBE
){
1088 struct a_nag_group_lookup ngl
;
1091 if(!(isaster
= (**argv
== '*')))
1092 ngp
= a_nag_group_find(nt
, *argv
);
1093 else if((ngp
= a_nag_group_go_first(nt
, &ngl
)) == NULL
)
1095 else if(ngp
!= NULL
&& !(ngp
->ng_type
& a_NAG_T_SUBSCRIBE
))
1100 if(ngp
->ng_type
& a_NAG_T_SUBSCRIBE
){
1101 a_NAG_MLMUX_LINKOUT(ngp
);
1102 ngp
->ng_type
&= ~a_NAG_T_SUBSCRIBE
;
1103 a_NAG_MLMUX_LINKIN(ngp
);
1107 while((ngp
= a_nag_group_go_next(&ngl
)) != NULL
&&
1108 !(ngp
->ng_type
& a_NAG_T_SUBSCRIBE
))
1114 n_err(_("Mailing-list not `mlsubscribe'd: %s\n"),
1115 n_shexp_quote_cp(*argv
, FAL0
));
1120 }else if(a_nag_group_del(nt
, *argv
))
1122 n_err(_("No such mailing-list: %s\n"), n_shexp_quote_cp(*argv
, FAL0
));
1131 a_nag_mlmux_linkin(struct a_nag_group
*ngp
){
1132 struct a_nag_grp_regex
**lpp
, *ngrp
, *lhp
;
1135 if(ngp
->ng_type
& a_NAG_T_SUBSCRIBE
){
1136 lpp
= &a_nag_mlsub_regex
;
1139 lpp
= &a_nag_mlist_regex
;
1143 a_NAG_GP_TO_SUBCLASS(ngrp
, ngp
);
1145 if((lhp
= *lpp
) != NULL
){
1146 (ngrp
->ngr_last
= lhp
->ngr_last
)->ngr_next
= ngrp
;
1147 (ngrp
->ngr_next
= lhp
)->ngr_last
= ngrp
;
1149 *lpp
= ngrp
->ngr_last
= ngrp
->ngr_next
= ngrp
;
1155 a_nag_mlmux_linkout(struct a_nag_group
*ngp
){
1156 struct a_nag_grp_regex
*ngrp
, **lpp
;
1159 a_NAG_GP_TO_SUBCLASS(ngrp
, ngp
);
1161 if(ngp
->ng_type
& a_NAG_T_SUBSCRIBE
){
1162 lpp
= &a_nag_mlsub_regex
;
1164 a_nag_mlsub_hits
-= ngrp
->ngr_hits
;
1166 lpp
= &a_nag_mlist_regex
;
1168 a_nag_mlist_hits
-= ngrp
->ngr_hits
;
1171 if(ngrp
->ngr_next
== ngrp
)
1174 (ngrp
->ngr_last
->ngr_next
= ngrp
->ngr_next
)->ngr_last
= ngrp
->ngr_last
;
1176 *lpp
= ngrp
->ngr_next
;
1180 #endif /* HAVE_REGEX */
1183 nalloc(char const *str
, enum gfield ntype
)
1185 struct n_addrguts ag
;
1189 assert(!(ntype
& GFULLEXTRA
) || (ntype
& GFULL
) != 0);
1191 str
= n_addrspec_with_guts(&ag
, str
,
1192 ((ntype
& (GFULL
| GSKIN
| GREF
)) != 0), FAL0
);
1195 np = NULL; TODO We cannot return NULL,
1196 goto jleave; TODO thus handle failures in here!
1201 if (!(ag
.ag_n_flags
& NAME_NAME_SALLOC
)) {
1202 ag
.ag_n_flags
|= NAME_NAME_SALLOC
;
1203 np
= n_autorec_alloc(sizeof(*np
) + ag
.ag_slen
+1);
1204 memcpy(np
+ 1, ag
.ag_skinned
, ag
.ag_slen
+1);
1205 ag
.ag_skinned
= (char*)(np
+ 1);
1207 np
= n_autorec_alloc(sizeof *np
);
1212 np
->n_fullname
= np
->n_name
= ag
.ag_skinned
;
1213 np
->n_fullextra
= NULL
;
1214 np
->n_flags
= ag
.ag_n_flags
;
1216 if (ntype
& GFULL
) {
1217 if (ag
.ag_ilen
== ag
.ag_slen
1219 && !(ag
.ag_n_flags
& NAME_IDNA
)
1223 if (ag
.ag_n_flags
& NAME_ADDRSPEC_ISFILEORPIPE
)
1226 /* n_fullextra is only the complete name part without address.
1227 * Beware of "-r '<abc@def>'", don't treat that as FULLEXTRA */
1228 if ((ntype
& GFULLEXTRA
) && ag
.ag_ilen
> ag
.ag_slen
+ 2) {
1229 size_t s
= ag
.ag_iaddr_start
, e
= ag
.ag_iaddr_aend
, i
;
1232 if (s
== 0 || str
[--s
] != '<' || str
[e
++] != '>')
1233 goto jskipfullextra
;
1235 in
.s
= n_lofi_alloc(s
+ 1 + i
+1);
1236 while(s
> 0 && blankchar(str
[s
- 1]))
1238 memcpy(in
.s
, str
, s
);
1241 while (blankchar(str
[e
])) {
1247 memcpy(&in
.s
[s
], &str
[e
], i
);
1250 in
.s
[in
.l
= s
] = '\0';
1251 mime_fromhdr(&in
, &out
, /* TODO TD_ISPR |*/ TD_ICONV
);
1253 for (cp
= out
.s
, i
= out
.l
; i
> 0 && spacechar(*cp
); --i
, ++cp
)
1255 while (i
> 0 && spacechar(cp
[i
- 1]))
1257 np
->n_fullextra
= savestrbuf(cp
, i
);
1264 /* n_fullname depends on IDNA conversion */
1266 if (!(ag
.ag_n_flags
& NAME_IDNA
)) {
1268 in
.s
= n_UNCONST(str
);
1272 /* The domain name was IDNA and has been converted. We also have to
1273 * ensure that the domain name in .n_fullname is replaced with the
1274 * converted version, since MIME doesn't perform encoding of addrs */
1275 /* TODO This definetely doesn't belong here! */
1276 size_t l
= ag
.ag_iaddr_start
,
1277 lsuff
= ag
.ag_ilen
- ag
.ag_iaddr_aend
;
1278 in
.s
= n_lofi_alloc(l
+ ag
.ag_slen
+ lsuff
+1);
1279 memcpy(in
.s
, str
, l
);
1280 memcpy(in
.s
+ l
, ag
.ag_skinned
, ag
.ag_slen
);
1282 memcpy(in
.s
+ l
, str
+ ag
.ag_iaddr_aend
, lsuff
);
1288 mime_fromhdr(&in
, &out
, /* TODO TD_ISPR |*/ TD_ICONV
);
1289 np
->n_fullname
= savestr(out
.s
);
1292 if (ag
.ag_n_flags
& NAME_IDNA
)
1302 nalloc_fcc(char const *file
){
1306 nnp
= n_autorec_alloc(sizeof *nnp
);
1307 nnp
->n_flink
= nnp
->n_blink
= NULL
;
1308 nnp
->n_type
= GBCC
| GBCC_IS_FCC
; /* xxx Bcc: <- namelist_vaporise_head */
1309 nnp
->n_flags
= NAME_NAME_SALLOC
| NAME_SKINNED
| NAME_ADDRSPEC_ISFILE
;
1310 nnp
->n_fullname
= nnp
->n_name
= savestr(file
);
1311 nnp
->n_fullextra
= NULL
;
1317 ndup(struct name
*np
, enum gfield ntype
)
1322 if ((ntype
& (GFULL
| GSKIN
)) && !(np
->n_flags
& NAME_SKINNED
)) {
1323 nnp
= nalloc(np
->n_name
, ntype
);
1327 nnp
= n_autorec_alloc(sizeof *np
);
1328 nnp
->n_flink
= nnp
->n_blink
= NULL
;
1329 nnp
->n_type
= ntype
;
1330 nnp
->n_flags
= np
->n_flags
| NAME_NAME_SALLOC
;
1331 nnp
->n_name
= savestr(np
->n_name
);
1332 if (np
->n_name
== np
->n_fullname
|| !(ntype
& (GFULL
| GSKIN
))) {
1333 nnp
->n_fullname
= nnp
->n_name
;
1334 nnp
->n_fullextra
= NULL
;
1336 nnp
->n_fullname
= savestr(np
->n_fullname
);
1337 nnp
->n_fullextra
= (np
->n_fullextra
== NULL
) ? NULL
1338 : savestr(np
->n_fullextra
);
1346 cat(struct name
*n1
, struct name
*n2
){
1354 if(n2
== NULL
|| (n2
->n_type
& GDEL
))
1357 while(tail
->n_flink
!= NULL
)
1358 tail
= tail
->n_flink
;
1368 namelist_dup(struct name
const *np
, enum gfield ntype
){
1369 struct name
*nlist
, *xnp
;
1372 for(nlist
= xnp
= NULL
; np
!= NULL
; np
= np
->n_flink
){
1375 if(!(np
->n_type
& GDEL
)){
1376 x
= ndup(n_UNCONST(np
), (np
->n_type
& ~GMASK
) | ntype
);
1377 if((x
->n_blink
= xnp
) == NULL
)
1389 count(struct name
const *np
)
1394 for (c
= 0; np
!= NULL
; np
= np
->n_flink
)
1395 if (!(np
->n_type
& GDEL
))
1402 count_nonlocal(struct name
const *np
)
1407 for (c
= 0; np
!= NULL
; np
= np
->n_flink
)
1408 if (!(np
->n_type
& GDEL
) && !(np
->n_flags
& NAME_ADDRSPEC_ISFILEORPIPE
))
1415 extract(char const *line
, enum gfield ntype
)
1420 rv
= a_nag_extract1(line
, ntype
, " \t,", 0);
1426 lextract(char const *line
, enum gfield ntype
)
1431 rv
= ((line
!= NULL
&& strpbrk(line
, ",\"\\(<|"))
1432 ? a_nag_extract1(line
, ntype
, ",", 1) : extract(line
, ntype
));
1438 detract(struct name
*np
, enum gfield ntype
)
1449 flags
= ntype
& (GCOMMA
| GNAMEONLY
);
1450 ntype
&= ~(GCOMMA
| GNAMEONLY
);
1453 for (p
= np
; p
!= NULL
; p
= p
->n_flink
) {
1454 if (ntype
&& (p
->n_type
& GMASK
) != ntype
)
1456 s
+= strlen(flags
& GNAMEONLY
? p
->n_name
: p
->n_fullname
) +1;
1464 topp
= n_autorec_alloc(s
);
1466 for (p
= np
; p
!= NULL
; p
= p
->n_flink
) {
1467 if (ntype
&& (p
->n_type
& GMASK
) != ntype
)
1469 cp
= sstpcpy(cp
, (flags
& GNAMEONLY
? p
->n_name
: p
->n_fullname
));
1470 if ((flags
& GCOMMA
) && p
->n_flink
!= NULL
)
1475 if ((flags
& GCOMMA
) && *--cp
== ',')
1483 grab_names(enum n_go_input_flags gif
, char const *field
, struct name
*np
,
1484 int comma
, enum gfield gflags
)
1490 np
= lextract(n_go_input_cp(gif
, field
, detract(np
, comma
)), gflags
);
1491 for (nq
= np
; nq
!= NULL
; nq
= nq
->n_flink
)
1492 if (is_addr_invalid(nq
, EACM_NONE
))
1499 name_is_same_domain(struct name
const *n1
, struct name
const *n2
)
1501 char const *d1
, *d2
;
1505 d1
= strrchr(n1
->n_name
, '@');
1506 d2
= strrchr(n2
->n_name
, '@');
1508 rv
= (d1
!= NULL
&& d2
!= NULL
) ? !asccasecmp(++d1
, ++d2
) : FAL0
;
1515 checkaddrs(struct name
*np
, enum expand_addr_check_mode eacm
,
1516 si8_t
*set_on_error
)
1521 for (n
= np
; n
!= NULL
; n
= n
->n_flink
) {
1524 if ((rv
= is_addr_invalid(n
, eacm
)) != 0) {
1525 if (set_on_error
!= NULL
)
1526 *set_on_error
|= rv
; /* don't loose -1! */
1527 else if (eacm
& EAF_MAYKEEP
) /* TODO HACK! See definition! */
1530 n
->n_blink
->n_flink
= n
->n_flink
;
1532 n
->n_flink
->n_blink
= n
->n_blink
;
1542 namelist_vaporise_head(struct header
*hp
, enum expand_addr_check_mode eacm
,
1543 bool_t metoo
, si8_t
*set_on_error
)
1545 /* TODO namelist_vaporise_head() is incredibly expensive and redundant */
1546 struct name
*tolist
, *np
, **npp
;
1549 tolist
= cat(hp
->h_to
, cat(hp
->h_cc
, cat(hp
->h_bcc
, hp
->h_fcc
)));
1550 hp
->h_to
= hp
->h_cc
= hp
->h_bcc
= hp
->h_fcc
= NULL
;
1552 tolist
= usermap(tolist
, metoo
);
1553 tolist
= n_alternates_remove(tolist
, TRU1
);
1554 tolist
= elide(checkaddrs(tolist
, eacm
, set_on_error
));
1556 for (np
= tolist
; np
!= NULL
; np
= np
->n_flink
) {
1557 switch (np
->n_type
& (GDEL
| GMASK
)) {
1558 case GTO
: npp
= &hp
->h_to
; break;
1559 case GCC
: npp
= &hp
->h_cc
; break;
1560 case GBCC
: npp
= &hp
->h_bcc
; break;
1563 *npp
= cat(*npp
, ndup(np
, np
->n_type
| GFULL
));
1570 usermap(struct name
*names
, bool_t force_metoo
){
1571 struct a_nag_group
*ngp
;
1572 struct name
*nlist
, *nlist_tail
, *np
, *cp
;
1576 metoo
= (force_metoo
|| ok_blook(metoo
));
1577 nlist
= nlist_tail
= NULL
;
1580 for(; np
!= NULL
; np
= cp
){
1581 assert(!(np
->n_type
& GDEL
)); /* TODO legacy */
1584 if(is_fileorpipe_addr(np
) ||
1585 (ngp
= a_nag_group_find(a_NAG_T_ALIAS
, np
->n_name
)) == NULL
){
1586 if((np
->n_blink
= nlist_tail
) != NULL
)
1587 nlist_tail
->n_flink
= np
;
1593 nlist
= a_nag_gexpand(0, nlist
, ngp
, metoo
, np
->n_type
);
1594 if((nlist_tail
= nlist
) != NULL
)
1595 while(nlist_tail
->n_flink
!= NULL
)
1596 nlist_tail
= nlist_tail
->n_flink
;
1604 elide(struct name
*names
)
1607 struct name
*nlist
, *np
, **nparr
;
1615 /* Throw away all deleted nodes */
1616 for(np
= NULL
, i
= 0; names
!= NULL
; names
= names
->n_flink
)
1617 if(!(names
->n_type
& GDEL
)){
1618 names
->n_blink
= np
;
1620 np
->n_flink
= names
;
1626 if(nlist
== NULL
|| i
== 1)
1630 /* Create a temporay array and sort that */
1631 nparr
= n_lofi_alloc(sizeof(*nparr
) * i
);
1633 for(i
= 0, np
= nlist
; np
!= NULL
; np
= np
->n_flink
)
1636 qsort(nparr
, i
, sizeof *nparr
, &a_nag_elide_qsort
);
1638 /* Remove duplicates XXX speedup, or list_uniq()! */
1639 for(j
= 0, --i
; j
< i
;){
1640 if(asccasecmp(nparr
[j
]->n_name
, nparr
[k
= j
+ 1]->n_name
))
1644 nparr
[k
] = nparr
[k
+ 1];
1649 /* Throw away all list members which are not part of the array.
1650 * Note this keeps the original, possibly carefully crafted, order of the
1651 * addressees, thus */
1652 for(np
= nlist
; np
!= NULL
; np
= np
->n_flink
){
1653 for(j
= 0; j
<= i
; ++j
)
1660 nlist
= np
->n_flink
;
1663 np
->n_blink
->n_flink
= np
->n_flink
;
1664 if(np
->n_flink
!= NULL
)
1665 np
->n_flink
->n_blink
= np
->n_blink
;
1676 c_alternates(void *vp
){
1677 struct a_nag_group
*ngp
;
1678 char const *varname
, *ccp
;
1682 n_pstate_err_no
= n_ERR_NONE
;
1685 varname
= (n_pstate
& n_PS_ARGMOD_VPUT
) ? *argv
++ : NULL
;
1688 if(!a_nag_group_print_all(a_NAG_T_ALTERNATES
, varname
))
1692 n_err(_("`alternates': `vput' only supported for show mode\n"));
1694 /* Delete the old set to "declare a list", if *posix* */
1696 a_nag_group_del(a_NAG_T_ALTERNATES
, n_star
);
1698 while((ccp
= *argv
++) != NULL
){
1702 if((np
= lextract(ccp
, GSKIN
)) == NULL
|| np
->n_flink
!= NULL
||
1703 (np
= checkaddrs(np
, EACM_STRICT
, NULL
)) == NULL
){
1704 n_err(_("Invalid `alternates' argument: %s\n"),
1705 n_shexp_quote_cp(ccp
, FAL0
));
1706 n_pstate_err_no
= n_ERR_INVAL
;
1713 if((ngp
= a_nag_group_fetch(a_NAG_T_ALTERNATES
, ccp
, l
)) == NULL
){
1714 n_err(_("Failed to create storage for alternates: %s\n"),
1715 n_shexp_quote_cp(ccp
, FAL0
));
1716 n_pstate_err_no
= n_ERR_NOMEM
;
1722 return (vp
!= NULL
? 0 : 1);
1726 c_unalternates(void *vp
){
1734 do if(!a_nag_group_del(a_NAG_T_ALTERNATES
, *argv
)){
1735 n_err(_("No such `alternates': %s\n"), n_shexp_quote_cp(*argv
, FAL0
));
1737 }while(*++argv
!= NULL
);
1743 n_alternates_remove(struct name
*np
, bool_t keep_single
){
1744 /* XXX keep a single pointer, initial null, and immediate remove nodes
1745 * XXX on successful match unless keep single and that pointer null! */
1746 struct a_nag_group_lookup ngl
;
1747 struct a_nag_group
*ngp
;
1748 struct name
*xp
, *newnp
;
1751 /* Delete the temporary bit from all */
1752 for(xp
= np
; xp
!= NULL
; xp
= xp
->n_flink
)
1753 xp
->n_flags
&= ~(ui32_t
)SI32_MIN
;
1755 /* Mark all possible alternate names (xxx sic: instead walk over namelist
1756 * and hash-lookup alternate instead (unless *allnet*) */
1757 for(ngp
= a_nag_group_go_first(a_NAG_T_ALTERNATES
, &ngl
); ngp
!= NULL
;
1758 ngp
= a_nag_group_go_next(&ngl
))
1759 np
= a_nag_namelist_mark_name(np
, ngp
->ng_id
);
1761 np
= a_nag_namelist_mark_name(np
, ok_vlook(LOGNAME
));
1763 if((xp
= extract(ok_vlook(sender
), GEXTRA
| GSKIN
)) != NULL
){
1764 /* TODO check_from_and_sender(): drop; *sender*: only one name!
1765 * TODO At assignment time, as VIP var? */
1767 np
= a_nag_namelist_mark_name(np
, xp
->n_name
);
1768 while((xp
= xp
->n_flink
) != NULL
);
1769 }else for(xp
= lextract(ok_vlook(from
), GEXTRA
| GSKIN
); xp
!= NULL
;
1771 np
= a_nag_namelist_mark_name(np
, xp
->n_name
);
1774 char const *v15compat
;
1776 if((v15compat
= ok_vlook(replyto
)) != NULL
){
1777 n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
1778 for(xp
= lextract(v15compat
, GEXTRA
| GSKIN
); xp
!= NULL
;
1780 np
= a_nag_namelist_mark_name(np
, xp
->n_name
);
1784 for(xp
= lextract(ok_vlook(reply_to
), GEXTRA
| GSKIN
); xp
!= NULL
;
1786 np
= a_nag_namelist_mark_name(np
, xp
->n_name
);
1788 /* Clean the list by throwing away all deleted or marked (but one) nodes */
1789 for(xp
= newnp
= NULL
; np
!= NULL
; np
= np
->n_flink
){
1790 if(np
->n_type
& GDEL
)
1792 if(np
->n_flags
& (ui32_t
)SI32_MIN
){
1804 xp
->n_flags
&= ~(ui32_t
)SI32_MIN
;
1815 n_is_myname(char const *name
){
1816 struct a_nag_group_lookup ngl
;
1817 struct a_nag_group
*ngp
;
1821 if(a_nag_is_same_name(ok_vlook(LOGNAME
), name
))
1824 if(!ok_blook(allnet
)){
1825 if(a_nag_group_lookup(a_NAG_T_ALTERNATES
, &ngl
, name
) != NULL
)
1828 for(ngp
= a_nag_group_go_first(a_NAG_T_ALTERNATES
, &ngl
); ngp
!= NULL
;
1829 ngp
= a_nag_group_go_next(&ngl
))
1830 if(a_nag_is_same_name(ngp
->ng_id
, name
))
1834 for(xp
= lextract(ok_vlook(from
), GEXTRA
| GSKIN
); xp
!= NULL
;
1836 if(a_nag_is_same_name(xp
->n_name
, name
))
1840 char const *v15compat
;
1842 if((v15compat
= ok_vlook(replyto
)) != NULL
){
1843 n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
1844 for(xp
= lextract(v15compat
, GEXTRA
| GSKIN
); xp
!= NULL
;
1846 if(a_nag_is_same_name(xp
->n_name
, name
))
1851 for(xp
= lextract(ok_vlook(reply_to
), GEXTRA
| GSKIN
); xp
!= NULL
;
1853 if(a_nag_is_same_name(xp
->n_name
, name
))
1856 for(xp
= extract(ok_vlook(sender
), GEXTRA
| GSKIN
); xp
!= NULL
;
1858 if(a_nag_is_same_name(xp
->n_name
, name
))
1864 return (name
!= NULL
);
1868 c_addrcodec(void *vp
){
1869 struct n_addrguts ag
;
1871 struct n_string s_b
, *sp
;
1874 char const **argv
, *varname
, *act
, *cp
;
1877 sp
= n_string_creat_auto(&s_b
);
1879 varname
= (n_pstate
& n_PS_ARGMOD_VPUT
) ? *argv
++ : NULL
;
1882 for(cp
= act
; *cp
!= '\0' && !blankspacechar(*cp
); ++cp
)
1893 alen
= PTR2SIZE(cp
- act
);
1897 trims
.l
= strlen(trims
.s
= n_UNCONST(cp
));
1898 cp
= savestrbuf(n_str_trim(&trims
, n_STR_TRIM_BOTH
)->s
, trims
.l
);
1899 if(trims
.l
<= UIZ_MAX
/ 4)
1901 sp
= n_string_reserve(sp
, trims
.l
);
1903 n_pstate_err_no
= n_ERR_NONE
;
1905 if(is_ascncaseprefix(act
, "encode", alen
)){
1906 /* This function cannot be a simple nalloc() wrapper even later on, since
1907 * we may need to turn any ", () or \ into quoted-pairs */
1910 while((c
= *cp
++) != '\0'){
1911 if(((c
== '(' || c
== ')') && mode
< 1) || (c
== '"' && mode
< 2) ||
1912 (c
== '\\' && mode
< 3))
1913 sp
= n_string_push_c(sp
, '\\');
1914 sp
= n_string_push_c(sp
, c
);
1917 if(n_addrspec_with_guts(&ag
, n_string_cp(sp
), TRU1
, TRU1
) == NULL
||
1918 (ag
.ag_n_flags
& (NAME_ADDRSPEC_ISADDR
| NAME_ADDRSPEC_INVALID
)
1919 ) != NAME_ADDRSPEC_ISADDR
){
1921 n_pstate_err_no
= n_ERR_INVAL
;
1926 np
= nalloc(ag
.ag_input
, GTO
| GFULL
| GSKIN
);
1927 cp
= np
->n_fullname
;
1929 }else if(mode
== 0){
1930 if(is_ascncaseprefix(act
, "decode", alen
)){
1933 while((c
= *cp
++) != '\0'){
1936 sp
= n_string_push_c(sp
, '(');
1937 act
= skip_comment(cp
);
1939 sp
= n_string_push_buf(sp
, cp
, PTR2SIZE(act
- cp
));
1940 sp
= n_string_push_c(sp
, ')');
1945 if((c
= *cp
++) == '"')
1947 if(c
== '\\' && (c
= *cp
) != '\0')
1949 sp
= n_string_push_c(sp
, c
);
1953 if(c
== '\\' && (c
= *cp
++) == '\0')
1955 sp
= n_string_push_c(sp
, c
);
1959 cp
= n_string_cp(sp
);
1960 }else if(is_ascncaseprefix(act
, "skin", alen
) ||
1961 (mode
= 1, is_ascncaseprefix(act
, "skinlist", alen
))){
1962 /* Let's just use the is-single-address hack for this one, too.. */
1963 if(n_addrspec_with_guts(&ag
, cp
, TRU1
, TRU1
) == NULL
||
1964 (ag
.ag_n_flags
& (NAME_ADDRSPEC_ISADDR
| NAME_ADDRSPEC_INVALID
)
1965 ) != NAME_ADDRSPEC_ISADDR
){
1966 n_pstate_err_no
= n_ERR_INVAL
;
1971 np
= nalloc(ag
.ag_input
, GTO
| GFULL
| GSKIN
);
1974 if(mode
== 1 && is_mlist(cp
, FAL0
) != MLIST_OTHER
)
1975 n_pstate_err_no
= n_ERR_EXIST
;
1982 if(varname
== NULL
){
1983 if(fprintf(n_stdout
, "%s\n", cp
) < 0){
1984 n_pstate_err_no
= n_err_no
;
1987 }else if(!n_var_vset(varname
, (uintptr_t)cp
)){
1988 n_pstate_err_no
= n_ERR_NOTSUP
;
1994 return (vp
!= NULL
? 0 : 1);
1996 n_err(_("Synopsis: addrcodec: <[+[+[+]]]e[ncode]|d[ecode]|s[kin]> "
1997 "<rest-of-line>\n"));
1998 n_pstate_err_no
= n_ERR_INVAL
;
2004 c_commandalias(void *vp
){
2005 struct a_nag_group
*ngp
;
2006 char const **argv
, *ccp
;
2013 if((ccp
= *argv
) == NULL
){
2014 a_nag_group_print_all(a_NAG_T_COMMANDALIAS
, NULL
);
2018 /* Verify the name is a valid one, and not a command modifier.
2019 * NOTE: this list duplicates settings isolated somewhere else (go.c) */
2020 if(*ccp
== '\0' || *n_cmd_isolate(ccp
) != '\0' ||
2021 !asccasecmp(ccp
, "ignerr") || !asccasecmp(ccp
, "local") ||
2022 !asccasecmp(ccp
, "wysh") || !asccasecmp(ccp
, "vput") ||
2023 !asccasecmp(ccp
, "scope") || !asccasecmp(ccp
, "u")){
2024 n_err(_("`commandalias': not a valid command name: %s\n"),
2025 n_shexp_quote_cp(ccp
, FAL0
));
2030 if(argv
[1] == NULL
){
2031 if((ngp
= a_nag_group_find(a_NAG_T_COMMANDALIAS
, ccp
)) != NULL
)
2032 a_nag_group_print(ngp
, n_stdout
, NULL
);
2034 n_err(_("No such commandalias: %s\n"), n_shexp_quote_cp(ccp
, FAL0
));
2038 /* Because one hardly ever redefines, anything is stored in one chunk */
2042 /* Delete the old one, if any; don't get fooled to remove them all */
2043 if(ccp
[0] != '*' || ccp
[1] != '\0')
2044 a_nag_group_del(a_NAG_T_COMMANDALIAS
, ccp
);
2046 for(i
= len
= 0, ++argv
; argv
[i
] != NULL
; ++i
)
2047 len
+= strlen(argv
[i
]) + 1;
2051 if((ngp
= a_nag_group_fetch(a_NAG_T_COMMANDALIAS
, ccp
, len
)) == NULL
){
2052 n_err(_("Failed to create storage for commandalias: %s\n"),
2053 n_shexp_quote_cp(ccp
, FAL0
));
2056 struct a_nag_cmd_alias
*ncap
;
2058 a_NAG_GP_TO_SUBCLASS(ncap
, ngp
);
2059 a_NAG_GP_TO_SUBCLASS(cp
, ngp
);
2061 ncap
->nca_expand
.s
= cp
;
2062 ncap
->nca_expand
.l
= len
- 1;
2064 for(len
= 0; (ccp
= *argv
++) != NULL
;)
2065 if((i
= strlen(ccp
)) > 0){
2080 c_uncommandalias(void *vp
){
2088 do if(!a_nag_group_del(a_NAG_T_COMMANDALIAS
, *argv
)){
2089 n_err(_("No such `commandalias': %s\n"), n_shexp_quote_cp(*argv
, FAL0
));
2091 }while(*++argv
!= NULL
);
2097 n_commandalias_exists(char const *name
, struct str
const **expansion_or_null
){
2098 struct a_nag_group
*ngp
;
2101 if((ngp
= a_nag_group_find(a_NAG_T_COMMANDALIAS
, name
)) != NULL
){
2104 if(expansion_or_null
!= NULL
){
2105 struct a_nag_cmd_alias
*ncap
;
2107 a_NAG_GP_TO_SUBCLASS(ncap
, ngp
);
2108 *expansion_or_null
= &ncap
->nca_expand
;
2117 n_alias_is_valid_name(char const *name
){
2123 for(rv
= TRU1
, cp
= name
++; (c
= *cp
++) != '\0';)
2124 /* User names, plus things explicitly mentioned in Postfix aliases(5),
2125 * i.e., [[:alnum:]_#:@.-]+$?.
2126 * As extensions allow high-bit bytes, semicolon and period. */
2127 if(!alnumchar(c
) && c
!= '_' && c
!= '-' &&
2128 c
!= '#' && c
!= ':' && c
!= '@' &&
2129 !((ui8_t
)c
& 0x80) && c
!= '!' && c
!= '.'){
2130 if(c
== '$' && cp
!= name
&& *cp
== '\0')
2144 struct a_nag_group
*ngp
;
2150 n_UNINIT(ecp
, NULL
);
2153 a_nag_group_print_all(a_NAG_T_ALIAS
, NULL
);
2154 else if(!n_alias_is_valid_name(*argv
)){
2155 ecp
= N_("Not a valid alias name: %s\n");
2157 }else if(argv
[1] == NULL
){
2158 if((ngp
= a_nag_group_find(a_NAG_T_ALIAS
, *argv
)) != NULL
)
2159 a_nag_group_print(ngp
, n_stdout
, NULL
);
2161 ecp
= N_("No such alias: %s\n");
2164 }else if((ngp
= a_nag_group_fetch(a_NAG_T_ALIAS
, *argv
, 0)) == NULL
){
2165 ecp
= N_("Failed to create alias storage for: %s\n");
2167 n_err(V_(ecp
), n_shexp_quote_cp(*argv
, FAL0
));
2170 struct a_nag_grp_names
*ngnp_tail
, *ngnp
;
2171 struct a_nag_grp_names_head
*ngnhp
;
2173 a_NAG_GP_TO_SUBCLASS(ngnhp
, ngp
);
2175 if((ngnp_tail
= ngnhp
->ngnh_head
) != NULL
)
2176 while((ngnp
= ngnp_tail
->ngn_next
) != NULL
)
2179 for(++argv
; *argv
!= NULL
; ++argv
){
2182 i
= strlen(*argv
) +1;
2183 ngnp
= n_alloc(n_VSTRUCT_SIZEOF(struct a_nag_grp_names
, ngn_id
) + i
);
2184 if(ngnp_tail
!= NULL
)
2185 ngnp_tail
->ngn_next
= ngnp
;
2187 ngnhp
->ngnh_head
= ngnp
;
2189 ngnp
->ngn_next
= NULL
;
2190 memcpy(ngnp
->ngn_id
, *argv
, i
);
2206 do if(!a_nag_group_del(a_NAG_T_ALIAS
, *argv
)){
2207 n_err(_("No such alias: %s\n"), *argv
);
2209 }while(*++argv
!= NULL
);
2219 rv
= a_nag_mlmux(a_NAG_T_MLIST
, v
);
2229 rv
= a_nag_unmlmux(a_NAG_T_MLIST
, v
);
2235 c_mlsubscribe(void *v
){
2239 rv
= a_nag_mlmux(a_NAG_T_MLIST
| a_NAG_T_SUBSCRIBE
, v
);
2245 c_unmlsubscribe(void *v
){
2249 rv
= a_nag_unmlmux(a_NAG_T_MLIST
| a_NAG_T_SUBSCRIBE
, v
);
2255 is_mlist(char const *name
, bool_t subscribed_only
){
2256 struct a_nag_group
*ngp
;
2258 struct a_nag_grp_regex
**lpp
, *ngrp
;
2261 enum mlist_state rv
;
2264 ngp
= a_nag_group_find(a_NAG_T_MLIST
, name
);
2265 rv
= (ngp
!= NULL
) ? MLIST_KNOWN
: MLIST_OTHER
;
2267 if(rv
== MLIST_KNOWN
){
2268 if(ngp
->ng_type
& a_NAG_T_SUBSCRIBE
)
2269 rv
= MLIST_SUBSCRIBED
;
2270 else if(subscribed_only
)
2272 /* Of course, if that is a regular expression it doesn't mean a thing */
2274 if(ngp
->ng_type
& a_NAG_T_REGEX
)
2281 /* Not in the hashmap (as something matchable), walk the lists */
2284 lpp
= &a_nag_mlsub_regex
;
2287 if((ngrp
= *lpp
) != NULL
){
2288 do if(regexec(&ngrp
->ngr_regex
, name
, 0,NULL
, 0) != REG_NOMATCH
){
2289 /* Relink as the head of this list if the hit count of this group is
2290 * >= 25% of the average hit count */
2294 i
= ++a_nag_mlsub_hits
/ a_nag_mlsub_size
;
2296 i
= ++a_nag_mlist_hits
/ a_nag_mlist_size
;
2299 if(++ngrp
->ngr_hits
>= i
&& *lpp
!= ngrp
&& ngrp
->ngr_next
!= ngrp
){
2300 ngrp
->ngr_last
->ngr_next
= ngrp
->ngr_next
;
2301 ngrp
->ngr_next
->ngr_last
= ngrp
->ngr_last
;
2302 (ngrp
->ngr_last
= (*lpp
)->ngr_last
)->ngr_next
= ngrp
;
2303 (ngrp
->ngr_next
= *lpp
)->ngr_last
= ngrp
;
2306 rv
= !re2
? MLIST_SUBSCRIBED
: MLIST_KNOWN
;
2308 }while((ngrp
= ngrp
->ngr_next
) != *lpp
);
2311 if(!re2
&& !subscribed_only
){
2313 lpp
= &a_nag_mlist_regex
;
2316 assert(rv
== MLIST_OTHER
);
2317 #endif /* HAVE_REGEX */
2325 is_mlist_mp(struct message
*mp
, enum mlist_state what
){
2328 enum mlist_state rv
;
2334 np
= lextract(hfield1("to", mp
), GTO
| GSKIN
);
2336 for(; np
!= NULL
; np
= np
->n_flink
){
2337 switch(is_mlist(np
->n_name
, FAL0
)){
2341 if(what
== MLIST_KNOWN
|| what
== MLIST_OTHER
){
2342 if(rv
== MLIST_OTHER
)
2344 if(what
== MLIST_KNOWN
)
2348 case MLIST_SUBSCRIBED
:
2349 if(what
== MLIST_SUBSCRIBED
|| what
== MLIST_OTHER
){
2350 if(rv
!= MLIST_SUBSCRIBED
)
2351 rv
= MLIST_SUBSCRIBED
;
2360 np
= lextract(hfield1("cc", mp
), GCC
| GSKIN
);
2369 c_shortcut(void *vp
){
2370 struct a_nag_group
*ngp
;
2379 a_nag_group_print_all(a_NAG_T_SHORTCUT
, NULL
);
2380 else if(argv
[1] == NULL
){
2381 if((ngp
= a_nag_group_find(a_NAG_T_SHORTCUT
, *argv
)) != NULL
)
2382 a_nag_group_print(ngp
, n_stdout
, NULL
);
2384 n_err(_("No such shortcut: %s\n"), n_shexp_quote_cp(*argv
, FAL0
));
2387 }else for(; *argv
!= NULL
; argv
+= 2){
2388 /* Because one hardly ever redefines, anything is stored in one chunk */
2392 if(argv
[1] == NULL
){
2393 n_err(_("Synopsis: shortcut: <shortcut> <expansion>\n"));
2397 if(a_nag_group_find(a_NAG_T_SHORTCUT
, *argv
) != NULL
)
2398 a_nag_group_del(a_NAG_T_SHORTCUT
, *argv
);
2400 l
= strlen(argv
[1]) +1;
2401 if((ngp
= a_nag_group_fetch(a_NAG_T_SHORTCUT
, *argv
, l
)) == NULL
){
2402 n_err(_("Failed to create storage for shortcut: %s\n"),
2403 n_shexp_quote_cp(*argv
, FAL0
));
2406 a_NAG_GP_TO_SUBCLASS(cp
, ngp
);
2407 memcpy(cp
, argv
[1], l
);
2415 c_unshortcut(void *vp
){
2423 do if(!a_nag_group_del(a_NAG_T_SHORTCUT
, *argv
)){
2424 n_err(_("No such shortcut: %s\n"), *argv
);
2426 }while(*++argv
!= NULL
);
2432 shortcut_expand(char const *str
){
2433 struct a_nag_group
*ngp
;
2436 if((ngp
= a_nag_group_find(a_NAG_T_SHORTCUT
, str
)) != NULL
)
2437 a_NAG_GP_TO_SUBCLASS(str
, ngp
);
2445 c_charsetalias(void *vp
){
2446 struct a_nag_group
*ngp
;
2455 a_nag_group_print_all(a_NAG_T_CHARSETALIAS
, NULL
);
2456 else if(argv
[1] == NULL
){
2457 if((ngp
= a_nag_group_find(a_NAG_T_CHARSETALIAS
, *argv
)) != NULL
)
2458 a_nag_group_print(ngp
, n_stdout
, NULL
);
2460 n_err(_("No such charsetalias: %s\n"), n_shexp_quote_cp(*argv
, FAL0
));
2463 }else for(; *argv
!= NULL
; argv
+= 2){
2464 /* Because one hardly ever redefines, anything is stored in one chunk */
2467 char const *dst
, *src
;
2469 if((dst
= argv
[1]) == NULL
){
2470 n_err(_("Synopsis: charsetalias: <charset> <charset-alias>\n"));
2473 }else if((dst
= n_iconv_normalize_name(dst
)) == NULL
){
2474 n_err(_("charsetalias: invalid target charset %s\n"),
2475 n_shexp_quote_cp(argv
[1], FAL0
));
2478 }else if((src
= n_iconv_normalize_name(argv
[0])) == NULL
){
2479 n_err(_("charsetalias: invalid source charset %s\n"),
2480 n_shexp_quote_cp(argv
[0], FAL0
));
2485 /* Delete the old one, if any; don't get fooled to remove them all */
2486 if(src
[0] != '*' || src
[1] != '\0')
2487 a_nag_group_del(a_NAG_T_CHARSETALIAS
, src
);
2489 dstl
= strlen(dst
) +1;
2490 if((ngp
= a_nag_group_fetch(a_NAG_T_CHARSETALIAS
, src
, dstl
)) == NULL
){
2491 n_err(_("Failed to create storage for charsetalias: %s\n"),
2492 n_shexp_quote_cp(src
, FAL0
));
2495 a_NAG_GP_TO_SUBCLASS(cp
, ngp
);
2496 memcpy(cp
, dst
, dstl
);
2504 c_uncharsetalias(void *vp
){
2513 if((cp
= n_iconv_normalize_name(*argv
)) == NULL
||
2514 !a_nag_group_del(a_NAG_T_CHARSETALIAS
, cp
)){
2515 n_err(_("No such `charsetalias': %s\n"),
2516 n_shexp_quote_cp(*argv
, FAL0
));
2519 }while(*++argv
!= NULL
);
2525 n_charsetalias_expand(char const *cp
){
2526 struct a_nag_group
*ngp
;
2528 char const *cp_orig
;
2533 for(i
= 0; (ngp
= a_nag_group_find(a_NAG_T_CHARSETALIAS
, cp
)) != NULL
;){
2534 a_NAG_GP_TO_SUBCLASS(cp
, ngp
);
2535 if(++i
== 8) /* XXX Magic (same as for `ghost' expansion) */
2546 c_filetype(void *vp
){ /* TODO support automatic chains: .tar.gz -> .gz + .tar */
2547 struct a_nag_group
*ngp
;
2548 char **argv
; /* TODO While there: let ! prefix mean: direct execlp(2) */
2556 a_nag_group_print_all(a_NAG_T_FILETYPE
, NULL
);
2557 else if(argv
[1] == NULL
){
2558 if((ngp
= a_nag_group_find(a_NAG_T_FILETYPE
, *argv
)) != NULL
)
2559 a_nag_group_print(ngp
, n_stdout
, NULL
);
2561 n_err(_("No such filetype: %s\n"), n_shexp_quote_cp(*argv
, FAL0
));
2564 }else for(; *argv
!= NULL
; argv
+= 3){
2565 /* Because one hardly ever redefines, anything is stored in one chunk */
2570 if(argv
[1] == NULL
|| argv
[2] == NULL
){
2571 n_err(_("Synopsis: filetype: <extension> <load-cmd> <save-cmd>\n"));
2576 /* Delete the old one, if any; don't get fooled to remove them all */
2578 if(ccp
[0] != '*' || ccp
[1] != '\0')
2579 a_nag_group_del(a_NAG_T_FILETYPE
, ccp
);
2581 /* Lowercase it all (for display purposes) */
2584 while((c
= *cp
) != '\0')
2585 *cp
++ = lowerconv(c
);
2587 llc
= strlen(argv
[1]) +1;
2588 lsc
= strlen(argv
[2]) +1;
2589 if(UIZ_MAX
- llc
<= lsc
)
2592 if((ngp
= a_nag_group_fetch(a_NAG_T_FILETYPE
, ccp
, llc
+ lsc
)) == NULL
){
2594 n_err(_("Failed to create storage for filetype: %s\n"),
2595 n_shexp_quote_cp(argv
[0], FAL0
));
2598 struct a_nag_file_type
*nftp
;
2600 a_NAG_GP_TO_SUBCLASS(nftp
, ngp
);
2601 a_NAG_GP_TO_SUBCLASS(cp
, ngp
);
2603 memcpy(nftp
->nft_load
.s
= cp
, argv
[1], llc
);
2605 nftp
->nft_load
.l
= --llc
;
2606 memcpy(nftp
->nft_save
.s
= cp
, argv
[2], lsc
);
2608 nftp
->nft_save
.l
= --lsc
;
2616 c_unfiletype(void *vp
){
2624 do if(!a_nag_group_del(a_NAG_T_FILETYPE
, *argv
)){
2625 n_err(_("No such `filetype': %s\n"), n_shexp_quote_cp(*argv
, FAL0
));
2627 }while(*++argv
!= NULL
);
2633 n_filetype_trial(struct n_file_type
*res_or_null
, char const *file
){
2635 struct a_nag_group_lookup ngl
;
2636 struct n_string s
, *sp
;
2637 struct a_nag_group
const *ngp
;
2641 sp
= n_string_creat_auto(&s
);
2642 sp
= n_string_assign_cp(sp
, file
);
2643 sp
= n_string_push_c(sp
, '.');
2646 for(ngp
= a_nag_group_go_first(a_NAG_T_FILETYPE
, &ngl
); ngp
!= NULL
;
2647 ngp
= a_nag_group_go_next(&ngl
)){
2648 sp
= n_string_trunc(sp
, l
);
2649 sp
= n_string_push_buf(sp
, ngp
->ng_id
,
2650 ngp
->ng_subclass_off
- ngp
->ng_id_len_sub
);
2652 if(!stat(n_string_cp(sp
), &stb
) && S_ISREG(stb
.st_mode
)){
2653 if(res_or_null
!= NULL
){
2654 struct a_nag_file_type
*nftp
;
2656 a_NAG_GP_TO_SUBCLASS(nftp
, ngp
);
2657 res_or_null
->ft_ext_dat
= ngp
->ng_id
;
2658 res_or_null
->ft_ext_len
= ngp
->ng_subclass_off
- ngp
->ng_id_len_sub
;
2659 res_or_null
->ft_load_dat
= nftp
->nft_load
.s
;
2660 res_or_null
->ft_load_len
= nftp
->nft_load
.l
;
2661 res_or_null
->ft_save_dat
= nftp
->nft_save
.s
;
2662 res_or_null
->ft_save_len
= nftp
->nft_save
.l
;
2664 goto jleave
; /* TODO after v15 legacy drop: break; */
2668 /* TODO v15 legacy code: automatic file hooks for .{bz2,gz,xz},
2669 * TODO but NOT supporting *file-hook-{load,save}-EXTENSION* */
2670 ngp
= (struct a_nag_group
*)0x1;
2672 sp
= n_string_trunc(sp
, l
);
2673 sp
= n_string_push_buf(sp
, a_nag_OBSOLETE_xz
.ft_ext_dat
,
2674 a_nag_OBSOLETE_xz
.ft_ext_len
);
2675 if(!stat(n_string_cp(sp
), &stb
) && S_ISREG(stb
.st_mode
)){
2676 n_OBSOLETE(".xz support will vanish, please use the `filetype' command");
2677 if(res_or_null
!= NULL
)
2678 *res_or_null
= a_nag_OBSOLETE_xz
;
2682 sp
= n_string_trunc(sp
, l
);
2683 sp
= n_string_push_buf(sp
, a_nag_OBSOLETE_gz
.ft_ext_dat
,
2684 a_nag_OBSOLETE_gz
.ft_ext_len
);
2685 if(!stat(n_string_cp(sp
), &stb
) && S_ISREG(stb
.st_mode
)){
2686 n_OBSOLETE(".gz support will vanish, please use the `filetype' command");
2687 if(res_or_null
!= NULL
)
2688 *res_or_null
= a_nag_OBSOLETE_gz
;
2692 sp
= n_string_trunc(sp
, l
);
2693 sp
= n_string_push_buf(sp
, a_nag_OBSOLETE_bz2
.ft_ext_dat
,
2694 a_nag_OBSOLETE_bz2
.ft_ext_len
);
2695 if(!stat(n_string_cp(sp
), &stb
) && S_ISREG(stb
.st_mode
)){
2696 n_OBSOLETE(".bz2 support will vanish, please use the `filetype' command");
2697 if(res_or_null
!= NULL
)
2698 *res_or_null
= a_nag_OBSOLETE_bz2
;
2706 return (ngp
!= NULL
);
2710 n_filetype_exists(struct n_file_type
*res_or_null
, char const *file
){
2711 char const *ext
, *lext
;
2714 if((ext
= strrchr(file
, '/')) != NULL
)
2717 for(lext
= NULL
; (ext
= strchr(file
, '.')) != NULL
; lext
= file
= ext
){
2718 struct a_nag_group
const *ngp
;
2720 if((ngp
= a_nag_group_find(a_NAG_T_FILETYPE
, ++ext
)) != NULL
){
2722 if(res_or_null
!= NULL
){
2723 struct a_nag_file_type
*nftp
;
2725 a_NAG_GP_TO_SUBCLASS(nftp
, ngp
);
2726 res_or_null
->ft_ext_dat
= ngp
->ng_id
;
2727 res_or_null
->ft_ext_len
= ngp
->ng_subclass_off
- ngp
->ng_id_len_sub
;
2728 res_or_null
->ft_load_dat
= nftp
->nft_load
.s
;
2729 res_or_null
->ft_load_len
= nftp
->nft_load
.l
;
2730 res_or_null
->ft_save_dat
= nftp
->nft_save
.s
;
2731 res_or_null
->ft_save_len
= nftp
->nft_save
.l
;
2733 goto jleave
; /* TODO after v15 legacy drop: break; */
2737 /* TODO v15 legacy code: automatic file hooks for .{bz2,gz,xz},
2738 * TODO as well as supporting *file-hook-{load,save}-EXTENSION* */
2742 if(!asccasecmp(lext
, "xz")){
2743 n_OBSOLETE(".xz support will vanish, please use the `filetype' command");
2744 if(res_or_null
!= NULL
)
2745 *res_or_null
= a_nag_OBSOLETE_xz
;
2747 }else if(!asccasecmp(lext
, "gz")){
2748 n_OBSOLETE(".gz support will vanish, please use the `filetype' command");
2749 if(res_or_null
!= NULL
)
2750 *res_or_null
= a_nag_OBSOLETE_gz
;
2752 }else if(!asccasecmp(lext
, "bz2")){
2753 n_OBSOLETE(".bz2 support will vanish, please use the `filetype' command");
2754 if(res_or_null
!= NULL
)
2755 *res_or_null
= a_nag_OBSOLETE_bz2
;
2758 char const *cload
, *csave
;
2763 #define a_X1 "file-hook-load-"
2765 #define a_X2 "file-hook-save-"
2767 vbuf
= n_lofi_alloc(l
+ n_MAX(sizeof(a_X1
), sizeof(a_X2
)));
2769 memcpy(vbuf
, a_X1
, sizeof(a_X1
) -1);
2770 memcpy(&vbuf
[sizeof(a_X1
) -1], lext
, l
);
2771 vbuf
[sizeof(a_X1
) -1 + l
] = '\0';
2772 cload
= n_var_vlook(vbuf
, FAL0
);
2774 memcpy(vbuf
, a_X2
, sizeof(a_X2
) -1);
2775 memcpy(&vbuf
[sizeof(a_X2
) -1], lext
, l
);
2776 vbuf
[sizeof(a_X2
) -1 + l
] = '\0';
2777 csave
= n_var_vlook(vbuf
, FAL0
);
2783 if((csave
!= NULL
) | (cload
!= NULL
)){
2784 n_OBSOLETE("*file-hook-{load,save}-EXTENSION* will vanish, "
2785 "please use the `filetype' command");
2787 if(((csave
!= NULL
) ^ (cload
!= NULL
)) == 0){
2788 if(res_or_null
!= NULL
){
2789 res_or_null
->ft_ext_dat
= lext
;
2790 res_or_null
->ft_ext_len
= l
;
2791 res_or_null
->ft_load_dat
= cload
;
2792 res_or_null
->ft_load_len
= strlen(cload
);
2793 res_or_null
->ft_save_dat
= csave
;
2794 res_or_null
->ft_save_len
= strlen(csave
);
2798 n_alert(_("Incomplete *file-hook-{load,save}-EXTENSION* for: .%s"),
2807 return (lext
!= NULL
);