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 - 2017 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
44 /* Main types (bits not values for easier testing only) */
45 GT_COMMANDALIAS
= 1u<<0,
49 GT_CHARSETALIAS
= 1u<<4,
51 GT_MASK
= GT_COMMANDALIAS
| GT_ALIAS
| GT_MLIST
| GT_SHORTCUT
|
52 GT_CHARSETALIAS
| GT_FILETYPE
,
54 /* Subtype bits and flags */
58 /* Extended type mask to be able to reflect what we really have; i.e., mlist
59 * can have GT_REGEX if they are subscribed or not, but `mlsubscribe' should
60 * print out only GT_MLIST which have the GT_SUBSCRIBE attribute set */
61 GT_PRINT_MASK
= GT_MASK
| GT_SUBSCRIBE
66 ui32_t g_subclass_off
; /* of "subclass" in .g_id (if any) */
67 ui16_t g_id_len_sub
; /* length of .g_id: _subclass_off - this */
68 ui8_t g_type
; /* enum group_type */
69 /* Identifying name, of variable size. Dependent on actual "subtype" more
70 * data follows thereafter, but note this is always used (i.e., for regular
71 * expression entries this is still set to the plain string) */
72 char g_id
[n_VFIELD_SIZE(1)];
74 #define GP_TO_SUBCLASS(X,G) \
76 union __group_subclass {void *gs_vp; char *gs_cp;} __gs__;\
77 __gs__.gs_cp = (char*)n_UNCONST(G) + (G)->g_subclass_off;\
81 struct grp_names_head
{
82 struct grp_names
*gnh_head
;
86 struct grp_names
*gn_next
;
87 char gn_id
[n_VFIELD_SIZE(0)];
92 struct grp_regex
*gr_last
;
93 struct grp_regex
*gr_next
;
94 struct group
*gr_mygroup
; /* xxx because lists use grp_regex*! ?? */
95 size_t gr_hits
; /* Number of times this group matched */
100 struct a_nag_cmd_alias
{
101 struct str ca_expand
;
104 struct a_nag_file_type
{
109 struct group_lookup
{
110 struct group
**gl_htable
;
111 struct group
**gl_slot
;
112 struct group
*gl_slot_last
;
113 struct group
*gl_group
;
116 static struct n_file_type
const a_nag_OBSOLETE_xz
= { /* TODO v15 compat */
117 "xz", 2, "xz -cd", sizeof("xz -cd") -1, "xz -cz", sizeof("xz -cz") -1
118 }, a_nag_OBSOLETE_gz
= {
119 "gz", 2, "gzip -cd", sizeof("gzip -cd") -1, "gzip -cz", sizeof("gzip -cz") -1
120 }, a_nag_OBSOLETE_bz2
= {
121 "bz2", 3, "bzip2 -cd", sizeof("bzip2 -cd") -1,
122 "bzip2 -cz", sizeof("bzip2 -cz") -1
125 /* List of alternate names of user */
126 struct n_strlist
*a_nag_altnames
;
129 static struct group
*_commandalias_heads
[HSHSIZE
]; /* TODO dynamic hash */
132 static struct group
*_alias_heads
[HSHSIZE
]; /* TODO dynamic hash */
134 /* `mlist', `mlsubscribe'. Anything is stored in the hashmap.. */
135 static struct group
*_mlist_heads
[HSHSIZE
]; /* TODO dynamic hash */
137 /* ..but entries which have GT_REGEX set are false lookups and will really be
138 * accessed via sequential lists instead, which are type-specific for better
139 * performance, but also to make it possible to have ".*@xy.org" as a mlist
140 * and "(one|two)@xy.org" as a mlsubscription.
141 * These lists use a bit of QOS optimization in that a matching group will
142 * become relinked as the new list head if its hit count is
143 * (>= ((xy_hits / _xy_size) >> 2))
144 * Note that the hit counts only count currently linked in nodes.. */
146 static struct grp_regex
*_mlist_regex
, *_mlsub_regex
;
147 static size_t _mlist_size
, _mlist_hits
, _mlsub_size
, _mlsub_hits
;
151 static struct group
*_shortcut_heads
[HSHSIZE
]; /* TODO dynamic hashmaps! */
154 static struct group
*_charsetalias_heads
[HSHSIZE
];
157 static struct group
*_filetype_heads
[HSHSIZE
];
159 /* Same name, while taking care for *allnet*? */
160 static bool_t
a_nag_is_same_name(char const *n1
, char const *n2
);
162 /* Delete the given name from a namelist, unless keep_single and only one */
163 static struct name
*a_nag_namelist_del_cp(struct name
*np
, char const *name
,
166 /* Grab a single name (liberal name) */
167 static char const * yankname(char const *ap
, char *wbuf
,
168 char const *separators
, int keepcomms
);
170 /* Extraction multiplexer that splits an input line to names */
171 static struct name
* _extract1(char const *line
, enum gfield ntype
,
172 char const *separators
, bool_t keepcomms
);
174 /* Recursively expand a alias name. Limit expansion to some fixed level.
175 * Direct recursion is not expanded for convenience */
176 static struct name
*a_nag_gexpand(size_t level
, struct name
*nlist
,
177 struct group
*gp
, bool_t metoo
, int ntype
);
180 static int a_nag_elide_qsort(void const *s1
, void const *s2
);
182 /* Lookup a group, return it or NULL, fill in glp anyway */
183 static struct group
* _group_lookup(enum group_type gt
,
184 struct group_lookup
*glp
, char const *id
);
186 /* Easier-to-use wrapper around _group_lookup() */
187 static struct group
* _group_find(enum group_type gt
, char const *id
);
189 /* Iteration: go to the first group, which also inits the iterator. A valid
190 * iterator can be stepped via _next(). A NULL return means no (more) groups
191 * to be iterated exist, in which case only glp->gl_group is set (NULL) */
192 static struct group
* _group_go_first(enum group_type gt
,
193 struct group_lookup
*glp
);
194 static struct group
* _group_go_next(struct group_lookup
*glp
);
196 /* Fetch the group id, create it as necessary, fail with NULL if impossible */
197 static struct group
* _group_fetch(enum group_type gt
, char const *id
,
200 /* "Intelligent" delete which handles a "*" id, too;
201 * returns a true boolean if a group was deleted, and always succeeds for "*" */
202 static bool_t
_group_del(enum group_type gt
, char const *id
);
204 static struct group
* __group_del(struct group_lookup
*glp
);
205 static void __names_del(struct group
*gp
);
207 /* Print all groups of the given type, alphasorted */
208 static void _group_print_all(enum group_type gt
);
210 static int __group_print_qsorter(void const *a
, void const *b
);
212 /* Really print a group, actually. Return number of written lines */
213 static size_t _group_print(struct group
const *gp
, FILE *fo
);
215 /* Multiplexers for list and subscribe commands */
216 static int _mlmux(enum group_type gt
, char **argv
);
217 static int _unmlmux(enum group_type gt
, char **argv
);
219 /* Relinkers for the sequential match lists */
221 static void _mlmux_linkin(struct group
*gp
);
222 static void _mlmux_linkout(struct group
*gp
);
223 # define _MLMUX_LINKIN(GP) \
224 do if ((GP)->g_type & GT_REGEX) _mlmux_linkin(GP); while (0)
225 # define _MLMUX_LINKOUT(GP) \
226 do if ((GP)->g_type & GT_REGEX) _mlmux_linkout(GP); while (0)
228 # define _MLMUX_LINKIN(GP)
229 # define _MLMUX_LINKOUT(GP)
233 a_nag_is_same_name(char const *n1
, char const *n2
){
238 if(ok_blook(allnet
)){
249 }while(c1
!= '\0' && c2
!= '\0' && c1
!= '@' && c2
!= '@');
251 rv
= !asccasecmp(n1
, n2
);
257 a_nag_namelist_del_cp(struct name
*np
, char const *name
, bool_t keep_single
){
261 for(p
= np
; p
!= NULL
; p
= p
->n_flink
)
262 if(a_nag_is_same_name(p
->n_name
, name
)){
263 if(keep_single
&& np
== p
&& np
->n_flink
== NULL
)
265 if(p
->n_blink
== NULL
){
266 if((np
= p
->n_flink
) != NULL
)
268 }else if(p
->n_flink
== NULL
)
269 p
->n_blink
->n_flink
= NULL
;
271 p
->n_blink
->n_flink
= p
->n_flink
;
272 p
->n_flink
->n_blink
= p
->n_blink
;
280 yankname(char const *ap
, char *wbuf
, char const *separators
, int keepcomms
)
283 char *wp
, c
, inquote
, lc
, lastsp
;
288 /* Skip over intermediate list trash, as in ".org> , <xy@zz.org>" */
289 for (c
= *ap
; blankchar(c
) || c
== ','; c
= *++ap
)
296 /* Parse a full name: TODO RFC 5322
297 * - Keep everything in quotes, liberal handle *quoted-pair*s therein
298 * - Skip entire (nested) comments
299 * - In non-quote, non-comment, join adjacent space to a single SP
300 * - Understand separators only in non-quote, non-comment context,
301 * and only if not part of a *quoted-pair* (XXX too liberal) */
303 for (inquote
= lc
= lastsp
= 0;; lc
= c
, ++cp
) {
312 #if 0 /* TODO when doing real RFC 5322 parsers - why have i done this? */
318 if (inquote
|| lc
== '\\') {
326 cp
= skip_comment(cp
+ 1);
334 if (strchr(separators
, c
) != NULL
)
338 lastsp
= blankchar(c
);
352 _extract1(char const *line
, enum gfield ntype
, char const *separators
,
355 struct name
*topp
, *np
, *t
;
361 if (line
== NULL
|| *line
== '\0')
366 nbuf
= smalloc(strlen(line
) +1);
367 while ((cp
= yankname(cp
, nbuf
, separators
, keepcomms
)) != NULL
) {
368 t
= nalloc(nbuf
, ntype
);
383 a_nag_gexpand(size_t level
, struct name
*nlist
, struct group
*gp
, bool_t metoo
,
385 struct grp_names
*gnp
;
386 struct name
*nlist_tail
;
388 struct grp_names_head
*gnhp
;
391 if(UICMP(z
, level
++, >, n_ALIAS_MAXEXP
)){
392 n_err(_("Expanding alias to depth larger than %d\n"), n_ALIAS_MAXEXP
);
396 GP_TO_SUBCLASS(gnhp
, gp
);
397 logname
= ok_vlook(LOGNAME
);
399 for(gnp
= gnhp
->gnh_head
; gnp
!= NULL
; gnp
= gnp
->gn_next
){
405 if(!strcmp(cp
, gp
->g_id
))
408 if((ngp
= _group_find(GT_ALIAS
, cp
)) != NULL
){
409 /* For S-nail(1), the "alias" may *be* the sender in that a name maps
410 * to a full address specification; aliases cannot be empty */
411 struct grp_names_head
*ngnhp
;
412 GP_TO_SUBCLASS(ngnhp
, ngp
);
414 assert(ngnhp
->gnh_head
!= NULL
);
415 if(metoo
|| ngnhp
->gnh_head
->gn_next
!= NULL
||
416 !a_nag_is_same_name(cp
, logname
))
417 nlist
= a_nag_gexpand(level
, nlist
, ngp
, metoo
, ntype
);
421 /* Here we should allow to expand to itself if only person in alias */
423 if(metoo
|| gnhp
->gnh_head
->gn_next
== NULL
||
424 !a_nag_is_same_name(cp
, logname
)){
427 np
= nalloc(cp
, ntype
| GFULL
);
428 if((nlist_tail
= nlist
) != NULL
){
429 while(nlist_tail
->n_flink
!= NULL
)
430 nlist_tail
= nlist_tail
->n_flink
;
431 nlist_tail
->n_flink
= np
;
432 np
->n_blink
= nlist_tail
;
443 a_nag_elide_qsort(void const *s1
, void const *s2
){
444 struct name
const * const *np1
, * const *np2
;
450 rv
= asccasecmp((*np1
)->n_name
, (*np2
)->n_name
);
455 static struct group
*
456 _group_lookup(enum group_type gt
, struct group_lookup
*glp
, char const *id
){
458 struct group
*lgp
, *gp
;
464 ( gt
& GT_COMMANDALIAS
? _commandalias_heads
465 : (gt
& GT_ALIAS
? _alias_heads
466 : (gt
& GT_MLIST
? _mlist_heads
467 : (gt
& GT_SHORTCUT
? _shortcut_heads
468 : (gt
& GT_CHARSETALIAS
? _charsetalias_heads
469 : (/*gt & GT_FILETYPE ?*/ _filetype_heads
471 gp
= *(glp
->gl_slot
= &glp
->gl_htable
[
472 ((gt
& (GT_MLIST
| GT_CHARSETALIAS
| GT_FILETYPE
))
473 ? n_torek_ihash(id
) : n_torek_hash(id
)) % HSHSIZE
]);
476 if(gt
& (GT_MLIST
| GT_CHARSETALIAS
| GT_FILETYPE
)){
478 for(; gp
!= NULL
; lgp
= gp
, gp
= gp
->g_next
)
479 if((gp
->g_type
& gt
) && *gp
->g_id
== c1
&&
480 !asccasecmp(&gp
->g_id
[1], id
))
483 for(; gp
!= NULL
; lgp
= gp
, gp
= gp
->g_next
)
484 if((gp
->g_type
& gt
) && *gp
->g_id
== c1
&& !strcmp(&gp
->g_id
[1], id
))
488 glp
->gl_slot_last
= lgp
;
494 static struct group
*
495 _group_find(enum group_type gt
, char const *id
)
497 struct group_lookup gl
;
501 gp
= _group_lookup(gt
, &gl
, id
);
506 static struct group
*
507 _group_go_first(enum group_type gt
, struct group_lookup
*glp
)
509 struct group
**gpa
, *gp
;
513 for (glp
->gl_htable
= gpa
= (
514 gt
& GT_COMMANDALIAS
? _commandalias_heads
515 : (gt
& GT_ALIAS
? _alias_heads
516 : (gt
& GT_MLIST
? _mlist_heads
517 : (gt
& GT_SHORTCUT
? _shortcut_heads
518 : (gt
& GT_CHARSETALIAS
? _charsetalias_heads
519 : (gt
& GT_FILETYPE
? _filetype_heads
522 i
< HSHSIZE
; ++gpa
, ++i
)
523 if ((gp
= *gpa
) != NULL
) {
529 glp
->gl_group
= gp
= NULL
;
531 glp
->gl_slot_last
= NULL
;
536 static struct group
*
537 _group_go_next(struct group_lookup
*glp
)
539 struct group
*gp
, **gpa
;
542 if ((gp
= glp
->gl_group
->g_next
) != NULL
)
543 glp
->gl_slot_last
= glp
->gl_group
;
545 glp
->gl_slot_last
= NULL
;
546 for (gpa
= glp
->gl_htable
+ HSHSIZE
; ++glp
->gl_slot
< gpa
;)
547 if ((gp
= *glp
->gl_slot
) != NULL
)
555 static struct group
*
556 _group_fetch(enum group_type gt
, char const *id
, size_t addsz
)
558 struct group_lookup gl
;
563 if ((gp
= _group_lookup(gt
, &gl
, id
)) != NULL
)
567 if (UIZ_MAX
- n_ALIGN(l
) <= n_ALIGN(n_VSTRUCT_SIZEOF(struct group
, g_id
)))
570 i
= n_ALIGN(n_VSTRUCT_SIZEOF(struct group
, g_id
) + l
);
571 switch (gt
& GT_MASK
) {
572 case GT_COMMANDALIAS
:
573 addsz
+= sizeof(struct a_nag_cmd_alias
);
576 addsz
+= sizeof(struct grp_names_head
);
579 addsz
+= sizeof(struct a_nag_file_type
);
583 if (n_is_maybe_regex(id
)) {
584 addsz
= sizeof(struct grp_regex
);
590 case GT_CHARSETALIAS
:
594 if (UIZ_MAX
- i
< addsz
|| UI32_MAX
<= i
|| UI16_MAX
< i
- l
)
597 gp
= smalloc(i
+ addsz
);
598 memcpy(gp
->g_id
, id
, l
);
599 gp
->g_subclass_off
= (ui32_t
)i
;
600 gp
->g_id_len_sub
= (ui16_t
)(i
- --l
);
602 if(gt
& (GT_MLIST
| GT_CHARSETALIAS
| GT_FILETYPE
)){
605 for(cp
= gp
->g_id
; (c
= *cp
) != '\0'; ++cp
)
610 struct grp_names_head
*gnhp
;
612 GP_TO_SUBCLASS(gnhp
, gp
);
613 gnhp
->gnh_head
= NULL
;
616 else if (/*(gt & GT_MLIST) &&*/ gt
& GT_REGEX
) {
618 struct grp_regex
*grp
;
619 GP_TO_SUBCLASS(grp
, gp
);
621 if((s
= regcomp(&grp
->gr_regex
, id
,
622 REG_EXTENDED
| REG_ICASE
| REG_NOSUB
)) != 0){
623 n_err(_("Invalid regular expression: %s: %s\n"),
624 n_shexp_quote_cp(id
, FAL0
), n_regex_err_to_doc(&grp
->gr_regex
, s
));
629 grp
->gr_mygroup
= gp
;
634 gp
->g_next
= *gl
.gl_slot
;
642 _group_del(enum group_type gt
, char const *id
)
644 enum group_type xgt
= gt
& GT_MASK
;
645 struct group_lookup gl
;
649 /* Delete 'em all? */
650 if (id
[0] == '*' && id
[1] == '\0') {
651 for (gp
= _group_go_first(gt
, &gl
); gp
!= NULL
;)
652 gp
= (gp
->g_type
& xgt
) ? __group_del(&gl
) : _group_go_next(&gl
);
653 gp
= (struct group
*)TRU1
;
654 } else if ((gp
= _group_lookup(gt
, &gl
, id
)) != NULL
) {
655 if (gp
->g_type
& xgt
)
664 static struct group
*
665 __group_del(struct group_lookup
*glp
)
667 struct group
*x
, *gp
;
670 /* Overly complicated: link off this node, step ahead to next.. */
672 if((gp
= glp
->gl_slot_last
) != NULL
)
673 gp
= (gp
->g_next
= x
->g_next
);
675 glp
->gl_slot_last
= NULL
;
676 gp
= (*glp
->gl_slot
= x
->g_next
);
681 for(gpa
= &glp
->gl_htable
[HSHSIZE
]; ++glp
->gl_slot
< gpa
;)
682 if((gp
= *glp
->gl_slot
) != NULL
)
688 if (x
->g_type
& GT_ALIAS
)
691 else if (/*(x->g_type & GT_MLIST) &&*/ x
->g_type
& GT_REGEX
) {
692 struct grp_regex
*grp
;
693 GP_TO_SUBCLASS(grp
, x
);
695 regfree(&grp
->gr_regex
);
706 __names_del(struct group
*gp
)
708 struct grp_names_head
*gnhp
;
709 struct grp_names
*gnp
;
712 GP_TO_SUBCLASS(gnhp
, gp
);
713 for (gnp
= gnhp
->gnh_head
; gnp
!= NULL
;) {
714 struct grp_names
*x
= gnp
;
722 _group_print_all(enum group_type gt
)
726 struct group
const *gp
;
733 xgt
= gt
& GT_PRINT_MASK
;
734 gpa
= ( xgt
& GT_COMMANDALIAS
? _commandalias_heads
735 : (xgt
& GT_ALIAS
? _alias_heads
736 : (xgt
& GT_MLIST
? _mlist_heads
737 : (xgt
& GT_SHORTCUT
? _shortcut_heads
738 : (xgt
& GT_CHARSETALIAS
? _charsetalias_heads
739 : (xgt
& GT_FILETYPE
? _filetype_heads
742 for (h
= 0, i
= 1; h
< HSHSIZE
; ++h
)
743 for (gp
= gpa
[h
]; gp
!= NULL
; gp
= gp
->g_next
)
744 if ((gp
->g_type
& xgt
) == xgt
)
746 ida
= salloc(i
* sizeof *ida
);
748 for (i
= h
= 0; h
< HSHSIZE
; ++h
)
749 for (gp
= gpa
[h
]; gp
!= NULL
; gp
= gp
->g_next
)
750 if ((gp
->g_type
& xgt
) == xgt
)
755 qsort(ida
, i
, sizeof *ida
, &__group_print_qsorter
);
757 if ((fp
= Ftmp(NULL
, "prgroup", OF_RDWR
| OF_UNLINK
| OF_REGISTER
)) == NULL
)
761 for (i
= 0; ida
[i
] != NULL
; ++i
)
762 lines
+= _group_print(_group_find(gt
, ida
[i
]), fp
);
765 if (gt
& GT_SUBSCRIBE
)
766 i
= (ui32_t
)_mlsub_size
, h
= (ui32_t
)_mlsub_hits
;
768 i
= (ui32_t
)_mlist_size
, h
= (ui32_t
)_mlist_hits
;
769 if (i
> 0 && (n_poption
& n_PO_D_V
)){
770 fprintf(fp
, _("# %s list regex(7) total: %u entries, %u hits\n"),
771 (gt
& GT_SUBSCRIBE
? _("Subscribed") : _("Non-subscribed")),
778 if (fp
!= n_stdout
) {
779 page_or_print(fp
, lines
);
786 __group_print_qsorter(void const *a
, void const *b
)
791 rv
= strcmp(*(char**)n_UNCONST(a
), *(char**)n_UNCONST(b
));
797 _group_print(struct group
const *gp
, FILE *fo
)
805 if(gp
->g_type
& GT_COMMANDALIAS
){
806 struct a_nag_cmd_alias
*ncap
;
808 GP_TO_SUBCLASS(ncap
, gp
);
809 fprintf(fo
, "commandalias %s %s\n",
810 n_shexp_quote_cp(gp
->g_id
, TRU1
),
811 n_shexp_quote_cp(ncap
->ca_expand
.s
, TRU1
));
812 } else if (gp
->g_type
& GT_ALIAS
) {
813 struct grp_names_head
*gnhp
;
814 struct grp_names
*gnp
;
816 fprintf(fo
, "alias %s ", gp
->g_id
);
818 GP_TO_SUBCLASS(gnhp
, gp
);
819 if ((gnp
= gnhp
->gnh_head
) != NULL
) { /* xxx always 1+ entries */
821 struct grp_names
*x
= gnp
;
823 fprintf(fo
, " \"%s\"", string_quote(x
->gn_id
)); /* TODO shexp */
824 } while (gnp
!= NULL
);
827 } else if (gp
->g_type
& GT_MLIST
) {
829 if ((gp
->g_type
& GT_REGEX
) && (n_poption
& n_PO_D_V
)){
831 struct grp_regex
*grp
,
832 *lp
= (gp
->g_type
& GT_SUBSCRIBE
? _mlsub_regex
: _mlist_regex
);
834 GP_TO_SUBCLASS(grp
, gp
);
835 for (i
= 1; lp
!= grp
; lp
= lp
->gr_next
)
837 fprintf(fo
, "# regex(7): hits %" PRIuZ
", sort %" PRIuZ
".\n ",
843 fprintf(fo
, "wysh %s %s\n",
844 (gp
->g_type
& GT_SUBSCRIBE
? "mlsubscribe" : "mlist"),
845 n_shexp_quote_cp(gp
->g_id
, TRU1
));
846 } else if (gp
->g_type
& GT_SHORTCUT
) {
847 GP_TO_SUBCLASS(cp
, gp
);
848 fprintf(fo
, "wysh shortcut %s %s\n",
849 gp
->g_id
, n_shexp_quote_cp(cp
, TRU1
));
850 } else if (gp
->g_type
& GT_CHARSETALIAS
) {
851 GP_TO_SUBCLASS(cp
, gp
);
852 fprintf(fo
, "charsetalias %s %s\n",
853 n_shexp_quote_cp(gp
->g_id
, TRU1
), n_shexp_quote_cp(cp
, TRU1
));
854 } else if (gp
->g_type
& GT_FILETYPE
) {
855 struct a_nag_file_type
*nftp
;
857 GP_TO_SUBCLASS(nftp
, gp
);
858 fprintf(fo
, "filetype %s %s %s\n",
859 n_shexp_quote_cp(gp
->g_id
, TRU1
),
860 n_shexp_quote_cp(nftp
->nft_load
.s
, TRU1
),
861 n_shexp_quote_cp(nftp
->nft_save
.s
, TRU1
));
869 _mlmux(enum group_type gt
, char **argv
)
880 _group_print_all(gt
);
882 if ((gp
= _group_find(gt
, *argv
)) != NULL
) {
883 if (gt
& GT_SUBSCRIBE
) {
884 if (!(gp
->g_type
& GT_SUBSCRIBE
)) {
886 gp
->g_type
|= GT_SUBSCRIBE
;
889 ecp
= N_("Mailing-list already `mlsubscribe'd: %s\n");
893 ecp
= N_("Mailing-list already `mlist'ed: %s\n");
896 } else if(_group_fetch(gt
, *argv
, 0) == NULL
) {
897 ecp
= N_("Failed to create storage for mailing-list: %s\n");
899 n_err(V_(ecp
), n_shexp_quote_cp(*argv
, FAL0
));
902 } while (*++argv
!= NULL
);
909 _unmlmux(enum group_type gt
, char **argv
)
915 for (; *argv
!= NULL
; ++argv
) {
916 if (gt
& GT_SUBSCRIBE
) {
917 struct group_lookup gl
;
920 if (!(isaster
= (**argv
== '*')))
921 gp
= _group_find(gt
, *argv
);
922 else if ((gp
= _group_go_first(gt
, &gl
)) == NULL
)
924 else if (gp
!= NULL
&& !(gp
->g_type
& GT_SUBSCRIBE
))
929 if (gp
->g_type
& GT_SUBSCRIBE
) {
931 gp
->g_type
&= ~GT_SUBSCRIBE
;
935 while ((gp
= _group_go_next(&gl
)) != NULL
&&
936 !(gp
->g_type
& GT_SUBSCRIBE
))
942 n_err(_("Mailing-list not `mlsubscribe'd: %s\n"),
943 n_shexp_quote_cp(*argv
, FAL0
));
948 } else if (_group_del(gt
, *argv
))
950 n_err(_("No such mailing-list: %s\n"), n_shexp_quote_cp(*argv
, FAL0
));
959 _mlmux_linkin(struct group
*gp
)
961 struct grp_regex
**lpp
, *grp
, *lhp
;
964 if (gp
->g_type
& GT_SUBSCRIBE
) {
972 GP_TO_SUBCLASS(grp
, gp
);
973 if ((lhp
= *lpp
) != NULL
) {
974 (grp
->gr_last
= lhp
->gr_last
)->gr_next
= grp
;
975 (grp
->gr_next
= lhp
)->gr_last
= grp
;
977 *lpp
= grp
->gr_last
= grp
->gr_next
= grp
;
983 _mlmux_linkout(struct group
*gp
)
985 struct grp_regex
*grp
, **lpp
;
988 GP_TO_SUBCLASS(grp
, gp
);
990 if (gp
->g_type
& GT_SUBSCRIBE
) {
993 _mlsub_hits
-= grp
->gr_hits
;
997 _mlist_hits
-= grp
->gr_hits
;
1000 if (grp
->gr_next
== grp
)
1003 (grp
->gr_last
->gr_next
= grp
->gr_next
)->gr_last
= grp
->gr_last
;
1005 *lpp
= grp
->gr_next
;
1009 #endif /* HAVE_REGEX */
1012 nalloc(char const *str
, enum gfield ntype
)
1014 struct n_addrguts ag
;
1018 assert(!(ntype
& GFULLEXTRA
) || (ntype
& GFULL
) != 0);
1020 str
= n_addrspec_with_guts(&ag
, str
,
1021 ((ntype
& (GFULL
| GSKIN
| GREF
)) != 0), FAL0
);
1024 np = NULL; TODO We cannot return NULL,
1025 goto jleave; TODO thus handle failures in here!
1030 if (!(ag
.ag_n_flags
& NAME_NAME_SALLOC
)) {
1031 ag
.ag_n_flags
|= NAME_NAME_SALLOC
;
1032 np
= salloc(sizeof(*np
) + ag
.ag_slen
+1);
1033 memcpy(np
+ 1, ag
.ag_skinned
, ag
.ag_slen
+1);
1034 ag
.ag_skinned
= (char*)(np
+ 1);
1036 np
= salloc(sizeof *np
);
1043 np
->n_fullname
= np
->n_name
= ag
.ag_skinned
;
1044 np
->n_fullextra
= NULL
;
1045 np
->n_flags
= ag
.ag_n_flags
;
1047 if (ntype
& GFULL
) {
1048 if (ag
.ag_ilen
== ag
.ag_slen
1050 && !(ag
.ag_n_flags
& NAME_IDNA
)
1054 if (ag
.ag_n_flags
& NAME_ADDRSPEC_ISFILEORPIPE
)
1057 /* n_fullextra is only the complete name part without address.
1058 * Beware of "-r '<abc@def>'", don't treat that as FULLEXTRA */
1059 if ((ntype
& GFULLEXTRA
) && ag
.ag_ilen
> ag
.ag_slen
+ 2) {
1060 size_t s
= ag
.ag_iaddr_start
, e
= ag
.ag_iaddr_aend
, i
;
1063 if (s
== 0 || str
[--s
] != '<' || str
[e
++] != '>')
1064 goto jskipfullextra
;
1066 in
.s
= n_lofi_alloc(s
+ 1 + i
+1);
1067 while(s
> 0 && blankchar(str
[s
- 1]))
1069 memcpy(in
.s
, str
, s
);
1072 while (blankchar(str
[e
])) {
1078 memcpy(&in
.s
[s
], &str
[e
], i
);
1081 in
.s
[in
.l
= s
] = '\0';
1082 mime_fromhdr(&in
, &out
, TD_ISPR
| TD_ICONV
);
1084 for (cp
= out
.s
, i
= out
.l
; i
> 0 && spacechar(*cp
); --i
, ++cp
)
1086 while (i
> 0 && spacechar(cp
[i
- 1]))
1088 np
->n_fullextra
= savestrbuf(cp
, i
);
1095 /* n_fullname depends on IDNA conversion */
1097 if (!(ag
.ag_n_flags
& NAME_IDNA
)) {
1099 in
.s
= n_UNCONST(str
);
1103 /* The domain name was IDNA and has been converted. We also have to
1104 * ensure that the domain name in .n_fullname is replaced with the
1105 * converted version, since MIME doesn't perform encoding of addrs */
1106 /* TODO This definetely doesn't belong here! */
1107 size_t l
= ag
.ag_iaddr_start
,
1108 lsuff
= ag
.ag_ilen
- ag
.ag_iaddr_aend
;
1109 in
.s
= ac_alloc(l
+ ag
.ag_slen
+ lsuff
+1);
1110 memcpy(in
.s
, str
, l
);
1111 memcpy(in
.s
+ l
, ag
.ag_skinned
, ag
.ag_slen
);
1113 memcpy(in
.s
+ l
, str
+ ag
.ag_iaddr_aend
, lsuff
);
1119 mime_fromhdr(&in
, &out
, TD_ISPR
| TD_ICONV
);
1120 np
->n_fullname
= savestr(out
.s
);
1123 if (ag
.ag_n_flags
& NAME_IDNA
)
1126 np
->n_flags
|= NAME_FULLNAME_SALLOC
;
1134 ndup(struct name
*np
, enum gfield ntype
)
1139 if ((ntype
& (GFULL
| GSKIN
)) && !(np
->n_flags
& NAME_SKINNED
)) {
1140 nnp
= nalloc(np
->n_name
, ntype
);
1144 nnp
= salloc(sizeof *np
);
1145 nnp
->n_flink
= nnp
->n_blink
= NULL
;
1146 nnp
->n_type
= ntype
;
1147 nnp
->n_flags
= (np
->n_flags
& ~(NAME_NAME_SALLOC
| NAME_FULLNAME_SALLOC
)) |
1149 nnp
->n_name
= savestr(np
->n_name
);
1150 if (np
->n_name
== np
->n_fullname
|| !(ntype
& (GFULL
| GSKIN
))) {
1151 nnp
->n_fullname
= nnp
->n_name
;
1152 nnp
->n_fullextra
= NULL
;
1154 nnp
->n_flags
|= NAME_FULLNAME_SALLOC
;
1155 nnp
->n_fullname
= savestr(np
->n_fullname
);
1156 nnp
->n_fullextra
= (np
->n_fullextra
== NULL
) ? NULL
1157 : savestr(np
->n_fullextra
);
1165 cat(struct name
*n1
, struct name
*n2
)
1177 while (tail
->n_flink
!= NULL
)
1178 tail
= tail
->n_flink
;
1188 namelist_dup(struct name
const *np
, enum gfield ntype
){
1189 struct name
*nlist
, *xnp
;
1192 for(nlist
= xnp
= NULL
; np
!= NULL
; np
= np
->n_flink
){
1195 x
= ndup(n_UNCONST(np
), (np
->n_type
& ~GMASK
) | ntype
);
1196 if((x
->n_blink
= xnp
) == NULL
)
1207 count(struct name
const *np
)
1212 for (c
= 0; np
!= NULL
; np
= np
->n_flink
)
1213 if (!(np
->n_type
& GDEL
))
1220 count_nonlocal(struct name
const *np
)
1225 for (c
= 0; np
!= NULL
; np
= np
->n_flink
)
1226 if (!(np
->n_type
& GDEL
) && !(np
->n_flags
& NAME_ADDRSPEC_ISFILEORPIPE
))
1233 extract(char const *line
, enum gfield ntype
)
1238 rv
= _extract1(line
, ntype
, " \t,", 0);
1244 lextract(char const *line
, enum gfield ntype
)
1249 rv
= ((line
!= NULL
&& strpbrk(line
, ",\"\\(<|"))
1250 ? _extract1(line
, ntype
, ",", 1) : extract(line
, ntype
));
1256 detract(struct name
*np
, enum gfield ntype
)
1267 flags
= ntype
& (GCOMMA
| GNAMEONLY
);
1268 ntype
&= ~(GCOMMA
| GNAMEONLY
);
1271 for (p
= np
; p
!= NULL
; p
= p
->n_flink
) {
1272 if (ntype
&& (p
->n_type
& GMASK
) != ntype
)
1274 s
+= strlen(flags
& GNAMEONLY
? p
->n_name
: p
->n_fullname
) +1;
1284 for (p
= np
; p
!= NULL
; p
= p
->n_flink
) {
1285 if (ntype
&& (p
->n_type
& GMASK
) != ntype
)
1287 cp
= sstpcpy(cp
, (flags
& GNAMEONLY
? p
->n_name
: p
->n_fullname
));
1288 if ((flags
& GCOMMA
) && p
->n_flink
!= NULL
)
1293 if ((flags
& GCOMMA
) && *--cp
== ',')
1301 grab_names(enum n_go_input_flags gif
, char const *field
, struct name
*np
,
1302 int comma
, enum gfield gflags
)
1308 np
= lextract(n_go_input_cp(gif
, field
, detract(np
, comma
)), gflags
);
1309 for (nq
= np
; nq
!= NULL
; nq
= nq
->n_flink
)
1310 if (is_addr_invalid(nq
, EACM_NONE
))
1317 name_is_same_domain(struct name
const *n1
, struct name
const *n2
)
1319 char const *d1
, *d2
;
1323 d1
= strrchr(n1
->n_name
, '@');
1324 d2
= strrchr(n2
->n_name
, '@');
1326 rv
= (d1
!= NULL
&& d2
!= NULL
) ? !asccasecmp(++d1
, ++d2
) : FAL0
;
1333 checkaddrs(struct name
*np
, enum expand_addr_check_mode eacm
,
1334 si8_t
*set_on_error
)
1339 for (n
= np
; n
!= NULL
; n
= n
->n_flink
) {
1342 if ((rv
= is_addr_invalid(n
, eacm
)) != 0) {
1343 if (set_on_error
!= NULL
)
1344 *set_on_error
|= rv
; /* don't loose -1! */
1345 else if (eacm
& EAF_MAYKEEP
) /* TODO HACK! See definition! */
1348 n
->n_blink
->n_flink
= n
->n_flink
;
1350 n
->n_flink
->n_blink
= n
->n_blink
;
1360 namelist_vaporise_head(struct header
*hp
, enum expand_addr_check_mode eacm
,
1361 bool_t metoo
, si8_t
*set_on_error
)
1363 /* TODO namelist_vaporise_head() is incredibly expensive and redundant */
1364 struct name
*tolist
, *np
, **npp
;
1367 tolist
= cat(hp
->h_to
, cat(hp
->h_cc
, hp
->h_bcc
));
1368 hp
->h_to
= hp
->h_cc
= hp
->h_bcc
= NULL
;
1370 tolist
= usermap(tolist
, metoo
);
1371 tolist
= n_alternates_delete(tolist
, TRU1
);
1372 tolist
= elide(checkaddrs(tolist
, eacm
, set_on_error
));
1374 for (np
= tolist
; np
!= NULL
; np
= np
->n_flink
) {
1375 switch (np
->n_type
& (GDEL
| GMASK
)) {
1376 case GTO
: npp
= &hp
->h_to
; break;
1377 case GCC
: npp
= &hp
->h_cc
; break;
1378 case GBCC
: npp
= &hp
->h_bcc
; break;
1381 *npp
= cat(*npp
, ndup(np
, np
->n_type
| GFULL
));
1388 usermap(struct name
*names
, bool_t force_metoo
){
1390 struct name
*nlist
, *nlist_tail
, *np
, *cp
;
1394 metoo
= (force_metoo
|| ok_blook(metoo
));
1395 nlist
= nlist_tail
= NULL
;
1398 for(; np
!= NULL
; np
= cp
){
1399 assert(!(np
->n_type
& GDEL
)); /* TODO legacy */
1402 if(is_fileorpipe_addr(np
) ||
1403 (gp
= _group_find(GT_ALIAS
, np
->n_name
)) == NULL
){
1404 if((np
->n_blink
= nlist_tail
) != NULL
)
1405 nlist_tail
->n_flink
= np
;
1411 nlist
= a_nag_gexpand(0, nlist
, gp
, metoo
, np
->n_type
);
1412 if((nlist_tail
= nlist
) != NULL
)
1413 while(nlist_tail
->n_flink
!= NULL
)
1414 nlist_tail
= nlist_tail
->n_flink
;
1422 elide(struct name
*names
)
1425 struct name
*nlist
, *np
, **nparr
;
1433 /* Throw away all deleted nodes */
1434 for(np
= NULL
, i
= 0; names
!= NULL
; names
= names
->n_flink
)
1435 if(!(names
->n_type
& GDEL
)){
1436 names
->n_blink
= np
;
1438 np
->n_flink
= names
;
1445 if(nlist
== NULL
|| i
== 1)
1448 /* Create a temporay array and sort that */
1449 nparr
= n_lofi_alloc(sizeof(*nparr
) * i
);
1451 for(i
= 0, np
= nlist
; np
!= NULL
; np
= np
->n_flink
)
1454 qsort(nparr
, i
, sizeof *nparr
, &a_nag_elide_qsort
);
1456 /* Remove duplicates XXX speedup, or list_uniq()! */
1457 for(j
= 0, --i
; j
< i
;){
1458 if(asccasecmp(nparr
[j
]->n_name
, nparr
[k
= j
+ 1]->n_name
))
1462 nparr
[k
] = nparr
[k
+ 1];
1467 /* Throw away all list members which are not part of the array.
1468 * Note this keeps the original, possibly carefully crafted, order of the
1469 * addressees, thus */
1470 for(np
= nlist
; np
!= NULL
; np
= np
->n_flink
){
1471 for(j
= 0; j
<= i
; ++j
)
1478 nlist
= np
->n_flink
;
1481 np
->n_blink
->n_flink
= np
->n_flink
;
1482 if(np
->n_flink
!= NULL
)
1483 np
->n_flink
->n_blink
= np
->n_blink
;
1494 c_alternates(void *v
){ /* TODO use a hashmap!! */
1501 if(*(argv
= v
) == NULL
){
1504 if((ccp
= ok_vlook(alternates
)) != NULL
)
1505 fprintf(n_stdout
, "alternates %s\n", ccp
);
1507 fputs(_("# no alternates registered\n"), n_stdout
);
1511 struct n_strlist
*slp
, **slpa
;
1513 while((slp
= a_nag_altnames
) != NULL
){
1514 a_nag_altnames
= slp
->sl_next
;
1519 /* Extension: only clearance? */
1520 if(argv
[1] == NULL
&& argv
[0][0] == '-' && argv
[0][1] == '\0')
1522 else for(slpa
= &a_nag_altnames
; *argv
!= NULL
; ++argv
){
1526 if((np
= lextract(*argv
, GSKIN
)) == NULL
|| np
->n_flink
!= NULL
||
1527 (np
= checkaddrs(np
, EACM_STRICT
, NULL
)) == NULL
){
1528 n_err(_("Invalid `alternates' argument: %s\n"),
1529 n_shexp_quote_cp(*argv
, FAL0
));
1534 l
= strlen(np
->n_name
);
1535 if(UIZ_MAX
- l
<= vl
){
1536 n_err(_("Failed to create storage for alternate: %s\n"),
1537 n_shexp_quote_cp(*argv
, FAL0
));
1542 slp
= n_STRLIST_ALLOC(l
);
1543 slp
->sl_next
= NULL
;
1545 memcpy(slp
->sl_dat
, np
->n_name
, ++l
);
1547 slpa
= &slp
->sl_next
;
1552 /* And put it into *alternates* */
1554 cp
= n_autorec_alloc(vl
);
1555 for(vl
= 0, slp
= a_nag_altnames
; slp
!= NULL
; slp
= slp
->sl_next
){
1556 memcpy(&cp
[vl
], slp
->sl_dat
, slp
->sl_len
);
1557 cp
[vl
+= slp
->sl_len
] = ' ';
1563 n_PS_ROOT_BLOCK(vl
> 0 ? ok_vset(alternates
, cp
) : ok_vclear(alternates
));
1570 n_alternates_delete(struct name
*np
, bool_t keep_single
){
1574 /* Throw away all deleted nodes: we may not allow to remove a lonely
1575 * addressee if that matches an alternate, so ensure we do not need to
1576 * fiddle around with GDEL; _namelist_del_cp() will fully del, too */
1580 for(xp
= newnp
= NULL
; np
!= NULL
; np
= np
->n_flink
)
1581 if(!(np
->n_type
& GDEL
)){
1594 if(keep_single
&& np
->n_flink
== NULL
)
1597 if(a_nag_altnames
!= NULL
){
1598 struct n_strlist
*slp
;
1600 for(slp
= a_nag_altnames
; slp
!= NULL
; slp
= slp
->sl_next
){
1601 np
= a_nag_namelist_del_cp(np
, slp
->sl_dat
, keep_single
);
1602 if(np
== NULL
|| (keep_single
&& np
->n_flink
== NULL
))
1607 np
= a_nag_namelist_del_cp(np
, ok_vlook(LOGNAME
), keep_single
);
1608 if(np
== NULL
|| (keep_single
&& np
->n_flink
== NULL
))
1611 for(xp
= lextract(ok_vlook(from
), GEXTRA
| GSKIN
); xp
!= NULL
;
1613 np
= a_nag_namelist_del_cp(np
, xp
->n_name
, keep_single
);
1614 if(np
== NULL
|| (keep_single
&& np
->n_flink
== NULL
))
1618 for(xp
= extract(ok_vlook(sender
), GEXTRA
| GSKIN
); xp
!= NULL
;
1620 np
= a_nag_namelist_del_cp(np
, xp
->n_name
, keep_single
);
1621 if(np
== NULL
|| (keep_single
&& np
->n_flink
== NULL
))
1625 for(xp
= lextract(ok_vlook(replyto
), GEXTRA
| GSKIN
); xp
!= NULL
;
1627 np
= a_nag_namelist_del_cp(np
, xp
->n_name
, keep_single
);
1628 if(np
== NULL
|| (keep_single
&& np
->n_flink
== NULL
))
1637 n_is_myname(char const *name
){
1641 if(a_nag_is_same_name(ok_vlook(LOGNAME
), name
))
1644 if(a_nag_altnames
!= NULL
){
1645 struct n_strlist
*slp
;
1647 for(slp
= a_nag_altnames
; slp
!= NULL
; slp
= slp
->sl_next
)
1648 if(a_nag_is_same_name(slp
->sl_dat
, name
))
1652 for(xp
= lextract(ok_vlook(from
), GEXTRA
| GSKIN
); xp
!= NULL
;
1654 if(a_nag_is_same_name(xp
->n_name
, name
))
1657 for(xp
= lextract(ok_vlook(replyto
), GEXTRA
| GSKIN
); xp
!= NULL
;
1659 if(a_nag_is_same_name(xp
->n_name
, name
))
1662 for(xp
= extract(ok_vlook(sender
), GEXTRA
| GSKIN
); xp
!= NULL
;
1664 if(a_nag_is_same_name(xp
->n_name
, name
))
1670 return (name
!= NULL
);
1674 c_addrcodec(void *vp
){
1675 struct n_addrguts ag
;
1676 struct n_string s_b
, *sp
;
1679 char const **argv
, *varname
, *act
, *cp
;
1682 sp
= n_string_creat_auto(&s_b
);
1684 varname
= (n_pstate
& n_PS_ARGMOD_VPUT
) ? *argv
++ : NULL
;
1687 for(cp
= act
; *cp
!= '\0' && !blankspacechar(*cp
); ++cp
)
1698 alen
= PTR2SIZE(cp
- act
);
1706 if(i
<= UIZ_MAX
/ 4)
1708 sp
= n_string_reserve(sp
, i
);
1711 n_pstate_err_no
= n_ERR_NONE
;
1713 if(is_ascncaseprefix(act
, "encode", alen
)){
1714 /* This function cannot be a simple nalloc() wrapper even later on, since
1715 * we may need to turn any ", () or \ into quoted-pairs */
1718 while((c
= *cp
++) != '\0'){
1719 if(((c
== '(' || c
== ')') && mode
< 1) || (c
== '"' && mode
< 2) ||
1720 (c
== '\\' && mode
< 3))
1721 sp
= n_string_push_c(sp
, '\\');
1722 sp
= n_string_push_c(sp
, c
);
1725 if(n_addrspec_with_guts(&ag
, n_string_cp(sp
), TRU1
, TRU1
) == NULL
||
1726 (ag
.ag_n_flags
& (NAME_ADDRSPEC_ISADDR
| NAME_ADDRSPEC_INVALID
)
1727 ) != NAME_ADDRSPEC_ISADDR
){
1729 n_pstate_err_no
= n_ERR_INVAL
;
1734 np
= nalloc(ag
.ag_input
, GTO
| GFULL
| GSKIN
);
1735 cp
= np
->n_fullname
;
1737 }else if(mode
== 0){
1738 if(is_ascncaseprefix(act
, "decode", alen
)){
1741 while((c
= *cp
++) != '\0'){
1744 sp
= n_string_push_c(sp
, '(');
1745 act
= skip_comment(cp
);
1747 sp
= n_string_push_buf(sp
, cp
, PTR2SIZE(act
- cp
));
1748 sp
= n_string_push_c(sp
, ')');
1753 if((c
= *cp
++) == '"')
1755 if(c
== '\\' && (c
= *cp
) != '\0')
1757 sp
= n_string_push_c(sp
, c
);
1761 if(c
== '\\' && (c
= *cp
++) == '\0')
1763 sp
= n_string_push_c(sp
, c
);
1767 cp
= n_string_cp(sp
);
1768 }else if(is_ascncaseprefix(act
, "skin", alen
)){
1769 /* Let's just use the is-single-address hack for this one, too.. */
1770 if(n_addrspec_with_guts(&ag
, cp
, TRU1
, TRU1
) == NULL
||
1771 (ag
.ag_n_flags
& (NAME_ADDRSPEC_ISADDR
| NAME_ADDRSPEC_INVALID
)
1772 ) != NAME_ADDRSPEC_ISADDR
){
1773 n_pstate_err_no
= n_ERR_INVAL
;
1778 np
= nalloc(ag
.ag_input
, GTO
| GFULL
| GSKIN
);
1786 if(varname
== NULL
){
1787 if(fprintf(n_stdout
, "%s\n", cp
) < 0){
1788 n_pstate_err_no
= n_err_no
;
1791 }else if(!n_var_vset(varname
, (uintptr_t)cp
)){
1792 n_pstate_err_no
= n_ERR_NOTSUP
;
1798 return (vp
!= NULL
? 0 : 1);
1800 n_err(_("Synopsis: addrcodec: <[+[+[+]]]e[ncode]|d[ecode]|s[kin]> "
1801 "<rest-of-line>\n"));
1802 n_pstate_err_no
= n_ERR_INVAL
;
1808 c_commandalias(void *vp
){
1810 char const **argv
, *ccp
;
1817 if((ccp
= *argv
) == NULL
){
1818 _group_print_all(GT_COMMANDALIAS
);
1822 /* Verify the name is a valid one, and not a command modifier */
1823 if(*ccp
== '\0' || *n_cmd_isolate(ccp
) != '\0' ||
1824 !asccasecmp(ccp
, "ignerr") || !asccasecmp(ccp
, "wysh") ||
1825 !asccasecmp(ccp
, "vput")){
1826 n_err(_("`commandalias': not a valid command name: %s\n"),
1827 n_shexp_quote_cp(ccp
, FAL0
));
1832 if(argv
[1] == NULL
){
1833 if((gp
= _group_find(GT_COMMANDALIAS
, ccp
)) != NULL
)
1834 _group_print(gp
, n_stdout
);
1836 n_err(_("No such commandalias: %s\n"), n_shexp_quote_cp(ccp
, FAL0
));
1840 /* Because one hardly ever redefines, anything is stored in one chunk */
1844 /* Delete the old one, if any; don't get fooled to remove them all */
1845 if(ccp
[0] != '*' || ccp
[1] != '\0')
1846 _group_del(GT_COMMANDALIAS
, ccp
);
1848 for(i
= len
= 0, ++argv
; argv
[i
] != NULL
; ++i
)
1849 len
+= strlen(argv
[i
]) + 1;
1853 if((gp
= _group_fetch(GT_COMMANDALIAS
, ccp
, len
)) == NULL
){
1854 n_err(_("Failed to create storage for commandalias: %s\n"),
1855 n_shexp_quote_cp(ccp
, FAL0
));
1858 struct a_nag_cmd_alias
*ncap
;
1860 GP_TO_SUBCLASS(ncap
, gp
);
1861 GP_TO_SUBCLASS(cp
, gp
);
1863 ncap
->ca_expand
.s
= cp
;
1864 ncap
->ca_expand
.l
= len
- 1;
1866 for(len
= 0; (ccp
= *argv
++) != NULL
;)
1867 if((i
= strlen(ccp
)) > 0){
1882 c_uncommandalias(void *vp
){
1890 do if(!_group_del(GT_COMMANDALIAS
, *argv
)){
1891 n_err(_("No such `commandalias': %s\n"), n_shexp_quote_cp(*argv
, FAL0
));
1893 }while(*++argv
!= NULL
);
1899 n_commandalias_exists(char const *name
, struct str
const **expansion_or_null
){
1903 if((gp
= _group_find(GT_COMMANDALIAS
, name
)) != NULL
){
1906 if(expansion_or_null
!= NULL
){
1907 struct a_nag_cmd_alias
*ncap
;
1909 GP_TO_SUBCLASS(ncap
, gp
);
1910 *expansion_or_null
= &ncap
->ca_expand
;
1919 n_alias_is_valid_name(char const *name
){
1925 for(rv
= TRU1
, cp
= name
++; (c
= *cp
++) != '\0';)
1926 /* User names, plus things explicitly mentioned in Postfix aliases(5).
1927 * As an extension, allow period: [[:alnum:]_#:@.-]+$? */
1928 if(!alnumchar(c
) && c
!= '_' && c
!= '-' &&
1929 c
!= '#' && c
!= ':' && c
!= '@' &&
1931 if(c
== '$' && cp
!= name
&& *cp
== '\0')
1951 n_UNINIT(ecp
, NULL
);
1954 _group_print_all(GT_ALIAS
);
1955 else if (!n_alias_is_valid_name(*argv
)) {
1956 ecp
= N_("Not a valid alias name: %s\n");
1958 } else if (argv
[1] == NULL
) {
1959 if ((gp
= _group_find(GT_ALIAS
, *argv
)) != NULL
)
1960 _group_print(gp
, n_stdout
);
1962 ecp
= N_("No such alias: %s\n");
1965 } else if ((gp
= _group_fetch(GT_ALIAS
, *argv
, 0)) == NULL
) {
1966 ecp
= N_("Failed to create alias storage for: %s\n");
1968 n_err(V_(ecp
), n_shexp_quote_cp(*argv
, FAL0
));
1971 struct grp_names
*gnp_tail
, *gnp
;
1972 struct grp_names_head
*gnhp
;
1974 GP_TO_SUBCLASS(gnhp
, gp
);
1976 if((gnp_tail
= gnhp
->gnh_head
) != NULL
)
1977 while((gnp
= gnp_tail
->gn_next
) != NULL
)
1980 for(++argv
; *argv
!= NULL
; ++argv
){
1983 i
= strlen(*argv
) +1;
1984 gnp
= smalloc(n_VSTRUCT_SIZEOF(struct grp_names
, gn_id
) + i
);
1985 if(gnp_tail
!= NULL
)
1986 gnp_tail
->gn_next
= gnp
;
1988 gnhp
->gnh_head
= gnp
;
1990 gnp
->gn_next
= NULL
;
1991 memcpy(gnp
->gn_id
, *argv
, i
);
2005 do if (!_group_del(GT_ALIAS
, *argv
)) {
2006 n_err(_("No such alias: %s\n"), *argv
);
2008 } while (*++argv
!= NULL
);
2019 rv
= _mlmux(GT_MLIST
, v
);
2030 rv
= _unmlmux(GT_MLIST
, v
);
2036 c_mlsubscribe(void *v
)
2041 rv
= _mlmux(GT_MLIST
| GT_SUBSCRIBE
, v
);
2047 c_unmlsubscribe(void *v
)
2052 rv
= _unmlmux(GT_MLIST
| GT_SUBSCRIBE
, v
);
2058 is_mlist(char const *name
, bool_t subscribed_only
)
2062 struct grp_regex
**lpp
, *grp
;
2065 enum mlist_state rv
;
2068 gp
= _group_find(GT_MLIST
, name
);
2069 rv
= (gp
!= NULL
) ? MLIST_KNOWN
: MLIST_OTHER
;
2070 if (rv
== MLIST_KNOWN
) {
2071 if (gp
->g_type
& GT_SUBSCRIBE
)
2072 rv
= MLIST_SUBSCRIBED
;
2073 else if (subscribed_only
)
2075 /* Of course, if that is a regular expression it doesn't mean a thing */
2077 if (gp
->g_type
& GT_REGEX
)
2084 /* Not in the hashmap (as something matchable), walk the lists */
2087 lpp
= &_mlsub_regex
;
2089 if ((grp
= *lpp
) != NULL
) {
2090 do if (regexec(&grp
->gr_regex
, name
, 0,NULL
, 0) != REG_NOMATCH
) {
2091 /* Relink as the head of this list if the hit count of this group is
2092 * >= 25% of the average hit count */
2095 i
= ++_mlsub_hits
/ _mlsub_size
;
2097 i
= ++_mlist_hits
/ _mlist_size
;
2100 if (++grp
->gr_hits
>= i
&& *lpp
!= grp
&& grp
->gr_next
!= grp
) {
2101 grp
->gr_last
->gr_next
= grp
->gr_next
;
2102 grp
->gr_next
->gr_last
= grp
->gr_last
;
2103 (grp
->gr_last
= (*lpp
)->gr_last
)->gr_next
= grp
;
2104 (grp
->gr_next
= *lpp
)->gr_last
= grp
;
2107 rv
= !re2
? MLIST_SUBSCRIBED
: MLIST_KNOWN
;
2109 } while ((grp
= grp
->gr_next
) != *lpp
);
2111 if (!re2
&& !subscribed_only
) {
2113 lpp
= &_mlist_regex
;
2116 assert(rv
== MLIST_OTHER
);
2136 _group_print_all(GT_SHORTCUT
);
2137 else if(argv
[1] == NULL
){
2138 if((gp
= _group_find(GT_SHORTCUT
, *argv
)) != NULL
)
2139 _group_print(gp
, n_stdout
);
2141 n_err(_("No such shortcut: %s\n"), n_shexp_quote_cp(*argv
, FAL0
));
2144 }else for (; *argv
!= NULL
; argv
+= 2) {
2145 /* Because one hardly ever redefines, anything is stored in one chunk */
2149 if (argv
[1] == NULL
) {
2150 n_err(_("Synopsis: shortcut: <shortcut> <expansion>\n"));
2154 if (_group_find(GT_SHORTCUT
, *argv
) != NULL
)
2155 _group_del(GT_SHORTCUT
, *argv
);
2157 l
= strlen(argv
[1]) +1;
2158 if ((gp
= _group_fetch(GT_SHORTCUT
, *argv
, l
)) == NULL
) {
2159 n_err(_("Failed to create storage for shortcut: %s\n"),
2160 n_shexp_quote_cp(*argv
, FAL0
));
2163 GP_TO_SUBCLASS(cp
, gp
);
2164 memcpy(cp
, argv
[1], l
);
2172 c_unshortcut(void *v
)
2178 do if (!_group_del(GT_SHORTCUT
, *argv
)) {
2179 n_err(_("No such shortcut: %s\n"), *argv
);
2181 } while (*++argv
!= NULL
);
2187 shortcut_expand(char const *str
){
2191 if((gp
= _group_find(GT_SHORTCUT
, str
)) != NULL
)
2192 GP_TO_SUBCLASS(str
, gp
);
2200 c_charsetalias(void *vp
){
2210 _group_print_all(GT_CHARSETALIAS
);
2211 else if(argv
[1] == NULL
){
2212 if((gp
= _group_find(GT_CHARSETALIAS
, *argv
)) != NULL
)
2213 _group_print(gp
, n_stdout
);
2215 n_err(_("No such charsetalias: %s\n"), n_shexp_quote_cp(*argv
, FAL0
));
2218 }else for(; *argv
!= NULL
; argv
+= 2){
2219 /* Because one hardly ever redefines, anything is stored in one chunk */
2224 if(argv
[1] == NULL
){
2225 n_err(_("Synopsis: charsetalias: <charset> <charset-alias>\n"));
2230 /* Delete the old one, if any; don't get fooled to remove them all */
2232 if(ccp
[0] != '*' || ccp
[1] != '\0')
2233 _group_del(GT_CHARSETALIAS
, ccp
);
2235 l
= strlen(argv
[1]) +1;
2236 if ((gp
= _group_fetch(GT_CHARSETALIAS
, ccp
, l
)) == NULL
) {
2237 n_err(_("Failed to create storage for charsetalias: %s\n"),
2238 n_shexp_quote_cp(ccp
, FAL0
));
2241 GP_TO_SUBCLASS(cp
, gp
);
2242 for(ccp
= argv
[1]; (c
= *ccp
++) != '\0';)
2243 *cp
++ = lowerconv(c
);
2252 c_uncharsetalias(void *vp
){
2260 do if(!_group_del(GT_CHARSETALIAS
, *argv
)){
2261 n_err(_("No such `charsetalias': %s\n"), n_shexp_quote_cp(*argv
, FAL0
));
2263 }while(*++argv
!= NULL
);
2269 n_charsetalias_expand(char const *cp
){
2272 char const *cp_orig
;
2277 for(i
= 0; (gp
= _group_find(GT_CHARSETALIAS
, cp
)) != NULL
;){
2278 GP_TO_SUBCLASS(cp
, gp
);
2279 if(++i
== 8) /* XXX Magic (same as for `ghost' expansion) */
2290 c_filetype(void *vp
){ /* TODO support automatic chains: .tar.gz -> .gz + .tar */
2292 char **argv
; /* TODO While there: let ! prefix mean: direct execlp(2) */
2300 _group_print_all(GT_FILETYPE
);
2301 else if(argv
[1] == NULL
){
2302 if((gp
= _group_find(GT_FILETYPE
, *argv
)) != NULL
)
2303 _group_print(gp
, n_stdout
);
2305 n_err(_("No such filetype: %s\n"), n_shexp_quote_cp(*argv
, FAL0
));
2308 }else for(; *argv
!= NULL
; argv
+= 3){
2309 /* Because one hardly ever redefines, anything is stored in one chunk */
2314 if(argv
[1] == NULL
|| argv
[2] == NULL
){
2315 n_err(_("Synopsis: filetype: <extension> <load-cmd> <save-cmd>\n"));
2320 /* Delete the old one, if any; don't get fooled to remove them all */
2322 if(ccp
[0] != '*' || ccp
[1] != '\0')
2323 _group_del(GT_FILETYPE
, ccp
);
2325 /* Lowercase it all (for display purposes) */
2328 while((c
= *cp
) != '\0')
2329 *cp
++ = lowerconv(c
);
2331 llc
= strlen(argv
[1]) +1;
2332 lsc
= strlen(argv
[2]) +1;
2333 if(UIZ_MAX
- llc
<= lsc
)
2336 if((gp
= _group_fetch(GT_FILETYPE
, ccp
, llc
+ lsc
)) == NULL
){
2338 n_err(_("Failed to create storage for filetype: %s\n"),
2339 n_shexp_quote_cp(argv
[0], FAL0
));
2342 struct a_nag_file_type
*nftp
;
2344 GP_TO_SUBCLASS(nftp
, gp
);
2345 GP_TO_SUBCLASS(cp
, gp
);
2347 memcpy(nftp
->nft_load
.s
= cp
, argv
[1], llc
);
2349 nftp
->nft_load
.l
= --llc
;
2350 memcpy(nftp
->nft_save
.s
= cp
, argv
[2], lsc
);
2352 nftp
->nft_save
.l
= --lsc
;
2360 c_unfiletype(void *vp
){
2368 do if(!_group_del(GT_FILETYPE
, *argv
)){
2369 n_err(_("No such `filetype': %s\n"), n_shexp_quote_cp(*argv
, FAL0
));
2371 }while(*++argv
!= NULL
);
2377 n_filetype_trial(struct n_file_type
*res_or_null
, char const *file
){
2379 struct group_lookup gl
;
2380 struct n_string s
, *sp
;
2381 struct group
const *gp
;
2385 sp
= n_string_creat_auto(&s
);
2386 sp
= n_string_assign_cp(sp
, file
);
2387 sp
= n_string_push_c(sp
, '.');
2390 for(gp
= _group_go_first(GT_FILETYPE
, &gl
); gp
!= NULL
;
2391 gp
= _group_go_next(&gl
)){
2392 sp
= n_string_trunc(sp
, l
);
2393 sp
= n_string_push_buf(sp
, gp
->g_id
,
2394 gp
->g_subclass_off
- gp
->g_id_len_sub
);
2396 if(!stat(n_string_cp(sp
), &stb
) && S_ISREG(stb
.st_mode
)){
2397 if(res_or_null
!= NULL
){
2398 struct a_nag_file_type
*nftp
;
2400 GP_TO_SUBCLASS(nftp
, gp
);
2401 res_or_null
->ft_ext_dat
= gp
->g_id
;
2402 res_or_null
->ft_ext_len
= gp
->g_subclass_off
- gp
->g_id_len_sub
;
2403 res_or_null
->ft_load_dat
= nftp
->nft_load
.s
;
2404 res_or_null
->ft_load_len
= nftp
->nft_load
.l
;
2405 res_or_null
->ft_save_dat
= nftp
->nft_save
.s
;
2406 res_or_null
->ft_save_len
= nftp
->nft_save
.l
;
2408 goto jleave
; /* TODO after v15 legacy drop: break; */
2412 /* TODO v15 legacy code: automatic file hooks for .{bz2,gz,xz},
2413 * TODO but NOT supporting *file-hook-{load,save}-EXTENSION* */
2414 gp
= (struct group
*)0x1;
2416 sp
= n_string_trunc(sp
, l
);
2417 sp
= n_string_push_buf(sp
, a_nag_OBSOLETE_xz
.ft_ext_dat
,
2418 a_nag_OBSOLETE_xz
.ft_ext_len
);
2419 if(!stat(n_string_cp(sp
), &stb
) && S_ISREG(stb
.st_mode
)){
2420 n_OBSOLETE(".xz support will vanish, please use the `filetype' command");
2421 if(res_or_null
!= NULL
)
2422 *res_or_null
= a_nag_OBSOLETE_xz
;
2426 sp
= n_string_trunc(sp
, l
);
2427 sp
= n_string_push_buf(sp
, a_nag_OBSOLETE_gz
.ft_ext_dat
,
2428 a_nag_OBSOLETE_gz
.ft_ext_len
);
2429 if(!stat(n_string_cp(sp
), &stb
) && S_ISREG(stb
.st_mode
)){
2430 n_OBSOLETE(".gz support will vanish, please use the `filetype' command");
2431 if(res_or_null
!= NULL
)
2432 *res_or_null
= a_nag_OBSOLETE_gz
;
2436 sp
= n_string_trunc(sp
, l
);
2437 sp
= n_string_push_buf(sp
, a_nag_OBSOLETE_bz2
.ft_ext_dat
,
2438 a_nag_OBSOLETE_bz2
.ft_ext_len
);
2439 if(!stat(n_string_cp(sp
), &stb
) && S_ISREG(stb
.st_mode
)){
2440 n_OBSOLETE(".bz2 support will vanish, please use the `filetype' command");
2441 if(res_or_null
!= NULL
)
2442 *res_or_null
= a_nag_OBSOLETE_bz2
;
2450 return (gp
!= NULL
);
2454 n_filetype_exists(struct n_file_type
*res_or_null
, char const *file
){
2455 char const *ext
, *lext
;
2458 if((ext
= strrchr(file
, '/')) != NULL
)
2461 for(lext
= NULL
; (ext
= strchr(file
, '.')) != NULL
; lext
= file
= ext
){
2462 struct group
const *gp
;
2464 if((gp
= _group_find(GT_FILETYPE
, ++ext
)) != NULL
){
2466 if(res_or_null
!= NULL
){
2467 struct a_nag_file_type
*nftp
;
2469 GP_TO_SUBCLASS(nftp
, gp
);
2470 res_or_null
->ft_ext_dat
= gp
->g_id
;
2471 res_or_null
->ft_ext_len
= gp
->g_subclass_off
- gp
->g_id_len_sub
;
2472 res_or_null
->ft_load_dat
= nftp
->nft_load
.s
;
2473 res_or_null
->ft_load_len
= nftp
->nft_load
.l
;
2474 res_or_null
->ft_save_dat
= nftp
->nft_save
.s
;
2475 res_or_null
->ft_save_len
= nftp
->nft_save
.l
;
2477 goto jleave
; /* TODO after v15 legacy drop: break; */
2481 /* TODO v15 legacy code: automatic file hooks for .{bz2,gz,xz},
2482 * TODO as well as supporting *file-hook-{load,save}-EXTENSION* */
2486 if(!asccasecmp(lext
, "xz")){
2487 n_OBSOLETE(".xz support will vanish, please use the `filetype' command");
2488 if(res_or_null
!= NULL
)
2489 *res_or_null
= a_nag_OBSOLETE_xz
;
2491 }else if(!asccasecmp(lext
, "gz")){
2492 n_OBSOLETE(".gz support will vanish, please use the `filetype' command");
2493 if(res_or_null
!= NULL
)
2494 *res_or_null
= a_nag_OBSOLETE_gz
;
2496 }else if(!asccasecmp(lext
, "bz2")){
2497 n_OBSOLETE(".bz2 support will vanish, please use the `filetype' command");
2498 if(res_or_null
!= NULL
)
2499 *res_or_null
= a_nag_OBSOLETE_bz2
;
2502 char const *cload
, *csave
;
2507 #define a_X1 "file-hook-load-"
2509 #define a_X2 "file-hook-save-"
2511 vbuf
= n_lofi_alloc(l
+ n_MAX(sizeof(a_X1
), sizeof(a_X2
)));
2513 memcpy(vbuf
, a_X1
, sizeof(a_X1
) -1);
2514 memcpy(&vbuf
[sizeof(a_X1
) -1], lext
, l
);
2515 vbuf
[sizeof(a_X1
) -1 + l
] = '\0';
2516 cload
= n_var_vlook(vbuf
, FAL0
);
2518 memcpy(vbuf
, a_X2
, sizeof(a_X2
) -1);
2519 memcpy(&vbuf
[sizeof(a_X2
) -1], lext
, l
);
2520 vbuf
[sizeof(a_X2
) -1 + l
] = '\0';
2521 csave
= n_var_vlook(vbuf
, FAL0
);
2527 if((csave
!= NULL
) | (cload
!= NULL
)){
2528 n_OBSOLETE("*file-hook-{load,save}-EXTENSION* will vanish, "
2529 "please use the `filetype' command");
2531 if(((csave
!= NULL
) ^ (cload
!= NULL
)) == 0){
2532 if(res_or_null
!= NULL
){
2533 res_or_null
->ft_ext_dat
= lext
;
2534 res_or_null
->ft_ext_len
= l
;
2535 res_or_null
->ft_load_dat
= cload
;
2536 res_or_null
->ft_load_len
= strlen(cload
);
2537 res_or_null
->ft_save_dat
= csave
;
2538 res_or_null
->ft_save_len
= strlen(csave
);
2542 n_alert(_("Incomplete *file-hook-{load,save}-EXTENSION* for: .%s"),
2551 return (lext
!= NULL
);