collect.c:print_collf(): CID 1377037 !
[s-mailx.git] / nam-a-grp.c
blob0c85af3bd24e68fcae95f5a1559d6ee15647503a
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 bool_t rv;
234 char c1, c2, c1r, c2r;
235 NYD2_ENTER;
237 if(ok_blook(allnet)){
238 for(;; ++n1, ++n2){
239 c1 = *n1;
240 c1 = lowerconv(c1);
241 c1r = (c1 == '\0' || c1 == '@');
242 c2 = *n2;
243 c2 = lowerconv(c2);
244 c2r = (c2 == '\0' || c2 == '@');
246 if(c1r || c2r){
247 rv = (c1r == c2r);
248 break;
249 }else if(c1 != c2){
250 rv = FAL0;
251 break;
254 }else
255 rv = !asccasecmp(n1, n2);
256 NYD2_LEAVE;
257 return rv;
260 static struct name *
261 a_nag_namelist_mark_name(struct name *np, char const *name){
262 struct name *p;
263 NYD2_ENTER;
265 for(p = np; p != NULL; p = p->n_flink)
266 if(!(p->n_type & GDEL) && !(p->n_flags & (ui32_t)SI32_MIN) &&
267 a_nag_is_same_name(p->n_name, name))
268 p->n_flags |= (ui32_t)SI32_MIN;
269 NYD2_LEAVE;
270 return np;
273 static char const *
274 yankname(char const *ap, char *wbuf, char const *separators, int keepcomms)
276 char const *cp;
277 char *wp, c, inquote, lc, lastsp;
278 NYD_ENTER;
280 *(wp = wbuf) = '\0';
282 /* Skip over intermediate list trash, as in ".org> , <xy@zz.org>" */
283 for (c = *ap; blankchar(c) || c == ','; c = *++ap)
285 if (c == '\0') {
286 cp = NULL;
287 goto jleave;
290 /* Parse a full name: TODO RFC 5322
291 * - Keep everything in quotes, liberal handle *quoted-pair*s therein
292 * - Skip entire (nested) comments
293 * - In non-quote, non-comment, join adjacent space to a single SP
294 * - Understand separators only in non-quote, non-comment context,
295 * and only if not part of a *quoted-pair* (XXX too liberal) */
296 cp = ap;
297 for (inquote = lc = lastsp = 0;; lc = c, ++cp) {
298 c = *cp;
299 if (c == '\0')
300 break;
301 if (c == '\\')
302 goto jwpwc;
303 if (c == '"') {
304 if (lc != '\\')
305 inquote = !inquote;
306 #if 0 /* TODO when doing real RFC 5322 parsers - why have i done this? */
307 else
308 --wp;
309 #endif
310 goto jwpwc;
312 if (inquote || lc == '\\') {
313 jwpwc:
314 *wp++ = c;
315 lastsp = 0;
316 continue;
318 if (c == '(') {
319 ap = cp;
320 cp = skip_comment(cp + 1);
321 if (keepcomms)
322 while (ap < cp)
323 *wp++ = *ap++;
324 --cp;
325 lastsp = 0;
326 continue;
328 if (strchr(separators, c) != NULL)
329 break;
331 lc = lastsp;
332 lastsp = blankchar(c);
333 if (!lastsp || !lc)
334 *wp++ = c;
336 if (blankchar(lc))
337 --wp;
339 *wp = '\0';
340 jleave:
341 NYD_LEAVE;
342 return cp;
345 static struct name *
346 _extract1(char const *line, enum gfield ntype, char const *separators,
347 bool_t keepcomms)
349 struct name *topp, *np, *t;
350 char const *cp;
351 char *nbuf;
352 NYD_ENTER;
354 topp = NULL;
355 if (line == NULL || *line == '\0')
356 goto jleave;
358 np = NULL;
359 cp = line;
360 nbuf = smalloc(strlen(line) +1);
361 while ((cp = yankname(cp, nbuf, separators, keepcomms)) != NULL) {
362 t = nalloc(nbuf, ntype);
363 if (topp == NULL)
364 topp = t;
365 else
366 np->n_flink = t;
367 t->n_blink = np;
368 np = t;
370 free(nbuf);
371 jleave:
372 NYD_LEAVE;
373 return topp;
376 static struct name *
377 a_nag_gexpand(size_t level, struct name *nlist, struct group *gp, bool_t metoo,
378 int ntype){
379 struct grp_names *gnp;
380 struct name *nlist_tail;
381 char const *logname;
382 struct grp_names_head *gnhp;
383 NYD2_ENTER;
385 if(UICMP(z, level++, >, n_ALIAS_MAXEXP)){
386 n_err(_("Expanding alias to depth larger than %d\n"), n_ALIAS_MAXEXP);
387 goto jleave;
390 GP_TO_SUBCLASS(gnhp, gp);
391 logname = ok_vlook(LOGNAME);
393 for(gnp = gnhp->gnh_head; gnp != NULL; gnp = gnp->gn_next){
394 struct group *ngp;
395 char *cp;
397 cp = gnp->gn_id;
399 if(!strcmp(cp, gp->g_id))
400 goto jas_is;
402 if((ngp = _group_find(GT_ALIAS, cp)) != NULL){
403 /* For S-nail(1), the "alias" may *be* the sender in that a name maps
404 * to a full address specification; aliases cannot be empty */
405 struct grp_names_head *ngnhp;
406 GP_TO_SUBCLASS(ngnhp, ngp);
408 assert(ngnhp->gnh_head != NULL);
409 if(metoo || ngnhp->gnh_head->gn_next != NULL ||
410 !a_nag_is_same_name(cp, logname))
411 nlist = a_nag_gexpand(level, nlist, ngp, metoo, ntype);
412 continue;
415 /* Here we should allow to expand to itself if only person in alias */
416 jas_is:
417 if(metoo || gnhp->gnh_head->gn_next == NULL ||
418 !a_nag_is_same_name(cp, logname)){
419 struct name *np;
421 np = nalloc(cp, ntype | GFULL);
422 if((nlist_tail = nlist) != NULL){
423 while(nlist_tail->n_flink != NULL)
424 nlist_tail = nlist_tail->n_flink;
425 nlist_tail->n_flink = np;
426 np->n_blink = nlist_tail;
427 }else
428 nlist = np;
431 jleave:
432 NYD2_LEAVE;
433 return nlist;
436 static int
437 a_nag_elide_qsort(void const *s1, void const *s2){
438 struct name const * const *np1, * const *np2;
439 int rv;
440 NYD2_ENTER;
442 np1 = s1;
443 np2 = s2;
444 rv = asccasecmp((*np1)->n_name, (*np2)->n_name);
445 NYD2_LEAVE;
446 return rv;
449 static struct group *
450 _group_lookup(enum group_type gt, struct group_lookup *glp, char const *id){
451 char c1;
452 struct group *lgp, *gp;
453 NYD_ENTER;
455 gt &= GT_MASK;
456 lgp = NULL;
457 glp->gl_htable =
458 ( gt & GT_COMMANDALIAS ? _commandalias_heads
459 : (gt & GT_ALIAS ? _alias_heads
460 : (gt & GT_MLIST ? _mlist_heads
461 : (gt & GT_SHORTCUT ? _shortcut_heads
462 : (gt & GT_CHARSETALIAS ? _charsetalias_heads
463 : (/*gt & GT_FILETYPE ?*/ _filetype_heads
464 ))))));
465 gp = *(glp->gl_slot = &glp->gl_htable[
466 ((gt & (GT_MLIST | GT_CHARSETALIAS | GT_FILETYPE))
467 ? n_torek_ihash(id) : n_torek_hash(id)) % HSHSIZE]);
468 c1 = *id++;
470 if(gt & (GT_MLIST | GT_CHARSETALIAS | GT_FILETYPE)){
471 c1 = lowerconv(c1);
472 for(; gp != NULL; lgp = gp, gp = gp->g_next)
473 if((gp->g_type & gt) && *gp->g_id == c1 &&
474 !asccasecmp(&gp->g_id[1], id))
475 break;
476 }else{
477 for(; gp != NULL; lgp = gp, gp = gp->g_next)
478 if((gp->g_type & gt) && *gp->g_id == c1 && !strcmp(&gp->g_id[1], id))
479 break;
482 glp->gl_slot_last = lgp;
483 glp->gl_group = gp;
484 NYD_LEAVE;
485 return gp;
488 static struct group *
489 _group_find(enum group_type gt, char const *id)
491 struct group_lookup gl;
492 struct group *gp;
493 NYD_ENTER;
495 gp = _group_lookup(gt, &gl, id);
496 NYD_LEAVE;
497 return gp;
500 static struct group *
501 _group_go_first(enum group_type gt, struct group_lookup *glp)
503 struct group **gpa, *gp;
504 size_t i;
505 NYD_ENTER;
507 for (glp->gl_htable = gpa = (
508 gt & GT_COMMANDALIAS ? _commandalias_heads
509 : (gt & GT_ALIAS ? _alias_heads
510 : (gt & GT_MLIST ? _mlist_heads
511 : (gt & GT_SHORTCUT ? _shortcut_heads
512 : (gt & GT_CHARSETALIAS ? _charsetalias_heads
513 : (gt & GT_FILETYPE ? _filetype_heads
514 : NULL)))))
515 ), i = 0;
516 i < HSHSIZE; ++gpa, ++i)
517 if ((gp = *gpa) != NULL) {
518 glp->gl_slot = gpa;
519 glp->gl_group = gp;
520 goto jleave;
523 glp->gl_group = gp = NULL;
524 jleave:
525 glp->gl_slot_last = NULL;
526 NYD_LEAVE;
527 return gp;
530 static struct group *
531 _group_go_next(struct group_lookup *glp)
533 struct group *gp, **gpa;
534 NYD_ENTER;
536 if ((gp = glp->gl_group->g_next) != NULL)
537 glp->gl_slot_last = glp->gl_group;
538 else {
539 glp->gl_slot_last = NULL;
540 for (gpa = glp->gl_htable + HSHSIZE; ++glp->gl_slot < gpa;)
541 if ((gp = *glp->gl_slot) != NULL)
542 break;
544 glp->gl_group = gp;
545 NYD_LEAVE;
546 return gp;
549 static struct group *
550 _group_fetch(enum group_type gt, char const *id, size_t addsz)
552 struct group_lookup gl;
553 struct group *gp;
554 size_t l, i;
555 NYD_ENTER;
557 if ((gp = _group_lookup(gt, &gl, id)) != NULL)
558 goto jleave;
560 l = strlen(id) +1;
561 if (UIZ_MAX - n_ALIGN(l) <= n_ALIGN(n_VSTRUCT_SIZEOF(struct group, g_id)))
562 goto jleave;
564 i = n_ALIGN(n_VSTRUCT_SIZEOF(struct group, g_id) + l);
565 switch (gt & GT_MASK) {
566 case GT_COMMANDALIAS:
567 addsz += sizeof(struct a_nag_cmd_alias);
568 break;
569 case GT_ALIAS:
570 addsz += sizeof(struct grp_names_head);
571 break;
572 case GT_FILETYPE:
573 addsz += sizeof(struct a_nag_file_type);
574 break;
575 case GT_MLIST:
576 #ifdef HAVE_REGEX
577 if (n_is_maybe_regex(id)) {
578 addsz = sizeof(struct grp_regex);
579 gt |= GT_REGEX;
581 #endif
582 /* FALLTHRU */
583 case GT_SHORTCUT:
584 case GT_CHARSETALIAS:
585 default:
586 break;
588 if (UIZ_MAX - i < addsz || UI32_MAX <= i || UI16_MAX < i - l)
589 goto jleave;
591 gp = smalloc(i + addsz);
592 memcpy(gp->g_id, id, l);
593 gp->g_subclass_off = (ui32_t)i;
594 gp->g_id_len_sub = (ui16_t)(i - --l);
595 gp->g_type = gt;
596 if(gt & (GT_MLIST | GT_CHARSETALIAS | GT_FILETYPE)){
597 char *cp, c;
599 for(cp = gp->g_id; (c = *cp) != '\0'; ++cp)
600 *cp = lowerconv(c);
603 if (gt & GT_ALIAS) {
604 struct grp_names_head *gnhp;
606 GP_TO_SUBCLASS(gnhp, gp);
607 gnhp->gnh_head = NULL;
609 #ifdef HAVE_REGEX
610 else if (/*(gt & GT_MLIST) &&*/ gt & GT_REGEX) {
611 int s;
612 struct grp_regex *grp;
613 GP_TO_SUBCLASS(grp, gp);
615 if((s = regcomp(&grp->gr_regex, id,
616 REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0){
617 n_err(_("Invalid regular expression: %s: %s\n"),
618 n_shexp_quote_cp(id, FAL0), n_regex_err_to_doc(&grp->gr_regex, s));
619 free(gp);
620 gp = NULL;
621 goto jleave;
623 grp->gr_mygroup = gp;
624 _mlmux_linkin(gp);
626 #endif
628 gp->g_next = *gl.gl_slot;
629 *gl.gl_slot = gp;
630 jleave:
631 NYD_LEAVE;
632 return gp;
635 static bool_t
636 _group_del(enum group_type gt, char const *id)
638 enum group_type xgt = gt & GT_MASK;
639 struct group_lookup gl;
640 struct group *gp;
641 NYD_ENTER;
643 /* Delete 'em all? */
644 if (id[0] == '*' && id[1] == '\0') {
645 for (gp = _group_go_first(gt, &gl); gp != NULL;)
646 gp = (gp->g_type & xgt) ? __group_del(&gl) : _group_go_next(&gl);
647 gp = (struct group*)TRU1;
648 } else if ((gp = _group_lookup(gt, &gl, id)) != NULL) {
649 if (gp->g_type & xgt)
650 __group_del(&gl);
651 else
652 gp = NULL;
654 NYD_LEAVE;
655 return (gp != NULL);
658 static struct group *
659 __group_del(struct group_lookup *glp)
661 struct group *x, *gp;
662 NYD_ENTER;
664 /* Overly complicated: link off this node, step ahead to next.. */
665 x = glp->gl_group;
666 if((gp = glp->gl_slot_last) != NULL)
667 gp = (gp->g_next = x->g_next);
668 else{
669 glp->gl_slot_last = NULL;
670 gp = (*glp->gl_slot = x->g_next);
672 if(gp == NULL){
673 struct group **gpa;
675 for(gpa = &glp->gl_htable[HSHSIZE]; ++glp->gl_slot < gpa;)
676 if((gp = *glp->gl_slot) != NULL)
677 break;
680 glp->gl_group = gp;
682 if (x->g_type & GT_ALIAS)
683 __names_del(x);
684 #ifdef HAVE_REGEX
685 else if (/*(x->g_type & GT_MLIST) &&*/ x->g_type & GT_REGEX) {
686 struct grp_regex *grp;
687 GP_TO_SUBCLASS(grp, x);
689 regfree(&grp->gr_regex);
690 _mlmux_linkout(x);
692 #endif
694 free(x);
695 NYD_LEAVE;
696 return gp;
699 static void
700 __names_del(struct group *gp)
702 struct grp_names_head *gnhp;
703 struct grp_names *gnp;
704 NYD_ENTER;
706 GP_TO_SUBCLASS(gnhp, gp);
707 for (gnp = gnhp->gnh_head; gnp != NULL;) {
708 struct grp_names *x = gnp;
709 gnp = gnp->gn_next;
710 free(x);
712 NYD_LEAVE;
715 static void
716 _group_print_all(enum group_type gt)
718 enum group_type xgt;
719 struct group **gpa;
720 struct group const *gp;
721 ui32_t h, i;
722 char const **ida;
723 FILE *fp;
724 size_t lines;
725 NYD_ENTER;
727 xgt = gt & GT_PRINT_MASK;
728 gpa = ( xgt & GT_COMMANDALIAS ? _commandalias_heads
729 : (xgt & GT_ALIAS ? _alias_heads
730 : (xgt & GT_MLIST ? _mlist_heads
731 : (xgt & GT_SHORTCUT ? _shortcut_heads
732 : (xgt & GT_CHARSETALIAS ? _charsetalias_heads
733 : (xgt & GT_FILETYPE ? _filetype_heads
734 : NULL))))));
736 for (h = 0, i = 1; h < HSHSIZE; ++h)
737 for (gp = gpa[h]; gp != NULL; gp = gp->g_next)
738 if ((gp->g_type & xgt) == xgt)
739 ++i;
740 ida = salloc(i * sizeof *ida);
742 for (i = h = 0; h < HSHSIZE; ++h)
743 for (gp = gpa[h]; gp != NULL; gp = gp->g_next)
744 if ((gp->g_type & xgt) == xgt)
745 ida[i++] = gp->g_id;
746 ida[i] = NULL;
748 if (i > 1)
749 qsort(ida, i, sizeof *ida, &__group_print_qsorter);
751 if ((fp = Ftmp(NULL, "prgroup", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL)
752 fp = n_stdout;
753 lines = 0;
755 for (i = 0; ida[i] != NULL; ++i)
756 lines += _group_print(_group_find(gt, ida[i]), fp);
757 #ifdef HAVE_REGEX
758 if (gt & GT_MLIST) {
759 if (gt & GT_SUBSCRIBE)
760 i = (ui32_t)_mlsub_size, h = (ui32_t)_mlsub_hits;
761 else
762 i = (ui32_t)_mlist_size, h = (ui32_t)_mlist_hits;
763 if (i > 0 && (n_poption & n_PO_D_V)){
764 fprintf(fp, _("# %s list regex(7) total: %u entries, %u hits\n"),
765 (gt & GT_SUBSCRIBE ? _("Subscribed") : _("Non-subscribed")),
766 i, h);
767 ++lines;
770 #endif
772 if (fp != n_stdout) {
773 page_or_print(fp, lines);
774 Fclose(fp);
776 NYD_LEAVE;
779 static int
780 __group_print_qsorter(void const *a, void const *b)
782 int rv;
783 NYD_ENTER;
785 rv = strcmp(*(char**)n_UNCONST(a), *(char**)n_UNCONST(b));
786 NYD_LEAVE;
787 return rv;
790 static size_t
791 _group_print(struct group const *gp, FILE *fo)
793 char const *cp;
794 size_t rv;
795 NYD_ENTER;
797 rv = 1;
799 if(gp->g_type & GT_COMMANDALIAS){
800 struct a_nag_cmd_alias *ncap;
802 GP_TO_SUBCLASS(ncap, gp);
803 fprintf(fo, "commandalias %s %s\n",
804 n_shexp_quote_cp(gp->g_id, TRU1),
805 n_shexp_quote_cp(ncap->ca_expand.s, TRU1));
806 } else if (gp->g_type & GT_ALIAS) {
807 struct grp_names_head *gnhp;
808 struct grp_names *gnp;
810 fprintf(fo, "alias %s ", gp->g_id);
812 GP_TO_SUBCLASS(gnhp, gp);
813 if ((gnp = gnhp->gnh_head) != NULL) { /* xxx always 1+ entries */
814 do {
815 struct grp_names *x = gnp;
816 gnp = gnp->gn_next;
817 fprintf(fo, " \"%s\"", string_quote(x->gn_id)); /* TODO shexp */
818 } while (gnp != NULL);
820 putc('\n', fo);
821 } else if (gp->g_type & GT_MLIST) {
822 #ifdef HAVE_REGEX
823 if ((gp->g_type & GT_REGEX) && (n_poption & n_PO_D_V)){
824 size_t i;
825 struct grp_regex *grp,
826 *lp = (gp->g_type & GT_SUBSCRIBE ? _mlsub_regex : _mlist_regex);
828 GP_TO_SUBCLASS(grp, gp);
829 for (i = 1; lp != grp; lp = lp->gr_next)
830 ++i;
831 fprintf(fo, "# regex(7): hits %" PRIuZ ", sort %" PRIuZ ".\n ",
832 grp->gr_hits, i);
833 ++rv;
835 #endif
837 fprintf(fo, "wysh %s %s\n",
838 (gp->g_type & GT_SUBSCRIBE ? "mlsubscribe" : "mlist"),
839 n_shexp_quote_cp(gp->g_id, TRU1));
840 } else if (gp->g_type & GT_SHORTCUT) {
841 GP_TO_SUBCLASS(cp, gp);
842 fprintf(fo, "wysh shortcut %s %s\n",
843 gp->g_id, n_shexp_quote_cp(cp, TRU1));
844 } else if (gp->g_type & GT_CHARSETALIAS) {
845 GP_TO_SUBCLASS(cp, gp);
846 fprintf(fo, "charsetalias %s %s\n",
847 n_shexp_quote_cp(gp->g_id, TRU1), n_shexp_quote_cp(cp, TRU1));
848 } else if (gp->g_type & GT_FILETYPE) {
849 struct a_nag_file_type *nftp;
851 GP_TO_SUBCLASS(nftp, gp);
852 fprintf(fo, "filetype %s %s %s\n",
853 n_shexp_quote_cp(gp->g_id, TRU1),
854 n_shexp_quote_cp(nftp->nft_load.s, TRU1),
855 n_shexp_quote_cp(nftp->nft_save.s, TRU1));
858 NYD_LEAVE;
859 return rv;
862 static int
863 _mlmux(enum group_type gt, char **argv)
865 char const *ecp;
866 struct group *gp;
867 int rv = 0;
868 NYD_ENTER;
870 rv = 0;
871 n_UNINIT(ecp, NULL);
873 if (*argv == NULL)
874 _group_print_all(gt);
875 else do {
876 if ((gp = _group_find(gt, *argv)) != NULL) {
877 if (gt & GT_SUBSCRIBE) {
878 if (!(gp->g_type & GT_SUBSCRIBE)) {
879 _MLMUX_LINKOUT(gp);
880 gp->g_type |= GT_SUBSCRIBE;
881 _MLMUX_LINKIN(gp);
882 } else {
883 ecp = N_("Mailing-list already `mlsubscribe'd: %s\n");
884 goto jerr;
886 } else {
887 ecp = N_("Mailing-list already `mlist'ed: %s\n");
888 goto jerr;
890 } else if(_group_fetch(gt, *argv, 0) == NULL) {
891 ecp = N_("Failed to create storage for mailing-list: %s\n");
892 jerr:
893 n_err(V_(ecp), n_shexp_quote_cp(*argv, FAL0));
894 rv = 1;
896 } while (*++argv != NULL);
898 NYD_LEAVE;
899 return rv;
902 static int
903 _unmlmux(enum group_type gt, char **argv)
905 struct group *gp;
906 int rv = 0;
907 NYD_ENTER;
909 for (; *argv != NULL; ++argv) {
910 if (gt & GT_SUBSCRIBE) {
911 struct group_lookup gl;
912 bool_t isaster;
914 if (!(isaster = (**argv == '*')))
915 gp = _group_find(gt, *argv);
916 else if ((gp = _group_go_first(gt, &gl)) == NULL)
917 continue;
918 else if (gp != NULL && !(gp->g_type & GT_SUBSCRIBE))
919 goto jaster_entry;
921 if (gp != NULL) {
922 jaster_redo:
923 if (gp->g_type & GT_SUBSCRIBE) {
924 _MLMUX_LINKOUT(gp);
925 gp->g_type &= ~GT_SUBSCRIBE;
926 _MLMUX_LINKIN(gp);
927 if (isaster) {
928 jaster_entry:
929 while ((gp = _group_go_next(&gl)) != NULL &&
930 !(gp->g_type & GT_SUBSCRIBE))
932 if (gp != NULL)
933 goto jaster_redo;
935 } else {
936 n_err(_("Mailing-list not `mlsubscribe'd: %s\n"),
937 n_shexp_quote_cp(*argv, FAL0));
938 rv = 1;
940 continue;
942 } else if (_group_del(gt, *argv))
943 continue;
944 n_err(_("No such mailing-list: %s\n"), n_shexp_quote_cp(*argv, FAL0));
945 rv = 1;
947 NYD_LEAVE;
948 return rv;
951 #ifdef HAVE_REGEX
952 static void
953 _mlmux_linkin(struct group *gp)
955 struct grp_regex **lpp, *grp, *lhp;
956 NYD_ENTER;
958 if (gp->g_type & GT_SUBSCRIBE) {
959 lpp = &_mlsub_regex;
960 ++_mlsub_size;
961 } else {
962 lpp = &_mlist_regex;
963 ++_mlist_size;
966 GP_TO_SUBCLASS(grp, gp);
967 if ((lhp = *lpp) != NULL) {
968 (grp->gr_last = lhp->gr_last)->gr_next = grp;
969 (grp->gr_next = lhp)->gr_last = grp;
970 } else
971 *lpp = grp->gr_last = grp->gr_next = grp;
972 grp->gr_hits = 0;
973 NYD_LEAVE;
976 static void
977 _mlmux_linkout(struct group *gp)
979 struct grp_regex *grp, **lpp;
980 NYD_ENTER;
982 GP_TO_SUBCLASS(grp, gp);
984 if (gp->g_type & GT_SUBSCRIBE) {
985 lpp = &_mlsub_regex;
986 --_mlsub_size;
987 _mlsub_hits -= grp->gr_hits;
988 } else {
989 lpp = &_mlist_regex;
990 --_mlist_size;
991 _mlist_hits -= grp->gr_hits;
994 if (grp->gr_next == grp)
995 *lpp = NULL;
996 else {
997 (grp->gr_last->gr_next = grp->gr_next)->gr_last = grp->gr_last;
998 if (*lpp == grp)
999 *lpp = grp->gr_next;
1001 NYD_LEAVE;
1003 #endif /* HAVE_REGEX */
1005 FL struct name *
1006 nalloc(char const *str, enum gfield ntype)
1008 struct n_addrguts ag;
1009 struct str in, out;
1010 struct name *np;
1011 NYD_ENTER;
1012 assert(!(ntype & GFULLEXTRA) || (ntype & GFULL) != 0);
1014 str = n_addrspec_with_guts(&ag, str,
1015 ((ntype & (GFULL | GSKIN | GREF)) != 0), FAL0);
1016 if(str == NULL){
1018 np = NULL; TODO We cannot return NULL,
1019 goto jleave; TODO thus handle failures in here!
1021 str = ag.ag_input;
1024 if (!(ag.ag_n_flags & NAME_NAME_SALLOC)) {
1025 ag.ag_n_flags |= NAME_NAME_SALLOC;
1026 np = salloc(sizeof(*np) + ag.ag_slen +1);
1027 memcpy(np + 1, ag.ag_skinned, ag.ag_slen +1);
1028 ag.ag_skinned = (char*)(np + 1);
1029 } else
1030 np = salloc(sizeof *np);
1032 np->n_flink = NULL;
1033 np->n_blink = NULL;
1034 np->n_type = ntype;
1035 np->n_flags = 0;
1037 np->n_fullname = np->n_name = ag.ag_skinned;
1038 np->n_fullextra = NULL;
1039 np->n_flags = ag.ag_n_flags;
1041 if (ntype & GFULL) {
1042 if (ag.ag_ilen == ag.ag_slen
1043 #ifdef HAVE_IDNA
1044 && !(ag.ag_n_flags & NAME_IDNA)
1045 #endif
1047 goto jleave;
1048 if (ag.ag_n_flags & NAME_ADDRSPEC_ISFILEORPIPE)
1049 goto jleave;
1051 /* n_fullextra is only the complete name part without address.
1052 * Beware of "-r '<abc@def>'", don't treat that as FULLEXTRA */
1053 if ((ntype & GFULLEXTRA) && ag.ag_ilen > ag.ag_slen + 2) {
1054 size_t s = ag.ag_iaddr_start, e = ag.ag_iaddr_aend, i;
1055 char const *cp;
1057 if (s == 0 || str[--s] != '<' || str[e++] != '>')
1058 goto jskipfullextra;
1059 i = ag.ag_ilen - e;
1060 in.s = n_lofi_alloc(s + 1 + i +1);
1061 while(s > 0 && blankchar(str[s - 1]))
1062 --s;
1063 memcpy(in.s, str, s);
1064 if (i > 0) {
1065 in.s[s++] = ' ';
1066 while (blankchar(str[e])) {
1067 ++e;
1068 if (--i == 0)
1069 break;
1071 if (i > 0)
1072 memcpy(&in.s[s], &str[e], i);
1074 s += i;
1075 in.s[in.l = s] = '\0';
1076 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
1078 for (cp = out.s, i = out.l; i > 0 && spacechar(*cp); --i, ++cp)
1080 while (i > 0 && spacechar(cp[i - 1]))
1081 --i;
1082 np->n_fullextra = savestrbuf(cp, i);
1084 n_lofi_free(in.s);
1085 free(out.s);
1087 jskipfullextra:
1089 /* n_fullname depends on IDNA conversion */
1090 #ifdef HAVE_IDNA
1091 if (!(ag.ag_n_flags & NAME_IDNA)) {
1092 #endif
1093 in.s = n_UNCONST(str);
1094 in.l = ag.ag_ilen;
1095 #ifdef HAVE_IDNA
1096 } else {
1097 /* The domain name was IDNA and has been converted. We also have to
1098 * ensure that the domain name in .n_fullname is replaced with the
1099 * converted version, since MIME doesn't perform encoding of addrs */
1100 /* TODO This definetely doesn't belong here! */
1101 size_t l = ag.ag_iaddr_start,
1102 lsuff = ag.ag_ilen - ag.ag_iaddr_aend;
1103 in.s = ac_alloc(l + ag.ag_slen + lsuff +1);
1104 memcpy(in.s, str, l);
1105 memcpy(in.s + l, ag.ag_skinned, ag.ag_slen);
1106 l += ag.ag_slen;
1107 memcpy(in.s + l, str + ag.ag_iaddr_aend, lsuff);
1108 l += lsuff;
1109 in.s[l] = '\0';
1110 in.l = l;
1112 #endif
1113 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
1114 np->n_fullname = savestr(out.s);
1115 free(out.s);
1116 #ifdef HAVE_IDNA
1117 if (ag.ag_n_flags & NAME_IDNA)
1118 ac_free(in.s);
1119 #endif
1120 np->n_flags |= NAME_FULLNAME_SALLOC;
1122 jleave:
1123 NYD_LEAVE;
1124 return np;
1127 FL struct name *
1128 ndup(struct name *np, enum gfield ntype)
1130 struct name *nnp;
1131 NYD_ENTER;
1133 if ((ntype & (GFULL | GSKIN)) && !(np->n_flags & NAME_SKINNED)) {
1134 nnp = nalloc(np->n_name, ntype);
1135 goto jleave;
1138 nnp = salloc(sizeof *np);
1139 nnp->n_flink = nnp->n_blink = NULL;
1140 nnp->n_type = ntype;
1141 nnp->n_flags = (np->n_flags & ~(NAME_NAME_SALLOC | NAME_FULLNAME_SALLOC)) |
1142 NAME_NAME_SALLOC;
1143 nnp->n_name = savestr(np->n_name);
1144 if (np->n_name == np->n_fullname || !(ntype & (GFULL | GSKIN))) {
1145 nnp->n_fullname = nnp->n_name;
1146 nnp->n_fullextra = NULL;
1147 } else {
1148 nnp->n_flags |= NAME_FULLNAME_SALLOC;
1149 nnp->n_fullname = savestr(np->n_fullname);
1150 nnp->n_fullextra = (np->n_fullextra == NULL) ? NULL
1151 : savestr(np->n_fullextra);
1153 jleave:
1154 NYD_LEAVE;
1155 return nnp;
1158 FL struct name *
1159 cat(struct name *n1, struct name *n2)
1161 struct name *tail;
1162 NYD_ENTER;
1164 tail = n2;
1165 if (n1 == NULL)
1166 goto jleave;
1167 tail = n1;
1168 if (n2 == NULL)
1169 goto jleave;
1171 while (tail->n_flink != NULL)
1172 tail = tail->n_flink;
1173 tail->n_flink = n2;
1174 n2->n_blink = tail;
1175 tail = n1;
1176 jleave:
1177 NYD_LEAVE;
1178 return tail;
1181 FL struct name *
1182 namelist_dup(struct name const *np, enum gfield ntype){
1183 struct name *nlist, *xnp;
1184 NYD2_ENTER;
1186 for(nlist = xnp = NULL; np != NULL; np = np->n_flink){
1187 struct name *x;
1189 x = ndup(n_UNCONST(np), (np->n_type & ~GMASK) | ntype);
1190 if((x->n_blink = xnp) == NULL)
1191 nlist = x;
1192 else
1193 xnp->n_flink = x;
1194 xnp = x;
1196 NYD2_LEAVE;
1197 return nlist;
1200 FL ui32_t
1201 count(struct name const *np)
1203 ui32_t c;
1204 NYD_ENTER;
1206 for (c = 0; np != NULL; np = np->n_flink)
1207 if (!(np->n_type & GDEL))
1208 ++c;
1209 NYD_LEAVE;
1210 return c;
1213 FL ui32_t
1214 count_nonlocal(struct name const *np)
1216 ui32_t c;
1217 NYD_ENTER;
1219 for (c = 0; np != NULL; np = np->n_flink)
1220 if (!(np->n_type & GDEL) && !(np->n_flags & NAME_ADDRSPEC_ISFILEORPIPE))
1221 ++c;
1222 NYD_LEAVE;
1223 return c;
1226 FL struct name *
1227 extract(char const *line, enum gfield ntype)
1229 struct name *rv;
1230 NYD_ENTER;
1232 rv = _extract1(line, ntype, " \t,", 0);
1233 NYD_LEAVE;
1234 return rv;
1237 FL struct name *
1238 lextract(char const *line, enum gfield ntype)
1240 struct name *rv;
1241 NYD_ENTER;
1243 rv = ((line != NULL && strpbrk(line, ",\"\\(<|"))
1244 ? _extract1(line, ntype, ",", 1) : extract(line, ntype));
1245 NYD_LEAVE;
1246 return rv;
1249 FL char *
1250 detract(struct name *np, enum gfield ntype)
1252 char *topp, *cp;
1253 struct name *p;
1254 int flags, s;
1255 NYD_ENTER;
1257 topp = NULL;
1258 if (np == NULL)
1259 goto jleave;
1261 flags = ntype & (GCOMMA | GNAMEONLY);
1262 ntype &= ~(GCOMMA | GNAMEONLY);
1263 s = 0;
1265 for (p = np; p != NULL; p = p->n_flink) {
1266 if (ntype && (p->n_type & GMASK) != ntype)
1267 continue;
1268 s += strlen(flags & GNAMEONLY ? p->n_name : p->n_fullname) +1;
1269 if (flags & GCOMMA)
1270 ++s;
1272 if (s == 0)
1273 goto jleave;
1275 s += 2;
1276 topp = salloc(s);
1277 cp = topp;
1278 for (p = np; p != NULL; p = p->n_flink) {
1279 if (ntype && (p->n_type & GMASK) != ntype)
1280 continue;
1281 cp = sstpcpy(cp, (flags & GNAMEONLY ? p->n_name : p->n_fullname));
1282 if ((flags & GCOMMA) && p->n_flink != NULL)
1283 *cp++ = ',';
1284 *cp++ = ' ';
1286 *--cp = 0;
1287 if ((flags & GCOMMA) && *--cp == ',')
1288 *cp = 0;
1289 jleave:
1290 NYD_LEAVE;
1291 return topp;
1294 FL struct name *
1295 grab_names(enum n_go_input_flags gif, char const *field, struct name *np,
1296 int comma, enum gfield gflags)
1298 struct name *nq;
1299 NYD_ENTER;
1301 jloop:
1302 np = lextract(n_go_input_cp(gif, field, detract(np, comma)), gflags);
1303 for (nq = np; nq != NULL; nq = nq->n_flink)
1304 if (is_addr_invalid(nq, EACM_NONE))
1305 goto jloop;
1306 NYD_LEAVE;
1307 return np;
1310 FL bool_t
1311 name_is_same_domain(struct name const *n1, struct name const *n2)
1313 char const *d1, *d2;
1314 bool_t rv;
1315 NYD_ENTER;
1317 d1 = strrchr(n1->n_name, '@');
1318 d2 = strrchr(n2->n_name, '@');
1320 rv = (d1 != NULL && d2 != NULL) ? !asccasecmp(++d1, ++d2) : FAL0;
1322 NYD_LEAVE;
1323 return rv;
1326 FL struct name *
1327 checkaddrs(struct name *np, enum expand_addr_check_mode eacm,
1328 si8_t *set_on_error)
1330 struct name *n;
1331 NYD_ENTER;
1333 for (n = np; n != NULL; n = n->n_flink) {
1334 si8_t rv;
1336 if ((rv = is_addr_invalid(n, eacm)) != 0) {
1337 if (set_on_error != NULL)
1338 *set_on_error |= rv; /* don't loose -1! */
1339 else if (eacm & EAF_MAYKEEP) /* TODO HACK! See definition! */
1340 continue;
1341 if (n->n_blink)
1342 n->n_blink->n_flink = n->n_flink;
1343 if (n->n_flink)
1344 n->n_flink->n_blink = n->n_blink;
1345 if (n == np)
1346 np = n->n_flink;
1349 NYD_LEAVE;
1350 return np;
1353 FL struct name *
1354 namelist_vaporise_head(struct header *hp, enum expand_addr_check_mode eacm,
1355 bool_t metoo, si8_t *set_on_error)
1357 /* TODO namelist_vaporise_head() is incredibly expensive and redundant */
1358 struct name *tolist, *np, **npp;
1359 NYD_ENTER;
1361 tolist = cat(hp->h_to, cat(hp->h_cc, hp->h_bcc));
1362 hp->h_to = hp->h_cc = hp->h_bcc = NULL;
1364 tolist = usermap(tolist, metoo);
1365 tolist = n_alternates_remove(tolist, TRU1);
1366 tolist = elide(checkaddrs(tolist, eacm, set_on_error));
1368 for (np = tolist; np != NULL; np = np->n_flink) {
1369 switch (np->n_type & (GDEL | GMASK)) {
1370 case GTO: npp = &hp->h_to; break;
1371 case GCC: npp = &hp->h_cc; break;
1372 case GBCC: npp = &hp->h_bcc; break;
1373 default: continue;
1375 *npp = cat(*npp, ndup(np, np->n_type | GFULL));
1377 NYD_LEAVE;
1378 return tolist;
1381 FL struct name *
1382 usermap(struct name *names, bool_t force_metoo){
1383 struct group *gp;
1384 struct name *nlist, *nlist_tail, *np, *cp;
1385 int metoo;
1386 NYD_ENTER;
1388 metoo = (force_metoo || ok_blook(metoo));
1389 nlist = nlist_tail = NULL;
1390 np = names;
1392 for(; np != NULL; np = cp){
1393 assert(!(np->n_type & GDEL)); /* TODO legacy */
1394 cp = np->n_flink;
1396 if(is_fileorpipe_addr(np) ||
1397 (gp = _group_find(GT_ALIAS, np->n_name)) == NULL){
1398 if((np->n_blink = nlist_tail) != NULL)
1399 nlist_tail->n_flink = np;
1400 else
1401 nlist = np;
1402 nlist_tail = np;
1403 np->n_flink = NULL;
1404 }else{
1405 nlist = a_nag_gexpand(0, nlist, gp, metoo, np->n_type);
1406 if((nlist_tail = nlist) != NULL)
1407 while(nlist_tail->n_flink != NULL)
1408 nlist_tail = nlist_tail->n_flink;
1411 NYD_LEAVE;
1412 return nlist;
1415 FL struct name *
1416 elide(struct name *names)
1418 size_t i, j, k;
1419 struct name *nlist, *np, **nparr;
1420 NYD_ENTER;
1422 nlist = NULL;
1424 if(names == NULL)
1425 goto jleave;
1427 /* Throw away all deleted nodes */
1428 for(np = NULL, i = 0; names != NULL; names = names->n_flink)
1429 if(!(names->n_type & GDEL)){
1430 names->n_blink = np;
1431 if(np != NULL)
1432 np->n_flink = names;
1433 else
1434 nlist = names;
1435 np = names;
1436 ++i;
1438 if(nlist == NULL || i == 1)
1439 goto jleave;
1440 np->n_flink = NULL;
1442 /* Create a temporay array and sort that */
1443 nparr = n_lofi_alloc(sizeof(*nparr) * i);
1445 for(i = 0, np = nlist; np != NULL; np = np->n_flink)
1446 nparr[i++] = np;
1448 qsort(nparr, i, sizeof *nparr, &a_nag_elide_qsort);
1450 /* Remove duplicates XXX speedup, or list_uniq()! */
1451 for(j = 0, --i; j < i;){
1452 if(asccasecmp(nparr[j]->n_name, nparr[k = j + 1]->n_name))
1453 ++j;
1454 else{
1455 for(; k < i; ++k)
1456 nparr[k] = nparr[k + 1];
1457 --i;
1461 /* Throw away all list members which are not part of the array.
1462 * Note this keeps the original, possibly carefully crafted, order of the
1463 * addressees, thus */
1464 for(np = nlist; np != NULL; np = np->n_flink){
1465 for(j = 0; j <= i; ++j)
1466 if(np == nparr[j]){
1467 nparr[j] = NULL;
1468 goto jiter;
1470 /* Drop it */
1471 if(np == nlist){
1472 nlist = np->n_flink;
1473 np->n_blink = NULL;
1474 }else
1475 np->n_blink->n_flink = np->n_flink;
1476 if(np->n_flink != NULL)
1477 np->n_flink->n_blink = np->n_blink;
1478 jiter:;
1481 n_lofi_free(nparr);
1482 jleave:
1483 NYD_LEAVE;
1484 return nlist;
1487 FL int
1488 c_alternates(void *v){ /* TODO use a hashmap!! */
1489 char **argv;
1490 int rv;
1491 NYD_ENTER;
1493 rv = 0;
1495 if(*(argv = v) == NULL){
1496 char const *ccp;
1498 if((ccp = ok_vlook(alternates)) != NULL)
1499 fprintf(n_stdout, "alternates %s\n", ccp);
1500 else
1501 fputs(_("# no alternates registered\n"), n_stdout);
1502 }else{
1503 char *cp;
1504 size_t l, vl;
1505 struct n_strlist *slp, **slpa;
1507 while((slp = a_nag_altnames) != NULL){
1508 a_nag_altnames = slp->sl_next;
1509 n_free(slp);
1511 vl = 0;
1513 /* Extension: only clearance? */
1514 if(argv[1] == NULL && argv[0][0] == '-' && argv[0][1] == '\0')
1515 n_UNINIT(cp, NULL);
1516 else for(slpa = &a_nag_altnames; *argv != NULL; ++argv){
1517 if(**argv != '\0'){
1518 struct name *np;
1520 if((np = lextract(*argv, GSKIN)) == NULL || np->n_flink != NULL ||
1521 (np = checkaddrs(np, EACM_STRICT, NULL)) == NULL){
1522 n_err(_("Invalid `alternates' argument: %s\n"),
1523 n_shexp_quote_cp(*argv, FAL0));
1524 rv = 1;
1525 continue;
1528 l = strlen(np->n_name);
1529 if(UIZ_MAX - l <= vl){
1530 n_err(_("Failed to create storage for alternate: %s\n"),
1531 n_shexp_quote_cp(*argv, FAL0));
1532 rv = 1;
1533 continue;
1536 slp = n_STRLIST_ALLOC(l);
1537 slp->sl_next = NULL;
1538 slp->sl_len = l;
1539 memcpy(slp->sl_dat, np->n_name, ++l);
1540 *slpa = slp;
1541 slpa = &slp->sl_next;
1542 vl += l;
1546 /* And put it into *alternates* */
1547 if(vl > 0){
1548 cp = n_autorec_alloc(vl);
1549 for(vl = 0, slp = a_nag_altnames; slp != NULL; slp = slp->sl_next){
1550 memcpy(&cp[vl], slp->sl_dat, slp->sl_len);
1551 cp[vl += slp->sl_len] = ' ';
1552 ++vl;
1554 cp[vl - 1] = '\0';
1557 n_PS_ROOT_BLOCK(vl > 0 ? ok_vset(alternates, cp) : ok_vclear(alternates));
1559 NYD_LEAVE;
1560 return rv;
1563 FL struct name *
1564 n_alternates_remove(struct name *np, bool_t keep_single){
1565 struct name *xp, *newnp;
1566 NYD_ENTER;
1568 /* Delete the temporary bit from all */
1569 for(xp = np; xp != NULL; xp = xp->n_flink)
1570 xp->n_flags &= ~(ui32_t)SI32_MIN;
1572 /* Mark all possible alternate names (xxx sic) */
1574 if(a_nag_altnames != NULL){
1575 struct n_strlist *slp;
1577 for(slp = a_nag_altnames; slp != NULL; slp = slp->sl_next)
1578 np = a_nag_namelist_mark_name(np, slp->sl_dat);
1581 np = a_nag_namelist_mark_name(np, ok_vlook(LOGNAME));
1583 for(xp = lextract(ok_vlook(from), GEXTRA | GSKIN); xp != NULL;
1584 xp = xp->n_flink)
1585 np = a_nag_namelist_mark_name(np, xp->n_name);
1587 for(xp = extract(ok_vlook(sender), GEXTRA | GSKIN); xp != NULL;
1588 xp = xp->n_flink)
1589 np = a_nag_namelist_mark_name(np, xp->n_name);
1591 for(xp = lextract(ok_vlook(replyto), GEXTRA | GSKIN); xp != NULL;
1592 xp = xp->n_flink)
1593 np = a_nag_namelist_mark_name(np, xp->n_name);
1595 /* GDEL all (but a single) marked node(s) */
1596 for(xp = np; xp != NULL; xp = xp->n_flink)
1597 if(xp->n_flags & (ui32_t)SI32_MIN){
1598 if(!keep_single)
1599 xp->n_type |= GDEL;
1600 keep_single = FAL0;
1603 /* Clean the list by throwing away all deleted nodes */
1604 for(xp = newnp = NULL; np != NULL; np = np->n_flink)
1605 if(!(np->n_type & GDEL)){
1606 np->n_blink = xp;
1607 if(xp != NULL)
1608 xp->n_flink = np;
1609 else
1610 newnp = np;
1611 xp = np;
1613 np = newnp;
1615 /* Delete the temporary bit from all remaining (again) */
1616 for(xp = np; xp != NULL; xp = xp->n_flink)
1617 xp->n_flags &= ~(ui32_t)SI32_MIN;
1619 NYD_LEAVE;
1620 return np;
1623 FL bool_t
1624 n_is_myname(char const *name){
1625 struct name *xp;
1626 NYD_ENTER;
1628 if(a_nag_is_same_name(ok_vlook(LOGNAME), name))
1629 goto jleave;
1631 if(a_nag_altnames != NULL){
1632 struct n_strlist *slp;
1634 for(slp = a_nag_altnames; slp != NULL; slp = slp->sl_next)
1635 if(a_nag_is_same_name(slp->sl_dat, name))
1636 goto jleave;
1639 for(xp = lextract(ok_vlook(from), 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 = lextract(ok_vlook(replyto), GEXTRA | GSKIN); xp != NULL;
1645 xp = xp->n_flink)
1646 if(a_nag_is_same_name(xp->n_name, name))
1647 goto jleave;
1649 for(xp = extract(ok_vlook(sender), GEXTRA | GSKIN); xp != NULL;
1650 xp = xp->n_flink)
1651 if(a_nag_is_same_name(xp->n_name, name))
1652 goto jleave;
1654 name = NULL;
1655 jleave:
1656 NYD_LEAVE;
1657 return (name != NULL);
1660 FL int
1661 c_addrcodec(void *vp){
1662 struct n_addrguts ag;
1663 struct n_string s_b, *sp;
1664 size_t alen;
1665 int mode;
1666 char const **argv, *varname, *act, *cp;
1667 NYD_ENTER;
1669 sp = n_string_creat_auto(&s_b);
1670 argv = vp;
1671 varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
1673 act = *argv;
1674 for(cp = act; *cp != '\0' && !blankspacechar(*cp); ++cp)
1676 mode = 0;
1677 if(*act == '+')
1678 mode = 1, ++act;
1679 if(*act == '+')
1680 mode = 2, ++act;
1681 if(*act == '+')
1682 mode = 3, ++act;
1683 if(act >= cp)
1684 goto jesynopsis;
1685 alen = PTR2SIZE(cp - act);
1686 if(*cp != '\0')
1687 ++cp;
1689 /* C99 */{
1690 size_t i;
1692 i = strlen(cp);
1693 if(i <= UIZ_MAX / 4)
1694 i <<= 1;
1695 sp = n_string_reserve(sp, i);
1698 n_pstate_err_no = n_ERR_NONE;
1700 if(is_ascncaseprefix(act, "encode", alen)){
1701 /* This function cannot be a simple nalloc() wrapper even later on, since
1702 * we may need to turn any ", () or \ into quoted-pairs */
1703 char c;
1705 while((c = *cp++) != '\0'){
1706 if(((c == '(' || c == ')') && mode < 1) || (c == '"' && mode < 2) ||
1707 (c == '\\' && mode < 3))
1708 sp = n_string_push_c(sp, '\\');
1709 sp = n_string_push_c(sp, c);
1712 if(n_addrspec_with_guts(&ag, n_string_cp(sp), TRU1, TRU1) == NULL ||
1713 (ag.ag_n_flags & (NAME_ADDRSPEC_ISADDR | NAME_ADDRSPEC_INVALID)
1714 ) != NAME_ADDRSPEC_ISADDR){
1715 cp = sp->s_dat;
1716 n_pstate_err_no = n_ERR_INVAL;
1717 vp = NULL;
1718 }else{
1719 struct name *np;
1721 np = nalloc(ag.ag_input, GTO | GFULL | GSKIN);
1722 cp = np->n_fullname;
1724 }else if(mode == 0){
1725 if(is_ascncaseprefix(act, "decode", alen)){
1726 char c;
1728 while((c = *cp++) != '\0'){
1729 switch(c){
1730 case '(':
1731 sp = n_string_push_c(sp, '(');
1732 act = skip_comment(cp);
1733 if(--act > cp)
1734 sp = n_string_push_buf(sp, cp, PTR2SIZE(act - cp));
1735 sp = n_string_push_c(sp, ')');
1736 cp = ++act;
1737 break;
1738 case '"':
1739 while(*cp != '\0'){
1740 if((c = *cp++) == '"')
1741 break;
1742 if(c == '\\' && (c = *cp) != '\0')
1743 ++cp;
1744 sp = n_string_push_c(sp, c);
1746 break;
1747 default:
1748 if(c == '\\' && (c = *cp++) == '\0')
1749 break;
1750 sp = n_string_push_c(sp, c);
1751 break;
1754 cp = n_string_cp(sp);
1755 }else if(is_ascncaseprefix(act, "skin", alen)){
1756 /* Let's just use the is-single-address hack for this one, too.. */
1757 if(n_addrspec_with_guts(&ag, cp, TRU1, TRU1) == NULL ||
1758 (ag.ag_n_flags & (NAME_ADDRSPEC_ISADDR | NAME_ADDRSPEC_INVALID)
1759 ) != NAME_ADDRSPEC_ISADDR){
1760 n_pstate_err_no = n_ERR_INVAL;
1761 vp = NULL;
1762 }else{
1763 struct name *np;
1765 np = nalloc(ag.ag_input, GTO | GFULL | GSKIN);
1766 cp = np->n_name;
1768 }else
1769 goto jesynopsis;
1770 }else
1771 goto jesynopsis;
1773 if(varname == NULL){
1774 if(fprintf(n_stdout, "%s\n", cp) < 0){
1775 n_pstate_err_no = n_err_no;
1776 vp = NULL;
1778 }else if(!n_var_vset(varname, (uintptr_t)cp)){
1779 n_pstate_err_no = n_ERR_NOTSUP;
1780 vp = NULL;
1783 jleave:
1784 NYD_LEAVE;
1785 return (vp != NULL ? 0 : 1);
1786 jesynopsis:
1787 n_err(_("Synopsis: addrcodec: <[+[+[+]]]e[ncode]|d[ecode]|s[kin]> "
1788 "<rest-of-line>\n"));
1789 n_pstate_err_no = n_ERR_INVAL;
1790 vp = NULL;
1791 goto jleave;
1794 FL int
1795 c_commandalias(void *vp){
1796 struct group *gp;
1797 char const **argv, *ccp;
1798 int rv;
1799 NYD_ENTER;
1801 rv = 0;
1802 argv = vp;
1804 if((ccp = *argv) == NULL){
1805 _group_print_all(GT_COMMANDALIAS);
1806 goto jleave;
1809 /* Verify the name is a valid one, and not a command modifier */
1810 if(*ccp == '\0' || *n_cmd_isolate(ccp) != '\0' ||
1811 !asccasecmp(ccp, "ignerr") || !asccasecmp(ccp, "wysh") ||
1812 !asccasecmp(ccp, "vput")){
1813 n_err(_("`commandalias': not a valid command name: %s\n"),
1814 n_shexp_quote_cp(ccp, FAL0));
1815 rv = 1;
1816 goto jleave;
1819 if(argv[1] == NULL){
1820 if((gp = _group_find(GT_COMMANDALIAS, ccp)) != NULL)
1821 _group_print(gp, n_stdout);
1822 else{
1823 n_err(_("No such commandalias: %s\n"), n_shexp_quote_cp(ccp, FAL0));
1824 rv = 1;
1826 }else{
1827 /* Because one hardly ever redefines, anything is stored in one chunk */
1828 char *cp;
1829 size_t i, len;
1831 /* Delete the old one, if any; don't get fooled to remove them all */
1832 if(ccp[0] != '*' || ccp[1] != '\0')
1833 _group_del(GT_COMMANDALIAS, ccp);
1835 for(i = len = 0, ++argv; argv[i] != NULL; ++i)
1836 len += strlen(argv[i]) + 1;
1837 if(len == 0)
1838 len = 1;
1840 if((gp = _group_fetch(GT_COMMANDALIAS, ccp, len)) == NULL){
1841 n_err(_("Failed to create storage for commandalias: %s\n"),
1842 n_shexp_quote_cp(ccp, FAL0));
1843 rv = 1;
1844 }else{
1845 struct a_nag_cmd_alias *ncap;
1847 GP_TO_SUBCLASS(ncap, gp);
1848 GP_TO_SUBCLASS(cp, gp);
1849 cp += sizeof *ncap;
1850 ncap->ca_expand.s = cp;
1851 ncap->ca_expand.l = len - 1;
1853 for(len = 0; (ccp = *argv++) != NULL;)
1854 if((i = strlen(ccp)) > 0){
1855 if(len++ != 0)
1856 *cp++ = ' ';
1857 memcpy(cp, ccp, i);
1858 cp += i;
1860 *cp = '\0';
1863 jleave:
1864 NYD_LEAVE;
1865 return rv;
1868 FL int
1869 c_uncommandalias(void *vp){
1870 char **argv;
1871 int rv;
1872 NYD_ENTER;
1874 rv = 0;
1875 argv = vp;
1877 do if(!_group_del(GT_COMMANDALIAS, *argv)){
1878 n_err(_("No such `commandalias': %s\n"), n_shexp_quote_cp(*argv, FAL0));
1879 rv = 1;
1880 }while(*++argv != NULL);
1881 NYD_LEAVE;
1882 return rv;
1885 FL char const *
1886 n_commandalias_exists(char const *name, struct str const **expansion_or_null){
1887 struct group *gp;
1888 NYD_ENTER;
1890 if((gp = _group_find(GT_COMMANDALIAS, name)) != NULL){
1891 name = gp->g_id;
1893 if(expansion_or_null != NULL){
1894 struct a_nag_cmd_alias *ncap;
1896 GP_TO_SUBCLASS(ncap, gp);
1897 *expansion_or_null = &ncap->ca_expand;
1899 }else
1900 name = NULL;
1901 NYD_LEAVE;
1902 return name;
1905 FL bool_t
1906 n_alias_is_valid_name(char const *name){
1907 char c;
1908 char const *cp;
1909 bool_t rv;
1910 NYD2_ENTER;
1912 for(rv = TRU1, cp = name++; (c = *cp++) != '\0';)
1913 /* User names, plus things explicitly mentioned in Postfix aliases(5).
1914 * As an extension, allow period: [[:alnum:]_#:@.-]+$? */
1915 if(!alnumchar(c) && c != '_' && c != '-' &&
1916 c != '#' && c != ':' && c != '@' &&
1917 c != '.'){
1918 if(c == '$' && cp != name && *cp == '\0')
1919 break;
1920 rv = FAL0;
1921 break;
1923 NYD2_LEAVE;
1924 return rv;
1927 FL int
1928 c_alias(void *v)
1930 char const *ecp;
1931 char **argv;
1932 struct group *gp;
1933 int rv;
1934 NYD_ENTER;
1936 rv = 0;
1937 argv = v;
1938 n_UNINIT(ecp, NULL);
1940 if (*argv == NULL)
1941 _group_print_all(GT_ALIAS);
1942 else if (!n_alias_is_valid_name(*argv)) {
1943 ecp = N_("Not a valid alias name: %s\n");
1944 goto jerr;
1945 } else if (argv[1] == NULL) {
1946 if ((gp = _group_find(GT_ALIAS, *argv)) != NULL)
1947 _group_print(gp, n_stdout);
1948 else {
1949 ecp = N_("No such alias: %s\n");
1950 goto jerr;
1952 } else if ((gp = _group_fetch(GT_ALIAS, *argv, 0)) == NULL) {
1953 ecp = N_("Failed to create alias storage for: %s\n");
1954 jerr:
1955 n_err(V_(ecp), n_shexp_quote_cp(*argv, FAL0));
1956 rv = 1;
1957 } else {
1958 struct grp_names *gnp_tail, *gnp;
1959 struct grp_names_head *gnhp;
1961 GP_TO_SUBCLASS(gnhp, gp);
1963 if((gnp_tail = gnhp->gnh_head) != NULL)
1964 while((gnp = gnp_tail->gn_next) != NULL)
1965 gnp_tail = gnp;
1967 for(++argv; *argv != NULL; ++argv){
1968 size_t i;
1970 i = strlen(*argv) +1;
1971 gnp = smalloc(n_VSTRUCT_SIZEOF(struct grp_names, gn_id) + i);
1972 if(gnp_tail != NULL)
1973 gnp_tail->gn_next = gnp;
1974 else
1975 gnhp->gnh_head = gnp;
1976 gnp_tail = gnp;
1977 gnp->gn_next = NULL;
1978 memcpy(gnp->gn_id, *argv, i);
1981 NYD_LEAVE;
1982 return rv;
1985 FL int
1986 c_unalias(void *v)
1988 char **argv = v;
1989 int rv = 0;
1990 NYD_ENTER;
1992 do if (!_group_del(GT_ALIAS, *argv)) {
1993 n_err(_("No such alias: %s\n"), *argv);
1994 rv = 1;
1995 } while (*++argv != NULL);
1996 NYD_LEAVE;
1997 return rv;
2000 FL int
2001 c_mlist(void *v)
2003 int rv;
2004 NYD_ENTER;
2006 rv = _mlmux(GT_MLIST, v);
2007 NYD_LEAVE;
2008 return rv;
2011 FL int
2012 c_unmlist(void *v)
2014 int rv;
2015 NYD_ENTER;
2017 rv = _unmlmux(GT_MLIST, v);
2018 NYD_LEAVE;
2019 return rv;
2022 FL int
2023 c_mlsubscribe(void *v)
2025 int rv;
2026 NYD_ENTER;
2028 rv = _mlmux(GT_MLIST | GT_SUBSCRIBE, v);
2029 NYD_LEAVE;
2030 return rv;
2033 FL int
2034 c_unmlsubscribe(void *v)
2036 int rv;
2037 NYD_ENTER;
2039 rv = _unmlmux(GT_MLIST | GT_SUBSCRIBE, v);
2040 NYD_LEAVE;
2041 return rv;
2044 FL enum mlist_state
2045 is_mlist(char const *name, bool_t subscribed_only)
2047 struct group *gp;
2048 #ifdef HAVE_REGEX
2049 struct grp_regex **lpp, *grp;
2050 bool_t re2;
2051 #endif
2052 enum mlist_state rv;
2053 NYD_ENTER;
2055 gp = _group_find(GT_MLIST, name);
2056 rv = (gp != NULL) ? MLIST_KNOWN : MLIST_OTHER;
2057 if (rv == MLIST_KNOWN) {
2058 if (gp->g_type & GT_SUBSCRIBE)
2059 rv = MLIST_SUBSCRIBED;
2060 else if (subscribed_only)
2061 rv = MLIST_OTHER;
2062 /* Of course, if that is a regular expression it doesn't mean a thing */
2063 #ifdef HAVE_REGEX
2064 if (gp->g_type & GT_REGEX)
2065 rv = MLIST_OTHER;
2066 else
2067 #endif
2068 goto jleave;
2071 /* Not in the hashmap (as something matchable), walk the lists */
2072 #ifdef HAVE_REGEX
2073 re2 = FAL0;
2074 lpp = &_mlsub_regex;
2075 jregex_redo:
2076 if ((grp = *lpp) != NULL) {
2077 do if (regexec(&grp->gr_regex, name, 0,NULL, 0) != REG_NOMATCH) {
2078 /* Relink as the head of this list if the hit count of this group is
2079 * >= 25% of the average hit count */
2080 size_t i;
2081 if (!re2)
2082 i = ++_mlsub_hits / _mlsub_size;
2083 else
2084 i = ++_mlist_hits / _mlist_size;
2085 i >>= 2;
2087 if (++grp->gr_hits >= i && *lpp != grp && grp->gr_next != grp) {
2088 grp->gr_last->gr_next = grp->gr_next;
2089 grp->gr_next->gr_last = grp->gr_last;
2090 (grp->gr_last = (*lpp)->gr_last)->gr_next = grp;
2091 (grp->gr_next = *lpp)->gr_last = grp;
2092 *lpp = grp;
2094 rv = !re2 ? MLIST_SUBSCRIBED : MLIST_KNOWN;
2095 goto jleave;
2096 } while ((grp = grp->gr_next) != *lpp);
2098 if (!re2 && !subscribed_only) {
2099 re2 = TRU1;
2100 lpp = &_mlist_regex;
2101 goto jregex_redo;
2103 assert(rv == MLIST_OTHER);
2104 #endif
2106 jleave:
2107 NYD_LEAVE;
2108 return rv;
2111 FL int
2112 c_shortcut(void *v)
2114 struct group *gp;
2115 char **argv;
2116 int rv;
2117 NYD_ENTER;
2119 rv = 0;
2120 argv = v;
2122 if(*argv == NULL)
2123 _group_print_all(GT_SHORTCUT);
2124 else if(argv[1] == NULL){
2125 if((gp = _group_find(GT_SHORTCUT, *argv)) != NULL)
2126 _group_print(gp, n_stdout);
2127 else{
2128 n_err(_("No such shortcut: %s\n"), n_shexp_quote_cp(*argv, FAL0));
2129 rv = 1;
2131 }else for (; *argv != NULL; argv += 2) {
2132 /* Because one hardly ever redefines, anything is stored in one chunk */
2133 size_t l;
2134 char *cp;
2136 if (argv[1] == NULL) {
2137 n_err(_("Synopsis: shortcut: <shortcut> <expansion>\n"));
2138 rv = 1;
2139 break;
2141 if (_group_find(GT_SHORTCUT, *argv) != NULL)
2142 _group_del(GT_SHORTCUT, *argv);
2144 l = strlen(argv[1]) +1;
2145 if ((gp = _group_fetch(GT_SHORTCUT, *argv, l)) == NULL) {
2146 n_err(_("Failed to create storage for shortcut: %s\n"),
2147 n_shexp_quote_cp(*argv, FAL0));
2148 rv = 1;
2149 } else {
2150 GP_TO_SUBCLASS(cp, gp);
2151 memcpy(cp, argv[1], l);
2154 NYD_LEAVE;
2155 return rv;
2158 FL int
2159 c_unshortcut(void *v)
2161 char **argv = v;
2162 int rv = 0;
2163 NYD_ENTER;
2165 do if (!_group_del(GT_SHORTCUT, *argv)) {
2166 n_err(_("No such shortcut: %s\n"), *argv);
2167 rv = 1;
2168 } while (*++argv != NULL);
2169 NYD_LEAVE;
2170 return rv;
2173 FL char const *
2174 shortcut_expand(char const *str){
2175 struct group *gp;
2176 NYD_ENTER;
2178 if((gp = _group_find(GT_SHORTCUT, str)) != NULL)
2179 GP_TO_SUBCLASS(str, gp);
2180 else
2181 str = NULL;
2182 NYD_LEAVE;
2183 return str;
2186 FL int
2187 c_charsetalias(void *vp){
2188 struct group *gp;
2189 char **argv;
2190 int rv;
2191 NYD_ENTER;
2193 rv = 0;
2194 argv = vp;
2196 if(*argv == NULL)
2197 _group_print_all(GT_CHARSETALIAS);
2198 else if(argv[1] == NULL){
2199 if((gp = _group_find(GT_CHARSETALIAS, *argv)) != NULL)
2200 _group_print(gp, n_stdout);
2201 else{
2202 n_err(_("No such charsetalias: %s\n"), n_shexp_quote_cp(*argv, FAL0));
2203 rv = 1;
2205 }else for(; *argv != NULL; argv += 2){
2206 /* Because one hardly ever redefines, anything is stored in one chunk */
2207 char const *ccp;
2208 char *cp, c;
2209 size_t l;
2211 if(argv[1] == NULL){
2212 n_err(_("Synopsis: charsetalias: <charset> <charset-alias>\n"));
2213 rv = 1;
2214 break;
2217 /* Delete the old one, if any; don't get fooled to remove them all */
2218 ccp = argv[0];
2219 if(ccp[0] != '*' || ccp[1] != '\0')
2220 _group_del(GT_CHARSETALIAS, ccp);
2222 l = strlen(argv[1]) +1;
2223 if ((gp = _group_fetch(GT_CHARSETALIAS, ccp, l)) == NULL) {
2224 n_err(_("Failed to create storage for charsetalias: %s\n"),
2225 n_shexp_quote_cp(ccp, FAL0));
2226 rv = 1;
2227 } else {
2228 GP_TO_SUBCLASS(cp, gp);
2229 for(ccp = argv[1]; (c = *ccp++) != '\0';)
2230 *cp++ = lowerconv(c);
2231 *cp = '\0';
2234 NYD_LEAVE;
2235 return rv;
2238 FL int
2239 c_uncharsetalias(void *vp){
2240 char **argv;
2241 int rv;
2242 NYD_ENTER;
2244 rv = 0;
2245 argv = vp;
2247 do if(!_group_del(GT_CHARSETALIAS, *argv)){
2248 n_err(_("No such `charsetalias': %s\n"), n_shexp_quote_cp(*argv, FAL0));
2249 rv = 1;
2250 }while(*++argv != NULL);
2251 NYD_LEAVE;
2252 return rv;
2255 FL char const *
2256 n_charsetalias_expand(char const *cp){
2257 struct group *gp;
2258 size_t i;
2259 char const *cp_orig;
2260 NYD_ENTER;
2262 cp_orig = cp;
2264 for(i = 0; (gp = _group_find(GT_CHARSETALIAS, cp)) != NULL;){
2265 GP_TO_SUBCLASS(cp, gp);
2266 if(++i == 8) /* XXX Magic (same as for `ghost' expansion) */
2267 break;
2270 if(cp != cp_orig)
2271 cp = savestr(cp);
2272 NYD_LEAVE;
2273 return cp;
2276 FL int
2277 c_filetype(void *vp){ /* TODO support automatic chains: .tar.gz -> .gz + .tar */
2278 struct group *gp;
2279 char **argv; /* TODO While there: let ! prefix mean: direct execlp(2) */
2280 int rv;
2281 NYD_ENTER;
2283 rv = 0;
2284 argv = vp;
2286 if(*argv == NULL)
2287 _group_print_all(GT_FILETYPE);
2288 else if(argv[1] == NULL){
2289 if((gp = _group_find(GT_FILETYPE, *argv)) != NULL)
2290 _group_print(gp, n_stdout);
2291 else{
2292 n_err(_("No such filetype: %s\n"), n_shexp_quote_cp(*argv, FAL0));
2293 rv = 1;
2295 }else for(; *argv != NULL; argv += 3){
2296 /* Because one hardly ever redefines, anything is stored in one chunk */
2297 char const *ccp;
2298 char *cp, c;
2299 size_t llc, lsc;
2301 if(argv[1] == NULL || argv[2] == NULL){
2302 n_err(_("Synopsis: filetype: <extension> <load-cmd> <save-cmd>\n"));
2303 rv = 1;
2304 break;
2307 /* Delete the old one, if any; don't get fooled to remove them all */
2308 ccp = argv[0];
2309 if(ccp[0] != '*' || ccp[1] != '\0')
2310 _group_del(GT_FILETYPE, ccp);
2312 /* Lowercase it all (for display purposes) */
2313 cp = savestr(ccp);
2314 ccp = cp;
2315 while((c = *cp) != '\0')
2316 *cp++ = lowerconv(c);
2318 llc = strlen(argv[1]) +1;
2319 lsc = strlen(argv[2]) +1;
2320 if(UIZ_MAX - llc <= lsc)
2321 goto jenomem;
2323 if((gp = _group_fetch(GT_FILETYPE, ccp, llc + lsc)) == NULL){
2324 jenomem:
2325 n_err(_("Failed to create storage for filetype: %s\n"),
2326 n_shexp_quote_cp(argv[0], FAL0));
2327 rv = 1;
2328 }else{
2329 struct a_nag_file_type *nftp;
2331 GP_TO_SUBCLASS(nftp, gp);
2332 GP_TO_SUBCLASS(cp, gp);
2333 cp += sizeof *nftp;
2334 memcpy(nftp->nft_load.s = cp, argv[1], llc);
2335 cp += llc;
2336 nftp->nft_load.l = --llc;
2337 memcpy(nftp->nft_save.s = cp, argv[2], lsc);
2338 /*cp += lsc;*/
2339 nftp->nft_save.l = --lsc;
2342 NYD_LEAVE;
2343 return rv;
2346 FL int
2347 c_unfiletype(void *vp){
2348 char **argv;
2349 int rv;
2350 NYD_ENTER;
2352 rv = 0;
2353 argv = vp;
2355 do if(!_group_del(GT_FILETYPE, *argv)){
2356 n_err(_("No such `filetype': %s\n"), n_shexp_quote_cp(*argv, FAL0));
2357 rv = 1;
2358 }while(*++argv != NULL);
2359 NYD_LEAVE;
2360 return rv;
2363 FL bool_t
2364 n_filetype_trial(struct n_file_type *res_or_null, char const *file){
2365 struct stat stb;
2366 struct group_lookup gl;
2367 struct n_string s, *sp;
2368 struct group const *gp;
2369 ui32_t l;
2370 NYD2_ENTER;
2372 sp = n_string_creat_auto(&s);
2373 sp = n_string_assign_cp(sp, file);
2374 sp = n_string_push_c(sp, '.');
2375 l = sp->s_len;
2377 for(gp = _group_go_first(GT_FILETYPE, &gl); gp != NULL;
2378 gp = _group_go_next(&gl)){
2379 sp = n_string_trunc(sp, l);
2380 sp = n_string_push_buf(sp, gp->g_id,
2381 gp->g_subclass_off - gp->g_id_len_sub);
2383 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2384 if(res_or_null != NULL){
2385 struct a_nag_file_type *nftp;
2387 GP_TO_SUBCLASS(nftp, gp);
2388 res_or_null->ft_ext_dat = gp->g_id;
2389 res_or_null->ft_ext_len = gp->g_subclass_off - gp->g_id_len_sub;
2390 res_or_null->ft_load_dat = nftp->nft_load.s;
2391 res_or_null->ft_load_len = nftp->nft_load.l;
2392 res_or_null->ft_save_dat = nftp->nft_save.s;
2393 res_or_null->ft_save_len = nftp->nft_save.l;
2395 goto jleave; /* TODO after v15 legacy drop: break; */
2399 /* TODO v15 legacy code: automatic file hooks for .{bz2,gz,xz},
2400 * TODO but NOT supporting *file-hook-{load,save}-EXTENSION* */
2401 gp = (struct group*)0x1;
2403 sp = n_string_trunc(sp, l);
2404 sp = n_string_push_buf(sp, a_nag_OBSOLETE_xz.ft_ext_dat,
2405 a_nag_OBSOLETE_xz.ft_ext_len);
2406 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2407 n_OBSOLETE(".xz support will vanish, please use the `filetype' command");
2408 if(res_or_null != NULL)
2409 *res_or_null = a_nag_OBSOLETE_xz;
2410 goto jleave;
2413 sp = n_string_trunc(sp, l);
2414 sp = n_string_push_buf(sp, a_nag_OBSOLETE_gz.ft_ext_dat,
2415 a_nag_OBSOLETE_gz.ft_ext_len);
2416 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2417 n_OBSOLETE(".gz support will vanish, please use the `filetype' command");
2418 if(res_or_null != NULL)
2419 *res_or_null = a_nag_OBSOLETE_gz;
2420 goto jleave;
2423 sp = n_string_trunc(sp, l);
2424 sp = n_string_push_buf(sp, a_nag_OBSOLETE_bz2.ft_ext_dat,
2425 a_nag_OBSOLETE_bz2.ft_ext_len);
2426 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2427 n_OBSOLETE(".bz2 support will vanish, please use the `filetype' command");
2428 if(res_or_null != NULL)
2429 *res_or_null = a_nag_OBSOLETE_bz2;
2430 goto jleave;
2433 gp = NULL;
2435 jleave:
2436 NYD2_LEAVE;
2437 return (gp != NULL);
2440 FL bool_t
2441 n_filetype_exists(struct n_file_type *res_or_null, char const *file){
2442 char const *ext, *lext;
2443 NYD2_ENTER;
2445 if((ext = strrchr(file, '/')) != NULL)
2446 file = ++ext;
2448 for(lext = NULL; (ext = strchr(file, '.')) != NULL; lext = file = ext){
2449 struct group const *gp;
2451 if((gp = _group_find(GT_FILETYPE, ++ext)) != NULL){
2452 lext = ext;
2453 if(res_or_null != NULL){
2454 struct a_nag_file_type *nftp;
2456 GP_TO_SUBCLASS(nftp, gp);
2457 res_or_null->ft_ext_dat = gp->g_id;
2458 res_or_null->ft_ext_len = gp->g_subclass_off - gp->g_id_len_sub;
2459 res_or_null->ft_load_dat = nftp->nft_load.s;
2460 res_or_null->ft_load_len = nftp->nft_load.l;
2461 res_or_null->ft_save_dat = nftp->nft_save.s;
2462 res_or_null->ft_save_len = nftp->nft_save.l;
2464 goto jleave; /* TODO after v15 legacy drop: break; */
2468 /* TODO v15 legacy code: automatic file hooks for .{bz2,gz,xz},
2469 * TODO as well as supporting *file-hook-{load,save}-EXTENSION* */
2470 if(lext == NULL)
2471 goto jleave;
2473 if(!asccasecmp(lext, "xz")){
2474 n_OBSOLETE(".xz support will vanish, please use the `filetype' command");
2475 if(res_or_null != NULL)
2476 *res_or_null = a_nag_OBSOLETE_xz;
2477 goto jleave;
2478 }else if(!asccasecmp(lext, "gz")){
2479 n_OBSOLETE(".gz support will vanish, please use the `filetype' command");
2480 if(res_or_null != NULL)
2481 *res_or_null = a_nag_OBSOLETE_gz;
2482 goto jleave;
2483 }else if(!asccasecmp(lext, "bz2")){
2484 n_OBSOLETE(".bz2 support will vanish, please use the `filetype' command");
2485 if(res_or_null != NULL)
2486 *res_or_null = a_nag_OBSOLETE_bz2;
2487 goto jleave;
2488 }else{
2489 char const *cload, *csave;
2490 char *vbuf;
2491 size_t l;
2493 #undef a_X1
2494 #define a_X1 "file-hook-load-"
2495 #undef a_X2
2496 #define a_X2 "file-hook-save-"
2497 l = strlen(lext);
2498 vbuf = n_lofi_alloc(l + n_MAX(sizeof(a_X1), sizeof(a_X2)));
2500 memcpy(vbuf, a_X1, sizeof(a_X1) -1);
2501 memcpy(&vbuf[sizeof(a_X1) -1], lext, l);
2502 vbuf[sizeof(a_X1) -1 + l] = '\0';
2503 cload = n_var_vlook(vbuf, FAL0);
2505 memcpy(vbuf, a_X2, sizeof(a_X2) -1);
2506 memcpy(&vbuf[sizeof(a_X2) -1], lext, l);
2507 vbuf[sizeof(a_X2) -1 + l] = '\0';
2508 csave = n_var_vlook(vbuf, FAL0);
2510 #undef a_X2
2511 #undef a_X1
2512 n_lofi_free(vbuf);
2514 if((csave != NULL) | (cload != NULL)){
2515 n_OBSOLETE("*file-hook-{load,save}-EXTENSION* will vanish, "
2516 "please use the `filetype' command");
2518 if(((csave != NULL) ^ (cload != NULL)) == 0){
2519 if(res_or_null != NULL){
2520 res_or_null->ft_ext_dat = lext;
2521 res_or_null->ft_ext_len = l;
2522 res_or_null->ft_load_dat = cload;
2523 res_or_null->ft_load_len = strlen(cload);
2524 res_or_null->ft_save_dat = csave;
2525 res_or_null->ft_save_len = strlen(csave);
2527 goto jleave;
2528 }else
2529 n_alert(_("Incomplete *file-hook-{load,save}-EXTENSION* for: .%s"),
2530 lext);
2534 lext = NULL;
2536 jleave:
2537 NYD2_LEAVE;
2538 return (lext != NULL);
2541 /* s-it-mode */