`shift': _allow_ in hook/account if stack created via `vpospar'!
[s-mailx.git] / nam-a-grp.c
blob20c2f3ac214587f495b2e7d341b809c6d2b688f3
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 /* Delete the given name from a namelist, unless keep_single and only one */
163 static struct name *a_nag_namelist_del_cp(struct name *np, char const *name,
164 bool_t keep_single);
166 /* Grab a single name (liberal name) */
167 static char const * yankname(char const *ap, char *wbuf,
168 char const *separators, int keepcomms);
170 /* Extraction multiplexer that splits an input line to names */
171 static struct name * _extract1(char const *line, enum gfield ntype,
172 char const *separators, bool_t keepcomms);
174 /* Recursively expand a alias name. Limit expansion to some fixed level.
175 * Direct recursion is not expanded for convenience */
176 static struct name *a_nag_gexpand(size_t level, struct name *nlist,
177 struct group *gp, bool_t metoo, int ntype);
179 /* elide() helper */
180 static int a_nag_elide_qsort(void const *s1, void const *s2);
182 /* Lookup a group, return it or NULL, fill in glp anyway */
183 static struct group * _group_lookup(enum group_type gt,
184 struct group_lookup *glp, char const *id);
186 /* Easier-to-use wrapper around _group_lookup() */
187 static struct group * _group_find(enum group_type gt, char const *id);
189 /* Iteration: go to the first group, which also inits the iterator. A valid
190 * iterator can be stepped via _next(). A NULL return means no (more) groups
191 * to be iterated exist, in which case only glp->gl_group is set (NULL) */
192 static struct group * _group_go_first(enum group_type gt,
193 struct group_lookup *glp);
194 static struct group * _group_go_next(struct group_lookup *glp);
196 /* Fetch the group id, create it as necessary, fail with NULL if impossible */
197 static struct group * _group_fetch(enum group_type gt, char const *id,
198 size_t addsz);
200 /* "Intelligent" delete which handles a "*" id, too;
201 * returns a true boolean if a group was deleted, and always succeeds for "*" */
202 static bool_t _group_del(enum group_type gt, char const *id);
204 static struct group * __group_del(struct group_lookup *glp);
205 static void __names_del(struct group *gp);
207 /* Print all groups of the given type, alphasorted */
208 static void _group_print_all(enum group_type gt);
210 static int __group_print_qsorter(void const *a, void const *b);
212 /* Really print a group, actually. Return number of written lines */
213 static size_t _group_print(struct group const *gp, FILE *fo);
215 /* Multiplexers for list and subscribe commands */
216 static int _mlmux(enum group_type gt, char **argv);
217 static int _unmlmux(enum group_type gt, char **argv);
219 /* Relinkers for the sequential match lists */
220 #ifdef HAVE_REGEX
221 static void _mlmux_linkin(struct group *gp);
222 static void _mlmux_linkout(struct group *gp);
223 # define _MLMUX_LINKIN(GP) \
224 do if ((GP)->g_type & GT_REGEX) _mlmux_linkin(GP); while (0)
225 # define _MLMUX_LINKOUT(GP) \
226 do if ((GP)->g_type & GT_REGEX) _mlmux_linkout(GP); while (0)
227 #else
228 # define _MLMUX_LINKIN(GP)
229 # define _MLMUX_LINKOUT(GP)
230 #endif
232 static bool_t
233 a_nag_is_same_name(char const *n1, char const *n2){
234 char c1, c2;
235 bool_t rv;
236 NYD2_ENTER;
238 if(ok_blook(allnet)){
239 rv = TRU1;
241 c1 = *n1++;
242 c2 = *n2++;
243 c1 = lowerconv(c1);
244 c2 = lowerconv(c2);
245 if(c1 != c2){
246 rv = FAL0;
247 break;
249 }while(c1 != '\0' && c2 != '\0' && c1 != '@' && c2 != '@');
250 }else
251 rv = !asccasecmp(n1, n2);
252 NYD2_LEAVE;
253 return rv;
256 static struct name *
257 a_nag_namelist_del_cp(struct name *np, char const *name, bool_t keep_single){
258 struct name *p;
259 NYD2_ENTER;
261 for(p = np; p != NULL; p = p->n_flink)
262 if(a_nag_is_same_name(p->n_name, name)){
263 if(keep_single && np == p && np->n_flink == NULL)
264 break;
265 if(p->n_blink == NULL){
266 if((np = p->n_flink) != NULL)
267 np->n_blink = NULL;
268 }else if(p->n_flink == NULL)
269 p->n_blink->n_flink = NULL;
270 else{
271 p->n_blink->n_flink = p->n_flink;
272 p->n_flink->n_blink = p->n_blink;
275 NYD2_LEAVE;
276 return np;
279 static char const *
280 yankname(char const *ap, char *wbuf, char const *separators, int keepcomms)
282 char const *cp;
283 char *wp, c, inquote, lc, lastsp;
284 NYD_ENTER;
286 *(wp = wbuf) = '\0';
288 /* Skip over intermediate list trash, as in ".org> , <xy@zz.org>" */
289 for (c = *ap; blankchar(c) || c == ','; c = *++ap)
291 if (c == '\0') {
292 cp = NULL;
293 goto jleave;
296 /* Parse a full name: TODO RFC 5322
297 * - Keep everything in quotes, liberal handle *quoted-pair*s therein
298 * - Skip entire (nested) comments
299 * - In non-quote, non-comment, join adjacent space to a single SP
300 * - Understand separators only in non-quote, non-comment context,
301 * and only if not part of a *quoted-pair* (XXX too liberal) */
302 cp = ap;
303 for (inquote = lc = lastsp = 0;; lc = c, ++cp) {
304 c = *cp;
305 if (c == '\0')
306 break;
307 if (c == '\\')
308 goto jwpwc;
309 if (c == '"') {
310 if (lc != '\\')
311 inquote = !inquote;
312 #if 0 /* TODO when doing real RFC 5322 parsers - why have i done this? */
313 else
314 --wp;
315 #endif
316 goto jwpwc;
318 if (inquote || lc == '\\') {
319 jwpwc:
320 *wp++ = c;
321 lastsp = 0;
322 continue;
324 if (c == '(') {
325 ap = cp;
326 cp = skip_comment(cp + 1);
327 if (keepcomms)
328 while (ap < cp)
329 *wp++ = *ap++;
330 --cp;
331 lastsp = 0;
332 continue;
334 if (strchr(separators, c) != NULL)
335 break;
337 lc = lastsp;
338 lastsp = blankchar(c);
339 if (!lastsp || !lc)
340 *wp++ = c;
342 if (blankchar(lc))
343 --wp;
345 *wp = '\0';
346 jleave:
347 NYD_LEAVE;
348 return cp;
351 static struct name *
352 _extract1(char const *line, enum gfield ntype, char const *separators,
353 bool_t keepcomms)
355 struct name *topp, *np, *t;
356 char const *cp;
357 char *nbuf;
358 NYD_ENTER;
360 topp = NULL;
361 if (line == NULL || *line == '\0')
362 goto jleave;
364 np = NULL;
365 cp = line;
366 nbuf = smalloc(strlen(line) +1);
367 while ((cp = yankname(cp, nbuf, separators, keepcomms)) != NULL) {
368 t = nalloc(nbuf, ntype);
369 if (topp == NULL)
370 topp = t;
371 else
372 np->n_flink = t;
373 t->n_blink = np;
374 np = t;
376 free(nbuf);
377 jleave:
378 NYD_LEAVE;
379 return topp;
382 static struct name *
383 a_nag_gexpand(size_t level, struct name *nlist, struct group *gp, bool_t metoo,
384 int ntype){
385 struct grp_names *gnp;
386 struct name *nlist_tail;
387 char const *logname;
388 struct grp_names_head *gnhp;
389 NYD2_ENTER;
391 if(UICMP(z, level++, >, n_ALIAS_MAXEXP)){
392 n_err(_("Expanding alias to depth larger than %d\n"), n_ALIAS_MAXEXP);
393 goto jleave;
396 GP_TO_SUBCLASS(gnhp, gp);
397 logname = ok_vlook(LOGNAME);
399 for(gnp = gnhp->gnh_head; gnp != NULL; gnp = gnp->gn_next){
400 struct group *ngp;
401 char *cp;
403 cp = gnp->gn_id;
405 if(!strcmp(cp, gp->g_id))
406 goto jas_is;
408 if((ngp = _group_find(GT_ALIAS, cp)) != NULL){
409 /* For S-nail(1), the "alias" may *be* the sender in that a name maps
410 * to a full address specification; aliases cannot be empty */
411 struct grp_names_head *ngnhp;
412 GP_TO_SUBCLASS(ngnhp, ngp);
414 assert(ngnhp->gnh_head != NULL);
415 if(metoo || ngnhp->gnh_head->gn_next != NULL ||
416 !a_nag_is_same_name(cp, logname))
417 nlist = a_nag_gexpand(level, nlist, ngp, metoo, ntype);
418 continue;
421 /* Here we should allow to expand to itself if only person in alias */
422 jas_is:
423 if(metoo || gnhp->gnh_head->gn_next == NULL ||
424 !a_nag_is_same_name(cp, logname)){
425 struct name *np;
427 np = nalloc(cp, ntype | GFULL);
428 if((nlist_tail = nlist) != NULL){
429 while(nlist_tail->n_flink != NULL)
430 nlist_tail = nlist_tail->n_flink;
431 nlist_tail->n_flink = np;
432 np->n_blink = nlist_tail;
433 }else
434 nlist = np;
437 jleave:
438 NYD2_LEAVE;
439 return nlist;
442 static int
443 a_nag_elide_qsort(void const *s1, void const *s2){
444 struct name const * const *np1, * const *np2;
445 int rv;
446 NYD2_ENTER;
448 np1 = s1;
449 np2 = s2;
450 rv = asccasecmp((*np1)->n_name, (*np2)->n_name);
451 NYD2_LEAVE;
452 return rv;
455 static struct group *
456 _group_lookup(enum group_type gt, struct group_lookup *glp, char const *id){
457 char c1;
458 struct group *lgp, *gp;
459 NYD_ENTER;
461 gt &= GT_MASK;
462 lgp = NULL;
463 glp->gl_htable =
464 ( gt & GT_COMMANDALIAS ? _commandalias_heads
465 : (gt & GT_ALIAS ? _alias_heads
466 : (gt & GT_MLIST ? _mlist_heads
467 : (gt & GT_SHORTCUT ? _shortcut_heads
468 : (gt & GT_CHARSETALIAS ? _charsetalias_heads
469 : (/*gt & GT_FILETYPE ?*/ _filetype_heads
470 ))))));
471 gp = *(glp->gl_slot = &glp->gl_htable[
472 ((gt & (GT_MLIST | GT_CHARSETALIAS | GT_FILETYPE))
473 ? n_torek_ihash(id) : n_torek_hash(id)) % HSHSIZE]);
474 c1 = *id++;
476 if(gt & (GT_MLIST | GT_CHARSETALIAS | GT_FILETYPE)){
477 c1 = lowerconv(c1);
478 for(; gp != NULL; lgp = gp, gp = gp->g_next)
479 if((gp->g_type & gt) && *gp->g_id == c1 &&
480 !asccasecmp(&gp->g_id[1], id))
481 break;
482 }else{
483 for(; gp != NULL; lgp = gp, gp = gp->g_next)
484 if((gp->g_type & gt) && *gp->g_id == c1 && !strcmp(&gp->g_id[1], id))
485 break;
488 glp->gl_slot_last = lgp;
489 glp->gl_group = gp;
490 NYD_LEAVE;
491 return gp;
494 static struct group *
495 _group_find(enum group_type gt, char const *id)
497 struct group_lookup gl;
498 struct group *gp;
499 NYD_ENTER;
501 gp = _group_lookup(gt, &gl, id);
502 NYD_LEAVE;
503 return gp;
506 static struct group *
507 _group_go_first(enum group_type gt, struct group_lookup *glp)
509 struct group **gpa, *gp;
510 size_t i;
511 NYD_ENTER;
513 for (glp->gl_htable = gpa = (
514 gt & GT_COMMANDALIAS ? _commandalias_heads
515 : (gt & GT_ALIAS ? _alias_heads
516 : (gt & GT_MLIST ? _mlist_heads
517 : (gt & GT_SHORTCUT ? _shortcut_heads
518 : (gt & GT_CHARSETALIAS ? _charsetalias_heads
519 : (gt & GT_FILETYPE ? _filetype_heads
520 : NULL)))))
521 ), i = 0;
522 i < HSHSIZE; ++gpa, ++i)
523 if ((gp = *gpa) != NULL) {
524 glp->gl_slot = gpa;
525 glp->gl_group = gp;
526 goto jleave;
529 glp->gl_group = gp = NULL;
530 jleave:
531 glp->gl_slot_last = NULL;
532 NYD_LEAVE;
533 return gp;
536 static struct group *
537 _group_go_next(struct group_lookup *glp)
539 struct group *gp, **gpa;
540 NYD_ENTER;
542 if ((gp = glp->gl_group->g_next) != NULL)
543 glp->gl_slot_last = glp->gl_group;
544 else {
545 glp->gl_slot_last = NULL;
546 for (gpa = glp->gl_htable + HSHSIZE; ++glp->gl_slot < gpa;)
547 if ((gp = *glp->gl_slot) != NULL)
548 break;
550 glp->gl_group = gp;
551 NYD_LEAVE;
552 return gp;
555 static struct group *
556 _group_fetch(enum group_type gt, char const *id, size_t addsz)
558 struct group_lookup gl;
559 struct group *gp;
560 size_t l, i;
561 NYD_ENTER;
563 if ((gp = _group_lookup(gt, &gl, id)) != NULL)
564 goto jleave;
566 l = strlen(id) +1;
567 if (UIZ_MAX - n_ALIGN(l) <= n_ALIGN(n_VSTRUCT_SIZEOF(struct group, g_id)))
568 goto jleave;
570 i = n_ALIGN(n_VSTRUCT_SIZEOF(struct group, g_id) + l);
571 switch (gt & GT_MASK) {
572 case GT_COMMANDALIAS:
573 addsz += sizeof(struct a_nag_cmd_alias);
574 break;
575 case GT_ALIAS:
576 addsz += sizeof(struct grp_names_head);
577 break;
578 case GT_FILETYPE:
579 addsz += sizeof(struct a_nag_file_type);
580 break;
581 case GT_MLIST:
582 #ifdef HAVE_REGEX
583 if (n_is_maybe_regex(id)) {
584 addsz = sizeof(struct grp_regex);
585 gt |= GT_REGEX;
587 #endif
588 /* FALLTHRU */
589 case GT_SHORTCUT:
590 case GT_CHARSETALIAS:
591 default:
592 break;
594 if (UIZ_MAX - i < addsz || UI32_MAX <= i || UI16_MAX < i - l)
595 goto jleave;
597 gp = smalloc(i + addsz);
598 memcpy(gp->g_id, id, l);
599 gp->g_subclass_off = (ui32_t)i;
600 gp->g_id_len_sub = (ui16_t)(i - --l);
601 gp->g_type = gt;
602 if(gt & (GT_MLIST | GT_CHARSETALIAS | GT_FILETYPE)){
603 char *cp, c;
605 for(cp = gp->g_id; (c = *cp) != '\0'; ++cp)
606 *cp = lowerconv(c);
609 if (gt & GT_ALIAS) {
610 struct grp_names_head *gnhp;
612 GP_TO_SUBCLASS(gnhp, gp);
613 gnhp->gnh_head = NULL;
615 #ifdef HAVE_REGEX
616 else if (/*(gt & GT_MLIST) &&*/ gt & GT_REGEX) {
617 int s;
618 struct grp_regex *grp;
619 GP_TO_SUBCLASS(grp, gp);
621 if((s = regcomp(&grp->gr_regex, id,
622 REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0){
623 n_err(_("Invalid regular expression: %s: %s\n"),
624 n_shexp_quote_cp(id, FAL0), n_regex_err_to_doc(&grp->gr_regex, s));
625 free(gp);
626 gp = NULL;
627 goto jleave;
629 grp->gr_mygroup = gp;
630 _mlmux_linkin(gp);
632 #endif
634 gp->g_next = *gl.gl_slot;
635 *gl.gl_slot = gp;
636 jleave:
637 NYD_LEAVE;
638 return gp;
641 static bool_t
642 _group_del(enum group_type gt, char const *id)
644 enum group_type xgt = gt & GT_MASK;
645 struct group_lookup gl;
646 struct group *gp;
647 NYD_ENTER;
649 /* Delete 'em all? */
650 if (id[0] == '*' && id[1] == '\0') {
651 for (gp = _group_go_first(gt, &gl); gp != NULL;)
652 gp = (gp->g_type & xgt) ? __group_del(&gl) : _group_go_next(&gl);
653 gp = (struct group*)TRU1;
654 } else if ((gp = _group_lookup(gt, &gl, id)) != NULL) {
655 if (gp->g_type & xgt)
656 __group_del(&gl);
657 else
658 gp = NULL;
660 NYD_LEAVE;
661 return (gp != NULL);
664 static struct group *
665 __group_del(struct group_lookup *glp)
667 struct group *x, *gp;
668 NYD_ENTER;
670 /* Overly complicated: link off this node, step ahead to next.. */
671 x = glp->gl_group;
672 if((gp = glp->gl_slot_last) != NULL)
673 gp = (gp->g_next = x->g_next);
674 else{
675 glp->gl_slot_last = NULL;
676 gp = (*glp->gl_slot = x->g_next);
678 if(gp == NULL){
679 struct group **gpa;
681 for(gpa = &glp->gl_htable[HSHSIZE]; ++glp->gl_slot < gpa;)
682 if((gp = *glp->gl_slot) != NULL)
683 break;
686 glp->gl_group = gp;
688 if (x->g_type & GT_ALIAS)
689 __names_del(x);
690 #ifdef HAVE_REGEX
691 else if (/*(x->g_type & GT_MLIST) &&*/ x->g_type & GT_REGEX) {
692 struct grp_regex *grp;
693 GP_TO_SUBCLASS(grp, x);
695 regfree(&grp->gr_regex);
696 _mlmux_linkout(x);
698 #endif
700 free(x);
701 NYD_LEAVE;
702 return gp;
705 static void
706 __names_del(struct group *gp)
708 struct grp_names_head *gnhp;
709 struct grp_names *gnp;
710 NYD_ENTER;
712 GP_TO_SUBCLASS(gnhp, gp);
713 for (gnp = gnhp->gnh_head; gnp != NULL;) {
714 struct grp_names *x = gnp;
715 gnp = gnp->gn_next;
716 free(x);
718 NYD_LEAVE;
721 static void
722 _group_print_all(enum group_type gt)
724 enum group_type xgt;
725 struct group **gpa;
726 struct group const *gp;
727 ui32_t h, i;
728 char const **ida;
729 FILE *fp;
730 size_t lines;
731 NYD_ENTER;
733 xgt = gt & GT_PRINT_MASK;
734 gpa = ( xgt & GT_COMMANDALIAS ? _commandalias_heads
735 : (xgt & GT_ALIAS ? _alias_heads
736 : (xgt & GT_MLIST ? _mlist_heads
737 : (xgt & GT_SHORTCUT ? _shortcut_heads
738 : (xgt & GT_CHARSETALIAS ? _charsetalias_heads
739 : (xgt & GT_FILETYPE ? _filetype_heads
740 : NULL))))));
742 for (h = 0, i = 1; h < HSHSIZE; ++h)
743 for (gp = gpa[h]; gp != NULL; gp = gp->g_next)
744 if ((gp->g_type & xgt) == xgt)
745 ++i;
746 ida = salloc(i * sizeof *ida);
748 for (i = h = 0; h < HSHSIZE; ++h)
749 for (gp = gpa[h]; gp != NULL; gp = gp->g_next)
750 if ((gp->g_type & xgt) == xgt)
751 ida[i++] = gp->g_id;
752 ida[i] = NULL;
754 if (i > 1)
755 qsort(ida, i, sizeof *ida, &__group_print_qsorter);
757 if ((fp = Ftmp(NULL, "prgroup", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL)
758 fp = n_stdout;
759 lines = 0;
761 for (i = 0; ida[i] != NULL; ++i)
762 lines += _group_print(_group_find(gt, ida[i]), fp);
763 #ifdef HAVE_REGEX
764 if (gt & GT_MLIST) {
765 if (gt & GT_SUBSCRIBE)
766 i = (ui32_t)_mlsub_size, h = (ui32_t)_mlsub_hits;
767 else
768 i = (ui32_t)_mlist_size, h = (ui32_t)_mlist_hits;
769 if (i > 0 && (n_poption & n_PO_D_V)){
770 fprintf(fp, _("# %s list regex(7) total: %u entries, %u hits\n"),
771 (gt & GT_SUBSCRIBE ? _("Subscribed") : _("Non-subscribed")),
772 i, h);
773 ++lines;
776 #endif
778 if (fp != n_stdout) {
779 page_or_print(fp, lines);
780 Fclose(fp);
782 NYD_LEAVE;
785 static int
786 __group_print_qsorter(void const *a, void const *b)
788 int rv;
789 NYD_ENTER;
791 rv = strcmp(*(char**)n_UNCONST(a), *(char**)n_UNCONST(b));
792 NYD_LEAVE;
793 return rv;
796 static size_t
797 _group_print(struct group const *gp, FILE *fo)
799 char const *cp;
800 size_t rv;
801 NYD_ENTER;
803 rv = 1;
805 if(gp->g_type & GT_COMMANDALIAS){
806 struct a_nag_cmd_alias *ncap;
808 GP_TO_SUBCLASS(ncap, gp);
809 fprintf(fo, "commandalias %s %s\n",
810 n_shexp_quote_cp(gp->g_id, TRU1),
811 n_shexp_quote_cp(ncap->ca_expand.s, TRU1));
812 } else if (gp->g_type & GT_ALIAS) {
813 struct grp_names_head *gnhp;
814 struct grp_names *gnp;
816 fprintf(fo, "alias %s ", gp->g_id);
818 GP_TO_SUBCLASS(gnhp, gp);
819 if ((gnp = gnhp->gnh_head) != NULL) { /* xxx always 1+ entries */
820 do {
821 struct grp_names *x = gnp;
822 gnp = gnp->gn_next;
823 fprintf(fo, " \"%s\"", string_quote(x->gn_id)); /* TODO shexp */
824 } while (gnp != NULL);
826 putc('\n', fo);
827 } else if (gp->g_type & GT_MLIST) {
828 #ifdef HAVE_REGEX
829 if ((gp->g_type & GT_REGEX) && (n_poption & n_PO_D_V)){
830 size_t i;
831 struct grp_regex *grp,
832 *lp = (gp->g_type & GT_SUBSCRIBE ? _mlsub_regex : _mlist_regex);
834 GP_TO_SUBCLASS(grp, gp);
835 for (i = 1; lp != grp; lp = lp->gr_next)
836 ++i;
837 fprintf(fo, "# regex(7): hits %" PRIuZ ", sort %" PRIuZ ".\n ",
838 grp->gr_hits, i);
839 ++rv;
841 #endif
843 fprintf(fo, "wysh %s %s\n",
844 (gp->g_type & GT_SUBSCRIBE ? "mlsubscribe" : "mlist"),
845 n_shexp_quote_cp(gp->g_id, TRU1));
846 } else if (gp->g_type & GT_SHORTCUT) {
847 GP_TO_SUBCLASS(cp, gp);
848 fprintf(fo, "wysh shortcut %s %s\n",
849 gp->g_id, n_shexp_quote_cp(cp, TRU1));
850 } else if (gp->g_type & GT_CHARSETALIAS) {
851 GP_TO_SUBCLASS(cp, gp);
852 fprintf(fo, "charsetalias %s %s\n",
853 n_shexp_quote_cp(gp->g_id, TRU1), n_shexp_quote_cp(cp, TRU1));
854 } else if (gp->g_type & GT_FILETYPE) {
855 struct a_nag_file_type *nftp;
857 GP_TO_SUBCLASS(nftp, gp);
858 fprintf(fo, "filetype %s %s %s\n",
859 n_shexp_quote_cp(gp->g_id, TRU1),
860 n_shexp_quote_cp(nftp->nft_load.s, TRU1),
861 n_shexp_quote_cp(nftp->nft_save.s, TRU1));
864 NYD_LEAVE;
865 return rv;
868 static int
869 _mlmux(enum group_type gt, char **argv)
871 char const *ecp;
872 struct group *gp;
873 int rv = 0;
874 NYD_ENTER;
876 rv = 0;
877 n_UNINIT(ecp, NULL);
879 if (*argv == NULL)
880 _group_print_all(gt);
881 else do {
882 if ((gp = _group_find(gt, *argv)) != NULL) {
883 if (gt & GT_SUBSCRIBE) {
884 if (!(gp->g_type & GT_SUBSCRIBE)) {
885 _MLMUX_LINKOUT(gp);
886 gp->g_type |= GT_SUBSCRIBE;
887 _MLMUX_LINKIN(gp);
888 } else {
889 ecp = N_("Mailing-list already `mlsubscribe'd: %s\n");
890 goto jerr;
892 } else {
893 ecp = N_("Mailing-list already `mlist'ed: %s\n");
894 goto jerr;
896 } else if(_group_fetch(gt, *argv, 0) == NULL) {
897 ecp = N_("Failed to create storage for mailing-list: %s\n");
898 jerr:
899 n_err(V_(ecp), n_shexp_quote_cp(*argv, FAL0));
900 rv = 1;
902 } while (*++argv != NULL);
904 NYD_LEAVE;
905 return rv;
908 static int
909 _unmlmux(enum group_type gt, char **argv)
911 struct group *gp;
912 int rv = 0;
913 NYD_ENTER;
915 for (; *argv != NULL; ++argv) {
916 if (gt & GT_SUBSCRIBE) {
917 struct group_lookup gl;
918 bool_t isaster;
920 if (!(isaster = (**argv == '*')))
921 gp = _group_find(gt, *argv);
922 else if ((gp = _group_go_first(gt, &gl)) == NULL)
923 continue;
924 else if (gp != NULL && !(gp->g_type & GT_SUBSCRIBE))
925 goto jaster_entry;
927 if (gp != NULL) {
928 jaster_redo:
929 if (gp->g_type & GT_SUBSCRIBE) {
930 _MLMUX_LINKOUT(gp);
931 gp->g_type &= ~GT_SUBSCRIBE;
932 _MLMUX_LINKIN(gp);
933 if (isaster) {
934 jaster_entry:
935 while ((gp = _group_go_next(&gl)) != NULL &&
936 !(gp->g_type & GT_SUBSCRIBE))
938 if (gp != NULL)
939 goto jaster_redo;
941 } else {
942 n_err(_("Mailing-list not `mlsubscribe'd: %s\n"),
943 n_shexp_quote_cp(*argv, FAL0));
944 rv = 1;
946 continue;
948 } else if (_group_del(gt, *argv))
949 continue;
950 n_err(_("No such mailing-list: %s\n"), n_shexp_quote_cp(*argv, FAL0));
951 rv = 1;
953 NYD_LEAVE;
954 return rv;
957 #ifdef HAVE_REGEX
958 static void
959 _mlmux_linkin(struct group *gp)
961 struct grp_regex **lpp, *grp, *lhp;
962 NYD_ENTER;
964 if (gp->g_type & GT_SUBSCRIBE) {
965 lpp = &_mlsub_regex;
966 ++_mlsub_size;
967 } else {
968 lpp = &_mlist_regex;
969 ++_mlist_size;
972 GP_TO_SUBCLASS(grp, gp);
973 if ((lhp = *lpp) != NULL) {
974 (grp->gr_last = lhp->gr_last)->gr_next = grp;
975 (grp->gr_next = lhp)->gr_last = grp;
976 } else
977 *lpp = grp->gr_last = grp->gr_next = grp;
978 grp->gr_hits = 0;
979 NYD_LEAVE;
982 static void
983 _mlmux_linkout(struct group *gp)
985 struct grp_regex *grp, **lpp;
986 NYD_ENTER;
988 GP_TO_SUBCLASS(grp, gp);
990 if (gp->g_type & GT_SUBSCRIBE) {
991 lpp = &_mlsub_regex;
992 --_mlsub_size;
993 _mlsub_hits -= grp->gr_hits;
994 } else {
995 lpp = &_mlist_regex;
996 --_mlist_size;
997 _mlist_hits -= grp->gr_hits;
1000 if (grp->gr_next == grp)
1001 *lpp = NULL;
1002 else {
1003 (grp->gr_last->gr_next = grp->gr_next)->gr_last = grp->gr_last;
1004 if (*lpp == grp)
1005 *lpp = grp->gr_next;
1007 NYD_LEAVE;
1009 #endif /* HAVE_REGEX */
1011 FL struct name *
1012 nalloc(char const *str, enum gfield ntype)
1014 struct n_addrguts ag;
1015 struct str in, out;
1016 struct name *np;
1017 NYD_ENTER;
1018 assert(!(ntype & GFULLEXTRA) || (ntype & GFULL) != 0);
1020 str = n_addrspec_with_guts(&ag, str,
1021 ((ntype & (GFULL | GSKIN | GREF)) != 0), FAL0);
1022 if(str == NULL){
1024 np = NULL; TODO We cannot return NULL,
1025 goto jleave; TODO thus handle failures in here!
1027 str = ag.ag_input;
1030 if (!(ag.ag_n_flags & NAME_NAME_SALLOC)) {
1031 ag.ag_n_flags |= NAME_NAME_SALLOC;
1032 np = salloc(sizeof(*np) + ag.ag_slen +1);
1033 memcpy(np + 1, ag.ag_skinned, ag.ag_slen +1);
1034 ag.ag_skinned = (char*)(np + 1);
1035 } else
1036 np = salloc(sizeof *np);
1038 np->n_flink = NULL;
1039 np->n_blink = NULL;
1040 np->n_type = ntype;
1041 np->n_flags = 0;
1043 np->n_fullname = np->n_name = ag.ag_skinned;
1044 np->n_fullextra = NULL;
1045 np->n_flags = ag.ag_n_flags;
1047 if (ntype & GFULL) {
1048 if (ag.ag_ilen == ag.ag_slen
1049 #ifdef HAVE_IDNA
1050 && !(ag.ag_n_flags & NAME_IDNA)
1051 #endif
1053 goto jleave;
1054 if (ag.ag_n_flags & NAME_ADDRSPEC_ISFILEORPIPE)
1055 goto jleave;
1057 /* n_fullextra is only the complete name part without address.
1058 * Beware of "-r '<abc@def>'", don't treat that as FULLEXTRA */
1059 if ((ntype & GFULLEXTRA) && ag.ag_ilen > ag.ag_slen + 2) {
1060 size_t s = ag.ag_iaddr_start, e = ag.ag_iaddr_aend, i;
1061 char const *cp;
1063 if (s == 0 || str[--s] != '<' || str[e++] != '>')
1064 goto jskipfullextra;
1065 i = ag.ag_ilen - e;
1066 in.s = n_lofi_alloc(s + 1 + i +1);
1067 while(s > 0 && blankchar(str[s - 1]))
1068 --s;
1069 memcpy(in.s, str, s);
1070 if (i > 0) {
1071 in.s[s++] = ' ';
1072 while (blankchar(str[e])) {
1073 ++e;
1074 if (--i == 0)
1075 break;
1077 if (i > 0)
1078 memcpy(&in.s[s], &str[e], i);
1080 s += i;
1081 in.s[in.l = s] = '\0';
1082 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
1084 for (cp = out.s, i = out.l; i > 0 && spacechar(*cp); --i, ++cp)
1086 while (i > 0 && spacechar(cp[i - 1]))
1087 --i;
1088 np->n_fullextra = savestrbuf(cp, i);
1090 n_lofi_free(in.s);
1091 free(out.s);
1093 jskipfullextra:
1095 /* n_fullname depends on IDNA conversion */
1096 #ifdef HAVE_IDNA
1097 if (!(ag.ag_n_flags & NAME_IDNA)) {
1098 #endif
1099 in.s = n_UNCONST(str);
1100 in.l = ag.ag_ilen;
1101 #ifdef HAVE_IDNA
1102 } else {
1103 /* The domain name was IDNA and has been converted. We also have to
1104 * ensure that the domain name in .n_fullname is replaced with the
1105 * converted version, since MIME doesn't perform encoding of addrs */
1106 /* TODO This definetely doesn't belong here! */
1107 size_t l = ag.ag_iaddr_start,
1108 lsuff = ag.ag_ilen - ag.ag_iaddr_aend;
1109 in.s = ac_alloc(l + ag.ag_slen + lsuff +1);
1110 memcpy(in.s, str, l);
1111 memcpy(in.s + l, ag.ag_skinned, ag.ag_slen);
1112 l += ag.ag_slen;
1113 memcpy(in.s + l, str + ag.ag_iaddr_aend, lsuff);
1114 l += lsuff;
1115 in.s[l] = '\0';
1116 in.l = l;
1118 #endif
1119 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
1120 np->n_fullname = savestr(out.s);
1121 free(out.s);
1122 #ifdef HAVE_IDNA
1123 if (ag.ag_n_flags & NAME_IDNA)
1124 ac_free(in.s);
1125 #endif
1126 np->n_flags |= NAME_FULLNAME_SALLOC;
1128 jleave:
1129 NYD_LEAVE;
1130 return np;
1133 FL struct name *
1134 ndup(struct name *np, enum gfield ntype)
1136 struct name *nnp;
1137 NYD_ENTER;
1139 if ((ntype & (GFULL | GSKIN)) && !(np->n_flags & NAME_SKINNED)) {
1140 nnp = nalloc(np->n_name, ntype);
1141 goto jleave;
1144 nnp = salloc(sizeof *np);
1145 nnp->n_flink = nnp->n_blink = NULL;
1146 nnp->n_type = ntype;
1147 nnp->n_flags = (np->n_flags & ~(NAME_NAME_SALLOC | NAME_FULLNAME_SALLOC)) |
1148 NAME_NAME_SALLOC;
1149 nnp->n_name = savestr(np->n_name);
1150 if (np->n_name == np->n_fullname || !(ntype & (GFULL | GSKIN))) {
1151 nnp->n_fullname = nnp->n_name;
1152 nnp->n_fullextra = NULL;
1153 } else {
1154 nnp->n_flags |= NAME_FULLNAME_SALLOC;
1155 nnp->n_fullname = savestr(np->n_fullname);
1156 nnp->n_fullextra = (np->n_fullextra == NULL) ? NULL
1157 : savestr(np->n_fullextra);
1159 jleave:
1160 NYD_LEAVE;
1161 return nnp;
1164 FL struct name *
1165 cat(struct name *n1, struct name *n2)
1167 struct name *tail;
1168 NYD_ENTER;
1170 tail = n2;
1171 if (n1 == NULL)
1172 goto jleave;
1173 tail = n1;
1174 if (n2 == NULL)
1175 goto jleave;
1177 while (tail->n_flink != NULL)
1178 tail = tail->n_flink;
1179 tail->n_flink = n2;
1180 n2->n_blink = tail;
1181 tail = n1;
1182 jleave:
1183 NYD_LEAVE;
1184 return tail;
1187 FL struct name *
1188 namelist_dup(struct name const *np, enum gfield ntype){
1189 struct name *nlist, *xnp;
1190 NYD2_ENTER;
1192 for(nlist = xnp = NULL; np != NULL; np = np->n_flink){
1193 struct name *x;
1195 x = ndup(n_UNCONST(np), (np->n_type & ~GMASK) | ntype);
1196 if((x->n_blink = xnp) == NULL)
1197 nlist = x;
1198 else
1199 xnp->n_flink = x;
1200 xnp = x;
1202 NYD2_LEAVE;
1203 return nlist;
1206 FL ui32_t
1207 count(struct name const *np)
1209 ui32_t c;
1210 NYD_ENTER;
1212 for (c = 0; np != NULL; np = np->n_flink)
1213 if (!(np->n_type & GDEL))
1214 ++c;
1215 NYD_LEAVE;
1216 return c;
1219 FL ui32_t
1220 count_nonlocal(struct name const *np)
1222 ui32_t c;
1223 NYD_ENTER;
1225 for (c = 0; np != NULL; np = np->n_flink)
1226 if (!(np->n_type & GDEL) && !(np->n_flags & NAME_ADDRSPEC_ISFILEORPIPE))
1227 ++c;
1228 NYD_LEAVE;
1229 return c;
1232 FL struct name *
1233 extract(char const *line, enum gfield ntype)
1235 struct name *rv;
1236 NYD_ENTER;
1238 rv = _extract1(line, ntype, " \t,", 0);
1239 NYD_LEAVE;
1240 return rv;
1243 FL struct name *
1244 lextract(char const *line, enum gfield ntype)
1246 struct name *rv;
1247 NYD_ENTER;
1249 rv = ((line != NULL && strpbrk(line, ",\"\\(<|"))
1250 ? _extract1(line, ntype, ",", 1) : extract(line, ntype));
1251 NYD_LEAVE;
1252 return rv;
1255 FL char *
1256 detract(struct name *np, enum gfield ntype)
1258 char *topp, *cp;
1259 struct name *p;
1260 int flags, s;
1261 NYD_ENTER;
1263 topp = NULL;
1264 if (np == NULL)
1265 goto jleave;
1267 flags = ntype & (GCOMMA | GNAMEONLY);
1268 ntype &= ~(GCOMMA | GNAMEONLY);
1269 s = 0;
1271 for (p = np; p != NULL; p = p->n_flink) {
1272 if (ntype && (p->n_type & GMASK) != ntype)
1273 continue;
1274 s += strlen(flags & GNAMEONLY ? p->n_name : p->n_fullname) +1;
1275 if (flags & GCOMMA)
1276 ++s;
1278 if (s == 0)
1279 goto jleave;
1281 s += 2;
1282 topp = salloc(s);
1283 cp = topp;
1284 for (p = np; p != NULL; p = p->n_flink) {
1285 if (ntype && (p->n_type & GMASK) != ntype)
1286 continue;
1287 cp = sstpcpy(cp, (flags & GNAMEONLY ? p->n_name : p->n_fullname));
1288 if ((flags & GCOMMA) && p->n_flink != NULL)
1289 *cp++ = ',';
1290 *cp++ = ' ';
1292 *--cp = 0;
1293 if ((flags & GCOMMA) && *--cp == ',')
1294 *cp = 0;
1295 jleave:
1296 NYD_LEAVE;
1297 return topp;
1300 FL struct name *
1301 grab_names(enum n_go_input_flags gif, char const *field, struct name *np,
1302 int comma, enum gfield gflags)
1304 struct name *nq;
1305 NYD_ENTER;
1307 jloop:
1308 np = lextract(n_go_input_cp(gif, field, detract(np, comma)), gflags);
1309 for (nq = np; nq != NULL; nq = nq->n_flink)
1310 if (is_addr_invalid(nq, EACM_NONE))
1311 goto jloop;
1312 NYD_LEAVE;
1313 return np;
1316 FL bool_t
1317 name_is_same_domain(struct name const *n1, struct name const *n2)
1319 char const *d1, *d2;
1320 bool_t rv;
1321 NYD_ENTER;
1323 d1 = strrchr(n1->n_name, '@');
1324 d2 = strrchr(n2->n_name, '@');
1326 rv = (d1 != NULL && d2 != NULL) ? !asccasecmp(++d1, ++d2) : FAL0;
1328 NYD_LEAVE;
1329 return rv;
1332 FL struct name *
1333 checkaddrs(struct name *np, enum expand_addr_check_mode eacm,
1334 si8_t *set_on_error)
1336 struct name *n;
1337 NYD_ENTER;
1339 for (n = np; n != NULL; n = n->n_flink) {
1340 si8_t rv;
1342 if ((rv = is_addr_invalid(n, eacm)) != 0) {
1343 if (set_on_error != NULL)
1344 *set_on_error |= rv; /* don't loose -1! */
1345 else if (eacm & EAF_MAYKEEP) /* TODO HACK! See definition! */
1346 continue;
1347 if (n->n_blink)
1348 n->n_blink->n_flink = n->n_flink;
1349 if (n->n_flink)
1350 n->n_flink->n_blink = n->n_blink;
1351 if (n == np)
1352 np = n->n_flink;
1355 NYD_LEAVE;
1356 return np;
1359 FL struct name *
1360 namelist_vaporise_head(struct header *hp, enum expand_addr_check_mode eacm,
1361 bool_t metoo, si8_t *set_on_error)
1363 /* TODO namelist_vaporise_head() is incredibly expensive and redundant */
1364 struct name *tolist, *np, **npp;
1365 NYD_ENTER;
1367 tolist = cat(hp->h_to, cat(hp->h_cc, hp->h_bcc));
1368 hp->h_to = hp->h_cc = hp->h_bcc = NULL;
1370 tolist = usermap(tolist, metoo);
1371 tolist = n_alternates_delete(tolist, TRU1);
1372 tolist = elide(checkaddrs(tolist, eacm, set_on_error));
1374 for (np = tolist; np != NULL; np = np->n_flink) {
1375 switch (np->n_type & (GDEL | GMASK)) {
1376 case GTO: npp = &hp->h_to; break;
1377 case GCC: npp = &hp->h_cc; break;
1378 case GBCC: npp = &hp->h_bcc; break;
1379 default: continue;
1381 *npp = cat(*npp, ndup(np, np->n_type | GFULL));
1383 NYD_LEAVE;
1384 return tolist;
1387 FL struct name *
1388 usermap(struct name *names, bool_t force_metoo){
1389 struct group *gp;
1390 struct name *nlist, *nlist_tail, *np, *cp;
1391 int metoo;
1392 NYD_ENTER;
1394 metoo = (force_metoo || ok_blook(metoo));
1395 nlist = nlist_tail = NULL;
1396 np = names;
1398 for(; np != NULL; np = cp){
1399 assert(!(np->n_type & GDEL)); /* TODO legacy */
1400 cp = np->n_flink;
1402 if(is_fileorpipe_addr(np) ||
1403 (gp = _group_find(GT_ALIAS, np->n_name)) == NULL){
1404 if((np->n_blink = nlist_tail) != NULL)
1405 nlist_tail->n_flink = np;
1406 else
1407 nlist = np;
1408 nlist_tail = np;
1409 np->n_flink = NULL;
1410 }else{
1411 nlist = a_nag_gexpand(0, nlist, gp, metoo, np->n_type);
1412 if((nlist_tail = nlist) != NULL)
1413 while(nlist_tail->n_flink != NULL)
1414 nlist_tail = nlist_tail->n_flink;
1417 NYD_LEAVE;
1418 return nlist;
1421 FL struct name *
1422 elide(struct name *names)
1424 size_t i, j, k;
1425 struct name *nlist, *np, **nparr;
1426 NYD_ENTER;
1428 nlist = NULL;
1430 if(names == NULL)
1431 goto jleave;
1433 /* Throw away all deleted nodes */
1434 for(np = NULL, i = 0; names != NULL; names = names->n_flink)
1435 if(!(names->n_type & GDEL)){
1436 names->n_blink = np;
1437 if(np != NULL)
1438 np->n_flink = names;
1439 else
1440 nlist = names;
1441 np = names;
1442 ++i;
1445 if(nlist == NULL || i == 1)
1446 goto jleave;
1448 /* Create a temporay array and sort that */
1449 nparr = n_lofi_alloc(sizeof(*nparr) * i);
1451 for(i = 0, np = nlist; np != NULL; np = np->n_flink)
1452 nparr[i++] = np;
1454 qsort(nparr, i, sizeof *nparr, &a_nag_elide_qsort);
1456 /* Remove duplicates XXX speedup, or list_uniq()! */
1457 for(j = 0, --i; j < i;){
1458 if(asccasecmp(nparr[j]->n_name, nparr[k = j + 1]->n_name))
1459 ++j;
1460 else{
1461 for(; k < i; ++k)
1462 nparr[k] = nparr[k + 1];
1463 --i;
1467 /* Throw away all list members which are not part of the array.
1468 * Note this keeps the original, possibly carefully crafted, order of the
1469 * addressees, thus */
1470 for(np = nlist; np != NULL; np = np->n_flink){
1471 for(j = 0; j <= i; ++j)
1472 if(np == nparr[j]){
1473 nparr[j] = NULL;
1474 goto jiter;
1476 /* Drop it */
1477 if(np == nlist){
1478 nlist = np->n_flink;
1479 np->n_blink = NULL;
1480 }else
1481 np->n_blink->n_flink = np->n_flink;
1482 if(np->n_flink != NULL)
1483 np->n_flink->n_blink = np->n_blink;
1484 jiter:;
1487 n_lofi_free(nparr);
1488 jleave:
1489 NYD_LEAVE;
1490 return nlist;
1493 FL int
1494 c_alternates(void *v){ /* TODO use a hashmap!! */
1495 char **argv;
1496 int rv;
1497 NYD_ENTER;
1499 rv = 0;
1501 if(*(argv = v) == NULL){
1502 char const *ccp;
1504 if((ccp = ok_vlook(alternates)) != NULL)
1505 fprintf(n_stdout, "alternates %s\n", ccp);
1506 else
1507 fputs(_("# no alternates registered\n"), n_stdout);
1508 }else{
1509 char *cp;
1510 size_t l, vl;
1511 struct n_strlist *slp, **slpa;
1513 while((slp = a_nag_altnames) != NULL){
1514 a_nag_altnames = slp->sl_next;
1515 n_free(slp);
1517 vl = 0;
1519 /* Extension: only clearance? */
1520 if(argv[1] == NULL && argv[0][0] == '-' && argv[0][1] == '\0')
1521 n_UNINIT(cp, NULL);
1522 else for(slpa = &a_nag_altnames; *argv != NULL; ++argv){
1523 if(**argv != '\0'){
1524 struct name *np;
1526 if((np = lextract(*argv, GSKIN)) == NULL || np->n_flink != NULL ||
1527 (np = checkaddrs(np, EACM_STRICT, NULL)) == NULL){
1528 n_err(_("Invalid `alternates' argument: %s\n"),
1529 n_shexp_quote_cp(*argv, FAL0));
1530 rv = 1;
1531 continue;
1534 l = strlen(np->n_name);
1535 if(UIZ_MAX - l <= vl){
1536 n_err(_("Failed to create storage for alternate: %s\n"),
1537 n_shexp_quote_cp(*argv, FAL0));
1538 rv = 1;
1539 continue;
1542 slp = n_STRLIST_ALLOC(l);
1543 slp->sl_next = NULL;
1544 slp->sl_len = l;
1545 memcpy(slp->sl_dat, np->n_name, ++l);
1546 *slpa = slp;
1547 slpa = &slp->sl_next;
1548 vl += l;
1552 /* And put it into *alternates* */
1553 if(vl > 0){
1554 cp = n_autorec_alloc(vl);
1555 for(vl = 0, slp = a_nag_altnames; slp != NULL; slp = slp->sl_next){
1556 memcpy(&cp[vl], slp->sl_dat, slp->sl_len);
1557 cp[vl += slp->sl_len] = ' ';
1558 ++vl;
1560 cp[vl - 1] = '\0';
1563 n_PS_ROOT_BLOCK(vl > 0 ? ok_vset(alternates, cp) : ok_vclear(alternates));
1565 NYD_LEAVE;
1566 return rv;
1569 FL struct name *
1570 n_alternates_delete(struct name *np, bool_t keep_single){
1571 struct name *xp;
1572 NYD_ENTER;
1574 /* Throw away all deleted nodes: we may not allow to remove a lonely
1575 * addressee if that matches an alternate, so ensure we do not need to
1576 * fiddle around with GDEL; _namelist_del_cp() will fully del, too */
1577 /* C99 */{
1578 struct name *newnp;
1580 for(xp = newnp = NULL; np != NULL; np = np->n_flink)
1581 if(!(np->n_type & GDEL)){
1582 np->n_blink = xp;
1583 if(xp != NULL)
1584 xp->n_flink = np;
1585 else
1586 newnp = np;
1587 xp = np;
1589 np = newnp;
1592 if(np == NULL)
1593 goto jleave;
1594 if(keep_single && np->n_flink == NULL)
1595 goto jleave;
1597 if(a_nag_altnames != NULL){
1598 struct n_strlist *slp;
1600 for(slp = a_nag_altnames; slp != NULL; slp = slp->sl_next){
1601 np = a_nag_namelist_del_cp(np, slp->sl_dat, keep_single);
1602 if(np == NULL || (keep_single && np->n_flink == NULL))
1603 goto jleave;
1607 np = a_nag_namelist_del_cp(np, ok_vlook(LOGNAME), keep_single);
1608 if(np == NULL || (keep_single && np->n_flink == NULL))
1609 goto jleave;
1611 for(xp = lextract(ok_vlook(from), GEXTRA | GSKIN); xp != NULL;
1612 xp = xp->n_flink){
1613 np = a_nag_namelist_del_cp(np, xp->n_name, keep_single);
1614 if(np == NULL || (keep_single && np->n_flink == NULL))
1615 goto jleave;
1618 for(xp = extract(ok_vlook(sender), GEXTRA | GSKIN); xp != NULL;
1619 xp = xp->n_flink){
1620 np = a_nag_namelist_del_cp(np, xp->n_name, keep_single);
1621 if(np == NULL || (keep_single && np->n_flink == NULL))
1622 goto jleave;
1625 for(xp = lextract(ok_vlook(replyto), GEXTRA | GSKIN); xp != NULL;
1626 xp = xp->n_flink){
1627 np = a_nag_namelist_del_cp(np, xp->n_name, keep_single);
1628 if(np == NULL || (keep_single && np->n_flink == NULL))
1629 goto jleave;
1631 jleave:
1632 NYD_LEAVE;
1633 return np;
1636 FL bool_t
1637 n_is_myname(char const *name){
1638 struct name *xp;
1639 NYD_ENTER;
1641 if(a_nag_is_same_name(ok_vlook(LOGNAME), name))
1642 goto jleave;
1644 if(a_nag_altnames != NULL){
1645 struct n_strlist *slp;
1647 for(slp = a_nag_altnames; slp != NULL; slp = slp->sl_next)
1648 if(a_nag_is_same_name(slp->sl_dat, name))
1649 goto jleave;
1652 for(xp = lextract(ok_vlook(from), GEXTRA | GSKIN); xp != NULL;
1653 xp = xp->n_flink)
1654 if(a_nag_is_same_name(xp->n_name, name))
1655 goto jleave;
1657 for(xp = lextract(ok_vlook(replyto), GEXTRA | GSKIN); xp != NULL;
1658 xp = xp->n_flink)
1659 if(a_nag_is_same_name(xp->n_name, name))
1660 goto jleave;
1662 for(xp = extract(ok_vlook(sender), GEXTRA | GSKIN); xp != NULL;
1663 xp = xp->n_flink)
1664 if(a_nag_is_same_name(xp->n_name, name))
1665 goto jleave;
1667 name = NULL;
1668 jleave:
1669 NYD_LEAVE;
1670 return (name != NULL);
1673 FL int
1674 c_addrcodec(void *vp){
1675 struct n_addrguts ag;
1676 struct n_string s_b, *sp;
1677 size_t alen;
1678 int mode;
1679 char const **argv, *varname, *act, *cp;
1680 NYD_ENTER;
1682 sp = n_string_creat_auto(&s_b);
1683 argv = vp;
1684 varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
1686 act = *argv;
1687 for(cp = act; *cp != '\0' && !blankspacechar(*cp); ++cp)
1689 mode = 0;
1690 if(*act == '+')
1691 mode = 1, ++act;
1692 if(*act == '+')
1693 mode = 2, ++act;
1694 if(*act == '+')
1695 mode = 3, ++act;
1696 if(act >= cp)
1697 goto jesynopsis;
1698 alen = PTR2SIZE(cp - act);
1699 if(*cp != '\0')
1700 ++cp;
1702 /* C99 */{
1703 size_t i;
1705 i = strlen(cp);
1706 if(i <= UIZ_MAX / 4)
1707 i <<= 1;
1708 sp = n_string_reserve(sp, i);
1711 n_pstate_err_no = n_ERR_NONE;
1713 if(is_ascncaseprefix(act, "encode", alen)){
1714 /* This function cannot be a simple nalloc() wrapper even later on, since
1715 * we may need to turn any ", () or \ into quoted-pairs */
1716 char c;
1718 while((c = *cp++) != '\0'){
1719 if(((c == '(' || c == ')') && mode < 1) || (c == '"' && mode < 2) ||
1720 (c == '\\' && mode < 3))
1721 sp = n_string_push_c(sp, '\\');
1722 sp = n_string_push_c(sp, c);
1725 if(n_addrspec_with_guts(&ag, n_string_cp(sp), TRU1, TRU1) == NULL ||
1726 (ag.ag_n_flags & (NAME_ADDRSPEC_ISADDR | NAME_ADDRSPEC_INVALID)
1727 ) != NAME_ADDRSPEC_ISADDR){
1728 cp = sp->s_dat;
1729 n_pstate_err_no = n_ERR_INVAL;
1730 vp = NULL;
1731 }else{
1732 struct name *np;
1734 np = nalloc(ag.ag_input, GTO | GFULL | GSKIN);
1735 cp = np->n_fullname;
1737 }else if(mode == 0){
1738 if(is_ascncaseprefix(act, "decode", alen)){
1739 char c;
1741 while((c = *cp++) != '\0'){
1742 switch(c){
1743 case '(':
1744 sp = n_string_push_c(sp, '(');
1745 act = skip_comment(cp);
1746 if(--act > cp)
1747 sp = n_string_push_buf(sp, cp, PTR2SIZE(act - cp));
1748 sp = n_string_push_c(sp, ')');
1749 cp = ++act;
1750 break;
1751 case '"':
1752 while(*cp != '\0'){
1753 if((c = *cp++) == '"')
1754 break;
1755 if(c == '\\' && (c = *cp) != '\0')
1756 ++cp;
1757 sp = n_string_push_c(sp, c);
1759 break;
1760 default:
1761 if(c == '\\' && (c = *cp++) == '\0')
1762 break;
1763 sp = n_string_push_c(sp, c);
1764 break;
1767 cp = n_string_cp(sp);
1768 }else if(is_ascncaseprefix(act, "skin", alen)){
1769 /* Let's just use the is-single-address hack for this one, too.. */
1770 if(n_addrspec_with_guts(&ag, cp, TRU1, TRU1) == NULL ||
1771 (ag.ag_n_flags & (NAME_ADDRSPEC_ISADDR | NAME_ADDRSPEC_INVALID)
1772 ) != NAME_ADDRSPEC_ISADDR){
1773 n_pstate_err_no = n_ERR_INVAL;
1774 vp = NULL;
1775 }else{
1776 struct name *np;
1778 np = nalloc(ag.ag_input, GTO | GFULL | GSKIN);
1779 cp = np->n_name;
1781 }else
1782 goto jesynopsis;
1783 }else
1784 goto jesynopsis;
1786 if(varname == NULL){
1787 if(fprintf(n_stdout, "%s\n", cp) < 0){
1788 n_pstate_err_no = n_err_no;
1789 vp = NULL;
1791 }else if(!n_var_vset(varname, (uintptr_t)cp)){
1792 n_pstate_err_no = n_ERR_NOTSUP;
1793 vp = NULL;
1796 jleave:
1797 NYD_LEAVE;
1798 return (vp != NULL ? 0 : 1);
1799 jesynopsis:
1800 n_err(_("Synopsis: addrcodec: <[+[+[+]]]e[ncode]|d[ecode]|s[kin]> "
1801 "<rest-of-line>\n"));
1802 n_pstate_err_no = n_ERR_INVAL;
1803 vp = NULL;
1804 goto jleave;
1807 FL int
1808 c_commandalias(void *vp){
1809 struct group *gp;
1810 char const **argv, *ccp;
1811 int rv;
1812 NYD_ENTER;
1814 rv = 0;
1815 argv = vp;
1817 if((ccp = *argv) == NULL){
1818 _group_print_all(GT_COMMANDALIAS);
1819 goto jleave;
1822 /* Verify the name is a valid one, and not a command modifier */
1823 if(*ccp == '\0' || *n_cmd_isolate(ccp) != '\0' ||
1824 !asccasecmp(ccp, "ignerr") || !asccasecmp(ccp, "wysh") ||
1825 !asccasecmp(ccp, "vput")){
1826 n_err(_("`commandalias': not a valid command name: %s\n"),
1827 n_shexp_quote_cp(ccp, FAL0));
1828 rv = 1;
1829 goto jleave;
1832 if(argv[1] == NULL){
1833 if((gp = _group_find(GT_COMMANDALIAS, ccp)) != NULL)
1834 _group_print(gp, n_stdout);
1835 else{
1836 n_err(_("No such commandalias: %s\n"), n_shexp_quote_cp(ccp, FAL0));
1837 rv = 1;
1839 }else{
1840 /* Because one hardly ever redefines, anything is stored in one chunk */
1841 char *cp;
1842 size_t i, len;
1844 /* Delete the old one, if any; don't get fooled to remove them all */
1845 if(ccp[0] != '*' || ccp[1] != '\0')
1846 _group_del(GT_COMMANDALIAS, ccp);
1848 for(i = len = 0, ++argv; argv[i] != NULL; ++i)
1849 len += strlen(argv[i]) + 1;
1850 if(len == 0)
1851 len = 1;
1853 if((gp = _group_fetch(GT_COMMANDALIAS, ccp, len)) == NULL){
1854 n_err(_("Failed to create storage for commandalias: %s\n"),
1855 n_shexp_quote_cp(ccp, FAL0));
1856 rv = 1;
1857 }else{
1858 struct a_nag_cmd_alias *ncap;
1860 GP_TO_SUBCLASS(ncap, gp);
1861 GP_TO_SUBCLASS(cp, gp);
1862 cp += sizeof *ncap;
1863 ncap->ca_expand.s = cp;
1864 ncap->ca_expand.l = len - 1;
1866 for(len = 0; (ccp = *argv++) != NULL;)
1867 if((i = strlen(ccp)) > 0){
1868 if(len++ != 0)
1869 *cp++ = ' ';
1870 memcpy(cp, ccp, i);
1871 cp += i;
1873 *cp = '\0';
1876 jleave:
1877 NYD_LEAVE;
1878 return rv;
1881 FL int
1882 c_uncommandalias(void *vp){
1883 char **argv;
1884 int rv;
1885 NYD_ENTER;
1887 rv = 0;
1888 argv = vp;
1890 do if(!_group_del(GT_COMMANDALIAS, *argv)){
1891 n_err(_("No such `commandalias': %s\n"), n_shexp_quote_cp(*argv, FAL0));
1892 rv = 1;
1893 }while(*++argv != NULL);
1894 NYD_LEAVE;
1895 return rv;
1898 FL char const *
1899 n_commandalias_exists(char const *name, struct str const **expansion_or_null){
1900 struct group *gp;
1901 NYD_ENTER;
1903 if((gp = _group_find(GT_COMMANDALIAS, name)) != NULL){
1904 name = gp->g_id;
1906 if(expansion_or_null != NULL){
1907 struct a_nag_cmd_alias *ncap;
1909 GP_TO_SUBCLASS(ncap, gp);
1910 *expansion_or_null = &ncap->ca_expand;
1912 }else
1913 name = NULL;
1914 NYD_LEAVE;
1915 return name;
1918 FL bool_t
1919 n_alias_is_valid_name(char const *name){
1920 char c;
1921 char const *cp;
1922 bool_t rv;
1923 NYD2_ENTER;
1925 for(rv = TRU1, cp = name++; (c = *cp++) != '\0';)
1926 /* User names, plus things explicitly mentioned in Postfix aliases(5).
1927 * As an extension, allow period: [[:alnum:]_#:@.-]+$? */
1928 if(!alnumchar(c) && c != '_' && c != '-' &&
1929 c != '#' && c != ':' && c != '@' &&
1930 c != '.'){
1931 if(c == '$' && cp != name && *cp == '\0')
1932 break;
1933 rv = FAL0;
1934 break;
1936 NYD2_LEAVE;
1937 return rv;
1940 FL int
1941 c_alias(void *v)
1943 char const *ecp;
1944 char **argv;
1945 struct group *gp;
1946 int rv;
1947 NYD_ENTER;
1949 rv = 0;
1950 argv = v;
1951 n_UNINIT(ecp, NULL);
1953 if (*argv == NULL)
1954 _group_print_all(GT_ALIAS);
1955 else if (!n_alias_is_valid_name(*argv)) {
1956 ecp = N_("Not a valid alias name: %s\n");
1957 goto jerr;
1958 } else if (argv[1] == NULL) {
1959 if ((gp = _group_find(GT_ALIAS, *argv)) != NULL)
1960 _group_print(gp, n_stdout);
1961 else {
1962 ecp = N_("No such alias: %s\n");
1963 goto jerr;
1965 } else if ((gp = _group_fetch(GT_ALIAS, *argv, 0)) == NULL) {
1966 ecp = N_("Failed to create alias storage for: %s\n");
1967 jerr:
1968 n_err(V_(ecp), n_shexp_quote_cp(*argv, FAL0));
1969 rv = 1;
1970 } else {
1971 struct grp_names *gnp_tail, *gnp;
1972 struct grp_names_head *gnhp;
1974 GP_TO_SUBCLASS(gnhp, gp);
1976 if((gnp_tail = gnhp->gnh_head) != NULL)
1977 while((gnp = gnp_tail->gn_next) != NULL)
1978 gnp_tail = gnp;
1980 for(++argv; *argv != NULL; ++argv){
1981 size_t i;
1983 i = strlen(*argv) +1;
1984 gnp = smalloc(n_VSTRUCT_SIZEOF(struct grp_names, gn_id) + i);
1985 if(gnp_tail != NULL)
1986 gnp_tail->gn_next = gnp;
1987 else
1988 gnhp->gnh_head = gnp;
1989 gnp_tail = gnp;
1990 gnp->gn_next = NULL;
1991 memcpy(gnp->gn_id, *argv, i);
1994 NYD_LEAVE;
1995 return rv;
1998 FL int
1999 c_unalias(void *v)
2001 char **argv = v;
2002 int rv = 0;
2003 NYD_ENTER;
2005 do if (!_group_del(GT_ALIAS, *argv)) {
2006 n_err(_("No such alias: %s\n"), *argv);
2007 rv = 1;
2008 } while (*++argv != NULL);
2009 NYD_LEAVE;
2010 return rv;
2013 FL int
2014 c_mlist(void *v)
2016 int rv;
2017 NYD_ENTER;
2019 rv = _mlmux(GT_MLIST, v);
2020 NYD_LEAVE;
2021 return rv;
2024 FL int
2025 c_unmlist(void *v)
2027 int rv;
2028 NYD_ENTER;
2030 rv = _unmlmux(GT_MLIST, v);
2031 NYD_LEAVE;
2032 return rv;
2035 FL int
2036 c_mlsubscribe(void *v)
2038 int rv;
2039 NYD_ENTER;
2041 rv = _mlmux(GT_MLIST | GT_SUBSCRIBE, v);
2042 NYD_LEAVE;
2043 return rv;
2046 FL int
2047 c_unmlsubscribe(void *v)
2049 int rv;
2050 NYD_ENTER;
2052 rv = _unmlmux(GT_MLIST | GT_SUBSCRIBE, v);
2053 NYD_LEAVE;
2054 return rv;
2057 FL enum mlist_state
2058 is_mlist(char const *name, bool_t subscribed_only)
2060 struct group *gp;
2061 #ifdef HAVE_REGEX
2062 struct grp_regex **lpp, *grp;
2063 bool_t re2;
2064 #endif
2065 enum mlist_state rv;
2066 NYD_ENTER;
2068 gp = _group_find(GT_MLIST, name);
2069 rv = (gp != NULL) ? MLIST_KNOWN : MLIST_OTHER;
2070 if (rv == MLIST_KNOWN) {
2071 if (gp->g_type & GT_SUBSCRIBE)
2072 rv = MLIST_SUBSCRIBED;
2073 else if (subscribed_only)
2074 rv = MLIST_OTHER;
2075 /* Of course, if that is a regular expression it doesn't mean a thing */
2076 #ifdef HAVE_REGEX
2077 if (gp->g_type & GT_REGEX)
2078 rv = MLIST_OTHER;
2079 else
2080 #endif
2081 goto jleave;
2084 /* Not in the hashmap (as something matchable), walk the lists */
2085 #ifdef HAVE_REGEX
2086 re2 = FAL0;
2087 lpp = &_mlsub_regex;
2088 jregex_redo:
2089 if ((grp = *lpp) != NULL) {
2090 do if (regexec(&grp->gr_regex, name, 0,NULL, 0) != REG_NOMATCH) {
2091 /* Relink as the head of this list if the hit count of this group is
2092 * >= 25% of the average hit count */
2093 size_t i;
2094 if (!re2)
2095 i = ++_mlsub_hits / _mlsub_size;
2096 else
2097 i = ++_mlist_hits / _mlist_size;
2098 i >>= 2;
2100 if (++grp->gr_hits >= i && *lpp != grp && grp->gr_next != grp) {
2101 grp->gr_last->gr_next = grp->gr_next;
2102 grp->gr_next->gr_last = grp->gr_last;
2103 (grp->gr_last = (*lpp)->gr_last)->gr_next = grp;
2104 (grp->gr_next = *lpp)->gr_last = grp;
2105 *lpp = grp;
2107 rv = !re2 ? MLIST_SUBSCRIBED : MLIST_KNOWN;
2108 goto jleave;
2109 } while ((grp = grp->gr_next) != *lpp);
2111 if (!re2 && !subscribed_only) {
2112 re2 = TRU1;
2113 lpp = &_mlist_regex;
2114 goto jregex_redo;
2116 assert(rv == MLIST_OTHER);
2117 #endif
2119 jleave:
2120 NYD_LEAVE;
2121 return rv;
2124 FL int
2125 c_shortcut(void *v)
2127 struct group *gp;
2128 char **argv;
2129 int rv;
2130 NYD_ENTER;
2132 rv = 0;
2133 argv = v;
2135 if(*argv == NULL)
2136 _group_print_all(GT_SHORTCUT);
2137 else if(argv[1] == NULL){
2138 if((gp = _group_find(GT_SHORTCUT, *argv)) != NULL)
2139 _group_print(gp, n_stdout);
2140 else{
2141 n_err(_("No such shortcut: %s\n"), n_shexp_quote_cp(*argv, FAL0));
2142 rv = 1;
2144 }else for (; *argv != NULL; argv += 2) {
2145 /* Because one hardly ever redefines, anything is stored in one chunk */
2146 size_t l;
2147 char *cp;
2149 if (argv[1] == NULL) {
2150 n_err(_("Synopsis: shortcut: <shortcut> <expansion>\n"));
2151 rv = 1;
2152 break;
2154 if (_group_find(GT_SHORTCUT, *argv) != NULL)
2155 _group_del(GT_SHORTCUT, *argv);
2157 l = strlen(argv[1]) +1;
2158 if ((gp = _group_fetch(GT_SHORTCUT, *argv, l)) == NULL) {
2159 n_err(_("Failed to create storage for shortcut: %s\n"),
2160 n_shexp_quote_cp(*argv, FAL0));
2161 rv = 1;
2162 } else {
2163 GP_TO_SUBCLASS(cp, gp);
2164 memcpy(cp, argv[1], l);
2167 NYD_LEAVE;
2168 return rv;
2171 FL int
2172 c_unshortcut(void *v)
2174 char **argv = v;
2175 int rv = 0;
2176 NYD_ENTER;
2178 do if (!_group_del(GT_SHORTCUT, *argv)) {
2179 n_err(_("No such shortcut: %s\n"), *argv);
2180 rv = 1;
2181 } while (*++argv != NULL);
2182 NYD_LEAVE;
2183 return rv;
2186 FL char const *
2187 shortcut_expand(char const *str){
2188 struct group *gp;
2189 NYD_ENTER;
2191 if((gp = _group_find(GT_SHORTCUT, str)) != NULL)
2192 GP_TO_SUBCLASS(str, gp);
2193 else
2194 str = NULL;
2195 NYD_LEAVE;
2196 return str;
2199 FL int
2200 c_charsetalias(void *vp){
2201 struct group *gp;
2202 char **argv;
2203 int rv;
2204 NYD_ENTER;
2206 rv = 0;
2207 argv = vp;
2209 if(*argv == NULL)
2210 _group_print_all(GT_CHARSETALIAS);
2211 else if(argv[1] == NULL){
2212 if((gp = _group_find(GT_CHARSETALIAS, *argv)) != NULL)
2213 _group_print(gp, n_stdout);
2214 else{
2215 n_err(_("No such charsetalias: %s\n"), n_shexp_quote_cp(*argv, FAL0));
2216 rv = 1;
2218 }else for(; *argv != NULL; argv += 2){
2219 /* Because one hardly ever redefines, anything is stored in one chunk */
2220 char const *ccp;
2221 char *cp, c;
2222 size_t l;
2224 if(argv[1] == NULL){
2225 n_err(_("Synopsis: charsetalias: <charset> <charset-alias>\n"));
2226 rv = 1;
2227 break;
2230 /* Delete the old one, if any; don't get fooled to remove them all */
2231 ccp = argv[0];
2232 if(ccp[0] != '*' || ccp[1] != '\0')
2233 _group_del(GT_CHARSETALIAS, ccp);
2235 l = strlen(argv[1]) +1;
2236 if ((gp = _group_fetch(GT_CHARSETALIAS, ccp, l)) == NULL) {
2237 n_err(_("Failed to create storage for charsetalias: %s\n"),
2238 n_shexp_quote_cp(ccp, FAL0));
2239 rv = 1;
2240 } else {
2241 GP_TO_SUBCLASS(cp, gp);
2242 for(ccp = argv[1]; (c = *ccp++) != '\0';)
2243 *cp++ = lowerconv(c);
2244 *cp = '\0';
2247 NYD_LEAVE;
2248 return rv;
2251 FL int
2252 c_uncharsetalias(void *vp){
2253 char **argv;
2254 int rv;
2255 NYD_ENTER;
2257 rv = 0;
2258 argv = vp;
2260 do if(!_group_del(GT_CHARSETALIAS, *argv)){
2261 n_err(_("No such `charsetalias': %s\n"), n_shexp_quote_cp(*argv, FAL0));
2262 rv = 1;
2263 }while(*++argv != NULL);
2264 NYD_LEAVE;
2265 return rv;
2268 FL char const *
2269 n_charsetalias_expand(char const *cp){
2270 struct group *gp;
2271 size_t i;
2272 char const *cp_orig;
2273 NYD_ENTER;
2275 cp_orig = cp;
2277 for(i = 0; (gp = _group_find(GT_CHARSETALIAS, cp)) != NULL;){
2278 GP_TO_SUBCLASS(cp, gp);
2279 if(++i == 8) /* XXX Magic (same as for `ghost' expansion) */
2280 break;
2283 if(cp != cp_orig)
2284 cp = savestr(cp);
2285 NYD_LEAVE;
2286 return cp;
2289 FL int
2290 c_filetype(void *vp){ /* TODO support automatic chains: .tar.gz -> .gz + .tar */
2291 struct group *gp;
2292 char **argv; /* TODO While there: let ! prefix mean: direct execlp(2) */
2293 int rv;
2294 NYD_ENTER;
2296 rv = 0;
2297 argv = vp;
2299 if(*argv == NULL)
2300 _group_print_all(GT_FILETYPE);
2301 else if(argv[1] == NULL){
2302 if((gp = _group_find(GT_FILETYPE, *argv)) != NULL)
2303 _group_print(gp, n_stdout);
2304 else{
2305 n_err(_("No such filetype: %s\n"), n_shexp_quote_cp(*argv, FAL0));
2306 rv = 1;
2308 }else for(; *argv != NULL; argv += 3){
2309 /* Because one hardly ever redefines, anything is stored in one chunk */
2310 char const *ccp;
2311 char *cp, c;
2312 size_t llc, lsc;
2314 if(argv[1] == NULL || argv[2] == NULL){
2315 n_err(_("Synopsis: filetype: <extension> <load-cmd> <save-cmd>\n"));
2316 rv = 1;
2317 break;
2320 /* Delete the old one, if any; don't get fooled to remove them all */
2321 ccp = argv[0];
2322 if(ccp[0] != '*' || ccp[1] != '\0')
2323 _group_del(GT_FILETYPE, ccp);
2325 /* Lowercase it all (for display purposes) */
2326 cp = savestr(ccp);
2327 ccp = cp;
2328 while((c = *cp) != '\0')
2329 *cp++ = lowerconv(c);
2331 llc = strlen(argv[1]) +1;
2332 lsc = strlen(argv[2]) +1;
2333 if(UIZ_MAX - llc <= lsc)
2334 goto jenomem;
2336 if((gp = _group_fetch(GT_FILETYPE, ccp, llc + lsc)) == NULL){
2337 jenomem:
2338 n_err(_("Failed to create storage for filetype: %s\n"),
2339 n_shexp_quote_cp(argv[0], FAL0));
2340 rv = 1;
2341 }else{
2342 struct a_nag_file_type *nftp;
2344 GP_TO_SUBCLASS(nftp, gp);
2345 GP_TO_SUBCLASS(cp, gp);
2346 cp += sizeof *nftp;
2347 memcpy(nftp->nft_load.s = cp, argv[1], llc);
2348 cp += llc;
2349 nftp->nft_load.l = --llc;
2350 memcpy(nftp->nft_save.s = cp, argv[2], lsc);
2351 cp += lsc;
2352 nftp->nft_save.l = --lsc;
2355 NYD_LEAVE;
2356 return rv;
2359 FL int
2360 c_unfiletype(void *vp){
2361 char **argv;
2362 int rv;
2363 NYD_ENTER;
2365 rv = 0;
2366 argv = vp;
2368 do if(!_group_del(GT_FILETYPE, *argv)){
2369 n_err(_("No such `filetype': %s\n"), n_shexp_quote_cp(*argv, FAL0));
2370 rv = 1;
2371 }while(*++argv != NULL);
2372 NYD_LEAVE;
2373 return rv;
2376 FL bool_t
2377 n_filetype_trial(struct n_file_type *res_or_null, char const *file){
2378 struct stat stb;
2379 struct group_lookup gl;
2380 struct n_string s, *sp;
2381 struct group const *gp;
2382 ui32_t l;
2383 NYD2_ENTER;
2385 sp = n_string_creat_auto(&s);
2386 sp = n_string_assign_cp(sp, file);
2387 sp = n_string_push_c(sp, '.');
2388 l = sp->s_len;
2390 for(gp = _group_go_first(GT_FILETYPE, &gl); gp != NULL;
2391 gp = _group_go_next(&gl)){
2392 sp = n_string_trunc(sp, l);
2393 sp = n_string_push_buf(sp, gp->g_id,
2394 gp->g_subclass_off - gp->g_id_len_sub);
2396 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2397 if(res_or_null != NULL){
2398 struct a_nag_file_type *nftp;
2400 GP_TO_SUBCLASS(nftp, gp);
2401 res_or_null->ft_ext_dat = gp->g_id;
2402 res_or_null->ft_ext_len = gp->g_subclass_off - gp->g_id_len_sub;
2403 res_or_null->ft_load_dat = nftp->nft_load.s;
2404 res_or_null->ft_load_len = nftp->nft_load.l;
2405 res_or_null->ft_save_dat = nftp->nft_save.s;
2406 res_or_null->ft_save_len = nftp->nft_save.l;
2408 goto jleave; /* TODO after v15 legacy drop: break; */
2412 /* TODO v15 legacy code: automatic file hooks for .{bz2,gz,xz},
2413 * TODO but NOT supporting *file-hook-{load,save}-EXTENSION* */
2414 gp = (struct group*)0x1;
2416 sp = n_string_trunc(sp, l);
2417 sp = n_string_push_buf(sp, a_nag_OBSOLETE_xz.ft_ext_dat,
2418 a_nag_OBSOLETE_xz.ft_ext_len);
2419 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2420 n_OBSOLETE(".xz support will vanish, please use the `filetype' command");
2421 if(res_or_null != NULL)
2422 *res_or_null = a_nag_OBSOLETE_xz;
2423 goto jleave;
2426 sp = n_string_trunc(sp, l);
2427 sp = n_string_push_buf(sp, a_nag_OBSOLETE_gz.ft_ext_dat,
2428 a_nag_OBSOLETE_gz.ft_ext_len);
2429 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2430 n_OBSOLETE(".gz support will vanish, please use the `filetype' command");
2431 if(res_or_null != NULL)
2432 *res_or_null = a_nag_OBSOLETE_gz;
2433 goto jleave;
2436 sp = n_string_trunc(sp, l);
2437 sp = n_string_push_buf(sp, a_nag_OBSOLETE_bz2.ft_ext_dat,
2438 a_nag_OBSOLETE_bz2.ft_ext_len);
2439 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2440 n_OBSOLETE(".bz2 support will vanish, please use the `filetype' command");
2441 if(res_or_null != NULL)
2442 *res_or_null = a_nag_OBSOLETE_bz2;
2443 goto jleave;
2446 gp = NULL;
2448 jleave:
2449 NYD2_LEAVE;
2450 return (gp != NULL);
2453 FL bool_t
2454 n_filetype_exists(struct n_file_type *res_or_null, char const *file){
2455 char const *ext, *lext;
2456 NYD2_ENTER;
2458 if((ext = strrchr(file, '/')) != NULL)
2459 file = ++ext;
2461 for(lext = NULL; (ext = strchr(file, '.')) != NULL; lext = file = ext){
2462 struct group const *gp;
2464 if((gp = _group_find(GT_FILETYPE, ++ext)) != NULL){
2465 lext = ext;
2466 if(res_or_null != NULL){
2467 struct a_nag_file_type *nftp;
2469 GP_TO_SUBCLASS(nftp, gp);
2470 res_or_null->ft_ext_dat = gp->g_id;
2471 res_or_null->ft_ext_len = gp->g_subclass_off - gp->g_id_len_sub;
2472 res_or_null->ft_load_dat = nftp->nft_load.s;
2473 res_or_null->ft_load_len = nftp->nft_load.l;
2474 res_or_null->ft_save_dat = nftp->nft_save.s;
2475 res_or_null->ft_save_len = nftp->nft_save.l;
2477 goto jleave; /* TODO after v15 legacy drop: break; */
2481 /* TODO v15 legacy code: automatic file hooks for .{bz2,gz,xz},
2482 * TODO as well as supporting *file-hook-{load,save}-EXTENSION* */
2483 if(lext == NULL)
2484 goto jleave;
2486 if(!asccasecmp(lext, "xz")){
2487 n_OBSOLETE(".xz support will vanish, please use the `filetype' command");
2488 if(res_or_null != NULL)
2489 *res_or_null = a_nag_OBSOLETE_xz;
2490 goto jleave;
2491 }else if(!asccasecmp(lext, "gz")){
2492 n_OBSOLETE(".gz support will vanish, please use the `filetype' command");
2493 if(res_or_null != NULL)
2494 *res_or_null = a_nag_OBSOLETE_gz;
2495 goto jleave;
2496 }else if(!asccasecmp(lext, "bz2")){
2497 n_OBSOLETE(".bz2 support will vanish, please use the `filetype' command");
2498 if(res_or_null != NULL)
2499 *res_or_null = a_nag_OBSOLETE_bz2;
2500 goto jleave;
2501 }else{
2502 char const *cload, *csave;
2503 char *vbuf;
2504 size_t l;
2506 #undef a_X1
2507 #define a_X1 "file-hook-load-"
2508 #undef a_X2
2509 #define a_X2 "file-hook-save-"
2510 l = strlen(lext);
2511 vbuf = n_lofi_alloc(l + n_MAX(sizeof(a_X1), sizeof(a_X2)));
2513 memcpy(vbuf, a_X1, sizeof(a_X1) -1);
2514 memcpy(&vbuf[sizeof(a_X1) -1], lext, l);
2515 vbuf[sizeof(a_X1) -1 + l] = '\0';
2516 cload = n_var_vlook(vbuf, FAL0);
2518 memcpy(vbuf, a_X2, sizeof(a_X2) -1);
2519 memcpy(&vbuf[sizeof(a_X2) -1], lext, l);
2520 vbuf[sizeof(a_X2) -1 + l] = '\0';
2521 csave = n_var_vlook(vbuf, FAL0);
2523 #undef a_X2
2524 #undef a_X1
2525 n_lofi_free(vbuf);
2527 if((csave != NULL) | (cload != NULL)){
2528 n_OBSOLETE("*file-hook-{load,save}-EXTENSION* will vanish, "
2529 "please use the `filetype' command");
2531 if(((csave != NULL) ^ (cload != NULL)) == 0){
2532 if(res_or_null != NULL){
2533 res_or_null->ft_ext_dat = lext;
2534 res_or_null->ft_ext_len = l;
2535 res_or_null->ft_load_dat = cload;
2536 res_or_null->ft_load_len = strlen(cload);
2537 res_or_null->ft_save_dat = csave;
2538 res_or_null->ft_save_len = strlen(csave);
2540 goto jleave;
2541 }else
2542 n_alert(_("Incomplete *file-hook-{load,save}-EXTENSION* for: .%s"),
2543 lext);
2547 lext = NULL;
2549 jleave:
2550 NYD2_LEAVE;
2551 return (lext != NULL);
2554 /* s-it-mode */