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>.
7 * SPDX-License-Identifier: BSD-3-Clause TODO ISC
10 * Copyright (c) 1980, 1993
11 * The Regents of the University of California. All rights reserved.
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions
16 * 1. Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
18 * 2. Redistributions in binary form must reproduce the above copyright
19 * notice, this list of conditions and the following disclaimer in the
20 * documentation and/or other materials provided with the distribution.
21 * 3. Neither the name of the University nor the names of its contributors
22 * may be used to endorse or promote products derived from this software
23 * without specific prior written permission.
25 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
29 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
38 #define n_FILE nam_a_grp
40 #ifndef HAVE_AMALGAMATION
46 a_NAG_T_ALTERNATES
= 1,
55 /* Subtype bits and flags */
56 a_NAG_T_SUBSCRIBE
= 1u<<6,
57 a_NAG_T_REGEX
= 1u<<7,
59 /* Extended type mask to be able to reflect what we really have; i.e., mlist
60 * can have a_NAG_T_REGEX if they are subscribed or not, but `mlsubscribe'
61 * should print out only a_NAG_T_MLIST which have the a_NAG_T_SUBSCRIBE
63 a_NAG_T_PRINT_MASK
= a_NAG_T_MASK
| a_NAG_T_SUBSCRIBE
65 n_CTA(a_NAG_T_MASK
>= a_NAG_T_FILETYPE
, "Mask does not cover necessary bits");
68 struct a_nag_group
*ng_next
;
69 ui32_t ng_subclass_off
; /* of "subclass" in .ng_id (if any) */
70 ui16_t ng_id_len_sub
; /* length of .ng_id: _subclass_off - this */
71 ui8_t ng_type
; /* enum a_nag_type */
72 /* Identifying name, of variable size. Dependent on actual "subtype" more
73 * data follows thereafter, but note this is always used (i.e., for regular
74 * expression entries this is still set to the plain string) */
75 char ng_id
[n_VFIELD_SIZE(1)];
77 #define a_NAG_GP_TO_SUBCLASS(X,G) \
79 union a_nag_group_subclass {void *gs_vp; char *gs_cp;} a__gs__;\
80 a__gs__.gs_cp = &((char*)n_UNCONST(G))[(G)->ng_subclass_off];\
84 struct a_nag_grp_names_head
{
85 struct a_nag_grp_names
*ngnh_head
;
88 struct a_nag_grp_names
{
89 struct a_nag_grp_names
*ngn_next
;
90 char ngn_id
[n_VFIELD_SIZE(0)];
94 struct a_nag_grp_regex
{
95 struct a_nag_grp_regex
*ngr_last
;
96 struct a_nag_grp_regex
*ngr_next
;
97 struct a_nag_group
*ngr_mygroup
; /* xxx because lists use grp_regex*! ?? */
98 size_t ngr_hits
; /* Number of times this group matched */
103 struct a_nag_cmd_alias
{
104 struct str nca_expand
;
107 struct a_nag_file_type
{
112 struct a_nag_group_lookup
{
113 struct a_nag_group
**ngl_htable
;
114 struct a_nag_group
**ngl_slot
;
115 struct a_nag_group
*ngl_slot_last
;
116 struct a_nag_group
*ngl_group
;
119 static struct n_file_type
const a_nag_OBSOLETE_xz
= { /* TODO v15 compat */
120 "xz", 2, "xz -cd", sizeof("xz -cd") -1, "xz -cz", sizeof("xz -cz") -1
121 }, a_nag_OBSOLETE_gz
= {
122 "gz", 2, "gzip -cd", sizeof("gzip -cd") -1, "gzip -cz", sizeof("gzip -cz") -1
123 }, a_nag_OBSOLETE_bz2
= {
124 "bz2", 3, "bzip2 -cd", sizeof("bzip2 -cd") -1,
125 "bzip2 -cz", sizeof("bzip2 -cz") -1
129 static struct a_nag_group
*a_nag_alternates_heads
[HSHSIZE
];
132 static struct a_nag_group
*a_nag_commandalias_heads
[HSHSIZE
];
135 static struct a_nag_group
*a_nag_alias_heads
[HSHSIZE
];
137 /* `mlist', `mlsubscribe'. Anything is stored in the hashmap.. */
138 static struct a_nag_group
*a_nag_mlist_heads
[HSHSIZE
];
140 /* ..but entries which have a_NAG_T_REGEX set are false lookups and will really
141 * be accessed via sequential lists instead, which are type-specific for better
142 * performance, but also to make it possible to have ".*@xy.org" as a mlist
143 * and "(one|two)@xy.org" as a mlsubscription.
144 * These lists use a bit of QOS optimization in that a matching group will
145 * become relinked as the new list head if its hit count is
146 * (>= ((xy_hits / _xy_size) >> 2))
147 * Note that the hit counts only count currently linked in nodes.. */
149 static struct a_nag_grp_regex
*a_nag_mlist_regex
;
150 static struct a_nag_grp_regex
*a_nag_mlsub_regex
;
151 static size_t a_nag_mlist_size
;
152 static size_t a_nag_mlist_hits
;
153 static size_t a_nag_mlsub_size
;
154 static size_t a_nag_mlsub_hits
;
158 static struct a_nag_group
*a_nag_shortcut_heads
[HSHSIZE
];
161 static struct a_nag_group
*a_nag_charsetalias_heads
[HSHSIZE
];
164 static struct a_nag_group
*a_nag_filetype_heads
[HSHSIZE
];
166 /* Same name, while taking care for *allnet*? */
167 static bool_t
a_nag_is_same_name(char const *n1
, char const *n2
);
169 /* Mark all (!file, !pipe) nodes with the given name */
170 static struct name
*a_nag_namelist_mark_name(struct name
*np
, char const *name
);
172 /* Grab a single name (liberal name) */
173 static char const *a_nag_yankname(char const *ap
, char *wbuf
,
174 char const *separators
, int keepcomms
);
176 /* Extraction multiplexer that splits an input line to names */
177 static struct name
*a_nag_extract1(char const *line
, enum gfield ntype
,
178 char const *separators
, bool_t keepcomms
);
180 /* Recursively expand a alias name. Limit expansion to some fixed level.
181 * Direct recursion is not expanded for convenience */
182 static struct name
*a_nag_gexpand(size_t level
, struct name
*nlist
,
183 struct a_nag_group
*ngp
, bool_t metoo
, int ntype
);
186 static int a_nag_elide_qsort(void const *s1
, void const *s2
);
188 /* Lookup a group, return it or NULL, fill in glp anyway */
189 static struct a_nag_group
*a_nag_group_lookup(enum a_nag_type nt
,
190 struct a_nag_group_lookup
*nglp
, char const *id
);
192 /* Easier-to-use wrapper around _group_lookup() */
193 static struct a_nag_group
*a_nag_group_find(enum a_nag_type nt
, char const *id
);
195 /* Iteration: go to the first group, which also inits the iterator. A valid
196 * iterator can be stepped via _next(). A NULL return means no (more) groups
197 * to be iterated exist, in which case only nglp->ngl_group is set (NULL) */
198 static struct a_nag_group
*a_nag_group_go_first(enum a_nag_type nt
,
199 struct a_nag_group_lookup
*nglp
);
200 static struct a_nag_group
*a_nag_group_go_next(struct a_nag_group_lookup
*nglp
);
202 /* Fetch the group id, create it as necessary, fail with NULL if impossible */
203 static struct a_nag_group
*a_nag_group_fetch(enum a_nag_type nt
, char const *id
,
206 /* "Intelligent" delete which handles a "*" id, too;
207 * returns a true boolean if a group was deleted, and always succeeds for "*" */
208 static bool_t
a_nag_group_del(enum a_nag_type nt
, char const *id
);
210 static struct a_nag_group
*a_nag__group_del(struct a_nag_group_lookup
*nglp
);
211 static void a_nag__names_del(struct a_nag_group
*ngp
);
213 /* Print all groups of the given type, alphasorted, or store in `vput' varname:
214 * only in this mode it can return failure */
215 static bool_t
a_nag_group_print_all(enum a_nag_type nt
,
216 char const *varname
);
218 static int a_nag__group_print_qsorter(void const *a
, void const *b
);
220 /* Really print a group, actually. Or store in vputsp, if set.
221 * Return number of written lines */
222 static size_t a_nag_group_print(struct a_nag_group
const *ngp
, FILE *fo
,
223 struct n_string
*vputsp
);
225 /* Multiplexers for list and subscribe commands */
226 static int a_nag_mlmux(enum a_nag_type nt
, char const **argv
);
227 static int a_nag_unmlmux(enum a_nag_type nt
, char const **argv
);
229 /* Relinkers for the sequential match lists */
231 static void a_nag_mlmux_linkin(struct a_nag_group
*ngp
);
232 static void a_nag_mlmux_linkout(struct a_nag_group
*ngp
);
233 # define a_NAG_MLMUX_LINKIN(GP) \
234 do if((GP)->ng_type & a_NAG_T_REGEX) a_nag_mlmux_linkin(GP); while(0)
235 # define a_NAG_MLMUX_LINKOUT(GP) \
236 do if((GP)->ng_type & a_NAG_T_REGEX) a_nag_mlmux_linkout(GP); while(0)
238 # define a_NAG_MLMUX_LINKIN(GP)
239 # define a_NAG_MLMUX_LINKOUT(GP)
243 a_nag_is_same_name(char const *n1
, char const *n2
){
245 char c1
, c2
, c1r
, c2r
;
248 if(ok_blook(allnet
)){
252 c1r
= (c1
== '\0' || c1
== '@');
255 c2r
= (c2
== '\0' || c2
== '@');
266 rv
= !asccasecmp(n1
, n2
);
272 a_nag_namelist_mark_name(struct name
*np
, char const *name
){
276 for(p
= np
; p
!= NULL
; p
= p
->n_flink
)
277 if(!(p
->n_type
& GDEL
) &&
278 !(p
->n_flags
& (((ui32_t
)SI32_MIN
) | NAME_ADDRSPEC_ISFILE
|
279 NAME_ADDRSPEC_ISPIPE
)) &&
280 a_nag_is_same_name(p
->n_name
, name
))
281 p
->n_flags
|= (ui32_t
)SI32_MIN
;
287 a_nag_yankname(char const *ap
, char *wbuf
, char const *separators
,
291 char *wp
, c
, inquote
, lc
, lastsp
;
296 /* Skip over intermediate list trash, as in ".org> , <xy@zz.org>" */
297 for (c
= *ap
; blankchar(c
) || c
== ','; c
= *++ap
)
304 /* Parse a full name: TODO RFC 5322
305 * - Keep everything in quotes, liberal handle *quoted-pair*s therein
306 * - Skip entire (nested) comments
307 * - In non-quote, non-comment, join adjacent space to a single SP
308 * - Understand separators only in non-quote, non-comment context,
309 * and only if not part of a *quoted-pair* (XXX too liberal) */
311 for (inquote
= lc
= lastsp
= 0;; lc
= c
, ++cp
) {
320 #if 0 /* TODO when doing real RFC 5322 parsers - why have i done this? */
326 if (inquote
|| lc
== '\\') {
334 cp
= skip_comment(cp
+ 1);
342 if (strchr(separators
, c
) != NULL
)
346 lastsp
= blankchar(c
);
360 a_nag_extract1(char const *line
, enum gfield ntype
, char const *separators
,
363 struct name
*topp
, *np
, *t
;
369 if (line
== NULL
|| *line
== '\0')
374 nbuf
= n_alloc(strlen(line
) +1);
375 while ((cp
= a_nag_yankname(cp
, nbuf
, separators
, keepcomms
)) != NULL
) {
376 t
= nalloc(nbuf
, ntype
);
391 a_nag_gexpand(size_t level
, struct name
*nlist
, struct a_nag_group
*ngp
,
392 bool_t metoo
, int ntype
){
393 struct a_nag_grp_names
*ngnp
;
394 struct name
*nlist_tail
;
396 struct a_nag_grp_names_head
*ngnhp
;
399 if(UICMP(z
, level
++, >, n_ALIAS_MAXEXP
)){
400 n_err(_("Expanding alias to depth larger than %d\n"), n_ALIAS_MAXEXP
);
404 a_NAG_GP_TO_SUBCLASS(ngnhp
, ngp
);
405 logname
= ok_vlook(LOGNAME
);
407 for(ngnp
= ngnhp
->ngnh_head
; ngnp
!= NULL
; ngnp
= ngnp
->ngn_next
){
408 struct a_nag_group
*xngp
;
413 if(!strcmp(cp
, ngp
->ng_id
))
416 if((xngp
= a_nag_group_find(a_NAG_T_ALIAS
, cp
)) != NULL
){
417 /* For S-nail(1), the "alias" may *be* the sender in that a name maps
418 * to a full address specification; aliases cannot be empty */
419 struct a_nag_grp_names_head
*xngnhp
;
421 a_NAG_GP_TO_SUBCLASS(xngnhp
, xngp
);
423 assert(xngnhp
->ngnh_head
!= NULL
);
424 if(metoo
|| xngnhp
->ngnh_head
->ngn_next
!= NULL
||
425 !a_nag_is_same_name(cp
, logname
))
426 nlist
= a_nag_gexpand(level
, nlist
, xngp
, metoo
, ntype
);
430 /* Here we should allow to expand to itself if only person in alias */
432 if(metoo
|| ngnhp
->ngnh_head
->ngn_next
== NULL
||
433 !a_nag_is_same_name(cp
, logname
)){
436 np
= nalloc(cp
, ntype
| GFULL
);
437 if((nlist_tail
= nlist
) != NULL
){
438 while(nlist_tail
->n_flink
!= NULL
)
439 nlist_tail
= nlist_tail
->n_flink
;
440 nlist_tail
->n_flink
= np
;
441 np
->n_blink
= nlist_tail
;
452 a_nag_elide_qsort(void const *s1
, void const *s2
){
453 struct name
const * const *np1
, * const *np2
;
459 if(!(rv
= asccasecmp((*np1
)->n_name
, (*np2
)->n_name
))){
460 n_LCTAV(GTO
< GCC
&& GCC
< GBCC
);
461 rv
= ((*np1
)->n_type
& (GTO
| GCC
| GBCC
)) -
462 ((*np2
)->n_type
& (GTO
| GCC
| GBCC
));
468 static struct a_nag_group
*
469 a_nag_group_lookup(enum a_nag_type nt
, struct a_nag_group_lookup
*nglp
,
472 struct a_nag_group
*lngp
, *ngp
;
480 struct a_nag_group
**ngpa
;
482 switch((nt
&= a_NAG_T_MASK
)){
483 case a_NAG_T_ALTERNATES
:
484 ngpa
= a_nag_alternates_heads
;
488 case a_NAG_T_COMMANDALIAS
:
489 ngpa
= a_nag_commandalias_heads
;
492 ngpa
= a_nag_alias_heads
;
495 ngpa
= a_nag_mlist_heads
;
498 case a_NAG_T_SHORTCUT
:
499 ngpa
= a_nag_shortcut_heads
;
501 case a_NAG_T_CHARSETALIAS
:
502 ngpa
= a_nag_charsetalias_heads
;
505 case a_NAG_T_FILETYPE
:
506 ngpa
= a_nag_filetype_heads
;
511 nglp
->ngl_htable
= ngpa
;
512 h
= icase
? n_torek_ihash(id
) : n_torek_hash(id
);
513 ngp
= *(nglp
->ngl_slot
= &ngpa
[h
% HSHSIZE
]);
521 for(; ngp
!= NULL
; lngp
= ngp
, ngp
= ngp
->ng_next
)
522 if((ngp
->ng_type
& a_NAG_T_MASK
) == nt
&& *ngp
->ng_id
== c1
&&
523 !asccasecmp(&ngp
->ng_id
[1], id
))
526 for(; ngp
!= NULL
; lngp
= ngp
, ngp
= ngp
->ng_next
)
527 if((ngp
->ng_type
& a_NAG_T_MASK
) == nt
&& *ngp
->ng_id
== c1
&&
528 !strcmp(&ngp
->ng_id
[1], id
))
532 nglp
->ngl_slot_last
= lngp
;
533 nglp
->ngl_group
= ngp
;
538 static struct a_nag_group
*
539 a_nag_group_find(enum a_nag_type nt
, char const *id
){
540 struct a_nag_group_lookup ngl
;
541 struct a_nag_group
*ngp
;
544 ngp
= a_nag_group_lookup(nt
, &ngl
, id
);
549 static struct a_nag_group
*
550 a_nag_group_go_first(enum a_nag_type nt
, struct a_nag_group_lookup
*nglp
){
552 struct a_nag_group
**ngpa
, *ngp
;
555 switch((nt
&= a_NAG_T_MASK
)){
556 case a_NAG_T_ALTERNATES
:
557 ngpa
= a_nag_alternates_heads
;
560 case a_NAG_T_COMMANDALIAS
:
561 ngpa
= a_nag_commandalias_heads
;
564 ngpa
= a_nag_alias_heads
;
567 ngpa
= a_nag_mlist_heads
;
569 case a_NAG_T_SHORTCUT
:
570 ngpa
= a_nag_shortcut_heads
;
572 case a_NAG_T_CHARSETALIAS
:
573 ngpa
= a_nag_charsetalias_heads
;
575 case a_NAG_T_FILETYPE
:
576 ngpa
= a_nag_filetype_heads
;
580 nglp
->ngl_htable
= ngpa
;
582 for(i
= 0; i
< HSHSIZE
; ++ngpa
, ++i
)
583 if((ngp
= *ngpa
) != NULL
){
584 nglp
->ngl_slot
= ngpa
;
585 nglp
->ngl_group
= ngp
;
589 nglp
->ngl_group
= ngp
= NULL
;
591 nglp
->ngl_slot_last
= NULL
;
596 static struct a_nag_group
*
597 a_nag_group_go_next(struct a_nag_group_lookup
*nglp
){
598 struct a_nag_group
*ngp
, **ngpa
;
601 if((ngp
= nglp
->ngl_group
->ng_next
) != NULL
)
602 nglp
->ngl_slot_last
= nglp
->ngl_group
;
604 nglp
->ngl_slot_last
= NULL
;
605 for(ngpa
= &nglp
->ngl_htable
[HSHSIZE
]; ++nglp
->ngl_slot
< ngpa
;)
606 if((ngp
= *nglp
->ngl_slot
) != NULL
)
609 nglp
->ngl_group
= ngp
;
614 static struct a_nag_group
*
615 a_nag_group_fetch(enum a_nag_type nt
, char const *id
, size_t addsz
){
616 struct a_nag_group_lookup ngl
;
617 struct a_nag_group
*ngp
;
621 if((ngp
= a_nag_group_lookup(nt
, &ngl
, id
)) != NULL
)
625 if(UIZ_MAX
- n_ALIGN(l
) <=
626 n_ALIGN(n_VSTRUCT_SIZEOF(struct a_nag_group
, ng_id
)))
629 i
= n_ALIGN(n_VSTRUCT_SIZEOF(struct a_nag_group
, ng_id
) + l
);
630 switch(nt
& a_NAG_T_MASK
){
631 case a_NAG_T_ALTERNATES
:
632 case a_NAG_T_SHORTCUT
:
633 case a_NAG_T_CHARSETALIAS
:
636 case a_NAG_T_COMMANDALIAS
:
637 addsz
+= sizeof(struct a_nag_cmd_alias
);
640 addsz
+= sizeof(struct a_nag_grp_names_head
);
644 if(n_is_maybe_regex(id
)){
645 addsz
= sizeof(struct a_nag_grp_regex
);
650 case a_NAG_T_FILETYPE
:
651 addsz
+= sizeof(struct a_nag_file_type
);
654 if(UIZ_MAX
- i
< addsz
|| UI32_MAX
<= i
|| UI16_MAX
< i
- l
)
657 ngp
= n_alloc(i
+ addsz
);
658 memcpy(ngp
->ng_id
, id
, l
);
659 ngp
->ng_subclass_off
= (ui32_t
)i
;
660 ngp
->ng_id_len_sub
= (ui16_t
)(i
- --l
);
662 switch(nt
& a_NAG_T_MASK
){
663 case a_NAG_T_ALTERNATES
:
665 case a_NAG_T_CHARSETALIAS
:
666 case a_NAG_T_FILETYPE
:{
669 for(cp
= ngp
->ng_id
; (c
= *cp
) != '\0'; ++cp
)
676 if((nt
& a_NAG_T_MASK
) == a_NAG_T_ALIAS
){
677 struct a_nag_grp_names_head
*ngnhp
;
679 a_NAG_GP_TO_SUBCLASS(ngnhp
, ngp
);
680 ngnhp
->ngnh_head
= NULL
;
683 else if(nt
& a_NAG_T_REGEX
){
685 struct a_nag_grp_regex
*ngrp
;
687 a_NAG_GP_TO_SUBCLASS(ngrp
, ngp
);
689 if((s
= regcomp(&ngrp
->ngr_regex
, id
,
690 REG_EXTENDED
| REG_ICASE
| REG_NOSUB
)) != 0){
691 n_err(_("Invalid regular expression: %s: %s\n"),
692 n_shexp_quote_cp(id
, FAL0
), n_regex_err_to_doc(NULL
, s
));
697 ngrp
->ngr_mygroup
= ngp
;
698 a_nag_mlmux_linkin(ngp
);
700 #endif /* HAVE_REGEX */
702 ngp
->ng_next
= *ngl
.ngl_slot
;
710 a_nag_group_del(enum a_nag_type nt
, char const *id
){
711 struct a_nag_group_lookup ngl
;
712 struct a_nag_group
*ngp
;
716 xnt
= nt
& a_NAG_T_MASK
;
718 /* Delete 'em all? */
719 if(id
[0] == '*' && id
[1] == '\0'){
720 for(ngp
= a_nag_group_go_first(nt
, &ngl
); ngp
!= NULL
;)
721 ngp
= ((ngp
->ng_type
& a_NAG_T_MASK
) == xnt
) ? a_nag__group_del(&ngl
)
722 : a_nag_group_go_next(&ngl
);
723 ngp
= (struct a_nag_group
*)TRU1
;
724 }else if((ngp
= a_nag_group_lookup(nt
, &ngl
, id
)) != NULL
){
725 if(ngp
->ng_type
& xnt
)
726 a_nag__group_del(&ngl
);
731 return (ngp
!= NULL
);
734 static struct a_nag_group
*
735 a_nag__group_del(struct a_nag_group_lookup
*nglp
){
736 struct a_nag_group
*x
, *ngp
;
739 /* Overly complicated: link off this node, step ahead to next.. */
741 if((ngp
= nglp
->ngl_slot_last
) != NULL
)
742 ngp
= (ngp
->ng_next
= x
->ng_next
);
744 nglp
->ngl_slot_last
= NULL
;
745 ngp
= (*nglp
->ngl_slot
= x
->ng_next
);
748 struct a_nag_group
**ngpa
;
750 for(ngpa
= &nglp
->ngl_htable
[HSHSIZE
]; ++nglp
->ngl_slot
< ngpa
;)
751 if((ngp
= *nglp
->ngl_slot
) != NULL
)
755 nglp
->ngl_group
= ngp
;
757 if((x
->ng_type
& a_NAG_T_MASK
) == a_NAG_T_ALIAS
)
760 else if(x
->ng_type
& a_NAG_T_REGEX
){
761 struct a_nag_grp_regex
*ngrp
;
763 a_NAG_GP_TO_SUBCLASS(ngrp
, x
);
765 regfree(&ngrp
->ngr_regex
);
766 a_nag_mlmux_linkout(x
);
776 a_nag__names_del(struct a_nag_group
*ngp
){
777 struct a_nag_grp_names_head
*ngnhp
;
778 struct a_nag_grp_names
*ngnp
;
781 a_NAG_GP_TO_SUBCLASS(ngnhp
, ngp
);
783 for(ngnp
= ngnhp
->ngnh_head
; ngnp
!= NULL
;){
784 struct a_nag_grp_names
*x
;
787 ngnp
= ngnp
->ngn_next
;
794 a_nag_group_print_all(enum a_nag_type nt
, char const *varname
){
799 struct a_nag_group
const *ngp
;
801 struct a_nag_group
**ngpa
;
807 n_string_creat_auto(&s
);
809 xnt
= nt
& a_NAG_T_PRINT_MASK
;
811 switch(xnt
& a_NAG_T_MASK
){
812 case a_NAG_T_ALTERNATES
:
813 tname
= "alternates";
814 ngpa
= a_nag_alternates_heads
;
817 case a_NAG_T_COMMANDALIAS
:
818 tname
= "commandalias";
819 ngpa
= a_nag_commandalias_heads
;
823 ngpa
= a_nag_alias_heads
;
827 ngpa
= a_nag_mlist_heads
;
829 case a_NAG_T_SHORTCUT
:
831 ngpa
= a_nag_shortcut_heads
;
833 case a_NAG_T_CHARSETALIAS
:
834 tname
= "charsetalias";
835 ngpa
= a_nag_charsetalias_heads
;
837 case a_NAG_T_FILETYPE
:
839 ngpa
= a_nag_filetype_heads
;
844 for(i
= h
= 0; h
< HSHSIZE
; ++h
)
845 for(ngp
= ngpa
[h
]; ngp
!= NULL
; ngp
= ngp
->ng_next
)
846 if((ngp
->ng_type
& a_NAG_T_PRINT_MASK
) == xnt
)
850 fprintf(n_stdout
, _("# no %s registered\n"), tname
);
854 ida
= n_autorec_alloc(i
* sizeof *ida
);
856 /* Create alpha sorted array of entries */
857 for(i
= h
= 0; h
< HSHSIZE
; ++h
)
858 for(ngp
= ngpa
[h
]; ngp
!= NULL
; ngp
= ngp
->ng_next
)
859 if((ngp
->ng_type
& a_NAG_T_PRINT_MASK
) == xnt
)
860 ida
[i
++] = ngp
->ng_id
;
862 qsort(ida
, i
, sizeof *ida
, &a_nag__group_print_qsorter
);
867 else if((fp
= Ftmp(NULL
, "nagprint", OF_RDWR
| OF_UNLINK
| OF_REGISTER
)
871 /* Create visual result */
874 switch(xnt
& a_NAG_T_MASK
){
875 case a_NAG_T_ALTERNATES
:
885 for(i
= 0; ida
[i
] != NULL
; ++i
)
886 lines
+= a_nag_group_print(a_nag_group_find(nt
, ida
[i
]), fp
, &s
);
889 if(varname
== NULL
&& (nt
& a_NAG_T_MASK
) == a_NAG_T_MLIST
){
890 if(nt
& a_NAG_T_SUBSCRIBE
)
891 i
= (ui32_t
)a_nag_mlsub_size
, h
= (ui32_t
)a_nag_mlsub_hits
;
893 i
= (ui32_t
)a_nag_mlist_size
, h
= (ui32_t
)a_nag_mlist_hits
;
895 if(i
> 0 && (n_poption
& n_PO_D_V
)){
897 fprintf(fp
, _("# %s list regex(7) total: %u entries, %u hits\n"),
898 (nt
& a_NAG_T_SUBSCRIBE
? _("Subscribed") : _("Non-subscribed")),
905 switch(xnt
& a_NAG_T_MASK
){
906 case a_NAG_T_ALTERNATES
:
916 if(varname
== NULL
&& fp
!= n_stdout
){
918 page_or_print(fp
, lines
);
924 tname
= n_string_cp(&s
);
925 if(n_var_vset(varname
, (uintptr_t)tname
))
928 n_pstate_err_no
= n_ERR_NOTSUP
;
931 return (varname
== NULL
);
935 a_nag__group_print_qsorter(void const *a
, void const *b
){
939 rv
= strcmp(*(char**)n_UNCONST(a
), *(char**)n_UNCONST(b
));
945 a_nag_group_print(struct a_nag_group
const *ngp
, FILE *fo
,
946 struct n_string
*vputsp
){
953 switch(ngp
->ng_type
& a_NAG_T_MASK
){
954 case a_NAG_T_ALTERNATES
:{
956 fprintf(fo
, " %s", ngp
->ng_id
);
958 if(vputsp
->s_len
> 0)
959 vputsp
= n_string_push_c(vputsp
, ' ');
960 /*vputsp =*/ n_string_push_cp(vputsp
, ngp
->ng_id
);
964 case a_NAG_T_COMMANDALIAS
:{
965 struct a_nag_cmd_alias
*ncap
;
967 assert(fo
!= NULL
); /* xxx no vput yet */
968 a_NAG_GP_TO_SUBCLASS(ncap
, ngp
);
969 fprintf(fo
, "commandalias %s %s\n",
970 n_shexp_quote_cp(ngp
->ng_id
, TRU1
),
971 n_shexp_quote_cp(ncap
->nca_expand
.s
, TRU1
));
974 struct a_nag_grp_names_head
*ngnhp
;
975 struct a_nag_grp_names
*ngnp
;
977 assert(fo
!= NULL
); /* xxx no vput yet */
978 fprintf(fo
, "alias %s ", ngp
->ng_id
);
980 a_NAG_GP_TO_SUBCLASS(ngnhp
, ngp
);
981 if((ngnp
= ngnhp
->ngnh_head
) != NULL
) { /* xxx always 1+ entries */
983 struct a_nag_grp_names
*x
;
986 ngnp
= ngnp
->ngn_next
;
987 fprintf(fo
, " \"%s\"", string_quote(x
->ngn_id
)); /* TODO shexp */
988 }while(ngnp
!= NULL
);
993 assert(fo
!= NULL
); /* xxx no vput yet */
995 if((ngp
->ng_type
& a_NAG_T_REGEX
) && (n_poption
& n_PO_D_V
)){
997 struct a_nag_grp_regex
*lp
, *ngrp
;
999 lp
= (ngp
->ng_type
& a_NAG_T_SUBSCRIBE
? a_nag_mlsub_regex
1000 : a_nag_mlist_regex
);
1001 a_NAG_GP_TO_SUBCLASS(ngrp
, ngp
);
1002 for(i
= 1; lp
!= ngrp
; lp
= lp
->ngr_next
)
1004 fprintf(fo
, "# regex(7): hits %" PRIuZ
", sort %" PRIuZ
".\n ",
1009 fprintf(fo
, "wysh %s %s\n",
1010 (ngp
->ng_type
& a_NAG_T_SUBSCRIBE
? "mlsubscribe" : "mlist"),
1011 n_shexp_quote_cp(ngp
->ng_id
, TRU1
));
1013 case a_NAG_T_SHORTCUT
:
1014 assert(fo
!= NULL
); /* xxx no vput yet */
1015 a_NAG_GP_TO_SUBCLASS(cp
, ngp
);
1016 fprintf(fo
, "wysh shortcut %s %s\n",
1017 ngp
->ng_id
, n_shexp_quote_cp(cp
, TRU1
));
1019 case a_NAG_T_CHARSETALIAS
:
1020 assert(fo
!= NULL
); /* xxx no vput yet */
1021 a_NAG_GP_TO_SUBCLASS(cp
, ngp
);
1022 fprintf(fo
, "charsetalias %s %s\n",
1023 n_shexp_quote_cp(ngp
->ng_id
, TRU1
), n_shexp_quote_cp(cp
, TRU1
));
1025 case a_NAG_T_FILETYPE
:{
1026 struct a_nag_file_type
*nftp
;
1028 assert(fo
!= NULL
); /* xxx no vput yet */
1029 a_NAG_GP_TO_SUBCLASS(nftp
, ngp
);
1030 fprintf(fo
, "filetype %s %s %s\n",
1031 n_shexp_quote_cp(ngp
->ng_id
, TRU1
),
1032 n_shexp_quote_cp(nftp
->nft_load
.s
, TRU1
),
1033 n_shexp_quote_cp(nftp
->nft_save
.s
, TRU1
));
1041 a_nag_mlmux(enum a_nag_type nt
, char const **argv
){
1042 struct a_nag_group
*ngp
;
1048 n_UNINIT(ecp
, NULL
);
1051 a_nag_group_print_all(nt
, NULL
);
1053 if((ngp
= a_nag_group_find(nt
, *argv
)) != NULL
){
1054 if(nt
& a_NAG_T_SUBSCRIBE
){
1055 if(!(ngp
->ng_type
& a_NAG_T_SUBSCRIBE
)){
1056 a_NAG_MLMUX_LINKOUT(ngp
);
1057 ngp
->ng_type
|= a_NAG_T_SUBSCRIBE
;
1058 a_NAG_MLMUX_LINKIN(ngp
);
1060 ecp
= N_("Mailing-list already `mlsubscribe'd: %s\n");
1064 ecp
= N_("Mailing-list already `mlist'ed: %s\n");
1067 }else if(a_nag_group_fetch(nt
, *argv
, 0) == NULL
){
1068 ecp
= N_("Failed to create storage for mailing-list: %s\n");
1070 n_err(V_(ecp
), n_shexp_quote_cp(*argv
, FAL0
));
1073 }while(*++argv
!= NULL
);
1080 a_nag_unmlmux(enum a_nag_type nt
, char const **argv
){
1081 struct a_nag_group
*ngp
;
1087 for(; *argv
!= NULL
; ++argv
){
1088 if(nt
& a_NAG_T_SUBSCRIBE
){
1089 struct a_nag_group_lookup ngl
;
1092 if(!(isaster
= (**argv
== '*')))
1093 ngp
= a_nag_group_find(nt
, *argv
);
1094 else if((ngp
= a_nag_group_go_first(nt
, &ngl
)) == NULL
)
1096 else if(ngp
!= NULL
&& !(ngp
->ng_type
& a_NAG_T_SUBSCRIBE
))
1101 if(ngp
->ng_type
& a_NAG_T_SUBSCRIBE
){
1102 a_NAG_MLMUX_LINKOUT(ngp
);
1103 ngp
->ng_type
&= ~a_NAG_T_SUBSCRIBE
;
1104 a_NAG_MLMUX_LINKIN(ngp
);
1108 while((ngp
= a_nag_group_go_next(&ngl
)) != NULL
&&
1109 !(ngp
->ng_type
& a_NAG_T_SUBSCRIBE
))
1115 n_err(_("Mailing-list not `mlsubscribe'd: %s\n"),
1116 n_shexp_quote_cp(*argv
, FAL0
));
1121 }else if(a_nag_group_del(nt
, *argv
))
1123 n_err(_("No such mailing-list: %s\n"), n_shexp_quote_cp(*argv
, FAL0
));
1132 a_nag_mlmux_linkin(struct a_nag_group
*ngp
){
1133 struct a_nag_grp_regex
**lpp
, *ngrp
, *lhp
;
1136 if(ngp
->ng_type
& a_NAG_T_SUBSCRIBE
){
1137 lpp
= &a_nag_mlsub_regex
;
1140 lpp
= &a_nag_mlist_regex
;
1144 a_NAG_GP_TO_SUBCLASS(ngrp
, ngp
);
1146 if((lhp
= *lpp
) != NULL
){
1147 (ngrp
->ngr_last
= lhp
->ngr_last
)->ngr_next
= ngrp
;
1148 (ngrp
->ngr_next
= lhp
)->ngr_last
= ngrp
;
1150 *lpp
= ngrp
->ngr_last
= ngrp
->ngr_next
= ngrp
;
1156 a_nag_mlmux_linkout(struct a_nag_group
*ngp
){
1157 struct a_nag_grp_regex
*ngrp
, **lpp
;
1160 a_NAG_GP_TO_SUBCLASS(ngrp
, ngp
);
1162 if(ngp
->ng_type
& a_NAG_T_SUBSCRIBE
){
1163 lpp
= &a_nag_mlsub_regex
;
1165 a_nag_mlsub_hits
-= ngrp
->ngr_hits
;
1167 lpp
= &a_nag_mlist_regex
;
1169 a_nag_mlist_hits
-= ngrp
->ngr_hits
;
1172 if(ngrp
->ngr_next
== ngrp
)
1175 (ngrp
->ngr_last
->ngr_next
= ngrp
->ngr_next
)->ngr_last
= ngrp
->ngr_last
;
1177 *lpp
= ngrp
->ngr_next
;
1181 #endif /* HAVE_REGEX */
1184 nalloc(char const *str
, enum gfield ntype
)
1186 struct n_addrguts ag
;
1190 assert(!(ntype
& GFULLEXTRA
) || (ntype
& GFULL
) != 0);
1192 str
= n_addrspec_with_guts(&ag
, str
,
1193 ((ntype
& (GFULL
| GSKIN
| GREF
)) != 0), FAL0
);
1196 np = NULL; TODO We cannot return NULL,
1197 goto jleave; TODO thus handle failures in here!
1202 if (!(ag
.ag_n_flags
& NAME_NAME_SALLOC
)) {
1203 ag
.ag_n_flags
|= NAME_NAME_SALLOC
;
1204 np
= n_autorec_alloc(sizeof(*np
) + ag
.ag_slen
+1);
1205 memcpy(np
+ 1, ag
.ag_skinned
, ag
.ag_slen
+1);
1206 ag
.ag_skinned
= (char*)(np
+ 1);
1208 np
= n_autorec_alloc(sizeof *np
);
1213 np
->n_fullname
= np
->n_name
= ag
.ag_skinned
;
1214 np
->n_fullextra
= NULL
;
1215 np
->n_flags
= ag
.ag_n_flags
;
1217 if (ntype
& GFULL
) {
1218 if (ag
.ag_ilen
== ag
.ag_slen
1220 && !(ag
.ag_n_flags
& NAME_IDNA
)
1224 if (ag
.ag_n_flags
& NAME_ADDRSPEC_ISFILEORPIPE
)
1227 /* n_fullextra is only the complete name part without address.
1228 * Beware of "-r '<abc@def>'", don't treat that as FULLEXTRA */
1229 if ((ntype
& GFULLEXTRA
) && ag
.ag_ilen
> ag
.ag_slen
+ 2) {
1230 size_t s
= ag
.ag_iaddr_start
, e
= ag
.ag_iaddr_aend
, i
;
1233 if (s
== 0 || str
[--s
] != '<' || str
[e
++] != '>')
1234 goto jskipfullextra
;
1236 in
.s
= n_lofi_alloc(s
+ 1 + i
+1);
1237 while(s
> 0 && blankchar(str
[s
- 1]))
1239 memcpy(in
.s
, str
, s
);
1242 while (blankchar(str
[e
])) {
1248 memcpy(&in
.s
[s
], &str
[e
], i
);
1251 in
.s
[in
.l
= s
] = '\0';
1252 mime_fromhdr(&in
, &out
, /* TODO TD_ISPR |*/ TD_ICONV
);
1254 for (cp
= out
.s
, i
= out
.l
; i
> 0 && spacechar(*cp
); --i
, ++cp
)
1256 while (i
> 0 && spacechar(cp
[i
- 1]))
1258 np
->n_fullextra
= savestrbuf(cp
, i
);
1265 /* n_fullname depends on IDNA conversion */
1267 if (!(ag
.ag_n_flags
& NAME_IDNA
)) {
1269 in
.s
= n_UNCONST(str
);
1273 /* The domain name was IDNA and has been converted. We also have to
1274 * ensure that the domain name in .n_fullname is replaced with the
1275 * converted version, since MIME doesn't perform encoding of addrs */
1276 /* TODO This definetely doesn't belong here! */
1277 size_t l
= ag
.ag_iaddr_start
,
1278 lsuff
= ag
.ag_ilen
- ag
.ag_iaddr_aend
;
1279 in
.s
= n_lofi_alloc(l
+ ag
.ag_slen
+ lsuff
+1);
1280 memcpy(in
.s
, str
, l
);
1281 memcpy(in
.s
+ l
, ag
.ag_skinned
, ag
.ag_slen
);
1283 memcpy(in
.s
+ l
, str
+ ag
.ag_iaddr_aend
, lsuff
);
1289 mime_fromhdr(&in
, &out
, /* TODO TD_ISPR |*/ TD_ICONV
);
1290 np
->n_fullname
= savestr(out
.s
);
1293 if (ag
.ag_n_flags
& NAME_IDNA
)
1303 nalloc_fcc(char const *file
){
1307 nnp
= n_autorec_alloc(sizeof *nnp
);
1308 nnp
->n_flink
= nnp
->n_blink
= NULL
;
1309 nnp
->n_type
= GBCC
| GBCC_IS_FCC
; /* xxx Bcc: <- namelist_vaporise_head */
1310 nnp
->n_flags
= NAME_NAME_SALLOC
| NAME_SKINNED
| NAME_ADDRSPEC_ISFILE
;
1311 nnp
->n_fullname
= nnp
->n_name
= savestr(file
);
1312 nnp
->n_fullextra
= NULL
;
1318 ndup(struct name
*np
, enum gfield ntype
)
1323 if ((ntype
& (GFULL
| GSKIN
)) && !(np
->n_flags
& NAME_SKINNED
)) {
1324 nnp
= nalloc(np
->n_name
, ntype
);
1328 nnp
= n_autorec_alloc(sizeof *np
);
1329 nnp
->n_flink
= nnp
->n_blink
= NULL
;
1330 nnp
->n_type
= ntype
;
1331 nnp
->n_flags
= np
->n_flags
| NAME_NAME_SALLOC
;
1332 nnp
->n_name
= savestr(np
->n_name
);
1333 if (np
->n_name
== np
->n_fullname
|| !(ntype
& (GFULL
| GSKIN
))) {
1334 nnp
->n_fullname
= nnp
->n_name
;
1335 nnp
->n_fullextra
= NULL
;
1337 nnp
->n_fullname
= savestr(np
->n_fullname
);
1338 nnp
->n_fullextra
= (np
->n_fullextra
== NULL
) ? NULL
1339 : savestr(np
->n_fullextra
);
1347 cat(struct name
*n1
, struct name
*n2
){
1355 if(n2
== NULL
|| (n2
->n_type
& GDEL
))
1358 while(tail
->n_flink
!= NULL
)
1359 tail
= tail
->n_flink
;
1369 n_namelist_dup(struct name
const *np
, enum gfield ntype
){
1370 struct name
*nlist
, *xnp
;
1373 for(nlist
= xnp
= NULL
; np
!= NULL
; np
= np
->n_flink
){
1376 if(!(np
->n_type
& GDEL
)){
1377 x
= ndup(n_UNCONST(np
), (np
->n_type
& ~GMASK
) | ntype
);
1378 if((x
->n_blink
= xnp
) == NULL
)
1390 count(struct name
const *np
)
1395 for (c
= 0; np
!= NULL
; np
= np
->n_flink
)
1396 if (!(np
->n_type
& GDEL
))
1403 count_nonlocal(struct name
const *np
)
1408 for (c
= 0; np
!= NULL
; np
= np
->n_flink
)
1409 if (!(np
->n_type
& GDEL
) && !(np
->n_flags
& NAME_ADDRSPEC_ISFILEORPIPE
))
1416 extract(char const *line
, enum gfield ntype
)
1421 rv
= a_nag_extract1(line
, ntype
, " \t,", 0);
1427 lextract(char const *line
, enum gfield ntype
)
1433 if(!(ntype
& GSHEXP_PARSE_HACK
) || !(expandaddr_to_eaf() & EAF_SHEXP_PARSE
))
1437 struct n_string s_b
, *sp
;
1438 enum n_shexp_state shs
;
1440 n_autorec_relax_create();
1441 sp
= n_string_creat_auto(&s_b
);
1442 sin
.s
= n_UNCONST(line
); /* logical */
1444 shs
= n_shexp_parse_token((n_SHEXP_PARSE_LOG
|
1445 n_SHEXP_PARSE_IGNORE_EMPTY
| n_SHEXP_PARSE_QUOTE_AUTO_FIXED
|
1446 n_SHEXP_PARSE_QUOTE_AUTO_DSQ
), sp
, &sin
, NULL
);
1447 if(!(shs
& n_SHEXP_STATE_ERR_MASK
) && (shs
& n_SHEXP_STATE_STOP
)){
1448 line
= cp
= n_lofi_alloc(sp
->s_len
+1);
1449 memcpy(cp
, n_string_cp(sp
), sp
->s_len
+1);
1452 n_autorec_relax_gut();
1455 rv
= ((line
!= NULL
&& strpbrk(line
, ",\"\\(<|"))
1456 ? a_nag_extract1(line
, ntype
, ",", 1) : extract(line
, ntype
));
1465 detract(struct name
*np
, enum gfield ntype
)
1476 flags
= ntype
& (GCOMMA
| GNAMEONLY
);
1477 ntype
&= ~(GCOMMA
| GNAMEONLY
);
1480 for (p
= np
; p
!= NULL
; p
= p
->n_flink
) {
1481 if (ntype
&& (p
->n_type
& GMASK
) != ntype
)
1483 s
+= strlen(flags
& GNAMEONLY
? p
->n_name
: p
->n_fullname
) +1;
1491 topp
= n_autorec_alloc(s
);
1493 for (p
= np
; p
!= NULL
; p
= p
->n_flink
) {
1494 if (ntype
&& (p
->n_type
& GMASK
) != ntype
)
1496 cp
= sstpcpy(cp
, (flags
& GNAMEONLY
? p
->n_name
: p
->n_fullname
));
1497 if ((flags
& GCOMMA
) && p
->n_flink
!= NULL
)
1502 if ((flags
& GCOMMA
) && *--cp
== ',')
1510 grab_names(enum n_go_input_flags gif
, char const *field
, struct name
*np
,
1511 int comma
, enum gfield gflags
)
1517 np
= lextract(n_go_input_cp(gif
, field
, detract(np
, comma
)), gflags
);
1518 for (nq
= np
; nq
!= NULL
; nq
= nq
->n_flink
)
1519 if (is_addr_invalid(nq
, EACM_NONE
))
1526 name_is_same_domain(struct name
const *n1
, struct name
const *n2
)
1528 char const *d1
, *d2
;
1532 d1
= strrchr(n1
->n_name
, '@');
1533 d2
= strrchr(n2
->n_name
, '@');
1535 rv
= (d1
!= NULL
&& d2
!= NULL
) ? !asccasecmp(++d1
, ++d2
) : FAL0
;
1542 checkaddrs(struct name
*np
, enum expand_addr_check_mode eacm
,
1543 si8_t
*set_on_error
)
1548 for (n
= np
; n
!= NULL
; n
= n
->n_flink
) {
1551 if ((rv
= is_addr_invalid(n
, eacm
)) != 0) {
1552 if (set_on_error
!= NULL
)
1553 *set_on_error
|= rv
; /* don't loose -1! */
1554 else if (eacm
& EAF_MAYKEEP
) /* TODO HACK! See definition! */
1557 n
->n_blink
->n_flink
= n
->n_flink
;
1559 n
->n_flink
->n_blink
= n
->n_blink
;
1569 n_namelist_vaporise_head(bool_t strip_alternates
, struct header
*hp
,
1570 enum expand_addr_check_mode eacm
, si8_t
*set_on_error
)
1572 /* TODO namelist_vaporise_head() is incredibly expensive and redundant */
1573 struct name
*tolist
, *np
, **npp
;
1576 tolist
= cat(hp
->h_to
, cat(hp
->h_cc
, cat(hp
->h_bcc
, hp
->h_fcc
)));
1577 hp
->h_to
= hp
->h_cc
= hp
->h_bcc
= hp
->h_fcc
= NULL
;
1579 tolist
= usermap(tolist
, strip_alternates
/*metoo*/);
1580 if(strip_alternates
)
1581 tolist
= n_alternates_remove(tolist
, TRU1
);
1582 tolist
= elide(checkaddrs(tolist
, eacm
, set_on_error
));
1584 for (np
= tolist
; np
!= NULL
; np
= np
->n_flink
) {
1585 switch (np
->n_type
& (GDEL
| GMASK
)) {
1586 case GTO
: npp
= &hp
->h_to
; break;
1587 case GCC
: npp
= &hp
->h_cc
; break;
1588 case GBCC
: npp
= &hp
->h_bcc
; break;
1591 *npp
= cat(*npp
, ndup(np
, np
->n_type
| GFULL
));
1598 usermap(struct name
*names
, bool_t force_metoo
){
1599 struct a_nag_group
*ngp
;
1600 struct name
*nlist
, *nlist_tail
, *np
, *cp
;
1604 metoo
= (force_metoo
|| ok_blook(metoo
));
1605 nlist
= nlist_tail
= NULL
;
1608 for(; np
!= NULL
; np
= cp
){
1609 assert(!(np
->n_type
& GDEL
)); /* TODO legacy */
1612 if(is_fileorpipe_addr(np
) ||
1613 (ngp
= a_nag_group_find(a_NAG_T_ALIAS
, np
->n_name
)) == NULL
){
1614 if((np
->n_blink
= nlist_tail
) != NULL
)
1615 nlist_tail
->n_flink
= np
;
1621 nlist
= a_nag_gexpand(0, nlist
, ngp
, metoo
, np
->n_type
);
1622 if((nlist_tail
= nlist
) != NULL
)
1623 while(nlist_tail
->n_flink
!= NULL
)
1624 nlist_tail
= nlist_tail
->n_flink
;
1632 elide(struct name
*names
)
1635 struct name
*nlist
, *np
, **nparr
;
1643 /* Throw away all deleted nodes */
1644 for(np
= NULL
, i
= 0; names
!= NULL
; names
= names
->n_flink
)
1645 if(!(names
->n_type
& GDEL
)){
1646 names
->n_blink
= np
;
1648 np
->n_flink
= names
;
1654 if(nlist
== NULL
|| i
== 1)
1658 /* Create a temporay array and sort that */
1659 nparr
= n_lofi_alloc(sizeof(*nparr
) * i
);
1661 for(i
= 0, np
= nlist
; np
!= NULL
; np
= np
->n_flink
)
1664 qsort(nparr
, i
, sizeof *nparr
, &a_nag_elide_qsort
);
1666 /* Remove duplicates XXX speedup, or list_uniq()! */
1667 for(j
= 0, --i
; j
< i
;){
1668 if(asccasecmp(nparr
[j
]->n_name
, nparr
[k
= j
+ 1]->n_name
))
1672 nparr
[k
] = nparr
[k
+ 1];
1677 /* Throw away all list members which are not part of the array.
1678 * Note this keeps the original, possibly carefully crafted, order of the
1679 * addressees, thus */
1680 for(np
= nlist
; np
!= NULL
; np
= np
->n_flink
){
1681 for(j
= 0; j
<= i
; ++j
)
1688 nlist
= np
->n_flink
;
1691 np
->n_blink
->n_flink
= np
->n_flink
;
1692 if(np
->n_flink
!= NULL
)
1693 np
->n_flink
->n_blink
= np
->n_blink
;
1704 c_alternates(void *vp
){
1705 struct a_nag_group
*ngp
;
1706 char const *varname
, *ccp
;
1710 n_pstate_err_no
= n_ERR_NONE
;
1713 varname
= (n_pstate
& n_PS_ARGMOD_VPUT
) ? *argv
++ : NULL
;
1716 if(!a_nag_group_print_all(a_NAG_T_ALTERNATES
, varname
))
1720 n_err(_("`alternates': `vput' only supported for show mode\n"));
1722 /* Delete the old set to "declare a list", if *posix* */
1724 a_nag_group_del(a_NAG_T_ALTERNATES
, n_star
);
1726 while((ccp
= *argv
++) != NULL
){
1730 if((np
= lextract(ccp
, GSKIN
)) == NULL
|| np
->n_flink
!= NULL
||
1731 (np
= checkaddrs(np
, EACM_STRICT
, NULL
)) == NULL
){
1732 n_err(_("Invalid `alternates' argument: %s\n"),
1733 n_shexp_quote_cp(ccp
, FAL0
));
1734 n_pstate_err_no
= n_ERR_INVAL
;
1741 if((ngp
= a_nag_group_fetch(a_NAG_T_ALTERNATES
, ccp
, l
)) == NULL
){
1742 n_err(_("Failed to create storage for alternates: %s\n"),
1743 n_shexp_quote_cp(ccp
, FAL0
));
1744 n_pstate_err_no
= n_ERR_NOMEM
;
1750 return (vp
!= NULL
? 0 : 1);
1754 c_unalternates(void *vp
){
1762 do if(!a_nag_group_del(a_NAG_T_ALTERNATES
, *argv
)){
1763 n_err(_("No such `alternates': %s\n"), n_shexp_quote_cp(*argv
, FAL0
));
1765 }while(*++argv
!= NULL
);
1771 n_alternates_remove(struct name
*np
, bool_t keep_single
){
1772 /* XXX keep a single pointer, initial null, and immediate remove nodes
1773 * XXX on successful match unless keep single and that pointer null! */
1774 struct a_nag_group_lookup ngl
;
1775 struct a_nag_group
*ngp
;
1776 struct name
*xp
, *newnp
;
1779 /* Delete the temporary bit from all */
1780 for(xp
= np
; xp
!= NULL
; xp
= xp
->n_flink
)
1781 xp
->n_flags
&= ~(ui32_t
)SI32_MIN
;
1783 /* Mark all possible alternate names (xxx sic: instead walk over namelist
1784 * and hash-lookup alternate instead (unless *allnet*) */
1785 for(ngp
= a_nag_group_go_first(a_NAG_T_ALTERNATES
, &ngl
); ngp
!= NULL
;
1786 ngp
= a_nag_group_go_next(&ngl
))
1787 np
= a_nag_namelist_mark_name(np
, ngp
->ng_id
);
1789 np
= a_nag_namelist_mark_name(np
, ok_vlook(LOGNAME
));
1791 if((xp
= extract(ok_vlook(sender
), GEXTRA
| GSKIN
)) != NULL
){
1792 /* TODO check_from_and_sender(): drop; *sender*: only one name!
1793 * TODO At assignment time, as VIP var? */
1795 np
= a_nag_namelist_mark_name(np
, xp
->n_name
);
1796 while((xp
= xp
->n_flink
) != NULL
);
1797 }else for(xp
= lextract(ok_vlook(from
), GEXTRA
| GSKIN
); xp
!= NULL
;
1799 np
= a_nag_namelist_mark_name(np
, xp
->n_name
);
1802 char const *v15compat
;
1804 if((v15compat
= ok_vlook(replyto
)) != NULL
){
1805 n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
1806 for(xp
= lextract(v15compat
, GEXTRA
| GSKIN
); xp
!= NULL
;
1808 np
= a_nag_namelist_mark_name(np
, xp
->n_name
);
1812 for(xp
= lextract(ok_vlook(reply_to
), GEXTRA
| GSKIN
); xp
!= NULL
;
1814 np
= a_nag_namelist_mark_name(np
, xp
->n_name
);
1816 /* Clean the list by throwing away all deleted or marked (but one) nodes */
1817 for(xp
= newnp
= NULL
; np
!= NULL
; np
= np
->n_flink
){
1818 if(np
->n_type
& GDEL
)
1820 if(np
->n_flags
& (ui32_t
)SI32_MIN
){
1832 xp
->n_flags
&= ~(ui32_t
)SI32_MIN
;
1843 n_is_myname(char const *name
){
1844 struct a_nag_group_lookup ngl
;
1845 struct a_nag_group
*ngp
;
1849 if(a_nag_is_same_name(ok_vlook(LOGNAME
), name
))
1852 if(!ok_blook(allnet
)){
1853 if(a_nag_group_lookup(a_NAG_T_ALTERNATES
, &ngl
, name
) != NULL
)
1856 for(ngp
= a_nag_group_go_first(a_NAG_T_ALTERNATES
, &ngl
); ngp
!= NULL
;
1857 ngp
= a_nag_group_go_next(&ngl
))
1858 if(a_nag_is_same_name(ngp
->ng_id
, name
))
1862 for(xp
= lextract(ok_vlook(from
), GEXTRA
| GSKIN
); xp
!= NULL
;
1864 if(a_nag_is_same_name(xp
->n_name
, name
))
1868 char const *v15compat
;
1870 if((v15compat
= ok_vlook(replyto
)) != NULL
){
1871 n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
1872 for(xp
= lextract(v15compat
, GEXTRA
| GSKIN
); xp
!= NULL
;
1874 if(a_nag_is_same_name(xp
->n_name
, name
))
1879 for(xp
= lextract(ok_vlook(reply_to
), GEXTRA
| GSKIN
); xp
!= NULL
;
1881 if(a_nag_is_same_name(xp
->n_name
, name
))
1884 for(xp
= extract(ok_vlook(sender
), GEXTRA
| GSKIN
); xp
!= NULL
;
1886 if(a_nag_is_same_name(xp
->n_name
, name
))
1892 return (name
!= NULL
);
1896 c_addrcodec(void *vp
){
1897 struct n_addrguts ag
;
1899 struct n_string s_b
, *sp
;
1902 char const **argv
, *varname
, *act
, *cp
;
1905 sp
= n_string_creat_auto(&s_b
);
1907 varname
= (n_pstate
& n_PS_ARGMOD_VPUT
) ? *argv
++ : NULL
;
1910 for(cp
= act
; *cp
!= '\0' && !blankspacechar(*cp
); ++cp
)
1921 alen
= PTR2SIZE(cp
- act
);
1925 trims
.l
= strlen(trims
.s
= n_UNCONST(cp
));
1926 cp
= savestrbuf(n_str_trim(&trims
, n_STR_TRIM_BOTH
)->s
, trims
.l
);
1927 if(trims
.l
<= UIZ_MAX
/ 4)
1929 sp
= n_string_reserve(sp
, trims
.l
);
1931 n_pstate_err_no
= n_ERR_NONE
;
1933 if(is_ascncaseprefix(act
, "encode", alen
)){
1934 /* This function cannot be a simple nalloc() wrapper even later on, since
1935 * we may need to turn any ", () or \ into quoted-pairs */
1938 while((c
= *cp
++) != '\0'){
1939 if(((c
== '(' || c
== ')') && mode
< 1) || (c
== '"' && mode
< 2) ||
1940 (c
== '\\' && mode
< 3))
1941 sp
= n_string_push_c(sp
, '\\');
1942 sp
= n_string_push_c(sp
, c
);
1945 if(n_addrspec_with_guts(&ag
, n_string_cp(sp
), TRU1
, TRU1
) == NULL
||
1946 (ag
.ag_n_flags
& (NAME_ADDRSPEC_ISADDR
| NAME_ADDRSPEC_INVALID
)
1947 ) != NAME_ADDRSPEC_ISADDR
){
1949 n_pstate_err_no
= n_ERR_INVAL
;
1954 np
= nalloc(ag
.ag_input
, GTO
| GFULL
| GSKIN
);
1955 cp
= np
->n_fullname
;
1957 }else if(mode
== 0){
1958 if(is_ascncaseprefix(act
, "decode", alen
)){
1961 while((c
= *cp
++) != '\0'){
1964 sp
= n_string_push_c(sp
, '(');
1965 act
= skip_comment(cp
);
1967 sp
= n_string_push_buf(sp
, cp
, PTR2SIZE(act
- cp
));
1968 sp
= n_string_push_c(sp
, ')');
1973 if((c
= *cp
++) == '"')
1975 if(c
== '\\' && (c
= *cp
) != '\0')
1977 sp
= n_string_push_c(sp
, c
);
1981 if(c
== '\\' && (c
= *cp
++) == '\0')
1983 sp
= n_string_push_c(sp
, c
);
1987 cp
= n_string_cp(sp
);
1988 }else if(is_ascncaseprefix(act
, "skin", alen
) ||
1989 (mode
= 1, is_ascncaseprefix(act
, "skinlist", alen
))){
1990 /* Let's just use the is-single-address hack for this one, too.. */
1991 if(n_addrspec_with_guts(&ag
, cp
, TRU1
, TRU1
) == NULL
||
1992 (ag
.ag_n_flags
& (NAME_ADDRSPEC_ISADDR
| NAME_ADDRSPEC_INVALID
)
1993 ) != NAME_ADDRSPEC_ISADDR
){
1994 n_pstate_err_no
= n_ERR_INVAL
;
1999 np
= nalloc(ag
.ag_input
, GTO
| GFULL
| GSKIN
);
2002 if(mode
== 1 && is_mlist(cp
, FAL0
) != MLIST_OTHER
)
2003 n_pstate_err_no
= n_ERR_EXIST
;
2010 if(varname
== NULL
){
2011 if(fprintf(n_stdout
, "%s\n", cp
) < 0){
2012 n_pstate_err_no
= n_err_no
;
2015 }else if(!n_var_vset(varname
, (uintptr_t)cp
)){
2016 n_pstate_err_no
= n_ERR_NOTSUP
;
2022 return (vp
!= NULL
? 0 : 1);
2024 n_err(_("Synopsis: addrcodec: <[+[+[+]]]e[ncode]|d[ecode]|s[kin]> "
2025 "<rest-of-line>\n"));
2026 n_pstate_err_no
= n_ERR_INVAL
;
2032 c_commandalias(void *vp
){
2033 struct a_nag_group
*ngp
;
2034 char const **argv
, *ccp
;
2041 if((ccp
= *argv
) == NULL
){
2042 a_nag_group_print_all(a_NAG_T_COMMANDALIAS
, NULL
);
2046 /* Verify the name is a valid one, and not a command modifier.
2047 * NOTE: this list duplicates settings isolated somewhere else (go.c) */
2048 if(*ccp
== '\0' || *n_cmd_isolate(ccp
) != '\0' ||
2049 !asccasecmp(ccp
, "ignerr") || !asccasecmp(ccp
, "local") ||
2050 !asccasecmp(ccp
, "wysh") || !asccasecmp(ccp
, "vput") ||
2051 !asccasecmp(ccp
, "scope") || !asccasecmp(ccp
, "u")){
2052 n_err(_("`commandalias': not a valid command name: %s\n"),
2053 n_shexp_quote_cp(ccp
, FAL0
));
2058 if(argv
[1] == NULL
){
2059 if((ngp
= a_nag_group_find(a_NAG_T_COMMANDALIAS
, ccp
)) != NULL
)
2060 a_nag_group_print(ngp
, n_stdout
, NULL
);
2062 n_err(_("No such commandalias: %s\n"), n_shexp_quote_cp(ccp
, FAL0
));
2066 /* Because one hardly ever redefines, anything is stored in one chunk */
2070 /* Delete the old one, if any; don't get fooled to remove them all */
2071 if(ccp
[0] != '*' || ccp
[1] != '\0')
2072 a_nag_group_del(a_NAG_T_COMMANDALIAS
, ccp
);
2074 for(i
= len
= 0, ++argv
; argv
[i
] != NULL
; ++i
)
2075 len
+= strlen(argv
[i
]) + 1;
2079 if((ngp
= a_nag_group_fetch(a_NAG_T_COMMANDALIAS
, ccp
, len
)) == NULL
){
2080 n_err(_("Failed to create storage for commandalias: %s\n"),
2081 n_shexp_quote_cp(ccp
, FAL0
));
2084 struct a_nag_cmd_alias
*ncap
;
2086 a_NAG_GP_TO_SUBCLASS(ncap
, ngp
);
2087 a_NAG_GP_TO_SUBCLASS(cp
, ngp
);
2089 ncap
->nca_expand
.s
= cp
;
2090 ncap
->nca_expand
.l
= len
- 1;
2092 for(len
= 0; (ccp
= *argv
++) != NULL
;)
2093 if((i
= strlen(ccp
)) > 0){
2108 c_uncommandalias(void *vp
){
2116 do if(!a_nag_group_del(a_NAG_T_COMMANDALIAS
, *argv
)){
2117 n_err(_("No such `commandalias': %s\n"), n_shexp_quote_cp(*argv
, FAL0
));
2119 }while(*++argv
!= NULL
);
2125 n_commandalias_exists(char const *name
, struct str
const **expansion_or_null
){
2126 struct a_nag_group
*ngp
;
2129 if((ngp
= a_nag_group_find(a_NAG_T_COMMANDALIAS
, name
)) != NULL
){
2132 if(expansion_or_null
!= NULL
){
2133 struct a_nag_cmd_alias
*ncap
;
2135 a_NAG_GP_TO_SUBCLASS(ncap
, ngp
);
2136 *expansion_or_null
= &ncap
->nca_expand
;
2145 n_alias_is_valid_name(char const *name
){
2151 for(rv
= TRU1
, cp
= name
++; (c
= *cp
++) != '\0';)
2152 /* User names, plus things explicitly mentioned in Postfix aliases(5),
2153 * i.e., [[:alnum:]_#:@.-]+$?.
2154 * As extensions allow high-bit bytes, semicolon and period. */
2155 /* TODO n_alias_is_valid_name(): locale dependent validity check,
2156 * TODO with Unicode prefix valid UTF-8! */
2157 if(!alnumchar(c
) && c
!= '_' && c
!= '-' &&
2158 c
!= '#' && c
!= ':' && c
!= '@' &&
2159 !((ui8_t
)c
& 0x80) && c
!= '!' && c
!= '.'){
2160 if(c
== '$' && cp
!= name
&& *cp
== '\0')
2174 struct a_nag_group
*ngp
;
2180 n_UNINIT(ecp
, NULL
);
2183 a_nag_group_print_all(a_NAG_T_ALIAS
, NULL
);
2184 else if(!n_alias_is_valid_name(*argv
)){
2185 ecp
= N_("Not a valid alias name: %s\n");
2187 }else if(argv
[1] == NULL
){
2188 if((ngp
= a_nag_group_find(a_NAG_T_ALIAS
, *argv
)) != NULL
)
2189 a_nag_group_print(ngp
, n_stdout
, NULL
);
2191 ecp
= N_("No such alias: %s\n");
2194 }else if((ngp
= a_nag_group_fetch(a_NAG_T_ALIAS
, *argv
, 0)) == NULL
){
2195 ecp
= N_("Failed to create alias storage for: %s\n");
2197 n_err(V_(ecp
), n_shexp_quote_cp(*argv
, FAL0
));
2200 struct a_nag_grp_names
*ngnp_tail
, *ngnp
;
2201 struct a_nag_grp_names_head
*ngnhp
;
2203 a_NAG_GP_TO_SUBCLASS(ngnhp
, ngp
);
2205 if((ngnp_tail
= ngnhp
->ngnh_head
) != NULL
)
2206 while((ngnp
= ngnp_tail
->ngn_next
) != NULL
)
2209 for(++argv
; *argv
!= NULL
; ++argv
){
2212 i
= strlen(*argv
) +1;
2213 ngnp
= n_alloc(n_VSTRUCT_SIZEOF(struct a_nag_grp_names
, ngn_id
) + i
);
2214 if(ngnp_tail
!= NULL
)
2215 ngnp_tail
->ngn_next
= ngnp
;
2217 ngnhp
->ngnh_head
= ngnp
;
2219 ngnp
->ngn_next
= NULL
;
2220 memcpy(ngnp
->ngn_id
, *argv
, i
);
2236 do if(!a_nag_group_del(a_NAG_T_ALIAS
, *argv
)){
2237 n_err(_("No such alias: %s\n"), *argv
);
2239 }while(*++argv
!= NULL
);
2249 rv
= a_nag_mlmux(a_NAG_T_MLIST
, v
);
2259 rv
= a_nag_unmlmux(a_NAG_T_MLIST
, v
);
2265 c_mlsubscribe(void *v
){
2269 rv
= a_nag_mlmux(a_NAG_T_MLIST
| a_NAG_T_SUBSCRIBE
, v
);
2275 c_unmlsubscribe(void *v
){
2279 rv
= a_nag_unmlmux(a_NAG_T_MLIST
| a_NAG_T_SUBSCRIBE
, v
);
2285 is_mlist(char const *name
, bool_t subscribed_only
){
2286 struct a_nag_group
*ngp
;
2288 struct a_nag_grp_regex
**lpp
, *ngrp
;
2291 enum mlist_state rv
;
2294 ngp
= a_nag_group_find(a_NAG_T_MLIST
, name
);
2295 rv
= (ngp
!= NULL
) ? MLIST_KNOWN
: MLIST_OTHER
;
2297 if(rv
== MLIST_KNOWN
){
2298 if(ngp
->ng_type
& a_NAG_T_SUBSCRIBE
)
2299 rv
= MLIST_SUBSCRIBED
;
2300 else if(subscribed_only
)
2302 /* Of course, if that is a regular expression it doesn't mean a thing */
2304 if(ngp
->ng_type
& a_NAG_T_REGEX
)
2311 /* Not in the hashmap (as something matchable), walk the lists */
2314 lpp
= &a_nag_mlsub_regex
;
2317 if((ngrp
= *lpp
) != NULL
){
2318 do if(regexec(&ngrp
->ngr_regex
, name
, 0,NULL
, 0) != REG_NOMATCH
){
2319 /* Relink as the head of this list if the hit count of this group is
2320 * >= 25% of the average hit count */
2324 i
= ++a_nag_mlsub_hits
/ a_nag_mlsub_size
;
2326 i
= ++a_nag_mlist_hits
/ a_nag_mlist_size
;
2329 if(++ngrp
->ngr_hits
>= i
&& *lpp
!= ngrp
&& ngrp
->ngr_next
!= ngrp
){
2330 ngrp
->ngr_last
->ngr_next
= ngrp
->ngr_next
;
2331 ngrp
->ngr_next
->ngr_last
= ngrp
->ngr_last
;
2332 (ngrp
->ngr_last
= (*lpp
)->ngr_last
)->ngr_next
= ngrp
;
2333 (ngrp
->ngr_next
= *lpp
)->ngr_last
= ngrp
;
2336 rv
= !re2
? MLIST_SUBSCRIBED
: MLIST_KNOWN
;
2338 }while((ngrp
= ngrp
->ngr_next
) != *lpp
);
2341 if(!re2
&& !subscribed_only
){
2343 lpp
= &a_nag_mlist_regex
;
2346 assert(rv
== MLIST_OTHER
);
2347 #endif /* HAVE_REGEX */
2355 is_mlist_mp(struct message
*mp
, enum mlist_state what
){
2358 enum mlist_state rv
;
2364 np
= lextract(hfield1("to", mp
), GTO
| GSKIN
);
2366 for(; np
!= NULL
; np
= np
->n_flink
){
2367 switch(is_mlist(np
->n_name
, FAL0
)){
2371 if(what
== MLIST_KNOWN
|| what
== MLIST_OTHER
){
2372 if(rv
== MLIST_OTHER
)
2374 if(what
== MLIST_KNOWN
)
2378 case MLIST_SUBSCRIBED
:
2379 if(what
== MLIST_SUBSCRIBED
|| what
== MLIST_OTHER
){
2380 if(rv
!= MLIST_SUBSCRIBED
)
2381 rv
= MLIST_SUBSCRIBED
;
2390 np
= lextract(hfield1("cc", mp
), GCC
| GSKIN
);
2399 c_shortcut(void *vp
){
2400 struct a_nag_group
*ngp
;
2409 a_nag_group_print_all(a_NAG_T_SHORTCUT
, NULL
);
2410 else if(argv
[1] == NULL
){
2411 if((ngp
= a_nag_group_find(a_NAG_T_SHORTCUT
, *argv
)) != NULL
)
2412 a_nag_group_print(ngp
, n_stdout
, NULL
);
2414 n_err(_("No such shortcut: %s\n"), n_shexp_quote_cp(*argv
, FAL0
));
2417 }else for(; *argv
!= NULL
; argv
+= 2){
2418 /* Because one hardly ever redefines, anything is stored in one chunk */
2422 if(argv
[1] == NULL
){
2423 n_err(_("Synopsis: shortcut: <shortcut> <expansion>\n"));
2427 if(a_nag_group_find(a_NAG_T_SHORTCUT
, *argv
) != NULL
)
2428 a_nag_group_del(a_NAG_T_SHORTCUT
, *argv
);
2430 l
= strlen(argv
[1]) +1;
2431 if((ngp
= a_nag_group_fetch(a_NAG_T_SHORTCUT
, *argv
, l
)) == NULL
){
2432 n_err(_("Failed to create storage for shortcut: %s\n"),
2433 n_shexp_quote_cp(*argv
, FAL0
));
2436 a_NAG_GP_TO_SUBCLASS(cp
, ngp
);
2437 memcpy(cp
, argv
[1], l
);
2445 c_unshortcut(void *vp
){
2453 do if(!a_nag_group_del(a_NAG_T_SHORTCUT
, *argv
)){
2454 n_err(_("No such shortcut: %s\n"), *argv
);
2456 }while(*++argv
!= NULL
);
2462 shortcut_expand(char const *str
){
2463 struct a_nag_group
*ngp
;
2466 if((ngp
= a_nag_group_find(a_NAG_T_SHORTCUT
, str
)) != NULL
)
2467 a_NAG_GP_TO_SUBCLASS(str
, ngp
);
2475 c_charsetalias(void *vp
){
2476 struct a_nag_group
*ngp
;
2485 a_nag_group_print_all(a_NAG_T_CHARSETALIAS
, NULL
);
2486 else if(argv
[1] == NULL
){
2487 if((ngp
= a_nag_group_find(a_NAG_T_CHARSETALIAS
, *argv
)) != NULL
)
2488 a_nag_group_print(ngp
, n_stdout
, NULL
);
2490 n_err(_("No such charsetalias: %s\n"), n_shexp_quote_cp(*argv
, FAL0
));
2493 }else for(; *argv
!= NULL
; argv
+= 2){
2494 /* Because one hardly ever redefines, anything is stored in one chunk */
2497 char const *dst
, *src
;
2499 if((dst
= argv
[1]) == NULL
){
2500 n_err(_("Synopsis: charsetalias: <charset> <charset-alias>\n"));
2503 }else if((dst
= n_iconv_normalize_name(dst
)) == NULL
){
2504 n_err(_("charsetalias: invalid target charset %s\n"),
2505 n_shexp_quote_cp(argv
[1], FAL0
));
2508 }else if((src
= n_iconv_normalize_name(argv
[0])) == NULL
){
2509 n_err(_("charsetalias: invalid source charset %s\n"),
2510 n_shexp_quote_cp(argv
[0], FAL0
));
2515 /* Delete the old one, if any; don't get fooled to remove them all */
2516 if(src
[0] != '*' || src
[1] != '\0')
2517 a_nag_group_del(a_NAG_T_CHARSETALIAS
, src
);
2519 dstl
= strlen(dst
) +1;
2520 if((ngp
= a_nag_group_fetch(a_NAG_T_CHARSETALIAS
, src
, dstl
)) == NULL
){
2521 n_err(_("Failed to create storage for charsetalias: %s\n"),
2522 n_shexp_quote_cp(src
, FAL0
));
2525 a_NAG_GP_TO_SUBCLASS(cp
, ngp
);
2526 memcpy(cp
, dst
, dstl
);
2534 c_uncharsetalias(void *vp
){
2543 if((cp
= n_iconv_normalize_name(*argv
)) == NULL
||
2544 !a_nag_group_del(a_NAG_T_CHARSETALIAS
, cp
)){
2545 n_err(_("No such `charsetalias': %s\n"),
2546 n_shexp_quote_cp(*argv
, FAL0
));
2549 }while(*++argv
!= NULL
);
2555 n_charsetalias_expand(char const *cp
){
2556 struct a_nag_group
*ngp
;
2558 char const *cp_orig
;
2563 for(i
= 0; (ngp
= a_nag_group_find(a_NAG_T_CHARSETALIAS
, cp
)) != NULL
;){
2564 a_NAG_GP_TO_SUBCLASS(cp
, ngp
);
2565 if(++i
== 8) /* XXX Magic (same as for `ghost' expansion) */
2576 c_filetype(void *vp
){ /* TODO support automatic chains: .tar.gz -> .gz + .tar */
2577 struct a_nag_group
*ngp
;
2578 char **argv
; /* TODO While there: let ! prefix mean: direct execlp(2) */
2586 a_nag_group_print_all(a_NAG_T_FILETYPE
, NULL
);
2587 else if(argv
[1] == NULL
){
2588 if((ngp
= a_nag_group_find(a_NAG_T_FILETYPE
, *argv
)) != NULL
)
2589 a_nag_group_print(ngp
, n_stdout
, NULL
);
2591 n_err(_("No such filetype: %s\n"), n_shexp_quote_cp(*argv
, FAL0
));
2594 }else for(; *argv
!= NULL
; argv
+= 3){
2595 /* Because one hardly ever redefines, anything is stored in one chunk */
2600 if(argv
[1] == NULL
|| argv
[2] == NULL
){
2601 n_err(_("Synopsis: filetype: <extension> <load-cmd> <save-cmd>\n"));
2606 /* Delete the old one, if any; don't get fooled to remove them all */
2608 if(ccp
[0] != '*' || ccp
[1] != '\0')
2609 a_nag_group_del(a_NAG_T_FILETYPE
, ccp
);
2611 /* Lowercase it all (for display purposes) */
2614 while((c
= *cp
) != '\0')
2615 *cp
++ = lowerconv(c
);
2617 llc
= strlen(argv
[1]) +1;
2618 lsc
= strlen(argv
[2]) +1;
2619 if(UIZ_MAX
- llc
<= lsc
)
2622 if((ngp
= a_nag_group_fetch(a_NAG_T_FILETYPE
, ccp
, llc
+ lsc
)) == NULL
){
2624 n_err(_("Failed to create storage for filetype: %s\n"),
2625 n_shexp_quote_cp(argv
[0], FAL0
));
2628 struct a_nag_file_type
*nftp
;
2630 a_NAG_GP_TO_SUBCLASS(nftp
, ngp
);
2631 a_NAG_GP_TO_SUBCLASS(cp
, ngp
);
2633 memcpy(nftp
->nft_load
.s
= cp
, argv
[1], llc
);
2635 nftp
->nft_load
.l
= --llc
;
2636 memcpy(nftp
->nft_save
.s
= cp
, argv
[2], lsc
);
2638 nftp
->nft_save
.l
= --lsc
;
2646 c_unfiletype(void *vp
){
2654 do if(!a_nag_group_del(a_NAG_T_FILETYPE
, *argv
)){
2655 n_err(_("No such `filetype': %s\n"), n_shexp_quote_cp(*argv
, FAL0
));
2657 }while(*++argv
!= NULL
);
2663 n_filetype_trial(struct n_file_type
*res_or_null
, char const *file
){
2665 struct a_nag_group_lookup ngl
;
2666 struct n_string s
, *sp
;
2667 struct a_nag_group
const *ngp
;
2671 sp
= n_string_creat_auto(&s
);
2672 sp
= n_string_assign_cp(sp
, file
);
2673 sp
= n_string_push_c(sp
, '.');
2676 for(ngp
= a_nag_group_go_first(a_NAG_T_FILETYPE
, &ngl
); ngp
!= NULL
;
2677 ngp
= a_nag_group_go_next(&ngl
)){
2678 sp
= n_string_trunc(sp
, l
);
2679 sp
= n_string_push_buf(sp
, ngp
->ng_id
,
2680 ngp
->ng_subclass_off
- ngp
->ng_id_len_sub
);
2682 if(!stat(n_string_cp(sp
), &stb
) && S_ISREG(stb
.st_mode
)){
2683 if(res_or_null
!= NULL
){
2684 struct a_nag_file_type
*nftp
;
2686 a_NAG_GP_TO_SUBCLASS(nftp
, ngp
);
2687 res_or_null
->ft_ext_dat
= ngp
->ng_id
;
2688 res_or_null
->ft_ext_len
= ngp
->ng_subclass_off
- ngp
->ng_id_len_sub
;
2689 res_or_null
->ft_load_dat
= nftp
->nft_load
.s
;
2690 res_or_null
->ft_load_len
= nftp
->nft_load
.l
;
2691 res_or_null
->ft_save_dat
= nftp
->nft_save
.s
;
2692 res_or_null
->ft_save_len
= nftp
->nft_save
.l
;
2694 goto jleave
; /* TODO after v15 legacy drop: break; */
2698 /* TODO v15 legacy code: automatic file hooks for .{bz2,gz,xz},
2699 * TODO but NOT supporting *file-hook-{load,save}-EXTENSION* */
2700 ngp
= (struct a_nag_group
*)0x1;
2702 sp
= n_string_trunc(sp
, l
);
2703 sp
= n_string_push_buf(sp
, a_nag_OBSOLETE_xz
.ft_ext_dat
,
2704 a_nag_OBSOLETE_xz
.ft_ext_len
);
2705 if(!stat(n_string_cp(sp
), &stb
) && S_ISREG(stb
.st_mode
)){
2706 n_OBSOLETE(".xz support will vanish, please use the `filetype' command");
2707 if(res_or_null
!= NULL
)
2708 *res_or_null
= a_nag_OBSOLETE_xz
;
2712 sp
= n_string_trunc(sp
, l
);
2713 sp
= n_string_push_buf(sp
, a_nag_OBSOLETE_gz
.ft_ext_dat
,
2714 a_nag_OBSOLETE_gz
.ft_ext_len
);
2715 if(!stat(n_string_cp(sp
), &stb
) && S_ISREG(stb
.st_mode
)){
2716 n_OBSOLETE(".gz support will vanish, please use the `filetype' command");
2717 if(res_or_null
!= NULL
)
2718 *res_or_null
= a_nag_OBSOLETE_gz
;
2722 sp
= n_string_trunc(sp
, l
);
2723 sp
= n_string_push_buf(sp
, a_nag_OBSOLETE_bz2
.ft_ext_dat
,
2724 a_nag_OBSOLETE_bz2
.ft_ext_len
);
2725 if(!stat(n_string_cp(sp
), &stb
) && S_ISREG(stb
.st_mode
)){
2726 n_OBSOLETE(".bz2 support will vanish, please use the `filetype' command");
2727 if(res_or_null
!= NULL
)
2728 *res_or_null
= a_nag_OBSOLETE_bz2
;
2736 return (ngp
!= NULL
);
2740 n_filetype_exists(struct n_file_type
*res_or_null
, char const *file
){
2741 char const *ext
, *lext
;
2744 if((ext
= strrchr(file
, '/')) != NULL
)
2747 for(lext
= NULL
; (ext
= strchr(file
, '.')) != NULL
; lext
= file
= ext
){
2748 struct a_nag_group
const *ngp
;
2750 if((ngp
= a_nag_group_find(a_NAG_T_FILETYPE
, ++ext
)) != NULL
){
2752 if(res_or_null
!= NULL
){
2753 struct a_nag_file_type
*nftp
;
2755 a_NAG_GP_TO_SUBCLASS(nftp
, ngp
);
2756 res_or_null
->ft_ext_dat
= ngp
->ng_id
;
2757 res_or_null
->ft_ext_len
= ngp
->ng_subclass_off
- ngp
->ng_id_len_sub
;
2758 res_or_null
->ft_load_dat
= nftp
->nft_load
.s
;
2759 res_or_null
->ft_load_len
= nftp
->nft_load
.l
;
2760 res_or_null
->ft_save_dat
= nftp
->nft_save
.s
;
2761 res_or_null
->ft_save_len
= nftp
->nft_save
.l
;
2763 goto jleave
; /* TODO after v15 legacy drop: break; */
2767 /* TODO v15 legacy code: automatic file hooks for .{bz2,gz,xz},
2768 * TODO as well as supporting *file-hook-{load,save}-EXTENSION* */
2772 if(!asccasecmp(lext
, "xz")){
2773 n_OBSOLETE(".xz support will vanish, please use the `filetype' command");
2774 if(res_or_null
!= NULL
)
2775 *res_or_null
= a_nag_OBSOLETE_xz
;
2777 }else if(!asccasecmp(lext
, "gz")){
2778 n_OBSOLETE(".gz support will vanish, please use the `filetype' command");
2779 if(res_or_null
!= NULL
)
2780 *res_or_null
= a_nag_OBSOLETE_gz
;
2782 }else if(!asccasecmp(lext
, "bz2")){
2783 n_OBSOLETE(".bz2 support will vanish, please use the `filetype' command");
2784 if(res_or_null
!= NULL
)
2785 *res_or_null
= a_nag_OBSOLETE_bz2
;
2788 char const *cload
, *csave
;
2793 #define a_X1 "file-hook-load-"
2795 #define a_X2 "file-hook-save-"
2797 vbuf
= n_lofi_alloc(l
+ n_MAX(sizeof(a_X1
), sizeof(a_X2
)));
2799 memcpy(vbuf
, a_X1
, sizeof(a_X1
) -1);
2800 memcpy(&vbuf
[sizeof(a_X1
) -1], lext
, l
);
2801 vbuf
[sizeof(a_X1
) -1 + l
] = '\0';
2802 cload
= n_var_vlook(vbuf
, FAL0
);
2804 memcpy(vbuf
, a_X2
, sizeof(a_X2
) -1);
2805 memcpy(&vbuf
[sizeof(a_X2
) -1], lext
, l
);
2806 vbuf
[sizeof(a_X2
) -1 + l
] = '\0';
2807 csave
= n_var_vlook(vbuf
, FAL0
);
2813 if((csave
!= NULL
) | (cload
!= NULL
)){
2814 n_OBSOLETE("*file-hook-{load,save}-EXTENSION* will vanish, "
2815 "please use the `filetype' command");
2817 if(((csave
!= NULL
) ^ (cload
!= NULL
)) == 0){
2818 if(res_or_null
!= NULL
){
2819 res_or_null
->ft_ext_dat
= lext
;
2820 res_or_null
->ft_ext_len
= l
;
2821 res_or_null
->ft_load_dat
= cload
;
2822 res_or_null
->ft_load_len
= strlen(cload
);
2823 res_or_null
->ft_save_dat
= csave
;
2824 res_or_null
->ft_save_len
= strlen(csave
);
2828 n_alert(_("Incomplete *file-hook-{load,save}-EXTENSION* for: .%s"),
2837 return (lext
!= NULL
);