"Fossies" - the Fresh Open Source Software Archive

Member "s-nail-14.9.7/nam-a-grp.c" (16 Feb 2018, 74952 Bytes) of package /linux/misc/s-nail-14.9.7.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.6_vs_14.9.7.

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