"Fossies" - the Fresh Open Source Software Archive

Member "s-nail-14.9.10/urlcrecry.c" (25 Mar 2018, 48301 Bytes) of package /linux/misc/s-nail-14.9.10.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 "urlcrecry.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 14.9.9_vs_14.9.10.

    1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
    2  *@ URL parsing, credential handling and crypto hooks.
    3  *@ .netrc parser quite loosely based upon NetBSD usr.bin/ftp/
    4  *@   $NetBSD: ruserpass.c,v 1.33 2007/04/17 05:52:04 lukem Exp $
    5  *
    6  * Copyright (c) 2014 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
    7  *
    8  * Permission to use, copy, modify, and/or distribute this software for any
    9  * purpose with or without fee is hereby granted, provided that the above
   10  * copyright notice and this permission notice appear in all copies.
   11  *
   12  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
   13  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
   14  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
   15  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
   16  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
   17  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
   18  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
   19  */
   20 #undef n_FILE
   21 #define n_FILE urlcrecry
   22 
   23 #ifndef HAVE_AMALGAMATION
   24 # include "nail.h"
   25 #endif
   26 
   27 #ifdef HAVE_NETRC
   28   /* NetBSD usr.bin/ftp/ruserpass.c uses 100 bytes for that, we need four
   29    * concurrently (dummy, host, user, pass), so make it a KB */
   30 # define NRC_TOKEN_MAXLEN   (1024 / 4)
   31 
   32 enum nrc_token {
   33    NRC_ERROR      = -1,
   34    NRC_NONE       = 0,
   35    NRC_DEFAULT,
   36    NRC_LOGIN,
   37    NRC_PASSWORD,
   38    NRC_ACCOUNT,
   39    NRC_MACDEF,
   40    NRC_MACHINE,
   41    NRC_INPUT
   42 };
   43 
   44 struct nrc_node {
   45    struct nrc_node   *nrc_next;
   46    struct nrc_node   *nrc_result;   /* In match phase, former possible one */
   47    ui32_t            nrc_mlen;      /* Length of machine name */
   48    ui32_t            nrc_ulen;      /* Length of user name */
   49    ui32_t            nrc_plen;      /* Length of password */
   50    char              nrc_dat[n_VFIELD_SIZE(sizeof(ui32_t))];
   51 };
   52 # define NRC_NODE_ERR   ((struct nrc_node*)-1)
   53 
   54 static struct nrc_node  *_nrc_list;
   55 #endif /* HAVE_NETRC */
   56 
   57 /* Find the last @ before a slash
   58  * TODO Casts off the const but this is ok here; obsolete function! */
   59 #ifdef HAVE_SOCKETS /* temporary (we'll have file://..) */
   60 static char *           _url_last_at_before_slash(char const *sp);
   61 #endif
   62 
   63 #ifdef HAVE_NETRC
   64 /* Initialize .netrc cache */
   65 static void             _nrc_init(void);
   66 static enum nrc_token   __nrc_token(FILE *fi, char buffer[NRC_TOKEN_MAXLEN],
   67                            bool_t *nl_last);
   68 
   69 /* We shall lookup a machine in .netrc says ok_blook(netrc_lookup).
   70  * only_pass is true then the lookup is for the password only, otherwise we
   71  * look for a user (and add password only if we have an exact machine match) */
   72 static bool_t           _nrc_lookup(struct url *urlp, bool_t only_pass);
   73 
   74 /* 0=no match; 1=exact match; -1=wildcard match */
   75 static int              __nrc_host_match(struct nrc_node const *nrc,
   76                            struct url const *urlp);
   77 static bool_t           __nrc_find_user(struct url *urlp,
   78                            struct nrc_node const *nrc);
   79 static bool_t           __nrc_find_pass(struct url *urlp, bool_t user_match,
   80                            struct nrc_node const *nrc);
   81 #endif /* HAVE_NETRC */
   82 
   83 /* The password can also be gained through external agents TODO v15-compat */
   84 #ifdef HAVE_AGENT
   85 static bool_t           _agent_shell_lookup(struct url *urlp, char const *comm);
   86 #endif
   87 
   88 #ifdef HAVE_SOCKETS
   89 static char *
   90 _url_last_at_before_slash(char const *sp)
   91 {
   92    char const *cp;
   93    char c;
   94    NYD2_ENTER;
   95 
   96    for (cp = sp; (c = *cp) != '\0'; ++cp)
   97       if (c == '/')
   98          break;
   99    while (cp > sp && *--cp != '@')
  100       ;
  101    if (*cp != '@')
  102       cp = NULL;
  103    NYD2_LEAVE;
  104    return n_UNCONST(cp);
  105 }
  106 #endif
  107 
  108 #ifdef HAVE_NETRC
  109 static void
  110 _nrc_init(void)
  111 {
  112    char buffer[NRC_TOKEN_MAXLEN], host[NRC_TOKEN_MAXLEN],
  113       user[NRC_TOKEN_MAXLEN], pass[NRC_TOKEN_MAXLEN], *netrc_load;
  114    struct stat sb;
  115    FILE * volatile fi;
  116    enum nrc_token t;
  117    bool_t volatile ispipe;
  118    bool_t seen_default, nl_last;
  119    struct nrc_node * volatile ntail, * volatile nhead, * volatile nrc;
  120    NYD_ENTER;
  121 
  122    n_UNINIT(ntail, NULL);
  123    nhead = NULL;
  124    nrc = NRC_NODE_ERR;
  125    ispipe = FAL0;
  126    fi = NULL;
  127 
  128    hold_all_sigs(); /* todo */
  129 
  130    if ((netrc_load = ok_vlook(netrc_pipe)) != NULL) {
  131       ispipe = TRU1;
  132       if ((fi = Popen(netrc_load, "r", ok_vlook(SHELL), NULL, n_CHILD_FD_NULL)
  133             ) == NULL) {
  134          n_perr(netrc_load, 0);
  135          goto j_leave;
  136       }
  137    } else {
  138       if ((netrc_load = fexpand(ok_vlook(NETRC), FEXP_LOCAL | FEXP_NOPROTO)
  139             ) == NULL)
  140          goto j_leave;
  141 
  142       if ((fi = Fopen(netrc_load, "r")) == NULL) {
  143          n_err(_("Cannot open %s\n"), n_shexp_quote_cp(netrc_load, FAL0));
  144          goto j_leave;
  145       }
  146 
  147       /* Be simple and apply rigid (permission) check(s) */
  148       if (fstat(fileno(fi), &sb) == -1 || !S_ISREG(sb.st_mode) ||
  149             (sb.st_mode & (S_IRWXG | S_IRWXO))) {
  150          n_err(_("Not a regular file, or accessible by non-user: %s\n"),
  151             n_shexp_quote_cp(netrc_load, FAL0));
  152          goto jleave;
  153       }
  154    }
  155 
  156    seen_default = FAL0;
  157    nl_last = TRU1;
  158 jnext:
  159    switch((t = __nrc_token(fi, buffer, &nl_last))) {
  160    case NRC_NONE:
  161       break;
  162    default: /* Doesn't happen (but on error?), keep CC happy */
  163    case NRC_DEFAULT:
  164 jdef:
  165       /* We ignore the default entry (require an exact host match), and we also
  166        * ignore anything after such an entry (faulty syntax) */
  167       seen_default = TRU1;
  168       /* FALLTHRU */
  169    case NRC_MACHINE:
  170 jm_h:
  171       /* Normalize HOST to lowercase */
  172       *host = '\0';
  173       if (!seen_default && (t = __nrc_token(fi, host, &nl_last)) != NRC_INPUT)
  174          goto jerr;
  175       else {
  176          char *cp;
  177          for (cp = host; *cp != '\0'; ++cp)
  178             *cp = lowerconv(*cp);
  179       }
  180 
  181       *user = *pass = '\0';
  182       while ((t = __nrc_token(fi, buffer, &nl_last)) != NRC_NONE &&
  183             t != NRC_MACHINE && t != NRC_DEFAULT) {
  184          switch(t) {
  185          case NRC_LOGIN:
  186             if ((t = __nrc_token(fi, user, &nl_last)) != NRC_INPUT)
  187                goto jerr;
  188             break;
  189          case NRC_PASSWORD:
  190             if ((t = __nrc_token(fi, pass, &nl_last)) != NRC_INPUT)
  191                goto jerr;
  192             break;
  193          case NRC_ACCOUNT:
  194             if ((t = __nrc_token(fi, buffer, &nl_last)) != NRC_INPUT)
  195                goto jerr;
  196             break;
  197          case NRC_MACDEF:
  198             if ((t = __nrc_token(fi, buffer, &nl_last)) != NRC_INPUT)
  199                goto jerr;
  200             else {
  201                int i = 0, c;
  202                while ((c = getc(fi)) != EOF)
  203                   if (c == '\n') { /* xxx */
  204                      /* Don't care about comments here, since we parse until
  205                       * we've seen two successive newline characters */
  206                      if (i)
  207                         break;
  208                      i = 1;
  209                   } else
  210                      i = 0;
  211             }
  212             break;
  213          default:
  214          case NRC_ERROR:
  215             goto jerr;
  216          }
  217       }
  218 
  219       if (!seen_default && (*user != '\0' || *pass != '\0')) {
  220          size_t hl = strlen(host), ul = strlen(user), pl = strlen(pass);
  221          struct nrc_node *nx = smalloc(n_VSTRUCT_SIZEOF(struct nrc_node,
  222                nrc_dat) + hl +1 + ul +1 + pl +1);
  223 
  224          if (nhead != NULL)
  225             ntail->nrc_next = nx;
  226          else
  227             nhead = nx;
  228          ntail = nx;
  229          nx->nrc_next = NULL;
  230          nx->nrc_mlen = hl;
  231          nx->nrc_ulen = ul;
  232          nx->nrc_plen = pl;
  233          memcpy(nx->nrc_dat, host, ++hl);
  234          memcpy(nx->nrc_dat + hl, user, ++ul);
  235          memcpy(nx->nrc_dat + hl + ul, pass, ++pl);
  236       }
  237       if (t == NRC_MACHINE)
  238          goto jm_h;
  239       if (t == NRC_DEFAULT)
  240          goto jdef;
  241       if (t != NRC_NONE)
  242          goto jnext;
  243       break;
  244    case NRC_ERROR:
  245 jerr:
  246       if(n_poption & n_PO_D_V)
  247          n_err(_("Errors occurred while parsing %s\n"),
  248             n_shexp_quote_cp(netrc_load, FAL0));
  249       assert(nrc == NRC_NODE_ERR);
  250       goto jleave;
  251    }
  252 
  253    if (nhead != NULL)
  254       nrc = nhead;
  255 jleave:
  256    if (fi != NULL) {
  257       if (ispipe)
  258             Pclose(fi, TRU1);
  259       else
  260          Fclose(fi);
  261    }
  262    if (nrc == NRC_NODE_ERR)
  263       while (nhead != NULL) {
  264          ntail = nhead;
  265          nhead = nhead->nrc_next;
  266          free(ntail);
  267       }
  268 j_leave:
  269    _nrc_list = nrc;
  270    rele_all_sigs();
  271    NYD_LEAVE;
  272 }
  273 
  274 static enum nrc_token
  275 __nrc_token(FILE *fi, char buffer[NRC_TOKEN_MAXLEN], bool_t *nl_last)
  276 {
  277    int c;
  278    char *cp;
  279    enum nrc_token rv;
  280    NYD2_ENTER;
  281 
  282    rv = NRC_NONE;
  283    for (;;) {
  284       bool_t seen_nl;
  285 
  286       c = EOF;
  287       if (feof(fi) || ferror(fi))
  288          goto jleave;
  289 
  290       for (seen_nl = *nl_last; (c = getc(fi)) != EOF && whitechar(c);)
  291          seen_nl |= (c == '\n');
  292 
  293       if (c == EOF)
  294          goto jleave;
  295       /* fetchmail and derived parsers support comments */
  296       if ((*nl_last = seen_nl) && c == '#') {
  297          while ((c = getc(fi)) != EOF && c != '\n')
  298             ;
  299          continue;
  300       }
  301       break;
  302    }
  303 
  304    cp = buffer;
  305    /* Is it a quoted token?  At least IBM syntax also supports ' quotes */
  306    if (c == '"' || c == '\'') {
  307       int quotec = c;
  308 
  309       /* Not requiring the closing QM is (Net)BSD syntax */
  310       while ((c = getc(fi)) != EOF && c != quotec) {
  311          /* Reverse solidus escaping the next character is (Net)BSD syntax */
  312          if (c == '\\')
  313             if ((c = getc(fi)) == EOF)
  314                break;
  315          *cp++ = c;
  316          if (PTRCMP(cp, ==, buffer + NRC_TOKEN_MAXLEN)) {
  317             rv = NRC_ERROR;
  318             goto jleave;
  319          }
  320       }
  321    } else {
  322       *cp++ = c;
  323       while ((c = getc(fi)) != EOF && !whitechar(c)) {
  324          /* Rverse solidus  escaping the next character is (Net)BSD syntax */
  325          if (c == '\\' && (c = getc(fi)) == EOF)
  326                break;
  327          *cp++ = c;
  328          if (PTRCMP(cp, ==, buffer + NRC_TOKEN_MAXLEN)) {
  329             rv = NRC_ERROR;
  330             goto jleave;
  331          }
  332       }
  333       *nl_last = (c == '\n');
  334    }
  335    *cp = '\0';
  336 
  337    if (*buffer == '\0')
  338       do {/*rv = NRC_NONE*/} while (0);
  339    else if (!strcmp(buffer, "default"))
  340       rv = NRC_DEFAULT;
  341    else if (!strcmp(buffer, "login"))
  342       rv = NRC_LOGIN;
  343    else if (!strcmp(buffer, "password") || !strcmp(buffer, "passwd"))
  344       rv = NRC_PASSWORD;
  345    else if (!strcmp(buffer, "account"))
  346       rv = NRC_ACCOUNT;
  347    else if (!strcmp(buffer, "macdef"))
  348       rv = NRC_MACDEF;
  349    else if (!strcmp(buffer, "machine"))
  350       rv = NRC_MACHINE;
  351    else
  352       rv = NRC_INPUT;
  353 jleave:
  354    if (c == EOF && !feof(fi))
  355       rv = NRC_ERROR;
  356    NYD2_LEAVE;
  357    return rv;
  358 }
  359 
  360 static bool_t
  361 _nrc_lookup(struct url *urlp, bool_t only_pass)
  362 {
  363    struct nrc_node *nrc, *nrc_wild, *nrc_exact;
  364    bool_t rv = FAL0;
  365    NYD_ENTER;
  366 
  367    assert(!only_pass || urlp->url_user.s != NULL);
  368    assert(only_pass || urlp->url_user.s == NULL);
  369 
  370    if (_nrc_list == NULL)
  371       _nrc_init();
  372    if (_nrc_list == NRC_NODE_ERR)
  373       goto jleave;
  374 
  375    nrc_wild = nrc_exact = NULL;
  376    for (nrc = _nrc_list; nrc != NULL; nrc = nrc->nrc_next)
  377       switch (__nrc_host_match(nrc, urlp)) {
  378       case 1:
  379          nrc->nrc_result = nrc_exact;
  380          nrc_exact = nrc;
  381          continue;
  382       case -1:
  383          nrc->nrc_result = nrc_wild;
  384          nrc_wild = nrc;
  385          /* FALLTHRU */
  386       case 0:
  387          continue;
  388       }
  389 
  390    if (!only_pass && urlp->url_user.s == NULL) {
  391       /* Must be an unambiguous entry of its kind */
  392       if (nrc_exact != NULL && nrc_exact->nrc_result != NULL)
  393          goto jleave;
  394       if (__nrc_find_user(urlp, nrc_exact))
  395          goto j_user;
  396 
  397       if (nrc_wild != NULL && nrc_wild->nrc_result != NULL)
  398          goto jleave;
  399       if (!__nrc_find_user(urlp, nrc_wild))
  400          goto jleave;
  401 j_user:
  402       ;
  403    }
  404 
  405    if (__nrc_find_pass(urlp, TRU1, nrc_exact) ||
  406          __nrc_find_pass(urlp, TRU1, nrc_wild) ||
  407          /* Do not try to find a password without exact user match unless we've
  408           * been called during credential lookup, a.k.a. the second time */
  409          !only_pass ||
  410          __nrc_find_pass(urlp, FAL0, nrc_exact) ||
  411          __nrc_find_pass(urlp, FAL0, nrc_wild))
  412       rv = TRU1;
  413 jleave:
  414    NYD_LEAVE;
  415    return rv;
  416 }
  417 
  418 static int
  419 __nrc_host_match(struct nrc_node const *nrc, struct url const *urlp)
  420 {
  421    char const *d2, *d1;
  422    size_t l2, l1;
  423    int rv = 0;
  424    NYD2_ENTER;
  425 
  426    /* Find a matching machine -- entries are all lowercase normalized */
  427    if (nrc->nrc_mlen == urlp->url_host.l) {
  428       if (n_LIKELY(!memcmp(nrc->nrc_dat, urlp->url_host.s, urlp->url_host.l)))
  429          rv = 1;
  430       goto jleave;
  431    }
  432 
  433    /* Cannot be an exact match, but maybe the .netrc machine starts with
  434     * a "*." glob, which we recognize as an extension, meaning "skip
  435     * a single subdomain, then match the rest" */
  436    d1 = nrc->nrc_dat + 2;
  437    l1 = nrc->nrc_mlen;
  438    if (l1 <= 2 || d1[-1] != '.' || d1[-2] != '*')
  439       goto jleave;
  440    l1 -= 2;
  441 
  442    /* Brute skipping over one subdomain, no RFC 1035 or RFC 1122 checks;
  443     * in fact this even succeeds for ".host.com", but - why care, here? */
  444    d2 = urlp->url_host.s;
  445    l2 = urlp->url_host.l;
  446    while (l2 > 0) {
  447       --l2;
  448       if (*d2++ == '.')
  449          break;
  450    }
  451 
  452    if (l2 == l1 && !memcmp(d1, d2, l1))
  453       /* This matches, but we won't use it directly but watch out for an
  454        * exact match first! */
  455       rv = -1;
  456 jleave:
  457    NYD2_LEAVE;
  458    return rv;
  459 }
  460 
  461 static bool_t
  462 __nrc_find_user(struct url *urlp, struct nrc_node const *nrc)
  463 {
  464    NYD2_ENTER;
  465 
  466    for (; nrc != NULL; nrc = nrc->nrc_result)
  467       if (nrc->nrc_ulen > 0) {
  468          /* Fake it was part of URL otherwise XXX */
  469          urlp->url_flags |= n_URL_HAD_USER;
  470          /* That buffer will be duplicated by url_parse() in this case! */
  471          urlp->url_user.s = n_UNCONST(nrc->nrc_dat + nrc->nrc_mlen +1);
  472          urlp->url_user.l = nrc->nrc_ulen;
  473          break;
  474       }
  475 
  476    NYD2_LEAVE;
  477    return (nrc != NULL);
  478 }
  479 
  480 static bool_t
  481 __nrc_find_pass(struct url *urlp, bool_t user_match, struct nrc_node const *nrc)
  482 {
  483    NYD2_ENTER;
  484 
  485    for (; nrc != NULL; nrc = nrc->nrc_result) {
  486       bool_t um = (nrc->nrc_ulen == urlp->url_user.l &&
  487             !memcmp(nrc->nrc_dat + nrc->nrc_mlen +1, urlp->url_user.s,
  488                urlp->url_user.l));
  489 
  490       if (user_match) {
  491          if (!um)
  492             continue;
  493       } else if (!um && nrc->nrc_ulen > 0)
  494          continue;
  495       if (nrc->nrc_plen == 0)
  496          continue;
  497 
  498       /* We are responsible for duplicating this buffer! */
  499       urlp->url_pass.s = savestrbuf(nrc->nrc_dat + nrc->nrc_mlen +1 +
  500             nrc->nrc_ulen + 1, (urlp->url_pass.l = nrc->nrc_plen));
  501       break;
  502    }
  503 
  504    NYD2_LEAVE;
  505    return (nrc != NULL);
  506 }
  507 #endif /* HAVE_NETRC */
  508 
  509 #ifdef HAVE_AGENT
  510 static bool_t
  511 _agent_shell_lookup(struct url *urlp, char const *comm) /* TODO v15-compat */
  512 {
  513    char buf[128];
  514    char const *env_addon[8];
  515    struct str s;
  516    FILE *pbuf;
  517    union {int c; sighandler_type sht;} u;
  518    size_t cl, l;
  519    bool_t rv = FAL0;
  520    NYD2_ENTER;
  521 
  522    env_addon[0] = str_concat_csvl(&s, "NAIL_USER", "=", urlp->url_user.s,
  523          NULL)->s;
  524    env_addon[1] = str_concat_csvl(&s,
  525          "NAIL_USER_ENC", "=", urlp->url_user_enc.s, NULL)->s;
  526    env_addon[2] = str_concat_csvl(&s, "NAIL_HOST", "=", urlp->url_host.s,
  527          NULL)->s;
  528    env_addon[3] = str_concat_csvl(&s, "NAIL_HOST_PORT", "=", urlp->url_h_p.s,
  529          NULL)->s;
  530    env_addon[4] = NULL;
  531 
  532    if ((pbuf = Popen(comm, "r", ok_vlook(SHELL), env_addon, -1)) == NULL) {
  533       n_err(_("*agent-shell-lookup* startup failed (%s)\n"), comm);
  534       goto jleave;
  535    }
  536 
  537    for (s.s = NULL, s.l = cl = l = 0; (u.c = getc(pbuf)) != EOF; ++cl) {
  538       if (u.c == '\n') /* xxx */
  539          continue;
  540       buf[l++] = u.c;
  541       if (l == sizeof(buf) - 1) {
  542          n_str_add_buf(&s, buf, l);
  543          l = 0;
  544       }
  545    }
  546    if (l > 0)
  547       n_str_add_buf(&s, buf, l);
  548 
  549    if (!Pclose(pbuf, TRU1)) {
  550       n_err(_("*agent-shell-lookup* execution failure (%s)\n"), comm);
  551       goto jleave;
  552    }
  553 
  554    /* We are responsible for duplicating this buffer! */
  555    if (s.s != NULL)
  556       urlp->url_pass.s = savestrbuf(s.s, urlp->url_pass.l = s.l);
  557    else if (cl > 0)
  558       urlp->url_pass.s = n_UNCONST(n_empty), urlp->url_pass.l = 0;
  559    rv = TRU1;
  560 jleave:
  561    if (s.s != NULL)
  562       free(s.s);
  563    NYD2_LEAVE;
  564    return rv;
  565 }
  566 #endif /* HAVE_AGENT */
  567 
  568 FL char *
  569 (urlxenc)(char const *cp, bool_t ispath n_MEMORY_DEBUG_ARGS)
  570 {
  571    char *n, *np, c1;
  572    NYD2_ENTER;
  573 
  574    /* C99 */{
  575       size_t i;
  576 
  577       i = strlen(cp);
  578       if(i >= UIZ_MAX / 3){
  579          n = NULL;
  580          goto jleave;
  581       }
  582       i *= 3;
  583       ++i;
  584       np = n = (n_autorec_alloc_from_pool)(NULL, i n_MEMORY_DEBUG_ARGSCALL);
  585    }
  586 
  587    for (; (c1 = *cp) != '\0'; ++cp) {
  588       /* (RFC 1738) RFC 3986, 2.3 Unreserved Characters:
  589        *    ALPHA / DIGIT / "-" / "." / "_" / "~"
  590        * However add a special is[file]path mode for file-system friendliness */
  591       if (alnumchar(c1) || c1 == '_')
  592          *np++ = c1;
  593       else if (!ispath) {
  594          if (c1 != '-' && c1 != '.' && c1 != '~')
  595             goto jesc;
  596          *np++ = c1;
  597       } else if (PTRCMP(np, >, n) && (*cp == '-' || *cp == '.')) /* XXX imap */
  598          *np++ = c1;
  599       else {
  600 jesc:
  601          np[0] = '%';
  602          n_c_to_hex_base16(np + 1, c1);
  603          np += 3;
  604       }
  605    }
  606    *np = '\0';
  607 jleave:
  608    NYD2_LEAVE;
  609    return n;
  610 }
  611 
  612 FL char *
  613 (urlxdec)(char const *cp n_MEMORY_DEBUG_ARGS)
  614 {
  615    char *n, *np;
  616    si32_t c;
  617    NYD2_ENTER;
  618 
  619    np = n = (n_autorec_alloc_from_pool)(NULL, strlen(cp) +1
  620          n_MEMORY_DEBUG_ARGSCALL);
  621 
  622    while ((c = (uc_i)*cp++) != '\0') {
  623       if (c == '%' && cp[0] != '\0' && cp[1] != '\0') {
  624          si32_t o = c;
  625          if (n_LIKELY((c = n_c_from_hex_base16(cp)) >= '\0'))
  626             cp += 2;
  627          else
  628             c = o;
  629       }
  630       *np++ = (char)c;
  631    }
  632    *np = '\0';
  633    NYD2_LEAVE;
  634    return n;
  635 }
  636 
  637 FL int
  638 c_urlcodec(void *vp){
  639    bool_t ispath;
  640    size_t alen;
  641    char const **argv, *varname, *varres, *act, *cp;
  642    NYD_ENTER;
  643 
  644    argv = vp;
  645    varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
  646 
  647    act = *argv;
  648    for(cp = act; *cp != '\0' && !blankspacechar(*cp); ++cp)
  649       ;
  650    if((ispath = (*act == 'p'))){
  651       if(!ascncasecmp(++act, "ath", 3))
  652          act += 3;
  653    }
  654    if(act >= cp)
  655       goto jesynopsis;
  656    alen = PTR2SIZE(cp - act);
  657    if(*cp != '\0')
  658       ++cp;
  659 
  660    n_pstate_err_no = n_ERR_NONE;
  661 
  662    if(is_ascncaseprefix(act, "encode", alen))
  663       varres = urlxenc(cp, ispath);
  664    else if(is_ascncaseprefix(act, "decode", alen))
  665       varres = urlxdec(cp);
  666    else
  667       goto jesynopsis;
  668 
  669    if(varres == NULL){
  670       n_pstate_err_no = n_ERR_CANCELED;
  671       varres = cp;
  672       vp = NULL;
  673    }
  674 
  675    if(varname != NULL){
  676       if(!n_var_vset(varname, (uintptr_t)varres)){
  677          n_pstate_err_no = n_ERR_NOTSUP;
  678          cp = NULL;
  679       }
  680    }else{
  681       struct str in, out;
  682 
  683       in.l = strlen(in.s = n_UNCONST(varres));
  684       makeprint(&in, &out);
  685       if(fprintf(n_stdout, "%s\n", out.s) < 0){
  686          n_pstate_err_no = n_err_no;
  687          vp = NULL;
  688       }
  689       free(out.s);
  690    }
  691 
  692 jleave:
  693    NYD_LEAVE;
  694    return (vp != NULL ? 0 : 1);
  695 jesynopsis:
  696    n_err(_("Synopsis: urlcodec: "
  697       "<[path]e[ncode]|[path]d[ecode]> <rest-of-line>\n"));
  698    n_pstate_err_no = n_ERR_INVAL;
  699    vp = NULL;
  700    goto jleave;
  701 }
  702 
  703 FL int
  704 c_urlencode(void *v) /* XXX IDNA?? */
  705 {
  706    char **ap;
  707    NYD_ENTER;
  708 
  709    n_OBSOLETE("`urlencode': please use `urlcodec enc[ode]' instead");
  710 
  711    for (ap = v; *ap != NULL; ++ap) {
  712       char *in = *ap, *out = urlxenc(in, FAL0);
  713 
  714       if(out == NULL)
  715          out = n_UNCONST(V_(n_error));
  716       fprintf(n_stdout,
  717          " in: <%s> (%" PRIuZ " bytes)\nout: <%s> (%" PRIuZ " bytes)\n",
  718          in, strlen(in), out, strlen(out));
  719    }
  720    NYD_LEAVE;
  721    return 0;
  722 }
  723 
  724 FL int
  725 c_urldecode(void *v) /* XXX IDNA?? */
  726 {
  727    char **ap;
  728    NYD_ENTER;
  729 
  730    n_OBSOLETE("`urldecode': please use `urlcodec dec[ode]' instead");
  731 
  732    for (ap = v; *ap != NULL; ++ap) {
  733       char *in = *ap, *out = urlxdec(in);
  734 
  735       if(out == NULL)
  736          out = n_UNCONST(V_(n_error));
  737       fprintf(n_stdout,
  738          " in: <%s> (%" PRIuZ " bytes)\nout: <%s> (%" PRIuZ " bytes)\n",
  739          in, strlen(in), out, strlen(out));
  740    }
  741    NYD_LEAVE;
  742    return 0;
  743 }
  744 
  745 FL char *
  746 url_mailto_to_address(char const *mailtop){ /* TODO hack! RFC 6068; factory? */
  747    size_t i;
  748    char *rv;
  749    char const *mailtop_orig;
  750    NYD_ENTER;
  751 
  752    if(!is_prefix("mailto:", mailtop_orig = mailtop)){
  753       rv = NULL;
  754       goto jleave;
  755    }
  756    mailtop += sizeof("mailto:") -1;
  757 
  758    /* TODO This is all intermediate, and for now just enough to understand
  759     * TODO a little bit of a little more advanced List-Post: headers. */
  760    /* Strip any hfield additions, keep only to addr-spec's */
  761    if((rv = strchr(mailtop, '?')) != NULL)
  762       rv = savestrbuf(mailtop, i = PTR2SIZE(rv - mailtop));
  763    else
  764       rv = savestrbuf(mailtop, i = strlen(mailtop));
  765 
  766    i = strlen(rv);
  767 
  768    /* Simply perform percent-decoding if there is a percent % */
  769    if(memchr(rv, '%', i) != NULL){
  770       char *rv_base;
  771       bool_t err;
  772 
  773       for(err = FAL0, mailtop = rv_base = rv; i > 0;){
  774          char c;
  775 
  776          if((c = *mailtop++) == '%'){
  777             si32_t cc;
  778 
  779             if(i < 3 || (cc = n_c_from_hex_base16(mailtop)) < 0){
  780                if(!err && (err = TRU1, n_poption & n_PO_D_V))
  781                   n_err(_("Invalid RFC 6068 'mailto' URL: %s\n"),
  782                      n_shexp_quote_cp(mailtop_orig, FAL0));
  783                goto jhex_putc;
  784             }
  785             *rv++ = (char)cc;
  786             mailtop += 2;
  787             i -= 3;
  788          }else{
  789 jhex_putc:
  790             *rv++ = c;
  791             --i;
  792          }
  793       }
  794       *rv = '\0';
  795       rv = rv_base;
  796    }
  797 jleave:
  798    NYD_LEAVE;
  799    return rv;
  800 }
  801 
  802 FL char const *
  803 n_servbyname(char const *proto, ui16_t *irv_or_null){
  804    static struct{
  805       char const name[14];
  806       char const port[8];
  807       ui16_t portno;
  808    } const tbl[] = {
  809       { "smtp", "25", 25},
  810       { "smtps", "465", 465},
  811       { "submission", "587", 587},
  812       { "submissions", "465", 465},
  813       { "pop3", "110", 110},
  814       { "pop3s", "995", 995},
  815       { "imap", "143", 143},
  816       { "imaps", "993", 993},
  817       { "file", "", 0}
  818    };
  819    char const *rv;
  820    size_t l, i;
  821    NYD2_ENTER;
  822 
  823    for(rv = proto; *rv != '\0'; ++rv)
  824       if(*rv == ':')
  825          break;
  826    l = PTR2SIZE(rv - proto);
  827 
  828    for(rv = NULL, i = 0; i < n_NELEM(tbl); ++i)
  829       if(!ascncasecmp(tbl[i].name, proto, l)){
  830          rv = tbl[i].port;
  831          if(irv_or_null != NULL)
  832             *irv_or_null = tbl[i].portno;
  833          break;
  834       }
  835    NYD2_LEAVE;
  836    return rv;
  837 }
  838 
  839 #ifdef HAVE_SOCKETS /* Note: not indented for that -- later: file:// etc.! */
  840 FL bool_t
  841 url_parse(struct url *urlp, enum cproto cproto, char const *data)
  842 {
  843 #if defined HAVE_SMTP && defined HAVE_POP3 && defined HAVE_IMAP
  844 # define a_ALLPROTO
  845 #endif
  846 #if defined HAVE_SMTP || defined HAVE_POP3 || defined HAVE_IMAP
  847 # define a_ANYPROTO
  848    char *cp, *x;
  849 #endif
  850    bool_t rv = FAL0;
  851    NYD_ENTER;
  852    n_UNUSED(data);
  853 
  854    memset(urlp, 0, sizeof *urlp);
  855    urlp->url_input = data;
  856    urlp->url_cproto = cproto;
  857 
  858    /* Network protocol */
  859 #define a_PROTOX(X,Y,Z)  \
  860    urlp->url_portno = Y;\
  861    /* .url_proto has two NULs ... */\
  862    memcpy(urlp->url_proto, X "://\0", sizeof(X "://\0"));\
  863    urlp->url_proto[sizeof(X) -1] = '\0';\
  864    urlp->url_proto_len = sizeof(X) -1;\
  865    do{ Z; }while(0)
  866 #define a__IF(X,Y,Z)  \
  867    if(!ascncasecmp(data, X "://", sizeof(X "://") -1)){\
  868       a_PROTOX(X, Y, Z);\
  869       data += sizeof(X "://") -1;\
  870       goto juser;\
  871    }
  872 #define a_IF(X,Y) a__IF(X, Y, (void)0)
  873 #ifdef HAVE_SSL
  874 # define a_IFS(X,Y) a__IF(X, Y, urlp->url_flags |= n_URL_TLS_REQUIRED)
  875 # define a_IFs(X,Y) a__IF(X, Y, urlp->url_flags |= n_URL_TLS_OPTIONAL)
  876 #else
  877 # define a_IFS(X,Y) goto jeproto;
  878 # define a_IFs(X,Y) a_IF(X, Y)
  879 #endif
  880 
  881    switch(cproto){
  882    case CPROTO_CCRED:
  883       /* The special S/MIME etc. credential lookup */
  884 #ifdef HAVE_SSL
  885       a_PROTOX("ccred", 0, (void)0);
  886       break;
  887 #else
  888       goto jeproto;
  889 #endif
  890    case CPROTO_SOCKS:
  891       a_IF("socks5", 1080);
  892       a_IF("socks", 1080);
  893       a_PROTOX("socks", 1080, (void)0);
  894       break;
  895    case CPROTO_SMTP:
  896 #ifdef HAVE_SMTP
  897       a_IFS("smtps", 465)
  898       a_IFs("smtp", 25)
  899       a_IFs("submission", 587)
  900       a_IFS("submissions", 465)
  901       a_PROTOX("smtp", 25, urlp->url_flags |= n_URL_TLS_OPTIONAL);
  902       break;
  903 #else
  904       goto jeproto;
  905 #endif
  906    case CPROTO_POP3:
  907 #ifdef HAVE_POP3
  908       a_IFS("pop3s", 995)
  909       a_IFs("pop3", 110)
  910       a_PROTOX("pop3", 110, urlp->url_flags |= n_URL_TLS_OPTIONAL);
  911       break;
  912 #else
  913       goto jeproto;
  914 #endif
  915 #ifdef HAVE_IMAP
  916    case CPROTO_IMAP:
  917       a_IFS("imaps", 993)
  918       a_IFs("imap", 143)
  919       a_PROTOX("imap", 143, urlp->url_flags |= n_URL_TLS_OPTIONAL);
  920       break;
  921 #else
  922       goto jeproto;
  923 #endif
  924    }
  925 
  926 #undef a_PROTOX
  927 #undef a__IF
  928 #undef a_IF
  929 #undef a_IFS
  930 #undef a_IFs
  931 
  932    if (strstr(data, "://") != NULL) {
  933 #if !defined a_ALLPROTO || !defined HAVE_SSL
  934 jeproto:
  935 #endif
  936       n_err(_("URL proto:// prefix invalid: %s\n"), urlp->url_input);
  937       goto jleave;
  938    }
  939 #ifdef a_ANYPROTO
  940 
  941    /* User and password, I */
  942 juser:
  943    if ((cp = _url_last_at_before_slash(data)) != NULL) {
  944       size_t l;
  945       char const *urlpe, *d;
  946       char *ub;
  947 
  948       l = PTR2SIZE(cp - data);
  949       ub = ac_alloc(l +1);
  950       d = data;
  951       urlp->url_flags |= n_URL_HAD_USER;
  952       data = &cp[1];
  953 
  954       /* And also have a password? */
  955       if((cp = memchr(d, ':', l)) != NULL){
  956          size_t i = PTR2SIZE(cp - d);
  957 
  958          l -= i + 1;
  959          memcpy(ub, cp + 1, l);
  960          ub[l] = '\0';
  961 
  962          if((urlp->url_pass.s = urlxdec(ub)) == NULL)
  963             goto jurlp_err;
  964          urlp->url_pass.l = strlen(urlp->url_pass.s);
  965          if((urlpe = urlxenc(urlp->url_pass.s, FAL0)) == NULL)
  966             goto jurlp_err;
  967          if(strcmp(ub, urlpe))
  968             goto jurlp_err;
  969          l = i;
  970       }
  971 
  972       memcpy(ub, d, l);
  973       ub[l] = '\0';
  974       if((urlp->url_user.s = urlxdec(ub)) == NULL)
  975          goto jurlp_err;
  976       urlp->url_user.l = strlen(urlp->url_user.s);
  977       if((urlp->url_user_enc.s = urlxenc(urlp->url_user.s, FAL0)) == NULL)
  978          goto jurlp_err;
  979       urlp->url_user_enc.l = strlen(urlp->url_user_enc.s);
  980 
  981       if(urlp->url_user_enc.l != l || memcmp(urlp->url_user_enc.s, ub, l)){
  982 jurlp_err:
  983          n_err(_("String is not properly URL percent encoded: %s\n"), ub);
  984          d = NULL;
  985       }
  986 
  987       ac_free(ub);
  988       if(d == NULL)
  989          goto jleave;
  990    }
  991 
  992    /* Servername and port -- and possible path suffix */
  993    if ((cp = strchr(data, ':')) != NULL) { /* TODO URL parse, use IPAddress! */
  994       urlp->url_port = x = savestr(x = &cp[1]);
  995       if ((x = strchr(x, '/')) != NULL) {
  996          *x = '\0';
  997          while(*++x == '/')
  998             ;
  999       }
 1000 
 1001       if((n_idec_ui16_cp(&urlp->url_portno, urlp->url_port, 10, NULL
 1002                ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
 1003             ) != n_IDEC_STATE_CONSUMED){
 1004          n_err(_("URL with invalid port number: %s\n"), urlp->url_input);
 1005          goto jleave;
 1006       }
 1007    } else {
 1008       if ((x = strchr(data, '/')) != NULL) {
 1009          data = savestrbuf(data, PTR2SIZE(x - data));
 1010          while(*++x == '/')
 1011             ;
 1012       }
 1013       cp = n_UNCONST(data + strlen(data));
 1014    }
 1015 
 1016    /* A (non-empty) path may only occur with IMAP */
 1017    if (x != NULL && *x != '\0') {
 1018       /* Take care not to count adjacent solidus for real, on either end */
 1019       char *x2;
 1020       size_t i;
 1021       bool_t trailsol;
 1022 
 1023       for(trailsol = FAL0, x2 = savestrbuf(x, i = strlen(x)); i > 0;
 1024             trailsol = TRU1, --i)
 1025          if(x2[i - 1] != '/')
 1026             break;
 1027       x2[i] = '\0';
 1028 
 1029       if (i > 0) {
 1030          if (cproto != CPROTO_IMAP) {
 1031             n_err(_("URL protocol doesn't support paths: \"%s\"\n"),
 1032                urlp->url_input);
 1033             goto jleave;
 1034          }
 1035 #ifdef HAVE_IMAP
 1036          if(trailsol){
 1037             urlp->url_path.s = n_autorec_alloc(i + sizeof("/INBOX"));
 1038             memcpy(urlp->url_path.s, x, i);
 1039             memcpy(&urlp->url_path.s[i], "/INBOX", sizeof("/INBOX"));
 1040             urlp->url_path.l = (i += sizeof("/INBOX") -1);
 1041          }else
 1042 #endif
 1043             urlp->url_path.l = i, urlp->url_path.s = x2;
 1044       }
 1045    }
 1046 #ifdef HAVE_IMAP
 1047    if(cproto == CPROTO_IMAP && urlp->url_path.s == NULL)
 1048       urlp->url_path.s = savestrbuf("INBOX",
 1049             urlp->url_path.l = sizeof("INBOX") -1);
 1050 #endif
 1051 
 1052    urlp->url_host.s = savestrbuf(data, urlp->url_host.l = PTR2SIZE(cp - data));
 1053    {  size_t i;
 1054       for (cp = urlp->url_host.s, i = urlp->url_host.l; i != 0; ++cp, --i)
 1055          *cp = lowerconv(*cp);
 1056    }
 1057 #ifdef HAVE_IDNA
 1058    if(!ok_blook(idna_disable)){
 1059       struct n_string idna;
 1060 
 1061       if(!n_idna_to_ascii(n_string_creat_auto(&idna), urlp->url_host.s,
 1062                urlp->url_host.l)){
 1063          n_err(_("URL host fails IDNA conversion: %s\n"), urlp->url_input);
 1064          goto jleave;
 1065       }
 1066       urlp->url_host.s = n_string_cp(&idna);
 1067       urlp->url_host.l = idna.s_len;
 1068    }
 1069 #endif /* HAVE_IDNA */
 1070 
 1071    /* .url_h_p: HOST:PORT */
 1072    {  size_t i;
 1073       struct str *s = &urlp->url_h_p;
 1074 
 1075       s->s = salloc(urlp->url_host.l + 1 + sizeof("65536")-1 +1);
 1076       memcpy(s->s, urlp->url_host.s, i = urlp->url_host.l);
 1077       if (urlp->url_port != NULL) {
 1078          size_t j = strlen(urlp->url_port);
 1079          s->s[i++] = ':';
 1080          memcpy(s->s + i, urlp->url_port, j);
 1081          i += j;
 1082       }
 1083       s->s[i] = '\0';
 1084       s->l = i;
 1085    }
 1086 
 1087    /* User, II
 1088     * If there was no user in the URL, do we have *user-HOST* or *user*? */
 1089    if (!(urlp->url_flags & n_URL_HAD_USER)) {
 1090       if ((urlp->url_user.s = xok_vlook(user, urlp, OXM_PLAIN | OXM_H_P))
 1091             == NULL) {
 1092          /* No, check whether .netrc lookup is desired */
 1093 # ifdef HAVE_NETRC
 1094          if (!ok_blook(v15_compat) ||
 1095                !xok_blook(netrc_lookup, urlp, OXM_PLAIN | OXM_H_P) ||
 1096                !_nrc_lookup(urlp, FAL0))
 1097 # endif
 1098             urlp->url_user.s = n_UNCONST(ok_vlook(LOGNAME));
 1099       }
 1100 
 1101       urlp->url_user.l = strlen(urlp->url_user.s);
 1102       urlp->url_user.s = savestrbuf(urlp->url_user.s, urlp->url_user.l);
 1103       if((urlp->url_user_enc.s = urlxenc(urlp->url_user.s, FAL0)) == NULL){
 1104          n_err(_("Cannot URL encode %s\n"), urlp->url_user.s);
 1105          goto jleave;
 1106       }
 1107       urlp->url_user_enc.l = strlen(urlp->url_user_enc.s);
 1108    }
 1109 
 1110    /* And then there are a lot of prebuild string combinations TODO do lazy */
 1111 
 1112    /* .url_u_h: .url_user@.url_host
 1113     * For SMTP we apply ridiculously complicated *v15-compat* plus
 1114     * *smtp-hostname* / *hostname* dependent rules */
 1115    {  struct str h, *s;
 1116       size_t i;
 1117 
 1118       if (cproto == CPROTO_SMTP && ok_blook(v15_compat) &&
 1119             (cp = ok_vlook(smtp_hostname)) != NULL) {
 1120          if (*cp == '\0')
 1121             cp = n_nodename(TRU1);
 1122          h.s = savestrbuf(cp, h.l = strlen(cp));
 1123       } else
 1124          h = urlp->url_host;
 1125 
 1126       s = &urlp->url_u_h;
 1127       i = urlp->url_user.l;
 1128 
 1129       s->s = salloc(i + 1 + h.l +1);
 1130       if (i > 0) {
 1131          memcpy(s->s, urlp->url_user.s, i);
 1132          s->s[i++] = '@';
 1133       }
 1134       memcpy(s->s + i, h.s, h.l +1);
 1135       i += h.l;
 1136       s->l = i;
 1137    }
 1138 
 1139    /* .url_u_h_p: .url_user@.url_host[:.url_port] */
 1140    {  struct str *s = &urlp->url_u_h_p;
 1141       size_t i = urlp->url_user.l;
 1142 
 1143       s->s = salloc(i + 1 + urlp->url_h_p.l +1);
 1144       if (i > 0) {
 1145          memcpy(s->s, urlp->url_user.s, i);
 1146          s->s[i++] = '@';
 1147       }
 1148       memcpy(s->s + i, urlp->url_h_p.s, urlp->url_h_p.l +1);
 1149       i += urlp->url_h_p.l;
 1150       s->l = i;
 1151    }
 1152 
 1153    /* .url_eu_h_p: .url_user_enc@.url_host[:.url_port] */
 1154    {  struct str *s = &urlp->url_eu_h_p;
 1155       size_t i = urlp->url_user_enc.l;
 1156 
 1157       s->s = salloc(i + 1 + urlp->url_h_p.l +1);
 1158       if (i > 0) {
 1159          memcpy(s->s, urlp->url_user_enc.s, i);
 1160          s->s[i++] = '@';
 1161       }
 1162       memcpy(s->s + i, urlp->url_h_p.s, urlp->url_h_p.l +1);
 1163       i += urlp->url_h_p.l;
 1164       s->l = i;
 1165    }
 1166 
 1167    /* .url_p_u_h_p: .url_proto://.url_u_h_p */
 1168    {  size_t i;
 1169       char *ud;
 1170 
 1171       ud = n_autorec_alloc((i = urlp->url_proto_len + sizeof("://") -1 +
 1172             urlp->url_u_h_p.l) +1);
 1173       urlp->url_proto[urlp->url_proto_len] = ':';
 1174       memcpy(sstpcpy(ud, urlp->url_proto), urlp->url_u_h_p.s,
 1175          urlp->url_u_h_p.l +1);
 1176       urlp->url_proto[urlp->url_proto_len] = '\0';
 1177 
 1178       urlp->url_p_u_h_p = ud;
 1179    }
 1180 
 1181    /* .url_p_eu_h_p, .url_p_eu_h_p_p: .url_proto://.url_eu_h_p[/.url_path] */
 1182    {  size_t i;
 1183       char *ud;
 1184 
 1185       ud = n_autorec_alloc((i = urlp->url_proto_len + sizeof("://") -1 +
 1186             urlp->url_eu_h_p.l) + 1 + urlp->url_path.l +1);
 1187       urlp->url_proto[urlp->url_proto_len] = ':';
 1188       memcpy(sstpcpy(ud, urlp->url_proto), urlp->url_eu_h_p.s,
 1189          urlp->url_eu_h_p.l +1);
 1190       urlp->url_proto[urlp->url_proto_len] = '\0';
 1191 
 1192       if (urlp->url_path.l == 0)
 1193          urlp->url_p_eu_h_p = urlp->url_p_eu_h_p_p = ud;
 1194       else {
 1195          urlp->url_p_eu_h_p = savestrbuf(ud, i);
 1196          urlp->url_p_eu_h_p_p = ud;
 1197          ud += i;
 1198          *ud++ = '/';
 1199          memcpy(ud, urlp->url_path.s, urlp->url_path.l +1);
 1200       }
 1201    }
 1202 
 1203    rv = TRU1;
 1204 #endif /* a_ANYPROTO */
 1205 jleave:
 1206    NYD_LEAVE;
 1207    return rv;
 1208 #undef a_ANYPROTO
 1209 #undef a_ALLPROTO
 1210 }
 1211 
 1212 FL bool_t
 1213 ccred_lookup_old(struct ccred *ccp, enum cproto cproto, char const *addr)
 1214 {
 1215    char const *pname, *pxstr, *authdef, *s;
 1216    size_t pxlen, addrlen, i;
 1217    char *vbuf;
 1218    ui8_t authmask;
 1219    enum {NONE=0, WANT_PASS=1<<0, REQ_PASS=1<<1, WANT_USER=1<<2, REQ_USER=1<<3}
 1220       ware = NONE;
 1221    bool_t addr_is_nuser = FAL0; /* XXX v15.0 legacy! v15_compat */
 1222    NYD_ENTER;
 1223 
 1224    n_OBSOLETE(_("Use of old-style credentials, which will vanish in v15!\n"
 1225       "  Please read the manual section "
 1226          "\"On URL syntax and credential lookup\""));
 1227 
 1228    memset(ccp, 0, sizeof *ccp);
 1229 
 1230    switch (cproto) {
 1231    default:
 1232    case CPROTO_SMTP:
 1233       pname = "SMTP";
 1234       pxstr = "smtp-auth";
 1235       pxlen = sizeof("smtp-auth") -1;
 1236       authmask = AUTHTYPE_NONE | AUTHTYPE_PLAIN | AUTHTYPE_LOGIN |
 1237             AUTHTYPE_CRAM_MD5 | AUTHTYPE_GSSAPI;
 1238       authdef = "none";
 1239       addr_is_nuser = TRU1;
 1240       break;
 1241    case CPROTO_POP3:
 1242       pname = "POP3";
 1243       pxstr = "pop3-auth";
 1244       pxlen = sizeof("pop3-auth") -1;
 1245       authmask = AUTHTYPE_PLAIN;
 1246       authdef = "plain";
 1247       break;
 1248 #ifdef HAVE_IMAP
 1249    case CPROTO_IMAP:
 1250       pname = "IMAP";
 1251       pxstr = "imap-auth";
 1252       pxlen = sizeof("imap-auth") -1;
 1253       authmask = AUTHTYPE_LOGIN | AUTHTYPE_CRAM_MD5 | AUTHTYPE_GSSAPI;
 1254       authdef = "login";
 1255       break;
 1256 #endif
 1257    }
 1258 
 1259    ccp->cc_cproto = cproto;
 1260    addrlen = strlen(addr);
 1261    vbuf = ac_alloc(pxlen + addrlen + sizeof("-password-")-1 +1);
 1262    memcpy(vbuf, pxstr, pxlen);
 1263 
 1264    /* Authentication type */
 1265    vbuf[pxlen] = '-';
 1266    memcpy(vbuf + pxlen + 1, addr, addrlen +1);
 1267    if ((s = n_var_vlook(vbuf, FAL0)) == NULL) {
 1268       vbuf[pxlen] = '\0';
 1269       if ((s = n_var_vlook(vbuf, FAL0)) == NULL)
 1270          s = n_UNCONST(authdef);
 1271    }
 1272 
 1273    if (!asccasecmp(s, "none")) {
 1274       ccp->cc_auth = "NONE";
 1275       ccp->cc_authtype = AUTHTYPE_NONE;
 1276       /*ware = NONE;*/
 1277    } else if (!asccasecmp(s, "plain")) {
 1278       ccp->cc_auth = "PLAIN";
 1279       ccp->cc_authtype = AUTHTYPE_PLAIN;
 1280       ware = REQ_PASS | REQ_USER;
 1281    } else if (!asccasecmp(s, "login")) {
 1282       ccp->cc_auth = "LOGIN";
 1283       ccp->cc_authtype = AUTHTYPE_LOGIN;
 1284       ware = REQ_PASS | REQ_USER;
 1285    } else if (!asccasecmp(s, "cram-md5")) {
 1286       ccp->cc_auth = "CRAM-MD5";
 1287       ccp->cc_authtype = AUTHTYPE_CRAM_MD5;
 1288       ware = REQ_PASS | REQ_USER;
 1289    } else if (!asccasecmp(s, "gssapi")) {
 1290       ccp->cc_auth = "GSS-API";
 1291       ccp->cc_authtype = AUTHTYPE_GSSAPI;
 1292       ware = REQ_USER;
 1293    } /* no else */
 1294 
 1295    /* Verify method */
 1296    if (!(ccp->cc_authtype & authmask)) {
 1297       n_err(_("Unsupported %s authentication method: %s\n"), pname, s);
 1298       ccp = NULL;
 1299       goto jleave;
 1300    }
 1301 # ifndef HAVE_MD5
 1302    if (ccp->cc_authtype == AUTHTYPE_CRAM_MD5) {
 1303       n_err(_("No CRAM-MD5 support compiled in\n"));
 1304       ccp = NULL;
 1305       goto jleave;
 1306    }
 1307 # endif
 1308 # ifndef HAVE_GSSAPI
 1309    if (ccp->cc_authtype == AUTHTYPE_GSSAPI) {
 1310       n_err(_("No GSS-API support compiled in\n"));
 1311       ccp = NULL;
 1312       goto jleave;
 1313    }
 1314 # endif
 1315 
 1316    /* User name */
 1317    if (!(ware & (WANT_USER | REQ_USER)))
 1318       goto jpass;
 1319 
 1320    if (!addr_is_nuser) {
 1321       if ((s = _url_last_at_before_slash(addr)) != NULL) {
 1322          char *cp;
 1323 
 1324          cp = savestrbuf(addr, PTR2SIZE(s - addr));
 1325 
 1326          if((ccp->cc_user.s = urlxdec(cp)) == NULL){
 1327             n_err(_("String is not properly URL percent encoded: %s\n"), cp);
 1328             ccp = NULL;
 1329             goto jleave;
 1330          }
 1331          ccp->cc_user.l = strlen(ccp->cc_user.s);
 1332       } else if (ware & REQ_USER)
 1333          goto jgetuser;
 1334       goto jpass;
 1335    }
 1336 
 1337    memcpy(vbuf + pxlen, "-user-", i = sizeof("-user-") -1);
 1338    i += pxlen;
 1339    memcpy(vbuf + i, addr, addrlen +1);
 1340    if ((s = n_var_vlook(vbuf, FAL0)) == NULL) {
 1341       vbuf[--i] = '\0';
 1342       if ((s = n_var_vlook(vbuf, FAL0)) == NULL && (ware & REQ_USER)) {
 1343          if ((s = getuser(NULL)) == NULL) {
 1344 jgetuser:   /* TODO v15.0: today we simply bail, but we should call getuser().
 1345              * TODO even better: introduce "PROTO-user" and "PROTO-pass" and
 1346              * TODO check that first, then! change control flow, grow vbuf */
 1347             n_err(_("A user is necessary for %s authentication\n"), pname);
 1348             ccp = NULL;
 1349             goto jleave;
 1350          }
 1351       }
 1352    }
 1353    ccp->cc_user.l = strlen(ccp->cc_user.s = savestr(s));
 1354 
 1355    /* Password */
 1356 jpass:
 1357    if (!(ware & (WANT_PASS | REQ_PASS)))
 1358       goto jleave;
 1359 
 1360    if (!addr_is_nuser) {
 1361       memcpy(vbuf, "password-", i = sizeof("password-") -1);
 1362    } else {
 1363       memcpy(vbuf + pxlen, "-password-", i = sizeof("-password-") -1);
 1364       i += pxlen;
 1365    }
 1366    memcpy(vbuf + i, addr, addrlen +1);
 1367    if ((s = n_var_vlook(vbuf, FAL0)) == NULL) {
 1368       vbuf[--i] = '\0';
 1369       if ((!addr_is_nuser || (s = n_var_vlook(vbuf, FAL0)) == NULL) &&
 1370             (ware & REQ_PASS)) {
 1371          if ((s = getpassword(savecat(_("Password for "), pname))) == NULL) {
 1372             n_err(_("A password is necessary for %s authentication\n"),
 1373                pname);
 1374             ccp = NULL;
 1375             goto jleave;
 1376          }
 1377       }
 1378    }
 1379    if (s != NULL)
 1380       ccp->cc_pass.l = strlen(ccp->cc_pass.s = savestr(s));
 1381 
 1382 jleave:
 1383    ac_free(vbuf);
 1384    if (ccp != NULL && (n_poption & n_PO_D_VV))
 1385       n_err(_("Credentials: host %s, user %s, pass %s\n"),
 1386          addr, (ccp->cc_user.s != NULL ? ccp->cc_user.s : n_empty),
 1387          (ccp->cc_pass.s != NULL ? ccp->cc_pass.s : n_empty));
 1388    NYD_LEAVE;
 1389    return (ccp != NULL);
 1390 }
 1391 
 1392 FL bool_t
 1393 ccred_lookup(struct ccred *ccp, struct url *urlp)
 1394 {
 1395    char *s;
 1396    char const *pstr, *authdef;
 1397    ui8_t authmask;
 1398    enum okeys authokey;
 1399    enum {NONE=0, WANT_PASS=1<<0, REQ_PASS=1<<1, WANT_USER=1<<2, REQ_USER=1<<3}
 1400       ware;
 1401    NYD_ENTER;
 1402 
 1403    memset(ccp, 0, sizeof *ccp);
 1404    ccp->cc_user = urlp->url_user;
 1405 
 1406    ware = NONE;
 1407 
 1408    switch ((ccp->cc_cproto = urlp->url_cproto)) {
 1409    case CPROTO_CCRED:
 1410       authokey = (enum okeys)-1;
 1411       authmask = AUTHTYPE_PLAIN;
 1412       authdef = "plain";
 1413       pstr = "ccred";
 1414       break;
 1415    default:
 1416    case CPROTO_SMTP:
 1417       authokey = ok_v_smtp_auth;
 1418       authmask = AUTHTYPE_NONE | AUTHTYPE_PLAIN | AUTHTYPE_LOGIN |
 1419             AUTHTYPE_CRAM_MD5 | AUTHTYPE_GSSAPI;
 1420       authdef = "plain";
 1421       pstr = "smtp";
 1422       break;
 1423    case CPROTO_POP3:
 1424       authokey = ok_v_pop3_auth;
 1425       authmask = AUTHTYPE_PLAIN;
 1426       authdef = "plain";
 1427       pstr = "pop3";
 1428       break;
 1429 #ifdef HAVE_IMAP
 1430    case CPROTO_IMAP:
 1431       pstr = "imap";
 1432       authokey = ok_v_imap_auth;
 1433       authmask = AUTHTYPE_LOGIN | AUTHTYPE_CRAM_MD5 | AUTHTYPE_GSSAPI;
 1434       authdef = "login";
 1435       break;
 1436 #endif
 1437    }
 1438 
 1439    /* Authentication type */
 1440    if (authokey == (enum okeys)-1 ||
 1441          (s = xok_VLOOK(authokey, urlp, OXM_ALL)) == NULL)
 1442       s = n_UNCONST(authdef);
 1443 
 1444    if (!asccasecmp(s, "none")) {
 1445       ccp->cc_auth = "NONE";
 1446       ccp->cc_authtype = AUTHTYPE_NONE;
 1447       /*ware = NONE;*/
 1448    } else if (!asccasecmp(s, "plain")) {
 1449       ccp->cc_auth = "PLAIN";
 1450       ccp->cc_authtype = AUTHTYPE_PLAIN;
 1451       ware = REQ_PASS | REQ_USER;
 1452    } else if (!asccasecmp(s, "login")) {
 1453       ccp->cc_auth = "LOGIN";
 1454       ccp->cc_authtype = AUTHTYPE_LOGIN;
 1455       ware = REQ_PASS | REQ_USER;
 1456    } else if (!asccasecmp(s, "cram-md5")) {
 1457       ccp->cc_auth = "CRAM-MD5";
 1458       ccp->cc_authtype = AUTHTYPE_CRAM_MD5;
 1459       ware = REQ_PASS | REQ_USER;
 1460    } else if (!asccasecmp(s, "gssapi")) {
 1461       ccp->cc_auth = "GSS-API";
 1462       ccp->cc_authtype = AUTHTYPE_GSSAPI;
 1463       ware = REQ_USER;
 1464    } /* no else */
 1465 
 1466    /* Verify method */
 1467    if (!(ccp->cc_authtype & authmask)) {
 1468       n_err(_("Unsupported %s authentication method: %s\n"), pstr, s);
 1469       ccp = NULL;
 1470       goto jleave;
 1471    }
 1472 # ifndef HAVE_MD5
 1473    if (ccp->cc_authtype == AUTHTYPE_CRAM_MD5) {
 1474       n_err(_("No CRAM-MD5 support compiled in\n"));
 1475       ccp = NULL;
 1476       goto jleave;
 1477    }
 1478 # endif
 1479 # ifndef HAVE_GSSAPI
 1480    if (ccp->cc_authtype == AUTHTYPE_GSSAPI) {
 1481       n_err(_("No GSS-API support compiled in\n"));
 1482       ccp = NULL;
 1483       goto jleave;
 1484    }
 1485 # endif
 1486 
 1487    /* Password */
 1488    ccp->cc_pass = urlp->url_pass;
 1489    if (ccp->cc_pass.s != NULL)
 1490       goto jleave;
 1491 
 1492    if ((s = xok_vlook(password, urlp, OXM_ALL)) != NULL)
 1493       goto js2pass;
 1494 # ifdef HAVE_AGENT /* TODO v15-compat obsolete */
 1495    if ((s = xok_vlook(agent_shell_lookup, urlp, OXM_ALL)) != NULL) {
 1496       n_OBSOLETE(_("*agent-shell-lookup* will vanish.  Please use encrypted "
 1497          "~/.netrc (via *netrc-pipe*), or simply `source' an encrypted file"));
 1498       if (!_agent_shell_lookup(urlp, s)) {
 1499          ccp = NULL;
 1500          goto jleave;
 1501       } else if (urlp->url_pass.s != NULL) {
 1502          ccp->cc_pass = urlp->url_pass;
 1503          goto jleave;
 1504       }
 1505    }
 1506 # endif
 1507 # ifdef HAVE_NETRC
 1508    if (xok_blook(netrc_lookup, urlp, OXM_ALL) && _nrc_lookup(urlp, TRU1)) {
 1509       ccp->cc_pass = urlp->url_pass;
 1510       goto jleave;
 1511    }
 1512 # endif
 1513    if (ware & REQ_PASS) {
 1514       if((s = getpassword(savecat(urlp->url_u_h.s, _(" requires a password: ")))
 1515             ) != NULL)
 1516 js2pass:
 1517          ccp->cc_pass.l = strlen(ccp->cc_pass.s = savestr(s));
 1518       else {
 1519          n_err(_("A password is necessary for %s authentication\n"), pstr);
 1520          ccp = NULL;
 1521       }
 1522    }
 1523 
 1524 jleave:
 1525    if(ccp != NULL && (n_poption & n_PO_D_VV))
 1526       n_err(_("Credentials: host %s, user %s, pass %s\n"),
 1527          urlp->url_h_p.s, (ccp->cc_user.s != NULL ? ccp->cc_user.s : n_empty),
 1528          (ccp->cc_pass.s != NULL ? ccp->cc_pass.s : n_empty));
 1529    NYD_LEAVE;
 1530    return (ccp != NULL);
 1531 }
 1532 #endif /* HAVE_SOCKETS */
 1533 
 1534 #ifdef HAVE_NETRC
 1535 FL int
 1536 c_netrc(void *v)
 1537 {
 1538    char **argv = v;
 1539    struct nrc_node *nrc;
 1540    bool_t load_only;
 1541    NYD_ENTER;
 1542 
 1543    load_only = FAL0;
 1544    if (*argv == NULL)
 1545       goto jlist;
 1546    if (argv[1] != NULL)
 1547       goto jerr;
 1548    if (!asccasecmp(*argv, "show"))
 1549       goto jlist;
 1550    load_only = TRU1;
 1551    if (!asccasecmp(*argv, "load"))
 1552       goto jlist;
 1553    if (!asccasecmp(*argv, "clear"))
 1554       goto jclear;
 1555 jerr:
 1556    n_err(_("Synopsis: netrc: (<show> or) <clear> the .netrc cache\n"));
 1557    v = NULL;
 1558 jleave:
 1559    NYD_LEAVE;
 1560    return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
 1561 
 1562 jlist:   {
 1563    FILE *fp;
 1564    size_t l;
 1565 
 1566    if (_nrc_list == NULL)
 1567       _nrc_init();
 1568    if (_nrc_list == NRC_NODE_ERR) {
 1569       n_err(_("Interpolate what file?\n"));
 1570       v = NULL;
 1571       goto jleave;
 1572    }
 1573    if (load_only)
 1574       goto jleave;
 1575 
 1576    if ((fp = Ftmp(NULL, "netrc", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL) {
 1577       n_perr(_("tmpfile"), 0);
 1578       v = NULL;
 1579       goto jleave;
 1580    }
 1581 
 1582    for (l = 0, nrc = _nrc_list; nrc != NULL; ++l, nrc = nrc->nrc_next) {
 1583       fprintf(fp, _("machine %s "), nrc->nrc_dat); /* XXX quote? */
 1584       if (nrc->nrc_ulen > 0)
 1585          fprintf(fp, _("login \"%s\" "),
 1586             string_quote(nrc->nrc_dat + nrc->nrc_mlen +1));
 1587       if (nrc->nrc_plen > 0)
 1588          fprintf(fp, _("password \"%s\"\n"),
 1589             string_quote(nrc->nrc_dat + nrc->nrc_mlen +1 + nrc->nrc_ulen +1));
 1590       else
 1591          putc('\n', fp);
 1592    }
 1593 
 1594    page_or_print(fp, l);
 1595    Fclose(fp);
 1596    }
 1597    goto jleave;
 1598 
 1599 jclear:
 1600    if (_nrc_list == NRC_NODE_ERR)
 1601       _nrc_list = NULL;
 1602    while ((nrc = _nrc_list) != NULL) {
 1603       _nrc_list = nrc->nrc_next;
 1604       free(nrc);
 1605    }
 1606    goto jleave;
 1607 }
 1608 #endif /* HAVE_NETRC */
 1609 
 1610 #ifdef HAVE_MD5
 1611 FL char *
 1612 md5tohex(char hex[MD5TOHEX_SIZE], void const *vp)
 1613 {
 1614    char const *cp = vp;
 1615    size_t i, j;
 1616    NYD_ENTER;
 1617 
 1618    for (i = 0; i < MD5TOHEX_SIZE / 2; ++i) {
 1619       j = i << 1;
 1620 # define __hex(n) ((n) > 9 ? (n) - 10 + 'a' : (n) + '0')
 1621       hex[j] = __hex((cp[i] & 0xF0) >> 4);
 1622       hex[++j] = __hex(cp[i] & 0x0F);
 1623 # undef __hex
 1624    }
 1625    NYD_LEAVE;
 1626    return hex;
 1627 }
 1628 
 1629 FL char *
 1630 cram_md5_string(struct str const *user, struct str const *pass,
 1631    char const *b64)
 1632 {
 1633    struct str in, out;
 1634    char digest[16], *cp;
 1635    NYD_ENTER;
 1636 
 1637    out.s = NULL;
 1638    if(user->l >= UIZ_MAX - 1 - MD5TOHEX_SIZE - 1)
 1639       goto jleave;
 1640    if(pass->l >= INT_MAX)
 1641       goto jleave;
 1642 
 1643    in.s = n_UNCONST(b64);
 1644    in.l = strlen(in.s);
 1645    if(!b64_decode(&out, &in))
 1646       goto jleave;
 1647    if(out.l >= INT_MAX){
 1648       free(out.s);
 1649       out.s = NULL;
 1650       goto jleave;
 1651    }
 1652 
 1653    hmac_md5((uc_i*)out.s, out.l, (uc_i*)pass->s, pass->l, digest);
 1654    free(out.s);
 1655    cp = md5tohex(salloc(MD5TOHEX_SIZE +1), digest);
 1656 
 1657    in.l = user->l + MD5TOHEX_SIZE +1;
 1658    in.s = ac_alloc(user->l + 1 + MD5TOHEX_SIZE +1);
 1659    memcpy(in.s, user->s, user->l);
 1660    in.s[user->l] = ' ';
 1661    memcpy(&in.s[user->l + 1], cp, MD5TOHEX_SIZE);
 1662    if(b64_encode(&out, &in, B64_SALLOC | B64_CRLF) == NULL)
 1663       out.s = NULL;
 1664    ac_free(in.s);
 1665 jleave:
 1666    NYD_LEAVE;
 1667    return out.s;
 1668 }
 1669 
 1670 FL void
 1671 hmac_md5(unsigned char *text, int text_len, unsigned char *key, int key_len,
 1672    void *digest)
 1673 {
 1674    /*
 1675     * This code is taken from
 1676     *
 1677     * Network Working Group                                       H. Krawczyk
 1678     * Request for Comments: 2104                                          IBM
 1679     * Category: Informational                                      M. Bellare
 1680     *                                                                    UCSD
 1681     *                                                              R. Canetti
 1682     *                                                                     IBM
 1683     *                                                           February 1997
 1684     *
 1685     *
 1686     *             HMAC: Keyed-Hashing for Message Authentication
 1687     */
 1688    md5_ctx context;
 1689    unsigned char k_ipad[65]; /* inner padding - key XORd with ipad */
 1690    unsigned char k_opad[65]; /* outer padding - key XORd with opad */
 1691    unsigned char tk[16];
 1692    int i;
 1693    NYD_ENTER;
 1694 
 1695    /* if key is longer than 64 bytes reset it to key=MD5(key) */
 1696    if (key_len > 64) {
 1697       md5_ctx tctx;
 1698 
 1699       md5_init(&tctx);
 1700       md5_update(&tctx, key, key_len);
 1701       md5_final(tk, &tctx);
 1702 
 1703       key = tk;
 1704       key_len = 16;
 1705    }
 1706 
 1707    /* the HMAC_MD5 transform looks like:
 1708     *
 1709     * MD5(K XOR opad, MD5(K XOR ipad, text))
 1710     *
 1711     * where K is an n byte key
 1712     * ipad is the byte 0x36 repeated 64 times
 1713     * opad is the byte 0x5c repeated 64 times
 1714     * and text is the data being protected */
 1715 
 1716    /* start out by storing key in pads */
 1717    memset(k_ipad, 0, sizeof k_ipad);
 1718    memset(k_opad, 0, sizeof k_opad);
 1719    memcpy(k_ipad, key, key_len);
 1720    memcpy(k_opad, key, key_len);
 1721 
 1722    /* XOR key with ipad and opad values */
 1723    for (i=0; i<64; i++) {
 1724       k_ipad[i] ^= 0x36;
 1725       k_opad[i] ^= 0x5c;
 1726    }
 1727 
 1728    /* perform inner MD5 */
 1729    md5_init(&context);                    /* init context for 1st pass */
 1730    md5_update(&context, k_ipad, 64);      /* start with inner pad */
 1731    md5_update(&context, text, text_len);  /* then text of datagram */
 1732    md5_final(digest, &context);           /* finish up 1st pass */
 1733 
 1734    /* perform outer MD5 */
 1735    md5_init(&context);                 /* init context for 2nd pass */
 1736    md5_update(&context, k_opad, 64);   /* start with outer pad */
 1737    md5_update(&context, digest, 16);   /* then results of 1st hash */
 1738    md5_final(digest, &context);        /* finish up 2nd pass */
 1739    NYD_LEAVE;
 1740 }
 1741 #endif /* HAVE_MD5 */
 1742 
 1743 /* s-it-mode */