nail.1: *ssl-ca-dir* requires special preparation (Olav Mørkrid)
[s-mailx.git] / nam-a-grp.c
blob0e6eaee82d95abe385f1429bbc3d4012ac79393a
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 */
8 /*
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
14 * are met:
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
34 * SUCH DAMAGE.
36 #undef n_FILE
37 #define n_FILE nam_a_grp
39 #ifndef HAVE_AMALGAMATION
40 # include "nail.h"
41 #endif
43 enum a_nag_type{
44 /* Main types */
45 a_NAG_T_ALTERNATES = 1,
46 a_NAG_T_COMMANDALIAS,
47 a_NAG_T_ALIAS,
48 a_NAG_T_MLIST,
49 a_NAG_T_SHORTCUT,
50 a_NAG_T_CHARSETALIAS,
51 a_NAG_T_FILETYPE,
52 a_NAG_T_MASK = 0x1F,
54 /* Subtype bits and flags */
55 a_NAG_T_SUBSCRIBE = 1u<<6,
56 a_NAG_T_REGEX = 1u<<7,
58 /* Extended type mask to be able to reflect what we really have; i.e., mlist
59 * can have a_NAG_T_REGEX if they are subscribed or not, but `mlsubscribe'
60 * should print out only a_NAG_T_MLIST which have the a_NAG_T_SUBSCRIBE
61 * attribute set */
62 a_NAG_T_PRINT_MASK = a_NAG_T_MASK | a_NAG_T_SUBSCRIBE
64 n_CTA(a_NAG_T_MASK >= a_NAG_T_FILETYPE, "Mask does not cover necessary bits");
66 struct a_nag_group{
67 struct a_nag_group *ng_next;
68 ui32_t ng_subclass_off; /* of "subclass" in .ng_id (if any) */
69 ui16_t ng_id_len_sub; /* length of .ng_id: _subclass_off - this */
70 ui8_t ng_type; /* enum a_nag_type */
71 /* Identifying name, of variable size. Dependent on actual "subtype" more
72 * data follows thereafter, but note this is always used (i.e., for regular
73 * expression entries this is still set to the plain string) */
74 char ng_id[n_VFIELD_SIZE(1)];
76 #define a_NAG_GP_TO_SUBCLASS(X,G) \
77 do{\
78 union a_nag_group_subclass {void *gs_vp; char *gs_cp;} a__gs__;\
79 a__gs__.gs_cp = &((char*)n_UNCONST(G))[(G)->ng_subclass_off];\
80 (X) = a__gs__.gs_vp;\
81 }while(0)
83 struct a_nag_grp_names_head{
84 struct a_nag_grp_names *ngnh_head;
87 struct a_nag_grp_names{
88 struct a_nag_grp_names *ngn_next;
89 char ngn_id[n_VFIELD_SIZE(0)];
92 #ifdef HAVE_REGEX
93 struct a_nag_grp_regex{
94 struct a_nag_grp_regex *ngr_last;
95 struct a_nag_grp_regex *ngr_next;
96 struct a_nag_group *ngr_mygroup; /* xxx because lists use grp_regex*! ?? */
97 size_t ngr_hits; /* Number of times this group matched */
98 regex_t ngr_regex;
100 #endif
102 struct a_nag_cmd_alias{
103 struct str nca_expand;
106 struct a_nag_file_type{
107 struct str nft_load;
108 struct str nft_save;
111 struct a_nag_group_lookup{
112 struct a_nag_group **ngl_htable;
113 struct a_nag_group **ngl_slot;
114 struct a_nag_group *ngl_slot_last;
115 struct a_nag_group *ngl_group;
118 static struct n_file_type const a_nag_OBSOLETE_xz = { /* TODO v15 compat */
119 "xz", 2, "xz -cd", sizeof("xz -cd") -1, "xz -cz", sizeof("xz -cz") -1
120 }, a_nag_OBSOLETE_gz = {
121 "gz", 2, "gzip -cd", sizeof("gzip -cd") -1, "gzip -cz", sizeof("gzip -cz") -1
122 }, a_nag_OBSOLETE_bz2 = {
123 "bz2", 3, "bzip2 -cd", sizeof("bzip2 -cd") -1,
124 "bzip2 -cz", sizeof("bzip2 -cz") -1
127 /* `alternates' */
128 static struct a_nag_group *a_nag_alternates_heads[HSHSIZE];
130 /* `commandalias' */
131 static struct a_nag_group *a_nag_commandalias_heads[HSHSIZE];
133 /* `alias' */
134 static struct a_nag_group *a_nag_alias_heads[HSHSIZE];
136 /* `mlist', `mlsubscribe'. Anything is stored in the hashmap.. */
137 static struct a_nag_group *a_nag_mlist_heads[HSHSIZE];
139 /* ..but entries which have a_NAG_T_REGEX set are false lookups and will really
140 * be accessed via sequential lists instead, which are type-specific for better
141 * performance, but also to make it possible to have ".*@xy.org" as a mlist
142 * and "(one|two)@xy.org" as a mlsubscription.
143 * These lists use a bit of QOS optimization in that a matching group will
144 * become relinked as the new list head if its hit count is
145 * (>= ((xy_hits / _xy_size) >> 2))
146 * Note that the hit counts only count currently linked in nodes.. */
147 #ifdef HAVE_REGEX
148 static struct a_nag_grp_regex *a_nag_mlist_regex;
149 static struct a_nag_grp_regex *a_nag_mlsub_regex;
150 static size_t a_nag_mlist_size;
151 static size_t a_nag_mlist_hits;
152 static size_t a_nag_mlsub_size;
153 static size_t a_nag_mlsub_hits;
154 #endif
156 /* `shortcut' */
157 static struct a_nag_group *a_nag_shortcut_heads[HSHSIZE];
159 /* `charsetalias' */
160 static struct a_nag_group *a_nag_charsetalias_heads[HSHSIZE];
162 /* `filetype' */
163 static struct a_nag_group *a_nag_filetype_heads[HSHSIZE];
165 /* Same name, while taking care for *allnet*? */
166 static bool_t a_nag_is_same_name(char const *n1, char const *n2);
168 /* Mark all nodes with the given name */
169 static struct name *a_nag_namelist_mark_name(struct name *np, char const *name);
171 /* Grab a single name (liberal name) */
172 static char const *a_nag_yankname(char const *ap, char *wbuf,
173 char const *separators, int keepcomms);
175 /* Extraction multiplexer that splits an input line to names */
176 static struct name *a_nag_extract1(char const *line, enum gfield ntype,
177 char const *separators, bool_t keepcomms);
179 /* Recursively expand a alias name. Limit expansion to some fixed level.
180 * Direct recursion is not expanded for convenience */
181 static struct name *a_nag_gexpand(size_t level, struct name *nlist,
182 struct a_nag_group *ngp, bool_t metoo, int ntype);
184 /* elide() helper */
185 static int a_nag_elide_qsort(void const *s1, void const *s2);
187 /* Lookup a group, return it or NULL, fill in glp anyway */
188 static struct a_nag_group *a_nag_group_lookup(enum a_nag_type nt,
189 struct a_nag_group_lookup *nglp, char const *id);
191 /* Easier-to-use wrapper around _group_lookup() */
192 static struct a_nag_group *a_nag_group_find(enum a_nag_type nt, char const *id);
194 /* Iteration: go to the first group, which also inits the iterator. A valid
195 * iterator can be stepped via _next(). A NULL return means no (more) groups
196 * to be iterated exist, in which case only nglp->ngl_group is set (NULL) */
197 static struct a_nag_group *a_nag_group_go_first(enum a_nag_type nt,
198 struct a_nag_group_lookup *nglp);
199 static struct a_nag_group *a_nag_group_go_next(struct a_nag_group_lookup *nglp);
201 /* Fetch the group id, create it as necessary, fail with NULL if impossible */
202 static struct a_nag_group *a_nag_group_fetch(enum a_nag_type nt, char const *id,
203 size_t addsz);
205 /* "Intelligent" delete which handles a "*" id, too;
206 * returns a true boolean if a group was deleted, and always succeeds for "*" */
207 static bool_t a_nag_group_del(enum a_nag_type nt, char const *id);
209 static struct a_nag_group *a_nag__group_del(struct a_nag_group_lookup *nglp);
210 static void a_nag__names_del(struct a_nag_group *ngp);
212 /* Print all groups of the given type, alphasorted, or store in `vput' varname:
213 * only in this mode it can return failure */
214 static bool_t a_nag_group_print_all(enum a_nag_type nt,
215 char const *varname);
217 static int a_nag__group_print_qsorter(void const *a, void const *b);
219 /* Really print a group, actually. Or store in vputsp, if set.
220 * Return number of written lines */
221 static size_t a_nag_group_print(struct a_nag_group const *ngp, FILE *fo,
222 struct n_string *vputsp);
224 /* Multiplexers for list and subscribe commands */
225 static int a_nag_mlmux(enum a_nag_type nt, char const **argv);
226 static int a_nag_unmlmux(enum a_nag_type nt, char const **argv);
228 /* Relinkers for the sequential match lists */
229 #ifdef HAVE_REGEX
230 static void a_nag_mlmux_linkin(struct a_nag_group *ngp);
231 static void a_nag_mlmux_linkout(struct a_nag_group *ngp);
232 # define a_NAG_MLMUX_LINKIN(GP) \
233 do if((GP)->ng_type & a_NAG_T_REGEX) a_nag_mlmux_linkin(GP); while(0)
234 # define a_NAG_MLMUX_LINKOUT(GP) \
235 do if((GP)->ng_type & a_NAG_T_REGEX) a_nag_mlmux_linkout(GP); while(0)
236 #else
237 # define a_NAG_MLMUX_LINKIN(GP)
238 # define a_NAG_MLMUX_LINKOUT(GP)
239 #endif
241 static bool_t
242 a_nag_is_same_name(char const *n1, char const *n2){
243 bool_t rv;
244 char c1, c2, c1r, c2r;
245 NYD2_ENTER;
247 if(ok_blook(allnet)){
248 for(;; ++n1, ++n2){
249 c1 = *n1;
250 c1 = lowerconv(c1);
251 c1r = (c1 == '\0' || c1 == '@');
252 c2 = *n2;
253 c2 = lowerconv(c2);
254 c2r = (c2 == '\0' || c2 == '@');
256 if(c1r || c2r){
257 rv = (c1r == c2r);
258 break;
259 }else if(c1 != c2){
260 rv = FAL0;
261 break;
264 }else
265 rv = !asccasecmp(n1, n2);
266 NYD2_LEAVE;
267 return rv;
270 static struct name *
271 a_nag_namelist_mark_name(struct name *np, char const *name){
272 struct name *p;
273 NYD2_ENTER;
275 for(p = np; p != NULL; p = p->n_flink)
276 if(!(p->n_type & GDEL) && !(p->n_flags & (ui32_t)SI32_MIN) &&
277 a_nag_is_same_name(p->n_name, name))
278 p->n_flags |= (ui32_t)SI32_MIN;
279 NYD2_LEAVE;
280 return np;
283 static char const *
284 a_nag_yankname(char const *ap, char *wbuf, char const *separators,
285 int keepcomms)
287 char const *cp;
288 char *wp, c, inquote, lc, lastsp;
289 NYD_ENTER;
291 *(wp = wbuf) = '\0';
293 /* Skip over intermediate list trash, as in ".org> , <xy@zz.org>" */
294 for (c = *ap; blankchar(c) || c == ','; c = *++ap)
296 if (c == '\0') {
297 cp = NULL;
298 goto jleave;
301 /* Parse a full name: TODO RFC 5322
302 * - Keep everything in quotes, liberal handle *quoted-pair*s therein
303 * - Skip entire (nested) comments
304 * - In non-quote, non-comment, join adjacent space to a single SP
305 * - Understand separators only in non-quote, non-comment context,
306 * and only if not part of a *quoted-pair* (XXX too liberal) */
307 cp = ap;
308 for (inquote = lc = lastsp = 0;; lc = c, ++cp) {
309 c = *cp;
310 if (c == '\0')
311 break;
312 if (c == '\\')
313 goto jwpwc;
314 if (c == '"') {
315 if (lc != '\\')
316 inquote = !inquote;
317 #if 0 /* TODO when doing real RFC 5322 parsers - why have i done this? */
318 else
319 --wp;
320 #endif
321 goto jwpwc;
323 if (inquote || lc == '\\') {
324 jwpwc:
325 *wp++ = c;
326 lastsp = 0;
327 continue;
329 if (c == '(') {
330 ap = cp;
331 cp = skip_comment(cp + 1);
332 if (keepcomms)
333 while (ap < cp)
334 *wp++ = *ap++;
335 --cp;
336 lastsp = 0;
337 continue;
339 if (strchr(separators, c) != NULL)
340 break;
342 lc = lastsp;
343 lastsp = blankchar(c);
344 if (!lastsp || !lc)
345 *wp++ = c;
347 if (blankchar(lc))
348 --wp;
350 *wp = '\0';
351 jleave:
352 NYD_LEAVE;
353 return cp;
356 static struct name *
357 a_nag_extract1(char const *line, enum gfield ntype, char const *separators,
358 bool_t keepcomms)
360 struct name *topp, *np, *t;
361 char const *cp;
362 char *nbuf;
363 NYD_ENTER;
365 topp = NULL;
366 if (line == NULL || *line == '\0')
367 goto jleave;
369 np = NULL;
370 cp = line;
371 nbuf = n_alloc(strlen(line) +1);
372 while ((cp = a_nag_yankname(cp, nbuf, separators, keepcomms)) != NULL) {
373 t = nalloc(nbuf, ntype);
374 if (topp == NULL)
375 topp = t;
376 else
377 np->n_flink = t;
378 t->n_blink = np;
379 np = t;
381 n_free(nbuf);
382 jleave:
383 NYD_LEAVE;
384 return topp;
387 static struct name *
388 a_nag_gexpand(size_t level, struct name *nlist, struct a_nag_group *ngp,
389 bool_t metoo, int ntype){
390 struct a_nag_grp_names *ngnp;
391 struct name *nlist_tail;
392 char const *logname;
393 struct a_nag_grp_names_head *ngnhp;
394 NYD2_ENTER;
396 if(UICMP(z, level++, >, n_ALIAS_MAXEXP)){
397 n_err(_("Expanding alias to depth larger than %d\n"), n_ALIAS_MAXEXP);
398 goto jleave;
401 a_NAG_GP_TO_SUBCLASS(ngnhp, ngp);
402 logname = ok_vlook(LOGNAME);
404 for(ngnp = ngnhp->ngnh_head; ngnp != NULL; ngnp = ngnp->ngn_next){
405 struct a_nag_group *xngp;
406 char *cp;
408 cp = ngnp->ngn_id;
410 if(!strcmp(cp, ngp->ng_id))
411 goto jas_is;
413 if((xngp = a_nag_group_find(a_NAG_T_ALIAS, cp)) != NULL){
414 /* For S-nail(1), the "alias" may *be* the sender in that a name maps
415 * to a full address specification; aliases cannot be empty */
416 struct a_nag_grp_names_head *xngnhp;
418 a_NAG_GP_TO_SUBCLASS(xngnhp, xngp);
420 assert(xngnhp->ngnh_head != NULL);
421 if(metoo || xngnhp->ngnh_head->ngn_next != NULL ||
422 !a_nag_is_same_name(cp, logname))
423 nlist = a_nag_gexpand(level, nlist, xngp, metoo, ntype);
424 continue;
427 /* Here we should allow to expand to itself if only person in alias */
428 jas_is:
429 if(metoo || ngnhp->ngnh_head->ngn_next == NULL ||
430 !a_nag_is_same_name(cp, logname)){
431 struct name *np;
433 np = nalloc(cp, ntype | GFULL);
434 if((nlist_tail = nlist) != NULL){
435 while(nlist_tail->n_flink != NULL)
436 nlist_tail = nlist_tail->n_flink;
437 nlist_tail->n_flink = np;
438 np->n_blink = nlist_tail;
439 }else
440 nlist = np;
443 jleave:
444 NYD2_LEAVE;
445 return nlist;
448 static int
449 a_nag_elide_qsort(void const *s1, void const *s2){
450 struct name const * const *np1, * const *np2;
451 int rv;
452 NYD2_ENTER;
454 np1 = s1;
455 np2 = s2;
456 if(!(rv = asccasecmp((*np1)->n_name, (*np2)->n_name))){
457 n_LCTAV(GTO < GCC && GCC < GBCC);
458 rv = ((*np1)->n_type & (GTO | GCC | GBCC)) -
459 ((*np2)->n_type & (GTO | GCC | GBCC));
461 NYD2_LEAVE;
462 return rv;
465 static struct a_nag_group *
466 a_nag_group_lookup(enum a_nag_type nt, struct a_nag_group_lookup *nglp,
467 char const *id){
468 char c1;
469 struct a_nag_group *lngp, *ngp;
470 bool_t icase;
471 NYD2_ENTER;
473 icase = FAL0;
475 /* C99 */{
476 ui32_t h;
477 struct a_nag_group **ngpa;
479 switch((nt &= a_NAG_T_MASK)){
480 case a_NAG_T_ALTERNATES:
481 ngpa = a_nag_alternates_heads;
482 icase = TRU1;
483 break;
484 default:
485 case a_NAG_T_COMMANDALIAS:
486 ngpa = a_nag_commandalias_heads;
487 break;
488 case a_NAG_T_ALIAS:
489 ngpa = a_nag_alias_heads;
490 break;
491 case a_NAG_T_MLIST:
492 ngpa = a_nag_mlist_heads;
493 icase = TRU1;
494 break;
495 case a_NAG_T_SHORTCUT:
496 ngpa = a_nag_shortcut_heads;
497 break;
498 case a_NAG_T_CHARSETALIAS:
499 ngpa = a_nag_charsetalias_heads;
500 icase = TRU1;
501 break;
502 case a_NAG_T_FILETYPE:
503 ngpa = a_nag_filetype_heads;
504 icase = TRU1;
505 break;
508 nglp->ngl_htable = ngpa;
509 h = icase ? n_torek_ihash(id) : n_torek_hash(id);
510 ngp = *(nglp->ngl_slot = &ngpa[h % HSHSIZE]);
513 lngp = NULL;
514 c1 = *id++;
516 if(icase){
517 c1 = lowerconv(c1);
518 for(; ngp != NULL; lngp = ngp, ngp = ngp->ng_next)
519 if((ngp->ng_type & a_NAG_T_MASK) == nt && *ngp->ng_id == c1 &&
520 !asccasecmp(&ngp->ng_id[1], id))
521 break;
522 }else{
523 for(; ngp != NULL; lngp = ngp, ngp = ngp->ng_next)
524 if((ngp->ng_type & a_NAG_T_MASK) == nt && *ngp->ng_id == c1 &&
525 !strcmp(&ngp->ng_id[1], id))
526 break;
529 nglp->ngl_slot_last = lngp;
530 nglp->ngl_group = ngp;
531 NYD2_LEAVE;
532 return ngp;
535 static struct a_nag_group *
536 a_nag_group_find(enum a_nag_type nt, char const *id){
537 struct a_nag_group_lookup ngl;
538 struct a_nag_group *ngp;
539 NYD2_ENTER;
541 ngp = a_nag_group_lookup(nt, &ngl, id);
542 NYD2_LEAVE;
543 return ngp;
546 static struct a_nag_group *
547 a_nag_group_go_first(enum a_nag_type nt, struct a_nag_group_lookup *nglp){
548 size_t i;
549 struct a_nag_group **ngpa, *ngp;
550 NYD2_ENTER;
552 switch((nt &= a_NAG_T_MASK)){
553 case a_NAG_T_ALTERNATES:
554 ngpa = a_nag_alternates_heads;
555 break;
556 default:
557 case a_NAG_T_COMMANDALIAS:
558 ngpa = a_nag_commandalias_heads;
559 break;
560 case a_NAG_T_ALIAS:
561 ngpa = a_nag_alias_heads;
562 break;
563 case a_NAG_T_MLIST:
564 ngpa = a_nag_mlist_heads;
565 break;
566 case a_NAG_T_SHORTCUT:
567 ngpa = a_nag_shortcut_heads;
568 break;
569 case a_NAG_T_CHARSETALIAS:
570 ngpa = a_nag_charsetalias_heads;
571 break;
572 case a_NAG_T_FILETYPE:
573 ngpa = a_nag_filetype_heads;
574 break;
577 nglp->ngl_htable = ngpa;
579 for(i = 0; i < HSHSIZE; ++ngpa, ++i)
580 if((ngp = *ngpa) != NULL){
581 nglp->ngl_slot = ngpa;
582 nglp->ngl_group = ngp;
583 goto jleave;
586 nglp->ngl_group = ngp = NULL;
587 jleave:
588 nglp->ngl_slot_last = NULL;
589 NYD2_LEAVE;
590 return ngp;
593 static struct a_nag_group *
594 a_nag_group_go_next(struct a_nag_group_lookup *nglp){
595 struct a_nag_group *ngp, **ngpa;
596 NYD2_ENTER;
598 if((ngp = nglp->ngl_group->ng_next) != NULL)
599 nglp->ngl_slot_last = nglp->ngl_group;
600 else{
601 nglp->ngl_slot_last = NULL;
602 for(ngpa = &nglp->ngl_htable[HSHSIZE]; ++nglp->ngl_slot < ngpa;)
603 if((ngp = *nglp->ngl_slot) != NULL)
604 break;
606 nglp->ngl_group = ngp;
607 NYD2_LEAVE;
608 return ngp;
611 static struct a_nag_group *
612 a_nag_group_fetch(enum a_nag_type nt, char const *id, size_t addsz){
613 struct a_nag_group_lookup ngl;
614 struct a_nag_group *ngp;
615 size_t l, i;
616 NYD2_ENTER;
618 if((ngp = a_nag_group_lookup(nt, &ngl, id)) != NULL)
619 goto jleave;
621 l = strlen(id) +1;
622 if(UIZ_MAX - n_ALIGN(l) <=
623 n_ALIGN(n_VSTRUCT_SIZEOF(struct a_nag_group, ng_id)))
624 goto jleave;
626 i = n_ALIGN(n_VSTRUCT_SIZEOF(struct a_nag_group, ng_id) + l);
627 switch(nt & a_NAG_T_MASK){
628 case a_NAG_T_ALTERNATES:
629 case a_NAG_T_SHORTCUT:
630 case a_NAG_T_CHARSETALIAS:
631 default:
632 break;
633 case a_NAG_T_COMMANDALIAS:
634 addsz += sizeof(struct a_nag_cmd_alias);
635 break;
636 case a_NAG_T_ALIAS:
637 addsz += sizeof(struct a_nag_grp_names_head);
638 break;
639 case a_NAG_T_MLIST:
640 #ifdef HAVE_REGEX
641 if(n_is_maybe_regex(id)){
642 addsz = sizeof(struct a_nag_grp_regex);
643 nt |= a_NAG_T_REGEX;
645 #endif
646 break;
647 case a_NAG_T_FILETYPE:
648 addsz += sizeof(struct a_nag_file_type);
649 break;
651 if(UIZ_MAX - i < addsz || UI32_MAX <= i || UI16_MAX < i - l)
652 goto jleave;
654 ngp = n_alloc(i + addsz);
655 memcpy(ngp->ng_id, id, l);
656 ngp->ng_subclass_off = (ui32_t)i;
657 ngp->ng_id_len_sub = (ui16_t)(i - --l);
658 ngp->ng_type = nt;
659 switch(nt & a_NAG_T_MASK){
660 case a_NAG_T_ALTERNATES:
661 case a_NAG_T_MLIST:
662 case a_NAG_T_CHARSETALIAS:
663 case a_NAG_T_FILETYPE:{
664 char *cp, c;
666 for(cp = ngp->ng_id; (c = *cp) != '\0'; ++cp)
667 *cp = lowerconv(c);
668 }break;
669 default:
670 break;
673 if((nt & a_NAG_T_MASK) == a_NAG_T_ALIAS){
674 struct a_nag_grp_names_head *ngnhp;
676 a_NAG_GP_TO_SUBCLASS(ngnhp, ngp);
677 ngnhp->ngnh_head = NULL;
679 #ifdef HAVE_REGEX
680 else if(nt & a_NAG_T_REGEX){
681 int s;
682 struct a_nag_grp_regex *ngrp;
684 a_NAG_GP_TO_SUBCLASS(ngrp, ngp);
686 if((s = regcomp(&ngrp->ngr_regex, id,
687 REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0){
688 n_err(_("Invalid regular expression: %s: %s\n"),
689 n_shexp_quote_cp(id, FAL0), n_regex_err_to_doc(NULL, s));
690 n_free(ngp);
691 ngp = NULL;
692 goto jleave;
694 ngrp->ngr_mygroup = ngp;
695 a_nag_mlmux_linkin(ngp);
697 #endif /* HAVE_REGEX */
699 ngp->ng_next = *ngl.ngl_slot;
700 *ngl.ngl_slot = ngp;
701 jleave:
702 NYD2_LEAVE;
703 return ngp;
706 static bool_t
707 a_nag_group_del(enum a_nag_type nt, char const *id){
708 struct a_nag_group_lookup ngl;
709 struct a_nag_group *ngp;
710 enum a_nag_type xnt;
711 NYD2_ENTER;
713 xnt = nt & a_NAG_T_MASK;
715 /* Delete 'em all? */
716 if(id[0] == '*' && id[1] == '\0'){
717 for(ngp = a_nag_group_go_first(nt, &ngl); ngp != NULL;)
718 ngp = ((ngp->ng_type & a_NAG_T_MASK) == xnt) ? a_nag__group_del(&ngl)
719 : a_nag_group_go_next(&ngl);
720 ngp = (struct a_nag_group*)TRU1;
721 }else if((ngp = a_nag_group_lookup(nt, &ngl, id)) != NULL){
722 if(ngp->ng_type & xnt)
723 a_nag__group_del(&ngl);
724 else
725 ngp = NULL;
727 NYD2_LEAVE;
728 return (ngp != NULL);
731 static struct a_nag_group *
732 a_nag__group_del(struct a_nag_group_lookup *nglp){
733 struct a_nag_group *x, *ngp;
734 NYD2_ENTER;
736 /* Overly complicated: link off this node, step ahead to next.. */
737 x = nglp->ngl_group;
738 if((ngp = nglp->ngl_slot_last) != NULL)
739 ngp = (ngp->ng_next = x->ng_next);
740 else{
741 nglp->ngl_slot_last = NULL;
742 ngp = (*nglp->ngl_slot = x->ng_next);
744 if(ngp == NULL){
745 struct a_nag_group **ngpa;
747 for(ngpa = &nglp->ngl_htable[HSHSIZE]; ++nglp->ngl_slot < ngpa;)
748 if((ngp = *nglp->ngl_slot) != NULL)
749 break;
752 nglp->ngl_group = ngp;
754 if((x->ng_type & a_NAG_T_MASK) == a_NAG_T_ALIAS)
755 a_nag__names_del(x);
756 #ifdef HAVE_REGEX
757 else if(x->ng_type & a_NAG_T_REGEX){
758 struct a_nag_grp_regex *ngrp;
760 a_NAG_GP_TO_SUBCLASS(ngrp, x);
762 regfree(&ngrp->ngr_regex);
763 a_nag_mlmux_linkout(x);
765 #endif
767 n_free(x);
768 NYD2_LEAVE;
769 return ngp;
772 static void
773 a_nag__names_del(struct a_nag_group *ngp){
774 struct a_nag_grp_names_head *ngnhp;
775 struct a_nag_grp_names *ngnp;
776 NYD2_ENTER;
778 a_NAG_GP_TO_SUBCLASS(ngnhp, ngp);
780 for(ngnp = ngnhp->ngnh_head; ngnp != NULL;){
781 struct a_nag_grp_names *x;
783 x = ngnp;
784 ngnp = ngnp->ngn_next;
785 n_free(x);
787 NYD2_LEAVE;
790 static bool_t
791 a_nag_group_print_all(enum a_nag_type nt, char const *varname){
792 struct n_string s;
793 size_t lines;
794 FILE *fp;
795 char const **ida;
796 struct a_nag_group const *ngp;
797 ui32_t h, i;
798 struct a_nag_group **ngpa;
799 char const *tname;
800 enum a_nag_type xnt;
801 NYD_ENTER;
803 if(varname != NULL)
804 n_string_creat_auto(&s);
806 xnt = nt & a_NAG_T_PRINT_MASK;
808 switch(xnt & a_NAG_T_MASK){
809 case a_NAG_T_ALTERNATES:
810 tname = "alternates";
811 ngpa = a_nag_alternates_heads;
812 break;
813 default:
814 case a_NAG_T_COMMANDALIAS:
815 tname = "commandalias";
816 ngpa = a_nag_commandalias_heads;
817 break;
818 case a_NAG_T_ALIAS:
819 tname = "alias";
820 ngpa = a_nag_alias_heads;
821 break;
822 case a_NAG_T_MLIST:
823 tname = "mlist";
824 ngpa = a_nag_mlist_heads;
825 break;
826 case a_NAG_T_SHORTCUT:
827 tname = "shortcut";
828 ngpa = a_nag_shortcut_heads;
829 break;
830 case a_NAG_T_CHARSETALIAS:
831 tname = "charsetalias";
832 ngpa = a_nag_charsetalias_heads;
833 break;
834 case a_NAG_T_FILETYPE:
835 tname = "filetype";
836 ngpa = a_nag_filetype_heads;
837 break;
840 /* Count entries */
841 for(i = h = 0; h < HSHSIZE; ++h)
842 for(ngp = ngpa[h]; ngp != NULL; ngp = ngp->ng_next)
843 if((ngp->ng_type & a_NAG_T_PRINT_MASK) == xnt)
844 ++i;
845 if(i == 0){
846 if(varname == NULL)
847 fprintf(n_stdout, _("# no %s registered\n"), tname);
848 goto jleave;
850 ++i;
851 ida = n_autorec_alloc(i * sizeof *ida);
853 /* Create alpha sorted array of entries */
854 for(i = h = 0; h < HSHSIZE; ++h)
855 for(ngp = ngpa[h]; ngp != NULL; ngp = ngp->ng_next)
856 if((ngp->ng_type & a_NAG_T_PRINT_MASK) == xnt)
857 ida[i++] = ngp->ng_id;
858 if(i > 1)
859 qsort(ida, i, sizeof *ida, &a_nag__group_print_qsorter);
860 ida[i] = NULL;
862 if(varname != NULL)
863 fp = NULL;
864 else if((fp = Ftmp(NULL, "nagprint", OF_RDWR | OF_UNLINK | OF_REGISTER)
865 ) == NULL)
866 fp = n_stdout;
868 /* Create visual result */
869 lines = 0;
871 switch(xnt & a_NAG_T_MASK){
872 case a_NAG_T_ALTERNATES:
873 if(fp != NULL){
874 fputs(tname, fp);
875 lines = 1;
877 break;
878 default:
879 break;
882 for(i = 0; ida[i] != NULL; ++i)
883 lines += a_nag_group_print(a_nag_group_find(nt, ida[i]), fp, &s);
885 #ifdef HAVE_REGEX
886 if(varname == NULL && (nt & a_NAG_T_MASK) == a_NAG_T_MLIST){
887 if(nt & a_NAG_T_SUBSCRIBE)
888 i = (ui32_t)a_nag_mlsub_size, h = (ui32_t)a_nag_mlsub_hits;
889 else
890 i = (ui32_t)a_nag_mlist_size, h = (ui32_t)a_nag_mlist_hits;
892 if(i > 0 && (n_poption & n_PO_D_V)){
893 assert(fp != NULL);
894 fprintf(fp, _("# %s list regex(7) total: %u entries, %u hits\n"),
895 (nt & a_NAG_T_SUBSCRIBE ? _("Subscribed") : _("Non-subscribed")),
896 i, h);
897 ++lines;
900 #endif
902 switch(xnt & a_NAG_T_MASK){
903 case a_NAG_T_ALTERNATES:
904 if(fp != NULL){
905 putc('\n', fp);
906 assert(lines == 1);
908 break;
909 default:
910 break;
913 if(varname == NULL && fp != n_stdout){
914 assert(fp != NULL);
915 page_or_print(fp, lines);
916 Fclose(fp);
919 jleave:
920 if(varname != NULL){
921 tname = n_string_cp(&s);
922 if(n_var_vset(varname, (uintptr_t)tname))
923 varname = NULL;
924 else
925 n_pstate_err_no = n_ERR_NOTSUP;
927 NYD_LEAVE;
928 return (varname == NULL);
931 static int
932 a_nag__group_print_qsorter(void const *a, void const *b){
933 int rv;
934 NYD2_ENTER;
936 rv = strcmp(*(char**)n_UNCONST(a), *(char**)n_UNCONST(b));
937 NYD2_LEAVE;
938 return rv;
941 static size_t
942 a_nag_group_print(struct a_nag_group const *ngp, FILE *fo,
943 struct n_string *vputsp){
944 char const *cp;
945 size_t rv;
946 NYD2_ENTER;
948 rv = 1;
950 switch(ngp->ng_type & a_NAG_T_MASK){
951 case a_NAG_T_ALTERNATES:{
952 if(fo != NULL)
953 fprintf(fo, " %s", ngp->ng_id);
954 else{
955 if(vputsp->s_len > 0)
956 vputsp = n_string_push_c(vputsp, ' ');
957 /*vputsp =*/ n_string_push_cp(vputsp, ngp->ng_id);
959 rv = 0;
960 }break;
961 case a_NAG_T_COMMANDALIAS:{
962 struct a_nag_cmd_alias *ncap;
964 assert(fo != NULL); /* xxx no vput yet */
965 a_NAG_GP_TO_SUBCLASS(ncap, ngp);
966 fprintf(fo, "commandalias %s %s\n",
967 n_shexp_quote_cp(ngp->ng_id, TRU1),
968 n_shexp_quote_cp(ncap->nca_expand.s, TRU1));
969 }break;
970 case a_NAG_T_ALIAS:{
971 struct a_nag_grp_names_head *ngnhp;
972 struct a_nag_grp_names *ngnp;
974 assert(fo != NULL); /* xxx no vput yet */
975 fprintf(fo, "alias %s ", ngp->ng_id);
977 a_NAG_GP_TO_SUBCLASS(ngnhp, ngp);
978 if((ngnp = ngnhp->ngnh_head) != NULL) { /* xxx always 1+ entries */
980 struct a_nag_grp_names *x;
982 x = ngnp;
983 ngnp = ngnp->ngn_next;
984 fprintf(fo, " \"%s\"", string_quote(x->ngn_id)); /* TODO shexp */
985 }while(ngnp != NULL);
987 putc('\n', fo);
988 }break;
989 case a_NAG_T_MLIST:
990 assert(fo != NULL); /* xxx no vput yet */
991 #ifdef HAVE_REGEX
992 if((ngp->ng_type & a_NAG_T_REGEX) && (n_poption & n_PO_D_V)){
993 size_t i;
994 struct a_nag_grp_regex *lp, *ngrp;
996 lp = (ngp->ng_type & a_NAG_T_SUBSCRIBE ? a_nag_mlsub_regex
997 : a_nag_mlist_regex);
998 a_NAG_GP_TO_SUBCLASS(ngrp, ngp);
999 for(i = 1; lp != ngrp; lp = lp->ngr_next)
1000 ++i;
1001 fprintf(fo, "# regex(7): hits %" PRIuZ ", sort %" PRIuZ ".\n ",
1002 ngrp->ngr_hits, i);
1003 ++rv;
1005 #endif
1006 fprintf(fo, "wysh %s %s\n",
1007 (ngp->ng_type & a_NAG_T_SUBSCRIBE ? "mlsubscribe" : "mlist"),
1008 n_shexp_quote_cp(ngp->ng_id, TRU1));
1009 break;
1010 case a_NAG_T_SHORTCUT:
1011 assert(fo != NULL); /* xxx no vput yet */
1012 a_NAG_GP_TO_SUBCLASS(cp, ngp);
1013 fprintf(fo, "wysh shortcut %s %s\n",
1014 ngp->ng_id, n_shexp_quote_cp(cp, TRU1));
1015 break;
1016 case a_NAG_T_CHARSETALIAS:
1017 assert(fo != NULL); /* xxx no vput yet */
1018 a_NAG_GP_TO_SUBCLASS(cp, ngp);
1019 fprintf(fo, "charsetalias %s %s\n",
1020 n_shexp_quote_cp(ngp->ng_id, TRU1), n_shexp_quote_cp(cp, TRU1));
1021 break;
1022 case a_NAG_T_FILETYPE:{
1023 struct a_nag_file_type *nftp;
1025 assert(fo != NULL); /* xxx no vput yet */
1026 a_NAG_GP_TO_SUBCLASS(nftp, ngp);
1027 fprintf(fo, "filetype %s %s %s\n",
1028 n_shexp_quote_cp(ngp->ng_id, TRU1),
1029 n_shexp_quote_cp(nftp->nft_load.s, TRU1),
1030 n_shexp_quote_cp(nftp->nft_save.s, TRU1));
1031 }break;
1033 NYD2_LEAVE;
1034 return rv;
1037 static int
1038 a_nag_mlmux(enum a_nag_type nt, char const **argv){
1039 struct a_nag_group *ngp;
1040 char const *ecp;
1041 int rv;
1042 NYD2_ENTER;
1044 rv = 0;
1045 n_UNINIT(ecp, NULL);
1047 if(*argv == NULL)
1048 a_nag_group_print_all(nt, NULL);
1049 else do{
1050 if((ngp = a_nag_group_find(nt, *argv)) != NULL){
1051 if(nt & a_NAG_T_SUBSCRIBE){
1052 if(!(ngp->ng_type & a_NAG_T_SUBSCRIBE)){
1053 a_NAG_MLMUX_LINKOUT(ngp);
1054 ngp->ng_type |= a_NAG_T_SUBSCRIBE;
1055 a_NAG_MLMUX_LINKIN(ngp);
1056 }else{
1057 ecp = N_("Mailing-list already `mlsubscribe'd: %s\n");
1058 goto jerr;
1060 }else{
1061 ecp = N_("Mailing-list already `mlist'ed: %s\n");
1062 goto jerr;
1064 }else if(a_nag_group_fetch(nt, *argv, 0) == NULL){
1065 ecp = N_("Failed to create storage for mailing-list: %s\n");
1066 jerr:
1067 n_err(V_(ecp), n_shexp_quote_cp(*argv, FAL0));
1068 rv = 1;
1070 }while(*++argv != NULL);
1072 NYD2_LEAVE;
1073 return rv;
1076 static int
1077 a_nag_unmlmux(enum a_nag_type nt, char const **argv){
1078 struct a_nag_group *ngp;
1079 int rv;
1080 NYD2_ENTER;
1082 rv = 0;
1084 for(; *argv != NULL; ++argv){
1085 if(nt & a_NAG_T_SUBSCRIBE){
1086 struct a_nag_group_lookup ngl;
1087 bool_t isaster;
1089 if(!(isaster = (**argv == '*')))
1090 ngp = a_nag_group_find(nt, *argv);
1091 else if((ngp = a_nag_group_go_first(nt, &ngl)) == NULL)
1092 continue;
1093 else if(ngp != NULL && !(ngp->ng_type & a_NAG_T_SUBSCRIBE))
1094 goto jaster_entry;
1096 if(ngp != NULL){
1097 jaster_redo:
1098 if(ngp->ng_type & a_NAG_T_SUBSCRIBE){
1099 a_NAG_MLMUX_LINKOUT(ngp);
1100 ngp->ng_type &= ~a_NAG_T_SUBSCRIBE;
1101 a_NAG_MLMUX_LINKIN(ngp);
1103 if(isaster){
1104 jaster_entry:
1105 while((ngp = a_nag_group_go_next(&ngl)) != NULL &&
1106 !(ngp->ng_type & a_NAG_T_SUBSCRIBE))
1108 if(ngp != NULL)
1109 goto jaster_redo;
1111 }else{
1112 n_err(_("Mailing-list not `mlsubscribe'd: %s\n"),
1113 n_shexp_quote_cp(*argv, FAL0));
1114 rv = 1;
1116 continue;
1118 }else if(a_nag_group_del(nt, *argv))
1119 continue;
1120 n_err(_("No such mailing-list: %s\n"), n_shexp_quote_cp(*argv, FAL0));
1121 rv = 1;
1123 NYD2_LEAVE;
1124 return rv;
1127 #ifdef HAVE_REGEX
1128 static void
1129 a_nag_mlmux_linkin(struct a_nag_group *ngp){
1130 struct a_nag_grp_regex **lpp, *ngrp, *lhp;
1131 NYD2_ENTER;
1133 if(ngp->ng_type & a_NAG_T_SUBSCRIBE){
1134 lpp = &a_nag_mlsub_regex;
1135 ++a_nag_mlsub_size;
1136 }else{
1137 lpp = &a_nag_mlist_regex;
1138 ++a_nag_mlist_size;
1141 a_NAG_GP_TO_SUBCLASS(ngrp, ngp);
1143 if((lhp = *lpp) != NULL){
1144 (ngrp->ngr_last = lhp->ngr_last)->ngr_next = ngrp;
1145 (ngrp->ngr_next = lhp)->ngr_last = ngrp;
1146 }else
1147 *lpp = ngrp->ngr_last = ngrp->ngr_next = ngrp;
1148 ngrp->ngr_hits = 0;
1149 NYD2_LEAVE;
1152 static void
1153 a_nag_mlmux_linkout(struct a_nag_group *ngp){
1154 struct a_nag_grp_regex *ngrp, **lpp;
1155 NYD2_ENTER;
1157 a_NAG_GP_TO_SUBCLASS(ngrp, ngp);
1159 if(ngp->ng_type & a_NAG_T_SUBSCRIBE){
1160 lpp = &a_nag_mlsub_regex;
1161 --a_nag_mlsub_size;
1162 a_nag_mlsub_hits -= ngrp->ngr_hits;
1163 }else{
1164 lpp = &a_nag_mlist_regex;
1165 --a_nag_mlist_size;
1166 a_nag_mlist_hits -= ngrp->ngr_hits;
1169 if(ngrp->ngr_next == ngrp)
1170 *lpp = NULL;
1171 else{
1172 (ngrp->ngr_last->ngr_next = ngrp->ngr_next)->ngr_last = ngrp->ngr_last;
1173 if(*lpp == ngrp)
1174 *lpp = ngrp->ngr_next;
1176 NYD2_LEAVE;
1178 #endif /* HAVE_REGEX */
1180 FL struct name *
1181 nalloc(char const *str, enum gfield ntype)
1183 struct n_addrguts ag;
1184 struct str in, out;
1185 struct name *np;
1186 NYD_ENTER;
1187 assert(!(ntype & GFULLEXTRA) || (ntype & GFULL) != 0);
1189 str = n_addrspec_with_guts(&ag, str,
1190 ((ntype & (GFULL | GSKIN | GREF)) != 0), FAL0);
1191 if(str == NULL){
1193 np = NULL; TODO We cannot return NULL,
1194 goto jleave; TODO thus handle failures in here!
1196 str = ag.ag_input;
1199 if (!(ag.ag_n_flags & NAME_NAME_SALLOC)) {
1200 ag.ag_n_flags |= NAME_NAME_SALLOC;
1201 np = n_autorec_alloc(sizeof(*np) + ag.ag_slen +1);
1202 memcpy(np + 1, ag.ag_skinned, ag.ag_slen +1);
1203 ag.ag_skinned = (char*)(np + 1);
1204 } else
1205 np = n_autorec_alloc(sizeof *np);
1207 np->n_flink = NULL;
1208 np->n_blink = NULL;
1209 np->n_type = ntype;
1210 np->n_fullname = np->n_name = ag.ag_skinned;
1211 np->n_fullextra = NULL;
1212 np->n_flags = ag.ag_n_flags;
1214 if (ntype & GFULL) {
1215 if (ag.ag_ilen == ag.ag_slen
1216 #ifdef HAVE_IDNA
1217 && !(ag.ag_n_flags & NAME_IDNA)
1218 #endif
1220 goto jleave;
1221 if (ag.ag_n_flags & NAME_ADDRSPEC_ISFILEORPIPE)
1222 goto jleave;
1224 /* n_fullextra is only the complete name part without address.
1225 * Beware of "-r '<abc@def>'", don't treat that as FULLEXTRA */
1226 if ((ntype & GFULLEXTRA) && ag.ag_ilen > ag.ag_slen + 2) {
1227 size_t s = ag.ag_iaddr_start, e = ag.ag_iaddr_aend, i;
1228 char const *cp;
1230 if (s == 0 || str[--s] != '<' || str[e++] != '>')
1231 goto jskipfullextra;
1232 i = ag.ag_ilen - e;
1233 in.s = n_lofi_alloc(s + 1 + i +1);
1234 while(s > 0 && blankchar(str[s - 1]))
1235 --s;
1236 memcpy(in.s, str, s);
1237 if (i > 0) {
1238 in.s[s++] = ' ';
1239 while (blankchar(str[e])) {
1240 ++e;
1241 if (--i == 0)
1242 break;
1244 if (i > 0)
1245 memcpy(&in.s[s], &str[e], i);
1247 s += i;
1248 in.s[in.l = s] = '\0';
1249 mime_fromhdr(&in, &out, /* TODO TD_ISPR |*/ TD_ICONV);
1251 for (cp = out.s, i = out.l; i > 0 && spacechar(*cp); --i, ++cp)
1253 while (i > 0 && spacechar(cp[i - 1]))
1254 --i;
1255 np->n_fullextra = savestrbuf(cp, i);
1257 n_lofi_free(in.s);
1258 n_free(out.s);
1260 jskipfullextra:
1262 /* n_fullname depends on IDNA conversion */
1263 #ifdef HAVE_IDNA
1264 if (!(ag.ag_n_flags & NAME_IDNA)) {
1265 #endif
1266 in.s = n_UNCONST(str);
1267 in.l = ag.ag_ilen;
1268 #ifdef HAVE_IDNA
1269 } else {
1270 /* The domain name was IDNA and has been converted. We also have to
1271 * ensure that the domain name in .n_fullname is replaced with the
1272 * converted version, since MIME doesn't perform encoding of addrs */
1273 /* TODO This definetely doesn't belong here! */
1274 size_t l = ag.ag_iaddr_start,
1275 lsuff = ag.ag_ilen - ag.ag_iaddr_aend;
1276 in.s = n_lofi_alloc(l + ag.ag_slen + lsuff +1);
1277 memcpy(in.s, str, l);
1278 memcpy(in.s + l, ag.ag_skinned, ag.ag_slen);
1279 l += ag.ag_slen;
1280 memcpy(in.s + l, str + ag.ag_iaddr_aend, lsuff);
1281 l += lsuff;
1282 in.s[l] = '\0';
1283 in.l = l;
1285 #endif
1286 mime_fromhdr(&in, &out, /* TODO TD_ISPR |*/ TD_ICONV);
1287 np->n_fullname = savestr(out.s);
1288 n_free(out.s);
1289 #ifdef HAVE_IDNA
1290 if (ag.ag_n_flags & NAME_IDNA)
1291 n_lofi_free(in.s);
1292 #endif
1294 jleave:
1295 NYD_LEAVE;
1296 return np;
1299 FL struct name *
1300 ndup(struct name *np, enum gfield ntype)
1302 struct name *nnp;
1303 NYD_ENTER;
1305 if ((ntype & (GFULL | GSKIN)) && !(np->n_flags & NAME_SKINNED)) {
1306 nnp = nalloc(np->n_name, ntype);
1307 goto jleave;
1310 nnp = n_autorec_alloc(sizeof *np);
1311 nnp->n_flink = nnp->n_blink = NULL;
1312 nnp->n_type = ntype;
1313 nnp->n_flags = np->n_flags | NAME_NAME_SALLOC;
1314 nnp->n_name = savestr(np->n_name);
1315 if (np->n_name == np->n_fullname || !(ntype & (GFULL | GSKIN))) {
1316 nnp->n_fullname = nnp->n_name;
1317 nnp->n_fullextra = NULL;
1318 } else {
1319 nnp->n_fullname = savestr(np->n_fullname);
1320 nnp->n_fullextra = (np->n_fullextra == NULL) ? NULL
1321 : savestr(np->n_fullextra);
1323 jleave:
1324 NYD_LEAVE;
1325 return nnp;
1328 FL struct name *
1329 cat(struct name *n1, struct name *n2){
1330 struct name *tail;
1331 NYD2_ENTER;
1333 tail = n2;
1334 if(n1 == NULL)
1335 goto jleave;
1336 tail = n1;
1337 if(n2 == NULL || (n2->n_type & GDEL))
1338 goto jleave;
1340 while(tail->n_flink != NULL)
1341 tail = tail->n_flink;
1342 tail->n_flink = n2;
1343 n2->n_blink = tail;
1344 tail = n1;
1345 jleave:
1346 NYD2_LEAVE;
1347 return tail;
1350 FL struct name *
1351 namelist_dup(struct name const *np, enum gfield ntype){
1352 struct name *nlist, *xnp;
1353 NYD2_ENTER;
1355 for(nlist = xnp = NULL; np != NULL; np = np->n_flink){
1356 struct name *x;
1358 if(!(np->n_type & GDEL)){
1359 x = ndup(n_UNCONST(np), (np->n_type & ~GMASK) | ntype);
1360 if((x->n_blink = xnp) == NULL)
1361 nlist = x;
1362 else
1363 xnp->n_flink = x;
1364 xnp = x;
1367 NYD2_LEAVE;
1368 return nlist;
1371 FL ui32_t
1372 count(struct name const *np)
1374 ui32_t c;
1375 NYD_ENTER;
1377 for (c = 0; np != NULL; np = np->n_flink)
1378 if (!(np->n_type & GDEL))
1379 ++c;
1380 NYD_LEAVE;
1381 return c;
1384 FL ui32_t
1385 count_nonlocal(struct name const *np)
1387 ui32_t c;
1388 NYD_ENTER;
1390 for (c = 0; np != NULL; np = np->n_flink)
1391 if (!(np->n_type & GDEL) && !(np->n_flags & NAME_ADDRSPEC_ISFILEORPIPE))
1392 ++c;
1393 NYD_LEAVE;
1394 return c;
1397 FL struct name *
1398 extract(char const *line, enum gfield ntype)
1400 struct name *rv;
1401 NYD_ENTER;
1403 rv = a_nag_extract1(line, ntype, " \t,", 0);
1404 NYD_LEAVE;
1405 return rv;
1408 FL struct name *
1409 lextract(char const *line, enum gfield ntype)
1411 struct name *rv;
1412 NYD_ENTER;
1414 rv = ((line != NULL && strpbrk(line, ",\"\\(<|"))
1415 ? a_nag_extract1(line, ntype, ",", 1) : extract(line, ntype));
1416 NYD_LEAVE;
1417 return rv;
1420 FL char *
1421 detract(struct name *np, enum gfield ntype)
1423 char *topp, *cp;
1424 struct name *p;
1425 int flags, s;
1426 NYD_ENTER;
1428 topp = NULL;
1429 if (np == NULL)
1430 goto jleave;
1432 flags = ntype & (GCOMMA | GNAMEONLY);
1433 ntype &= ~(GCOMMA | GNAMEONLY);
1434 s = 0;
1436 for (p = np; p != NULL; p = p->n_flink) {
1437 if (ntype && (p->n_type & GMASK) != ntype)
1438 continue;
1439 s += strlen(flags & GNAMEONLY ? p->n_name : p->n_fullname) +1;
1440 if (flags & GCOMMA)
1441 ++s;
1443 if (s == 0)
1444 goto jleave;
1446 s += 2;
1447 topp = n_autorec_alloc(s);
1448 cp = topp;
1449 for (p = np; p != NULL; p = p->n_flink) {
1450 if (ntype && (p->n_type & GMASK) != ntype)
1451 continue;
1452 cp = sstpcpy(cp, (flags & GNAMEONLY ? p->n_name : p->n_fullname));
1453 if ((flags & GCOMMA) && p->n_flink != NULL)
1454 *cp++ = ',';
1455 *cp++ = ' ';
1457 *--cp = 0;
1458 if ((flags & GCOMMA) && *--cp == ',')
1459 *cp = 0;
1460 jleave:
1461 NYD_LEAVE;
1462 return topp;
1465 FL struct name *
1466 grab_names(enum n_go_input_flags gif, char const *field, struct name *np,
1467 int comma, enum gfield gflags)
1469 struct name *nq;
1470 NYD_ENTER;
1472 jloop:
1473 np = lextract(n_go_input_cp(gif, field, detract(np, comma)), gflags);
1474 for (nq = np; nq != NULL; nq = nq->n_flink)
1475 if (is_addr_invalid(nq, EACM_NONE))
1476 goto jloop;
1477 NYD_LEAVE;
1478 return np;
1481 FL bool_t
1482 name_is_same_domain(struct name const *n1, struct name const *n2)
1484 char const *d1, *d2;
1485 bool_t rv;
1486 NYD_ENTER;
1488 d1 = strrchr(n1->n_name, '@');
1489 d2 = strrchr(n2->n_name, '@');
1491 rv = (d1 != NULL && d2 != NULL) ? !asccasecmp(++d1, ++d2) : FAL0;
1493 NYD_LEAVE;
1494 return rv;
1497 FL struct name *
1498 checkaddrs(struct name *np, enum expand_addr_check_mode eacm,
1499 si8_t *set_on_error)
1501 struct name *n;
1502 NYD_ENTER;
1504 for (n = np; n != NULL; n = n->n_flink) {
1505 si8_t rv;
1507 if ((rv = is_addr_invalid(n, eacm)) != 0) {
1508 if (set_on_error != NULL)
1509 *set_on_error |= rv; /* don't loose -1! */
1510 else if (eacm & EAF_MAYKEEP) /* TODO HACK! See definition! */
1511 continue;
1512 if (n->n_blink)
1513 n->n_blink->n_flink = n->n_flink;
1514 if (n->n_flink)
1515 n->n_flink->n_blink = n->n_blink;
1516 if (n == np)
1517 np = n->n_flink;
1520 NYD_LEAVE;
1521 return np;
1524 FL struct name *
1525 namelist_vaporise_head(struct header *hp, enum expand_addr_check_mode eacm,
1526 bool_t metoo, si8_t *set_on_error)
1528 /* TODO namelist_vaporise_head() is incredibly expensive and redundant */
1529 struct name *tolist, *np, **npp;
1530 NYD_ENTER;
1532 tolist = cat(hp->h_to, cat(hp->h_cc, hp->h_bcc));
1533 hp->h_to = hp->h_cc = hp->h_bcc = NULL;
1535 tolist = usermap(tolist, metoo);
1536 tolist = n_alternates_remove(tolist, TRU1);
1537 tolist = elide(checkaddrs(tolist, eacm, set_on_error));
1539 for (np = tolist; np != NULL; np = np->n_flink) {
1540 switch (np->n_type & (GDEL | GMASK)) {
1541 case GTO: npp = &hp->h_to; break;
1542 case GCC: npp = &hp->h_cc; break;
1543 case GBCC: npp = &hp->h_bcc; break;
1544 default: continue;
1546 *npp = cat(*npp, ndup(np, np->n_type | GFULL));
1548 NYD_LEAVE;
1549 return tolist;
1552 FL struct name *
1553 usermap(struct name *names, bool_t force_metoo){
1554 struct a_nag_group *ngp;
1555 struct name *nlist, *nlist_tail, *np, *cp;
1556 int metoo;
1557 NYD_ENTER;
1559 metoo = (force_metoo || ok_blook(metoo));
1560 nlist = nlist_tail = NULL;
1561 np = names;
1563 for(; np != NULL; np = cp){
1564 assert(!(np->n_type & GDEL)); /* TODO legacy */
1565 cp = np->n_flink;
1567 if(is_fileorpipe_addr(np) ||
1568 (ngp = a_nag_group_find(a_NAG_T_ALIAS, np->n_name)) == NULL){
1569 if((np->n_blink = nlist_tail) != NULL)
1570 nlist_tail->n_flink = np;
1571 else
1572 nlist = np;
1573 nlist_tail = np;
1574 np->n_flink = NULL;
1575 }else{
1576 nlist = a_nag_gexpand(0, nlist, ngp, metoo, np->n_type);
1577 if((nlist_tail = nlist) != NULL)
1578 while(nlist_tail->n_flink != NULL)
1579 nlist_tail = nlist_tail->n_flink;
1582 NYD_LEAVE;
1583 return nlist;
1586 FL struct name *
1587 elide(struct name *names)
1589 size_t i, j, k;
1590 struct name *nlist, *np, **nparr;
1591 NYD_ENTER;
1593 nlist = NULL;
1595 if(names == NULL)
1596 goto jleave;
1598 /* Throw away all deleted nodes */
1599 for(np = NULL, i = 0; names != NULL; names = names->n_flink)
1600 if(!(names->n_type & GDEL)){
1601 names->n_blink = np;
1602 if(np != NULL)
1603 np->n_flink = names;
1604 else
1605 nlist = names;
1606 np = names;
1607 ++i;
1609 if(nlist == NULL || i == 1)
1610 goto jleave;
1611 np->n_flink = NULL;
1613 /* Create a temporay array and sort that */
1614 nparr = n_lofi_alloc(sizeof(*nparr) * i);
1616 for(i = 0, np = nlist; np != NULL; np = np->n_flink)
1617 nparr[i++] = np;
1619 qsort(nparr, i, sizeof *nparr, &a_nag_elide_qsort);
1621 /* Remove duplicates XXX speedup, or list_uniq()! */
1622 for(j = 0, --i; j < i;){
1623 if(asccasecmp(nparr[j]->n_name, nparr[k = j + 1]->n_name))
1624 ++j;
1625 else{
1626 for(; k < i; ++k)
1627 nparr[k] = nparr[k + 1];
1628 --i;
1632 /* Throw away all list members which are not part of the array.
1633 * Note this keeps the original, possibly carefully crafted, order of the
1634 * addressees, thus */
1635 for(np = nlist; np != NULL; np = np->n_flink){
1636 for(j = 0; j <= i; ++j)
1637 if(np == nparr[j]){
1638 nparr[j] = NULL;
1639 goto jiter;
1641 /* Drop it */
1642 if(np == nlist){
1643 nlist = np->n_flink;
1644 np->n_blink = NULL;
1645 }else
1646 np->n_blink->n_flink = np->n_flink;
1647 if(np->n_flink != NULL)
1648 np->n_flink->n_blink = np->n_blink;
1649 jiter:;
1652 n_lofi_free(nparr);
1653 jleave:
1654 NYD_LEAVE;
1655 return nlist;
1658 FL int
1659 c_alternates(void *vp){
1660 struct a_nag_group *ngp;
1661 char const *varname, *ccp;
1662 char **argv;
1663 NYD_ENTER;
1665 n_pstate_err_no = n_ERR_NONE;
1667 argv = vp;
1668 varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
1670 if(*argv == NULL){
1671 if(!a_nag_group_print_all(a_NAG_T_ALTERNATES, varname))
1672 vp = NULL;
1673 }else{
1674 if(varname != NULL)
1675 n_err(_("`alternates': `vput' only supported for show mode\n"));
1677 /* Delete the old set to "declare a list", if *posix* */
1678 if(ok_blook(posix))
1679 a_nag_group_del(a_NAG_T_ALTERNATES, n_star);
1681 while((ccp = *argv++) != NULL){
1682 size_t l;
1683 struct name *np;
1685 if((np = lextract(ccp, GSKIN)) == NULL || np->n_flink != NULL ||
1686 (np = checkaddrs(np, EACM_STRICT, NULL)) == NULL){
1687 n_err(_("Invalid `alternates' argument: %s\n"),
1688 n_shexp_quote_cp(ccp, FAL0));
1689 n_pstate_err_no = n_ERR_INVAL;
1690 vp = NULL;
1691 continue;
1693 ccp = np->n_name;
1695 l = strlen(ccp) +1;
1696 if((ngp = a_nag_group_fetch(a_NAG_T_ALTERNATES, ccp, l)) == NULL){
1697 n_err(_("Failed to create storage for alternates: %s\n"),
1698 n_shexp_quote_cp(ccp, FAL0));
1699 n_pstate_err_no = n_ERR_NOMEM;
1700 vp = NULL;
1704 NYD_LEAVE;
1705 return (vp != NULL ? 0 : 1);
1708 FL int
1709 c_unalternates(void *vp){
1710 char **argv;
1711 int rv;
1712 NYD_ENTER;
1714 rv = 0;
1715 argv = vp;
1717 do if(!a_nag_group_del(a_NAG_T_ALTERNATES, *argv)){
1718 n_err(_("No such `alternates': %s\n"), n_shexp_quote_cp(*argv, FAL0));
1719 rv = 1;
1720 }while(*++argv != NULL);
1721 NYD_LEAVE;
1722 return rv;
1725 FL struct name *
1726 n_alternates_remove(struct name *np, bool_t keep_single){
1727 /* XXX keep a single pointer, initial null, and immediate remove nodes
1728 * XXX on successful match unless keep single and that pointer null! */
1729 struct a_nag_group_lookup ngl;
1730 struct a_nag_group *ngp;
1731 struct name *xp, *newnp;
1732 NYD_ENTER;
1734 /* Delete the temporary bit from all */
1735 for(xp = np; xp != NULL; xp = xp->n_flink)
1736 xp->n_flags &= ~(ui32_t)SI32_MIN;
1738 /* Mark all possible alternate names (xxx sic: instead walk over namelist
1739 * and hash-lookup alternate instead (unless *allnet*) */
1740 for(ngp = a_nag_group_go_first(a_NAG_T_ALTERNATES, &ngl); ngp != NULL;
1741 ngp = a_nag_group_go_next(&ngl))
1742 np = a_nag_namelist_mark_name(np, ngp->ng_id);
1744 np = a_nag_namelist_mark_name(np, ok_vlook(LOGNAME));
1746 if((xp = extract(ok_vlook(sender), GEXTRA | GSKIN)) != NULL){
1747 /* TODO check_from_and_sender(): drop; *sender*: only one name!
1748 * TODO At assignment time, as VIP var? */
1750 np = a_nag_namelist_mark_name(np, xp->n_name);
1751 while((xp = xp->n_flink) != NULL);
1752 }else for(xp = lextract(ok_vlook(from), GEXTRA | GSKIN); xp != NULL;
1753 xp = xp->n_flink)
1754 np = a_nag_namelist_mark_name(np, xp->n_name);
1756 /* C99 */{
1757 char const *v15compat;
1759 if((v15compat = ok_vlook(replyto)) != NULL){
1760 n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
1761 for(xp = lextract(v15compat, GEXTRA | GSKIN); xp != NULL;
1762 xp = xp->n_flink)
1763 np = a_nag_namelist_mark_name(np, xp->n_name);
1767 for(xp = lextract(ok_vlook(reply_to), GEXTRA | GSKIN); xp != NULL;
1768 xp = xp->n_flink)
1769 np = a_nag_namelist_mark_name(np, xp->n_name);
1771 /* Clean the list by throwing away all deleted or marked (but one) nodes */
1772 for(xp = newnp = NULL; np != NULL; np = np->n_flink){
1773 if(np->n_type & GDEL)
1774 continue;
1775 if(np->n_flags & (ui32_t)SI32_MIN){
1776 if(!keep_single)
1777 continue;
1778 keep_single = FAL0;
1781 np->n_blink = xp;
1782 if(xp != NULL)
1783 xp->n_flink = np;
1784 else
1785 newnp = np;
1786 xp = np;
1787 xp->n_flags &= ~(ui32_t)SI32_MIN;
1789 if(xp != NULL)
1790 xp->n_flink = NULL;
1791 np = newnp;
1793 NYD_LEAVE;
1794 return np;
1797 FL bool_t
1798 n_is_myname(char const *name){
1799 struct a_nag_group_lookup ngl;
1800 struct a_nag_group *ngp;
1801 struct name *xp;
1802 NYD_ENTER;
1804 if(a_nag_is_same_name(ok_vlook(LOGNAME), name))
1805 goto jleave;
1807 if(!ok_blook(allnet)){
1808 if(a_nag_group_lookup(a_NAG_T_ALTERNATES, &ngl, name) != NULL)
1809 goto jleave;
1810 }else{
1811 for(ngp = a_nag_group_go_first(a_NAG_T_ALTERNATES, &ngl); ngp != NULL;
1812 ngp = a_nag_group_go_next(&ngl))
1813 if(a_nag_is_same_name(ngp->ng_id, name))
1814 goto jleave;
1817 for(xp = lextract(ok_vlook(from), GEXTRA | GSKIN); xp != NULL;
1818 xp = xp->n_flink)
1819 if(a_nag_is_same_name(xp->n_name, name))
1820 goto jleave;
1822 /* C99 */{
1823 char const *v15compat;
1825 if((v15compat = ok_vlook(replyto)) != NULL){
1826 n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
1827 for(xp = lextract(v15compat, GEXTRA | GSKIN); xp != NULL;
1828 xp = xp->n_flink)
1829 if(a_nag_is_same_name(xp->n_name, name))
1830 goto jleave;
1834 for(xp = lextract(ok_vlook(reply_to), GEXTRA | GSKIN); xp != NULL;
1835 xp = xp->n_flink)
1836 if(a_nag_is_same_name(xp->n_name, name))
1837 goto jleave;
1839 for(xp = extract(ok_vlook(sender), GEXTRA | GSKIN); xp != NULL;
1840 xp = xp->n_flink)
1841 if(a_nag_is_same_name(xp->n_name, name))
1842 goto jleave;
1844 name = NULL;
1845 jleave:
1846 NYD_LEAVE;
1847 return (name != NULL);
1850 FL int
1851 c_addrcodec(void *vp){
1852 struct n_addrguts ag;
1853 struct str trims;
1854 struct n_string s_b, *sp;
1855 size_t alen;
1856 int mode;
1857 char const **argv, *varname, *act, *cp;
1858 NYD_ENTER;
1860 sp = n_string_creat_auto(&s_b);
1861 argv = vp;
1862 varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
1864 act = *argv;
1865 for(cp = act; *cp != '\0' && !blankspacechar(*cp); ++cp)
1867 mode = 0;
1868 if(*act == '+')
1869 mode = 1, ++act;
1870 if(*act == '+')
1871 mode = 2, ++act;
1872 if(*act == '+')
1873 mode = 3, ++act;
1874 if(act >= cp)
1875 goto jesynopsis;
1876 alen = PTR2SIZE(cp - act);
1877 if(*cp != '\0')
1878 ++cp;
1880 trims.l = strlen(trims.s = n_UNCONST(cp));
1881 cp = savestrbuf(n_str_trim(&trims, n_STR_TRIM_BOTH)->s, trims.l);
1882 if(trims.l <= UIZ_MAX / 4)
1883 trims.l <<= 1;
1884 sp = n_string_reserve(sp, trims.l);
1886 n_pstate_err_no = n_ERR_NONE;
1888 if(is_ascncaseprefix(act, "encode", alen)){
1889 /* This function cannot be a simple nalloc() wrapper even later on, since
1890 * we may need to turn any ", () or \ into quoted-pairs */
1891 char c;
1893 while((c = *cp++) != '\0'){
1894 if(((c == '(' || c == ')') && mode < 1) || (c == '"' && mode < 2) ||
1895 (c == '\\' && mode < 3))
1896 sp = n_string_push_c(sp, '\\');
1897 sp = n_string_push_c(sp, c);
1900 if(n_addrspec_with_guts(&ag, n_string_cp(sp), TRU1, TRU1) == NULL ||
1901 (ag.ag_n_flags & (NAME_ADDRSPEC_ISADDR | NAME_ADDRSPEC_INVALID)
1902 ) != NAME_ADDRSPEC_ISADDR){
1903 cp = sp->s_dat;
1904 n_pstate_err_no = n_ERR_INVAL;
1905 vp = NULL;
1906 }else{
1907 struct name *np;
1909 np = nalloc(ag.ag_input, GTO | GFULL | GSKIN);
1910 cp = np->n_fullname;
1912 }else if(mode == 0){
1913 if(is_ascncaseprefix(act, "decode", alen)){
1914 char c;
1916 while((c = *cp++) != '\0'){
1917 switch(c){
1918 case '(':
1919 sp = n_string_push_c(sp, '(');
1920 act = skip_comment(cp);
1921 if(--act > cp)
1922 sp = n_string_push_buf(sp, cp, PTR2SIZE(act - cp));
1923 sp = n_string_push_c(sp, ')');
1924 cp = ++act;
1925 break;
1926 case '"':
1927 while(*cp != '\0'){
1928 if((c = *cp++) == '"')
1929 break;
1930 if(c == '\\' && (c = *cp) != '\0')
1931 ++cp;
1932 sp = n_string_push_c(sp, c);
1934 break;
1935 default:
1936 if(c == '\\' && (c = *cp++) == '\0')
1937 break;
1938 sp = n_string_push_c(sp, c);
1939 break;
1942 cp = n_string_cp(sp);
1943 }else if(is_ascncaseprefix(act, "skin", alen) ||
1944 (mode = 1, is_ascncaseprefix(act, "skinlist", alen))){
1945 /* Let's just use the is-single-address hack for this one, too.. */
1946 if(n_addrspec_with_guts(&ag, cp, TRU1, TRU1) == NULL ||
1947 (ag.ag_n_flags & (NAME_ADDRSPEC_ISADDR | NAME_ADDRSPEC_INVALID)
1948 ) != NAME_ADDRSPEC_ISADDR){
1949 n_pstate_err_no = n_ERR_INVAL;
1950 vp = NULL;
1951 }else{
1952 struct name *np;
1954 np = nalloc(ag.ag_input, GTO | GFULL | GSKIN);
1955 cp = np->n_name;
1957 if(mode == 1 && is_mlist(cp, FAL0) != MLIST_OTHER)
1958 n_pstate_err_no = n_ERR_EXIST;
1960 }else
1961 goto jesynopsis;
1962 }else
1963 goto jesynopsis;
1965 if(varname == NULL){
1966 if(fprintf(n_stdout, "%s\n", cp) < 0){
1967 n_pstate_err_no = n_err_no;
1968 vp = NULL;
1970 }else if(!n_var_vset(varname, (uintptr_t)cp)){
1971 n_pstate_err_no = n_ERR_NOTSUP;
1972 vp = NULL;
1975 jleave:
1976 NYD_LEAVE;
1977 return (vp != NULL ? 0 : 1);
1978 jesynopsis:
1979 n_err(_("Synopsis: addrcodec: <[+[+[+]]]e[ncode]|d[ecode]|s[kin]> "
1980 "<rest-of-line>\n"));
1981 n_pstate_err_no = n_ERR_INVAL;
1982 vp = NULL;
1983 goto jleave;
1986 FL int
1987 c_commandalias(void *vp){
1988 struct a_nag_group *ngp;
1989 char const **argv, *ccp;
1990 int rv;
1991 NYD_ENTER;
1993 rv = 0;
1994 argv = vp;
1996 if((ccp = *argv) == NULL){
1997 a_nag_group_print_all(a_NAG_T_COMMANDALIAS, NULL);
1998 goto jleave;
2001 /* Verify the name is a valid one, and not a command modifier.
2002 * NOTE: this list duplicates settings isolated somewhere else (go.c) */
2003 if(*ccp == '\0' || *n_cmd_isolate(ccp) != '\0' ||
2004 !asccasecmp(ccp, "ignerr") || !asccasecmp(ccp, "local") ||
2005 !asccasecmp(ccp, "wysh") || !asccasecmp(ccp, "vput") ||
2006 !asccasecmp(ccp, "scope") || !asccasecmp(ccp, "u")){
2007 n_err(_("`commandalias': not a valid command name: %s\n"),
2008 n_shexp_quote_cp(ccp, FAL0));
2009 rv = 1;
2010 goto jleave;
2013 if(argv[1] == NULL){
2014 if((ngp = a_nag_group_find(a_NAG_T_COMMANDALIAS, ccp)) != NULL)
2015 a_nag_group_print(ngp, n_stdout, NULL);
2016 else{
2017 n_err(_("No such commandalias: %s\n"), n_shexp_quote_cp(ccp, FAL0));
2018 rv = 1;
2020 }else{
2021 /* Because one hardly ever redefines, anything is stored in one chunk */
2022 char *cp;
2023 size_t i, len;
2025 /* Delete the old one, if any; don't get fooled to remove them all */
2026 if(ccp[0] != '*' || ccp[1] != '\0')
2027 a_nag_group_del(a_NAG_T_COMMANDALIAS, ccp);
2029 for(i = len = 0, ++argv; argv[i] != NULL; ++i)
2030 len += strlen(argv[i]) + 1;
2031 if(len == 0)
2032 len = 1;
2034 if((ngp = a_nag_group_fetch(a_NAG_T_COMMANDALIAS, ccp, len)) == NULL){
2035 n_err(_("Failed to create storage for commandalias: %s\n"),
2036 n_shexp_quote_cp(ccp, FAL0));
2037 rv = 1;
2038 }else{
2039 struct a_nag_cmd_alias *ncap;
2041 a_NAG_GP_TO_SUBCLASS(ncap, ngp);
2042 a_NAG_GP_TO_SUBCLASS(cp, ngp);
2043 cp += sizeof *ncap;
2044 ncap->nca_expand.s = cp;
2045 ncap->nca_expand.l = len - 1;
2047 for(len = 0; (ccp = *argv++) != NULL;)
2048 if((i = strlen(ccp)) > 0){
2049 if(len++ != 0)
2050 *cp++ = ' ';
2051 memcpy(cp, ccp, i);
2052 cp += i;
2054 *cp = '\0';
2057 jleave:
2058 NYD_LEAVE;
2059 return rv;
2062 FL int
2063 c_uncommandalias(void *vp){
2064 char **argv;
2065 int rv;
2066 NYD_ENTER;
2068 rv = 0;
2069 argv = vp;
2071 do if(!a_nag_group_del(a_NAG_T_COMMANDALIAS, *argv)){
2072 n_err(_("No such `commandalias': %s\n"), n_shexp_quote_cp(*argv, FAL0));
2073 rv = 1;
2074 }while(*++argv != NULL);
2075 NYD_LEAVE;
2076 return rv;
2079 FL char const *
2080 n_commandalias_exists(char const *name, struct str const **expansion_or_null){
2081 struct a_nag_group *ngp;
2082 NYD_ENTER;
2084 if((ngp = a_nag_group_find(a_NAG_T_COMMANDALIAS, name)) != NULL){
2085 name = ngp->ng_id;
2087 if(expansion_or_null != NULL){
2088 struct a_nag_cmd_alias *ncap;
2090 a_NAG_GP_TO_SUBCLASS(ncap, ngp);
2091 *expansion_or_null = &ncap->nca_expand;
2093 }else
2094 name = NULL;
2095 NYD_LEAVE;
2096 return name;
2099 FL bool_t
2100 n_alias_is_valid_name(char const *name){
2101 char c;
2102 char const *cp;
2103 bool_t rv;
2104 NYD2_ENTER;
2106 for(rv = TRU1, cp = name++; (c = *cp++) != '\0';)
2107 /* User names, plus things explicitly mentioned in Postfix aliases(5),
2108 * i.e., [[:alnum:]_#:@.-]+$?.
2109 * As extensions allow high-bit bytes, semicolon and period. */
2110 if(!alnumchar(c) && c != '_' && c != '-' &&
2111 c != '#' && c != ':' && c != '@' &&
2112 !((ui8_t)c & 0x80) && c != '!' && c != '.'){
2113 if(c == '$' && cp != name && *cp == '\0')
2114 break;
2115 rv = FAL0;
2116 break;
2118 NYD2_LEAVE;
2119 return rv;
2122 FL int
2123 c_alias(void *v)
2125 char const *ecp;
2126 char **argv;
2127 struct a_nag_group *ngp;
2128 int rv;
2129 NYD_ENTER;
2131 rv = 0;
2132 argv = v;
2133 n_UNINIT(ecp, NULL);
2135 if(*argv == NULL)
2136 a_nag_group_print_all(a_NAG_T_ALIAS, NULL);
2137 else if(!n_alias_is_valid_name(*argv)){
2138 ecp = N_("Not a valid alias name: %s\n");
2139 goto jerr;
2140 }else if(argv[1] == NULL){
2141 if((ngp = a_nag_group_find(a_NAG_T_ALIAS, *argv)) != NULL)
2142 a_nag_group_print(ngp, n_stdout, NULL);
2143 else{
2144 ecp = N_("No such alias: %s\n");
2145 goto jerr;
2147 }else if((ngp = a_nag_group_fetch(a_NAG_T_ALIAS, *argv, 0)) == NULL){
2148 ecp = N_("Failed to create alias storage for: %s\n");
2149 jerr:
2150 n_err(V_(ecp), n_shexp_quote_cp(*argv, FAL0));
2151 rv = 1;
2152 }else{
2153 struct a_nag_grp_names *ngnp_tail, *ngnp;
2154 struct a_nag_grp_names_head *ngnhp;
2156 a_NAG_GP_TO_SUBCLASS(ngnhp, ngp);
2158 if((ngnp_tail = ngnhp->ngnh_head) != NULL)
2159 while((ngnp = ngnp_tail->ngn_next) != NULL)
2160 ngnp_tail = ngnp;
2162 for(++argv; *argv != NULL; ++argv){
2163 size_t i;
2165 i = strlen(*argv) +1;
2166 ngnp = n_alloc(n_VSTRUCT_SIZEOF(struct a_nag_grp_names, ngn_id) + i);
2167 if(ngnp_tail != NULL)
2168 ngnp_tail->ngn_next = ngnp;
2169 else
2170 ngnhp->ngnh_head = ngnp;
2171 ngnp_tail = ngnp;
2172 ngnp->ngn_next = NULL;
2173 memcpy(ngnp->ngn_id, *argv, i);
2176 NYD_LEAVE;
2177 return rv;
2180 FL int
2181 c_unalias(void *v){
2182 char **argv;
2183 int rv;
2184 NYD_ENTER;
2186 rv = 0;
2187 argv = v;
2189 do if(!a_nag_group_del(a_NAG_T_ALIAS, *argv)){
2190 n_err(_("No such alias: %s\n"), *argv);
2191 rv = 1;
2192 }while(*++argv != NULL);
2193 NYD_LEAVE;
2194 return rv;
2197 FL int
2198 c_mlist(void *v){
2199 int rv;
2200 NYD_ENTER;
2202 rv = a_nag_mlmux(a_NAG_T_MLIST, v);
2203 NYD_LEAVE;
2204 return rv;
2207 FL int
2208 c_unmlist(void *v){
2209 int rv;
2210 NYD_ENTER;
2212 rv = a_nag_unmlmux(a_NAG_T_MLIST, v);
2213 NYD_LEAVE;
2214 return rv;
2217 FL int
2218 c_mlsubscribe(void *v){
2219 int rv;
2220 NYD_ENTER;
2222 rv = a_nag_mlmux(a_NAG_T_MLIST | a_NAG_T_SUBSCRIBE, v);
2223 NYD_LEAVE;
2224 return rv;
2227 FL int
2228 c_unmlsubscribe(void *v){
2229 int rv;
2230 NYD_ENTER;
2232 rv = a_nag_unmlmux(a_NAG_T_MLIST | a_NAG_T_SUBSCRIBE, v);
2233 NYD_LEAVE;
2234 return rv;
2237 FL enum mlist_state
2238 is_mlist(char const *name, bool_t subscribed_only){
2239 struct a_nag_group *ngp;
2240 #ifdef HAVE_REGEX
2241 struct a_nag_grp_regex **lpp, *ngrp;
2242 bool_t re2;
2243 #endif
2244 enum mlist_state rv;
2245 NYD_ENTER;
2247 ngp = a_nag_group_find(a_NAG_T_MLIST, name);
2248 rv = (ngp != NULL) ? MLIST_KNOWN : MLIST_OTHER;
2250 if(rv == MLIST_KNOWN){
2251 if(ngp->ng_type & a_NAG_T_SUBSCRIBE)
2252 rv = MLIST_SUBSCRIBED;
2253 else if(subscribed_only)
2254 rv = MLIST_OTHER;
2255 /* Of course, if that is a regular expression it doesn't mean a thing */
2256 #ifdef HAVE_REGEX
2257 if(ngp->ng_type & a_NAG_T_REGEX)
2258 rv = MLIST_OTHER;
2259 else
2260 #endif
2261 goto jleave;
2264 /* Not in the hashmap (as something matchable), walk the lists */
2265 #ifdef HAVE_REGEX
2266 re2 = FAL0;
2267 lpp = &a_nag_mlsub_regex;
2269 jregex_redo:
2270 if((ngrp = *lpp) != NULL){
2271 do if(regexec(&ngrp->ngr_regex, name, 0,NULL, 0) != REG_NOMATCH){
2272 /* Relink as the head of this list if the hit count of this group is
2273 * >= 25% of the average hit count */
2274 size_t i;
2276 if(!re2)
2277 i = ++a_nag_mlsub_hits / a_nag_mlsub_size;
2278 else
2279 i = ++a_nag_mlist_hits / a_nag_mlist_size;
2280 i >>= 2;
2282 if(++ngrp->ngr_hits >= i && *lpp != ngrp && ngrp->ngr_next != ngrp){
2283 ngrp->ngr_last->ngr_next = ngrp->ngr_next;
2284 ngrp->ngr_next->ngr_last = ngrp->ngr_last;
2285 (ngrp->ngr_last = (*lpp)->ngr_last)->ngr_next = ngrp;
2286 (ngrp->ngr_next = *lpp)->ngr_last = ngrp;
2287 *lpp = ngrp;
2289 rv = !re2 ? MLIST_SUBSCRIBED : MLIST_KNOWN;
2290 goto jleave;
2291 }while((ngrp = ngrp->ngr_next) != *lpp);
2294 if(!re2 && !subscribed_only){
2295 re2 = TRU1;
2296 lpp = &a_nag_mlist_regex;
2297 goto jregex_redo;
2299 assert(rv == MLIST_OTHER);
2300 #endif /* HAVE_REGEX */
2302 jleave:
2303 NYD_LEAVE;
2304 return rv;
2307 FL enum mlist_state
2308 is_mlist_mp(struct message *mp, enum mlist_state what){
2309 struct name *np;
2310 bool_t cc;
2311 enum mlist_state rv;
2312 NYD_ENTER;
2314 rv = MLIST_OTHER;
2316 cc = FAL0;
2317 np = lextract(hfield1("to", mp), GTO | GSKIN);
2318 jredo:
2319 for(; np != NULL; np = np->n_flink){
2320 switch(is_mlist(np->n_name, FAL0)){
2321 case MLIST_OTHER:
2322 break;
2323 case MLIST_KNOWN:
2324 if(what == MLIST_KNOWN || what == MLIST_OTHER){
2325 if(rv == MLIST_OTHER)
2326 rv = MLIST_KNOWN;
2327 if(what == MLIST_KNOWN)
2328 goto jleave;
2330 break;
2331 case MLIST_SUBSCRIBED:
2332 if(what == MLIST_SUBSCRIBED || what == MLIST_OTHER){
2333 if(rv != MLIST_SUBSCRIBED)
2334 rv = MLIST_SUBSCRIBED;
2335 goto jleave;
2337 break;
2341 if(!cc){
2342 cc = TRU1;
2343 np = lextract(hfield1("cc", mp), GCC | GSKIN);
2344 goto jredo;
2346 jleave:
2347 NYD_LEAVE;
2348 return rv;
2351 FL int
2352 c_shortcut(void *vp){
2353 struct a_nag_group *ngp;
2354 char **argv;
2355 int rv;
2356 NYD_ENTER;
2358 rv = 0;
2359 argv = vp;
2361 if(*argv == NULL)
2362 a_nag_group_print_all(a_NAG_T_SHORTCUT, NULL);
2363 else if(argv[1] == NULL){
2364 if((ngp = a_nag_group_find(a_NAG_T_SHORTCUT, *argv)) != NULL)
2365 a_nag_group_print(ngp, n_stdout, NULL);
2366 else{
2367 n_err(_("No such shortcut: %s\n"), n_shexp_quote_cp(*argv, FAL0));
2368 rv = 1;
2370 }else for(; *argv != NULL; argv += 2){
2371 /* Because one hardly ever redefines, anything is stored in one chunk */
2372 size_t l;
2373 char *cp;
2375 if(argv[1] == NULL){
2376 n_err(_("Synopsis: shortcut: <shortcut> <expansion>\n"));
2377 rv = 1;
2378 break;
2380 if(a_nag_group_find(a_NAG_T_SHORTCUT, *argv) != NULL)
2381 a_nag_group_del(a_NAG_T_SHORTCUT, *argv);
2383 l = strlen(argv[1]) +1;
2384 if((ngp = a_nag_group_fetch(a_NAG_T_SHORTCUT, *argv, l)) == NULL){
2385 n_err(_("Failed to create storage for shortcut: %s\n"),
2386 n_shexp_quote_cp(*argv, FAL0));
2387 rv = 1;
2388 }else{
2389 a_NAG_GP_TO_SUBCLASS(cp, ngp);
2390 memcpy(cp, argv[1], l);
2393 NYD_LEAVE;
2394 return rv;
2397 FL int
2398 c_unshortcut(void *vp){
2399 char **argv;
2400 int rv;
2401 NYD_ENTER;
2403 rv = 0;
2404 argv = vp;
2406 do if(!a_nag_group_del(a_NAG_T_SHORTCUT, *argv)){
2407 n_err(_("No such shortcut: %s\n"), *argv);
2408 rv = 1;
2409 }while(*++argv != NULL);
2410 NYD_LEAVE;
2411 return rv;
2414 FL char const *
2415 shortcut_expand(char const *str){
2416 struct a_nag_group *ngp;
2417 NYD_ENTER;
2419 if((ngp = a_nag_group_find(a_NAG_T_SHORTCUT, str)) != NULL)
2420 a_NAG_GP_TO_SUBCLASS(str, ngp);
2421 else
2422 str = NULL;
2423 NYD_LEAVE;
2424 return str;
2427 FL int
2428 c_charsetalias(void *vp){
2429 struct a_nag_group *ngp;
2430 char **argv;
2431 int rv;
2432 NYD_ENTER;
2434 rv = 0;
2435 argv = vp;
2437 if(*argv == NULL)
2438 a_nag_group_print_all(a_NAG_T_CHARSETALIAS, NULL);
2439 else if(argv[1] == NULL){
2440 if((ngp = a_nag_group_find(a_NAG_T_CHARSETALIAS, *argv)) != NULL)
2441 a_nag_group_print(ngp, n_stdout, NULL);
2442 else{
2443 n_err(_("No such charsetalias: %s\n"), n_shexp_quote_cp(*argv, FAL0));
2444 rv = 1;
2446 }else for(; *argv != NULL; argv += 2){
2447 /* Because one hardly ever redefines, anything is stored in one chunk */
2448 char *cp;
2449 size_t dstl;
2450 char const *dst, *src;
2452 if((dst = argv[1]) == NULL){
2453 n_err(_("Synopsis: charsetalias: <charset> <charset-alias>\n"));
2454 rv = 1;
2455 break;
2456 }else if((dst = n_iconv_normalize_name(dst)) == NULL){
2457 n_err(_("charsetalias: invalid target charset %s\n"),
2458 n_shexp_quote_cp(argv[1], FAL0));
2459 rv = 1;
2460 continue;
2461 }else if((src = n_iconv_normalize_name(argv[0])) == NULL){
2462 n_err(_("charsetalias: invalid source charset %s\n"),
2463 n_shexp_quote_cp(argv[0], FAL0));
2464 rv = 1;
2465 continue;
2468 /* Delete the old one, if any; don't get fooled to remove them all */
2469 if(src[0] != '*' || src[1] != '\0')
2470 a_nag_group_del(a_NAG_T_CHARSETALIAS, src);
2472 dstl = strlen(dst) +1;
2473 if((ngp = a_nag_group_fetch(a_NAG_T_CHARSETALIAS, src, dstl)) == NULL){
2474 n_err(_("Failed to create storage for charsetalias: %s\n"),
2475 n_shexp_quote_cp(src, FAL0));
2476 rv = 1;
2477 }else{
2478 a_NAG_GP_TO_SUBCLASS(cp, ngp);
2479 memcpy(cp, dst, dstl);
2482 NYD_LEAVE;
2483 return rv;
2486 FL int
2487 c_uncharsetalias(void *vp){
2488 char **argv, *cp;
2489 int rv;
2490 NYD_ENTER;
2492 rv = 0;
2493 argv = vp;
2496 if((cp = n_iconv_normalize_name(*argv)) == NULL ||
2497 !a_nag_group_del(a_NAG_T_CHARSETALIAS, cp)){
2498 n_err(_("No such `charsetalias': %s\n"),
2499 n_shexp_quote_cp(*argv, FAL0));
2500 rv = 1;
2502 }while(*++argv != NULL);
2503 NYD_LEAVE;
2504 return rv;
2507 FL char const *
2508 n_charsetalias_expand(char const *cp){
2509 struct a_nag_group *ngp;
2510 size_t i;
2511 char const *cp_orig;
2512 NYD_ENTER;
2514 cp_orig = cp;
2516 for(i = 0; (ngp = a_nag_group_find(a_NAG_T_CHARSETALIAS, cp)) != NULL;){
2517 a_NAG_GP_TO_SUBCLASS(cp, ngp);
2518 if(++i == 8) /* XXX Magic (same as for `ghost' expansion) */
2519 break;
2522 if(cp != cp_orig)
2523 cp = savestr(cp);
2524 NYD_LEAVE;
2525 return cp;
2528 FL int
2529 c_filetype(void *vp){ /* TODO support automatic chains: .tar.gz -> .gz + .tar */
2530 struct a_nag_group *ngp;
2531 char **argv; /* TODO While there: let ! prefix mean: direct execlp(2) */
2532 int rv;
2533 NYD_ENTER;
2535 rv = 0;
2536 argv = vp;
2538 if(*argv == NULL)
2539 a_nag_group_print_all(a_NAG_T_FILETYPE, NULL);
2540 else if(argv[1] == NULL){
2541 if((ngp = a_nag_group_find(a_NAG_T_FILETYPE, *argv)) != NULL)
2542 a_nag_group_print(ngp, n_stdout, NULL);
2543 else{
2544 n_err(_("No such filetype: %s\n"), n_shexp_quote_cp(*argv, FAL0));
2545 rv = 1;
2547 }else for(; *argv != NULL; argv += 3){
2548 /* Because one hardly ever redefines, anything is stored in one chunk */
2549 char const *ccp;
2550 char *cp, c;
2551 size_t llc, lsc;
2553 if(argv[1] == NULL || argv[2] == NULL){
2554 n_err(_("Synopsis: filetype: <extension> <load-cmd> <save-cmd>\n"));
2555 rv = 1;
2556 break;
2559 /* Delete the old one, if any; don't get fooled to remove them all */
2560 ccp = argv[0];
2561 if(ccp[0] != '*' || ccp[1] != '\0')
2562 a_nag_group_del(a_NAG_T_FILETYPE, ccp);
2564 /* Lowercase it all (for display purposes) */
2565 cp = savestr(ccp);
2566 ccp = cp;
2567 while((c = *cp) != '\0')
2568 *cp++ = lowerconv(c);
2570 llc = strlen(argv[1]) +1;
2571 lsc = strlen(argv[2]) +1;
2572 if(UIZ_MAX - llc <= lsc)
2573 goto jenomem;
2575 if((ngp = a_nag_group_fetch(a_NAG_T_FILETYPE, ccp, llc + lsc)) == NULL){
2576 jenomem:
2577 n_err(_("Failed to create storage for filetype: %s\n"),
2578 n_shexp_quote_cp(argv[0], FAL0));
2579 rv = 1;
2580 }else{
2581 struct a_nag_file_type *nftp;
2583 a_NAG_GP_TO_SUBCLASS(nftp, ngp);
2584 a_NAG_GP_TO_SUBCLASS(cp, ngp);
2585 cp += sizeof *nftp;
2586 memcpy(nftp->nft_load.s = cp, argv[1], llc);
2587 cp += llc;
2588 nftp->nft_load.l = --llc;
2589 memcpy(nftp->nft_save.s = cp, argv[2], lsc);
2590 /*cp += lsc;*/
2591 nftp->nft_save.l = --lsc;
2594 NYD_LEAVE;
2595 return rv;
2598 FL int
2599 c_unfiletype(void *vp){
2600 char **argv;
2601 int rv;
2602 NYD_ENTER;
2604 rv = 0;
2605 argv = vp;
2607 do if(!a_nag_group_del(a_NAG_T_FILETYPE, *argv)){
2608 n_err(_("No such `filetype': %s\n"), n_shexp_quote_cp(*argv, FAL0));
2609 rv = 1;
2610 }while(*++argv != NULL);
2611 NYD_LEAVE;
2612 return rv;
2615 FL bool_t
2616 n_filetype_trial(struct n_file_type *res_or_null, char const *file){
2617 struct stat stb;
2618 struct a_nag_group_lookup ngl;
2619 struct n_string s, *sp;
2620 struct a_nag_group const *ngp;
2621 ui32_t l;
2622 NYD2_ENTER;
2624 sp = n_string_creat_auto(&s);
2625 sp = n_string_assign_cp(sp, file);
2626 sp = n_string_push_c(sp, '.');
2627 l = sp->s_len;
2629 for(ngp = a_nag_group_go_first(a_NAG_T_FILETYPE, &ngl); ngp != NULL;
2630 ngp = a_nag_group_go_next(&ngl)){
2631 sp = n_string_trunc(sp, l);
2632 sp = n_string_push_buf(sp, ngp->ng_id,
2633 ngp->ng_subclass_off - ngp->ng_id_len_sub);
2635 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2636 if(res_or_null != NULL){
2637 struct a_nag_file_type *nftp;
2639 a_NAG_GP_TO_SUBCLASS(nftp, ngp);
2640 res_or_null->ft_ext_dat = ngp->ng_id;
2641 res_or_null->ft_ext_len = ngp->ng_subclass_off - ngp->ng_id_len_sub;
2642 res_or_null->ft_load_dat = nftp->nft_load.s;
2643 res_or_null->ft_load_len = nftp->nft_load.l;
2644 res_or_null->ft_save_dat = nftp->nft_save.s;
2645 res_or_null->ft_save_len = nftp->nft_save.l;
2647 goto jleave; /* TODO after v15 legacy drop: break; */
2651 /* TODO v15 legacy code: automatic file hooks for .{bz2,gz,xz},
2652 * TODO but NOT supporting *file-hook-{load,save}-EXTENSION* */
2653 ngp = (struct a_nag_group*)0x1;
2655 sp = n_string_trunc(sp, l);
2656 sp = n_string_push_buf(sp, a_nag_OBSOLETE_xz.ft_ext_dat,
2657 a_nag_OBSOLETE_xz.ft_ext_len);
2658 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2659 n_OBSOLETE(".xz support will vanish, please use the `filetype' command");
2660 if(res_or_null != NULL)
2661 *res_or_null = a_nag_OBSOLETE_xz;
2662 goto jleave;
2665 sp = n_string_trunc(sp, l);
2666 sp = n_string_push_buf(sp, a_nag_OBSOLETE_gz.ft_ext_dat,
2667 a_nag_OBSOLETE_gz.ft_ext_len);
2668 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2669 n_OBSOLETE(".gz support will vanish, please use the `filetype' command");
2670 if(res_or_null != NULL)
2671 *res_or_null = a_nag_OBSOLETE_gz;
2672 goto jleave;
2675 sp = n_string_trunc(sp, l);
2676 sp = n_string_push_buf(sp, a_nag_OBSOLETE_bz2.ft_ext_dat,
2677 a_nag_OBSOLETE_bz2.ft_ext_len);
2678 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2679 n_OBSOLETE(".bz2 support will vanish, please use the `filetype' command");
2680 if(res_or_null != NULL)
2681 *res_or_null = a_nag_OBSOLETE_bz2;
2682 goto jleave;
2685 ngp = NULL;
2687 jleave:
2688 NYD2_LEAVE;
2689 return (ngp != NULL);
2692 FL bool_t
2693 n_filetype_exists(struct n_file_type *res_or_null, char const *file){
2694 char const *ext, *lext;
2695 NYD2_ENTER;
2697 if((ext = strrchr(file, '/')) != NULL)
2698 file = ++ext;
2700 for(lext = NULL; (ext = strchr(file, '.')) != NULL; lext = file = ext){
2701 struct a_nag_group const *ngp;
2703 if((ngp = a_nag_group_find(a_NAG_T_FILETYPE, ++ext)) != NULL){
2704 lext = ext;
2705 if(res_or_null != NULL){
2706 struct a_nag_file_type *nftp;
2708 a_NAG_GP_TO_SUBCLASS(nftp, ngp);
2709 res_or_null->ft_ext_dat = ngp->ng_id;
2710 res_or_null->ft_ext_len = ngp->ng_subclass_off - ngp->ng_id_len_sub;
2711 res_or_null->ft_load_dat = nftp->nft_load.s;
2712 res_or_null->ft_load_len = nftp->nft_load.l;
2713 res_or_null->ft_save_dat = nftp->nft_save.s;
2714 res_or_null->ft_save_len = nftp->nft_save.l;
2716 goto jleave; /* TODO after v15 legacy drop: break; */
2720 /* TODO v15 legacy code: automatic file hooks for .{bz2,gz,xz},
2721 * TODO as well as supporting *file-hook-{load,save}-EXTENSION* */
2722 if(lext == NULL)
2723 goto jleave;
2725 if(!asccasecmp(lext, "xz")){
2726 n_OBSOLETE(".xz support will vanish, please use the `filetype' command");
2727 if(res_or_null != NULL)
2728 *res_or_null = a_nag_OBSOLETE_xz;
2729 goto jleave;
2730 }else if(!asccasecmp(lext, "gz")){
2731 n_OBSOLETE(".gz support will vanish, please use the `filetype' command");
2732 if(res_or_null != NULL)
2733 *res_or_null = a_nag_OBSOLETE_gz;
2734 goto jleave;
2735 }else if(!asccasecmp(lext, "bz2")){
2736 n_OBSOLETE(".bz2 support will vanish, please use the `filetype' command");
2737 if(res_or_null != NULL)
2738 *res_or_null = a_nag_OBSOLETE_bz2;
2739 goto jleave;
2740 }else{
2741 char const *cload, *csave;
2742 char *vbuf;
2743 size_t l;
2745 #undef a_X1
2746 #define a_X1 "file-hook-load-"
2747 #undef a_X2
2748 #define a_X2 "file-hook-save-"
2749 l = strlen(lext);
2750 vbuf = n_lofi_alloc(l + n_MAX(sizeof(a_X1), sizeof(a_X2)));
2752 memcpy(vbuf, a_X1, sizeof(a_X1) -1);
2753 memcpy(&vbuf[sizeof(a_X1) -1], lext, l);
2754 vbuf[sizeof(a_X1) -1 + l] = '\0';
2755 cload = n_var_vlook(vbuf, FAL0);
2757 memcpy(vbuf, a_X2, sizeof(a_X2) -1);
2758 memcpy(&vbuf[sizeof(a_X2) -1], lext, l);
2759 vbuf[sizeof(a_X2) -1 + l] = '\0';
2760 csave = n_var_vlook(vbuf, FAL0);
2762 #undef a_X2
2763 #undef a_X1
2764 n_lofi_free(vbuf);
2766 if((csave != NULL) | (cload != NULL)){
2767 n_OBSOLETE("*file-hook-{load,save}-EXTENSION* will vanish, "
2768 "please use the `filetype' command");
2770 if(((csave != NULL) ^ (cload != NULL)) == 0){
2771 if(res_or_null != NULL){
2772 res_or_null->ft_ext_dat = lext;
2773 res_or_null->ft_ext_len = l;
2774 res_or_null->ft_load_dat = cload;
2775 res_or_null->ft_load_len = strlen(cload);
2776 res_or_null->ft_save_dat = csave;
2777 res_or_null->ft_save_len = strlen(csave);
2779 goto jleave;
2780 }else
2781 n_alert(_("Incomplete *file-hook-{load,save}-EXTENSION* for: .%s"),
2782 lext);
2786 lext = NULL;
2788 jleave:
2789 NYD2_LEAVE;
2790 return (lext != NULL);
2793 /* s-it-mode */