`echo'++: take care for I/O errors
[s-mailx.git] / nam-a-grp.c
blob13f0b5f74fb25c9807e4e43dbc872dad354339f2
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 _same_name(char const *n1, char const *n2);
162 /* Delete the given name from a namelist */
163 static struct name * delname(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 _same_name(char const *n1, char const *n2)
234 bool_t rv = FAL0;
235 char c1, c2;
236 NYD_ENTER;
238 if (ok_blook(allnet)) {
239 do {
240 c1 = *n1++;
241 c2 = *n2++;
242 c1 = lowerconv(c1);
243 c2 = lowerconv(c2);
244 if (c1 != c2)
245 goto jleave;
246 } while (c1 != '\0' && c2 != '\0' && c1 != '@' && c2 != '@');
247 rv = 1;
248 } else
249 rv = !asccasecmp(n1, n2);
250 jleave:
251 NYD_LEAVE;
252 return rv;
255 static struct name *
256 delname(struct name *np, char const *name)
258 struct name *p;
259 NYD_ENTER;
261 for (p = np; p != NULL; p = p->n_flink)
262 if (_same_name(p->n_name, name)) {
263 if (p->n_blink == NULL) {
264 if (p->n_flink != NULL)
265 p->n_flink->n_blink = NULL;
266 np = p->n_flink;
267 continue;
269 if (p->n_flink == NULL) {
270 if (p->n_blink != NULL)
271 p->n_blink->n_flink = NULL;
272 continue;
274 p->n_blink->n_flink = p->n_flink;
275 p->n_flink->n_blink = p->n_blink;
277 NYD_LEAVE;
278 return np;
281 static char const *
282 yankname(char const *ap, char *wbuf, char const *separators, int keepcomms)
284 char const *cp;
285 char *wp, c, inquote, lc, lastsp;
286 NYD_ENTER;
288 *(wp = wbuf) = '\0';
290 /* Skip over intermediate list trash, as in ".org> , <xy@zz.org>" */
291 for (c = *ap; blankchar(c) || c == ','; c = *++ap)
293 if (c == '\0') {
294 cp = NULL;
295 goto jleave;
298 /* Parse a full name: TODO RFC 5322
299 * - Keep everything in quotes, liberal handle *quoted-pair*s therein
300 * - Skip entire (nested) comments
301 * - In non-quote, non-comment, join adjacent space to a single SP
302 * - Understand separators only in non-quote, non-comment context,
303 * and only if not part of a *quoted-pair* (XXX too liberal) */
304 cp = ap;
305 for (inquote = lc = lastsp = 0;; lc = c, ++cp) {
306 c = *cp;
307 if (c == '\0')
308 break;
309 if (c == '\\')
310 goto jwpwc;
311 if (c == '"') {
312 if (lc != '\\')
313 inquote = !inquote;
314 #if 0 /* TODO when doing real RFC 5322 parsers - why have i done this? */
315 else
316 --wp;
317 #endif
318 goto jwpwc;
320 if (inquote || lc == '\\') {
321 jwpwc:
322 *wp++ = c;
323 lastsp = 0;
324 continue;
326 if (c == '(') {
327 ap = cp;
328 cp = skip_comment(cp + 1);
329 if (keepcomms)
330 while (ap < cp)
331 *wp++ = *ap++;
332 --cp;
333 lastsp = 0;
334 continue;
336 if (strchr(separators, c) != NULL)
337 break;
339 lc = lastsp;
340 lastsp = blankchar(c);
341 if (!lastsp || !lc)
342 *wp++ = c;
344 if (blankchar(lc))
345 --wp;
347 *wp = '\0';
348 jleave:
349 NYD_LEAVE;
350 return cp;
353 static struct name *
354 _extract1(char const *line, enum gfield ntype, char const *separators,
355 bool_t keepcomms)
357 struct name *topp, *np, *t;
358 char const *cp;
359 char *nbuf;
360 NYD_ENTER;
362 topp = NULL;
363 if (line == NULL || *line == '\0')
364 goto jleave;
366 np = NULL;
367 cp = line;
368 nbuf = smalloc(strlen(line) +1);
369 while ((cp = yankname(cp, nbuf, separators, keepcomms)) != NULL) {
370 t = nalloc(nbuf, ntype);
371 if (topp == NULL)
372 topp = t;
373 else
374 np->n_flink = t;
375 t->n_blink = np;
376 np = t;
378 free(nbuf);
379 jleave:
380 NYD_LEAVE;
381 return topp;
384 static struct name *
385 a_nag_gexpand(size_t level, struct name *nlist, struct group *gp, bool_t metoo,
386 int ntype){
387 struct grp_names *gnp;
388 struct name *nlist_tail;
389 char const *logname;
390 struct grp_names_head *gnhp;
391 NYD2_ENTER;
393 if(UICMP(z, level++, >, n_ALIAS_MAXEXP)){
394 n_err(_("Expanding alias to depth larger than %d\n"), n_ALIAS_MAXEXP);
395 goto jleave;
398 GP_TO_SUBCLASS(gnhp, gp);
399 logname = ok_vlook(LOGNAME);
401 for(gnp = gnhp->gnh_head; gnp != NULL; gnp = gnp->gn_next){
402 struct group *ngp;
403 char *cp;
405 cp = gnp->gn_id;
407 if(!strcmp(cp, gp->g_id))
408 goto jas_is;
410 if((ngp = _group_find(GT_ALIAS, cp)) != NULL){
411 /* For S-nail(1), the "alias" may *be* the sender in that a name maps
412 * to a full address specification; aliases cannot be empty */
413 struct grp_names_head *ngnhp;
414 GP_TO_SUBCLASS(ngnhp, ngp);
416 assert(ngnhp->gnh_head != NULL);
417 if(metoo || ngnhp->gnh_head->gn_next != NULL ||
418 !_same_name(cp, logname))
419 nlist = a_nag_gexpand(level, nlist, ngp, metoo, ntype);
420 continue;
423 /* Here we should allow to expand to itself if only person in alias */
424 jas_is:
425 if(metoo || gnhp->gnh_head->gn_next == NULL || !_same_name(cp, logname)){
426 struct name *np;
428 np = nalloc(cp, ntype | GFULL);
429 if((nlist_tail = nlist) != NULL){
430 while(nlist_tail->n_flink != NULL)
431 nlist_tail = nlist_tail->n_flink;
432 nlist_tail->n_flink = np;
433 np->n_blink = nlist_tail;
434 }else
435 nlist = np;
438 jleave:
439 NYD2_LEAVE;
440 return nlist;
443 static int
444 a_nag_elide_qsort(void const *s1, void const *s2){
445 struct name const * const *np1, * const *np2;
446 int rv;
447 NYD2_ENTER;
449 np1 = s1;
450 np2 = s2;
451 rv = asccasecmp((*np1)->n_name, (*np2)->n_name);
452 NYD2_LEAVE;
453 return rv;
456 static struct group *
457 _group_lookup(enum group_type gt, struct group_lookup *glp, char const *id){
458 char c1;
459 struct group *lgp, *gp;
460 NYD_ENTER;
462 gt &= GT_MASK;
463 lgp = NULL;
464 glp->gl_htable =
465 ( gt & GT_COMMANDALIAS ? _commandalias_heads
466 : (gt & GT_ALIAS ? _alias_heads
467 : (gt & GT_MLIST ? _mlist_heads
468 : (gt & GT_SHORTCUT ? _shortcut_heads
469 : (gt & GT_CHARSETALIAS ? _charsetalias_heads
470 : (/*gt & GT_FILETYPE ?*/ _filetype_heads
471 ))))));
472 gp = *(glp->gl_slot = &glp->gl_htable[
473 ((gt & (GT_MLIST | GT_CHARSETALIAS | GT_FILETYPE))
474 ? n_torek_ihash(id) : n_torek_hash(id)) % HSHSIZE]);
475 c1 = *id++;
477 if(gt & (GT_MLIST | GT_CHARSETALIAS | GT_FILETYPE)){
478 c1 = lowerconv(c1);
479 for(; gp != NULL; lgp = gp, gp = gp->g_next)
480 if((gp->g_type & gt) && *gp->g_id == c1 &&
481 !asccasecmp(&gp->g_id[1], id))
482 break;
483 }else{
484 for(; gp != NULL; lgp = gp, gp = gp->g_next)
485 if((gp->g_type & gt) && *gp->g_id == c1 && !strcmp(&gp->g_id[1], id))
486 break;
489 glp->gl_slot_last = lgp;
490 glp->gl_group = gp;
491 NYD_LEAVE;
492 return gp;
495 static struct group *
496 _group_find(enum group_type gt, char const *id)
498 struct group_lookup gl;
499 struct group *gp;
500 NYD_ENTER;
502 gp = _group_lookup(gt, &gl, id);
503 NYD_LEAVE;
504 return gp;
507 static struct group *
508 _group_go_first(enum group_type gt, struct group_lookup *glp)
510 struct group **gpa, *gp;
511 size_t i;
512 NYD_ENTER;
514 for (glp->gl_htable = gpa = (
515 gt & GT_COMMANDALIAS ? _commandalias_heads
516 : (gt & GT_ALIAS ? _alias_heads
517 : (gt & GT_MLIST ? _mlist_heads
518 : (gt & GT_SHORTCUT ? _shortcut_heads
519 : (gt & GT_CHARSETALIAS ? _charsetalias_heads
520 : (gt & GT_FILETYPE ? _filetype_heads
521 : NULL)))))
522 ), i = 0;
523 i < HSHSIZE; ++gpa, ++i)
524 if ((gp = *gpa) != NULL) {
525 glp->gl_slot = gpa;
526 glp->gl_group = gp;
527 goto jleave;
530 glp->gl_group = gp = NULL;
531 jleave:
532 glp->gl_slot_last = NULL;
533 NYD_LEAVE;
534 return gp;
537 static struct group *
538 _group_go_next(struct group_lookup *glp)
540 struct group *gp, **gpa;
541 NYD_ENTER;
543 if ((gp = glp->gl_group->g_next) != NULL)
544 glp->gl_slot_last = glp->gl_group;
545 else {
546 glp->gl_slot_last = NULL;
547 for (gpa = glp->gl_htable + HSHSIZE; ++glp->gl_slot < gpa;)
548 if ((gp = *glp->gl_slot) != NULL)
549 break;
551 glp->gl_group = gp;
552 NYD_LEAVE;
553 return gp;
556 static struct group *
557 _group_fetch(enum group_type gt, char const *id, size_t addsz)
559 struct group_lookup gl;
560 struct group *gp;
561 size_t l, i;
562 NYD_ENTER;
564 if ((gp = _group_lookup(gt, &gl, id)) != NULL)
565 goto jleave;
567 l = strlen(id) +1;
568 if (UIZ_MAX - n_ALIGN(l) <= n_ALIGN(n_VSTRUCT_SIZEOF(struct group, g_id)))
569 goto jleave;
571 i = n_ALIGN(n_VSTRUCT_SIZEOF(struct group, g_id) + l);
572 switch (gt & GT_MASK) {
573 case GT_COMMANDALIAS:
574 addsz += sizeof(struct a_nag_cmd_alias);
575 break;
576 case GT_ALIAS:
577 addsz += sizeof(struct grp_names_head);
578 break;
579 case GT_FILETYPE:
580 addsz += sizeof(struct a_nag_file_type);
581 break;
582 case GT_MLIST:
583 #ifdef HAVE_REGEX
584 if (n_is_maybe_regex(id)) {
585 addsz = sizeof(struct grp_regex);
586 gt |= GT_REGEX;
588 #endif
589 /* FALLTHRU */
590 case GT_SHORTCUT:
591 case GT_CHARSETALIAS:
592 default:
593 break;
595 if (UIZ_MAX - i < addsz || UI32_MAX <= i || UI16_MAX < i - l)
596 goto jleave;
598 gp = smalloc(i + addsz);
599 memcpy(gp->g_id, id, l);
600 gp->g_subclass_off = (ui32_t)i;
601 gp->g_id_len_sub = (ui16_t)(i - --l);
602 gp->g_type = gt;
603 if(gt & (GT_MLIST | GT_CHARSETALIAS | GT_FILETYPE)){
604 char *cp, c;
606 for(cp = gp->g_id; (c = *cp) != '\0'; ++cp)
607 *cp = lowerconv(c);
610 if (gt & GT_ALIAS) {
611 struct grp_names_head *gnhp;
613 GP_TO_SUBCLASS(gnhp, gp);
614 gnhp->gnh_head = NULL;
616 #ifdef HAVE_REGEX
617 else if (/*(gt & GT_MLIST) &&*/ gt & GT_REGEX) {
618 int s;
619 struct grp_regex *grp;
620 GP_TO_SUBCLASS(grp, gp);
622 if((s = regcomp(&grp->gr_regex, id,
623 REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0){
624 n_err(_("Invalid regular expression: %s: %s\n"),
625 n_shexp_quote_cp(id, FAL0), n_regex_err_to_doc(&grp->gr_regex, s));
626 free(gp);
627 gp = NULL;
628 goto jleave;
630 grp->gr_mygroup = gp;
631 _mlmux_linkin(gp);
633 #endif
635 gp->g_next = *gl.gl_slot;
636 *gl.gl_slot = gp;
637 jleave:
638 NYD_LEAVE;
639 return gp;
642 static bool_t
643 _group_del(enum group_type gt, char const *id)
645 enum group_type xgt = gt & GT_MASK;
646 struct group_lookup gl;
647 struct group *gp;
648 NYD_ENTER;
650 /* Delete 'em all? */
651 if (id[0] == '*' && id[1] == '\0') {
652 for (gp = _group_go_first(gt, &gl); gp != NULL;)
653 gp = (gp->g_type & xgt) ? __group_del(&gl) : _group_go_next(&gl);
654 gp = (struct group*)TRU1;
655 } else if ((gp = _group_lookup(gt, &gl, id)) != NULL) {
656 if (gp->g_type & xgt)
657 __group_del(&gl);
658 else
659 gp = NULL;
661 NYD_LEAVE;
662 return (gp != NULL);
665 static struct group *
666 __group_del(struct group_lookup *glp)
668 struct group *x, *gp;
669 NYD_ENTER;
671 /* Overly complicated: link off this node, step ahead to next.. */
672 x = glp->gl_group;
673 if((gp = glp->gl_slot_last) != NULL)
674 gp = (gp->g_next = x->g_next);
675 else{
676 glp->gl_slot_last = NULL;
677 gp = (*glp->gl_slot = x->g_next);
679 if(gp == NULL){
680 struct group **gpa;
682 for(gpa = &glp->gl_htable[HSHSIZE]; ++glp->gl_slot < gpa;)
683 if((gp = *glp->gl_slot) != NULL)
684 break;
687 glp->gl_group = gp;
689 if (x->g_type & GT_ALIAS)
690 __names_del(x);
691 #ifdef HAVE_REGEX
692 else if (/*(x->g_type & GT_MLIST) &&*/ x->g_type & GT_REGEX) {
693 struct grp_regex *grp;
694 GP_TO_SUBCLASS(grp, x);
696 regfree(&grp->gr_regex);
697 _mlmux_linkout(x);
699 #endif
701 free(x);
702 NYD_LEAVE;
703 return gp;
706 static void
707 __names_del(struct group *gp)
709 struct grp_names_head *gnhp;
710 struct grp_names *gnp;
711 NYD_ENTER;
713 GP_TO_SUBCLASS(gnhp, gp);
714 for (gnp = gnhp->gnh_head; gnp != NULL;) {
715 struct grp_names *x = gnp;
716 gnp = gnp->gn_next;
717 free(x);
719 NYD_LEAVE;
722 static void
723 _group_print_all(enum group_type gt)
725 enum group_type xgt;
726 struct group **gpa;
727 struct group const *gp;
728 ui32_t h, i;
729 char const **ida;
730 FILE *fp;
731 size_t lines;
732 NYD_ENTER;
734 xgt = gt & GT_PRINT_MASK;
735 gpa = ( xgt & GT_COMMANDALIAS ? _commandalias_heads
736 : (xgt & GT_ALIAS ? _alias_heads
737 : (xgt & GT_MLIST ? _mlist_heads
738 : (xgt & GT_SHORTCUT ? _shortcut_heads
739 : (xgt & GT_CHARSETALIAS ? _charsetalias_heads
740 : (xgt & GT_FILETYPE ? _filetype_heads
741 : NULL))))));
743 for (h = 0, i = 1; h < HSHSIZE; ++h)
744 for (gp = gpa[h]; gp != NULL; gp = gp->g_next)
745 if ((gp->g_type & xgt) == xgt)
746 ++i;
747 ida = salloc(i * sizeof *ida);
749 for (i = h = 0; h < HSHSIZE; ++h)
750 for (gp = gpa[h]; gp != NULL; gp = gp->g_next)
751 if ((gp->g_type & xgt) == xgt)
752 ida[i++] = gp->g_id;
753 ida[i] = NULL;
755 if (i > 1)
756 qsort(ida, i, sizeof *ida, &__group_print_qsorter);
758 if ((fp = Ftmp(NULL, "prgroup", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL)
759 fp = n_stdout;
760 lines = 0;
762 for (i = 0; ida[i] != NULL; ++i)
763 lines += _group_print(_group_find(gt, ida[i]), fp);
764 #ifdef HAVE_REGEX
765 if (gt & GT_MLIST) {
766 if (gt & GT_SUBSCRIBE)
767 i = (ui32_t)_mlsub_size, h = (ui32_t)_mlsub_hits;
768 else
769 i = (ui32_t)_mlist_size, h = (ui32_t)_mlist_hits;
770 if (i > 0 && (n_poption & n_PO_D_V)){
771 fprintf(fp, _("# %s list regex(7) total: %u entries, %u hits\n"),
772 (gt & GT_SUBSCRIBE ? _("Subscribed") : _("Non-subscribed")),
773 i, h);
774 ++lines;
777 #endif
779 if (fp != n_stdout) {
780 page_or_print(fp, lines);
781 Fclose(fp);
783 NYD_LEAVE;
786 static int
787 __group_print_qsorter(void const *a, void const *b)
789 int rv;
790 NYD_ENTER;
792 rv = strcmp(*(char**)n_UNCONST(a), *(char**)n_UNCONST(b));
793 NYD_LEAVE;
794 return rv;
797 static size_t
798 _group_print(struct group const *gp, FILE *fo)
800 char const *cp;
801 size_t rv;
802 NYD_ENTER;
804 rv = 1;
806 if(gp->g_type & GT_COMMANDALIAS){
807 struct a_nag_cmd_alias *ncap;
809 GP_TO_SUBCLASS(ncap, gp);
810 fprintf(fo, "commandalias %s %s\n",
811 n_shexp_quote_cp(gp->g_id, TRU1),
812 n_shexp_quote_cp(ncap->ca_expand.s, TRU1));
813 } else if (gp->g_type & GT_ALIAS) {
814 struct grp_names_head *gnhp;
815 struct grp_names *gnp;
817 fprintf(fo, "alias %s ", gp->g_id);
819 GP_TO_SUBCLASS(gnhp, gp);
820 if ((gnp = gnhp->gnh_head) != NULL) { /* xxx always 1+ entries */
821 do {
822 struct grp_names *x = gnp;
823 gnp = gnp->gn_next;
824 fprintf(fo, " \"%s\"", string_quote(x->gn_id)); /* TODO shexp */
825 } while (gnp != NULL);
827 putc('\n', fo);
828 } else if (gp->g_type & GT_MLIST) {
829 #ifdef HAVE_REGEX
830 if ((gp->g_type & GT_REGEX) && (n_poption & n_PO_D_V)){
831 size_t i;
832 struct grp_regex *grp,
833 *lp = (gp->g_type & GT_SUBSCRIBE ? _mlsub_regex : _mlist_regex);
835 GP_TO_SUBCLASS(grp, gp);
836 for (i = 1; lp != grp; lp = lp->gr_next)
837 ++i;
838 fprintf(fo, "# regex(7): hits %" PRIuZ ", sort %" PRIuZ ".\n ",
839 grp->gr_hits, i);
840 ++rv;
842 #endif
844 fprintf(fo, "wysh %s %s\n",
845 (gp->g_type & GT_SUBSCRIBE ? "mlsubscribe" : "mlist"),
846 n_shexp_quote_cp(gp->g_id, TRU1));
847 } else if (gp->g_type & GT_SHORTCUT) {
848 GP_TO_SUBCLASS(cp, gp);
849 fprintf(fo, "wysh shortcut %s %s\n",
850 gp->g_id, n_shexp_quote_cp(cp, TRU1));
851 } else if (gp->g_type & GT_CHARSETALIAS) {
852 GP_TO_SUBCLASS(cp, gp);
853 fprintf(fo, "charsetalias %s %s\n",
854 n_shexp_quote_cp(gp->g_id, TRU1), n_shexp_quote_cp(cp, TRU1));
855 } else if (gp->g_type & GT_FILETYPE) {
856 struct a_nag_file_type *nftp;
858 GP_TO_SUBCLASS(nftp, gp);
859 fprintf(fo, "filetype %s %s %s\n",
860 n_shexp_quote_cp(gp->g_id, TRU1),
861 n_shexp_quote_cp(nftp->nft_load.s, TRU1),
862 n_shexp_quote_cp(nftp->nft_save.s, TRU1));
865 NYD_LEAVE;
866 return rv;
869 static int
870 _mlmux(enum group_type gt, char **argv)
872 char const *ecp;
873 struct group *gp;
874 int rv = 0;
875 NYD_ENTER;
877 rv = 0;
878 n_UNINIT(ecp, NULL);
880 if (*argv == NULL)
881 _group_print_all(gt);
882 else do {
883 if ((gp = _group_find(gt, *argv)) != NULL) {
884 if (gt & GT_SUBSCRIBE) {
885 if (!(gp->g_type & GT_SUBSCRIBE)) {
886 _MLMUX_LINKOUT(gp);
887 gp->g_type |= GT_SUBSCRIBE;
888 _MLMUX_LINKIN(gp);
889 } else {
890 ecp = N_("Mailing-list already `mlsubscribe'd: %s\n");
891 goto jerr;
893 } else {
894 ecp = N_("Mailing-list already `mlist'ed: %s\n");
895 goto jerr;
897 } else if(_group_fetch(gt, *argv, 0) == NULL) {
898 ecp = N_("Failed to create storage for mailing-list: %s\n");
899 jerr:
900 n_err(V_(ecp), n_shexp_quote_cp(*argv, FAL0));
901 rv = 1;
903 } while (*++argv != NULL);
905 NYD_LEAVE;
906 return rv;
909 static int
910 _unmlmux(enum group_type gt, char **argv)
912 struct group *gp;
913 int rv = 0;
914 NYD_ENTER;
916 for (; *argv != NULL; ++argv) {
917 if (gt & GT_SUBSCRIBE) {
918 struct group_lookup gl;
919 bool_t isaster;
921 if (!(isaster = (**argv == '*')))
922 gp = _group_find(gt, *argv);
923 else if ((gp = _group_go_first(gt, &gl)) == NULL)
924 continue;
925 else if (gp != NULL && !(gp->g_type & GT_SUBSCRIBE))
926 goto jaster_entry;
928 if (gp != NULL) {
929 jaster_redo:
930 if (gp->g_type & GT_SUBSCRIBE) {
931 _MLMUX_LINKOUT(gp);
932 gp->g_type &= ~GT_SUBSCRIBE;
933 _MLMUX_LINKIN(gp);
934 if (isaster) {
935 jaster_entry:
936 while ((gp = _group_go_next(&gl)) != NULL &&
937 !(gp->g_type & GT_SUBSCRIBE))
939 if (gp != NULL)
940 goto jaster_redo;
942 } else {
943 n_err(_("Mailing-list not `mlsubscribe'd: %s\n"),
944 n_shexp_quote_cp(*argv, FAL0));
945 rv = 1;
947 continue;
949 } else if (_group_del(gt, *argv))
950 continue;
951 n_err(_("No such mailing-list: %s\n"), n_shexp_quote_cp(*argv, FAL0));
952 rv = 1;
954 NYD_LEAVE;
955 return rv;
958 #ifdef HAVE_REGEX
959 static void
960 _mlmux_linkin(struct group *gp)
962 struct grp_regex **lpp, *grp, *lhp;
963 NYD_ENTER;
965 if (gp->g_type & GT_SUBSCRIBE) {
966 lpp = &_mlsub_regex;
967 ++_mlsub_size;
968 } else {
969 lpp = &_mlist_regex;
970 ++_mlist_size;
973 GP_TO_SUBCLASS(grp, gp);
974 if ((lhp = *lpp) != NULL) {
975 (grp->gr_last = lhp->gr_last)->gr_next = grp;
976 (grp->gr_next = lhp)->gr_last = grp;
977 } else
978 *lpp = grp->gr_last = grp->gr_next = grp;
979 grp->gr_hits = 0;
980 NYD_LEAVE;
983 static void
984 _mlmux_linkout(struct group *gp)
986 struct grp_regex *grp, **lpp;
987 NYD_ENTER;
989 GP_TO_SUBCLASS(grp, gp);
991 if (gp->g_type & GT_SUBSCRIBE) {
992 lpp = &_mlsub_regex;
993 --_mlsub_size;
994 _mlsub_hits -= grp->gr_hits;
995 } else {
996 lpp = &_mlist_regex;
997 --_mlist_size;
998 _mlist_hits -= grp->gr_hits;
1001 if (grp->gr_next == grp)
1002 *lpp = NULL;
1003 else {
1004 (grp->gr_last->gr_next = grp->gr_next)->gr_last = grp->gr_last;
1005 if (*lpp == grp)
1006 *lpp = grp->gr_next;
1008 NYD_LEAVE;
1010 #endif /* HAVE_REGEX */
1012 FL struct name *
1013 nalloc(char const *str, enum gfield ntype)
1015 struct n_addrguts ag;
1016 struct str in, out;
1017 struct name *np;
1018 NYD_ENTER;
1019 assert(!(ntype & GFULLEXTRA) || (ntype & GFULL) != 0);
1021 str = n_addrspec_with_guts(&ag, str,
1022 ((ntype & (GFULL | GSKIN | GREF)) != 0), FAL0);
1023 if(str == NULL){
1025 np = NULL; TODO We cannot return NULL,
1026 goto jleave; TODO thus handle failures in here!
1028 str = ag.ag_input;
1031 if (!(ag.ag_n_flags & NAME_NAME_SALLOC)) {
1032 ag.ag_n_flags |= NAME_NAME_SALLOC;
1033 np = salloc(sizeof(*np) + ag.ag_slen +1);
1034 memcpy(np + 1, ag.ag_skinned, ag.ag_slen +1);
1035 ag.ag_skinned = (char*)(np + 1);
1036 } else
1037 np = salloc(sizeof *np);
1039 np->n_flink = NULL;
1040 np->n_blink = NULL;
1041 np->n_type = ntype;
1042 np->n_flags = 0;
1044 np->n_fullname = np->n_name = ag.ag_skinned;
1045 np->n_fullextra = NULL;
1046 np->n_flags = ag.ag_n_flags;
1048 if (ntype & GFULL) {
1049 if (ag.ag_ilen == ag.ag_slen
1050 #ifdef HAVE_IDNA
1051 && !(ag.ag_n_flags & NAME_IDNA)
1052 #endif
1054 goto jleave;
1055 if (ag.ag_n_flags & NAME_ADDRSPEC_ISFILEORPIPE)
1056 goto jleave;
1058 /* n_fullextra is only the complete name part without address.
1059 * Beware of "-r '<abc@def>'", don't treat that as FULLEXTRA */
1060 if ((ntype & GFULLEXTRA) && ag.ag_ilen > ag.ag_slen + 2) {
1061 size_t s = ag.ag_iaddr_start, e = ag.ag_iaddr_aend, i;
1062 char const *cp;
1064 if (s == 0 || str[--s] != '<' || str[e++] != '>')
1065 goto jskipfullextra;
1066 i = ag.ag_ilen - e;
1067 in.s = n_lofi_alloc(s + 1 + i +1);
1068 while(s > 0 && blankchar(str[s - 1]))
1069 --s;
1070 memcpy(in.s, str, s);
1071 if (i > 0) {
1072 in.s[s++] = ' ';
1073 while (blankchar(str[e])) {
1074 ++e;
1075 if (--i == 0)
1076 break;
1078 if (i > 0)
1079 memcpy(&in.s[s], &str[e], i);
1081 s += i;
1082 in.s[in.l = s] = '\0';
1083 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
1085 for (cp = out.s, i = out.l; i > 0 && spacechar(*cp); --i, ++cp)
1087 while (i > 0 && spacechar(cp[i - 1]))
1088 --i;
1089 np->n_fullextra = savestrbuf(cp, i);
1091 n_lofi_free(in.s);
1092 free(out.s);
1094 jskipfullextra:
1096 /* n_fullname depends on IDNA conversion */
1097 #ifdef HAVE_IDNA
1098 if (!(ag.ag_n_flags & NAME_IDNA)) {
1099 #endif
1100 in.s = n_UNCONST(str);
1101 in.l = ag.ag_ilen;
1102 #ifdef HAVE_IDNA
1103 } else {
1104 /* The domain name was IDNA and has been converted. We also have to
1105 * ensure that the domain name in .n_fullname is replaced with the
1106 * converted version, since MIME doesn't perform encoding of addrs */
1107 /* TODO This definetely doesn't belong here! */
1108 size_t l = ag.ag_iaddr_start,
1109 lsuff = ag.ag_ilen - ag.ag_iaddr_aend;
1110 in.s = ac_alloc(l + ag.ag_slen + lsuff +1);
1111 memcpy(in.s, str, l);
1112 memcpy(in.s + l, ag.ag_skinned, ag.ag_slen);
1113 l += ag.ag_slen;
1114 memcpy(in.s + l, str + ag.ag_iaddr_aend, lsuff);
1115 l += lsuff;
1116 in.s[l] = '\0';
1117 in.l = l;
1119 #endif
1120 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
1121 np->n_fullname = savestr(out.s);
1122 free(out.s);
1123 #ifdef HAVE_IDNA
1124 if (ag.ag_n_flags & NAME_IDNA)
1125 ac_free(in.s);
1126 #endif
1127 np->n_flags |= NAME_FULLNAME_SALLOC;
1129 jleave:
1130 NYD_LEAVE;
1131 return np;
1134 FL struct name *
1135 ndup(struct name *np, enum gfield ntype)
1137 struct name *nnp;
1138 NYD_ENTER;
1140 if ((ntype & (GFULL | GSKIN)) && !(np->n_flags & NAME_SKINNED)) {
1141 nnp = nalloc(np->n_name, ntype);
1142 goto jleave;
1145 nnp = salloc(sizeof *np);
1146 nnp->n_flink = nnp->n_blink = NULL;
1147 nnp->n_type = ntype;
1148 nnp->n_flags = (np->n_flags & ~(NAME_NAME_SALLOC | NAME_FULLNAME_SALLOC)) |
1149 NAME_NAME_SALLOC;
1150 nnp->n_name = savestr(np->n_name);
1151 if (np->n_name == np->n_fullname || !(ntype & (GFULL | GSKIN))) {
1152 nnp->n_fullname = nnp->n_name;
1153 nnp->n_fullextra = NULL;
1154 } else {
1155 nnp->n_flags |= NAME_FULLNAME_SALLOC;
1156 nnp->n_fullname = savestr(np->n_fullname);
1157 nnp->n_fullextra = (np->n_fullextra == NULL) ? NULL
1158 : savestr(np->n_fullextra);
1160 jleave:
1161 NYD_LEAVE;
1162 return nnp;
1165 FL struct name *
1166 cat(struct name *n1, struct name *n2)
1168 struct name *tail;
1169 NYD_ENTER;
1171 tail = n2;
1172 if (n1 == NULL)
1173 goto jleave;
1174 tail = n1;
1175 if (n2 == NULL)
1176 goto jleave;
1178 while (tail->n_flink != NULL)
1179 tail = tail->n_flink;
1180 tail->n_flink = n2;
1181 n2->n_blink = tail;
1182 tail = n1;
1183 jleave:
1184 NYD_LEAVE;
1185 return tail;
1188 FL struct name *
1189 namelist_dup(struct name const *np, enum gfield ntype){
1190 struct name *nlist, *xnp;
1191 NYD2_ENTER;
1193 for(nlist = xnp = NULL; np != NULL; np = np->n_flink){
1194 struct name *x;
1196 x = ndup(n_UNCONST(np), (np->n_type & ~GMASK) | ntype);
1197 if((x->n_blink = xnp) == NULL)
1198 nlist = x;
1199 else
1200 xnp->n_flink = x;
1201 xnp = x;
1203 NYD2_LEAVE;
1204 return nlist;
1207 FL ui32_t
1208 count(struct name const *np)
1210 ui32_t c;
1211 NYD_ENTER;
1213 for (c = 0; np != NULL; np = np->n_flink)
1214 if (!(np->n_type & GDEL))
1215 ++c;
1216 NYD_LEAVE;
1217 return c;
1220 FL ui32_t
1221 count_nonlocal(struct name const *np)
1223 ui32_t c;
1224 NYD_ENTER;
1226 for (c = 0; np != NULL; np = np->n_flink)
1227 if (!(np->n_type & GDEL) && !(np->n_flags & NAME_ADDRSPEC_ISFILEORPIPE))
1228 ++c;
1229 NYD_LEAVE;
1230 return c;
1233 FL struct name *
1234 extract(char const *line, enum gfield ntype)
1236 struct name *rv;
1237 NYD_ENTER;
1239 rv = _extract1(line, ntype, " \t,", 0);
1240 NYD_LEAVE;
1241 return rv;
1244 FL struct name *
1245 lextract(char const *line, enum gfield ntype)
1247 struct name *rv;
1248 NYD_ENTER;
1250 rv = ((line != NULL && strpbrk(line, ",\"\\(<|"))
1251 ? _extract1(line, ntype, ",", 1) : extract(line, ntype));
1252 NYD_LEAVE;
1253 return rv;
1256 FL char *
1257 detract(struct name *np, enum gfield ntype)
1259 char *topp, *cp;
1260 struct name *p;
1261 int flags, s;
1262 NYD_ENTER;
1264 topp = NULL;
1265 if (np == NULL)
1266 goto jleave;
1268 flags = ntype & (GCOMMA | GNAMEONLY);
1269 ntype &= ~(GCOMMA | GNAMEONLY);
1270 s = 0;
1272 for (p = np; p != NULL; p = p->n_flink) {
1273 if (ntype && (p->n_type & GMASK) != ntype)
1274 continue;
1275 s += strlen(flags & GNAMEONLY ? p->n_name : p->n_fullname) +1;
1276 if (flags & GCOMMA)
1277 ++s;
1279 if (s == 0)
1280 goto jleave;
1282 s += 2;
1283 topp = salloc(s);
1284 cp = topp;
1285 for (p = np; p != NULL; p = p->n_flink) {
1286 if (ntype && (p->n_type & GMASK) != ntype)
1287 continue;
1288 cp = sstpcpy(cp, (flags & GNAMEONLY ? p->n_name : p->n_fullname));
1289 if ((flags & GCOMMA) && p->n_flink != NULL)
1290 *cp++ = ',';
1291 *cp++ = ' ';
1293 *--cp = 0;
1294 if ((flags & GCOMMA) && *--cp == ',')
1295 *cp = 0;
1296 jleave:
1297 NYD_LEAVE;
1298 return topp;
1301 FL struct name *
1302 grab_names(enum n_go_input_flags gif, char const *field, struct name *np,
1303 int comma, enum gfield gflags)
1305 struct name *nq;
1306 NYD_ENTER;
1308 jloop:
1309 np = lextract(n_go_input_cp(gif, field, detract(np, comma)), gflags);
1310 for (nq = np; nq != NULL; nq = nq->n_flink)
1311 if (is_addr_invalid(nq, EACM_NONE))
1312 goto jloop;
1313 NYD_LEAVE;
1314 return np;
1317 FL bool_t
1318 name_is_same_domain(struct name const *n1, struct name const *n2)
1320 char const *d1, *d2;
1321 bool_t rv;
1322 NYD_ENTER;
1324 d1 = strrchr(n1->n_name, '@');
1325 d2 = strrchr(n2->n_name, '@');
1327 rv = (d1 != NULL && d2 != NULL) ? !asccasecmp(++d1, ++d2) : FAL0;
1329 NYD_LEAVE;
1330 return rv;
1333 FL struct name *
1334 checkaddrs(struct name *np, enum expand_addr_check_mode eacm,
1335 si8_t *set_on_error)
1337 struct name *n;
1338 NYD_ENTER;
1340 for (n = np; n != NULL; n = n->n_flink) {
1341 si8_t rv;
1343 if ((rv = is_addr_invalid(n, eacm)) != 0) {
1344 if (set_on_error != NULL)
1345 *set_on_error |= rv; /* don't loose -1! */
1346 else if (eacm & EAF_MAYKEEP) /* TODO HACK! See definition! */
1347 continue;
1348 if (n->n_blink)
1349 n->n_blink->n_flink = n->n_flink;
1350 if (n->n_flink)
1351 n->n_flink->n_blink = n->n_blink;
1352 if (n == np)
1353 np = n->n_flink;
1356 NYD_LEAVE;
1357 return np;
1360 FL struct name *
1361 namelist_vaporise_head(struct header *hp, enum expand_addr_check_mode eacm,
1362 bool_t metoo, si8_t *set_on_error)
1364 /* TODO namelist_vaporise_head() is incredibly expensive and redundant */
1365 struct name *tolist, *np, **npp;
1366 NYD_ENTER;
1368 tolist = cat(hp->h_to, cat(hp->h_cc, hp->h_bcc));
1369 hp->h_to = hp->h_cc = hp->h_bcc = NULL;
1371 tolist = usermap(tolist, metoo);
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){
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 delete_alternates(struct name *np)
1572 struct name *xp;
1573 NYD_ENTER;
1575 np = delname(np, ok_vlook(LOGNAME));
1577 if(a_nag_altnames != NULL){
1578 struct n_strlist *slp;
1580 for(slp = a_nag_altnames; slp != NULL; slp = slp->sl_next)
1581 np = delname(np, slp->sl_dat);
1584 if ((xp = lextract(ok_vlook(from), GEXTRA | GSKIN)) != NULL)
1585 while (xp != NULL) {
1586 np = delname(np, xp->n_name);
1587 xp = xp->n_flink;
1590 if ((xp = lextract(ok_vlook(replyto), GEXTRA | GSKIN)) != NULL)
1591 while (xp != NULL) {
1592 np = delname(np, xp->n_name);
1593 xp = xp->n_flink;
1596 if ((xp = extract(ok_vlook(sender), GEXTRA | GSKIN)) != NULL)
1597 while (xp != NULL) {
1598 np = delname(np, xp->n_name);
1599 xp = xp->n_flink;
1601 NYD_LEAVE;
1602 return np;
1605 FL int
1606 is_myname(char const *name)
1608 int rv = 1;
1609 struct name *xp;
1610 NYD_ENTER;
1612 if (_same_name(ok_vlook(LOGNAME), name))
1613 goto jleave;
1615 if(a_nag_altnames != NULL){
1616 struct n_strlist *slp;
1618 for(slp = a_nag_altnames; slp != NULL; slp = slp->sl_next)
1619 if(_same_name(slp->sl_dat, name))
1620 goto jleave;
1623 if ((xp = lextract(ok_vlook(from), GEXTRA | GSKIN)) != NULL)
1624 while (xp != NULL) {
1625 if (_same_name(xp->n_name, name))
1626 goto jleave;
1627 xp = xp->n_flink;
1630 if ((xp = lextract(ok_vlook(replyto), GEXTRA | GSKIN)) != NULL)
1631 while (xp != NULL) {
1632 if (_same_name(xp->n_name, name))
1633 goto jleave;
1634 xp = xp->n_flink;
1637 if ((xp = extract(ok_vlook(sender), GEXTRA | GSKIN)) != NULL)
1638 while (xp != NULL) {
1639 if (_same_name(xp->n_name, name))
1640 goto jleave;
1641 xp = xp->n_flink;
1643 rv = 0;
1644 jleave:
1645 NYD_LEAVE;
1646 return rv;
1649 FL int
1650 c_addrcodec(void *vp){
1651 struct n_addrguts ag;
1652 struct n_string s_b, *sp;
1653 size_t alen;
1654 int mode;
1655 char const **argv, *varname, *act, *cp;
1656 NYD_ENTER;
1658 sp = n_string_creat_auto(&s_b);
1659 argv = vp;
1660 varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
1662 act = *argv;
1663 for(cp = act; *cp != '\0' && !blankspacechar(*cp); ++cp)
1665 mode = 0;
1666 if(*act == '+')
1667 mode = 1, ++act;
1668 if(*act == '+')
1669 mode = 2, ++act;
1670 if(*act == '+')
1671 mode = 3, ++act;
1672 if(act >= cp)
1673 goto jesynopsis;
1674 alen = PTR2SIZE(cp - act);
1675 if(*cp != '\0')
1676 ++cp;
1678 /* C99 */{
1679 size_t i;
1681 i = strlen(cp);
1682 if(i <= UIZ_MAX / 4)
1683 i <<= 1;
1684 sp = n_string_reserve(sp, i);
1687 n_pstate_err_no = n_ERR_NONE;
1689 if(is_ascncaseprefix(act, "encode", alen)){
1690 /* This function cannot be a simple nalloc() wrapper even later on, since
1691 * we may need to turn any ", () or \ into quoted-pairs */
1692 char c;
1694 while((c = *cp++) != '\0'){
1695 if(((c == '(' || c == ')') && mode < 1) || (c == '"' && mode < 2) ||
1696 (c == '\\' && mode < 3))
1697 sp = n_string_push_c(sp, '\\');
1698 sp = n_string_push_c(sp, c);
1701 if(n_addrspec_with_guts(&ag, n_string_cp(sp), TRU1, TRU1) == NULL ||
1702 (ag.ag_n_flags & (NAME_ADDRSPEC_ISADDR | NAME_ADDRSPEC_INVALID)
1703 ) != NAME_ADDRSPEC_ISADDR){
1704 cp = sp->s_dat;
1705 n_pstate_err_no = n_ERR_INVAL;
1706 vp = NULL;
1707 }else{
1708 struct name *np;
1710 np = nalloc(ag.ag_input, GTO | GFULL | GSKIN);
1711 cp = np->n_fullname;
1713 }else if(mode == 0){
1714 if(is_ascncaseprefix(act, "decode", alen)){
1715 char c;
1717 while((c = *cp++) != '\0'){
1718 switch(c){
1719 case '(':
1720 sp = n_string_push_c(sp, '(');
1721 act = skip_comment(cp);
1722 if(--act > cp)
1723 sp = n_string_push_buf(sp, cp, PTR2SIZE(act - cp));
1724 sp = n_string_push_c(sp, ')');
1725 cp = ++act;
1726 break;
1727 case '"':
1728 while(*cp != '\0'){
1729 if((c = *cp++) == '"')
1730 break;
1731 if(c == '\\' && (c = *cp) != '\0')
1732 ++cp;
1733 sp = n_string_push_c(sp, c);
1735 break;
1736 default:
1737 if(c == '\\' && (c = *cp++) == '\0')
1738 break;
1739 sp = n_string_push_c(sp, c);
1740 break;
1743 cp = n_string_cp(sp);
1744 }else if(is_ascncaseprefix(act, "skin", alen)){
1745 /* Let's just use the is-single-address hack for this one, too.. */
1746 if(n_addrspec_with_guts(&ag, cp, TRU1, TRU1) == NULL ||
1747 (ag.ag_n_flags & (NAME_ADDRSPEC_ISADDR | NAME_ADDRSPEC_INVALID)
1748 ) != NAME_ADDRSPEC_ISADDR){
1749 n_pstate_err_no = n_ERR_INVAL;
1750 vp = NULL;
1751 }else{
1752 struct name *np;
1754 np = nalloc(ag.ag_input, GTO | GFULL | GSKIN);
1755 cp = np->n_name;
1757 }else
1758 goto jesynopsis;
1759 }else
1760 goto jesynopsis;
1762 if(varname == NULL){
1763 if(fprintf(n_stdout, "%s\n", cp) < 0){
1764 n_pstate_err_no = n_err_no;
1765 vp = NULL;
1767 }else if(!n_var_vset(varname, (uintptr_t)cp)){
1768 n_pstate_err_no = n_ERR_NOTSUP;
1769 vp = NULL;
1772 jleave:
1773 NYD_LEAVE;
1774 return (vp != NULL ? 0 : 1);
1775 jesynopsis:
1776 n_err(_("Synopsis: addrcodec: <[+[+[+]]]e[ncode]|d[ecode]|s[kin]> "
1777 "<rest-of-line>\n"));
1778 n_pstate_err_no = n_ERR_INVAL;
1779 vp = NULL;
1780 goto jleave;
1783 FL int
1784 c_commandalias(void *vp){
1785 struct group *gp;
1786 char const **argv, *ccp;
1787 int rv;
1788 NYD_ENTER;
1790 rv = 0;
1791 argv = vp;
1793 if((ccp = *argv) == NULL){
1794 _group_print_all(GT_COMMANDALIAS);
1795 goto jleave;
1798 /* Verify the name is a valid one, and not a command modifier */
1799 if(*ccp == '\0' || *n_cmd_isolate(ccp) != '\0' ||
1800 !asccasecmp(ccp, "ignerr") || !asccasecmp(ccp, "wysh") ||
1801 !asccasecmp(ccp, "vput")){
1802 n_err(_("`commandalias': not a valid command name: %s\n"),
1803 n_shexp_quote_cp(ccp, FAL0));
1804 rv = 1;
1805 goto jleave;
1808 if(argv[1] == NULL){
1809 if((gp = _group_find(GT_COMMANDALIAS, ccp)) != NULL)
1810 _group_print(gp, n_stdout);
1811 else{
1812 n_err(_("No such commandalias: %s\n"), n_shexp_quote_cp(ccp, FAL0));
1813 rv = 1;
1815 }else{
1816 /* Because one hardly ever redefines, anything is stored in one chunk */
1817 char *cp;
1818 size_t i, len;
1820 /* Delete the old one, if any; don't get fooled to remove them all */
1821 if(ccp[0] != '*' || ccp[1] != '\0')
1822 _group_del(GT_COMMANDALIAS, ccp);
1824 for(i = len = 0, ++argv; argv[i] != NULL; ++i)
1825 len += strlen(argv[i]) + 1;
1826 if(len == 0)
1827 len = 1;
1829 if((gp = _group_fetch(GT_COMMANDALIAS, ccp, len)) == NULL){
1830 n_err(_("Failed to create storage for commandalias: %s\n"),
1831 n_shexp_quote_cp(ccp, FAL0));
1832 rv = 1;
1833 }else{
1834 struct a_nag_cmd_alias *ncap;
1836 GP_TO_SUBCLASS(ncap, gp);
1837 GP_TO_SUBCLASS(cp, gp);
1838 cp += sizeof *ncap;
1839 ncap->ca_expand.s = cp;
1840 ncap->ca_expand.l = len - 1;
1842 for(len = 0; (ccp = *argv++) != NULL;)
1843 if((i = strlen(ccp)) > 0){
1844 if(len++ != 0)
1845 *cp++ = ' ';
1846 memcpy(cp, ccp, i);
1847 cp += i;
1849 *cp = '\0';
1852 jleave:
1853 NYD_LEAVE;
1854 return rv;
1857 FL int
1858 c_uncommandalias(void *vp){
1859 char **argv;
1860 int rv;
1861 NYD_ENTER;
1863 rv = 0;
1864 argv = vp;
1866 do if(!_group_del(GT_COMMANDALIAS, *argv)){
1867 n_err(_("No such `commandalias': %s\n"), n_shexp_quote_cp(*argv, FAL0));
1868 rv = 1;
1869 }while(*++argv != NULL);
1870 NYD_LEAVE;
1871 return rv;
1874 FL char const *
1875 n_commandalias_exists(char const *name, struct str const **expansion_or_null){
1876 struct group *gp;
1877 NYD_ENTER;
1879 if((gp = _group_find(GT_COMMANDALIAS, name)) != NULL){
1880 name = gp->g_id;
1882 if(expansion_or_null != NULL){
1883 struct a_nag_cmd_alias *ncap;
1885 GP_TO_SUBCLASS(ncap, gp);
1886 *expansion_or_null = &ncap->ca_expand;
1888 }else
1889 name = NULL;
1890 NYD_LEAVE;
1891 return name;
1894 FL bool_t
1895 n_alias_is_valid_name(char const *name){
1896 char c;
1897 char const *cp;
1898 bool_t rv;
1899 NYD2_ENTER;
1901 for(rv = TRU1, cp = name++; (c = *cp++) != '\0';)
1902 /* User names, plus things explicitly mentioned in Postfix aliases(5).
1903 * As an extension, allow period: [[:alnum:]_#:@.-]+$? */
1904 if(!alnumchar(c) && c != '_' && c != '-' &&
1905 c != '#' && c != ':' && c != '@' &&
1906 c != '.'){
1907 if(c == '$' && cp != name && *cp == '\0')
1908 break;
1909 rv = FAL0;
1910 break;
1912 NYD2_LEAVE;
1913 return rv;
1916 FL int
1917 c_alias(void *v)
1919 char const *ecp;
1920 char **argv;
1921 struct group *gp;
1922 int rv;
1923 NYD_ENTER;
1925 rv = 0;
1926 argv = v;
1927 n_UNINIT(ecp, NULL);
1929 if (*argv == NULL)
1930 _group_print_all(GT_ALIAS);
1931 else if (!n_alias_is_valid_name(*argv)) {
1932 ecp = N_("Not a valid alias name: %s\n");
1933 goto jerr;
1934 } else if (argv[1] == NULL) {
1935 if ((gp = _group_find(GT_ALIAS, *argv)) != NULL)
1936 _group_print(gp, n_stdout);
1937 else {
1938 ecp = N_("No such alias: %s\n");
1939 goto jerr;
1941 } else if ((gp = _group_fetch(GT_ALIAS, *argv, 0)) == NULL) {
1942 ecp = N_("Failed to create alias storage for: %s\n");
1943 jerr:
1944 n_err(V_(ecp), n_shexp_quote_cp(*argv, FAL0));
1945 rv = 1;
1946 } else {
1947 struct grp_names *gnp_tail, *gnp;
1948 struct grp_names_head *gnhp;
1950 GP_TO_SUBCLASS(gnhp, gp);
1952 if((gnp_tail = gnhp->gnh_head) != NULL)
1953 while((gnp = gnp_tail->gn_next) != NULL)
1954 gnp_tail = gnp;
1956 for(++argv; *argv != NULL; ++argv){
1957 size_t i;
1959 i = strlen(*argv) +1;
1960 gnp = smalloc(n_VSTRUCT_SIZEOF(struct grp_names, gn_id) + i);
1961 if(gnp_tail != NULL)
1962 gnp_tail->gn_next = gnp;
1963 else
1964 gnhp->gnh_head = gnp;
1965 gnp_tail = gnp;
1966 gnp->gn_next = NULL;
1967 memcpy(gnp->gn_id, *argv, i);
1970 NYD_LEAVE;
1971 return rv;
1974 FL int
1975 c_unalias(void *v)
1977 char **argv = v;
1978 int rv = 0;
1979 NYD_ENTER;
1981 do if (!_group_del(GT_ALIAS, *argv)) {
1982 n_err(_("No such alias: %s\n"), *argv);
1983 rv = 1;
1984 } while (*++argv != NULL);
1985 NYD_LEAVE;
1986 return rv;
1989 FL int
1990 c_mlist(void *v)
1992 int rv;
1993 NYD_ENTER;
1995 rv = _mlmux(GT_MLIST, v);
1996 NYD_LEAVE;
1997 return rv;
2000 FL int
2001 c_unmlist(void *v)
2003 int rv;
2004 NYD_ENTER;
2006 rv = _unmlmux(GT_MLIST, v);
2007 NYD_LEAVE;
2008 return rv;
2011 FL int
2012 c_mlsubscribe(void *v)
2014 int rv;
2015 NYD_ENTER;
2017 rv = _mlmux(GT_MLIST | GT_SUBSCRIBE, v);
2018 NYD_LEAVE;
2019 return rv;
2022 FL int
2023 c_unmlsubscribe(void *v)
2025 int rv;
2026 NYD_ENTER;
2028 rv = _unmlmux(GT_MLIST | GT_SUBSCRIBE, v);
2029 NYD_LEAVE;
2030 return rv;
2033 FL enum mlist_state
2034 is_mlist(char const *name, bool_t subscribed_only)
2036 struct group *gp;
2037 #ifdef HAVE_REGEX
2038 struct grp_regex **lpp, *grp;
2039 bool_t re2;
2040 #endif
2041 enum mlist_state rv;
2042 NYD_ENTER;
2044 gp = _group_find(GT_MLIST, name);
2045 rv = (gp != NULL) ? MLIST_KNOWN : MLIST_OTHER;
2046 if (rv == MLIST_KNOWN) {
2047 if (gp->g_type & GT_SUBSCRIBE)
2048 rv = MLIST_SUBSCRIBED;
2049 else if (subscribed_only)
2050 rv = MLIST_OTHER;
2051 /* Of course, if that is a regular expression it doesn't mean a thing */
2052 #ifdef HAVE_REGEX
2053 if (gp->g_type & GT_REGEX)
2054 rv = MLIST_OTHER;
2055 else
2056 #endif
2057 goto jleave;
2060 /* Not in the hashmap (as something matchable), walk the lists */
2061 #ifdef HAVE_REGEX
2062 re2 = FAL0;
2063 lpp = &_mlsub_regex;
2064 jregex_redo:
2065 if ((grp = *lpp) != NULL) {
2066 do if (regexec(&grp->gr_regex, name, 0,NULL, 0) != REG_NOMATCH) {
2067 /* Relink as the head of this list if the hit count of this group is
2068 * >= 25% of the average hit count */
2069 size_t i;
2070 if (!re2)
2071 i = ++_mlsub_hits / _mlsub_size;
2072 else
2073 i = ++_mlist_hits / _mlist_size;
2074 i >>= 2;
2076 if (++grp->gr_hits >= i && *lpp != grp && grp->gr_next != grp) {
2077 grp->gr_last->gr_next = grp->gr_next;
2078 grp->gr_next->gr_last = grp->gr_last;
2079 (grp->gr_last = (*lpp)->gr_last)->gr_next = grp;
2080 (grp->gr_next = *lpp)->gr_last = grp;
2081 *lpp = grp;
2083 rv = !re2 ? MLIST_SUBSCRIBED : MLIST_KNOWN;
2084 goto jleave;
2085 } while ((grp = grp->gr_next) != *lpp);
2087 if (!re2 && !subscribed_only) {
2088 re2 = TRU1;
2089 lpp = &_mlist_regex;
2090 goto jregex_redo;
2092 assert(rv == MLIST_OTHER);
2093 #endif
2095 jleave:
2096 NYD_LEAVE;
2097 return rv;
2100 FL int
2101 c_shortcut(void *v)
2103 struct group *gp;
2104 char **argv;
2105 int rv;
2106 NYD_ENTER;
2108 rv = 0;
2109 argv = v;
2111 if(*argv == NULL)
2112 _group_print_all(GT_SHORTCUT);
2113 else if(argv[1] == NULL){
2114 if((gp = _group_find(GT_SHORTCUT, *argv)) != NULL)
2115 _group_print(gp, n_stdout);
2116 else{
2117 n_err(_("No such shortcut: %s\n"), n_shexp_quote_cp(*argv, FAL0));
2118 rv = 1;
2120 }else for (; *argv != NULL; argv += 2) {
2121 /* Because one hardly ever redefines, anything is stored in one chunk */
2122 size_t l;
2123 char *cp;
2125 if (argv[1] == NULL) {
2126 n_err(_("Synopsis: shortcut: <shortcut> <expansion>\n"));
2127 rv = 1;
2128 break;
2130 if (_group_find(GT_SHORTCUT, *argv) != NULL)
2131 _group_del(GT_SHORTCUT, *argv);
2133 l = strlen(argv[1]) +1;
2134 if ((gp = _group_fetch(GT_SHORTCUT, *argv, l)) == NULL) {
2135 n_err(_("Failed to create storage for shortcut: %s\n"),
2136 n_shexp_quote_cp(*argv, FAL0));
2137 rv = 1;
2138 } else {
2139 GP_TO_SUBCLASS(cp, gp);
2140 memcpy(cp, argv[1], l);
2143 NYD_LEAVE;
2144 return rv;
2147 FL int
2148 c_unshortcut(void *v)
2150 char **argv = v;
2151 int rv = 0;
2152 NYD_ENTER;
2154 do if (!_group_del(GT_SHORTCUT, *argv)) {
2155 n_err(_("No such shortcut: %s\n"), *argv);
2156 rv = 1;
2157 } while (*++argv != NULL);
2158 NYD_LEAVE;
2159 return rv;
2162 FL char const *
2163 shortcut_expand(char const *str){
2164 struct group *gp;
2165 NYD_ENTER;
2167 if((gp = _group_find(GT_SHORTCUT, str)) != NULL)
2168 GP_TO_SUBCLASS(str, gp);
2169 else
2170 str = NULL;
2171 NYD_LEAVE;
2172 return str;
2175 FL int
2176 c_charsetalias(void *vp){
2177 struct group *gp;
2178 char **argv;
2179 int rv;
2180 NYD_ENTER;
2182 rv = 0;
2183 argv = vp;
2185 if(*argv == NULL)
2186 _group_print_all(GT_CHARSETALIAS);
2187 else if(argv[1] == NULL){
2188 if((gp = _group_find(GT_CHARSETALIAS, *argv)) != NULL)
2189 _group_print(gp, n_stdout);
2190 else{
2191 n_err(_("No such charsetalias: %s\n"), n_shexp_quote_cp(*argv, FAL0));
2192 rv = 1;
2194 }else for(; *argv != NULL; argv += 2){
2195 /* Because one hardly ever redefines, anything is stored in one chunk */
2196 char const *ccp;
2197 char *cp, c;
2198 size_t l;
2200 if(argv[1] == NULL){
2201 n_err(_("Synopsis: charsetalias: <charset> <charset-alias>\n"));
2202 rv = 1;
2203 break;
2206 /* Delete the old one, if any; don't get fooled to remove them all */
2207 ccp = argv[0];
2208 if(ccp[0] != '*' || ccp[1] != '\0')
2209 _group_del(GT_CHARSETALIAS, ccp);
2211 l = strlen(argv[1]) +1;
2212 if ((gp = _group_fetch(GT_CHARSETALIAS, ccp, l)) == NULL) {
2213 n_err(_("Failed to create storage for charsetalias: %s\n"),
2214 n_shexp_quote_cp(ccp, FAL0));
2215 rv = 1;
2216 } else {
2217 GP_TO_SUBCLASS(cp, gp);
2218 for(ccp = argv[1]; (c = *ccp++) != '\0';)
2219 *cp++ = lowerconv(c);
2220 *cp = '\0';
2223 NYD_LEAVE;
2224 return rv;
2227 FL int
2228 c_uncharsetalias(void *vp){
2229 char **argv;
2230 int rv;
2231 NYD_ENTER;
2233 rv = 0;
2234 argv = vp;
2236 do if(!_group_del(GT_CHARSETALIAS, *argv)){
2237 n_err(_("No such `charsetalias': %s\n"), n_shexp_quote_cp(*argv, FAL0));
2238 rv = 1;
2239 }while(*++argv != NULL);
2240 NYD_LEAVE;
2241 return rv;
2244 FL char const *
2245 n_charsetalias_expand(char const *cp){
2246 struct group *gp;
2247 size_t i;
2248 char const *cp_orig;
2249 NYD_ENTER;
2251 cp_orig = cp;
2253 for(i = 0; (gp = _group_find(GT_CHARSETALIAS, cp)) != NULL;){
2254 GP_TO_SUBCLASS(cp, gp);
2255 if(++i == 8) /* XXX Magic (same as for `ghost' expansion) */
2256 break;
2259 if(cp != cp_orig)
2260 cp = savestr(cp);
2261 NYD_LEAVE;
2262 return cp;
2265 FL int
2266 c_filetype(void *vp){ /* TODO support automatic chains: .tar.gz -> .gz + .tar */
2267 struct group *gp;
2268 char **argv; /* TODO While there: let ! prefix mean: direct execlp(2) */
2269 int rv;
2270 NYD_ENTER;
2272 rv = 0;
2273 argv = vp;
2275 if(*argv == NULL)
2276 _group_print_all(GT_FILETYPE);
2277 else if(argv[1] == NULL){
2278 if((gp = _group_find(GT_FILETYPE, *argv)) != NULL)
2279 _group_print(gp, n_stdout);
2280 else{
2281 n_err(_("No such filetype: %s\n"), n_shexp_quote_cp(*argv, FAL0));
2282 rv = 1;
2284 }else for(; *argv != NULL; argv += 3){
2285 /* Because one hardly ever redefines, anything is stored in one chunk */
2286 char const *ccp;
2287 char *cp, c;
2288 size_t llc, lsc;
2290 if(argv[1] == NULL || argv[2] == NULL){
2291 n_err(_("Synopsis: filetype: <extension> <load-cmd> <save-cmd>\n"));
2292 rv = 1;
2293 break;
2296 /* Delete the old one, if any; don't get fooled to remove them all */
2297 ccp = argv[0];
2298 if(ccp[0] != '*' || ccp[1] != '\0')
2299 _group_del(GT_FILETYPE, ccp);
2301 /* Lowercase it all (for display purposes) */
2302 cp = savestr(ccp);
2303 ccp = cp;
2304 while((c = *cp) != '\0')
2305 *cp++ = lowerconv(c);
2307 llc = strlen(argv[1]) +1;
2308 lsc = strlen(argv[2]) +1;
2309 if(UIZ_MAX - llc <= lsc)
2310 goto jenomem;
2312 if((gp = _group_fetch(GT_FILETYPE, ccp, llc + lsc)) == NULL){
2313 jenomem:
2314 n_err(_("Failed to create storage for filetype: %s\n"),
2315 n_shexp_quote_cp(argv[0], FAL0));
2316 rv = 1;
2317 }else{
2318 struct a_nag_file_type *nftp;
2320 GP_TO_SUBCLASS(nftp, gp);
2321 GP_TO_SUBCLASS(cp, gp);
2322 cp += sizeof *nftp;
2323 memcpy(nftp->nft_load.s = cp, argv[1], llc);
2324 cp += llc;
2325 nftp->nft_load.l = --llc;
2326 memcpy(nftp->nft_save.s = cp, argv[2], lsc);
2327 cp += lsc;
2328 nftp->nft_save.l = --lsc;
2331 NYD_LEAVE;
2332 return rv;
2335 FL int
2336 c_unfiletype(void *vp){
2337 char **argv;
2338 int rv;
2339 NYD_ENTER;
2341 rv = 0;
2342 argv = vp;
2344 do if(!_group_del(GT_FILETYPE, *argv)){
2345 n_err(_("No such `filetype': %s\n"), n_shexp_quote_cp(*argv, FAL0));
2346 rv = 1;
2347 }while(*++argv != NULL);
2348 NYD_LEAVE;
2349 return rv;
2352 FL bool_t
2353 n_filetype_trial(struct n_file_type *res_or_null, char const *file){
2354 struct stat stb;
2355 struct group_lookup gl;
2356 struct n_string s, *sp;
2357 struct group const *gp;
2358 ui32_t l;
2359 NYD2_ENTER;
2361 sp = n_string_creat_auto(&s);
2362 sp = n_string_assign_cp(sp, file);
2363 sp = n_string_push_c(sp, '.');
2364 l = sp->s_len;
2366 for(gp = _group_go_first(GT_FILETYPE, &gl); gp != NULL;
2367 gp = _group_go_next(&gl)){
2368 sp = n_string_trunc(sp, l);
2369 sp = n_string_push_buf(sp, gp->g_id,
2370 gp->g_subclass_off - gp->g_id_len_sub);
2372 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2373 if(res_or_null != NULL){
2374 struct a_nag_file_type *nftp;
2376 GP_TO_SUBCLASS(nftp, gp);
2377 res_or_null->ft_ext_dat = gp->g_id;
2378 res_or_null->ft_ext_len = gp->g_subclass_off - gp->g_id_len_sub;
2379 res_or_null->ft_load_dat = nftp->nft_load.s;
2380 res_or_null->ft_load_len = nftp->nft_load.l;
2381 res_or_null->ft_save_dat = nftp->nft_save.s;
2382 res_or_null->ft_save_len = nftp->nft_save.l;
2384 goto jleave; /* TODO after v15 legacy drop: break; */
2388 /* TODO v15 legacy code: automatic file hooks for .{bz2,gz,xz},
2389 * TODO but NOT supporting *file-hook-{load,save}-EXTENSION* */
2390 gp = (struct group*)0x1;
2392 sp = n_string_trunc(sp, l);
2393 sp = n_string_push_buf(sp, a_nag_OBSOLETE_xz.ft_ext_dat,
2394 a_nag_OBSOLETE_xz.ft_ext_len);
2395 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2396 n_OBSOLETE(".xz support will vanish, please use the `filetype' command");
2397 if(res_or_null != NULL)
2398 *res_or_null = a_nag_OBSOLETE_xz;
2399 goto jleave;
2402 sp = n_string_trunc(sp, l);
2403 sp = n_string_push_buf(sp, a_nag_OBSOLETE_gz.ft_ext_dat,
2404 a_nag_OBSOLETE_gz.ft_ext_len);
2405 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2406 n_OBSOLETE(".gz support will vanish, please use the `filetype' command");
2407 if(res_or_null != NULL)
2408 *res_or_null = a_nag_OBSOLETE_gz;
2409 goto jleave;
2412 sp = n_string_trunc(sp, l);
2413 sp = n_string_push_buf(sp, a_nag_OBSOLETE_bz2.ft_ext_dat,
2414 a_nag_OBSOLETE_bz2.ft_ext_len);
2415 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2416 n_OBSOLETE(".bz2 support will vanish, please use the `filetype' command");
2417 if(res_or_null != NULL)
2418 *res_or_null = a_nag_OBSOLETE_bz2;
2419 goto jleave;
2422 gp = NULL;
2424 jleave:
2425 NYD2_LEAVE;
2426 return (gp != NULL);
2429 FL bool_t
2430 n_filetype_exists(struct n_file_type *res_or_null, char const *file){
2431 char const *ext, *lext;
2432 NYD2_ENTER;
2434 if((ext = strrchr(file, '/')) != NULL)
2435 file = ++ext;
2437 for(lext = NULL; (ext = strchr(file, '.')) != NULL; lext = file = ext){
2438 struct group const *gp;
2440 if((gp = _group_find(GT_FILETYPE, ++ext)) != NULL){
2441 lext = ext;
2442 if(res_or_null != NULL){
2443 struct a_nag_file_type *nftp;
2445 GP_TO_SUBCLASS(nftp, gp);
2446 res_or_null->ft_ext_dat = gp->g_id;
2447 res_or_null->ft_ext_len = gp->g_subclass_off - gp->g_id_len_sub;
2448 res_or_null->ft_load_dat = nftp->nft_load.s;
2449 res_or_null->ft_load_len = nftp->nft_load.l;
2450 res_or_null->ft_save_dat = nftp->nft_save.s;
2451 res_or_null->ft_save_len = nftp->nft_save.l;
2453 goto jleave; /* TODO after v15 legacy drop: break; */
2457 /* TODO v15 legacy code: automatic file hooks for .{bz2,gz,xz},
2458 * TODO as well as supporting *file-hook-{load,save}-EXTENSION* */
2459 if(lext == NULL)
2460 goto jleave;
2462 if(!asccasecmp(lext, "xz")){
2463 n_OBSOLETE(".xz support will vanish, please use the `filetype' command");
2464 if(res_or_null != NULL)
2465 *res_or_null = a_nag_OBSOLETE_xz;
2466 goto jleave;
2467 }else if(!asccasecmp(lext, "gz")){
2468 n_OBSOLETE(".gz support will vanish, please use the `filetype' command");
2469 if(res_or_null != NULL)
2470 *res_or_null = a_nag_OBSOLETE_gz;
2471 goto jleave;
2472 }else if(!asccasecmp(lext, "bz2")){
2473 n_OBSOLETE(".bz2 support will vanish, please use the `filetype' command");
2474 if(res_or_null != NULL)
2475 *res_or_null = a_nag_OBSOLETE_bz2;
2476 goto jleave;
2477 }else{
2478 char const *cload, *csave;
2479 char *vbuf;
2480 size_t l;
2482 #undef a_X1
2483 #define a_X1 "file-hook-load-"
2484 #undef a_X2
2485 #define a_X2 "file-hook-save-"
2486 l = strlen(lext);
2487 vbuf = n_lofi_alloc(l + n_MAX(sizeof(a_X1), sizeof(a_X2)));
2489 memcpy(vbuf, a_X1, sizeof(a_X1) -1);
2490 memcpy(&vbuf[sizeof(a_X1) -1], lext, l);
2491 vbuf[sizeof(a_X1) -1 + l] = '\0';
2492 cload = n_var_vlook(vbuf, FAL0);
2494 memcpy(vbuf, a_X2, sizeof(a_X2) -1);
2495 memcpy(&vbuf[sizeof(a_X2) -1], lext, l);
2496 vbuf[sizeof(a_X2) -1 + l] = '\0';
2497 csave = n_var_vlook(vbuf, FAL0);
2499 #undef a_X2
2500 #undef a_X1
2501 n_lofi_free(vbuf);
2503 if((csave != NULL) | (cload != NULL)){
2504 n_OBSOLETE("*file-hook-{load,save}-EXTENSION* will vanish, "
2505 "please use the `filetype' command");
2507 if(((csave != NULL) ^ (cload != NULL)) == 0){
2508 if(res_or_null != NULL){
2509 res_or_null->ft_ext_dat = lext;
2510 res_or_null->ft_ext_len = l;
2511 res_or_null->ft_load_dat = cload;
2512 res_or_null->ft_load_len = strlen(cload);
2513 res_or_null->ft_save_dat = csave;
2514 res_or_null->ft_save_len = strlen(csave);
2516 goto jleave;
2517 }else
2518 n_alert(_("Incomplete *file-hook-{load,save}-EXTENSION* for: .%s"),
2519 lext);
2523 lext = NULL;
2525 jleave:
2526 NYD2_LEAVE;
2527 return (lext != NULL);
2530 /* s-it-mode */