"Fossies" - the Fresh Open Source Software Archive

Member "s-nail-14.9.11/nam-a-grp.c" (8 Aug 2018, 77281 Bytes) of package /linux/misc/s-nail-14.9.11.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "nam-a-grp.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 14.9.10_vs_14.9.11.

    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!
    4  *
    5  * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
    6  * Copyright (c) 2012 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
    7  * SPDX-License-Identifier: BSD-3-Clause TODO ISC
    8  */
    9 /*
   10  * Copyright (c) 1980, 1993
   11  *      The Regents of the University of California.  All rights reserved.
   12  *
   13  * Redistribution and use in source and binary forms, with or without
   14  * modification, are permitted provided that the following conditions
   15  * are met:
   16  * 1. Redistributions of source code must retain the above copyright
   17  *    notice, this list of conditions and the following disclaimer.
   18  * 2. Redistributions in binary form must reproduce the above copyright
   19  *    notice, this list of conditions and the following disclaimer in the
   20  *    documentation and/or other materials provided with the distribution.
   21  * 3. Neither the name of the University nor the names of its contributors
   22  *    may be used to endorse or promote products derived from this software
   23  *    without specific prior written permission.
   24  *
   25  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
   26  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   27  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   28  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
   29  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   30  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
   31  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
   32  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   33  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   34  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   35  * SUCH DAMAGE.
   36  */
   37 #undef n_FILE
   38 #define n_FILE nam_a_grp
   39 
   40 #ifndef HAVE_AMALGAMATION
   41 # include "nail.h"
   42 #endif
   43 
   44 enum a_nag_type{
   45    /* Main types */
   46    a_NAG_T_ALTERNATES = 1,
   47    a_NAG_T_COMMANDALIAS,
   48    a_NAG_T_ALIAS,
   49    a_NAG_T_MLIST,
   50    a_NAG_T_SHORTCUT,
   51    a_NAG_T_CHARSETALIAS,
   52    a_NAG_T_FILETYPE,
   53    a_NAG_T_MASK = 0x1F,
   54 
   55    /* Subtype bits and flags */
   56    a_NAG_T_SUBSCRIBE = 1u<<6,
   57    a_NAG_T_REGEX = 1u<<7,
   58 
   59    /* Extended type mask to be able to reflect what we really have; i.e., mlist
   60     * can have a_NAG_T_REGEX if they are subscribed or not, but `mlsubscribe'
   61     * should print out only a_NAG_T_MLIST which have the a_NAG_T_SUBSCRIBE
   62     * attribute set */
   63    a_NAG_T_PRINT_MASK = a_NAG_T_MASK | a_NAG_T_SUBSCRIBE
   64 };
   65 n_CTA(a_NAG_T_MASK >= a_NAG_T_FILETYPE, "Mask does not cover necessary bits");
   66 
   67 struct a_nag_group{
   68    struct a_nag_group *ng_next;
   69    ui32_t ng_subclass_off; /* of "subclass" in .ng_id (if any) */
   70    ui16_t ng_id_len_sub;   /* length of .ng_id: _subclass_off - this */
   71    ui8_t ng_type;          /* enum a_nag_type */
   72    /* Identifying name, of variable size.  Dependent on actual "subtype" more
   73     * data follows thereafter, but note this is always used (i.e., for regular
   74     * expression entries this is still set to the plain string) */
   75    char ng_id[n_VFIELD_SIZE(1)];
   76 };
   77 #define a_NAG_GP_TO_SUBCLASS(X,G) \
   78 do{\
   79    union a_nag_group_subclass {void *gs_vp; char *gs_cp;} a__gs__;\
   80    a__gs__.gs_cp = &((char*)n_UNCONST(G))[(G)->ng_subclass_off];\
   81    (X) = a__gs__.gs_vp;\
   82 }while(0)
   83 
   84 struct a_nag_grp_names_head{
   85    struct a_nag_grp_names *ngnh_head;
   86 };
   87 
   88 struct a_nag_grp_names{
   89    struct a_nag_grp_names *ngn_next;
   90    char ngn_id[n_VFIELD_SIZE(0)];
   91 };
   92 
   93 #ifdef HAVE_REGEX
   94 struct a_nag_grp_regex{
   95    struct a_nag_grp_regex *ngr_last;
   96    struct a_nag_grp_regex *ngr_next;
   97    struct a_nag_group *ngr_mygroup; /* xxx because lists use grp_regex*! ?? */
   98    size_t ngr_hits;                 /* Number of times this group matched */
   99    regex_t ngr_regex;
  100 };
  101 #endif
  102 
  103 struct a_nag_cmd_alias{
  104    struct str nca_expand;
  105 };
  106 
  107 struct a_nag_file_type{
  108    struct str nft_load;
  109    struct str nft_save;
  110 };
  111 
  112 struct a_nag_group_lookup{
  113    struct a_nag_group **ngl_htable;
  114    struct a_nag_group **ngl_slot;
  115    struct a_nag_group *ngl_slot_last;
  116    struct a_nag_group *ngl_group;
  117 };
  118 
  119 static struct n_file_type const a_nag_OBSOLETE_xz = { /* TODO v15 compat */
  120    "xz", 2, "xz -cd", sizeof("xz -cd") -1, "xz -cz", sizeof("xz -cz") -1
  121 }, a_nag_OBSOLETE_gz = {
  122    "gz", 2, "gzip -cd", sizeof("gzip -cd") -1, "gzip -cz", sizeof("gzip -cz") -1
  123 }, a_nag_OBSOLETE_bz2 = {
  124    "bz2", 3, "bzip2 -cd", sizeof("bzip2 -cd") -1,
  125    "bzip2 -cz", sizeof("bzip2 -cz") -1
  126 };
  127 
  128 /* `alternates' */
  129 static struct a_nag_group *a_nag_alternates_heads[HSHSIZE];
  130 
  131 /* `commandalias' */
  132 static struct a_nag_group *a_nag_commandalias_heads[HSHSIZE];
  133 
  134 /* `alias' */
  135 static struct a_nag_group *a_nag_alias_heads[HSHSIZE];
  136 
  137 /* `mlist', `mlsubscribe'.  Anything is stored in the hashmap.. */
  138 static struct a_nag_group *a_nag_mlist_heads[HSHSIZE];
  139 
  140 /* ..but entries which have a_NAG_T_REGEX set are false lookups and will really
  141  * be accessed via sequential lists instead, which are type-specific for better
  142  * performance, but also to make it possible to have ".*@xy.org" as a mlist
  143  * and "(one|two)@xy.org" as a mlsubscription.
  144  * These lists use a bit of QOS optimization in that a matching group will
  145  * become relinked as the new list head if its hit count is
  146  *    (>= ((xy_hits / _xy_size) >> 2))
  147  * Note that the hit counts only count currently linked in nodes.. */
  148 #ifdef HAVE_REGEX
  149 static struct a_nag_grp_regex *a_nag_mlist_regex;
  150 static struct a_nag_grp_regex *a_nag_mlsub_regex;
  151 static size_t a_nag_mlist_size;
  152 static size_t a_nag_mlist_hits;
  153 static size_t a_nag_mlsub_size;
  154 static size_t a_nag_mlsub_hits;
  155 #endif
  156 
  157 /* `shortcut' */
  158 static struct a_nag_group *a_nag_shortcut_heads[HSHSIZE];
  159 
  160 /* `charsetalias' */
  161 static struct a_nag_group *a_nag_charsetalias_heads[HSHSIZE];
  162 
  163 /* `filetype' */
  164 static struct a_nag_group *a_nag_filetype_heads[HSHSIZE];
  165 
  166 /* Same name, while taking care for *allnet*? */
  167 static bool_t a_nag_is_same_name(char const *n1, char const *n2);
  168 
  169 /* Mark all (!file, !pipe) nodes with the given name */
  170 static struct name *a_nag_namelist_mark_name(struct name *np, char const *name);
  171 
  172 /* Grab a single name (liberal name) */
  173 static char const *a_nag_yankname(char const *ap, char *wbuf,
  174                         char const *separators, int keepcomms);
  175 
  176 /* Extraction multiplexer that splits an input line to names */
  177 static struct name *a_nag_extract1(char const *line, enum gfield ntype,
  178                         char const *separators, bool_t keepcomms);
  179 
  180 /* Recursively expand a alias name.  Limit expansion to some fixed level.
  181  * Direct recursion is not expanded for convenience */
  182 static struct name *a_nag_gexpand(size_t level, struct name *nlist,
  183                         struct a_nag_group *ngp, bool_t metoo, int ntype);
  184 
  185 /* elide() helper */
  186 static int a_nag_elide_qsort(void const *s1, void const *s2);
  187 
  188 /* Lookup a group, return it or NULL, fill in glp anyway */
  189 static struct a_nag_group *a_nag_group_lookup(enum a_nag_type nt,
  190                            struct a_nag_group_lookup *nglp, char const *id);
  191 
  192 /* Easier-to-use wrapper around _group_lookup() */
  193 static struct a_nag_group *a_nag_group_find(enum a_nag_type nt, char const *id);
  194 
  195 /* Iteration: go to the first group, which also inits the iterator.  A valid
  196  * iterator can be stepped via _next().  A NULL return means no (more) groups
  197  * to be iterated exist, in which case only nglp->ngl_group is set (NULL) */
  198 static struct a_nag_group *a_nag_group_go_first(enum a_nag_type nt,
  199                         struct a_nag_group_lookup *nglp);
  200 static struct a_nag_group *a_nag_group_go_next(struct a_nag_group_lookup *nglp);
  201 
  202 /* Fetch the group id, create it as necessary, fail with NULL if impossible */
  203 static struct a_nag_group *a_nag_group_fetch(enum a_nag_type nt, char const *id,
  204                         size_t addsz);
  205 
  206 /* "Intelligent" delete which handles a "*" id, too;
  207  * returns a true boolean if a group was deleted, and always succeeds for "*" */
  208 static bool_t a_nag_group_del(enum a_nag_type nt, char const *id);
  209 
  210 static struct a_nag_group *a_nag__group_del(struct a_nag_group_lookup *nglp);
  211 static void a_nag__names_del(struct a_nag_group *ngp);
  212 
  213 /* Print all groups of the given type, alphasorted, or store in `vput' varname:
  214  * only in this mode it can return failure */
  215 static bool_t a_nag_group_print_all(enum a_nag_type nt,
  216                char const *varname);
  217 
  218 static int a_nag__group_print_qsorter(void const *a, void const *b);
  219 
  220 /* Really print a group, actually.  Or store in vputsp, if set.
  221  * Return number of written lines */
  222 static size_t a_nag_group_print(struct a_nag_group const *ngp, FILE *fo,
  223                struct n_string *vputsp);
  224 
  225 /* Multiplexers for list and subscribe commands */
  226 static int a_nag_mlmux(enum a_nag_type nt, char const **argv);
  227 static int a_nag_unmlmux(enum a_nag_type nt, char const **argv);
  228 
  229 /* Relinkers for the sequential match lists */
  230 #ifdef HAVE_REGEX
  231 static void a_nag_mlmux_linkin(struct a_nag_group *ngp);
  232 static void a_nag_mlmux_linkout(struct a_nag_group *ngp);
  233 # define a_NAG_MLMUX_LINKIN(GP) \
  234    do if((GP)->ng_type & a_NAG_T_REGEX) a_nag_mlmux_linkin(GP); while(0)
  235 # define a_NAG_MLMUX_LINKOUT(GP) \
  236    do if((GP)->ng_type & a_NAG_T_REGEX) a_nag_mlmux_linkout(GP); while(0)
  237 #else
  238 # define a_NAG_MLMUX_LINKIN(GP)
  239 # define a_NAG_MLMUX_LINKOUT(GP)
  240 #endif
  241 
  242 static bool_t
  243 a_nag_is_same_name(char const *n1, char const *n2){
  244    bool_t rv;
  245    char c1, c2, c1r, c2r;
  246    NYD2_ENTER;
  247 
  248    if(ok_blook(allnet)){
  249       for(;; ++n1, ++n2){
  250          c1 = *n1;
  251          c1 = lowerconv(c1);
  252          c1r = (c1 == '\0' || c1 == '@');
  253          c2 = *n2;
  254          c2 = lowerconv(c2);
  255          c2r = (c2 == '\0' || c2 == '@');
  256 
  257          if(c1r || c2r){
  258             rv = (c1r == c2r);
  259             break;
  260          }else if(c1 != c2){
  261             rv = FAL0;
  262             break;
  263          }
  264       }
  265    }else
  266       rv = !asccasecmp(n1, n2);
  267    NYD2_LEAVE;
  268    return rv;
  269 }
  270 
  271 static struct name *
  272 a_nag_namelist_mark_name(struct name *np, char const *name){
  273    struct name *p;
  274    NYD2_ENTER;
  275 
  276    for(p = np; p != NULL; p = p->n_flink)
  277       if(!(p->n_type & GDEL) &&
  278             !(p->n_flags & (((ui32_t)SI32_MIN) | NAME_ADDRSPEC_ISFILE |
  279                NAME_ADDRSPEC_ISPIPE)) &&
  280             a_nag_is_same_name(p->n_name, name))
  281          p->n_flags |= (ui32_t)SI32_MIN;
  282    NYD2_LEAVE;
  283    return np;
  284 }
  285 
  286 static char const *
  287 a_nag_yankname(char const *ap, char *wbuf, char const *separators,
  288    int keepcomms)
  289 {
  290    char const *cp;
  291    char *wp, c, inquote, lc, lastsp;
  292    NYD_ENTER;
  293 
  294    *(wp = wbuf) = '\0';
  295 
  296    /* Skip over intermediate list trash, as in ".org>  ,  <xy@zz.org>" */
  297    for (c = *ap; blankchar(c) || c == ','; c = *++ap)
  298       ;
  299    if (c == '\0') {
  300       cp = NULL;
  301       goto jleave;
  302    }
  303 
  304    /* Parse a full name: TODO RFC 5322
  305     * - Keep everything in quotes, liberal handle *quoted-pair*s therein
  306     * - Skip entire (nested) comments
  307     * - In non-quote, non-comment, join adjacent space to a single SP
  308     * - Understand separators only in non-quote, non-comment context,
  309     *   and only if not part of a *quoted-pair* (XXX too liberal) */
  310    cp = ap;
  311    for (inquote = lc = lastsp = 0;; lc = c, ++cp) {
  312       c = *cp;
  313       if (c == '\0')
  314          break;
  315       if (c == '\\')
  316          goto jwpwc;
  317       if (c == '"') {
  318          if (lc != '\\')
  319             inquote = !inquote;
  320 #if 0 /* TODO when doing real RFC 5322 parsers - why have i done this? */
  321          else
  322             --wp;
  323 #endif
  324          goto jwpwc;
  325       }
  326       if (inquote || lc == '\\') {
  327 jwpwc:
  328          *wp++ = c;
  329          lastsp = 0;
  330          continue;
  331       }
  332       if (c == '(') {
  333          ap = cp;
  334          cp = skip_comment(cp + 1);
  335          if (keepcomms)
  336             while (ap < cp)
  337                *wp++ = *ap++;
  338          --cp;
  339          lastsp = 0;
  340          continue;
  341       }
  342       if (strchr(separators, c) != NULL)
  343          break;
  344 
  345       lc = lastsp;
  346       lastsp = blankchar(c);
  347       if (!lastsp || !lc)
  348          *wp++ = c;
  349    }
  350    if (blankchar(lc))
  351       --wp;
  352 
  353    *wp = '\0';
  354 jleave:
  355    NYD_LEAVE;
  356    return cp;
  357 }
  358 
  359 static struct name *
  360 a_nag_extract1(char const *line, enum gfield ntype, char const *separators,
  361    bool_t keepcomms)
  362 {
  363    struct name *topp, *np, *t;
  364    char const *cp;
  365    char *nbuf;
  366    NYD_ENTER;
  367 
  368    topp = NULL;
  369    if (line == NULL || *line == '\0')
  370       goto jleave;
  371 
  372    np = NULL;
  373    cp = line;
  374    nbuf = n_alloc(strlen(line) +1);
  375    while ((cp = a_nag_yankname(cp, nbuf, separators, keepcomms)) != NULL) {
  376       t = nalloc(nbuf, ntype);
  377       if (topp == NULL)
  378          topp = t;
  379       else
  380          np->n_flink = t;
  381       t->n_blink = np;
  382       np = t;
  383    }
  384    n_free(nbuf);
  385 jleave:
  386    NYD_LEAVE;
  387    return topp;
  388 }
  389 
  390 static struct name *
  391 a_nag_gexpand(size_t level, struct name *nlist, struct a_nag_group *ngp,
  392       bool_t metoo, int ntype){
  393    struct a_nag_grp_names *ngnp;
  394    struct name *nlist_tail;
  395    char const *logname;
  396    struct a_nag_grp_names_head *ngnhp;
  397    NYD2_ENTER;
  398 
  399    if(UICMP(z, level++, >, n_ALIAS_MAXEXP)){
  400       n_err(_("Expanding alias to depth larger than %d\n"), n_ALIAS_MAXEXP);
  401       goto jleave;
  402    }
  403 
  404    a_NAG_GP_TO_SUBCLASS(ngnhp, ngp);
  405    logname = ok_vlook(LOGNAME);
  406 
  407    for(ngnp = ngnhp->ngnh_head; ngnp != NULL; ngnp = ngnp->ngn_next){
  408       struct a_nag_group *xngp;
  409       char *cp;
  410 
  411       cp = ngnp->ngn_id;
  412 
  413       if(!strcmp(cp, ngp->ng_id))
  414          goto jas_is;
  415 
  416       if((xngp = a_nag_group_find(a_NAG_T_ALIAS, cp)) != NULL){
  417          /* For S-nail(1), the "alias" may *be* the sender in that a name maps
  418           * to a full address specification; aliases cannot be empty */
  419          struct a_nag_grp_names_head *xngnhp;
  420 
  421          a_NAG_GP_TO_SUBCLASS(xngnhp, xngp);
  422 
  423          assert(xngnhp->ngnh_head != NULL);
  424          if(metoo || xngnhp->ngnh_head->ngn_next != NULL ||
  425                !a_nag_is_same_name(cp, logname))
  426             nlist = a_nag_gexpand(level, nlist, xngp, metoo, ntype);
  427          continue;
  428       }
  429 
  430       /* Here we should allow to expand to itself if only person in alias */
  431 jas_is:
  432       if(metoo || ngnhp->ngnh_head->ngn_next == NULL ||
  433             !a_nag_is_same_name(cp, logname)){
  434          struct name *np;
  435 
  436          np = nalloc(cp, ntype | GFULL);
  437          if((nlist_tail = nlist) != NULL){
  438             while(nlist_tail->n_flink != NULL)
  439                nlist_tail = nlist_tail->n_flink;
  440             nlist_tail->n_flink = np;
  441             np->n_blink = nlist_tail;
  442          }else
  443             nlist = np;
  444       }
  445    }
  446 jleave:
  447    NYD2_LEAVE;
  448    return nlist;
  449 }
  450 
  451 static int
  452 a_nag_elide_qsort(void const *s1, void const *s2){
  453    struct name const * const *np1, * const *np2;
  454    int rv;
  455    NYD2_ENTER;
  456 
  457    np1 = s1;
  458    np2 = s2;
  459    if(!(rv = asccasecmp((*np1)->n_name, (*np2)->n_name))){
  460       n_LCTAV(GTO < GCC && GCC < GBCC);
  461       rv = ((*np1)->n_type & (GTO | GCC | GBCC)) -
  462             ((*np2)->n_type & (GTO | GCC | GBCC));
  463    }
  464    NYD2_LEAVE;
  465    return rv;
  466 }
  467 
  468 static struct a_nag_group *
  469 a_nag_group_lookup(enum a_nag_type nt, struct a_nag_group_lookup *nglp,
  470       char const *id){
  471    char c1;
  472    struct a_nag_group *lngp, *ngp;
  473    bool_t icase;
  474    NYD2_ENTER;
  475 
  476    icase = FAL0;
  477 
  478    /* C99 */{
  479       ui32_t h;
  480       struct a_nag_group **ngpa;
  481 
  482       switch((nt &= a_NAG_T_MASK)){
  483       case a_NAG_T_ALTERNATES:
  484          ngpa = a_nag_alternates_heads;
  485          icase = TRU1;
  486          break;
  487       default:
  488       case a_NAG_T_COMMANDALIAS:
  489          ngpa = a_nag_commandalias_heads;
  490          break;
  491       case a_NAG_T_ALIAS:
  492          ngpa = a_nag_alias_heads;
  493          break;
  494       case a_NAG_T_MLIST:
  495          ngpa = a_nag_mlist_heads;
  496          icase = TRU1;
  497          break;
  498       case a_NAG_T_SHORTCUT:
  499          ngpa = a_nag_shortcut_heads;
  500          break;
  501       case a_NAG_T_CHARSETALIAS:
  502          ngpa = a_nag_charsetalias_heads;
  503          icase = TRU1;
  504          break;
  505       case a_NAG_T_FILETYPE:
  506          ngpa = a_nag_filetype_heads;
  507          icase = TRU1;
  508          break;
  509       }
  510 
  511       nglp->ngl_htable = ngpa;
  512       h = icase ? n_torek_ihash(id) : n_torek_hash(id);
  513       ngp = *(nglp->ngl_slot = &ngpa[h % HSHSIZE]);
  514    }
  515 
  516    lngp = NULL;
  517    c1 = *id++;
  518 
  519    if(icase){
  520       c1 = lowerconv(c1);
  521       for(; ngp != NULL; lngp = ngp, ngp = ngp->ng_next)
  522          if((ngp->ng_type & a_NAG_T_MASK) == nt && *ngp->ng_id == c1 &&
  523                !asccasecmp(&ngp->ng_id[1], id))
  524             break;
  525    }else{
  526       for(; ngp != NULL; lngp = ngp, ngp = ngp->ng_next)
  527          if((ngp->ng_type & a_NAG_T_MASK) == nt && *ngp->ng_id == c1 &&
  528                !strcmp(&ngp->ng_id[1], id))
  529             break;
  530    }
  531 
  532    nglp->ngl_slot_last = lngp;
  533    nglp->ngl_group = ngp;
  534    NYD2_LEAVE;
  535    return ngp;
  536 }
  537 
  538 static struct a_nag_group *
  539 a_nag_group_find(enum a_nag_type nt, char const *id){
  540    struct a_nag_group_lookup ngl;
  541    struct a_nag_group *ngp;
  542    NYD2_ENTER;
  543 
  544    ngp = a_nag_group_lookup(nt, &ngl, id);
  545    NYD2_LEAVE;
  546    return ngp;
  547 }
  548 
  549 static struct a_nag_group *
  550 a_nag_group_go_first(enum a_nag_type nt, struct a_nag_group_lookup *nglp){
  551    size_t i;
  552    struct a_nag_group **ngpa, *ngp;
  553    NYD2_ENTER;
  554 
  555    switch((nt &= a_NAG_T_MASK)){
  556    case a_NAG_T_ALTERNATES:
  557       ngpa = a_nag_alternates_heads;
  558       break;
  559    default:
  560    case a_NAG_T_COMMANDALIAS:
  561       ngpa = a_nag_commandalias_heads;
  562       break;
  563    case a_NAG_T_ALIAS:
  564       ngpa = a_nag_alias_heads;
  565       break;
  566    case a_NAG_T_MLIST:
  567       ngpa = a_nag_mlist_heads;
  568       break;
  569    case a_NAG_T_SHORTCUT:
  570       ngpa = a_nag_shortcut_heads;
  571       break;
  572    case a_NAG_T_CHARSETALIAS:
  573       ngpa = a_nag_charsetalias_heads;
  574       break;
  575    case a_NAG_T_FILETYPE:
  576       ngpa = a_nag_filetype_heads;
  577       break;
  578    }
  579 
  580    nglp->ngl_htable = ngpa;
  581 
  582    for(i = 0; i < HSHSIZE; ++ngpa, ++i)
  583       if((ngp = *ngpa) != NULL){
  584          nglp->ngl_slot = ngpa;
  585          nglp->ngl_group = ngp;
  586          goto jleave;
  587       }
  588 
  589    nglp->ngl_group = ngp = NULL;
  590 jleave:
  591    nglp->ngl_slot_last = NULL;
  592    NYD2_LEAVE;
  593    return ngp;
  594 }
  595 
  596 static struct a_nag_group *
  597 a_nag_group_go_next(struct a_nag_group_lookup *nglp){
  598    struct a_nag_group *ngp, **ngpa;
  599    NYD2_ENTER;
  600 
  601    if((ngp = nglp->ngl_group->ng_next) != NULL)
  602       nglp->ngl_slot_last = nglp->ngl_group;
  603    else{
  604       nglp->ngl_slot_last = NULL;
  605       for(ngpa = &nglp->ngl_htable[HSHSIZE]; ++nglp->ngl_slot < ngpa;)
  606          if((ngp = *nglp->ngl_slot) != NULL)
  607             break;
  608    }
  609    nglp->ngl_group = ngp;
  610    NYD2_LEAVE;
  611    return ngp;
  612 }
  613 
  614 static struct a_nag_group *
  615 a_nag_group_fetch(enum a_nag_type nt, char const *id, size_t addsz){
  616    struct a_nag_group_lookup ngl;
  617    struct a_nag_group *ngp;
  618    size_t l, i;
  619    NYD2_ENTER;
  620 
  621    if((ngp = a_nag_group_lookup(nt, &ngl, id)) != NULL)
  622       goto jleave;
  623 
  624    l = strlen(id) +1;
  625    if(UIZ_MAX - n_ALIGN(l) <=
  626          n_ALIGN(n_VSTRUCT_SIZEOF(struct a_nag_group, ng_id)))
  627       goto jleave;
  628 
  629    i = n_ALIGN(n_VSTRUCT_SIZEOF(struct a_nag_group, ng_id) + l);
  630    switch(nt & a_NAG_T_MASK){
  631    case a_NAG_T_ALTERNATES:
  632    case a_NAG_T_SHORTCUT:
  633    case a_NAG_T_CHARSETALIAS:
  634    default:
  635       break;
  636    case a_NAG_T_COMMANDALIAS:
  637       addsz += sizeof(struct a_nag_cmd_alias);
  638       break;
  639    case a_NAG_T_ALIAS:
  640       addsz += sizeof(struct a_nag_grp_names_head);
  641       break;
  642    case a_NAG_T_MLIST:
  643 #ifdef HAVE_REGEX
  644       if(n_is_maybe_regex(id)){
  645          addsz = sizeof(struct a_nag_grp_regex);
  646          nt |= a_NAG_T_REGEX;
  647       }
  648 #endif
  649       break;
  650    case a_NAG_T_FILETYPE:
  651       addsz += sizeof(struct a_nag_file_type);
  652       break;
  653    }
  654    if(UIZ_MAX - i < addsz || UI32_MAX <= i || UI16_MAX < i - l)
  655       goto jleave;
  656 
  657    ngp = n_alloc(i + addsz);
  658    memcpy(ngp->ng_id, id, l);
  659    ngp->ng_subclass_off = (ui32_t)i;
  660    ngp->ng_id_len_sub = (ui16_t)(i - --l);
  661    ngp->ng_type = nt;
  662    switch(nt & a_NAG_T_MASK){
  663    case a_NAG_T_ALTERNATES:
  664    case a_NAG_T_MLIST:
  665    case a_NAG_T_CHARSETALIAS:
  666    case a_NAG_T_FILETYPE:{
  667       char *cp, c;
  668 
  669       for(cp = ngp->ng_id; (c = *cp) != '\0'; ++cp)
  670          *cp = lowerconv(c);
  671       }break;
  672    default:
  673       break;
  674    }
  675 
  676    if((nt & a_NAG_T_MASK) == a_NAG_T_ALIAS){
  677       struct a_nag_grp_names_head *ngnhp;
  678 
  679       a_NAG_GP_TO_SUBCLASS(ngnhp, ngp);
  680       ngnhp->ngnh_head = NULL;
  681    }
  682 #ifdef HAVE_REGEX
  683    else if(nt & a_NAG_T_REGEX){
  684       int s;
  685       struct a_nag_grp_regex *ngrp;
  686 
  687       a_NAG_GP_TO_SUBCLASS(ngrp, ngp);
  688 
  689       if((s = regcomp(&ngrp->ngr_regex, id,
  690             REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0){
  691          n_err(_("Invalid regular expression: %s: %s\n"),
  692             n_shexp_quote_cp(id, FAL0), n_regex_err_to_doc(NULL, s));
  693          n_free(ngp);
  694          ngp = NULL;
  695          goto jleave;
  696       }
  697       ngrp->ngr_mygroup = ngp;
  698       a_nag_mlmux_linkin(ngp);
  699    }
  700 #endif /* HAVE_REGEX */
  701 
  702    ngp->ng_next = *ngl.ngl_slot;
  703    *ngl.ngl_slot = ngp;
  704 jleave:
  705    NYD2_LEAVE;
  706    return ngp;
  707 }
  708 
  709 static bool_t
  710 a_nag_group_del(enum a_nag_type nt, char const *id){
  711    struct a_nag_group_lookup ngl;
  712    struct a_nag_group *ngp;
  713    enum a_nag_type xnt;
  714    NYD2_ENTER;
  715 
  716    xnt = nt & a_NAG_T_MASK;
  717 
  718    /* Delete 'em all? */
  719    if(id[0] == '*' && id[1] == '\0'){
  720       for(ngp = a_nag_group_go_first(nt, &ngl); ngp != NULL;)
  721          ngp = ((ngp->ng_type & a_NAG_T_MASK) == xnt) ? a_nag__group_del(&ngl)
  722                : a_nag_group_go_next(&ngl);
  723       ngp = (struct a_nag_group*)TRU1;
  724    }else if((ngp = a_nag_group_lookup(nt, &ngl, id)) != NULL){
  725       if(ngp->ng_type & xnt)
  726          a_nag__group_del(&ngl);
  727       else
  728          ngp = NULL;
  729    }
  730    NYD2_LEAVE;
  731    return (ngp != NULL);
  732 }
  733 
  734 static struct a_nag_group *
  735 a_nag__group_del(struct a_nag_group_lookup *nglp){
  736    struct a_nag_group *x, *ngp;
  737    NYD2_ENTER;
  738 
  739    /* Overly complicated: link off this node, step ahead to next.. */
  740    x = nglp->ngl_group;
  741    if((ngp = nglp->ngl_slot_last) != NULL)
  742       ngp = (ngp->ng_next = x->ng_next);
  743    else{
  744       nglp->ngl_slot_last = NULL;
  745       ngp = (*nglp->ngl_slot = x->ng_next);
  746 
  747       if(ngp == NULL){
  748          struct a_nag_group **ngpa;
  749 
  750          for(ngpa = &nglp->ngl_htable[HSHSIZE]; ++nglp->ngl_slot < ngpa;)
  751             if((ngp = *nglp->ngl_slot) != NULL)
  752                break;
  753       }
  754    }
  755    nglp->ngl_group = ngp;
  756 
  757    if((x->ng_type & a_NAG_T_MASK) == a_NAG_T_ALIAS)
  758       a_nag__names_del(x);
  759 #ifdef HAVE_REGEX
  760    else if(x->ng_type & a_NAG_T_REGEX){
  761       struct a_nag_grp_regex *ngrp;
  762 
  763       a_NAG_GP_TO_SUBCLASS(ngrp, x);
  764 
  765       regfree(&ngrp->ngr_regex);
  766       a_nag_mlmux_linkout(x);
  767    }
  768 #endif
  769 
  770    n_free(x);
  771    NYD2_LEAVE;
  772    return ngp;
  773 }
  774 
  775 static void
  776 a_nag__names_del(struct a_nag_group *ngp){
  777    struct a_nag_grp_names_head *ngnhp;
  778    struct a_nag_grp_names *ngnp;
  779    NYD2_ENTER;
  780 
  781    a_NAG_GP_TO_SUBCLASS(ngnhp, ngp);
  782 
  783    for(ngnp = ngnhp->ngnh_head; ngnp != NULL;){
  784       struct a_nag_grp_names *x;
  785 
  786       x = ngnp;
  787       ngnp = ngnp->ngn_next;
  788       n_free(x);
  789    }
  790    NYD2_LEAVE;
  791 }
  792 
  793 static bool_t
  794 a_nag_group_print_all(enum a_nag_type nt, char const *varname){
  795    struct n_string s;
  796    size_t lines;
  797    FILE *fp;
  798    char const **ida;
  799    struct a_nag_group const *ngp;
  800    ui32_t h, i;
  801    struct a_nag_group **ngpa;
  802    char const *tname;
  803    enum a_nag_type xnt;
  804    NYD_ENTER;
  805 
  806    if(varname != NULL)
  807       n_string_creat_auto(&s);
  808 
  809    xnt = nt & a_NAG_T_PRINT_MASK;
  810 
  811    switch(xnt & a_NAG_T_MASK){
  812    case a_NAG_T_ALTERNATES:
  813       tname = "alternates";
  814       ngpa = a_nag_alternates_heads;
  815       break;
  816    default:
  817    case a_NAG_T_COMMANDALIAS:
  818       tname = "commandalias";
  819       ngpa = a_nag_commandalias_heads;
  820       break;
  821    case a_NAG_T_ALIAS:
  822       tname = "alias";
  823       ngpa = a_nag_alias_heads;
  824       break;
  825    case a_NAG_T_MLIST:
  826       tname = "mlist";
  827       ngpa = a_nag_mlist_heads;
  828       break;
  829    case a_NAG_T_SHORTCUT:
  830       tname = "shortcut";
  831       ngpa = a_nag_shortcut_heads;
  832       break;
  833    case a_NAG_T_CHARSETALIAS:
  834       tname = "charsetalias";
  835       ngpa = a_nag_charsetalias_heads;
  836       break;
  837    case a_NAG_T_FILETYPE:
  838       tname = "filetype";
  839       ngpa = a_nag_filetype_heads;
  840       break;
  841    }
  842 
  843    /* Count entries */
  844    for(i = h = 0; h < HSHSIZE; ++h)
  845       for(ngp = ngpa[h]; ngp != NULL; ngp = ngp->ng_next)
  846          if((ngp->ng_type & a_NAG_T_PRINT_MASK) == xnt)
  847             ++i;
  848    if(i == 0){
  849       if(varname == NULL)
  850          fprintf(n_stdout, _("# no %s registered\n"), tname);
  851       goto jleave;
  852    }
  853    ++i;
  854    ida = n_autorec_alloc(i * sizeof *ida);
  855 
  856    /* Create alpha sorted array of entries */
  857    for(i = h = 0; h < HSHSIZE; ++h)
  858       for(ngp = ngpa[h]; ngp != NULL; ngp = ngp->ng_next)
  859          if((ngp->ng_type & a_NAG_T_PRINT_MASK) == xnt)
  860             ida[i++] = ngp->ng_id;
  861    if(i > 1)
  862       qsort(ida, i, sizeof *ida, &a_nag__group_print_qsorter);
  863    ida[i] = NULL;
  864 
  865    if(varname != NULL)
  866       fp = NULL;
  867    else if((fp = Ftmp(NULL, "nagprint", OF_RDWR | OF_UNLINK | OF_REGISTER)
  868          ) == NULL)
  869       fp = n_stdout;
  870 
  871    /* Create visual result */
  872    lines = 0;
  873 
  874    switch(xnt & a_NAG_T_MASK){
  875    case a_NAG_T_ALTERNATES:
  876       if(fp != NULL){
  877          fputs(tname, fp);
  878          lines = 1;
  879       }
  880       break;
  881    default:
  882       break;
  883    }
  884 
  885    for(i = 0; ida[i] != NULL; ++i)
  886       lines += a_nag_group_print(a_nag_group_find(nt, ida[i]), fp, &s);
  887 
  888 #ifdef HAVE_REGEX
  889    if(varname == NULL && (nt & a_NAG_T_MASK) == a_NAG_T_MLIST){
  890       if(nt & a_NAG_T_SUBSCRIBE)
  891          i = (ui32_t)a_nag_mlsub_size, h = (ui32_t)a_nag_mlsub_hits;
  892       else
  893          i = (ui32_t)a_nag_mlist_size, h = (ui32_t)a_nag_mlist_hits;
  894 
  895       if(i > 0 && (n_poption & n_PO_D_V)){
  896          assert(fp != NULL);
  897          fprintf(fp, _("# %s list regex(7) total: %u entries, %u hits\n"),
  898             (nt & a_NAG_T_SUBSCRIBE ? _("Subscribed") : _("Non-subscribed")),
  899             i, h);
  900          ++lines;
  901       }
  902    }
  903 #endif
  904 
  905    switch(xnt & a_NAG_T_MASK){
  906    case a_NAG_T_ALTERNATES:
  907       if(fp != NULL){
  908          putc('\n', fp);
  909          assert(lines == 1);
  910       }
  911       break;
  912    default:
  913       break;
  914    }
  915 
  916    if(varname == NULL && fp != n_stdout){
  917       assert(fp != NULL);
  918       page_or_print(fp, lines);
  919       Fclose(fp);
  920    }
  921 
  922 jleave:
  923    if(varname != NULL){
  924       tname = n_string_cp(&s);
  925       if(n_var_vset(varname, (uintptr_t)tname))
  926          varname = NULL;
  927       else
  928          n_pstate_err_no = n_ERR_NOTSUP;
  929    }
  930    NYD_LEAVE;
  931    return (varname == NULL);
  932 }
  933 
  934 static int
  935 a_nag__group_print_qsorter(void const *a, void const *b){
  936    int rv;
  937    NYD2_ENTER;
  938 
  939    rv = strcmp(*(char**)n_UNCONST(a), *(char**)n_UNCONST(b));
  940    NYD2_LEAVE;
  941    return rv;
  942 }
  943 
  944 static size_t
  945 a_nag_group_print(struct a_nag_group const *ngp, FILE *fo,
  946       struct n_string *vputsp){
  947    char const *cp;
  948    size_t rv;
  949    NYD2_ENTER;
  950 
  951    rv = 1;
  952 
  953    switch(ngp->ng_type & a_NAG_T_MASK){
  954    case a_NAG_T_ALTERNATES:{
  955       if(fo != NULL)
  956          fprintf(fo, " %s", ngp->ng_id);
  957       else{
  958          if(vputsp->s_len > 0)
  959             vputsp = n_string_push_c(vputsp, ' ');
  960          /*vputsp =*/ n_string_push_cp(vputsp, ngp->ng_id);
  961       }
  962       rv = 0;
  963       }break;
  964    case a_NAG_T_COMMANDALIAS:{
  965       struct a_nag_cmd_alias *ncap;
  966 
  967       assert(fo != NULL); /* xxx no vput yet */
  968       a_NAG_GP_TO_SUBCLASS(ncap, ngp);
  969       fprintf(fo, "commandalias %s %s\n",
  970          n_shexp_quote_cp(ngp->ng_id, TRU1),
  971          n_shexp_quote_cp(ncap->nca_expand.s, TRU1));
  972       }break;
  973    case a_NAG_T_ALIAS:{
  974       struct a_nag_grp_names_head *ngnhp;
  975       struct a_nag_grp_names *ngnp;
  976 
  977       assert(fo != NULL); /* xxx no vput yet */
  978       fprintf(fo, "alias %s ", ngp->ng_id);
  979 
  980       a_NAG_GP_TO_SUBCLASS(ngnhp, ngp);
  981       if((ngnp = ngnhp->ngnh_head) != NULL) { /* xxx always 1+ entries */
  982          do{
  983             struct a_nag_grp_names *x;
  984 
  985             x = ngnp;
  986             ngnp = ngnp->ngn_next;
  987             fprintf(fo, " \"%s\"", string_quote(x->ngn_id)); /* TODO shexp */
  988          }while(ngnp != NULL);
  989       }
  990       putc('\n', fo);
  991       }break;
  992    case a_NAG_T_MLIST:
  993       assert(fo != NULL); /* xxx no vput yet */
  994 #ifdef HAVE_REGEX
  995       if((ngp->ng_type & a_NAG_T_REGEX) && (n_poption & n_PO_D_V)){
  996          size_t i;
  997          struct a_nag_grp_regex *lp, *ngrp;
  998 
  999          lp = (ngp->ng_type & a_NAG_T_SUBSCRIBE ? a_nag_mlsub_regex
 1000                : a_nag_mlist_regex);
 1001          a_NAG_GP_TO_SUBCLASS(ngrp, ngp);
 1002          for(i = 1; lp != ngrp; lp = lp->ngr_next)
 1003             ++i;
 1004          fprintf(fo, "# regex(7): hits %" PRIuZ ", sort %" PRIuZ ".\n  ",
 1005             ngrp->ngr_hits, i);
 1006          ++rv;
 1007       }
 1008 #endif
 1009       fprintf(fo, "wysh %s %s\n",
 1010          (ngp->ng_type & a_NAG_T_SUBSCRIBE ? "mlsubscribe" : "mlist"),
 1011          n_shexp_quote_cp(ngp->ng_id, TRU1));
 1012       break;
 1013    case a_NAG_T_SHORTCUT:
 1014       assert(fo != NULL); /* xxx no vput yet */
 1015       a_NAG_GP_TO_SUBCLASS(cp, ngp);
 1016       fprintf(fo, "wysh shortcut %s %s\n",
 1017          ngp->ng_id, n_shexp_quote_cp(cp, TRU1));
 1018       break;
 1019    case a_NAG_T_CHARSETALIAS:
 1020       assert(fo != NULL); /* xxx no vput yet */
 1021       a_NAG_GP_TO_SUBCLASS(cp, ngp);
 1022       fprintf(fo, "charsetalias %s %s\n",
 1023          n_shexp_quote_cp(ngp->ng_id, TRU1), n_shexp_quote_cp(cp, TRU1));
 1024       break;
 1025    case a_NAG_T_FILETYPE:{
 1026       struct a_nag_file_type *nftp;
 1027 
 1028       assert(fo != NULL); /* xxx no vput yet */
 1029       a_NAG_GP_TO_SUBCLASS(nftp, ngp);
 1030       fprintf(fo, "filetype %s %s %s\n",
 1031          n_shexp_quote_cp(ngp->ng_id, TRU1),
 1032          n_shexp_quote_cp(nftp->nft_load.s, TRU1),
 1033          n_shexp_quote_cp(nftp->nft_save.s, TRU1));
 1034       }break;
 1035    }
 1036    NYD2_LEAVE;
 1037    return rv;
 1038 }
 1039 
 1040 static int
 1041 a_nag_mlmux(enum a_nag_type nt, char const **argv){
 1042    struct a_nag_group *ngp;
 1043    char const *ecp;
 1044    int rv;
 1045    NYD2_ENTER;
 1046 
 1047    rv = 0;
 1048    n_UNINIT(ecp, NULL);
 1049 
 1050    if(*argv == NULL)
 1051       a_nag_group_print_all(nt, NULL);
 1052    else do{
 1053       if((ngp = a_nag_group_find(nt, *argv)) != NULL){
 1054          if(nt & a_NAG_T_SUBSCRIBE){
 1055             if(!(ngp->ng_type & a_NAG_T_SUBSCRIBE)){
 1056                a_NAG_MLMUX_LINKOUT(ngp);
 1057                ngp->ng_type |= a_NAG_T_SUBSCRIBE;
 1058                a_NAG_MLMUX_LINKIN(ngp);
 1059             }else{
 1060                ecp = N_("Mailing-list already `mlsubscribe'd: %s\n");
 1061                goto jerr;
 1062             }
 1063          }else{
 1064             ecp = N_("Mailing-list already `mlist'ed: %s\n");
 1065             goto jerr;
 1066          }
 1067       }else if(a_nag_group_fetch(nt, *argv, 0) == NULL){
 1068          ecp = N_("Failed to create storage for mailing-list: %s\n");
 1069 jerr:
 1070          n_err(V_(ecp), n_shexp_quote_cp(*argv, FAL0));
 1071          rv = 1;
 1072       }
 1073    }while(*++argv != NULL);
 1074 
 1075    NYD2_LEAVE;
 1076    return rv;
 1077 }
 1078 
 1079 static int
 1080 a_nag_unmlmux(enum a_nag_type nt, char const **argv){
 1081    struct a_nag_group *ngp;
 1082    int rv;
 1083    NYD2_ENTER;
 1084 
 1085    rv = 0;
 1086 
 1087    for(; *argv != NULL; ++argv){
 1088       if(nt & a_NAG_T_SUBSCRIBE){
 1089          struct a_nag_group_lookup ngl;
 1090          bool_t isaster;
 1091 
 1092          if(!(isaster = (**argv == '*')))
 1093             ngp = a_nag_group_find(nt, *argv);
 1094          else if((ngp = a_nag_group_go_first(nt, &ngl)) == NULL)
 1095             continue;
 1096          else if(ngp != NULL && !(ngp->ng_type & a_NAG_T_SUBSCRIBE))
 1097             goto jaster_entry;
 1098 
 1099          if(ngp != NULL){
 1100 jaster_redo:
 1101             if(ngp->ng_type & a_NAG_T_SUBSCRIBE){
 1102                a_NAG_MLMUX_LINKOUT(ngp);
 1103                ngp->ng_type &= ~a_NAG_T_SUBSCRIBE;
 1104                a_NAG_MLMUX_LINKIN(ngp);
 1105 
 1106                if(isaster){
 1107 jaster_entry:
 1108                   while((ngp = a_nag_group_go_next(&ngl)) != NULL &&
 1109                         !(ngp->ng_type & a_NAG_T_SUBSCRIBE))
 1110                      ;
 1111                   if(ngp != NULL)
 1112                      goto jaster_redo;
 1113                }
 1114             }else{
 1115                n_err(_("Mailing-list not `mlsubscribe'd: %s\n"),
 1116                   n_shexp_quote_cp(*argv, FAL0));
 1117                rv = 1;
 1118             }
 1119             continue;
 1120          }
 1121       }else if(a_nag_group_del(nt, *argv))
 1122          continue;
 1123       n_err(_("No such mailing-list: %s\n"), n_shexp_quote_cp(*argv, FAL0));
 1124       rv = 1;
 1125    }
 1126    NYD2_LEAVE;
 1127    return rv;
 1128 }
 1129 
 1130 #ifdef HAVE_REGEX
 1131 static void
 1132 a_nag_mlmux_linkin(struct a_nag_group *ngp){
 1133    struct a_nag_grp_regex **lpp, *ngrp, *lhp;
 1134    NYD2_ENTER;
 1135 
 1136    if(ngp->ng_type & a_NAG_T_SUBSCRIBE){
 1137       lpp = &a_nag_mlsub_regex;
 1138       ++a_nag_mlsub_size;
 1139    }else{
 1140       lpp = &a_nag_mlist_regex;
 1141       ++a_nag_mlist_size;
 1142    }
 1143 
 1144    a_NAG_GP_TO_SUBCLASS(ngrp, ngp);
 1145 
 1146    if((lhp = *lpp) != NULL){
 1147       (ngrp->ngr_last = lhp->ngr_last)->ngr_next = ngrp;
 1148       (ngrp->ngr_next = lhp)->ngr_last = ngrp;
 1149    }else
 1150       *lpp = ngrp->ngr_last = ngrp->ngr_next = ngrp;
 1151    ngrp->ngr_hits = 0;
 1152    NYD2_LEAVE;
 1153 }
 1154 
 1155 static void
 1156 a_nag_mlmux_linkout(struct a_nag_group *ngp){
 1157    struct a_nag_grp_regex *ngrp, **lpp;
 1158    NYD2_ENTER;
 1159 
 1160    a_NAG_GP_TO_SUBCLASS(ngrp, ngp);
 1161 
 1162    if(ngp->ng_type & a_NAG_T_SUBSCRIBE){
 1163       lpp = &a_nag_mlsub_regex;
 1164       --a_nag_mlsub_size;
 1165       a_nag_mlsub_hits -= ngrp->ngr_hits;
 1166    }else{
 1167       lpp = &a_nag_mlist_regex;
 1168       --a_nag_mlist_size;
 1169       a_nag_mlist_hits -= ngrp->ngr_hits;
 1170    }
 1171 
 1172    if(ngrp->ngr_next == ngrp)
 1173       *lpp = NULL;
 1174    else{
 1175       (ngrp->ngr_last->ngr_next = ngrp->ngr_next)->ngr_last = ngrp->ngr_last;
 1176       if(*lpp == ngrp)
 1177          *lpp = ngrp->ngr_next;
 1178    }
 1179    NYD2_LEAVE;
 1180 }
 1181 #endif /* HAVE_REGEX */
 1182 
 1183 FL struct name *
 1184 nalloc(char const *str, enum gfield ntype)
 1185 {
 1186    struct n_addrguts ag;
 1187    struct str in, out;
 1188    struct name *np;
 1189    NYD_ENTER;
 1190    assert(!(ntype & GFULLEXTRA) || (ntype & GFULL) != 0);
 1191 
 1192    str = n_addrspec_with_guts(&ag, str,
 1193          ((ntype & (GFULL | GSKIN | GREF)) != 0), FAL0);
 1194    if(str == NULL){
 1195       /*
 1196       np = NULL; TODO We cannot return NULL,
 1197       goto jleave; TODO thus handle failures in here!
 1198       */
 1199       str = ag.ag_input;
 1200    }
 1201 
 1202    if (!(ag.ag_n_flags & NAME_NAME_SALLOC)) {
 1203       ag.ag_n_flags |= NAME_NAME_SALLOC;
 1204       np = n_autorec_alloc(sizeof(*np) + ag.ag_slen +1);
 1205       memcpy(np + 1, ag.ag_skinned, ag.ag_slen +1);
 1206       ag.ag_skinned = (char*)(np + 1);
 1207    } else
 1208       np = n_autorec_alloc(sizeof *np);
 1209 
 1210    np->n_flink = NULL;
 1211    np->n_blink = NULL;
 1212    np->n_type = ntype;
 1213    np->n_fullname = np->n_name = ag.ag_skinned;
 1214    np->n_fullextra = NULL;
 1215    np->n_flags = ag.ag_n_flags;
 1216 
 1217    if (ntype & GFULL) {
 1218       if (ag.ag_ilen == ag.ag_slen
 1219 #ifdef HAVE_IDNA
 1220             && !(ag.ag_n_flags & NAME_IDNA)
 1221 #endif
 1222       )
 1223          goto jleave;
 1224       if (ag.ag_n_flags & NAME_ADDRSPEC_ISFILEORPIPE)
 1225          goto jleave;
 1226 
 1227       /* n_fullextra is only the complete name part without address.
 1228        * Beware of "-r '<abc@def>'", don't treat that as FULLEXTRA */
 1229       if ((ntype & GFULLEXTRA) && ag.ag_ilen > ag.ag_slen + 2) {
 1230          size_t s = ag.ag_iaddr_start, e = ag.ag_iaddr_aend, i;
 1231          char const *cp;
 1232 
 1233          if (s == 0 || str[--s] != '<' || str[e++] != '>')
 1234             goto jskipfullextra;
 1235          i = ag.ag_ilen - e;
 1236          in.s = n_lofi_alloc(s + 1 + i +1);
 1237          while(s > 0 && blankchar(str[s - 1]))
 1238             --s;
 1239          memcpy(in.s, str, s);
 1240          if (i > 0) {
 1241             in.s[s++] = ' ';
 1242             while (blankchar(str[e])) {
 1243                ++e;
 1244                if (--i == 0)
 1245                   break;
 1246             }
 1247             if (i > 0)
 1248                memcpy(&in.s[s], &str[e], i);
 1249          }
 1250          s += i;
 1251          in.s[in.l = s] = '\0';
 1252          mime_fromhdr(&in, &out, /* TODO TD_ISPR |*/ TD_ICONV);
 1253 
 1254          for (cp = out.s, i = out.l; i > 0 && spacechar(*cp); --i, ++cp)
 1255             ;
 1256          while (i > 0 && spacechar(cp[i - 1]))
 1257             --i;
 1258          np->n_fullextra = savestrbuf(cp, i);
 1259 
 1260          n_lofi_free(in.s);
 1261          n_free(out.s);
 1262       }
 1263 jskipfullextra:
 1264 
 1265       /* n_fullname depends on IDNA conversion */
 1266 #ifdef HAVE_IDNA
 1267       if (!(ag.ag_n_flags & NAME_IDNA)) {
 1268 #endif
 1269          in.s = n_UNCONST(str);
 1270          in.l = ag.ag_ilen;
 1271 #ifdef HAVE_IDNA
 1272       } else {
 1273          /* The domain name was IDNA and has been converted.  We also have to
 1274           * ensure that the domain name in .n_fullname is replaced with the
 1275           * converted version, since MIME doesn't perform encoding of addrs */
 1276          /* TODO This definetely doesn't belong here! */
 1277          size_t l = ag.ag_iaddr_start,
 1278             lsuff = ag.ag_ilen - ag.ag_iaddr_aend;
 1279          in.s = n_lofi_alloc(l + ag.ag_slen + lsuff +1);
 1280          memcpy(in.s, str, l);
 1281          memcpy(in.s + l, ag.ag_skinned, ag.ag_slen);
 1282          l += ag.ag_slen;
 1283          memcpy(in.s + l, str + ag.ag_iaddr_aend, lsuff);
 1284          l += lsuff;
 1285          in.s[l] = '\0';
 1286          in.l = l;
 1287       }
 1288 #endif
 1289       mime_fromhdr(&in, &out, /* TODO TD_ISPR |*/ TD_ICONV);
 1290       np->n_fullname = savestr(out.s);
 1291       n_free(out.s);
 1292 #ifdef HAVE_IDNA
 1293       if (ag.ag_n_flags & NAME_IDNA)
 1294          n_lofi_free(in.s);
 1295 #endif
 1296    }
 1297 jleave:
 1298    NYD_LEAVE;
 1299    return np;
 1300 }
 1301 
 1302 FL struct name *
 1303 nalloc_fcc(char const *file){
 1304    struct name *nnp;
 1305    NYD_ENTER;
 1306 
 1307    nnp = n_autorec_alloc(sizeof *nnp);
 1308    nnp->n_flink = nnp->n_blink = NULL;
 1309    nnp->n_type = GBCC | GBCC_IS_FCC; /* xxx Bcc: <- namelist_vaporise_head */
 1310    nnp->n_flags = NAME_NAME_SALLOC | NAME_SKINNED | NAME_ADDRSPEC_ISFILE;
 1311    nnp->n_fullname = nnp->n_name = savestr(file);
 1312    nnp->n_fullextra = NULL;
 1313    NYD_LEAVE;
 1314    return nnp;
 1315 }
 1316 
 1317 FL struct name *
 1318 ndup(struct name *np, enum gfield ntype)
 1319 {
 1320    struct name *nnp;
 1321    NYD_ENTER;
 1322 
 1323    if ((ntype & (GFULL | GSKIN)) && !(np->n_flags & NAME_SKINNED)) {
 1324       nnp = nalloc(np->n_name, ntype);
 1325       goto jleave;
 1326    }
 1327 
 1328    nnp = n_autorec_alloc(sizeof *np);
 1329    nnp->n_flink = nnp->n_blink = NULL;
 1330    nnp->n_type = ntype;
 1331    nnp->n_flags = np->n_flags | NAME_NAME_SALLOC;
 1332    nnp->n_name = savestr(np->n_name);
 1333    if (np->n_name == np->n_fullname || !(ntype & (GFULL | GSKIN))) {
 1334       nnp->n_fullname = nnp->n_name;
 1335       nnp->n_fullextra = NULL;
 1336    } else {
 1337       nnp->n_fullname = savestr(np->n_fullname);
 1338       nnp->n_fullextra = (np->n_fullextra == NULL) ? NULL
 1339             : savestr(np->n_fullextra);
 1340    }
 1341 jleave:
 1342    NYD_LEAVE;
 1343    return nnp;
 1344 }
 1345 
 1346 FL struct name *
 1347 cat(struct name *n1, struct name *n2){
 1348    struct name *tail;
 1349    NYD2_ENTER;
 1350 
 1351    tail = n2;
 1352    if(n1 == NULL)
 1353       goto jleave;
 1354    tail = n1;
 1355    if(n2 == NULL || (n2->n_type & GDEL))
 1356       goto jleave;
 1357 
 1358    while(tail->n_flink != NULL)
 1359       tail = tail->n_flink;
 1360    tail->n_flink = n2;
 1361    n2->n_blink = tail;
 1362    tail = n1;
 1363 jleave:
 1364    NYD2_LEAVE;
 1365    return tail;
 1366 }
 1367 
 1368 FL struct name *
 1369 n_namelist_dup(struct name const *np, enum gfield ntype){
 1370    struct name *nlist, *xnp;
 1371    NYD2_ENTER;
 1372 
 1373    for(nlist = xnp = NULL; np != NULL; np = np->n_flink){
 1374       struct name *x;
 1375 
 1376       if(!(np->n_type & GDEL)){
 1377          x = ndup(n_UNCONST(np), (np->n_type & ~GMASK) | ntype);
 1378          if((x->n_blink = xnp) == NULL)
 1379             nlist = x;
 1380          else
 1381             xnp->n_flink = x;
 1382          xnp = x;
 1383       }
 1384    }
 1385    NYD2_LEAVE;
 1386    return nlist;
 1387 }
 1388 
 1389 FL ui32_t
 1390 count(struct name const *np)
 1391 {
 1392    ui32_t c;
 1393    NYD_ENTER;
 1394 
 1395    for (c = 0; np != NULL; np = np->n_flink)
 1396       if (!(np->n_type & GDEL))
 1397          ++c;
 1398    NYD_LEAVE;
 1399    return c;
 1400 }
 1401 
 1402 FL ui32_t
 1403 count_nonlocal(struct name const *np)
 1404 {
 1405    ui32_t c;
 1406    NYD_ENTER;
 1407 
 1408    for (c = 0; np != NULL; np = np->n_flink)
 1409       if (!(np->n_type & GDEL) && !(np->n_flags & NAME_ADDRSPEC_ISFILEORPIPE))
 1410          ++c;
 1411    NYD_LEAVE;
 1412    return c;
 1413 }
 1414 
 1415 FL struct name *
 1416 extract(char const *line, enum gfield ntype)
 1417 {
 1418    struct name *rv;
 1419    NYD_ENTER;
 1420 
 1421    rv = a_nag_extract1(line, ntype, " \t,", 0);
 1422    NYD_LEAVE;
 1423    return rv;
 1424 }
 1425 
 1426 FL struct name *
 1427 lextract(char const *line, enum gfield ntype)
 1428 {
 1429    char *cp;
 1430    struct name *rv;
 1431    NYD_ENTER;
 1432 
 1433    if(!(ntype & GSHEXP_PARSE_HACK) || !(expandaddr_to_eaf() & EAF_SHEXP_PARSE))
 1434       cp = NULL;
 1435    else{
 1436       struct str sin;
 1437       struct n_string s_b, *sp;
 1438       enum n_shexp_state shs;
 1439 
 1440       n_autorec_relax_create();
 1441       sp = n_string_creat_auto(&s_b);
 1442       sin.s = n_UNCONST(line); /* logical */
 1443       sin.l = UIZ_MAX;
 1444       shs = n_shexp_parse_token((n_SHEXP_PARSE_LOG |
 1445             n_SHEXP_PARSE_IGNORE_EMPTY | n_SHEXP_PARSE_QUOTE_AUTO_FIXED |
 1446             n_SHEXP_PARSE_QUOTE_AUTO_DSQ), sp, &sin, NULL);
 1447       if(!(shs & n_SHEXP_STATE_ERR_MASK) && (shs & n_SHEXP_STATE_STOP)){
 1448          line = cp = n_lofi_alloc(sp->s_len +1);
 1449          memcpy(cp, n_string_cp(sp), sp->s_len +1);
 1450       }else
 1451          line = cp = NULL;
 1452       n_autorec_relax_gut();
 1453    }
 1454 
 1455    rv = ((line != NULL && strpbrk(line, ",\"\\(<|"))
 1456          ? a_nag_extract1(line, ntype, ",", 1) : extract(line, ntype));
 1457 
 1458    if(cp != NULL)
 1459       n_lofi_free(cp);
 1460    NYD_LEAVE;
 1461    return rv;
 1462 }
 1463 
 1464 FL char *
 1465 detract(struct name *np, enum gfield ntype)
 1466 {
 1467    char *topp, *cp;
 1468    struct name *p;
 1469    int flags, s;
 1470    NYD_ENTER;
 1471 
 1472    topp = NULL;
 1473    if (np == NULL)
 1474       goto jleave;
 1475 
 1476    flags = ntype & (GCOMMA | GNAMEONLY);
 1477    ntype &= ~(GCOMMA | GNAMEONLY);
 1478    s = 0;
 1479 
 1480    for (p = np; p != NULL; p = p->n_flink) {
 1481       if (ntype && (p->n_type & GMASK) != ntype)
 1482          continue;
 1483       s += strlen(flags & GNAMEONLY ? p->n_name : p->n_fullname) +1;
 1484       if (flags & GCOMMA)
 1485          ++s;
 1486    }
 1487    if (s == 0)
 1488       goto jleave;
 1489 
 1490    s += 2;
 1491    topp = n_autorec_alloc(s);
 1492    cp = topp;
 1493    for (p = np; p != NULL; p = p->n_flink) {
 1494       if (ntype && (p->n_type & GMASK) != ntype)
 1495          continue;
 1496       cp = sstpcpy(cp, (flags & GNAMEONLY ? p->n_name : p->n_fullname));
 1497       if ((flags & GCOMMA) && p->n_flink != NULL)
 1498          *cp++ = ',';
 1499       *cp++ = ' ';
 1500    }
 1501    *--cp = 0;
 1502    if ((flags & GCOMMA) && *--cp == ',')
 1503       *cp = 0;
 1504 jleave:
 1505    NYD_LEAVE;
 1506    return topp;
 1507 }
 1508 
 1509 FL struct name *
 1510 grab_names(enum n_go_input_flags gif, char const *field, struct name *np,
 1511       int comma, enum gfield gflags)
 1512 {
 1513    struct name *nq;
 1514    NYD_ENTER;
 1515 
 1516 jloop:
 1517    np = lextract(n_go_input_cp(gif, field, detract(np, comma)), gflags);
 1518    for (nq = np; nq != NULL; nq = nq->n_flink)
 1519       if (is_addr_invalid(nq, EACM_NONE))
 1520          goto jloop;
 1521    NYD_LEAVE;
 1522    return np;
 1523 }
 1524 
 1525 FL bool_t
 1526 name_is_same_domain(struct name const *n1, struct name const *n2)
 1527 {
 1528    char const *d1, *d2;
 1529    bool_t rv;
 1530    NYD_ENTER;
 1531 
 1532    d1 = strrchr(n1->n_name, '@');
 1533    d2 = strrchr(n2->n_name, '@');
 1534 
 1535    rv = (d1 != NULL && d2 != NULL) ? !asccasecmp(++d1, ++d2) : FAL0;
 1536 
 1537    NYD_LEAVE;
 1538    return rv;
 1539 }
 1540 
 1541 FL struct name *
 1542 checkaddrs(struct name *np, enum expand_addr_check_mode eacm,
 1543    si8_t *set_on_error)
 1544 {
 1545    struct name *n;
 1546    NYD_ENTER;
 1547 
 1548    for (n = np; n != NULL; n = n->n_flink) {
 1549       si8_t rv;
 1550 
 1551       if ((rv = is_addr_invalid(n, eacm)) != 0) {
 1552          if (set_on_error != NULL)
 1553             *set_on_error |= rv; /* don't loose -1! */
 1554          else if (eacm & EAF_MAYKEEP) /* TODO HACK!  See definition! */
 1555             continue;
 1556          if (n->n_blink)
 1557             n->n_blink->n_flink = n->n_flink;
 1558          if (n->n_flink)
 1559             n->n_flink->n_blink = n->n_blink;
 1560          if (n == np)
 1561             np = n->n_flink;
 1562       }
 1563    }
 1564    NYD_LEAVE;
 1565    return np;
 1566 }
 1567 
 1568 FL struct name *
 1569 n_namelist_vaporise_head(bool_t strip_alternates, struct header *hp,
 1570    enum expand_addr_check_mode eacm, si8_t *set_on_error)
 1571 {
 1572    /* TODO namelist_vaporise_head() is incredibly expensive and redundant */
 1573    struct name *tolist, *np, **npp;
 1574    NYD_ENTER;
 1575 
 1576    tolist = cat(hp->h_to, cat(hp->h_cc, cat(hp->h_bcc, hp->h_fcc)));
 1577    hp->h_to = hp->h_cc = hp->h_bcc = hp->h_fcc = NULL;
 1578 
 1579    tolist = usermap(tolist, strip_alternates/*metoo*/);
 1580    if(strip_alternates)
 1581       tolist = n_alternates_remove(tolist, TRU1);
 1582    tolist = elide(checkaddrs(tolist, eacm, set_on_error));
 1583 
 1584    for (np = tolist; np != NULL; np = np->n_flink) {
 1585       switch (np->n_type & (GDEL | GMASK)) {
 1586       case GTO:   npp = &hp->h_to; break;
 1587       case GCC:   npp = &hp->h_cc; break;
 1588       case GBCC:  npp = &hp->h_bcc; break;
 1589       default:    continue;
 1590       }
 1591       *npp = cat(*npp, ndup(np, np->n_type | GFULL));
 1592    }
 1593    NYD_LEAVE;
 1594    return tolist;
 1595 }
 1596 
 1597 FL struct name *
 1598 usermap(struct name *names, bool_t force_metoo){
 1599    struct a_nag_group *ngp;
 1600    struct name *nlist, *nlist_tail, *np, *cp;
 1601    int metoo;
 1602    NYD_ENTER;
 1603 
 1604    metoo = (force_metoo || ok_blook(metoo));
 1605    nlist = nlist_tail = NULL;
 1606    np = names;
 1607 
 1608    for(; np != NULL; np = cp){
 1609       assert(!(np->n_type & GDEL)); /* TODO legacy */
 1610       cp = np->n_flink;
 1611 
 1612       if(is_fileorpipe_addr(np) ||
 1613             (ngp = a_nag_group_find(a_NAG_T_ALIAS, np->n_name)) == NULL){
 1614          if((np->n_blink = nlist_tail) != NULL)
 1615             nlist_tail->n_flink = np;
 1616          else
 1617             nlist = np;
 1618          nlist_tail = np;
 1619          np->n_flink = NULL;
 1620       }else{
 1621          nlist = a_nag_gexpand(0, nlist, ngp, metoo, np->n_type);
 1622          if((nlist_tail = nlist) != NULL)
 1623             while(nlist_tail->n_flink != NULL)
 1624                nlist_tail = nlist_tail->n_flink;
 1625       }
 1626    }
 1627    NYD_LEAVE;
 1628    return nlist;
 1629 }
 1630 
 1631 FL struct name *
 1632 elide(struct name *names)
 1633 {
 1634    size_t i, j, k;
 1635    struct name *nlist, *np, **nparr;
 1636    NYD_ENTER;
 1637 
 1638    nlist = NULL;
 1639 
 1640    if(names == NULL)
 1641       goto jleave;
 1642 
 1643    /* Throw away all deleted nodes */
 1644    for(np = NULL, i = 0; names != NULL; names = names->n_flink)
 1645       if(!(names->n_type & GDEL)){
 1646          names->n_blink = np;
 1647          if(np != NULL)
 1648             np->n_flink = names;
 1649          else
 1650             nlist = names;
 1651          np = names;
 1652          ++i;
 1653       }
 1654    if(nlist == NULL || i == 1)
 1655       goto jleave;
 1656    np->n_flink = NULL;
 1657 
 1658    /* Create a temporay array and sort that */
 1659    nparr = n_lofi_alloc(sizeof(*nparr) * i);
 1660 
 1661    for(i = 0, np = nlist; np != NULL; np = np->n_flink)
 1662       nparr[i++] = np;
 1663 
 1664    qsort(nparr, i, sizeof *nparr, &a_nag_elide_qsort);
 1665 
 1666    /* Remove duplicates XXX speedup, or list_uniq()! */
 1667    for(j = 0, --i; j < i;){
 1668       if(asccasecmp(nparr[j]->n_name, nparr[k = j + 1]->n_name))
 1669          ++j;
 1670       else{
 1671          for(; k < i; ++k)
 1672             nparr[k] = nparr[k + 1];
 1673          --i;
 1674       }
 1675    }
 1676 
 1677    /* Throw away all list members which are not part of the array.
 1678     * Note this keeps the original, possibly carefully crafted, order of the
 1679     * addressees, thus */
 1680    for(np = nlist; np != NULL; np = np->n_flink){
 1681       for(j = 0; j <= i; ++j)
 1682          if(np == nparr[j]){
 1683             nparr[j] = NULL;
 1684             goto jiter;
 1685          }
 1686       /* Drop it */
 1687       if(np == nlist){
 1688          nlist = np->n_flink;
 1689          np->n_blink = NULL;
 1690       }else
 1691          np->n_blink->n_flink = np->n_flink;
 1692       if(np->n_flink != NULL)
 1693          np->n_flink->n_blink = np->n_blink;
 1694 jiter:;
 1695    }
 1696 
 1697    n_lofi_free(nparr);
 1698 jleave:
 1699    NYD_LEAVE;
 1700    return nlist;
 1701 }
 1702 
 1703 FL int
 1704 c_alternates(void *vp){
 1705    struct a_nag_group *ngp;
 1706    char const *varname, *ccp;
 1707    char **argv;
 1708    NYD_ENTER;
 1709 
 1710    n_pstate_err_no = n_ERR_NONE;
 1711 
 1712    argv = vp;
 1713    varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
 1714 
 1715    if(*argv == NULL){
 1716       if(!a_nag_group_print_all(a_NAG_T_ALTERNATES, varname))
 1717          vp = NULL;
 1718    }else{
 1719       if(varname != NULL)
 1720          n_err(_("`alternates': `vput' only supported for show mode\n"));
 1721 
 1722       /* Delete the old set to "declare a list", if *posix* */
 1723       if(ok_blook(posix))
 1724          a_nag_group_del(a_NAG_T_ALTERNATES, n_star);
 1725 
 1726       while((ccp = *argv++) != NULL){
 1727          size_t l;
 1728          struct name *np;
 1729 
 1730          if((np = lextract(ccp, GSKIN)) == NULL || np->n_flink != NULL ||
 1731                (np = checkaddrs(np, EACM_STRICT, NULL)) == NULL){
 1732             n_err(_("Invalid `alternates' argument: %s\n"),
 1733                n_shexp_quote_cp(ccp, FAL0));
 1734             n_pstate_err_no = n_ERR_INVAL;
 1735             vp = NULL;
 1736             continue;
 1737          }
 1738          ccp = np->n_name;
 1739 
 1740          l = strlen(ccp) +1;
 1741          if((ngp = a_nag_group_fetch(a_NAG_T_ALTERNATES, ccp, l)) == NULL){
 1742             n_err(_("Failed to create storage for alternates: %s\n"),
 1743                n_shexp_quote_cp(ccp, FAL0));
 1744             n_pstate_err_no = n_ERR_NOMEM;
 1745             vp = NULL;
 1746          }
 1747       }
 1748    }
 1749    NYD_LEAVE;
 1750    return (vp != NULL ? 0 : 1);
 1751 }
 1752 
 1753 FL int
 1754 c_unalternates(void *vp){
 1755    char **argv;
 1756    int rv;
 1757    NYD_ENTER;
 1758 
 1759    rv = 0;
 1760    argv = vp;
 1761 
 1762    do if(!a_nag_group_del(a_NAG_T_ALTERNATES, *argv)){
 1763       n_err(_("No such `alternates': %s\n"), n_shexp_quote_cp(*argv, FAL0));
 1764       rv = 1;
 1765    }while(*++argv != NULL);
 1766    NYD_LEAVE;
 1767    return rv;
 1768 }
 1769 
 1770 FL struct name *
 1771 n_alternates_remove(struct name *np, bool_t keep_single){
 1772    /* XXX keep a single pointer, initial null, and immediate remove nodes
 1773     * XXX on successful match unless keep single and that pointer null! */
 1774    struct a_nag_group_lookup ngl;
 1775    struct a_nag_group *ngp;
 1776    struct name *xp, *newnp;
 1777    NYD_ENTER;
 1778 
 1779    /* Delete the temporary bit from all */
 1780    for(xp = np; xp != NULL; xp = xp->n_flink)
 1781       xp->n_flags &= ~(ui32_t)SI32_MIN;
 1782 
 1783    /* Mark all possible alternate names (xxx sic: instead walk over namelist
 1784     * and hash-lookup alternate instead (unless *allnet*) */
 1785    for(ngp = a_nag_group_go_first(a_NAG_T_ALTERNATES, &ngl); ngp != NULL;
 1786          ngp = a_nag_group_go_next(&ngl))
 1787       np = a_nag_namelist_mark_name(np, ngp->ng_id);
 1788 
 1789    np = a_nag_namelist_mark_name(np, ok_vlook(LOGNAME));
 1790 
 1791    if((xp = extract(ok_vlook(sender), GEXTRA | GSKIN)) != NULL){
 1792       /* TODO check_from_and_sender(): drop; *sender*: only one name!
 1793        * TODO At assignment time, as VIP var? */
 1794       do
 1795          np = a_nag_namelist_mark_name(np, xp->n_name);
 1796       while((xp = xp->n_flink) != NULL);
 1797    }else for(xp = lextract(ok_vlook(from), GEXTRA | GSKIN); xp != NULL;
 1798          xp = xp->n_flink)
 1799       np = a_nag_namelist_mark_name(np, xp->n_name);
 1800 
 1801    /* C99 */{
 1802       char const *v15compat;
 1803 
 1804       if((v15compat = ok_vlook(replyto)) != NULL){
 1805          n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
 1806          for(xp = lextract(v15compat, GEXTRA | GSKIN); xp != NULL;
 1807                xp = xp->n_flink)
 1808             np = a_nag_namelist_mark_name(np, xp->n_name);
 1809       }
 1810    }
 1811 
 1812    for(xp = lextract(ok_vlook(reply_to), GEXTRA | GSKIN); xp != NULL;
 1813          xp = xp->n_flink)
 1814       np = a_nag_namelist_mark_name(np, xp->n_name);
 1815 
 1816    /* Clean the list by throwing away all deleted or marked (but one) nodes */
 1817    for(xp = newnp = NULL; np != NULL; np = np->n_flink){
 1818       if(np->n_type & GDEL)
 1819          continue;
 1820       if(np->n_flags & (ui32_t)SI32_MIN){
 1821          if(!keep_single)
 1822             continue;
 1823          keep_single = FAL0;
 1824       }
 1825 
 1826       np->n_blink = xp;
 1827       if(xp != NULL)
 1828          xp->n_flink = np;
 1829       else
 1830          newnp = np;
 1831       xp = np;
 1832       xp->n_flags &= ~(ui32_t)SI32_MIN;
 1833    }
 1834    if(xp != NULL)
 1835       xp->n_flink = NULL;
 1836    np = newnp;
 1837 
 1838    NYD_LEAVE;
 1839    return np;
 1840 }
 1841 
 1842 FL bool_t
 1843 n_is_myname(char const *name){
 1844    struct a_nag_group_lookup ngl;
 1845    struct a_nag_group *ngp;
 1846    struct name *xp;
 1847    NYD_ENTER;
 1848 
 1849    if(a_nag_is_same_name(ok_vlook(LOGNAME), name))
 1850       goto jleave;
 1851 
 1852    if(!ok_blook(allnet)){
 1853       if(a_nag_group_lookup(a_NAG_T_ALTERNATES, &ngl, name) != NULL)
 1854          goto jleave;
 1855    }else{
 1856       for(ngp = a_nag_group_go_first(a_NAG_T_ALTERNATES, &ngl); ngp != NULL;
 1857             ngp = a_nag_group_go_next(&ngl))
 1858          if(a_nag_is_same_name(ngp->ng_id, name))
 1859             goto jleave;
 1860    }
 1861 
 1862    for(xp = lextract(ok_vlook(from), GEXTRA | GSKIN); xp != NULL;
 1863          xp = xp->n_flink)
 1864       if(a_nag_is_same_name(xp->n_name, name))
 1865          goto jleave;
 1866 
 1867    /* C99 */{
 1868       char const *v15compat;
 1869 
 1870       if((v15compat = ok_vlook(replyto)) != NULL){
 1871          n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
 1872          for(xp = lextract(v15compat, GEXTRA | GSKIN); xp != NULL;
 1873                xp = xp->n_flink)
 1874             if(a_nag_is_same_name(xp->n_name, name))
 1875                goto jleave;
 1876       }
 1877    }
 1878 
 1879    for(xp = lextract(ok_vlook(reply_to), GEXTRA | GSKIN); xp != NULL;
 1880          xp = xp->n_flink)
 1881       if(a_nag_is_same_name(xp->n_name, name))
 1882          goto jleave;
 1883 
 1884    for(xp = extract(ok_vlook(sender), GEXTRA | GSKIN); xp != NULL;
 1885          xp = xp->n_flink)
 1886       if(a_nag_is_same_name(xp->n_name, name))
 1887          goto jleave;
 1888 
 1889    name = NULL;
 1890 jleave:
 1891    NYD_LEAVE;
 1892    return (name != NULL);
 1893 }
 1894 
 1895 FL int
 1896 c_addrcodec(void *vp){
 1897    struct n_addrguts ag;
 1898    struct str trims;
 1899    struct n_string s_b, *sp;
 1900    size_t alen;
 1901    int mode;
 1902    char const **argv, *varname, *act, *cp;
 1903    NYD_ENTER;
 1904 
 1905    sp = n_string_creat_auto(&s_b);
 1906    argv = vp;
 1907    varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
 1908 
 1909    act = *argv;
 1910    for(cp = act; *cp != '\0' && !blankspacechar(*cp); ++cp)
 1911       ;
 1912    mode = 0;
 1913    if(*act == '+')
 1914       mode = 1, ++act;
 1915    if(*act == '+')
 1916       mode = 2, ++act;
 1917    if(*act == '+')
 1918       mode = 3, ++act;
 1919    if(act >= cp)
 1920       goto jesynopsis;
 1921    alen = PTR2SIZE(cp - act);
 1922    if(*cp != '\0')
 1923       ++cp;
 1924 
 1925    trims.l = strlen(trims.s = n_UNCONST(cp));
 1926    cp = savestrbuf(n_str_trim(&trims, n_STR_TRIM_BOTH)->s, trims.l);
 1927    if(trims.l <= UIZ_MAX / 4)
 1928          trims.l <<= 1;
 1929    sp = n_string_reserve(sp, trims.l);
 1930 
 1931    n_pstate_err_no = n_ERR_NONE;
 1932 
 1933    if(is_ascncaseprefix(act, "encode", alen)){
 1934       /* This function cannot be a simple nalloc() wrapper even later on, since
 1935        * we may need to turn any ", () or \ into quoted-pairs */
 1936       char c;
 1937 
 1938       while((c = *cp++) != '\0'){
 1939          if(((c == '(' || c == ')') && mode < 1) || (c == '"' && mode < 2) ||
 1940                (c == '\\' && mode < 3))
 1941             sp = n_string_push_c(sp, '\\');
 1942          sp = n_string_push_c(sp, c);
 1943       }
 1944 
 1945       if(n_addrspec_with_guts(&ag, n_string_cp(sp), TRU1, TRU1) == NULL ||
 1946             (ag.ag_n_flags & (NAME_ADDRSPEC_ISADDR | NAME_ADDRSPEC_INVALID)
 1947                ) != NAME_ADDRSPEC_ISADDR){
 1948          cp = sp->s_dat;
 1949          n_pstate_err_no = n_ERR_INVAL;
 1950          vp = NULL;
 1951       }else{
 1952          struct name *np;
 1953 
 1954          np = nalloc(ag.ag_input, GTO | GFULL | GSKIN);
 1955          cp = np->n_fullname;
 1956       }
 1957    }else if(mode == 0){
 1958       if(is_ascncaseprefix(act, "decode", alen)){
 1959          char c;
 1960 
 1961          while((c = *cp++) != '\0'){
 1962             switch(c){
 1963             case '(':
 1964                sp = n_string_push_c(sp, '(');
 1965                act = skip_comment(cp);
 1966                if(--act > cp)
 1967                   sp = n_string_push_buf(sp, cp, PTR2SIZE(act - cp));
 1968                sp = n_string_push_c(sp, ')');
 1969                cp = ++act;
 1970                break;
 1971             case '"':
 1972                while(*cp != '\0'){
 1973                   if((c = *cp++) == '"')
 1974                      break;
 1975                   if(c == '\\' && (c = *cp) != '\0')
 1976                      ++cp;
 1977                   sp = n_string_push_c(sp, c);
 1978                }
 1979                break;
 1980             default:
 1981                if(c == '\\' && (c = *cp++) == '\0')
 1982                   break;
 1983                sp = n_string_push_c(sp, c);
 1984                break;
 1985             }
 1986          }
 1987          cp = n_string_cp(sp);
 1988       }else if(is_ascncaseprefix(act, "skin", alen) ||
 1989             (mode = 1, is_ascncaseprefix(act, "skinlist", alen))){
 1990          /* Let's just use the is-single-address hack for this one, too.. */
 1991          if(n_addrspec_with_guts(&ag, cp, TRU1, TRU1) == NULL ||
 1992                (ag.ag_n_flags & (NAME_ADDRSPEC_ISADDR | NAME_ADDRSPEC_INVALID)
 1993                   ) != NAME_ADDRSPEC_ISADDR){
 1994             n_pstate_err_no = n_ERR_INVAL;
 1995             vp = NULL;
 1996          }else{
 1997             struct name *np;
 1998 
 1999             np = nalloc(ag.ag_input, GTO | GFULL | GSKIN);
 2000             cp = np->n_name;
 2001 
 2002             if(mode == 1 && is_mlist(cp, FAL0) != MLIST_OTHER)
 2003                n_pstate_err_no = n_ERR_EXIST;
 2004          }
 2005       }else
 2006          goto jesynopsis;
 2007    }else
 2008       goto jesynopsis;
 2009 
 2010    if(varname == NULL){
 2011       if(fprintf(n_stdout, "%s\n", cp) < 0){
 2012          n_pstate_err_no = n_err_no;
 2013          vp = NULL;
 2014       }
 2015    }else if(!n_var_vset(varname, (uintptr_t)cp)){
 2016       n_pstate_err_no = n_ERR_NOTSUP;
 2017       vp = NULL;
 2018    }
 2019 
 2020 jleave:
 2021    NYD_LEAVE;
 2022    return (vp != NULL ? 0 : 1);
 2023 jesynopsis:
 2024    n_err(_("Synopsis: addrcodec: <[+[+[+]]]e[ncode]|d[ecode]|s[kin]> "
 2025       "<rest-of-line>\n"));
 2026    n_pstate_err_no = n_ERR_INVAL;
 2027    vp = NULL;
 2028    goto jleave;
 2029 }
 2030 
 2031 FL int
 2032 c_commandalias(void *vp){
 2033    struct a_nag_group *ngp;
 2034    char const **argv, *ccp;
 2035    int rv;
 2036    NYD_ENTER;
 2037 
 2038    rv = 0;
 2039    argv = vp;
 2040 
 2041    if((ccp = *argv) == NULL){
 2042       a_nag_group_print_all(a_NAG_T_COMMANDALIAS, NULL);
 2043       goto jleave;
 2044    }
 2045 
 2046    /* Verify the name is a valid one, and not a command modifier.
 2047     * NOTE: this list duplicates settings isolated somewhere else (go.c) */
 2048    if(*ccp == '\0' || *n_cmd_isolate(ccp) != '\0' ||
 2049          !asccasecmp(ccp, "ignerr") || !asccasecmp(ccp, "local") ||
 2050          !asccasecmp(ccp, "wysh") || !asccasecmp(ccp, "vput") ||
 2051          !asccasecmp(ccp, "scope") || !asccasecmp(ccp, "u")){
 2052       n_err(_("`commandalias': not a valid command name: %s\n"),
 2053          n_shexp_quote_cp(ccp, FAL0));
 2054       rv = 1;
 2055       goto jleave;
 2056    }
 2057 
 2058    if(argv[1] == NULL){
 2059       if((ngp = a_nag_group_find(a_NAG_T_COMMANDALIAS, ccp)) != NULL)
 2060          a_nag_group_print(ngp, n_stdout, NULL);
 2061       else{
 2062          n_err(_("No such commandalias: %s\n"), n_shexp_quote_cp(ccp, FAL0));
 2063          rv = 1;
 2064       }
 2065    }else{
 2066       /* Because one hardly ever redefines, anything is stored in one chunk */
 2067       char *cp;
 2068       size_t i, len;
 2069 
 2070       /* Delete the old one, if any; don't get fooled to remove them all */
 2071       if(ccp[0] != '*' || ccp[1] != '\0')
 2072          a_nag_group_del(a_NAG_T_COMMANDALIAS, ccp);
 2073 
 2074       for(i = len = 0, ++argv; argv[i] != NULL; ++i)
 2075          len += strlen(argv[i]) + 1;
 2076       if(len == 0)
 2077          len = 1;
 2078 
 2079       if((ngp = a_nag_group_fetch(a_NAG_T_COMMANDALIAS, ccp, len)) == NULL){
 2080          n_err(_("Failed to create storage for commandalias: %s\n"),
 2081             n_shexp_quote_cp(ccp, FAL0));
 2082          rv = 1;
 2083       }else{
 2084          struct a_nag_cmd_alias *ncap;
 2085 
 2086          a_NAG_GP_TO_SUBCLASS(ncap, ngp);
 2087          a_NAG_GP_TO_SUBCLASS(cp, ngp);
 2088          cp += sizeof *ncap;
 2089          ncap->nca_expand.s = cp;
 2090          ncap->nca_expand.l = len - 1;
 2091 
 2092          for(len = 0; (ccp = *argv++) != NULL;)
 2093             if((i = strlen(ccp)) > 0){
 2094                if(len++ != 0)
 2095                   *cp++ = ' ';
 2096                memcpy(cp, ccp, i);
 2097                cp += i;
 2098             }
 2099          *cp = '\0';
 2100       }
 2101    }
 2102 jleave:
 2103    NYD_LEAVE;
 2104    return rv;
 2105 }
 2106 
 2107 FL int
 2108 c_uncommandalias(void *vp){
 2109    char **argv;
 2110    int rv;
 2111    NYD_ENTER;
 2112 
 2113    rv = 0;
 2114    argv = vp;
 2115 
 2116    do if(!a_nag_group_del(a_NAG_T_COMMANDALIAS, *argv)){
 2117       n_err(_("No such `commandalias': %s\n"), n_shexp_quote_cp(*argv, FAL0));
 2118       rv = 1;
 2119    }while(*++argv != NULL);
 2120    NYD_LEAVE;
 2121    return rv;
 2122 }
 2123 
 2124 FL char const *
 2125 n_commandalias_exists(char const *name, struct str const **expansion_or_null){
 2126    struct a_nag_group *ngp;
 2127    NYD_ENTER;
 2128 
 2129    if((ngp = a_nag_group_find(a_NAG_T_COMMANDALIAS, name)) != NULL){
 2130       name = ngp->ng_id;
 2131 
 2132       if(expansion_or_null != NULL){
 2133          struct a_nag_cmd_alias *ncap;
 2134 
 2135          a_NAG_GP_TO_SUBCLASS(ncap, ngp);
 2136          *expansion_or_null = &ncap->nca_expand;
 2137       }
 2138    }else
 2139       name = NULL;
 2140    NYD_LEAVE;
 2141    return name;
 2142 }
 2143 
 2144 FL bool_t
 2145 n_alias_is_valid_name(char const *name){
 2146    char c;
 2147    char const *cp;
 2148    bool_t rv;
 2149    NYD2_ENTER;
 2150 
 2151    for(rv = TRU1, cp = name++; (c = *cp++) != '\0';)
 2152       /* User names, plus things explicitly mentioned in Postfix aliases(5),
 2153        * i.e., [[:alnum:]_#:@.-]+$?.
 2154        * As extensions allow high-bit bytes, semicolon and period. */
 2155       /* TODO n_alias_is_valid_name(): locale dependent validity check,
 2156        * TODO with Unicode prefix valid UTF-8! */
 2157       if(!alnumchar(c) && c != '_' && c != '-' &&
 2158             c != '#' && c != ':' && c != '@' &&
 2159             !((ui8_t)c & 0x80) && c != '!' && c != '.'){
 2160          if(c == '$' && cp != name && *cp == '\0')
 2161             break;
 2162          rv = FAL0;
 2163          break;
 2164       }
 2165    NYD2_LEAVE;
 2166    return rv;
 2167 }
 2168 
 2169 FL int
 2170 c_alias(void *v)
 2171 {
 2172    char const *ecp;
 2173    char **argv;
 2174    struct a_nag_group *ngp;
 2175    int rv;
 2176    NYD_ENTER;
 2177 
 2178    rv = 0;
 2179    argv = v;
 2180    n_UNINIT(ecp, NULL);
 2181 
 2182    if(*argv == NULL)
 2183       a_nag_group_print_all(a_NAG_T_ALIAS, NULL);
 2184    else if(!n_alias_is_valid_name(*argv)){
 2185       ecp = N_("Not a valid alias name: %s\n");
 2186       goto jerr;
 2187    }else if(argv[1] == NULL){
 2188       if((ngp = a_nag_group_find(a_NAG_T_ALIAS, *argv)) != NULL)
 2189          a_nag_group_print(ngp, n_stdout, NULL);
 2190       else{
 2191          ecp = N_("No such alias: %s\n");
 2192          goto jerr;
 2193       }
 2194    }else if((ngp = a_nag_group_fetch(a_NAG_T_ALIAS, *argv, 0)) == NULL){
 2195       ecp = N_("Failed to create alias storage for: %s\n");
 2196 jerr:
 2197       n_err(V_(ecp), n_shexp_quote_cp(*argv, FAL0));
 2198       rv = 1;
 2199    }else{
 2200       struct a_nag_grp_names *ngnp_tail, *ngnp;
 2201       struct a_nag_grp_names_head *ngnhp;
 2202 
 2203       a_NAG_GP_TO_SUBCLASS(ngnhp, ngp);
 2204 
 2205       if((ngnp_tail = ngnhp->ngnh_head) != NULL)
 2206          while((ngnp = ngnp_tail->ngn_next) != NULL)
 2207             ngnp_tail = ngnp;
 2208 
 2209       for(++argv; *argv != NULL; ++argv){
 2210          size_t i;
 2211 
 2212          i = strlen(*argv) +1;
 2213          ngnp = n_alloc(n_VSTRUCT_SIZEOF(struct a_nag_grp_names, ngn_id) + i);
 2214          if(ngnp_tail != NULL)
 2215             ngnp_tail->ngn_next = ngnp;
 2216          else
 2217             ngnhp->ngnh_head = ngnp;
 2218          ngnp_tail = ngnp;
 2219          ngnp->ngn_next = NULL;
 2220          memcpy(ngnp->ngn_id, *argv, i);
 2221       }
 2222    }
 2223    NYD_LEAVE;
 2224    return rv;
 2225 }
 2226 
 2227 FL int
 2228 c_unalias(void *v){
 2229    char **argv;
 2230    int rv;
 2231    NYD_ENTER;
 2232 
 2233    rv = 0;
 2234    argv = v;
 2235 
 2236    do if(!a_nag_group_del(a_NAG_T_ALIAS, *argv)){
 2237       n_err(_("No such alias: %s\n"), *argv);
 2238       rv = 1;
 2239    }while(*++argv != NULL);
 2240    NYD_LEAVE;
 2241    return rv;
 2242 }
 2243 
 2244 FL int
 2245 c_mlist(void *v){
 2246    int rv;
 2247    NYD_ENTER;
 2248 
 2249    rv = a_nag_mlmux(a_NAG_T_MLIST, v);
 2250    NYD_LEAVE;
 2251    return rv;
 2252 }
 2253 
 2254 FL int
 2255 c_unmlist(void *v){
 2256    int rv;
 2257    NYD_ENTER;
 2258 
 2259    rv = a_nag_unmlmux(a_NAG_T_MLIST, v);
 2260    NYD_LEAVE;
 2261    return rv;
 2262 }
 2263 
 2264 FL int
 2265 c_mlsubscribe(void *v){
 2266    int rv;
 2267    NYD_ENTER;
 2268 
 2269    rv = a_nag_mlmux(a_NAG_T_MLIST | a_NAG_T_SUBSCRIBE, v);
 2270    NYD_LEAVE;
 2271    return rv;
 2272 }
 2273 
 2274 FL int
 2275 c_unmlsubscribe(void *v){
 2276    int rv;
 2277    NYD_ENTER;
 2278 
 2279    rv = a_nag_unmlmux(a_NAG_T_MLIST | a_NAG_T_SUBSCRIBE, v);
 2280    NYD_LEAVE;
 2281    return rv;
 2282 }
 2283 
 2284 FL enum mlist_state
 2285 is_mlist(char const *name, bool_t subscribed_only){
 2286    struct a_nag_group *ngp;
 2287 #ifdef HAVE_REGEX
 2288    struct a_nag_grp_regex **lpp, *ngrp;
 2289    bool_t re2;
 2290 #endif
 2291    enum mlist_state rv;
 2292    NYD_ENTER;
 2293 
 2294    ngp = a_nag_group_find(a_NAG_T_MLIST, name);
 2295    rv = (ngp != NULL) ? MLIST_KNOWN : MLIST_OTHER;
 2296 
 2297    if(rv == MLIST_KNOWN){
 2298       if(ngp->ng_type & a_NAG_T_SUBSCRIBE)
 2299          rv = MLIST_SUBSCRIBED;
 2300       else if(subscribed_only)
 2301          rv = MLIST_OTHER;
 2302       /* Of course, if that is a regular expression it doesn't mean a thing */
 2303 #ifdef HAVE_REGEX
 2304       if(ngp->ng_type & a_NAG_T_REGEX)
 2305          rv = MLIST_OTHER;
 2306       else
 2307 #endif
 2308          goto jleave;
 2309    }
 2310 
 2311    /* Not in the hashmap (as something matchable), walk the lists */
 2312 #ifdef HAVE_REGEX
 2313    re2 = FAL0;
 2314    lpp = &a_nag_mlsub_regex;
 2315 
 2316 jregex_redo:
 2317    if((ngrp = *lpp) != NULL){
 2318       do if(regexec(&ngrp->ngr_regex, name, 0,NULL, 0) != REG_NOMATCH){
 2319          /* Relink as the head of this list if the hit count of this group is
 2320           * >= 25% of the average hit count */
 2321          size_t i;
 2322 
 2323          if(!re2)
 2324             i = ++a_nag_mlsub_hits / a_nag_mlsub_size;
 2325          else
 2326             i = ++a_nag_mlist_hits / a_nag_mlist_size;
 2327          i >>= 2;
 2328 
 2329          if(++ngrp->ngr_hits >= i && *lpp != ngrp && ngrp->ngr_next != ngrp){
 2330             ngrp->ngr_last->ngr_next = ngrp->ngr_next;
 2331             ngrp->ngr_next->ngr_last = ngrp->ngr_last;
 2332             (ngrp->ngr_last = (*lpp)->ngr_last)->ngr_next = ngrp;
 2333             (ngrp->ngr_next = *lpp)->ngr_last = ngrp;
 2334             *lpp = ngrp;
 2335          }
 2336          rv = !re2 ? MLIST_SUBSCRIBED : MLIST_KNOWN;
 2337          goto jleave;
 2338       }while((ngrp = ngrp->ngr_next) != *lpp);
 2339    }
 2340 
 2341    if(!re2 && !subscribed_only){
 2342       re2 = TRU1;
 2343       lpp = &a_nag_mlist_regex;
 2344       goto jregex_redo;
 2345    }
 2346    assert(rv == MLIST_OTHER);
 2347 #endif /* HAVE_REGEX */
 2348 
 2349 jleave:
 2350    NYD_LEAVE;
 2351    return rv;
 2352 }
 2353 
 2354 FL enum mlist_state
 2355 is_mlist_mp(struct message *mp, enum mlist_state what){
 2356    struct name *np;
 2357    bool_t cc;
 2358    enum mlist_state rv;
 2359    NYD_ENTER;
 2360 
 2361    rv = MLIST_OTHER;
 2362 
 2363    cc = FAL0;
 2364    np = lextract(hfield1("to", mp), GTO | GSKIN);
 2365 jredo:
 2366    for(; np != NULL; np = np->n_flink){
 2367       switch(is_mlist(np->n_name, FAL0)){
 2368       case MLIST_OTHER:
 2369          break;
 2370       case MLIST_KNOWN:
 2371          if(what == MLIST_KNOWN || what == MLIST_OTHER){
 2372             if(rv == MLIST_OTHER)
 2373                rv = MLIST_KNOWN;
 2374             if(what == MLIST_KNOWN)
 2375                goto jleave;
 2376          }
 2377          break;
 2378       case MLIST_SUBSCRIBED:
 2379          if(what == MLIST_SUBSCRIBED || what == MLIST_OTHER){
 2380             if(rv != MLIST_SUBSCRIBED)
 2381                rv = MLIST_SUBSCRIBED;
 2382             goto jleave;
 2383          }
 2384          break;
 2385       }
 2386    }
 2387 
 2388    if(!cc){
 2389       cc = TRU1;
 2390       np = lextract(hfield1("cc", mp), GCC | GSKIN);
 2391       goto jredo;
 2392    }
 2393 jleave:
 2394    NYD_LEAVE;
 2395    return rv;
 2396 }
 2397 
 2398 FL int
 2399 c_shortcut(void *vp){
 2400    struct a_nag_group *ngp;
 2401    char **argv;
 2402    int rv;
 2403    NYD_ENTER;
 2404 
 2405    rv = 0;
 2406    argv = vp;
 2407 
 2408    if(*argv == NULL)
 2409       a_nag_group_print_all(a_NAG_T_SHORTCUT, NULL);
 2410    else if(argv[1] == NULL){
 2411       if((ngp = a_nag_group_find(a_NAG_T_SHORTCUT, *argv)) != NULL)
 2412          a_nag_group_print(ngp, n_stdout, NULL);
 2413       else{
 2414          n_err(_("No such shortcut: %s\n"), n_shexp_quote_cp(*argv, FAL0));
 2415          rv = 1;
 2416       }
 2417    }else for(; *argv != NULL; argv += 2){
 2418       /* Because one hardly ever redefines, anything is stored in one chunk */
 2419       size_t l;
 2420       char *cp;
 2421 
 2422       if(argv[1] == NULL){
 2423          n_err(_("Synopsis: shortcut: <shortcut> <expansion>\n"));
 2424          rv = 1;
 2425          break;
 2426       }
 2427       if(a_nag_group_find(a_NAG_T_SHORTCUT, *argv) != NULL)
 2428          a_nag_group_del(a_NAG_T_SHORTCUT, *argv);
 2429 
 2430       l = strlen(argv[1]) +1;
 2431       if((ngp = a_nag_group_fetch(a_NAG_T_SHORTCUT, *argv, l)) == NULL){
 2432          n_err(_("Failed to create storage for shortcut: %s\n"),
 2433             n_shexp_quote_cp(*argv, FAL0));
 2434          rv = 1;
 2435       }else{
 2436          a_NAG_GP_TO_SUBCLASS(cp, ngp);
 2437          memcpy(cp, argv[1], l);
 2438       }
 2439    }
 2440    NYD_LEAVE;
 2441    return rv;
 2442 }
 2443 
 2444 FL int
 2445 c_unshortcut(void *vp){
 2446    char **argv;
 2447    int rv;
 2448    NYD_ENTER;
 2449 
 2450    rv = 0;
 2451    argv = vp;
 2452 
 2453    do if(!a_nag_group_del(a_NAG_T_SHORTCUT, *argv)){
 2454       n_err(_("No such shortcut: %s\n"), *argv);
 2455       rv = 1;
 2456    }while(*++argv != NULL);
 2457    NYD_LEAVE;
 2458    return rv;
 2459 }
 2460 
 2461 FL char const *
 2462 shortcut_expand(char const *str){
 2463    struct a_nag_group *ngp;
 2464    NYD_ENTER;
 2465 
 2466    if((ngp = a_nag_group_find(a_NAG_T_SHORTCUT, str)) != NULL)
 2467       a_NAG_GP_TO_SUBCLASS(str, ngp);
 2468    else
 2469       str = NULL;
 2470    NYD_LEAVE;
 2471    return str;
 2472 }
 2473 
 2474 FL int
 2475 c_charsetalias(void *vp){
 2476    struct a_nag_group *ngp;
 2477    char **argv;
 2478    int rv;
 2479    NYD_ENTER;
 2480 
 2481    rv = 0;
 2482    argv = vp;
 2483 
 2484    if(*argv == NULL)
 2485       a_nag_group_print_all(a_NAG_T_CHARSETALIAS, NULL);
 2486    else if(argv[1] == NULL){
 2487       if((ngp = a_nag_group_find(a_NAG_T_CHARSETALIAS, *argv)) != NULL)
 2488          a_nag_group_print(ngp, n_stdout, NULL);
 2489       else{
 2490          n_err(_("No such charsetalias: %s\n"), n_shexp_quote_cp(*argv, FAL0));
 2491          rv = 1;
 2492       }
 2493    }else for(; *argv != NULL; argv += 2){
 2494       /* Because one hardly ever redefines, anything is stored in one chunk */
 2495       char *cp;
 2496       size_t dstl;
 2497       char const *dst, *src;
 2498 
 2499       if((dst = argv[1]) == NULL){
 2500          n_err(_("Synopsis: charsetalias: <charset> <charset-alias>\n"));
 2501          rv = 1;
 2502          break;
 2503       }else if((dst = n_iconv_normalize_name(dst)) == NULL){
 2504          n_err(_("charsetalias: invalid target charset %s\n"),
 2505             n_shexp_quote_cp(argv[1], FAL0));
 2506          rv = 1;
 2507          continue;
 2508       }else if((src = n_iconv_normalize_name(argv[0])) == NULL){
 2509          n_err(_("charsetalias: invalid source charset %s\n"),
 2510             n_shexp_quote_cp(argv[0], FAL0));
 2511          rv = 1;
 2512          continue;
 2513       }
 2514 
 2515       /* Delete the old one, if any; don't get fooled to remove them all */
 2516       if(src[0] != '*' || src[1] != '\0')
 2517          a_nag_group_del(a_NAG_T_CHARSETALIAS, src);
 2518 
 2519       dstl = strlen(dst) +1;
 2520       if((ngp = a_nag_group_fetch(a_NAG_T_CHARSETALIAS, src, dstl)) == NULL){
 2521          n_err(_("Failed to create storage for charsetalias: %s\n"),
 2522             n_shexp_quote_cp(src, FAL0));
 2523          rv = 1;
 2524       }else{
 2525          a_NAG_GP_TO_SUBCLASS(cp, ngp);
 2526          memcpy(cp, dst, dstl);
 2527       }
 2528    }
 2529    NYD_LEAVE;
 2530    return rv;
 2531 }
 2532 
 2533 FL int
 2534 c_uncharsetalias(void *vp){
 2535    char **argv, *cp;
 2536    int rv;
 2537    NYD_ENTER;
 2538 
 2539    rv = 0;
 2540    argv = vp;
 2541 
 2542    do{
 2543       if((cp = n_iconv_normalize_name(*argv)) == NULL ||
 2544             !a_nag_group_del(a_NAG_T_CHARSETALIAS, cp)){
 2545          n_err(_("No such `charsetalias': %s\n"),
 2546             n_shexp_quote_cp(*argv, FAL0));
 2547          rv = 1;
 2548       }
 2549    }while(*++argv != NULL);
 2550    NYD_LEAVE;
 2551    return rv;
 2552 }
 2553 
 2554 FL char const *
 2555 n_charsetalias_expand(char const *cp){
 2556    struct a_nag_group *ngp;
 2557    size_t i;
 2558    char const *cp_orig;
 2559    NYD_ENTER;
 2560 
 2561    cp_orig = cp;
 2562 
 2563    for(i = 0; (ngp = a_nag_group_find(a_NAG_T_CHARSETALIAS, cp)) != NULL;){
 2564       a_NAG_GP_TO_SUBCLASS(cp, ngp);
 2565       if(++i == 8) /* XXX Magic (same as for `ghost' expansion) */
 2566          break;
 2567    }
 2568 
 2569    if(cp != cp_orig)
 2570       cp = savestr(cp);
 2571    NYD_LEAVE;
 2572    return cp;
 2573 }
 2574 
 2575 FL int
 2576 c_filetype(void *vp){ /* TODO support automatic chains: .tar.gz -> .gz + .tar */
 2577    struct a_nag_group *ngp;
 2578    char **argv; /* TODO While there: let ! prefix mean: direct execlp(2) */
 2579    int rv;
 2580    NYD_ENTER;
 2581 
 2582    rv = 0;
 2583    argv = vp;
 2584 
 2585    if(*argv == NULL)
 2586       a_nag_group_print_all(a_NAG_T_FILETYPE, NULL);
 2587    else if(argv[1] == NULL){
 2588       if((ngp = a_nag_group_find(a_NAG_T_FILETYPE, *argv)) != NULL)
 2589          a_nag_group_print(ngp, n_stdout, NULL);
 2590       else{
 2591          n_err(_("No such filetype: %s\n"), n_shexp_quote_cp(*argv, FAL0));
 2592          rv = 1;
 2593       }
 2594    }else for(; *argv != NULL; argv += 3){
 2595       /* Because one hardly ever redefines, anything is stored in one chunk */
 2596       char const *ccp;
 2597       char *cp, c;
 2598       size_t llc, lsc;
 2599 
 2600       if(argv[1] == NULL || argv[2] == NULL){
 2601          n_err(_("Synopsis: filetype: <extension> <load-cmd> <save-cmd>\n"));
 2602          rv = 1;
 2603          break;
 2604       }
 2605 
 2606       /* Delete the old one, if any; don't get fooled to remove them all */
 2607       ccp = argv[0];
 2608       if(ccp[0] != '*' || ccp[1] != '\0')
 2609          a_nag_group_del(a_NAG_T_FILETYPE, ccp);
 2610 
 2611       /* Lowercase it all (for display purposes) */
 2612       cp = savestr(ccp);
 2613       ccp = cp;
 2614       while((c = *cp) != '\0')
 2615          *cp++ = lowerconv(c);
 2616 
 2617       llc = strlen(argv[1]) +1;
 2618       lsc = strlen(argv[2]) +1;
 2619       if(UIZ_MAX - llc <= lsc)
 2620          goto jenomem;
 2621 
 2622       if((ngp = a_nag_group_fetch(a_NAG_T_FILETYPE, ccp, llc + lsc)) == NULL){
 2623 jenomem:
 2624          n_err(_("Failed to create storage for filetype: %s\n"),
 2625             n_shexp_quote_cp(argv[0], FAL0));
 2626          rv = 1;
 2627       }else{
 2628          struct a_nag_file_type *nftp;
 2629 
 2630          a_NAG_GP_TO_SUBCLASS(nftp, ngp);
 2631          a_NAG_GP_TO_SUBCLASS(cp, ngp);
 2632          cp += sizeof *nftp;
 2633          memcpy(nftp->nft_load.s = cp, argv[1], llc);
 2634             cp += llc;
 2635             nftp->nft_load.l = --llc;
 2636          memcpy(nftp->nft_save.s = cp, argv[2], lsc);
 2637             /*cp += lsc;*/
 2638             nftp->nft_save.l = --lsc;
 2639       }
 2640    }
 2641    NYD_LEAVE;
 2642    return rv;
 2643 }
 2644 
 2645 FL int
 2646 c_unfiletype(void *vp){
 2647    char **argv;
 2648    int rv;
 2649    NYD_ENTER;
 2650 
 2651    rv = 0;
 2652    argv = vp;
 2653 
 2654    do if(!a_nag_group_del(a_NAG_T_FILETYPE, *argv)){
 2655       n_err(_("No such `filetype': %s\n"), n_shexp_quote_cp(*argv, FAL0));
 2656       rv = 1;
 2657    }while(*++argv != NULL);
 2658    NYD_LEAVE;
 2659    return rv;
 2660 }
 2661 
 2662 FL bool_t
 2663 n_filetype_trial(struct n_file_type *res_or_null, char const *file){
 2664    struct stat stb;
 2665    struct a_nag_group_lookup ngl;
 2666    struct n_string s, *sp;
 2667    struct a_nag_group const *ngp;
 2668    ui32_t l;
 2669    NYD2_ENTER;
 2670 
 2671    sp = n_string_creat_auto(&s);
 2672    sp = n_string_assign_cp(sp, file);
 2673    sp = n_string_push_c(sp, '.');
 2674    l = sp->s_len;
 2675 
 2676    for(ngp = a_nag_group_go_first(a_NAG_T_FILETYPE, &ngl); ngp != NULL;
 2677          ngp = a_nag_group_go_next(&ngl)){
 2678       sp = n_string_trunc(sp, l);
 2679       sp = n_string_push_buf(sp, ngp->ng_id,
 2680             ngp->ng_subclass_off - ngp->ng_id_len_sub);
 2681 
 2682       if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
 2683          if(res_or_null != NULL){
 2684             struct a_nag_file_type *nftp;
 2685 
 2686             a_NAG_GP_TO_SUBCLASS(nftp, ngp);
 2687             res_or_null->ft_ext_dat = ngp->ng_id;
 2688             res_or_null->ft_ext_len = ngp->ng_subclass_off - ngp->ng_id_len_sub;
 2689             res_or_null->ft_load_dat = nftp->nft_load.s;
 2690             res_or_null->ft_load_len = nftp->nft_load.l;
 2691             res_or_null->ft_save_dat = nftp->nft_save.s;
 2692             res_or_null->ft_save_len = nftp->nft_save.l;
 2693          }
 2694          goto jleave; /* TODO after v15 legacy drop: break; */
 2695       }
 2696    }
 2697 
 2698    /* TODO v15 legacy code: automatic file hooks for .{bz2,gz,xz},
 2699     * TODO but NOT supporting *file-hook-{load,save}-EXTENSION* */
 2700    ngp = (struct a_nag_group*)0x1;
 2701 
 2702    sp = n_string_trunc(sp, l);
 2703    sp = n_string_push_buf(sp, a_nag_OBSOLETE_xz.ft_ext_dat,
 2704          a_nag_OBSOLETE_xz.ft_ext_len);
 2705    if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
 2706       n_OBSOLETE(".xz support will vanish, please use the `filetype' command");
 2707       if(res_or_null != NULL)
 2708          *res_or_null = a_nag_OBSOLETE_xz;
 2709       goto jleave;
 2710    }
 2711 
 2712    sp = n_string_trunc(sp, l);
 2713    sp = n_string_push_buf(sp, a_nag_OBSOLETE_gz.ft_ext_dat,
 2714          a_nag_OBSOLETE_gz.ft_ext_len);
 2715    if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
 2716       n_OBSOLETE(".gz support will vanish, please use the `filetype' command");
 2717       if(res_or_null != NULL)
 2718          *res_or_null = a_nag_OBSOLETE_gz;
 2719       goto jleave;
 2720    }
 2721 
 2722    sp = n_string_trunc(sp, l);
 2723    sp = n_string_push_buf(sp, a_nag_OBSOLETE_bz2.ft_ext_dat,
 2724          a_nag_OBSOLETE_bz2.ft_ext_len);
 2725    if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
 2726       n_OBSOLETE(".bz2 support will vanish, please use the `filetype' command");
 2727       if(res_or_null != NULL)
 2728          *res_or_null = a_nag_OBSOLETE_bz2;
 2729       goto jleave;
 2730    }
 2731 
 2732    ngp = NULL;
 2733 
 2734 jleave:
 2735    NYD2_LEAVE;
 2736    return (ngp != NULL);
 2737 }
 2738 
 2739 FL bool_t
 2740 n_filetype_exists(struct n_file_type *res_or_null, char const *file){
 2741    char const *ext, *lext;
 2742    NYD2_ENTER;
 2743 
 2744    if((ext = strrchr(file, '/')) != NULL)
 2745       file = ++ext;
 2746 
 2747    for(lext = NULL; (ext = strchr(file, '.')) != NULL; lext = file = ext){
 2748       struct a_nag_group const *ngp;
 2749 
 2750       if((ngp = a_nag_group_find(a_NAG_T_FILETYPE, ++ext)) != NULL){
 2751          lext = ext;
 2752          if(res_or_null != NULL){
 2753             struct a_nag_file_type *nftp;
 2754 
 2755             a_NAG_GP_TO_SUBCLASS(nftp, ngp);
 2756             res_or_null->ft_ext_dat = ngp->ng_id;
 2757             res_or_null->ft_ext_len = ngp->ng_subclass_off - ngp->ng_id_len_sub;
 2758             res_or_null->ft_load_dat = nftp->nft_load.s;
 2759             res_or_null->ft_load_len = nftp->nft_load.l;
 2760             res_or_null->ft_save_dat = nftp->nft_save.s;
 2761             res_or_null->ft_save_len = nftp->nft_save.l;
 2762          }
 2763          goto jleave; /* TODO after v15 legacy drop: break; */
 2764       }
 2765    }
 2766 
 2767    /* TODO v15 legacy code: automatic file hooks for .{bz2,gz,xz},
 2768     * TODO as well as supporting *file-hook-{load,save}-EXTENSION* */
 2769    if(lext == NULL)
 2770       goto jleave;
 2771 
 2772    if(!asccasecmp(lext, "xz")){
 2773       n_OBSOLETE(".xz support will vanish, please use the `filetype' command");
 2774       if(res_or_null != NULL)
 2775          *res_or_null = a_nag_OBSOLETE_xz;
 2776       goto jleave;
 2777    }else if(!asccasecmp(lext, "gz")){
 2778       n_OBSOLETE(".gz support will vanish, please use the `filetype' command");
 2779       if(res_or_null != NULL)
 2780          *res_or_null = a_nag_OBSOLETE_gz;
 2781       goto jleave;
 2782    }else if(!asccasecmp(lext, "bz2")){
 2783       n_OBSOLETE(".bz2 support will vanish, please use the `filetype' command");
 2784       if(res_or_null != NULL)
 2785          *res_or_null = a_nag_OBSOLETE_bz2;
 2786       goto jleave;
 2787    }else{
 2788       char const *cload, *csave;
 2789       char *vbuf;
 2790       size_t l; 
 2791 
 2792 #undef a_X1
 2793 #define a_X1 "file-hook-load-"
 2794 #undef a_X2
 2795 #define a_X2 "file-hook-save-"
 2796       l = strlen(lext);
 2797       vbuf = n_lofi_alloc(l + n_MAX(sizeof(a_X1), sizeof(a_X2)));
 2798 
 2799       memcpy(vbuf, a_X1, sizeof(a_X1) -1);
 2800       memcpy(&vbuf[sizeof(a_X1) -1], lext, l);
 2801       vbuf[sizeof(a_X1) -1 + l] = '\0';
 2802       cload = n_var_vlook(vbuf, FAL0);
 2803 
 2804       memcpy(vbuf, a_X2, sizeof(a_X2) -1);
 2805       memcpy(&vbuf[sizeof(a_X2) -1], lext, l);
 2806       vbuf[sizeof(a_X2) -1 + l] = '\0';
 2807       csave = n_var_vlook(vbuf, FAL0);
 2808 
 2809 #undef a_X2
 2810 #undef a_X1
 2811       n_lofi_free(vbuf);
 2812 
 2813       if((csave != NULL) | (cload != NULL)){
 2814          n_OBSOLETE("*file-hook-{load,save}-EXTENSION* will vanish, "
 2815             "please use the `filetype' command");
 2816 
 2817          if(((csave != NULL) ^ (cload != NULL)) == 0){
 2818             if(res_or_null != NULL){
 2819                res_or_null->ft_ext_dat = lext;
 2820                res_or_null->ft_ext_len = l;
 2821                res_or_null->ft_load_dat = cload;
 2822                res_or_null->ft_load_len = strlen(cload);
 2823                res_or_null->ft_save_dat = csave;
 2824                res_or_null->ft_save_len = strlen(csave);
 2825             }
 2826             goto jleave;
 2827          }else
 2828             n_alert(_("Incomplete *file-hook-{load,save}-EXTENSION* for: .%s"),
 2829                lext);
 2830       }
 2831    }
 2832 
 2833    lext = NULL;
 2834 
 2835 jleave:
 2836    NYD2_LEAVE;
 2837    return (lext != NULL);
 2838 }
 2839 
 2840 /* s-it-mode */