..and.. FIX previous: do not stop list walk upon match, of course
[s-mailx.git] / nam-a-grp.c
blob31678f4e9f9b25b60e746a4c66ff257e6483fcd6
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>.
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 group_type {
44 /* Main types (bits not values for easier testing only) */
45 GT_COMMANDALIAS = 1u<<0,
46 GT_ALIAS = 1u<<1,
47 GT_MLIST = 1u<<2,
48 GT_SHORTCUT = 1u<<3,
49 GT_CHARSETALIAS = 1u<<4,
50 GT_FILETYPE = 1u<<5,
51 GT_MASK = GT_COMMANDALIAS | GT_ALIAS | GT_MLIST | GT_SHORTCUT |
52 GT_CHARSETALIAS | GT_FILETYPE,
54 /* Subtype bits and flags */
55 GT_SUBSCRIBE = 1u<<6,
56 GT_REGEX = 1u<<7,
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
64 struct group {
65 struct group *g_next;
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) \
75 do {\
76 union __group_subclass {void *gs_vp; char *gs_cp;} __gs__;\
77 __gs__.gs_cp = (char*)n_UNCONST(G) + (G)->g_subclass_off;\
78 (X) = __gs__.gs_vp;\
79 } while (0)
81 struct grp_names_head {
82 struct grp_names *gnh_head;
85 struct grp_names {
86 struct grp_names *gn_next;
87 char gn_id[n_VFIELD_SIZE(0)];
90 #ifdef HAVE_REGEX
91 struct grp_regex {
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 */
96 regex_t gr_regex;
98 #endif
100 struct a_nag_cmd_alias{
101 struct str ca_expand;
104 struct a_nag_file_type{
105 struct str nft_load;
106 struct str nft_save;
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;
128 /* `commandalias' */
129 static struct group *_commandalias_heads[HSHSIZE]; /* TODO dynamic hash */
131 /* `alias' */
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.. */
145 #ifdef HAVE_REGEX
146 static struct grp_regex *_mlist_regex, *_mlsub_regex;
147 static size_t _mlist_size, _mlist_hits, _mlsub_size, _mlsub_hits;
148 #endif
150 /* `shortcut' */
151 static struct group *_shortcut_heads[HSHSIZE]; /* TODO dynamic hashmaps! */
153 /* `charsetalias' */
154 static struct group *_charsetalias_heads[HSHSIZE];
156 /* `filetype' */
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 /* Mark all nodes with the given name */
163 static struct name *a_nag_namelist_mark_name(struct name *np, char const *name);
165 /* Grab a single name (liberal name) */
166 static char const * yankname(char const *ap, char *wbuf,
167 char const *separators, int keepcomms);
169 /* Extraction multiplexer that splits an input line to names */
170 static struct name * _extract1(char const *line, enum gfield ntype,
171 char const *separators, bool_t keepcomms);
173 /* Recursively expand a alias name. Limit expansion to some fixed level.
174 * Direct recursion is not expanded for convenience */
175 static struct name *a_nag_gexpand(size_t level, struct name *nlist,
176 struct group *gp, bool_t metoo, int ntype);
178 /* elide() helper */
179 static int a_nag_elide_qsort(void const *s1, void const *s2);
181 /* Lookup a group, return it or NULL, fill in glp anyway */
182 static struct group * _group_lookup(enum group_type gt,
183 struct group_lookup *glp, char const *id);
185 /* Easier-to-use wrapper around _group_lookup() */
186 static struct group * _group_find(enum group_type gt, char const *id);
188 /* Iteration: go to the first group, which also inits the iterator. A valid
189 * iterator can be stepped via _next(). A NULL return means no (more) groups
190 * to be iterated exist, in which case only glp->gl_group is set (NULL) */
191 static struct group * _group_go_first(enum group_type gt,
192 struct group_lookup *glp);
193 static struct group * _group_go_next(struct group_lookup *glp);
195 /* Fetch the group id, create it as necessary, fail with NULL if impossible */
196 static struct group * _group_fetch(enum group_type gt, char const *id,
197 size_t addsz);
199 /* "Intelligent" delete which handles a "*" id, too;
200 * returns a true boolean if a group was deleted, and always succeeds for "*" */
201 static bool_t _group_del(enum group_type gt, char const *id);
203 static struct group * __group_del(struct group_lookup *glp);
204 static void __names_del(struct group *gp);
206 /* Print all groups of the given type, alphasorted */
207 static void _group_print_all(enum group_type gt);
209 static int __group_print_qsorter(void const *a, void const *b);
211 /* Really print a group, actually. Return number of written lines */
212 static size_t _group_print(struct group const *gp, FILE *fo);
214 /* Multiplexers for list and subscribe commands */
215 static int _mlmux(enum group_type gt, char **argv);
216 static int _unmlmux(enum group_type gt, char **argv);
218 /* Relinkers for the sequential match lists */
219 #ifdef HAVE_REGEX
220 static void _mlmux_linkin(struct group *gp);
221 static void _mlmux_linkout(struct group *gp);
222 # define _MLMUX_LINKIN(GP) \
223 do if ((GP)->g_type & GT_REGEX) _mlmux_linkin(GP); while (0)
224 # define _MLMUX_LINKOUT(GP) \
225 do if ((GP)->g_type & GT_REGEX) _mlmux_linkout(GP); while (0)
226 #else
227 # define _MLMUX_LINKIN(GP)
228 # define _MLMUX_LINKOUT(GP)
229 #endif
231 static bool_t
232 a_nag_is_same_name(char const *n1, char const *n2){
233 char c1, c2;
234 bool_t rv;
235 NYD2_ENTER;
237 if(ok_blook(allnet)){
238 rv = TRU1;
240 c1 = *n1++;
241 c2 = *n2++;
242 c1 = lowerconv(c1);
243 c2 = lowerconv(c2);
244 if(c1 != c2){
245 rv = FAL0;
246 break;
248 }while(c1 != '\0' && c2 != '\0' && c1 != '@' && c2 != '@');
249 }else
250 rv = !asccasecmp(n1, n2);
251 NYD2_LEAVE;
252 return rv;
255 static struct name *
256 a_nag_namelist_mark_name(struct name *np, char const *name){
257 struct name *p;
258 NYD2_ENTER;
260 for(p = np; p != NULL; p = p->n_flink)
261 if(!(p->n_type & GDEL) && !(p->n_flags & (ui32_t)SI32_MIN) &&
262 a_nag_is_same_name(p->n_name, name))
263 p->n_flags |= (ui32_t)SI32_MIN;
264 NYD2_LEAVE;
265 return np;
268 static char const *
269 yankname(char const *ap, char *wbuf, char const *separators, int keepcomms)
271 char const *cp;
272 char *wp, c, inquote, lc, lastsp;
273 NYD_ENTER;
275 *(wp = wbuf) = '\0';
277 /* Skip over intermediate list trash, as in ".org> , <xy@zz.org>" */
278 for (c = *ap; blankchar(c) || c == ','; c = *++ap)
280 if (c == '\0') {
281 cp = NULL;
282 goto jleave;
285 /* Parse a full name: TODO RFC 5322
286 * - Keep everything in quotes, liberal handle *quoted-pair*s therein
287 * - Skip entire (nested) comments
288 * - In non-quote, non-comment, join adjacent space to a single SP
289 * - Understand separators only in non-quote, non-comment context,
290 * and only if not part of a *quoted-pair* (XXX too liberal) */
291 cp = ap;
292 for (inquote = lc = lastsp = 0;; lc = c, ++cp) {
293 c = *cp;
294 if (c == '\0')
295 break;
296 if (c == '\\')
297 goto jwpwc;
298 if (c == '"') {
299 if (lc != '\\')
300 inquote = !inquote;
301 #if 0 /* TODO when doing real RFC 5322 parsers - why have i done this? */
302 else
303 --wp;
304 #endif
305 goto jwpwc;
307 if (inquote || lc == '\\') {
308 jwpwc:
309 *wp++ = c;
310 lastsp = 0;
311 continue;
313 if (c == '(') {
314 ap = cp;
315 cp = skip_comment(cp + 1);
316 if (keepcomms)
317 while (ap < cp)
318 *wp++ = *ap++;
319 --cp;
320 lastsp = 0;
321 continue;
323 if (strchr(separators, c) != NULL)
324 break;
326 lc = lastsp;
327 lastsp = blankchar(c);
328 if (!lastsp || !lc)
329 *wp++ = c;
331 if (blankchar(lc))
332 --wp;
334 *wp = '\0';
335 jleave:
336 NYD_LEAVE;
337 return cp;
340 static struct name *
341 _extract1(char const *line, enum gfield ntype, char const *separators,
342 bool_t keepcomms)
344 struct name *topp, *np, *t;
345 char const *cp;
346 char *nbuf;
347 NYD_ENTER;
349 topp = NULL;
350 if (line == NULL || *line == '\0')
351 goto jleave;
353 np = NULL;
354 cp = line;
355 nbuf = smalloc(strlen(line) +1);
356 while ((cp = yankname(cp, nbuf, separators, keepcomms)) != NULL) {
357 t = nalloc(nbuf, ntype);
358 if (topp == NULL)
359 topp = t;
360 else
361 np->n_flink = t;
362 t->n_blink = np;
363 np = t;
365 free(nbuf);
366 jleave:
367 NYD_LEAVE;
368 return topp;
371 static struct name *
372 a_nag_gexpand(size_t level, struct name *nlist, struct group *gp, bool_t metoo,
373 int ntype){
374 struct grp_names *gnp;
375 struct name *nlist_tail;
376 char const *logname;
377 struct grp_names_head *gnhp;
378 NYD2_ENTER;
380 if(UICMP(z, level++, >, n_ALIAS_MAXEXP)){
381 n_err(_("Expanding alias to depth larger than %d\n"), n_ALIAS_MAXEXP);
382 goto jleave;
385 GP_TO_SUBCLASS(gnhp, gp);
386 logname = ok_vlook(LOGNAME);
388 for(gnp = gnhp->gnh_head; gnp != NULL; gnp = gnp->gn_next){
389 struct group *ngp;
390 char *cp;
392 cp = gnp->gn_id;
394 if(!strcmp(cp, gp->g_id))
395 goto jas_is;
397 if((ngp = _group_find(GT_ALIAS, cp)) != NULL){
398 /* For S-nail(1), the "alias" may *be* the sender in that a name maps
399 * to a full address specification; aliases cannot be empty */
400 struct grp_names_head *ngnhp;
401 GP_TO_SUBCLASS(ngnhp, ngp);
403 assert(ngnhp->gnh_head != NULL);
404 if(metoo || ngnhp->gnh_head->gn_next != NULL ||
405 !a_nag_is_same_name(cp, logname))
406 nlist = a_nag_gexpand(level, nlist, ngp, metoo, ntype);
407 continue;
410 /* Here we should allow to expand to itself if only person in alias */
411 jas_is:
412 if(metoo || gnhp->gnh_head->gn_next == NULL ||
413 !a_nag_is_same_name(cp, logname)){
414 struct name *np;
416 np = nalloc(cp, ntype | GFULL);
417 if((nlist_tail = nlist) != NULL){
418 while(nlist_tail->n_flink != NULL)
419 nlist_tail = nlist_tail->n_flink;
420 nlist_tail->n_flink = np;
421 np->n_blink = nlist_tail;
422 }else
423 nlist = np;
426 jleave:
427 NYD2_LEAVE;
428 return nlist;
431 static int
432 a_nag_elide_qsort(void const *s1, void const *s2){
433 struct name const * const *np1, * const *np2;
434 int rv;
435 NYD2_ENTER;
437 np1 = s1;
438 np2 = s2;
439 rv = asccasecmp((*np1)->n_name, (*np2)->n_name);
440 NYD2_LEAVE;
441 return rv;
444 static struct group *
445 _group_lookup(enum group_type gt, struct group_lookup *glp, char const *id){
446 char c1;
447 struct group *lgp, *gp;
448 NYD_ENTER;
450 gt &= GT_MASK;
451 lgp = NULL;
452 glp->gl_htable =
453 ( gt & GT_COMMANDALIAS ? _commandalias_heads
454 : (gt & GT_ALIAS ? _alias_heads
455 : (gt & GT_MLIST ? _mlist_heads
456 : (gt & GT_SHORTCUT ? _shortcut_heads
457 : (gt & GT_CHARSETALIAS ? _charsetalias_heads
458 : (/*gt & GT_FILETYPE ?*/ _filetype_heads
459 ))))));
460 gp = *(glp->gl_slot = &glp->gl_htable[
461 ((gt & (GT_MLIST | GT_CHARSETALIAS | GT_FILETYPE))
462 ? n_torek_ihash(id) : n_torek_hash(id)) % HSHSIZE]);
463 c1 = *id++;
465 if(gt & (GT_MLIST | GT_CHARSETALIAS | GT_FILETYPE)){
466 c1 = lowerconv(c1);
467 for(; gp != NULL; lgp = gp, gp = gp->g_next)
468 if((gp->g_type & gt) && *gp->g_id == c1 &&
469 !asccasecmp(&gp->g_id[1], id))
470 break;
471 }else{
472 for(; gp != NULL; lgp = gp, gp = gp->g_next)
473 if((gp->g_type & gt) && *gp->g_id == c1 && !strcmp(&gp->g_id[1], id))
474 break;
477 glp->gl_slot_last = lgp;
478 glp->gl_group = gp;
479 NYD_LEAVE;
480 return gp;
483 static struct group *
484 _group_find(enum group_type gt, char const *id)
486 struct group_lookup gl;
487 struct group *gp;
488 NYD_ENTER;
490 gp = _group_lookup(gt, &gl, id);
491 NYD_LEAVE;
492 return gp;
495 static struct group *
496 _group_go_first(enum group_type gt, struct group_lookup *glp)
498 struct group **gpa, *gp;
499 size_t i;
500 NYD_ENTER;
502 for (glp->gl_htable = gpa = (
503 gt & GT_COMMANDALIAS ? _commandalias_heads
504 : (gt & GT_ALIAS ? _alias_heads
505 : (gt & GT_MLIST ? _mlist_heads
506 : (gt & GT_SHORTCUT ? _shortcut_heads
507 : (gt & GT_CHARSETALIAS ? _charsetalias_heads
508 : (gt & GT_FILETYPE ? _filetype_heads
509 : NULL)))))
510 ), i = 0;
511 i < HSHSIZE; ++gpa, ++i)
512 if ((gp = *gpa) != NULL) {
513 glp->gl_slot = gpa;
514 glp->gl_group = gp;
515 goto jleave;
518 glp->gl_group = gp = NULL;
519 jleave:
520 glp->gl_slot_last = NULL;
521 NYD_LEAVE;
522 return gp;
525 static struct group *
526 _group_go_next(struct group_lookup *glp)
528 struct group *gp, **gpa;
529 NYD_ENTER;
531 if ((gp = glp->gl_group->g_next) != NULL)
532 glp->gl_slot_last = glp->gl_group;
533 else {
534 glp->gl_slot_last = NULL;
535 for (gpa = glp->gl_htable + HSHSIZE; ++glp->gl_slot < gpa;)
536 if ((gp = *glp->gl_slot) != NULL)
537 break;
539 glp->gl_group = gp;
540 NYD_LEAVE;
541 return gp;
544 static struct group *
545 _group_fetch(enum group_type gt, char const *id, size_t addsz)
547 struct group_lookup gl;
548 struct group *gp;
549 size_t l, i;
550 NYD_ENTER;
552 if ((gp = _group_lookup(gt, &gl, id)) != NULL)
553 goto jleave;
555 l = strlen(id) +1;
556 if (UIZ_MAX - n_ALIGN(l) <= n_ALIGN(n_VSTRUCT_SIZEOF(struct group, g_id)))
557 goto jleave;
559 i = n_ALIGN(n_VSTRUCT_SIZEOF(struct group, g_id) + l);
560 switch (gt & GT_MASK) {
561 case GT_COMMANDALIAS:
562 addsz += sizeof(struct a_nag_cmd_alias);
563 break;
564 case GT_ALIAS:
565 addsz += sizeof(struct grp_names_head);
566 break;
567 case GT_FILETYPE:
568 addsz += sizeof(struct a_nag_file_type);
569 break;
570 case GT_MLIST:
571 #ifdef HAVE_REGEX
572 if (n_is_maybe_regex(id)) {
573 addsz = sizeof(struct grp_regex);
574 gt |= GT_REGEX;
576 #endif
577 /* FALLTHRU */
578 case GT_SHORTCUT:
579 case GT_CHARSETALIAS:
580 default:
581 break;
583 if (UIZ_MAX - i < addsz || UI32_MAX <= i || UI16_MAX < i - l)
584 goto jleave;
586 gp = smalloc(i + addsz);
587 memcpy(gp->g_id, id, l);
588 gp->g_subclass_off = (ui32_t)i;
589 gp->g_id_len_sub = (ui16_t)(i - --l);
590 gp->g_type = gt;
591 if(gt & (GT_MLIST | GT_CHARSETALIAS | GT_FILETYPE)){
592 char *cp, c;
594 for(cp = gp->g_id; (c = *cp) != '\0'; ++cp)
595 *cp = lowerconv(c);
598 if (gt & GT_ALIAS) {
599 struct grp_names_head *gnhp;
601 GP_TO_SUBCLASS(gnhp, gp);
602 gnhp->gnh_head = NULL;
604 #ifdef HAVE_REGEX
605 else if (/*(gt & GT_MLIST) &&*/ gt & GT_REGEX) {
606 int s;
607 struct grp_regex *grp;
608 GP_TO_SUBCLASS(grp, gp);
610 if((s = regcomp(&grp->gr_regex, id,
611 REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0){
612 n_err(_("Invalid regular expression: %s: %s\n"),
613 n_shexp_quote_cp(id, FAL0), n_regex_err_to_doc(&grp->gr_regex, s));
614 free(gp);
615 gp = NULL;
616 goto jleave;
618 grp->gr_mygroup = gp;
619 _mlmux_linkin(gp);
621 #endif
623 gp->g_next = *gl.gl_slot;
624 *gl.gl_slot = gp;
625 jleave:
626 NYD_LEAVE;
627 return gp;
630 static bool_t
631 _group_del(enum group_type gt, char const *id)
633 enum group_type xgt = gt & GT_MASK;
634 struct group_lookup gl;
635 struct group *gp;
636 NYD_ENTER;
638 /* Delete 'em all? */
639 if (id[0] == '*' && id[1] == '\0') {
640 for (gp = _group_go_first(gt, &gl); gp != NULL;)
641 gp = (gp->g_type & xgt) ? __group_del(&gl) : _group_go_next(&gl);
642 gp = (struct group*)TRU1;
643 } else if ((gp = _group_lookup(gt, &gl, id)) != NULL) {
644 if (gp->g_type & xgt)
645 __group_del(&gl);
646 else
647 gp = NULL;
649 NYD_LEAVE;
650 return (gp != NULL);
653 static struct group *
654 __group_del(struct group_lookup *glp)
656 struct group *x, *gp;
657 NYD_ENTER;
659 /* Overly complicated: link off this node, step ahead to next.. */
660 x = glp->gl_group;
661 if((gp = glp->gl_slot_last) != NULL)
662 gp = (gp->g_next = x->g_next);
663 else{
664 glp->gl_slot_last = NULL;
665 gp = (*glp->gl_slot = x->g_next);
667 if(gp == NULL){
668 struct group **gpa;
670 for(gpa = &glp->gl_htable[HSHSIZE]; ++glp->gl_slot < gpa;)
671 if((gp = *glp->gl_slot) != NULL)
672 break;
675 glp->gl_group = gp;
677 if (x->g_type & GT_ALIAS)
678 __names_del(x);
679 #ifdef HAVE_REGEX
680 else if (/*(x->g_type & GT_MLIST) &&*/ x->g_type & GT_REGEX) {
681 struct grp_regex *grp;
682 GP_TO_SUBCLASS(grp, x);
684 regfree(&grp->gr_regex);
685 _mlmux_linkout(x);
687 #endif
689 free(x);
690 NYD_LEAVE;
691 return gp;
694 static void
695 __names_del(struct group *gp)
697 struct grp_names_head *gnhp;
698 struct grp_names *gnp;
699 NYD_ENTER;
701 GP_TO_SUBCLASS(gnhp, gp);
702 for (gnp = gnhp->gnh_head; gnp != NULL;) {
703 struct grp_names *x = gnp;
704 gnp = gnp->gn_next;
705 free(x);
707 NYD_LEAVE;
710 static void
711 _group_print_all(enum group_type gt)
713 enum group_type xgt;
714 struct group **gpa;
715 struct group const *gp;
716 ui32_t h, i;
717 char const **ida;
718 FILE *fp;
719 size_t lines;
720 NYD_ENTER;
722 xgt = gt & GT_PRINT_MASK;
723 gpa = ( xgt & GT_COMMANDALIAS ? _commandalias_heads
724 : (xgt & GT_ALIAS ? _alias_heads
725 : (xgt & GT_MLIST ? _mlist_heads
726 : (xgt & GT_SHORTCUT ? _shortcut_heads
727 : (xgt & GT_CHARSETALIAS ? _charsetalias_heads
728 : (xgt & GT_FILETYPE ? _filetype_heads
729 : NULL))))));
731 for (h = 0, i = 1; h < HSHSIZE; ++h)
732 for (gp = gpa[h]; gp != NULL; gp = gp->g_next)
733 if ((gp->g_type & xgt) == xgt)
734 ++i;
735 ida = salloc(i * sizeof *ida);
737 for (i = h = 0; h < HSHSIZE; ++h)
738 for (gp = gpa[h]; gp != NULL; gp = gp->g_next)
739 if ((gp->g_type & xgt) == xgt)
740 ida[i++] = gp->g_id;
741 ida[i] = NULL;
743 if (i > 1)
744 qsort(ida, i, sizeof *ida, &__group_print_qsorter);
746 if ((fp = Ftmp(NULL, "prgroup", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL)
747 fp = n_stdout;
748 lines = 0;
750 for (i = 0; ida[i] != NULL; ++i)
751 lines += _group_print(_group_find(gt, ida[i]), fp);
752 #ifdef HAVE_REGEX
753 if (gt & GT_MLIST) {
754 if (gt & GT_SUBSCRIBE)
755 i = (ui32_t)_mlsub_size, h = (ui32_t)_mlsub_hits;
756 else
757 i = (ui32_t)_mlist_size, h = (ui32_t)_mlist_hits;
758 if (i > 0 && (n_poption & n_PO_D_V)){
759 fprintf(fp, _("# %s list regex(7) total: %u entries, %u hits\n"),
760 (gt & GT_SUBSCRIBE ? _("Subscribed") : _("Non-subscribed")),
761 i, h);
762 ++lines;
765 #endif
767 if (fp != n_stdout) {
768 page_or_print(fp, lines);
769 Fclose(fp);
771 NYD_LEAVE;
774 static int
775 __group_print_qsorter(void const *a, void const *b)
777 int rv;
778 NYD_ENTER;
780 rv = strcmp(*(char**)n_UNCONST(a), *(char**)n_UNCONST(b));
781 NYD_LEAVE;
782 return rv;
785 static size_t
786 _group_print(struct group const *gp, FILE *fo)
788 char const *cp;
789 size_t rv;
790 NYD_ENTER;
792 rv = 1;
794 if(gp->g_type & GT_COMMANDALIAS){
795 struct a_nag_cmd_alias *ncap;
797 GP_TO_SUBCLASS(ncap, gp);
798 fprintf(fo, "commandalias %s %s\n",
799 n_shexp_quote_cp(gp->g_id, TRU1),
800 n_shexp_quote_cp(ncap->ca_expand.s, TRU1));
801 } else if (gp->g_type & GT_ALIAS) {
802 struct grp_names_head *gnhp;
803 struct grp_names *gnp;
805 fprintf(fo, "alias %s ", gp->g_id);
807 GP_TO_SUBCLASS(gnhp, gp);
808 if ((gnp = gnhp->gnh_head) != NULL) { /* xxx always 1+ entries */
809 do {
810 struct grp_names *x = gnp;
811 gnp = gnp->gn_next;
812 fprintf(fo, " \"%s\"", string_quote(x->gn_id)); /* TODO shexp */
813 } while (gnp != NULL);
815 putc('\n', fo);
816 } else if (gp->g_type & GT_MLIST) {
817 #ifdef HAVE_REGEX
818 if ((gp->g_type & GT_REGEX) && (n_poption & n_PO_D_V)){
819 size_t i;
820 struct grp_regex *grp,
821 *lp = (gp->g_type & GT_SUBSCRIBE ? _mlsub_regex : _mlist_regex);
823 GP_TO_SUBCLASS(grp, gp);
824 for (i = 1; lp != grp; lp = lp->gr_next)
825 ++i;
826 fprintf(fo, "# regex(7): hits %" PRIuZ ", sort %" PRIuZ ".\n ",
827 grp->gr_hits, i);
828 ++rv;
830 #endif
832 fprintf(fo, "wysh %s %s\n",
833 (gp->g_type & GT_SUBSCRIBE ? "mlsubscribe" : "mlist"),
834 n_shexp_quote_cp(gp->g_id, TRU1));
835 } else if (gp->g_type & GT_SHORTCUT) {
836 GP_TO_SUBCLASS(cp, gp);
837 fprintf(fo, "wysh shortcut %s %s\n",
838 gp->g_id, n_shexp_quote_cp(cp, TRU1));
839 } else if (gp->g_type & GT_CHARSETALIAS) {
840 GP_TO_SUBCLASS(cp, gp);
841 fprintf(fo, "charsetalias %s %s\n",
842 n_shexp_quote_cp(gp->g_id, TRU1), n_shexp_quote_cp(cp, TRU1));
843 } else if (gp->g_type & GT_FILETYPE) {
844 struct a_nag_file_type *nftp;
846 GP_TO_SUBCLASS(nftp, gp);
847 fprintf(fo, "filetype %s %s %s\n",
848 n_shexp_quote_cp(gp->g_id, TRU1),
849 n_shexp_quote_cp(nftp->nft_load.s, TRU1),
850 n_shexp_quote_cp(nftp->nft_save.s, TRU1));
853 NYD_LEAVE;
854 return rv;
857 static int
858 _mlmux(enum group_type gt, char **argv)
860 char const *ecp;
861 struct group *gp;
862 int rv = 0;
863 NYD_ENTER;
865 rv = 0;
866 n_UNINIT(ecp, NULL);
868 if (*argv == NULL)
869 _group_print_all(gt);
870 else do {
871 if ((gp = _group_find(gt, *argv)) != NULL) {
872 if (gt & GT_SUBSCRIBE) {
873 if (!(gp->g_type & GT_SUBSCRIBE)) {
874 _MLMUX_LINKOUT(gp);
875 gp->g_type |= GT_SUBSCRIBE;
876 _MLMUX_LINKIN(gp);
877 } else {
878 ecp = N_("Mailing-list already `mlsubscribe'd: %s\n");
879 goto jerr;
881 } else {
882 ecp = N_("Mailing-list already `mlist'ed: %s\n");
883 goto jerr;
885 } else if(_group_fetch(gt, *argv, 0) == NULL) {
886 ecp = N_("Failed to create storage for mailing-list: %s\n");
887 jerr:
888 n_err(V_(ecp), n_shexp_quote_cp(*argv, FAL0));
889 rv = 1;
891 } while (*++argv != NULL);
893 NYD_LEAVE;
894 return rv;
897 static int
898 _unmlmux(enum group_type gt, char **argv)
900 struct group *gp;
901 int rv = 0;
902 NYD_ENTER;
904 for (; *argv != NULL; ++argv) {
905 if (gt & GT_SUBSCRIBE) {
906 struct group_lookup gl;
907 bool_t isaster;
909 if (!(isaster = (**argv == '*')))
910 gp = _group_find(gt, *argv);
911 else if ((gp = _group_go_first(gt, &gl)) == NULL)
912 continue;
913 else if (gp != NULL && !(gp->g_type & GT_SUBSCRIBE))
914 goto jaster_entry;
916 if (gp != NULL) {
917 jaster_redo:
918 if (gp->g_type & GT_SUBSCRIBE) {
919 _MLMUX_LINKOUT(gp);
920 gp->g_type &= ~GT_SUBSCRIBE;
921 _MLMUX_LINKIN(gp);
922 if (isaster) {
923 jaster_entry:
924 while ((gp = _group_go_next(&gl)) != NULL &&
925 !(gp->g_type & GT_SUBSCRIBE))
927 if (gp != NULL)
928 goto jaster_redo;
930 } else {
931 n_err(_("Mailing-list not `mlsubscribe'd: %s\n"),
932 n_shexp_quote_cp(*argv, FAL0));
933 rv = 1;
935 continue;
937 } else if (_group_del(gt, *argv))
938 continue;
939 n_err(_("No such mailing-list: %s\n"), n_shexp_quote_cp(*argv, FAL0));
940 rv = 1;
942 NYD_LEAVE;
943 return rv;
946 #ifdef HAVE_REGEX
947 static void
948 _mlmux_linkin(struct group *gp)
950 struct grp_regex **lpp, *grp, *lhp;
951 NYD_ENTER;
953 if (gp->g_type & GT_SUBSCRIBE) {
954 lpp = &_mlsub_regex;
955 ++_mlsub_size;
956 } else {
957 lpp = &_mlist_regex;
958 ++_mlist_size;
961 GP_TO_SUBCLASS(grp, gp);
962 if ((lhp = *lpp) != NULL) {
963 (grp->gr_last = lhp->gr_last)->gr_next = grp;
964 (grp->gr_next = lhp)->gr_last = grp;
965 } else
966 *lpp = grp->gr_last = grp->gr_next = grp;
967 grp->gr_hits = 0;
968 NYD_LEAVE;
971 static void
972 _mlmux_linkout(struct group *gp)
974 struct grp_regex *grp, **lpp;
975 NYD_ENTER;
977 GP_TO_SUBCLASS(grp, gp);
979 if (gp->g_type & GT_SUBSCRIBE) {
980 lpp = &_mlsub_regex;
981 --_mlsub_size;
982 _mlsub_hits -= grp->gr_hits;
983 } else {
984 lpp = &_mlist_regex;
985 --_mlist_size;
986 _mlist_hits -= grp->gr_hits;
989 if (grp->gr_next == grp)
990 *lpp = NULL;
991 else {
992 (grp->gr_last->gr_next = grp->gr_next)->gr_last = grp->gr_last;
993 if (*lpp == grp)
994 *lpp = grp->gr_next;
996 NYD_LEAVE;
998 #endif /* HAVE_REGEX */
1000 FL struct name *
1001 nalloc(char const *str, enum gfield ntype)
1003 struct n_addrguts ag;
1004 struct str in, out;
1005 struct name *np;
1006 NYD_ENTER;
1007 assert(!(ntype & GFULLEXTRA) || (ntype & GFULL) != 0);
1009 str = n_addrspec_with_guts(&ag, str,
1010 ((ntype & (GFULL | GSKIN | GREF)) != 0), FAL0);
1011 if(str == NULL){
1013 np = NULL; TODO We cannot return NULL,
1014 goto jleave; TODO thus handle failures in here!
1016 str = ag.ag_input;
1019 if (!(ag.ag_n_flags & NAME_NAME_SALLOC)) {
1020 ag.ag_n_flags |= NAME_NAME_SALLOC;
1021 np = salloc(sizeof(*np) + ag.ag_slen +1);
1022 memcpy(np + 1, ag.ag_skinned, ag.ag_slen +1);
1023 ag.ag_skinned = (char*)(np + 1);
1024 } else
1025 np = salloc(sizeof *np);
1027 np->n_flink = NULL;
1028 np->n_blink = NULL;
1029 np->n_type = ntype;
1030 np->n_flags = 0;
1032 np->n_fullname = np->n_name = ag.ag_skinned;
1033 np->n_fullextra = NULL;
1034 np->n_flags = ag.ag_n_flags;
1036 if (ntype & GFULL) {
1037 if (ag.ag_ilen == ag.ag_slen
1038 #ifdef HAVE_IDNA
1039 && !(ag.ag_n_flags & NAME_IDNA)
1040 #endif
1042 goto jleave;
1043 if (ag.ag_n_flags & NAME_ADDRSPEC_ISFILEORPIPE)
1044 goto jleave;
1046 /* n_fullextra is only the complete name part without address.
1047 * Beware of "-r '<abc@def>'", don't treat that as FULLEXTRA */
1048 if ((ntype & GFULLEXTRA) && ag.ag_ilen > ag.ag_slen + 2) {
1049 size_t s = ag.ag_iaddr_start, e = ag.ag_iaddr_aend, i;
1050 char const *cp;
1052 if (s == 0 || str[--s] != '<' || str[e++] != '>')
1053 goto jskipfullextra;
1054 i = ag.ag_ilen - e;
1055 in.s = n_lofi_alloc(s + 1 + i +1);
1056 while(s > 0 && blankchar(str[s - 1]))
1057 --s;
1058 memcpy(in.s, str, s);
1059 if (i > 0) {
1060 in.s[s++] = ' ';
1061 while (blankchar(str[e])) {
1062 ++e;
1063 if (--i == 0)
1064 break;
1066 if (i > 0)
1067 memcpy(&in.s[s], &str[e], i);
1069 s += i;
1070 in.s[in.l = s] = '\0';
1071 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
1073 for (cp = out.s, i = out.l; i > 0 && spacechar(*cp); --i, ++cp)
1075 while (i > 0 && spacechar(cp[i - 1]))
1076 --i;
1077 np->n_fullextra = savestrbuf(cp, i);
1079 n_lofi_free(in.s);
1080 free(out.s);
1082 jskipfullextra:
1084 /* n_fullname depends on IDNA conversion */
1085 #ifdef HAVE_IDNA
1086 if (!(ag.ag_n_flags & NAME_IDNA)) {
1087 #endif
1088 in.s = n_UNCONST(str);
1089 in.l = ag.ag_ilen;
1090 #ifdef HAVE_IDNA
1091 } else {
1092 /* The domain name was IDNA and has been converted. We also have to
1093 * ensure that the domain name in .n_fullname is replaced with the
1094 * converted version, since MIME doesn't perform encoding of addrs */
1095 /* TODO This definetely doesn't belong here! */
1096 size_t l = ag.ag_iaddr_start,
1097 lsuff = ag.ag_ilen - ag.ag_iaddr_aend;
1098 in.s = ac_alloc(l + ag.ag_slen + lsuff +1);
1099 memcpy(in.s, str, l);
1100 memcpy(in.s + l, ag.ag_skinned, ag.ag_slen);
1101 l += ag.ag_slen;
1102 memcpy(in.s + l, str + ag.ag_iaddr_aend, lsuff);
1103 l += lsuff;
1104 in.s[l] = '\0';
1105 in.l = l;
1107 #endif
1108 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
1109 np->n_fullname = savestr(out.s);
1110 free(out.s);
1111 #ifdef HAVE_IDNA
1112 if (ag.ag_n_flags & NAME_IDNA)
1113 ac_free(in.s);
1114 #endif
1115 np->n_flags |= NAME_FULLNAME_SALLOC;
1117 jleave:
1118 NYD_LEAVE;
1119 return np;
1122 FL struct name *
1123 ndup(struct name *np, enum gfield ntype)
1125 struct name *nnp;
1126 NYD_ENTER;
1128 if ((ntype & (GFULL | GSKIN)) && !(np->n_flags & NAME_SKINNED)) {
1129 nnp = nalloc(np->n_name, ntype);
1130 goto jleave;
1133 nnp = salloc(sizeof *np);
1134 nnp->n_flink = nnp->n_blink = NULL;
1135 nnp->n_type = ntype;
1136 nnp->n_flags = (np->n_flags & ~(NAME_NAME_SALLOC | NAME_FULLNAME_SALLOC)) |
1137 NAME_NAME_SALLOC;
1138 nnp->n_name = savestr(np->n_name);
1139 if (np->n_name == np->n_fullname || !(ntype & (GFULL | GSKIN))) {
1140 nnp->n_fullname = nnp->n_name;
1141 nnp->n_fullextra = NULL;
1142 } else {
1143 nnp->n_flags |= NAME_FULLNAME_SALLOC;
1144 nnp->n_fullname = savestr(np->n_fullname);
1145 nnp->n_fullextra = (np->n_fullextra == NULL) ? NULL
1146 : savestr(np->n_fullextra);
1148 jleave:
1149 NYD_LEAVE;
1150 return nnp;
1153 FL struct name *
1154 cat(struct name *n1, struct name *n2)
1156 struct name *tail;
1157 NYD_ENTER;
1159 tail = n2;
1160 if (n1 == NULL)
1161 goto jleave;
1162 tail = n1;
1163 if (n2 == NULL)
1164 goto jleave;
1166 while (tail->n_flink != NULL)
1167 tail = tail->n_flink;
1168 tail->n_flink = n2;
1169 n2->n_blink = tail;
1170 tail = n1;
1171 jleave:
1172 NYD_LEAVE;
1173 return tail;
1176 FL struct name *
1177 namelist_dup(struct name const *np, enum gfield ntype){
1178 struct name *nlist, *xnp;
1179 NYD2_ENTER;
1181 for(nlist = xnp = NULL; np != NULL; np = np->n_flink){
1182 struct name *x;
1184 x = ndup(n_UNCONST(np), (np->n_type & ~GMASK) | ntype);
1185 if((x->n_blink = xnp) == NULL)
1186 nlist = x;
1187 else
1188 xnp->n_flink = x;
1189 xnp = x;
1191 NYD2_LEAVE;
1192 return nlist;
1195 FL ui32_t
1196 count(struct name const *np)
1198 ui32_t c;
1199 NYD_ENTER;
1201 for (c = 0; np != NULL; np = np->n_flink)
1202 if (!(np->n_type & GDEL))
1203 ++c;
1204 NYD_LEAVE;
1205 return c;
1208 FL ui32_t
1209 count_nonlocal(struct name const *np)
1211 ui32_t c;
1212 NYD_ENTER;
1214 for (c = 0; np != NULL; np = np->n_flink)
1215 if (!(np->n_type & GDEL) && !(np->n_flags & NAME_ADDRSPEC_ISFILEORPIPE))
1216 ++c;
1217 NYD_LEAVE;
1218 return c;
1221 FL struct name *
1222 extract(char const *line, enum gfield ntype)
1224 struct name *rv;
1225 NYD_ENTER;
1227 rv = _extract1(line, ntype, " \t,", 0);
1228 NYD_LEAVE;
1229 return rv;
1232 FL struct name *
1233 lextract(char const *line, enum gfield ntype)
1235 struct name *rv;
1236 NYD_ENTER;
1238 rv = ((line != NULL && strpbrk(line, ",\"\\(<|"))
1239 ? _extract1(line, ntype, ",", 1) : extract(line, ntype));
1240 NYD_LEAVE;
1241 return rv;
1244 FL char *
1245 detract(struct name *np, enum gfield ntype)
1247 char *topp, *cp;
1248 struct name *p;
1249 int flags, s;
1250 NYD_ENTER;
1252 topp = NULL;
1253 if (np == NULL)
1254 goto jleave;
1256 flags = ntype & (GCOMMA | GNAMEONLY);
1257 ntype &= ~(GCOMMA | GNAMEONLY);
1258 s = 0;
1260 for (p = np; p != NULL; p = p->n_flink) {
1261 if (ntype && (p->n_type & GMASK) != ntype)
1262 continue;
1263 s += strlen(flags & GNAMEONLY ? p->n_name : p->n_fullname) +1;
1264 if (flags & GCOMMA)
1265 ++s;
1267 if (s == 0)
1268 goto jleave;
1270 s += 2;
1271 topp = salloc(s);
1272 cp = topp;
1273 for (p = np; p != NULL; p = p->n_flink) {
1274 if (ntype && (p->n_type & GMASK) != ntype)
1275 continue;
1276 cp = sstpcpy(cp, (flags & GNAMEONLY ? p->n_name : p->n_fullname));
1277 if ((flags & GCOMMA) && p->n_flink != NULL)
1278 *cp++ = ',';
1279 *cp++ = ' ';
1281 *--cp = 0;
1282 if ((flags & GCOMMA) && *--cp == ',')
1283 *cp = 0;
1284 jleave:
1285 NYD_LEAVE;
1286 return topp;
1289 FL struct name *
1290 grab_names(enum n_go_input_flags gif, char const *field, struct name *np,
1291 int comma, enum gfield gflags)
1293 struct name *nq;
1294 NYD_ENTER;
1296 jloop:
1297 np = lextract(n_go_input_cp(gif, field, detract(np, comma)), gflags);
1298 for (nq = np; nq != NULL; nq = nq->n_flink)
1299 if (is_addr_invalid(nq, EACM_NONE))
1300 goto jloop;
1301 NYD_LEAVE;
1302 return np;
1305 FL bool_t
1306 name_is_same_domain(struct name const *n1, struct name const *n2)
1308 char const *d1, *d2;
1309 bool_t rv;
1310 NYD_ENTER;
1312 d1 = strrchr(n1->n_name, '@');
1313 d2 = strrchr(n2->n_name, '@');
1315 rv = (d1 != NULL && d2 != NULL) ? !asccasecmp(++d1, ++d2) : FAL0;
1317 NYD_LEAVE;
1318 return rv;
1321 FL struct name *
1322 checkaddrs(struct name *np, enum expand_addr_check_mode eacm,
1323 si8_t *set_on_error)
1325 struct name *n;
1326 NYD_ENTER;
1328 for (n = np; n != NULL; n = n->n_flink) {
1329 si8_t rv;
1331 if ((rv = is_addr_invalid(n, eacm)) != 0) {
1332 if (set_on_error != NULL)
1333 *set_on_error |= rv; /* don't loose -1! */
1334 else if (eacm & EAF_MAYKEEP) /* TODO HACK! See definition! */
1335 continue;
1336 if (n->n_blink)
1337 n->n_blink->n_flink = n->n_flink;
1338 if (n->n_flink)
1339 n->n_flink->n_blink = n->n_blink;
1340 if (n == np)
1341 np = n->n_flink;
1344 NYD_LEAVE;
1345 return np;
1348 FL struct name *
1349 namelist_vaporise_head(struct header *hp, enum expand_addr_check_mode eacm,
1350 bool_t metoo, si8_t *set_on_error)
1352 /* TODO namelist_vaporise_head() is incredibly expensive and redundant */
1353 struct name *tolist, *np, **npp;
1354 NYD_ENTER;
1356 tolist = cat(hp->h_to, cat(hp->h_cc, hp->h_bcc));
1357 hp->h_to = hp->h_cc = hp->h_bcc = NULL;
1359 tolist = usermap(tolist, metoo);
1360 tolist = n_alternates_remove(tolist, TRU1);
1361 tolist = elide(checkaddrs(tolist, eacm, set_on_error));
1363 for (np = tolist; np != NULL; np = np->n_flink) {
1364 switch (np->n_type & (GDEL | GMASK)) {
1365 case GTO: npp = &hp->h_to; break;
1366 case GCC: npp = &hp->h_cc; break;
1367 case GBCC: npp = &hp->h_bcc; break;
1368 default: continue;
1370 *npp = cat(*npp, ndup(np, np->n_type | GFULL));
1372 NYD_LEAVE;
1373 return tolist;
1376 FL struct name *
1377 usermap(struct name *names, bool_t force_metoo){
1378 struct group *gp;
1379 struct name *nlist, *nlist_tail, *np, *cp;
1380 int metoo;
1381 NYD_ENTER;
1383 metoo = (force_metoo || ok_blook(metoo));
1384 nlist = nlist_tail = NULL;
1385 np = names;
1387 for(; np != NULL; np = cp){
1388 assert(!(np->n_type & GDEL)); /* TODO legacy */
1389 cp = np->n_flink;
1391 if(is_fileorpipe_addr(np) ||
1392 (gp = _group_find(GT_ALIAS, np->n_name)) == NULL){
1393 if((np->n_blink = nlist_tail) != NULL)
1394 nlist_tail->n_flink = np;
1395 else
1396 nlist = np;
1397 nlist_tail = np;
1398 np->n_flink = NULL;
1399 }else{
1400 nlist = a_nag_gexpand(0, nlist, gp, metoo, np->n_type);
1401 if((nlist_tail = nlist) != NULL)
1402 while(nlist_tail->n_flink != NULL)
1403 nlist_tail = nlist_tail->n_flink;
1406 NYD_LEAVE;
1407 return nlist;
1410 FL struct name *
1411 elide(struct name *names)
1413 size_t i, j, k;
1414 struct name *nlist, *np, **nparr;
1415 NYD_ENTER;
1417 nlist = NULL;
1419 if(names == NULL)
1420 goto jleave;
1422 /* Throw away all deleted nodes */
1423 for(np = NULL, i = 0; names != NULL; names = names->n_flink)
1424 if(!(names->n_type & GDEL)){
1425 names->n_blink = np;
1426 if(np != NULL)
1427 np->n_flink = names;
1428 else
1429 nlist = names;
1430 np = names;
1431 ++i;
1433 if(nlist == NULL || i == 1)
1434 goto jleave;
1435 np->n_flink = NULL;
1437 /* Create a temporay array and sort that */
1438 nparr = n_lofi_alloc(sizeof(*nparr) * i);
1440 for(i = 0, np = nlist; np != NULL; np = np->n_flink)
1441 nparr[i++] = np;
1443 qsort(nparr, i, sizeof *nparr, &a_nag_elide_qsort);
1445 /* Remove duplicates XXX speedup, or list_uniq()! */
1446 for(j = 0, --i; j < i;){
1447 if(asccasecmp(nparr[j]->n_name, nparr[k = j + 1]->n_name))
1448 ++j;
1449 else{
1450 for(; k < i; ++k)
1451 nparr[k] = nparr[k + 1];
1452 --i;
1456 /* Throw away all list members which are not part of the array.
1457 * Note this keeps the original, possibly carefully crafted, order of the
1458 * addressees, thus */
1459 for(np = nlist; np != NULL; np = np->n_flink){
1460 for(j = 0; j <= i; ++j)
1461 if(np == nparr[j]){
1462 nparr[j] = NULL;
1463 goto jiter;
1465 /* Drop it */
1466 if(np == nlist){
1467 nlist = np->n_flink;
1468 np->n_blink = NULL;
1469 }else
1470 np->n_blink->n_flink = np->n_flink;
1471 if(np->n_flink != NULL)
1472 np->n_flink->n_blink = np->n_blink;
1473 jiter:;
1476 n_lofi_free(nparr);
1477 jleave:
1478 NYD_LEAVE;
1479 return nlist;
1482 FL int
1483 c_alternates(void *v){ /* TODO use a hashmap!! */
1484 char **argv;
1485 int rv;
1486 NYD_ENTER;
1488 rv = 0;
1490 if(*(argv = v) == NULL){
1491 char const *ccp;
1493 if((ccp = ok_vlook(alternates)) != NULL)
1494 fprintf(n_stdout, "alternates %s\n", ccp);
1495 else
1496 fputs(_("# no alternates registered\n"), n_stdout);
1497 }else{
1498 char *cp;
1499 size_t l, vl;
1500 struct n_strlist *slp, **slpa;
1502 while((slp = a_nag_altnames) != NULL){
1503 a_nag_altnames = slp->sl_next;
1504 n_free(slp);
1506 vl = 0;
1508 /* Extension: only clearance? */
1509 if(argv[1] == NULL && argv[0][0] == '-' && argv[0][1] == '\0')
1510 n_UNINIT(cp, NULL);
1511 else for(slpa = &a_nag_altnames; *argv != NULL; ++argv){
1512 if(**argv != '\0'){
1513 struct name *np;
1515 if((np = lextract(*argv, GSKIN)) == NULL || np->n_flink != NULL ||
1516 (np = checkaddrs(np, EACM_STRICT, NULL)) == NULL){
1517 n_err(_("Invalid `alternates' argument: %s\n"),
1518 n_shexp_quote_cp(*argv, FAL0));
1519 rv = 1;
1520 continue;
1523 l = strlen(np->n_name);
1524 if(UIZ_MAX - l <= vl){
1525 n_err(_("Failed to create storage for alternate: %s\n"),
1526 n_shexp_quote_cp(*argv, FAL0));
1527 rv = 1;
1528 continue;
1531 slp = n_STRLIST_ALLOC(l);
1532 slp->sl_next = NULL;
1533 slp->sl_len = l;
1534 memcpy(slp->sl_dat, np->n_name, ++l);
1535 *slpa = slp;
1536 slpa = &slp->sl_next;
1537 vl += l;
1541 /* And put it into *alternates* */
1542 if(vl > 0){
1543 cp = n_autorec_alloc(vl);
1544 for(vl = 0, slp = a_nag_altnames; slp != NULL; slp = slp->sl_next){
1545 memcpy(&cp[vl], slp->sl_dat, slp->sl_len);
1546 cp[vl += slp->sl_len] = ' ';
1547 ++vl;
1549 cp[vl - 1] = '\0';
1552 n_PS_ROOT_BLOCK(vl > 0 ? ok_vset(alternates, cp) : ok_vclear(alternates));
1554 NYD_LEAVE;
1555 return rv;
1558 FL struct name *
1559 n_alternates_remove(struct name *np, bool_t keep_single){
1560 struct name *xp, *newnp;
1561 NYD_ENTER;
1563 /* Delete the temporary bit from all */
1564 for(xp = np; xp != NULL; xp = xp->n_flink)
1565 xp->n_flags &= ~(ui32_t)SI32_MIN;
1567 /* Mark all possible alternate names (xxx sic) */
1569 if(a_nag_altnames != NULL){
1570 struct n_strlist *slp;
1572 for(slp = a_nag_altnames; slp != NULL; slp = slp->sl_next)
1573 np = a_nag_namelist_mark_name(np, slp->sl_dat);
1576 np = a_nag_namelist_mark_name(np, ok_vlook(LOGNAME));
1578 for(xp = lextract(ok_vlook(from), GEXTRA | GSKIN); xp != NULL;
1579 xp = xp->n_flink)
1580 np = a_nag_namelist_mark_name(np, xp->n_name);
1582 for(xp = extract(ok_vlook(sender), GEXTRA | GSKIN); xp != NULL;
1583 xp = xp->n_flink)
1584 np = a_nag_namelist_mark_name(np, xp->n_name);
1586 for(xp = lextract(ok_vlook(replyto), GEXTRA | GSKIN); xp != NULL;
1587 xp = xp->n_flink)
1588 np = a_nag_namelist_mark_name(np, xp->n_name);
1590 /* GDEL all (but a single) marked node(s) */
1591 for(xp = np; xp != NULL; xp = xp->n_flink)
1592 if(xp->n_flags & (ui32_t)SI32_MIN){
1593 if(!keep_single)
1594 xp->n_type |= GDEL;
1595 keep_single = FAL0;
1598 /* Clean the list by throwing away all deleted nodes */
1599 for(xp = newnp = NULL; np != NULL; np = np->n_flink)
1600 if(!(np->n_type & GDEL)){
1601 np->n_blink = xp;
1602 if(xp != NULL)
1603 xp->n_flink = np;
1604 else
1605 newnp = np;
1606 xp = np;
1608 np = newnp;
1610 /* Delete the temporary bit from all remaining (again) */
1611 for(xp = np; xp != NULL; xp = xp->n_flink)
1612 xp->n_flags &= ~(ui32_t)SI32_MIN;
1614 NYD_LEAVE;
1615 return np;
1618 FL bool_t
1619 n_is_myname(char const *name){
1620 struct name *xp;
1621 NYD_ENTER;
1623 if(a_nag_is_same_name(ok_vlook(LOGNAME), name))
1624 goto jleave;
1626 if(a_nag_altnames != NULL){
1627 struct n_strlist *slp;
1629 for(slp = a_nag_altnames; slp != NULL; slp = slp->sl_next)
1630 if(a_nag_is_same_name(slp->sl_dat, name))
1631 goto jleave;
1634 for(xp = lextract(ok_vlook(from), GEXTRA | GSKIN); xp != NULL;
1635 xp = xp->n_flink)
1636 if(a_nag_is_same_name(xp->n_name, name))
1637 goto jleave;
1639 for(xp = lextract(ok_vlook(replyto), GEXTRA | GSKIN); xp != NULL;
1640 xp = xp->n_flink)
1641 if(a_nag_is_same_name(xp->n_name, name))
1642 goto jleave;
1644 for(xp = extract(ok_vlook(sender), GEXTRA | GSKIN); xp != NULL;
1645 xp = xp->n_flink)
1646 if(a_nag_is_same_name(xp->n_name, name))
1647 goto jleave;
1649 name = NULL;
1650 jleave:
1651 NYD_LEAVE;
1652 return (name != NULL);
1655 FL int
1656 c_addrcodec(void *vp){
1657 struct n_addrguts ag;
1658 struct n_string s_b, *sp;
1659 size_t alen;
1660 int mode;
1661 char const **argv, *varname, *act, *cp;
1662 NYD_ENTER;
1664 sp = n_string_creat_auto(&s_b);
1665 argv = vp;
1666 varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
1668 act = *argv;
1669 for(cp = act; *cp != '\0' && !blankspacechar(*cp); ++cp)
1671 mode = 0;
1672 if(*act == '+')
1673 mode = 1, ++act;
1674 if(*act == '+')
1675 mode = 2, ++act;
1676 if(*act == '+')
1677 mode = 3, ++act;
1678 if(act >= cp)
1679 goto jesynopsis;
1680 alen = PTR2SIZE(cp - act);
1681 if(*cp != '\0')
1682 ++cp;
1684 /* C99 */{
1685 size_t i;
1687 i = strlen(cp);
1688 if(i <= UIZ_MAX / 4)
1689 i <<= 1;
1690 sp = n_string_reserve(sp, i);
1693 n_pstate_err_no = n_ERR_NONE;
1695 if(is_ascncaseprefix(act, "encode", alen)){
1696 /* This function cannot be a simple nalloc() wrapper even later on, since
1697 * we may need to turn any ", () or \ into quoted-pairs */
1698 char c;
1700 while((c = *cp++) != '\0'){
1701 if(((c == '(' || c == ')') && mode < 1) || (c == '"' && mode < 2) ||
1702 (c == '\\' && mode < 3))
1703 sp = n_string_push_c(sp, '\\');
1704 sp = n_string_push_c(sp, c);
1707 if(n_addrspec_with_guts(&ag, n_string_cp(sp), TRU1, TRU1) == NULL ||
1708 (ag.ag_n_flags & (NAME_ADDRSPEC_ISADDR | NAME_ADDRSPEC_INVALID)
1709 ) != NAME_ADDRSPEC_ISADDR){
1710 cp = sp->s_dat;
1711 n_pstate_err_no = n_ERR_INVAL;
1712 vp = NULL;
1713 }else{
1714 struct name *np;
1716 np = nalloc(ag.ag_input, GTO | GFULL | GSKIN);
1717 cp = np->n_fullname;
1719 }else if(mode == 0){
1720 if(is_ascncaseprefix(act, "decode", alen)){
1721 char c;
1723 while((c = *cp++) != '\0'){
1724 switch(c){
1725 case '(':
1726 sp = n_string_push_c(sp, '(');
1727 act = skip_comment(cp);
1728 if(--act > cp)
1729 sp = n_string_push_buf(sp, cp, PTR2SIZE(act - cp));
1730 sp = n_string_push_c(sp, ')');
1731 cp = ++act;
1732 break;
1733 case '"':
1734 while(*cp != '\0'){
1735 if((c = *cp++) == '"')
1736 break;
1737 if(c == '\\' && (c = *cp) != '\0')
1738 ++cp;
1739 sp = n_string_push_c(sp, c);
1741 break;
1742 default:
1743 if(c == '\\' && (c = *cp++) == '\0')
1744 break;
1745 sp = n_string_push_c(sp, c);
1746 break;
1749 cp = n_string_cp(sp);
1750 }else if(is_ascncaseprefix(act, "skin", alen)){
1751 /* Let's just use the is-single-address hack for this one, too.. */
1752 if(n_addrspec_with_guts(&ag, cp, TRU1, TRU1) == NULL ||
1753 (ag.ag_n_flags & (NAME_ADDRSPEC_ISADDR | NAME_ADDRSPEC_INVALID)
1754 ) != NAME_ADDRSPEC_ISADDR){
1755 n_pstate_err_no = n_ERR_INVAL;
1756 vp = NULL;
1757 }else{
1758 struct name *np;
1760 np = nalloc(ag.ag_input, GTO | GFULL | GSKIN);
1761 cp = np->n_name;
1763 }else
1764 goto jesynopsis;
1765 }else
1766 goto jesynopsis;
1768 if(varname == NULL){
1769 if(fprintf(n_stdout, "%s\n", cp) < 0){
1770 n_pstate_err_no = n_err_no;
1771 vp = NULL;
1773 }else if(!n_var_vset(varname, (uintptr_t)cp)){
1774 n_pstate_err_no = n_ERR_NOTSUP;
1775 vp = NULL;
1778 jleave:
1779 NYD_LEAVE;
1780 return (vp != NULL ? 0 : 1);
1781 jesynopsis:
1782 n_err(_("Synopsis: addrcodec: <[+[+[+]]]e[ncode]|d[ecode]|s[kin]> "
1783 "<rest-of-line>\n"));
1784 n_pstate_err_no = n_ERR_INVAL;
1785 vp = NULL;
1786 goto jleave;
1789 FL int
1790 c_commandalias(void *vp){
1791 struct group *gp;
1792 char const **argv, *ccp;
1793 int rv;
1794 NYD_ENTER;
1796 rv = 0;
1797 argv = vp;
1799 if((ccp = *argv) == NULL){
1800 _group_print_all(GT_COMMANDALIAS);
1801 goto jleave;
1804 /* Verify the name is a valid one, and not a command modifier */
1805 if(*ccp == '\0' || *n_cmd_isolate(ccp) != '\0' ||
1806 !asccasecmp(ccp, "ignerr") || !asccasecmp(ccp, "wysh") ||
1807 !asccasecmp(ccp, "vput")){
1808 n_err(_("`commandalias': not a valid command name: %s\n"),
1809 n_shexp_quote_cp(ccp, FAL0));
1810 rv = 1;
1811 goto jleave;
1814 if(argv[1] == NULL){
1815 if((gp = _group_find(GT_COMMANDALIAS, ccp)) != NULL)
1816 _group_print(gp, n_stdout);
1817 else{
1818 n_err(_("No such commandalias: %s\n"), n_shexp_quote_cp(ccp, FAL0));
1819 rv = 1;
1821 }else{
1822 /* Because one hardly ever redefines, anything is stored in one chunk */
1823 char *cp;
1824 size_t i, len;
1826 /* Delete the old one, if any; don't get fooled to remove them all */
1827 if(ccp[0] != '*' || ccp[1] != '\0')
1828 _group_del(GT_COMMANDALIAS, ccp);
1830 for(i = len = 0, ++argv; argv[i] != NULL; ++i)
1831 len += strlen(argv[i]) + 1;
1832 if(len == 0)
1833 len = 1;
1835 if((gp = _group_fetch(GT_COMMANDALIAS, ccp, len)) == NULL){
1836 n_err(_("Failed to create storage for commandalias: %s\n"),
1837 n_shexp_quote_cp(ccp, FAL0));
1838 rv = 1;
1839 }else{
1840 struct a_nag_cmd_alias *ncap;
1842 GP_TO_SUBCLASS(ncap, gp);
1843 GP_TO_SUBCLASS(cp, gp);
1844 cp += sizeof *ncap;
1845 ncap->ca_expand.s = cp;
1846 ncap->ca_expand.l = len - 1;
1848 for(len = 0; (ccp = *argv++) != NULL;)
1849 if((i = strlen(ccp)) > 0){
1850 if(len++ != 0)
1851 *cp++ = ' ';
1852 memcpy(cp, ccp, i);
1853 cp += i;
1855 *cp = '\0';
1858 jleave:
1859 NYD_LEAVE;
1860 return rv;
1863 FL int
1864 c_uncommandalias(void *vp){
1865 char **argv;
1866 int rv;
1867 NYD_ENTER;
1869 rv = 0;
1870 argv = vp;
1872 do if(!_group_del(GT_COMMANDALIAS, *argv)){
1873 n_err(_("No such `commandalias': %s\n"), n_shexp_quote_cp(*argv, FAL0));
1874 rv = 1;
1875 }while(*++argv != NULL);
1876 NYD_LEAVE;
1877 return rv;
1880 FL char const *
1881 n_commandalias_exists(char const *name, struct str const **expansion_or_null){
1882 struct group *gp;
1883 NYD_ENTER;
1885 if((gp = _group_find(GT_COMMANDALIAS, name)) != NULL){
1886 name = gp->g_id;
1888 if(expansion_or_null != NULL){
1889 struct a_nag_cmd_alias *ncap;
1891 GP_TO_SUBCLASS(ncap, gp);
1892 *expansion_or_null = &ncap->ca_expand;
1894 }else
1895 name = NULL;
1896 NYD_LEAVE;
1897 return name;
1900 FL bool_t
1901 n_alias_is_valid_name(char const *name){
1902 char c;
1903 char const *cp;
1904 bool_t rv;
1905 NYD2_ENTER;
1907 for(rv = TRU1, cp = name++; (c = *cp++) != '\0';)
1908 /* User names, plus things explicitly mentioned in Postfix aliases(5).
1909 * As an extension, allow period: [[:alnum:]_#:@.-]+$? */
1910 if(!alnumchar(c) && c != '_' && c != '-' &&
1911 c != '#' && c != ':' && c != '@' &&
1912 c != '.'){
1913 if(c == '$' && cp != name && *cp == '\0')
1914 break;
1915 rv = FAL0;
1916 break;
1918 NYD2_LEAVE;
1919 return rv;
1922 FL int
1923 c_alias(void *v)
1925 char const *ecp;
1926 char **argv;
1927 struct group *gp;
1928 int rv;
1929 NYD_ENTER;
1931 rv = 0;
1932 argv = v;
1933 n_UNINIT(ecp, NULL);
1935 if (*argv == NULL)
1936 _group_print_all(GT_ALIAS);
1937 else if (!n_alias_is_valid_name(*argv)) {
1938 ecp = N_("Not a valid alias name: %s\n");
1939 goto jerr;
1940 } else if (argv[1] == NULL) {
1941 if ((gp = _group_find(GT_ALIAS, *argv)) != NULL)
1942 _group_print(gp, n_stdout);
1943 else {
1944 ecp = N_("No such alias: %s\n");
1945 goto jerr;
1947 } else if ((gp = _group_fetch(GT_ALIAS, *argv, 0)) == NULL) {
1948 ecp = N_("Failed to create alias storage for: %s\n");
1949 jerr:
1950 n_err(V_(ecp), n_shexp_quote_cp(*argv, FAL0));
1951 rv = 1;
1952 } else {
1953 struct grp_names *gnp_tail, *gnp;
1954 struct grp_names_head *gnhp;
1956 GP_TO_SUBCLASS(gnhp, gp);
1958 if((gnp_tail = gnhp->gnh_head) != NULL)
1959 while((gnp = gnp_tail->gn_next) != NULL)
1960 gnp_tail = gnp;
1962 for(++argv; *argv != NULL; ++argv){
1963 size_t i;
1965 i = strlen(*argv) +1;
1966 gnp = smalloc(n_VSTRUCT_SIZEOF(struct grp_names, gn_id) + i);
1967 if(gnp_tail != NULL)
1968 gnp_tail->gn_next = gnp;
1969 else
1970 gnhp->gnh_head = gnp;
1971 gnp_tail = gnp;
1972 gnp->gn_next = NULL;
1973 memcpy(gnp->gn_id, *argv, i);
1976 NYD_LEAVE;
1977 return rv;
1980 FL int
1981 c_unalias(void *v)
1983 char **argv = v;
1984 int rv = 0;
1985 NYD_ENTER;
1987 do if (!_group_del(GT_ALIAS, *argv)) {
1988 n_err(_("No such alias: %s\n"), *argv);
1989 rv = 1;
1990 } while (*++argv != NULL);
1991 NYD_LEAVE;
1992 return rv;
1995 FL int
1996 c_mlist(void *v)
1998 int rv;
1999 NYD_ENTER;
2001 rv = _mlmux(GT_MLIST, v);
2002 NYD_LEAVE;
2003 return rv;
2006 FL int
2007 c_unmlist(void *v)
2009 int rv;
2010 NYD_ENTER;
2012 rv = _unmlmux(GT_MLIST, v);
2013 NYD_LEAVE;
2014 return rv;
2017 FL int
2018 c_mlsubscribe(void *v)
2020 int rv;
2021 NYD_ENTER;
2023 rv = _mlmux(GT_MLIST | GT_SUBSCRIBE, v);
2024 NYD_LEAVE;
2025 return rv;
2028 FL int
2029 c_unmlsubscribe(void *v)
2031 int rv;
2032 NYD_ENTER;
2034 rv = _unmlmux(GT_MLIST | GT_SUBSCRIBE, v);
2035 NYD_LEAVE;
2036 return rv;
2039 FL enum mlist_state
2040 is_mlist(char const *name, bool_t subscribed_only)
2042 struct group *gp;
2043 #ifdef HAVE_REGEX
2044 struct grp_regex **lpp, *grp;
2045 bool_t re2;
2046 #endif
2047 enum mlist_state rv;
2048 NYD_ENTER;
2050 gp = _group_find(GT_MLIST, name);
2051 rv = (gp != NULL) ? MLIST_KNOWN : MLIST_OTHER;
2052 if (rv == MLIST_KNOWN) {
2053 if (gp->g_type & GT_SUBSCRIBE)
2054 rv = MLIST_SUBSCRIBED;
2055 else if (subscribed_only)
2056 rv = MLIST_OTHER;
2057 /* Of course, if that is a regular expression it doesn't mean a thing */
2058 #ifdef HAVE_REGEX
2059 if (gp->g_type & GT_REGEX)
2060 rv = MLIST_OTHER;
2061 else
2062 #endif
2063 goto jleave;
2066 /* Not in the hashmap (as something matchable), walk the lists */
2067 #ifdef HAVE_REGEX
2068 re2 = FAL0;
2069 lpp = &_mlsub_regex;
2070 jregex_redo:
2071 if ((grp = *lpp) != NULL) {
2072 do if (regexec(&grp->gr_regex, name, 0,NULL, 0) != REG_NOMATCH) {
2073 /* Relink as the head of this list if the hit count of this group is
2074 * >= 25% of the average hit count */
2075 size_t i;
2076 if (!re2)
2077 i = ++_mlsub_hits / _mlsub_size;
2078 else
2079 i = ++_mlist_hits / _mlist_size;
2080 i >>= 2;
2082 if (++grp->gr_hits >= i && *lpp != grp && grp->gr_next != grp) {
2083 grp->gr_last->gr_next = grp->gr_next;
2084 grp->gr_next->gr_last = grp->gr_last;
2085 (grp->gr_last = (*lpp)->gr_last)->gr_next = grp;
2086 (grp->gr_next = *lpp)->gr_last = grp;
2087 *lpp = grp;
2089 rv = !re2 ? MLIST_SUBSCRIBED : MLIST_KNOWN;
2090 goto jleave;
2091 } while ((grp = grp->gr_next) != *lpp);
2093 if (!re2 && !subscribed_only) {
2094 re2 = TRU1;
2095 lpp = &_mlist_regex;
2096 goto jregex_redo;
2098 assert(rv == MLIST_OTHER);
2099 #endif
2101 jleave:
2102 NYD_LEAVE;
2103 return rv;
2106 FL int
2107 c_shortcut(void *v)
2109 struct group *gp;
2110 char **argv;
2111 int rv;
2112 NYD_ENTER;
2114 rv = 0;
2115 argv = v;
2117 if(*argv == NULL)
2118 _group_print_all(GT_SHORTCUT);
2119 else if(argv[1] == NULL){
2120 if((gp = _group_find(GT_SHORTCUT, *argv)) != NULL)
2121 _group_print(gp, n_stdout);
2122 else{
2123 n_err(_("No such shortcut: %s\n"), n_shexp_quote_cp(*argv, FAL0));
2124 rv = 1;
2126 }else for (; *argv != NULL; argv += 2) {
2127 /* Because one hardly ever redefines, anything is stored in one chunk */
2128 size_t l;
2129 char *cp;
2131 if (argv[1] == NULL) {
2132 n_err(_("Synopsis: shortcut: <shortcut> <expansion>\n"));
2133 rv = 1;
2134 break;
2136 if (_group_find(GT_SHORTCUT, *argv) != NULL)
2137 _group_del(GT_SHORTCUT, *argv);
2139 l = strlen(argv[1]) +1;
2140 if ((gp = _group_fetch(GT_SHORTCUT, *argv, l)) == NULL) {
2141 n_err(_("Failed to create storage for shortcut: %s\n"),
2142 n_shexp_quote_cp(*argv, FAL0));
2143 rv = 1;
2144 } else {
2145 GP_TO_SUBCLASS(cp, gp);
2146 memcpy(cp, argv[1], l);
2149 NYD_LEAVE;
2150 return rv;
2153 FL int
2154 c_unshortcut(void *v)
2156 char **argv = v;
2157 int rv = 0;
2158 NYD_ENTER;
2160 do if (!_group_del(GT_SHORTCUT, *argv)) {
2161 n_err(_("No such shortcut: %s\n"), *argv);
2162 rv = 1;
2163 } while (*++argv != NULL);
2164 NYD_LEAVE;
2165 return rv;
2168 FL char const *
2169 shortcut_expand(char const *str){
2170 struct group *gp;
2171 NYD_ENTER;
2173 if((gp = _group_find(GT_SHORTCUT, str)) != NULL)
2174 GP_TO_SUBCLASS(str, gp);
2175 else
2176 str = NULL;
2177 NYD_LEAVE;
2178 return str;
2181 FL int
2182 c_charsetalias(void *vp){
2183 struct group *gp;
2184 char **argv;
2185 int rv;
2186 NYD_ENTER;
2188 rv = 0;
2189 argv = vp;
2191 if(*argv == NULL)
2192 _group_print_all(GT_CHARSETALIAS);
2193 else if(argv[1] == NULL){
2194 if((gp = _group_find(GT_CHARSETALIAS, *argv)) != NULL)
2195 _group_print(gp, n_stdout);
2196 else{
2197 n_err(_("No such charsetalias: %s\n"), n_shexp_quote_cp(*argv, FAL0));
2198 rv = 1;
2200 }else for(; *argv != NULL; argv += 2){
2201 /* Because one hardly ever redefines, anything is stored in one chunk */
2202 char const *ccp;
2203 char *cp, c;
2204 size_t l;
2206 if(argv[1] == NULL){
2207 n_err(_("Synopsis: charsetalias: <charset> <charset-alias>\n"));
2208 rv = 1;
2209 break;
2212 /* Delete the old one, if any; don't get fooled to remove them all */
2213 ccp = argv[0];
2214 if(ccp[0] != '*' || ccp[1] != '\0')
2215 _group_del(GT_CHARSETALIAS, ccp);
2217 l = strlen(argv[1]) +1;
2218 if ((gp = _group_fetch(GT_CHARSETALIAS, ccp, l)) == NULL) {
2219 n_err(_("Failed to create storage for charsetalias: %s\n"),
2220 n_shexp_quote_cp(ccp, FAL0));
2221 rv = 1;
2222 } else {
2223 GP_TO_SUBCLASS(cp, gp);
2224 for(ccp = argv[1]; (c = *ccp++) != '\0';)
2225 *cp++ = lowerconv(c);
2226 *cp = '\0';
2229 NYD_LEAVE;
2230 return rv;
2233 FL int
2234 c_uncharsetalias(void *vp){
2235 char **argv;
2236 int rv;
2237 NYD_ENTER;
2239 rv = 0;
2240 argv = vp;
2242 do if(!_group_del(GT_CHARSETALIAS, *argv)){
2243 n_err(_("No such `charsetalias': %s\n"), n_shexp_quote_cp(*argv, FAL0));
2244 rv = 1;
2245 }while(*++argv != NULL);
2246 NYD_LEAVE;
2247 return rv;
2250 FL char const *
2251 n_charsetalias_expand(char const *cp){
2252 struct group *gp;
2253 size_t i;
2254 char const *cp_orig;
2255 NYD_ENTER;
2257 cp_orig = cp;
2259 for(i = 0; (gp = _group_find(GT_CHARSETALIAS, cp)) != NULL;){
2260 GP_TO_SUBCLASS(cp, gp);
2261 if(++i == 8) /* XXX Magic (same as for `ghost' expansion) */
2262 break;
2265 if(cp != cp_orig)
2266 cp = savestr(cp);
2267 NYD_LEAVE;
2268 return cp;
2271 FL int
2272 c_filetype(void *vp){ /* TODO support automatic chains: .tar.gz -> .gz + .tar */
2273 struct group *gp;
2274 char **argv; /* TODO While there: let ! prefix mean: direct execlp(2) */
2275 int rv;
2276 NYD_ENTER;
2278 rv = 0;
2279 argv = vp;
2281 if(*argv == NULL)
2282 _group_print_all(GT_FILETYPE);
2283 else if(argv[1] == NULL){
2284 if((gp = _group_find(GT_FILETYPE, *argv)) != NULL)
2285 _group_print(gp, n_stdout);
2286 else{
2287 n_err(_("No such filetype: %s\n"), n_shexp_quote_cp(*argv, FAL0));
2288 rv = 1;
2290 }else for(; *argv != NULL; argv += 3){
2291 /* Because one hardly ever redefines, anything is stored in one chunk */
2292 char const *ccp;
2293 char *cp, c;
2294 size_t llc, lsc;
2296 if(argv[1] == NULL || argv[2] == NULL){
2297 n_err(_("Synopsis: filetype: <extension> <load-cmd> <save-cmd>\n"));
2298 rv = 1;
2299 break;
2302 /* Delete the old one, if any; don't get fooled to remove them all */
2303 ccp = argv[0];
2304 if(ccp[0] != '*' || ccp[1] != '\0')
2305 _group_del(GT_FILETYPE, ccp);
2307 /* Lowercase it all (for display purposes) */
2308 cp = savestr(ccp);
2309 ccp = cp;
2310 while((c = *cp) != '\0')
2311 *cp++ = lowerconv(c);
2313 llc = strlen(argv[1]) +1;
2314 lsc = strlen(argv[2]) +1;
2315 if(UIZ_MAX - llc <= lsc)
2316 goto jenomem;
2318 if((gp = _group_fetch(GT_FILETYPE, ccp, llc + lsc)) == NULL){
2319 jenomem:
2320 n_err(_("Failed to create storage for filetype: %s\n"),
2321 n_shexp_quote_cp(argv[0], FAL0));
2322 rv = 1;
2323 }else{
2324 struct a_nag_file_type *nftp;
2326 GP_TO_SUBCLASS(nftp, gp);
2327 GP_TO_SUBCLASS(cp, gp);
2328 cp += sizeof *nftp;
2329 memcpy(nftp->nft_load.s = cp, argv[1], llc);
2330 cp += llc;
2331 nftp->nft_load.l = --llc;
2332 memcpy(nftp->nft_save.s = cp, argv[2], lsc);
2333 cp += lsc;
2334 nftp->nft_save.l = --lsc;
2337 NYD_LEAVE;
2338 return rv;
2341 FL int
2342 c_unfiletype(void *vp){
2343 char **argv;
2344 int rv;
2345 NYD_ENTER;
2347 rv = 0;
2348 argv = vp;
2350 do if(!_group_del(GT_FILETYPE, *argv)){
2351 n_err(_("No such `filetype': %s\n"), n_shexp_quote_cp(*argv, FAL0));
2352 rv = 1;
2353 }while(*++argv != NULL);
2354 NYD_LEAVE;
2355 return rv;
2358 FL bool_t
2359 n_filetype_trial(struct n_file_type *res_or_null, char const *file){
2360 struct stat stb;
2361 struct group_lookup gl;
2362 struct n_string s, *sp;
2363 struct group const *gp;
2364 ui32_t l;
2365 NYD2_ENTER;
2367 sp = n_string_creat_auto(&s);
2368 sp = n_string_assign_cp(sp, file);
2369 sp = n_string_push_c(sp, '.');
2370 l = sp->s_len;
2372 for(gp = _group_go_first(GT_FILETYPE, &gl); gp != NULL;
2373 gp = _group_go_next(&gl)){
2374 sp = n_string_trunc(sp, l);
2375 sp = n_string_push_buf(sp, gp->g_id,
2376 gp->g_subclass_off - gp->g_id_len_sub);
2378 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2379 if(res_or_null != NULL){
2380 struct a_nag_file_type *nftp;
2382 GP_TO_SUBCLASS(nftp, gp);
2383 res_or_null->ft_ext_dat = gp->g_id;
2384 res_or_null->ft_ext_len = gp->g_subclass_off - gp->g_id_len_sub;
2385 res_or_null->ft_load_dat = nftp->nft_load.s;
2386 res_or_null->ft_load_len = nftp->nft_load.l;
2387 res_or_null->ft_save_dat = nftp->nft_save.s;
2388 res_or_null->ft_save_len = nftp->nft_save.l;
2390 goto jleave; /* TODO after v15 legacy drop: break; */
2394 /* TODO v15 legacy code: automatic file hooks for .{bz2,gz,xz},
2395 * TODO but NOT supporting *file-hook-{load,save}-EXTENSION* */
2396 gp = (struct group*)0x1;
2398 sp = n_string_trunc(sp, l);
2399 sp = n_string_push_buf(sp, a_nag_OBSOLETE_xz.ft_ext_dat,
2400 a_nag_OBSOLETE_xz.ft_ext_len);
2401 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2402 n_OBSOLETE(".xz support will vanish, please use the `filetype' command");
2403 if(res_or_null != NULL)
2404 *res_or_null = a_nag_OBSOLETE_xz;
2405 goto jleave;
2408 sp = n_string_trunc(sp, l);
2409 sp = n_string_push_buf(sp, a_nag_OBSOLETE_gz.ft_ext_dat,
2410 a_nag_OBSOLETE_gz.ft_ext_len);
2411 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2412 n_OBSOLETE(".gz support will vanish, please use the `filetype' command");
2413 if(res_or_null != NULL)
2414 *res_or_null = a_nag_OBSOLETE_gz;
2415 goto jleave;
2418 sp = n_string_trunc(sp, l);
2419 sp = n_string_push_buf(sp, a_nag_OBSOLETE_bz2.ft_ext_dat,
2420 a_nag_OBSOLETE_bz2.ft_ext_len);
2421 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2422 n_OBSOLETE(".bz2 support will vanish, please use the `filetype' command");
2423 if(res_or_null != NULL)
2424 *res_or_null = a_nag_OBSOLETE_bz2;
2425 goto jleave;
2428 gp = NULL;
2430 jleave:
2431 NYD2_LEAVE;
2432 return (gp != NULL);
2435 FL bool_t
2436 n_filetype_exists(struct n_file_type *res_or_null, char const *file){
2437 char const *ext, *lext;
2438 NYD2_ENTER;
2440 if((ext = strrchr(file, '/')) != NULL)
2441 file = ++ext;
2443 for(lext = NULL; (ext = strchr(file, '.')) != NULL; lext = file = ext){
2444 struct group const *gp;
2446 if((gp = _group_find(GT_FILETYPE, ++ext)) != NULL){
2447 lext = ext;
2448 if(res_or_null != NULL){
2449 struct a_nag_file_type *nftp;
2451 GP_TO_SUBCLASS(nftp, gp);
2452 res_or_null->ft_ext_dat = gp->g_id;
2453 res_or_null->ft_ext_len = gp->g_subclass_off - gp->g_id_len_sub;
2454 res_or_null->ft_load_dat = nftp->nft_load.s;
2455 res_or_null->ft_load_len = nftp->nft_load.l;
2456 res_or_null->ft_save_dat = nftp->nft_save.s;
2457 res_or_null->ft_save_len = nftp->nft_save.l;
2459 goto jleave; /* TODO after v15 legacy drop: break; */
2463 /* TODO v15 legacy code: automatic file hooks for .{bz2,gz,xz},
2464 * TODO as well as supporting *file-hook-{load,save}-EXTENSION* */
2465 if(lext == NULL)
2466 goto jleave;
2468 if(!asccasecmp(lext, "xz")){
2469 n_OBSOLETE(".xz support will vanish, please use the `filetype' command");
2470 if(res_or_null != NULL)
2471 *res_or_null = a_nag_OBSOLETE_xz;
2472 goto jleave;
2473 }else if(!asccasecmp(lext, "gz")){
2474 n_OBSOLETE(".gz support will vanish, please use the `filetype' command");
2475 if(res_or_null != NULL)
2476 *res_or_null = a_nag_OBSOLETE_gz;
2477 goto jleave;
2478 }else if(!asccasecmp(lext, "bz2")){
2479 n_OBSOLETE(".bz2 support will vanish, please use the `filetype' command");
2480 if(res_or_null != NULL)
2481 *res_or_null = a_nag_OBSOLETE_bz2;
2482 goto jleave;
2483 }else{
2484 char const *cload, *csave;
2485 char *vbuf;
2486 size_t l;
2488 #undef a_X1
2489 #define a_X1 "file-hook-load-"
2490 #undef a_X2
2491 #define a_X2 "file-hook-save-"
2492 l = strlen(lext);
2493 vbuf = n_lofi_alloc(l + n_MAX(sizeof(a_X1), sizeof(a_X2)));
2495 memcpy(vbuf, a_X1, sizeof(a_X1) -1);
2496 memcpy(&vbuf[sizeof(a_X1) -1], lext, l);
2497 vbuf[sizeof(a_X1) -1 + l] = '\0';
2498 cload = n_var_vlook(vbuf, FAL0);
2500 memcpy(vbuf, a_X2, sizeof(a_X2) -1);
2501 memcpy(&vbuf[sizeof(a_X2) -1], lext, l);
2502 vbuf[sizeof(a_X2) -1 + l] = '\0';
2503 csave = n_var_vlook(vbuf, FAL0);
2505 #undef a_X2
2506 #undef a_X1
2507 n_lofi_free(vbuf);
2509 if((csave != NULL) | (cload != NULL)){
2510 n_OBSOLETE("*file-hook-{load,save}-EXTENSION* will vanish, "
2511 "please use the `filetype' command");
2513 if(((csave != NULL) ^ (cload != NULL)) == 0){
2514 if(res_or_null != NULL){
2515 res_or_null->ft_ext_dat = lext;
2516 res_or_null->ft_ext_len = l;
2517 res_or_null->ft_load_dat = cload;
2518 res_or_null->ft_load_len = strlen(cload);
2519 res_or_null->ft_save_dat = csave;
2520 res_or_null->ft_save_len = strlen(csave);
2522 goto jleave;
2523 }else
2524 n_alert(_("Incomplete *file-hook-{load,save}-EXTENSION* for: .%s"),
2525 lext);
2529 lext = NULL;
2531 jleave:
2532 NYD2_LEAVE;
2533 return (lext != NULL);
2536 /* s-it-mode */