Generalize and straigthen handling of the soft-exit-status *!*
[s-mailx.git] / nam_a_grp.c
blobac83ed86ea30ff7c66288e9f9eea8373b200d6b3
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Name lists, alternates and groups: aliases, mailing lists, shortcuts.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2017 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
6 */
7 /*
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
35 #undef n_FILE
36 #define n_FILE nam_a_grp
38 #ifndef HAVE_AMALGAMATION
39 # include "nail.h"
40 #endif
42 enum group_type {
43 /* Main types (bits not values for easier testing only) */
44 GT_ALIAS = 1<< 0,
45 GT_MLIST = 1<< 1,
46 GT_SHORTCUT = 1<< 2,
47 GT_MASK = GT_ALIAS | GT_MLIST | GT_SHORTCUT,
49 /* Subtype bits and flags */
50 GT_SUBSCRIBE = 1<< 4,
51 GT_REGEX = 1<< 5,
53 /* Extended type mask to be able to reflect what we really have; i.e., mlist
54 * can have GT_REGEX if they are subscribed or not, but `mlsubscribe' should
55 * print out only GT_MLIST which have the GT_SUBSCRIBE attribute set */
56 GT_PRINT_MASK = GT_MASK | GT_SUBSCRIBE
59 struct group {
60 struct group *g_next;
61 size_t g_subclass_off; /* of "subclass" in .g_id (if any) */
62 ui8_t g_type; /* enum group_type */
63 /* Identifying name, of variable size. Dependent on actual "subtype" more
64 * data follows thereafter, but note this is always used (i.e., for regular
65 * expression entries this is still set to the plain string) */
66 char g_id[n_VFIELD_SIZE(-1)];
68 #define GP_TO_SUBCLASS(X,G) \
69 do {\
70 union __group_subclass {void *gs_vp; char *gs_cp;} __gs__;\
71 __gs__.gs_cp = (char*)n_UNCONST(G) + (G)->g_subclass_off;\
72 (X) = __gs__.gs_vp;\
73 } while (0)
75 struct grp_names_head {
76 struct grp_names *gnh_head;
79 struct grp_names {
80 struct grp_names *gn_next;
81 char gn_id[n_VFIELD_SIZE(0)];
84 #ifdef HAVE_REGEX
85 struct grp_regex {
86 struct grp_regex *gr_last;
87 struct grp_regex *gr_next;
88 struct group *gr_mygroup; /* xxx because lists use grp_regex*! ?? */
89 size_t gr_hits; /* Number of times this group matched */
90 regex_t gr_regex;
92 #endif
94 struct group_lookup {
95 struct group **gl_htable;
96 struct group **gl_slot;
97 struct group *gl_slot_last;
98 struct group *gl_group;
101 /* List of alternate names of user */
102 static char **a_nag_altnames;
104 /* `alias' */
105 static struct group *_alias_heads[HSHSIZE]; /* TODO dynamic hash */
107 /* `mlist', `mlsubscribe'. Anything is stored in the hashmap.. */
108 static struct group *_mlist_heads[HSHSIZE]; /* TODO dynamic hash */
110 /* ..but entries which have GT_REGEX set are false lookups and will really be
111 * accessed via sequential lists instead, which are type-specific for better
112 * performance, but also to make it possible to have ".*@xy.org" as a mlist
113 * and "(one|two)@xy.org" as a mlsubscription.
114 * These lists use a bit of QOS optimization in that a matching group will
115 * become relinked as the new list head if its hit count is
116 * (>= ((xy_hits / _xy_size) >> 2))
117 * Note that the hit counts only count currently linked in nodes.. */
118 #ifdef HAVE_REGEX
119 static struct grp_regex *_mlist_regex, *_mlsub_regex;
120 static size_t _mlist_size, _mlist_hits, _mlsub_size, _mlsub_hits;
121 #endif
123 /* `shortcut' */
124 static struct group *_shortcut_heads[HSHSIZE]; /* TODO dynamic hash */
126 /* Same name, while taking care for *allnet*? */
127 static bool_t _same_name(char const *n1, char const *n2);
129 /* Delete the given name from a namelist */
130 static struct name * delname(struct name *np, char const *name);
132 /* Put another node onto a list of names and return the list */
133 static struct name * put(struct name *list, struct name *node);
135 /* Grab a single name (liberal name) */
136 static char const * yankname(char const *ap, char *wbuf,
137 char const *separators, int keepcomms);
139 /* Extraction multiplexer that splits an input line to names */
140 static struct name * _extract1(char const *line, enum gfield ntype,
141 char const *separators, bool_t keepcomms);
143 /* Recursively expand a alias name. Limit expansion to some fixed level.
144 * Direct recursion is not expanded for convenience */
145 static struct name * _gexpand(size_t level, struct name *nlist,
146 struct group *gp, bool_t metoo, int ntype);
148 /* Lookup a group, return it or NULL, fill in glp anyway */
149 static struct group * _group_lookup(enum group_type gt,
150 struct group_lookup *glp, char const *id);
152 /* Easier-to-use wrapper around _group_lookup() */
153 static struct group * _group_find(enum group_type gt, char const *id);
155 /* Iteration: go to the first group, which also inits the iterator. A valid
156 * iterator can be stepped via _next(). A NULL return means no (more) groups
157 * to be iterated exist, in which case only glp->gl_group is set (NULL) */
158 static struct group * _group_go_first(enum group_type gt,
159 struct group_lookup *glp);
160 static struct group * _group_go_next(struct group_lookup *glp);
162 /* Fetch the group id, create it as necessary */
163 static struct group * _group_fetch(enum group_type gt, char const *id,
164 size_t addsz);
166 /* "Intelligent" delete which handles a "*" id, too;
167 * returns a true boolean if a group was deleted, and always succeeds for "*" */
168 static bool_t _group_del(enum group_type gt, char const *id);
170 static struct group * __group_del(struct group_lookup *glp);
171 static void __names_del(struct group *gp);
173 /* Print all groups of the given type, alphasorted */
174 static void _group_print_all(enum group_type gt);
176 static int __group_print_qsorter(void const *a, void const *b);
178 /* Really print a group, actually. Return number of written lines */
179 static size_t _group_print(struct group const *gp, FILE *fo);
181 /* Multiplexers for list and subscribe commands */
182 static int _mlmux(enum group_type gt, char **argv);
183 static int _unmlmux(enum group_type gt, char **argv);
185 /* Relinkers for the sequential match lists */
186 #ifdef HAVE_REGEX
187 static void _mlmux_linkin(struct group *gp);
188 static void _mlmux_linkout(struct group *gp);
189 # define _MLMUX_LINKIN(GP) \
190 do if ((GP)->g_type & GT_REGEX) _mlmux_linkin(GP); while (0)
191 # define _MLMUX_LINKOUT(GP) \
192 do if ((GP)->g_type & GT_REGEX) _mlmux_linkout(GP); while (0)
193 #else
194 # define _MLMUX_LINKIN(GP)
195 # define _MLMUX_LINKOUT(GP)
196 #endif
198 static bool_t
199 _same_name(char const *n1, char const *n2)
201 bool_t rv = FAL0;
202 char c1, c2;
203 NYD_ENTER;
205 if (ok_blook(allnet)) {
206 do {
207 c1 = *n1++;
208 c2 = *n2++;
209 c1 = lowerconv(c1);
210 c2 = lowerconv(c2);
211 if (c1 != c2)
212 goto jleave;
213 } while (c1 != '\0' && c2 != '\0' && c1 != '@' && c2 != '@');
214 rv = 1;
215 } else
216 rv = !asccasecmp(n1, n2);
217 jleave:
218 NYD_LEAVE;
219 return rv;
222 static struct name *
223 delname(struct name *np, char const *name)
225 struct name *p;
226 NYD_ENTER;
228 for (p = np; p != NULL; p = p->n_flink)
229 if (_same_name(p->n_name, name)) {
230 if (p->n_blink == NULL) {
231 if (p->n_flink != NULL)
232 p->n_flink->n_blink = NULL;
233 np = p->n_flink;
234 continue;
236 if (p->n_flink == NULL) {
237 if (p->n_blink != NULL)
238 p->n_blink->n_flink = NULL;
239 continue;
241 p->n_blink->n_flink = p->n_flink;
242 p->n_flink->n_blink = p->n_blink;
244 NYD_LEAVE;
245 return np;
248 static struct name *
249 put(struct name *list, struct name *node)
251 NYD_ENTER;
252 node->n_flink = list;
253 node->n_blink = NULL;
254 if (list != NULL)
255 list->n_blink = node;
256 NYD_LEAVE;
257 return node;
260 static char const *
261 yankname(char const *ap, char *wbuf, char const *separators, int keepcomms)
263 char const *cp;
264 char *wp, c, inquote, lc, lastsp;
265 NYD_ENTER;
267 *(wp = wbuf) = '\0';
269 /* Skip over intermediate list trash, as in ".org> , <xy@zz.org>" */
270 for (c = *ap; blankchar(c) || c == ','; c = *++ap)
272 if (c == '\0') {
273 cp = NULL;
274 goto jleave;
277 /* Parse a full name: TODO RFC 5322
278 * - Keep everything in quotes, liberal handle *quoted-pair*s therein
279 * - Skip entire (nested) comments
280 * - In non-quote, non-comment, join adjacent space to a single SP
281 * - Understand separators only in non-quote, non-comment context,
282 * and only if not part of a *quoted-pair* (XXX too liberal) */
283 cp = ap;
284 for (inquote = lc = lastsp = 0;; lc = c, ++cp) {
285 c = *cp;
286 if (c == '\0')
287 break;
288 if (c == '\\')
289 goto jwpwc;
290 if (c == '"') {
291 if (lc != '\\')
292 inquote = !inquote;
293 #if 0 /* TODO when doing real RFC 5322 parsers - why have i done this? */
294 else
295 --wp;
296 #endif
297 goto jwpwc;
299 if (inquote || lc == '\\') {
300 jwpwc:
301 *wp++ = c;
302 lastsp = 0;
303 continue;
305 if (c == '(') {
306 ap = cp;
307 cp = skip_comment(cp + 1);
308 if (keepcomms)
309 while (ap < cp)
310 *wp++ = *ap++;
311 --cp;
312 lastsp = 0;
313 continue;
315 if (strchr(separators, c) != NULL)
316 break;
318 lc = lastsp;
319 lastsp = blankchar(c);
320 if (!lastsp || !lc)
321 *wp++ = c;
323 if (blankchar(lc))
324 --wp;
326 *wp = '\0';
327 jleave:
328 NYD_LEAVE;
329 return cp;
332 static struct name *
333 _extract1(char const *line, enum gfield ntype, char const *separators,
334 bool_t keepcomms)
336 struct name *topp, *np, *t;
337 char const *cp;
338 char *nbuf;
339 NYD_ENTER;
341 topp = NULL;
342 if (line == NULL || *line == '\0')
343 goto jleave;
345 np = NULL;
346 cp = line;
347 nbuf = smalloc(strlen(line) +1);
348 while ((cp = yankname(cp, nbuf, separators, keepcomms)) != NULL) {
349 t = nalloc(nbuf, ntype);
350 if (topp == NULL)
351 topp = t;
352 else
353 np->n_flink = t;
354 t->n_blink = np;
355 np = t;
357 free(nbuf);
358 jleave:
359 NYD_LEAVE;
360 return topp;
363 static struct name *
364 _gexpand(size_t level, struct name *nlist, struct group *gp, bool_t metoo,
365 int ntype)
367 char const *logname;
368 struct grp_names_head *gnhp;
369 struct grp_names *gnp;
370 NYD_ENTER;
372 if (UICMP(z, level++, >, MAXEXP)) {
373 n_err(_("Expanding alias to depth larger than %d\n"), MAXEXP);
374 goto jleave;
377 GP_TO_SUBCLASS(gnhp, gp);
378 logname = ok_vlook(LOGNAME);
379 for (gnp = gnhp->gnh_head; gnp != NULL; gnp = gnp->gn_next) {
380 char *cp;
381 struct group *ngp;
383 /* FIXME we do not really support leading backslash quoting do we??? */
384 if (*(cp = gnp->gn_id) == '\\' || !strcmp(cp, gp->g_id))
385 goto jquote;
387 if ((ngp = _group_find(GT_ALIAS, cp)) != NULL) {
388 /* For S-nail(1), the "alias" may *be* the sender in that a name maps
389 * to a full address specification; aliases cannot be empty */
390 struct grp_names_head *ngnhp;
391 GP_TO_SUBCLASS(ngnhp, ngp);
393 assert(ngnhp->gnh_head != NULL);
394 if (metoo || ngnhp->gnh_head->gn_next != NULL ||
395 !_same_name(cp, logname))
396 nlist = _gexpand(level, nlist, ngp, metoo, ntype);
397 continue;
400 /* Here we should allow to expand to itself if only person in alias */
401 jquote:
402 if (metoo || gnhp->gnh_head->gn_next == NULL || !_same_name(cp, logname))
403 nlist = put(nlist, nalloc(cp, ntype | GFULL));
405 jleave:
406 NYD_LEAVE;
407 return nlist;
410 static struct group *
411 _group_lookup(enum group_type gt, struct group_lookup *glp, char const *id)
413 struct group *lgp, *gp;
414 NYD_ENTER;
416 gt &= GT_MASK;
417 lgp = NULL;
418 gp = *(glp->gl_htable = glp->gl_slot =
419 ((gt & GT_ALIAS ? _alias_heads :
420 (gt & GT_MLIST ? _mlist_heads :
421 (/*gt & GT_SHORTCUT ?*/ _shortcut_heads /*: NULL */))) +
422 torek_hash(id) % HSHSIZE));
424 for (; gp != NULL; lgp = gp, gp = gp->g_next)
425 if ((gp->g_type & gt) && *gp->g_id == *id && !strcmp(gp->g_id, id))
426 break;
428 glp->gl_slot_last = lgp;
429 glp->gl_group = gp;
430 NYD_LEAVE;
431 return gp;
434 static struct group *
435 _group_find(enum group_type gt, char const *id)
437 struct group_lookup gl;
438 struct group *gp;
439 NYD_ENTER;
441 gp = _group_lookup(gt, &gl, id);
442 NYD_LEAVE;
443 return gp;
446 static struct group *
447 _group_go_first(enum group_type gt, struct group_lookup *glp)
449 struct group **gpa, *gp;
450 size_t i;
451 NYD_ENTER;
453 for (glp->gl_htable = gpa = (gt & GT_ALIAS ? _alias_heads :
454 (gt & GT_MLIST ? _mlist_heads :
455 (/*gt & GT_SHORTCUT ?*/ _shortcut_heads /*: NULL */))), i = 0;
456 i < HSHSIZE; ++gpa, ++i)
457 if ((gp = *gpa) != NULL) {
458 glp->gl_slot = gpa;
459 glp->gl_group = gp;
460 goto jleave;
463 glp->gl_group = gp = NULL;
464 jleave:
465 glp->gl_slot_last = NULL;
466 NYD_LEAVE;
467 return gp;
470 static struct group *
471 _group_go_next(struct group_lookup *glp)
473 struct group *gp, **gpa;
474 NYD_ENTER;
476 if ((gp = glp->gl_group->g_next) != NULL)
477 glp->gl_slot_last = glp->gl_group;
478 else {
479 glp->gl_slot_last = NULL;
480 for (gpa = glp->gl_htable + HSHSIZE; ++glp->gl_slot < gpa;)
481 if ((gp = *glp->gl_slot) != NULL)
482 break;
484 glp->gl_group = gp;
485 NYD_LEAVE;
486 return gp;
489 static struct group *
490 _group_fetch(enum group_type gt, char const *id, size_t addsz)
492 struct group_lookup gl;
493 struct group *gp;
494 size_t l, i;
495 NYD_ENTER;
497 if ((gp = _group_lookup(gt, &gl, id)) != NULL)
498 goto jleave;
500 l = strlen(id) +1;
501 i = n_ALIGN(n_VSTRUCT_SIZEOF(struct group, g_id) + l);
502 switch (gt & GT_MASK) {
503 case GT_ALIAS:
504 addsz = sizeof(struct grp_names_head);
505 break;
506 case GT_MLIST:
507 #ifdef HAVE_REGEX
508 if (n_is_maybe_regex(id)) {
509 addsz = sizeof(struct grp_regex);
510 gt |= GT_REGEX;
512 #endif
513 case GT_SHORTCUT:
514 default:
515 break;
518 gp = smalloc(i + addsz);
519 gp->g_subclass_off = i;
520 gp->g_type = gt;
521 memcpy(gp->g_id, id, l);
523 if (gt & GT_ALIAS) {
524 struct grp_names_head *gnhp;
526 GP_TO_SUBCLASS(gnhp, gp);
527 gnhp->gnh_head = NULL;
529 #ifdef HAVE_REGEX
530 else if (/*(gt & GT_MLIST) &&*/ gt & GT_REGEX) {
531 int s;
532 struct grp_regex *grp;
533 GP_TO_SUBCLASS(grp, gp);
535 if((s = regcomp(&grp->gr_regex, id,
536 REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0){
537 n_err(_("Invalid regular expression: %s: %s\n"),
538 n_shexp_quote_cp(id, FAL0), n_regex_err_to_str(&grp->gr_regex, s));
539 free(gp);
540 gp = NULL;
541 goto jleave;
543 grp->gr_mygroup = gp;
544 _mlmux_linkin(gp);
546 #endif
548 gp->g_next = *gl.gl_slot;
549 *gl.gl_slot = gp;
550 jleave:
551 NYD_LEAVE;
552 return gp;
555 static bool_t
556 _group_del(enum group_type gt, char const *id)
558 enum group_type xgt = gt & GT_MASK;
559 struct group_lookup gl;
560 struct group *gp;
561 NYD_ENTER;
563 /* Delete 'em all? */
564 if (id[0] == '*' && id[1] == '\0') {
565 for (gp = _group_go_first(gt, &gl); gp != NULL;)
566 gp = (gp->g_type & xgt) ? __group_del(&gl) : _group_go_next(&gl);
567 gp = (struct group*)TRU1;
568 } else if ((gp = _group_lookup(gt, &gl, id)) != NULL) {
569 if (gp->g_type & xgt)
570 __group_del(&gl);
571 else
572 gp = NULL;
574 NYD_LEAVE;
575 return (gp != NULL);
578 static struct group *
579 __group_del(struct group_lookup *glp)
581 struct group *x, *gp;
582 NYD_ENTER;
584 /* Overly complicated: link off this node, step ahead to next.. */
585 x = glp->gl_group;
586 if ((gp = glp->gl_slot_last) != NULL) {
587 gp = (gp->g_next = x->g_next);
588 } else {
589 glp->gl_slot_last = NULL;
590 gp = (*glp->gl_slot = x->g_next);
592 if (gp == NULL) {
593 struct group **gpa = glp->gl_htable + HSHSIZE;
595 while (++glp->gl_slot < gpa)
596 if ((gp = *glp->gl_slot) != NULL)
597 break;
600 glp->gl_group = gp;
602 if (x->g_type & GT_ALIAS)
603 __names_del(x);
604 #ifdef HAVE_REGEX
605 else if (/*(x->g_type & GT_MLIST) &&*/ x->g_type & GT_REGEX) {
606 struct grp_regex *grp;
607 GP_TO_SUBCLASS(grp, x);
609 regfree(&grp->gr_regex);
610 _mlmux_linkout(x);
612 #endif
614 free(x);
615 NYD_LEAVE;
616 return gp;
619 static void
620 __names_del(struct group *gp)
622 struct grp_names_head *gnhp;
623 struct grp_names *gnp;
624 NYD_ENTER;
626 GP_TO_SUBCLASS(gnhp, gp);
627 for (gnp = gnhp->gnh_head; gnp != NULL;) {
628 struct grp_names *x = gnp;
629 gnp = gnp->gn_next;
630 free(x);
632 NYD_LEAVE;
635 static void
636 _group_print_all(enum group_type gt)
638 enum group_type xgt;
639 struct group **gpa;
640 struct group const *gp;
641 ui32_t h, i;
642 char const **ida;
643 FILE *fp;
644 size_t lines;
645 NYD_ENTER;
647 xgt = gt & GT_PRINT_MASK;
648 gpa = (xgt & GT_ALIAS ? _alias_heads
649 : (xgt & GT_MLIST ? _mlist_heads
650 : (/*xgt & GT_SHORTCUT ?*/ _shortcut_heads
651 /*: NULL */)));
653 for (h = 0, i = 1; h < HSHSIZE; ++h)
654 for (gp = gpa[h]; gp != NULL; gp = gp->g_next)
655 if ((gp->g_type & xgt) == xgt)
656 ++i;
657 ida = salloc(i * sizeof *ida);
659 for (i = h = 0; h < HSHSIZE; ++h)
660 for (gp = gpa[h]; gp != NULL; gp = gp->g_next)
661 if ((gp->g_type & xgt) == xgt)
662 ida[i++] = gp->g_id;
663 ida[i] = NULL;
665 if (i > 1)
666 qsort(ida, i, sizeof *ida, &__group_print_qsorter);
668 if ((fp = Ftmp(NULL, "prgroup", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL)
669 fp = n_stdout;
670 lines = 0;
672 for (i = 0; ida[i] != NULL; ++i)
673 lines += _group_print(_group_find(gt, ida[i]), fp);
674 #ifdef HAVE_REGEX
675 if (gt & GT_MLIST) {
676 if (gt & GT_SUBSCRIBE)
677 i = (ui32_t)_mlsub_size, h = (ui32_t)_mlsub_hits;
678 else
679 i = (ui32_t)_mlist_size, h = (ui32_t)_mlist_hits;
680 if (i > 0 && (n_poption & n_PO_D_V)){
681 fprintf(fp, _("# %s list regex(7) total: %u entries, %u hits\n"),
682 (gt & GT_SUBSCRIBE ? _("Subscribed") : _("Non-subscribed")),
683 i, h);
684 ++lines;
687 #endif
689 if (fp != n_stdout) {
690 page_or_print(fp, lines);
691 Fclose(fp);
693 NYD_LEAVE;
696 static int
697 __group_print_qsorter(void const *a, void const *b)
699 int rv;
700 NYD_ENTER;
702 rv = strcmp(*(char**)n_UNCONST(a), *(char**)n_UNCONST(b));
703 NYD_LEAVE;
704 return rv;
707 static size_t
708 _group_print(struct group const *gp, FILE *fo)
710 char const *cp;
711 size_t rv;
712 NYD_ENTER;
714 rv = 1;
716 if (gp->g_type & GT_ALIAS) {
717 struct grp_names_head *gnhp;
718 struct grp_names *gnp;
720 fprintf(fo, "alias %s ", gp->g_id);
722 GP_TO_SUBCLASS(gnhp, gp);
723 if ((gnp = gnhp->gnh_head) != NULL) { /* xxx always 1+ entries */
724 do {
725 struct grp_names *x = gnp;
726 gnp = gnp->gn_next;
727 fprintf(fo, " \"%s\"", string_quote(x->gn_id)); /* TODO shexp */
728 } while (gnp != NULL);
730 putc('\n', fo);
731 } else if (gp->g_type & GT_MLIST) {
732 #ifdef HAVE_REGEX
733 if ((gp->g_type & GT_REGEX) && (n_poption & n_PO_D_V)){
734 size_t i;
735 struct grp_regex *grp,
736 *lp = (gp->g_type & GT_SUBSCRIBE ? _mlsub_regex : _mlist_regex);
738 GP_TO_SUBCLASS(grp, gp);
739 for (i = 1; lp != grp; lp = lp->gr_next)
740 ++i;
741 fprintf(fo, "# regex(7): hits %" PRIuZ ", sort %" PRIuZ ".\n ",
742 grp->gr_hits, i);
743 ++rv;
745 #endif
747 fprintf(fo, "wysh %s %s\n",
748 (gp->g_type & GT_SUBSCRIBE ? "mlsubscribe" : "mlist"),
749 n_shexp_quote_cp(gp->g_id, TRU1));
750 } else if (gp->g_type & GT_SHORTCUT) {
751 GP_TO_SUBCLASS(cp, gp);
752 fprintf(fo, "wysh shortcut %s %s\n",
753 gp->g_id, n_shexp_quote_cp(cp, TRU1));
756 NYD_LEAVE;
757 return rv;
760 static int
761 _mlmux(enum group_type gt, char **argv)
763 struct group *gp;
764 int rv = 0;
765 NYD_ENTER;
767 if (*argv == NULL)
768 _group_print_all(gt);
769 else do {
770 if ((gp = _group_find(gt, *argv)) != NULL) {
771 if (gt & GT_SUBSCRIBE) {
772 if (!(gp->g_type & GT_SUBSCRIBE)) {
773 _MLMUX_LINKOUT(gp);
774 gp->g_type |= GT_SUBSCRIBE;
775 _MLMUX_LINKIN(gp);
776 } else {
777 n_err(_("Mailing-list already `mlsubscribe'd: %s\n"),
778 *argv);
779 rv = 1;
781 } else {
782 n_err(_("Mailing-list already `mlist'ed: %s\n"), *argv);
783 rv = 1;
785 } else
786 _group_fetch(gt, *argv, 0);
787 } while (*++argv != NULL);
789 NYD_LEAVE;
790 return rv;
793 static int
794 _unmlmux(enum group_type gt, char **argv)
796 struct group *gp;
797 int rv = 0;
798 NYD_ENTER;
800 for (; *argv != NULL; ++argv) {
801 if (gt & GT_SUBSCRIBE) {
802 struct group_lookup gl;
803 bool_t isaster;
805 if (!(isaster = (**argv == '*')))
806 gp = _group_find(gt, *argv);
807 else if ((gp = _group_go_first(gt, &gl)) == NULL)
808 continue;
809 else if (gp != NULL && !(gp->g_type & GT_SUBSCRIBE))
810 goto jaster_entry;
812 if (gp != NULL) {
813 jaster_redo:
814 if (gp->g_type & GT_SUBSCRIBE) {
815 _MLMUX_LINKOUT(gp);
816 gp->g_type &= ~GT_SUBSCRIBE;
817 _MLMUX_LINKIN(gp);
818 if (isaster) {
819 jaster_entry:
820 while ((gp = _group_go_next(&gl)) != NULL &&
821 !(gp->g_type & GT_SUBSCRIBE))
823 if (gp != NULL)
824 goto jaster_redo;
826 } else {
827 n_err(_("Mailing-list not `mlsubscribe'd: %s\n"),
828 n_shexp_quote_cp(*argv, FAL0));
829 rv = 1;
831 continue;
833 } else if (_group_del(gt, *argv))
834 continue;
835 n_err(_("No such mailing-list: %s\n"), n_shexp_quote_cp(*argv, FAL0));
836 rv = 1;
838 NYD_LEAVE;
839 return rv;
842 #ifdef HAVE_REGEX
843 static void
844 _mlmux_linkin(struct group *gp)
846 struct grp_regex **lpp, *grp, *lhp;
847 NYD_ENTER;
849 if (gp->g_type & GT_SUBSCRIBE) {
850 lpp = &_mlsub_regex;
851 ++_mlsub_size;
852 } else {
853 lpp = &_mlist_regex;
854 ++_mlist_size;
857 GP_TO_SUBCLASS(grp, gp);
858 if ((lhp = *lpp) != NULL) {
859 (grp->gr_last = lhp->gr_last)->gr_next = grp;
860 (grp->gr_next = lhp)->gr_last = grp;
861 } else
862 *lpp = grp->gr_last = grp->gr_next = grp;
863 grp->gr_hits = 0;
864 NYD_LEAVE;
867 static void
868 _mlmux_linkout(struct group *gp)
870 struct grp_regex *grp, **lpp;
871 NYD_ENTER;
873 GP_TO_SUBCLASS(grp, gp);
875 if (gp->g_type & GT_SUBSCRIBE) {
876 lpp = &_mlsub_regex;
877 --_mlsub_size;
878 _mlsub_hits -= grp->gr_hits;
879 } else {
880 lpp = &_mlist_regex;
881 --_mlist_size;
882 _mlist_hits -= grp->gr_hits;
885 if (grp->gr_next == grp)
886 *lpp = NULL;
887 else {
888 (grp->gr_last->gr_next = grp->gr_next)->gr_last = grp->gr_last;
889 if (*lpp == grp)
890 *lpp = grp->gr_next;
892 NYD_LEAVE;
894 #endif /* HAVE_REGEX */
896 FL struct name *
897 nalloc(char const *str, enum gfield ntype)
899 struct n_addrguts ag;
900 struct str in, out;
901 struct name *np;
902 NYD_ENTER;
903 assert(!(ntype & GFULLEXTRA) || (ntype & GFULL) != 0);
905 str = n_addrspec_with_guts(&ag, str,
906 ((ntype & (GFULL | GSKIN | GREF)) != 0));
907 if(str == NULL){
909 np = NULL; TODO We cannot return NULL,
910 goto jleave; TODO thus handle failures in here!
912 str = ag.ag_input;
915 if (!(ag.ag_n_flags & NAME_NAME_SALLOC)) {
916 ag.ag_n_flags |= NAME_NAME_SALLOC;
917 np = salloc(sizeof(*np) + ag.ag_slen +1);
918 memcpy(np + 1, ag.ag_skinned, ag.ag_slen +1);
919 ag.ag_skinned = (char*)(np + 1);
920 } else
921 np = salloc(sizeof *np);
923 np->n_flink = NULL;
924 np->n_blink = NULL;
925 np->n_type = ntype;
926 np->n_flags = 0;
928 np->n_fullname = np->n_name = ag.ag_skinned;
929 np->n_fullextra = NULL;
930 np->n_flags = ag.ag_n_flags;
932 if (ntype & GFULL) {
933 if (ag.ag_ilen == ag.ag_slen
934 #ifdef HAVE_IDNA
935 && !(ag.ag_n_flags & NAME_IDNA)
936 #endif
938 goto jleave;
939 if (ag.ag_n_flags & NAME_ADDRSPEC_ISFILEORPIPE)
940 goto jleave;
942 /* n_fullextra is only the complete name part without address.
943 * Beware of "-r '<abc@def>'", don't treat that as FULLEXTRA */
944 if ((ntype & GFULLEXTRA) && ag.ag_ilen > ag.ag_slen + 2) {
945 size_t s = ag.ag_iaddr_start, e = ag.ag_iaddr_aend, i;
946 char const *cp;
948 if (s == 0 || str[--s] != '<' || str[e++] != '>')
949 goto jskipfullextra;
950 i = ag.ag_ilen - e;
951 in.s = n_lofi_alloc(s + 1 + i +1);
952 while(s > 0 && blankchar(str[s - 1]))
953 --s;
954 memcpy(in.s, str, s);
955 if (i > 0) {
956 in.s[s++] = ' ';
957 while (blankchar(str[e])) {
958 ++e;
959 if (--i == 0)
960 break;
962 if (i > 0)
963 memcpy(&in.s[s], &str[e], i);
965 s += i;
966 in.s[in.l = s] = '\0';
967 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
969 for (cp = out.s, i = out.l; i > 0 && spacechar(*cp); --i, ++cp)
971 while (i > 0 && spacechar(cp[i - 1]))
972 --i;
973 np->n_fullextra = savestrbuf(cp, i);
975 n_lofi_free(in.s);
976 free(out.s);
978 jskipfullextra:
980 /* n_fullname depends on IDNA conversion */
981 #ifdef HAVE_IDNA
982 if (!(ag.ag_n_flags & NAME_IDNA)) {
983 #endif
984 in.s = n_UNCONST(str);
985 in.l = ag.ag_ilen;
986 #ifdef HAVE_IDNA
987 } else {
988 /* The domain name was IDNA and has been converted. We also have to
989 * ensure that the domain name in .n_fullname is replaced with the
990 * converted version, since MIME doesn't perform encoding of addrs */
991 /* TODO This definetely doesn't belong here! */
992 size_t l = ag.ag_iaddr_start,
993 lsuff = ag.ag_ilen - ag.ag_iaddr_aend;
994 in.s = ac_alloc(l + ag.ag_slen + lsuff +1);
995 memcpy(in.s, str, l);
996 memcpy(in.s + l, ag.ag_skinned, ag.ag_slen);
997 l += ag.ag_slen;
998 memcpy(in.s + l, str + ag.ag_iaddr_aend, lsuff);
999 l += lsuff;
1000 in.s[l] = '\0';
1001 in.l = l;
1003 #endif
1004 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
1005 np->n_fullname = savestr(out.s);
1006 free(out.s);
1007 #ifdef HAVE_IDNA
1008 if (ag.ag_n_flags & NAME_IDNA)
1009 ac_free(in.s);
1010 #endif
1011 np->n_flags |= NAME_FULLNAME_SALLOC;
1013 jleave:
1014 NYD_LEAVE;
1015 return np;
1018 FL struct name *
1019 ndup(struct name *np, enum gfield ntype)
1021 struct name *nnp;
1022 NYD_ENTER;
1024 if ((ntype & (GFULL | GSKIN)) && !(np->n_flags & NAME_SKINNED)) {
1025 nnp = nalloc(np->n_name, ntype);
1026 goto jleave;
1029 nnp = salloc(sizeof *np);
1030 nnp->n_flink = nnp->n_blink = NULL;
1031 nnp->n_type = ntype;
1032 nnp->n_flags = (np->n_flags & ~(NAME_NAME_SALLOC | NAME_FULLNAME_SALLOC)) |
1033 NAME_NAME_SALLOC;
1034 nnp->n_name = savestr(np->n_name);
1035 if (np->n_name == np->n_fullname || !(ntype & (GFULL | GSKIN))) {
1036 nnp->n_fullname = nnp->n_name;
1037 nnp->n_fullextra = NULL;
1038 } else {
1039 nnp->n_flags |= NAME_FULLNAME_SALLOC;
1040 nnp->n_fullname = savestr(np->n_fullname);
1041 nnp->n_fullextra = (np->n_fullextra == NULL) ? NULL
1042 : savestr(np->n_fullextra);
1044 jleave:
1045 NYD_LEAVE;
1046 return nnp;
1049 FL struct name *
1050 cat(struct name *n1, struct name *n2)
1052 struct name *tail;
1053 NYD_ENTER;
1055 tail = n2;
1056 if (n1 == NULL)
1057 goto jleave;
1058 tail = n1;
1059 if (n2 == NULL)
1060 goto jleave;
1062 while (tail->n_flink != NULL)
1063 tail = tail->n_flink;
1064 tail->n_flink = n2;
1065 n2->n_blink = tail;
1066 tail = n1;
1067 jleave:
1068 NYD_LEAVE;
1069 return tail;
1072 FL struct name *
1073 namelist_dup(struct name const *np, enum gfield ntype)
1075 struct name *nnp;
1076 NYD_ENTER;
1078 for (nnp = NULL; np != NULL; np = np->n_flink) {
1079 struct name *x = ndup(n_UNCONST(np), (np->n_type & ~GMASK) | ntype);
1080 x->n_flink = nnp;
1081 nnp = x;
1083 NYD_LEAVE;
1084 return nnp;
1087 FL ui32_t
1088 count(struct name const *np)
1090 ui32_t c;
1091 NYD_ENTER;
1093 for (c = 0; np != NULL; np = np->n_flink)
1094 if (!(np->n_type & GDEL))
1095 ++c;
1096 NYD_LEAVE;
1097 return c;
1100 FL ui32_t
1101 count_nonlocal(struct name const *np)
1103 ui32_t c;
1104 NYD_ENTER;
1106 for (c = 0; np != NULL; np = np->n_flink)
1107 if (!(np->n_type & GDEL) && !(np->n_flags & NAME_ADDRSPEC_ISFILEORPIPE))
1108 ++c;
1109 NYD_LEAVE;
1110 return c;
1113 FL struct name *
1114 extract(char const *line, enum gfield ntype)
1116 struct name *rv;
1117 NYD_ENTER;
1119 rv = _extract1(line, ntype, " \t,", 0);
1120 NYD_LEAVE;
1121 return rv;
1124 FL struct name *
1125 lextract(char const *line, enum gfield ntype)
1127 struct name *rv;
1128 NYD_ENTER;
1130 rv = ((line != NULL && strpbrk(line, ",\"\\(<|"))
1131 ? _extract1(line, ntype, ",", 1) : extract(line, ntype));
1132 NYD_LEAVE;
1133 return rv;
1136 FL char *
1137 detract(struct name *np, enum gfield ntype)
1139 char *topp, *cp;
1140 struct name *p;
1141 int flags, s;
1142 NYD_ENTER;
1144 topp = NULL;
1145 if (np == NULL)
1146 goto jleave;
1148 flags = ntype & (GCOMMA | GNAMEONLY);
1149 ntype &= ~(GCOMMA | GNAMEONLY);
1150 s = 0;
1152 for (p = np; p != NULL; p = p->n_flink) {
1153 if (ntype && (p->n_type & GMASK) != ntype)
1154 continue;
1155 s += strlen(flags & GNAMEONLY ? p->n_name : p->n_fullname) +1;
1156 if (flags & GCOMMA)
1157 ++s;
1159 if (s == 0)
1160 goto jleave;
1162 s += 2;
1163 topp = salloc(s);
1164 cp = topp;
1165 for (p = np; p != NULL; p = p->n_flink) {
1166 if (ntype && (p->n_type & GMASK) != ntype)
1167 continue;
1168 cp = sstpcpy(cp, (flags & GNAMEONLY ? p->n_name : p->n_fullname));
1169 if ((flags & GCOMMA) && p->n_flink != NULL)
1170 *cp++ = ',';
1171 *cp++ = ' ';
1173 *--cp = 0;
1174 if ((flags & GCOMMA) && *--cp == ',')
1175 *cp = 0;
1176 jleave:
1177 NYD_LEAVE;
1178 return topp;
1181 FL struct name *
1182 grab_names(enum n_lexinput_flags lif, char const *field, struct name *np,
1183 int comma, enum gfield gflags)
1185 struct name *nq;
1186 NYD_ENTER;
1188 jloop:
1189 np = lextract(n_lex_input_cp(lif, field, detract(np, comma)), gflags);
1190 for (nq = np; nq != NULL; nq = nq->n_flink)
1191 if (is_addr_invalid(nq, EACM_NONE))
1192 goto jloop;
1193 NYD_LEAVE;
1194 return np;
1197 FL bool_t
1198 name_is_same_domain(struct name const *n1, struct name const *n2)
1200 char const *d1, *d2;
1201 bool_t rv;
1202 NYD_ENTER;
1204 d1 = strrchr(n1->n_name, '@');
1205 d2 = strrchr(n2->n_name, '@');
1207 rv = (d1 != NULL && d2 != NULL) ? !asccasecmp(++d1, ++d2) : FAL0;
1209 NYD_LEAVE;
1210 return rv;
1213 FL struct name *
1214 checkaddrs(struct name *np, enum expand_addr_check_mode eacm,
1215 si8_t *set_on_error)
1217 struct name *n;
1218 NYD_ENTER;
1220 for (n = np; n != NULL; n = n->n_flink) {
1221 si8_t rv;
1223 if ((rv = is_addr_invalid(n, eacm)) != 0) {
1224 if (set_on_error != NULL)
1225 *set_on_error |= rv; /* don't loose -1! */
1226 else if (eacm & EAF_MAYKEEP) /* TODO HACK! See definition! */
1227 continue;
1228 if (n->n_blink)
1229 n->n_blink->n_flink = n->n_flink;
1230 if (n->n_flink)
1231 n->n_flink->n_blink = n->n_blink;
1232 if (n == np)
1233 np = n->n_flink;
1236 NYD_LEAVE;
1237 return np;
1240 FL struct name *
1241 namelist_vaporise_head(struct header *hp, enum expand_addr_check_mode eacm,
1242 bool_t metoo, si8_t *set_on_error)
1244 struct name *tolist, *np, **npp;
1245 NYD_ENTER;
1247 tolist = usermap(cat(hp->h_to, cat(hp->h_cc, hp->h_bcc)), metoo);
1248 hp->h_to = hp->h_cc = hp->h_bcc = NULL;
1250 tolist = elide(checkaddrs(tolist, eacm, set_on_error));
1252 for (np = tolist; np != NULL; np = np->n_flink) {
1253 switch (np->n_type & (GDEL | GMASK)) {
1254 case GTO: npp = &hp->h_to; break;
1255 case GCC: npp = &hp->h_cc; break;
1256 case GBCC: npp = &hp->h_bcc; break;
1257 default: continue;
1259 *npp = cat(*npp, ndup(np, np->n_type | GFULL));
1261 NYD_LEAVE;
1262 return tolist;
1265 FL struct name *
1266 usermap(struct name *names, bool_t force_metoo)
1268 struct name *new, *np, *cp;
1269 struct group *gp;
1270 int metoo;
1271 NYD_ENTER;
1273 new = NULL;
1274 np = names;
1275 metoo = (force_metoo || ok_blook(metoo));
1276 while (np != NULL) {
1277 assert(!(np->n_type & GDEL)); /* TODO legacy */
1278 if (is_fileorpipe_addr(np) || np->n_name[0] == '\\') {
1279 cp = np->n_flink;
1280 new = put(new, np);
1281 np = cp;
1282 continue;
1284 gp = _group_find(GT_ALIAS, np->n_name);
1285 cp = np->n_flink;
1286 if (gp != NULL)
1287 new = _gexpand(0, new, gp, metoo, np->n_type);
1288 else
1289 new = put(new, np);
1290 np = cp;
1292 NYD_LEAVE;
1293 return new;
1296 FL struct name *
1297 elide(struct name *names)
1299 struct name *np, *t, *newn, *x;
1300 NYD_ENTER;
1302 newn = NULL;
1303 if (names == NULL)
1304 goto jleave;
1306 /* Throw away all deleted nodes (XXX merge with plain sort below?) */
1307 for (np = NULL; names != NULL; names = names->n_flink)
1308 if (!(names->n_type & GDEL)) {
1309 names->n_blink = np;
1310 if (np)
1311 np->n_flink = names;
1312 else
1313 newn = names;
1314 np = names;
1316 if (newn == NULL)
1317 goto jleave;
1319 np = newn->n_flink;
1320 if (np != NULL)
1321 np->n_blink = NULL;
1322 newn->n_flink = NULL;
1324 while (np != NULL) {
1325 int cmpres;
1327 t = newn;
1328 while ((cmpres = asccasecmp(t->n_name, np->n_name)) < 0) {
1329 if (t->n_flink == NULL)
1330 break;
1331 t = t->n_flink;
1334 /* If we ran out of t's, put new entry after the current value of t */
1335 if (cmpres < 0) {
1336 t->n_flink = np;
1337 np->n_blink = t;
1338 t = np;
1339 np = np->n_flink;
1340 t->n_flink = NULL;
1341 continue;
1344 /* Otherwise, put the new entry in front of the current t. If at the
1345 * front of the list, the new guy becomes the new head of the list */
1346 if (t == newn) {
1347 t = np;
1348 np = np->n_flink;
1349 t->n_flink = newn;
1350 newn->n_blink = t;
1351 t->n_blink = NULL;
1352 newn = t;
1353 continue;
1356 /* The normal case -- we are inserting into the middle of the list */
1357 x = np;
1358 np = np->n_flink;
1359 x->n_flink = t;
1360 x->n_blink = t->n_blink;
1361 t->n_blink->n_flink = x;
1362 t->n_blink = x;
1365 /* Now the list headed up by new is sorted. Remove duplicates */
1366 np = newn;
1367 while (np != NULL) {
1368 t = np;
1369 while (t->n_flink != NULL && !asccasecmp(np->n_name, t->n_flink->n_name))
1370 t = t->n_flink;
1371 if (t == np) {
1372 np = np->n_flink;
1373 continue;
1376 /* Now t points to the last entry with the same name as np.
1377 * Make np point beyond t */
1378 np->n_flink = t->n_flink;
1379 if (t->n_flink != NULL)
1380 t->n_flink->n_blink = np;
1381 np = np->n_flink;
1383 jleave:
1384 NYD_LEAVE;
1385 return newn;
1388 FL int
1389 c_alternates(void *v){
1390 char **namelist, **ap, **ap2, *cp;
1391 size_t l, sl;
1392 int rv;
1393 NYD_ENTER;
1395 rv = 0;
1397 l = 0;
1398 for(namelist = v; namelist[l] != NULL; ++l)
1401 if(l == 0){
1402 if(a_nag_altnames != NULL){
1403 fprintf(n_stdout, "alternates ");
1404 for(ap = a_nag_altnames; *ap != NULL; ++ap)
1405 fprintf(n_stdout, "%s ", *ap);
1406 putc('\n', n_stdout);
1408 }else{
1409 if(a_nag_altnames != NULL){
1410 for(ap = a_nag_altnames; *ap != NULL; ++ap)
1411 free(*ap);
1412 free(a_nag_altnames);
1415 ++l;
1416 ap2 =
1417 a_nag_altnames = smalloc(l * sizeof(*a_nag_altnames));
1419 sl = 0;
1420 for(ap = namelist; *ap != NULL; ++ap)
1421 if(*ap != '\0'){
1422 struct name *np;
1424 if((np = lextract(*ap, GSKIN)) == NULL || np->n_flink != NULL ||
1425 (np = checkaddrs(np, EACM_STRICT, NULL)) == NULL){
1426 n_err(_("Invalid `alternates' argument: %s\n"),
1427 n_shexp_quote_cp(*ap, FAL0));
1428 rv = 1;
1429 continue;
1431 l = strlen(np->n_name) +1;
1432 sl += l;
1433 cp = smalloc(l);
1434 memcpy(cp, np->n_name, l);
1435 *ap2++ = cp;
1437 *ap2 = NULL;
1439 /* And put it into *-alternates* */
1440 if(sl > 0){
1441 cp = salloc(sl);
1442 for(sl = 0, ap = a_nag_altnames; *ap != NULL; ++ap)
1443 if((l = strlen(*ap)) > 0){
1444 memcpy(&cp[sl], *ap, l);
1445 cp[sl += l] = ' ';
1446 ++sl;
1448 if(sl > 0)
1449 --sl;
1450 cp[sl] = '\0';
1451 }else{
1452 free(a_nag_altnames);
1453 a_nag_altnames = NULL;
1456 n_PS_ROOT_BLOCK(sl > 0 ? ok_vset(_alternates, cp)
1457 : ok_vclear(_alternates));
1459 NYD_LEAVE;
1460 return rv;
1463 FL struct name *
1464 delete_alternates(struct name *np)
1466 struct name *xp;
1467 char **ap;
1468 NYD_ENTER;
1470 np = delname(np, ok_vlook(LOGNAME));
1471 if (a_nag_altnames != NULL)
1472 for (ap = a_nag_altnames; *ap != '\0'; ++ap)
1473 np = delname(np, *ap);
1475 if ((xp = lextract(ok_vlook(from), GEXTRA | GSKIN)) != NULL)
1476 while (xp != NULL) {
1477 np = delname(np, xp->n_name);
1478 xp = xp->n_flink;
1481 if ((xp = lextract(ok_vlook(replyto), GEXTRA | GSKIN)) != NULL)
1482 while (xp != NULL) {
1483 np = delname(np, xp->n_name);
1484 xp = xp->n_flink;
1487 if ((xp = extract(ok_vlook(sender), GEXTRA | GSKIN)) != NULL)
1488 while (xp != NULL) {
1489 np = delname(np, xp->n_name);
1490 xp = xp->n_flink;
1492 NYD_LEAVE;
1493 return np;
1496 FL int
1497 is_myname(char const *name)
1499 int rv = 1;
1500 struct name *xp;
1501 char **ap;
1502 NYD_ENTER;
1504 if (_same_name(ok_vlook(LOGNAME), name))
1505 goto jleave;
1506 if (a_nag_altnames != NULL)
1507 for (ap = a_nag_altnames; *ap != NULL; ++ap)
1508 if (_same_name(*ap, name))
1509 goto jleave;
1511 if ((xp = lextract(ok_vlook(from), GEXTRA | GSKIN)) != NULL)
1512 while (xp != NULL) {
1513 if (_same_name(xp->n_name, name))
1514 goto jleave;
1515 xp = xp->n_flink;
1518 if ((xp = lextract(ok_vlook(replyto), GEXTRA | GSKIN)) != NULL)
1519 while (xp != NULL) {
1520 if (_same_name(xp->n_name, name))
1521 goto jleave;
1522 xp = xp->n_flink;
1525 if ((xp = extract(ok_vlook(sender), GEXTRA | GSKIN)) != NULL)
1526 while (xp != NULL) {
1527 if (_same_name(xp->n_name, name))
1528 goto jleave;
1529 xp = xp->n_flink;
1531 rv = 0;
1532 jleave:
1533 NYD_LEAVE;
1534 return rv;
1537 FL int
1538 c_addrcodec(void *v){
1539 struct n_addrguts ag;
1540 struct n_string s_b, *sp;
1541 char const **argv, *varname, *varres, *cp;
1542 int rv;
1543 NYD_ENTER;
1545 sp = n_string_creat_auto(&s_b);
1546 rv = 0;
1547 argv = v;
1548 varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
1550 for(; *argv != NULL; ++argv){
1551 if(sp->s_len > 0)
1552 sp = n_string_push_c(sp, ' ');
1553 sp = n_string_push_cp(sp, *argv);
1556 /* */
1557 /* TODO nalloc() cannot yet fail, thus need to do the work twice!!
1558 * TODO I.e. later on this could be a simple nalloc() wrapper.. */
1559 for(cp = n_string_cp(sp); blankchar(*cp); ++cp)
1561 if(cp != sp->s_dat)
1562 sp = n_string_cut(sp, 0, PTR2SIZE(cp - sp->s_dat));
1563 for(varres = cp = &sp->s_dat[sp->s_len];
1564 cp > sp->s_dat && blankchar(cp[-1]); --cp)
1566 if(cp != varres)
1567 sp = n_string_trunc(sp, sp->s_len - (ui32_t)PTR2SIZE(varres - cp));
1569 /* C99 */{
1570 size_t i;
1572 /* However, the difference for this command is that the user enters what
1573 * she wants to have, and we should make something of it. Therefore any
1574 * quotes are necessarily to be turned to quoted-pair! */
1575 n_string_cp(sp);
1576 for(i = 0; i < sp->s_len; ++i)
1577 if(sp->s_dat[i] == '"' || sp->s_dat[i] == '\\')
1578 sp = n_string_insert_c(sp, i++, '\\');
1581 if(n_addrspec_with_guts(&ag, n_string_cp(sp), TRU1) == NULL){
1582 varres = sp->s_dat;
1583 v = NULL;
1584 }else{
1585 struct name *np;
1587 np = nalloc(n_string_cp(sp), GTO | GFULL | GSKIN);
1588 varres = np->n_fullname;
1591 if(varname == NULL){
1592 if(fprintf(n_stdout, "%s\n", varres) < 0)
1593 rv = 1;
1594 }else if(!n_var_vset(varname, (uintptr_t)varres)){
1595 rv = 1;
1596 v = NULL;
1599 if(v != NULL)
1600 n_pstate_var__em = n_0;
1601 NYD_LEAVE;
1602 return rv;
1605 FL int
1606 c_alias(void *v)
1608 char **argv = v;
1609 struct group *gp;
1610 int rv = 0;
1611 NYD_ENTER;
1613 if (*argv == NULL)
1614 _group_print_all(GT_ALIAS);
1615 else if (argv[1] == NULL) {
1616 if ((gp = _group_find(GT_ALIAS, *argv)) != NULL)
1617 _group_print(gp, n_stdout);
1618 else {
1619 n_err(_("No such alias: %s\n"), *argv);
1620 rv = 1;
1622 } else {
1623 struct grp_names_head *gnhp;
1625 gp = _group_fetch(GT_ALIAS, *argv, 0);
1626 GP_TO_SUBCLASS(gnhp, gp);
1628 for (++argv; *argv != NULL; ++argv) {
1629 size_t l = strlen(*argv) +1;
1630 struct grp_names *gnp = smalloc(n_VSTRUCT_SIZEOF(struct grp_names,
1631 gn_id) + l);
1632 gnp->gn_next = gnhp->gnh_head;
1633 gnhp->gnh_head = gnp;
1634 memcpy(gnp->gn_id, *argv, l);
1636 assert(gnhp->gnh_head != NULL);
1638 NYD_LEAVE;
1639 return rv;
1642 FL int
1643 c_unalias(void *v)
1645 char **argv = v;
1646 int rv = 0;
1647 NYD_ENTER;
1649 do if (!_group_del(GT_ALIAS, *argv)) {
1650 n_err(_("No such alias: %s\n"), *argv);
1651 rv = 1;
1652 } while (*++argv != NULL);
1653 NYD_LEAVE;
1654 return rv;
1657 FL int
1658 c_mlist(void *v)
1660 int rv;
1661 NYD_ENTER;
1663 rv = _mlmux(GT_MLIST, v);
1664 NYD_LEAVE;
1665 return rv;
1668 FL int
1669 c_unmlist(void *v)
1671 int rv;
1672 NYD_ENTER;
1674 rv = _unmlmux(GT_MLIST, v);
1675 NYD_LEAVE;
1676 return rv;
1679 FL int
1680 c_mlsubscribe(void *v)
1682 int rv;
1683 NYD_ENTER;
1685 rv = _mlmux(GT_MLIST | GT_SUBSCRIBE, v);
1686 NYD_LEAVE;
1687 return rv;
1690 FL int
1691 c_unmlsubscribe(void *v)
1693 int rv;
1694 NYD_ENTER;
1696 rv = _unmlmux(GT_MLIST | GT_SUBSCRIBE, v);
1697 NYD_LEAVE;
1698 return rv;
1701 FL enum mlist_state
1702 is_mlist(char const *name, bool_t subscribed_only)
1704 struct group *gp;
1705 #ifdef HAVE_REGEX
1706 struct grp_regex **lpp, *grp;
1707 bool_t re2;
1708 #endif
1709 enum mlist_state rv;
1710 NYD_ENTER;
1712 gp = _group_find(GT_MLIST, name);
1713 rv = (gp != NULL) ? MLIST_KNOWN : MLIST_OTHER;
1714 if (rv == MLIST_KNOWN) {
1715 if (gp->g_type & GT_SUBSCRIBE)
1716 rv = MLIST_SUBSCRIBED;
1717 else if (subscribed_only)
1718 rv = MLIST_OTHER;
1719 /* Of course, if that is a regular expression it doesn't mean a thing */
1720 #ifdef HAVE_REGEX
1721 if (gp->g_type & GT_REGEX)
1722 rv = MLIST_OTHER;
1723 else
1724 #endif
1725 goto jleave;
1728 /* Not in the hashmap (as something matchable), walk the lists */
1729 #ifdef HAVE_REGEX
1730 re2 = FAL0;
1731 lpp = &_mlsub_regex;
1732 jregex_redo:
1733 if ((grp = *lpp) != NULL) {
1734 do if (regexec(&grp->gr_regex, name, 0,NULL, 0) != REG_NOMATCH) {
1735 /* Relink as the head of this list if the hit count of this group is
1736 * >= 25% of the average hit count */
1737 size_t i;
1738 if (!re2)
1739 i = ++_mlsub_hits / _mlsub_size;
1740 else
1741 i = ++_mlist_hits / _mlist_size;
1742 i >>= 2;
1744 if (++grp->gr_hits >= i && *lpp != grp && grp->gr_next != grp) {
1745 grp->gr_last->gr_next = grp->gr_next;
1746 grp->gr_next->gr_last = grp->gr_last;
1747 (grp->gr_last = (*lpp)->gr_last)->gr_next = grp;
1748 (grp->gr_next = *lpp)->gr_last = grp;
1749 *lpp = grp;
1751 rv = !re2 ? MLIST_SUBSCRIBED : MLIST_KNOWN;
1752 goto jleave;
1753 } while ((grp = grp->gr_next) != *lpp);
1755 if (!re2 && !subscribed_only) {
1756 re2 = TRU1;
1757 lpp = &_mlist_regex;
1758 goto jregex_redo;
1760 assert(rv == MLIST_OTHER);
1761 #endif
1763 jleave:
1764 NYD_LEAVE;
1765 return rv;
1768 FL int
1769 c_shortcut(void *v)
1771 char **argv = v;
1772 int rv = 0;
1773 NYD_ENTER;
1775 if (*argv == NULL)
1776 _group_print_all(GT_SHORTCUT);
1777 else for (; *argv != NULL; argv += 2) {
1778 /* Because one hardly ever redefines, anything is stored in one chunk */
1779 size_t l;
1780 struct group *gp;
1781 char *cp;
1783 if (argv[1] == NULL) {
1784 n_err(_("Shortcut expansion is missing: %s\n"), *argv);
1785 rv = 1;
1786 break;
1788 if (_group_find(GT_SHORTCUT, *argv) != NULL)
1789 _group_del(GT_SHORTCUT, *argv);
1791 l = strlen(argv[1]) +1;
1792 gp = _group_fetch(GT_SHORTCUT, *argv, l);
1793 GP_TO_SUBCLASS(cp, gp);
1794 memcpy(cp, argv[1], l);
1796 NYD_LEAVE;
1797 return rv;
1800 FL int
1801 c_unshortcut(void *v)
1803 char **argv = v;
1804 int rv = 0;
1805 NYD_ENTER;
1807 do if (!_group_del(GT_SHORTCUT, *argv)) {
1808 n_err(_("No such shortcut: %s\n"), *argv);
1809 rv = 1;
1810 } while (*++argv != NULL);
1811 NYD_LEAVE;
1812 return rv;
1815 FL char const *
1816 shortcut_expand(char const *str)
1818 struct group *gp;
1819 NYD_ENTER;
1821 if ((gp = _group_find(GT_SHORTCUT, str)) != NULL)
1822 GP_TO_SUBCLASS(str, gp);
1823 else
1824 str = NULL;
1825 NYD_LEAVE;
1826 return str;
1829 /* s-it-mode */