"Fossies" - the Fresh Open Source Software Archive

Member "s-nail-14.9.7/urlcrecry.c" (16 Feb 2018, 48220 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 "urlcrecry.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  *@ 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    memcpy(urlp->url_proto, X "://", sizeof(X "://"));\
  862    urlp->url_proto[sizeof(X) -1] = '\0';\
  863    urlp->url_proto_len = sizeof(X) -1;\
  864    urlp->url_proto_xlen = 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 = salloc((i = urlp->url_proto_xlen + urlp->url_u_h_p.l) +1);
 1170 
 1171       urlp->url_proto[urlp->url_proto_len] = ':';
 1172       memcpy(sstpcpy(ud, urlp->url_proto), urlp->url_u_h_p.s,
 1173          urlp->url_u_h_p.l +1);
 1174       urlp->url_proto[urlp->url_proto_len] = '\0';
 1175 
 1176       urlp->url_p_u_h_p = ud;
 1177    }
 1178 
 1179    /* .url_p_eu_h_p, .url_p_eu_h_p_p: .url_proto://.url_eu_h_p[/.url_path] */
 1180    {  size_t i;
 1181       char *ud = salloc((i = urlp->url_proto_xlen + urlp->url_eu_h_p.l) +
 1182             1 + urlp->url_path.l +1);
 1183 
 1184       urlp->url_proto[urlp->url_proto_len] = ':';
 1185       memcpy(sstpcpy(ud, urlp->url_proto), urlp->url_eu_h_p.s,
 1186          urlp->url_eu_h_p.l +1);
 1187       urlp->url_proto[urlp->url_proto_len] = '\0';
 1188 
 1189       if (urlp->url_path.l == 0)
 1190          urlp->url_p_eu_h_p = urlp->url_p_eu_h_p_p = ud;
 1191       else {
 1192          urlp->url_p_eu_h_p = savestrbuf(ud, i);
 1193          urlp->url_p_eu_h_p_p = ud;
 1194          ud += i;
 1195          *ud++ = '/';
 1196          memcpy(ud, urlp->url_path.s, urlp->url_path.l +1);
 1197       }
 1198    }
 1199 
 1200    rv = TRU1;
 1201 #endif /* a_ANYPROTO */
 1202 jleave:
 1203    NYD_LEAVE;
 1204    return rv;
 1205 #undef a_ANYPROTO
 1206 #undef a_ALLPROTO
 1207 }
 1208 
 1209 FL bool_t
 1210 ccred_lookup_old(struct ccred *ccp, enum cproto cproto, char const *addr)
 1211 {
 1212    char const *pname, *pxstr, *authdef, *s;
 1213    size_t pxlen, addrlen, i;
 1214    char *vbuf;
 1215    ui8_t authmask;
 1216    enum {NONE=0, WANT_PASS=1<<0, REQ_PASS=1<<1, WANT_USER=1<<2, REQ_USER=1<<3}
 1217       ware = NONE;
 1218    bool_t addr_is_nuser = FAL0; /* XXX v15.0 legacy! v15_compat */
 1219    NYD_ENTER;
 1220 
 1221    n_OBSOLETE(_("Use of old-style credentials, which will vanish in v15!\n"
 1222       "  Please read the manual section "
 1223          "\"On URL syntax and credential lookup\""));
 1224 
 1225    memset(ccp, 0, sizeof *ccp);
 1226 
 1227    switch (cproto) {
 1228    default:
 1229    case CPROTO_SMTP:
 1230       pname = "SMTP";
 1231       pxstr = "smtp-auth";
 1232       pxlen = sizeof("smtp-auth") -1;
 1233       authmask = AUTHTYPE_NONE | AUTHTYPE_PLAIN | AUTHTYPE_LOGIN |
 1234             AUTHTYPE_CRAM_MD5 | AUTHTYPE_GSSAPI;
 1235       authdef = "none";
 1236       addr_is_nuser = TRU1;
 1237       break;
 1238    case CPROTO_POP3:
 1239       pname = "POP3";
 1240       pxstr = "pop3-auth";
 1241       pxlen = sizeof("pop3-auth") -1;
 1242       authmask = AUTHTYPE_PLAIN;
 1243       authdef = "plain";
 1244       break;
 1245 #ifdef HAVE_IMAP
 1246    case CPROTO_IMAP:
 1247       pname = "IMAP";
 1248       pxstr = "imap-auth";
 1249       pxlen = sizeof("imap-auth") -1;
 1250       authmask = AUTHTYPE_LOGIN | AUTHTYPE_CRAM_MD5 | AUTHTYPE_GSSAPI;
 1251       authdef = "login";
 1252       break;
 1253 #endif
 1254    }
 1255 
 1256    ccp->cc_cproto = cproto;
 1257    addrlen = strlen(addr);
 1258    vbuf = ac_alloc(pxlen + addrlen + sizeof("-password-")-1 +1);
 1259    memcpy(vbuf, pxstr, pxlen);
 1260 
 1261    /* Authentication type */
 1262    vbuf[pxlen] = '-';
 1263    memcpy(vbuf + pxlen + 1, addr, addrlen +1);
 1264    if ((s = n_var_vlook(vbuf, FAL0)) == NULL) {
 1265       vbuf[pxlen] = '\0';
 1266       if ((s = n_var_vlook(vbuf, FAL0)) == NULL)
 1267          s = n_UNCONST(authdef);
 1268    }
 1269 
 1270    if (!asccasecmp(s, "none")) {
 1271       ccp->cc_auth = "NONE";
 1272       ccp->cc_authtype = AUTHTYPE_NONE;
 1273       /*ware = NONE;*/
 1274    } else if (!asccasecmp(s, "plain")) {
 1275       ccp->cc_auth = "PLAIN";
 1276       ccp->cc_authtype = AUTHTYPE_PLAIN;
 1277       ware = REQ_PASS | REQ_USER;
 1278    } else if (!asccasecmp(s, "login")) {
 1279       ccp->cc_auth = "LOGIN";
 1280       ccp->cc_authtype = AUTHTYPE_LOGIN;
 1281       ware = REQ_PASS | REQ_USER;
 1282    } else if (!asccasecmp(s, "cram-md5")) {
 1283       ccp->cc_auth = "CRAM-MD5";
 1284       ccp->cc_authtype = AUTHTYPE_CRAM_MD5;
 1285       ware = REQ_PASS | REQ_USER;
 1286    } else if (!asccasecmp(s, "gssapi")) {
 1287       ccp->cc_auth = "GSS-API";
 1288       ccp->cc_authtype = AUTHTYPE_GSSAPI;
 1289       ware = REQ_USER;
 1290    } /* no else */
 1291 
 1292    /* Verify method */
 1293    if (!(ccp->cc_authtype & authmask)) {
 1294       n_err(_("Unsupported %s authentication method: %s\n"), pname, s);
 1295       ccp = NULL;
 1296       goto jleave;
 1297    }
 1298 # ifndef HAVE_MD5
 1299    if (ccp->cc_authtype == AUTHTYPE_CRAM_MD5) {
 1300       n_err(_("No CRAM-MD5 support compiled in\n"));
 1301       ccp = NULL;
 1302       goto jleave;
 1303    }
 1304 # endif
 1305 # ifndef HAVE_GSSAPI
 1306    if (ccp->cc_authtype == AUTHTYPE_GSSAPI) {
 1307       n_err(_("No GSS-API support compiled in\n"));
 1308       ccp = NULL;
 1309       goto jleave;
 1310    }
 1311 # endif
 1312 
 1313    /* User name */
 1314    if (!(ware & (WANT_USER | REQ_USER)))
 1315       goto jpass;
 1316 
 1317    if (!addr_is_nuser) {
 1318       if ((s = _url_last_at_before_slash(addr)) != NULL) {
 1319          char *cp;
 1320 
 1321          cp = savestrbuf(addr, PTR2SIZE(s - addr));
 1322 
 1323          if((ccp->cc_user.s = urlxdec(cp)) == NULL){
 1324             n_err(_("String is not properly URL percent encoded: %s\n"), cp);
 1325             ccp = NULL;
 1326             goto jleave;
 1327          }
 1328          ccp->cc_user.l = strlen(ccp->cc_user.s);
 1329       } else if (ware & REQ_USER)
 1330          goto jgetuser;
 1331       goto jpass;
 1332    }
 1333 
 1334    memcpy(vbuf + pxlen, "-user-", i = sizeof("-user-") -1);
 1335    i += pxlen;
 1336    memcpy(vbuf + i, addr, addrlen +1);
 1337    if ((s = n_var_vlook(vbuf, FAL0)) == NULL) {
 1338       vbuf[--i] = '\0';
 1339       if ((s = n_var_vlook(vbuf, FAL0)) == NULL && (ware & REQ_USER)) {
 1340          if ((s = getuser(NULL)) == NULL) {
 1341 jgetuser:   /* TODO v15.0: today we simply bail, but we should call getuser().
 1342              * TODO even better: introduce "PROTO-user" and "PROTO-pass" and
 1343              * TODO check that first, then! change control flow, grow vbuf */
 1344             n_err(_("A user is necessary for %s authentication\n"), pname);
 1345             ccp = NULL;
 1346             goto jleave;
 1347          }
 1348       }
 1349    }
 1350    ccp->cc_user.l = strlen(ccp->cc_user.s = savestr(s));
 1351 
 1352    /* Password */
 1353 jpass:
 1354    if (!(ware & (WANT_PASS | REQ_PASS)))
 1355       goto jleave;
 1356 
 1357    if (!addr_is_nuser) {
 1358       memcpy(vbuf, "password-", i = sizeof("password-") -1);
 1359    } else {
 1360       memcpy(vbuf + pxlen, "-password-", i = sizeof("-password-") -1);
 1361       i += pxlen;
 1362    }
 1363    memcpy(vbuf + i, addr, addrlen +1);
 1364    if ((s = n_var_vlook(vbuf, FAL0)) == NULL) {
 1365       vbuf[--i] = '\0';
 1366       if ((!addr_is_nuser || (s = n_var_vlook(vbuf, FAL0)) == NULL) &&
 1367             (ware & REQ_PASS)) {
 1368          if ((s = getpassword(savecat(_("Password for "), pname))) == NULL) {
 1369             n_err(_("A password is necessary for %s authentication\n"),
 1370                pname);
 1371             ccp = NULL;
 1372             goto jleave;
 1373          }
 1374       }
 1375    }
 1376    if (s != NULL)
 1377       ccp->cc_pass.l = strlen(ccp->cc_pass.s = savestr(s));
 1378 
 1379 jleave:
 1380    ac_free(vbuf);
 1381    if (ccp != NULL && (n_poption & n_PO_D_VV))
 1382       n_err(_("Credentials: host %s, user %s, pass %s\n"),
 1383          addr, (ccp->cc_user.s != NULL ? ccp->cc_user.s : n_empty),
 1384          (ccp->cc_pass.s != NULL ? ccp->cc_pass.s : n_empty));
 1385    NYD_LEAVE;
 1386    return (ccp != NULL);
 1387 }
 1388 
 1389 FL bool_t
 1390 ccred_lookup(struct ccred *ccp, struct url *urlp)
 1391 {
 1392    char *s;
 1393    char const *pstr, *authdef;
 1394    ui8_t authmask;
 1395    enum okeys authokey;
 1396    enum {NONE=0, WANT_PASS=1<<0, REQ_PASS=1<<1, WANT_USER=1<<2, REQ_USER=1<<3}
 1397       ware;
 1398    NYD_ENTER;
 1399 
 1400    memset(ccp, 0, sizeof *ccp);
 1401    ccp->cc_user = urlp->url_user;
 1402 
 1403    ware = NONE;
 1404 
 1405    switch ((ccp->cc_cproto = urlp->url_cproto)) {
 1406    case CPROTO_CCRED:
 1407       authokey = (enum okeys)-1;
 1408       authmask = AUTHTYPE_PLAIN;
 1409       authdef = "plain";
 1410       pstr = "ccred";
 1411       break;
 1412    default:
 1413    case CPROTO_SMTP:
 1414       authokey = ok_v_smtp_auth;
 1415       authmask = AUTHTYPE_NONE | AUTHTYPE_PLAIN | AUTHTYPE_LOGIN |
 1416             AUTHTYPE_CRAM_MD5 | AUTHTYPE_GSSAPI;
 1417       authdef = "plain";
 1418       pstr = "smtp";
 1419       break;
 1420    case CPROTO_POP3:
 1421       authokey = ok_v_pop3_auth;
 1422       authmask = AUTHTYPE_PLAIN;
 1423       authdef = "plain";
 1424       pstr = "pop3";
 1425       break;
 1426 #ifdef HAVE_IMAP
 1427    case CPROTO_IMAP:
 1428       pstr = "imap";
 1429       authokey = ok_v_imap_auth;
 1430       authmask = AUTHTYPE_LOGIN | AUTHTYPE_CRAM_MD5 | AUTHTYPE_GSSAPI;
 1431       authdef = "login";
 1432       break;
 1433 #endif
 1434    }
 1435 
 1436    /* Authentication type */
 1437    if (authokey == (enum okeys)-1 ||
 1438          (s = xok_VLOOK(authokey, urlp, OXM_ALL)) == NULL)
 1439       s = n_UNCONST(authdef);
 1440 
 1441    if (!asccasecmp(s, "none")) {
 1442       ccp->cc_auth = "NONE";
 1443       ccp->cc_authtype = AUTHTYPE_NONE;
 1444       /*ware = NONE;*/
 1445    } else if (!asccasecmp(s, "plain")) {
 1446       ccp->cc_auth = "PLAIN";
 1447       ccp->cc_authtype = AUTHTYPE_PLAIN;
 1448       ware = REQ_PASS | REQ_USER;
 1449    } else if (!asccasecmp(s, "login")) {
 1450       ccp->cc_auth = "LOGIN";
 1451       ccp->cc_authtype = AUTHTYPE_LOGIN;
 1452       ware = REQ_PASS | REQ_USER;
 1453    } else if (!asccasecmp(s, "cram-md5")) {
 1454       ccp->cc_auth = "CRAM-MD5";
 1455       ccp->cc_authtype = AUTHTYPE_CRAM_MD5;
 1456       ware = REQ_PASS | REQ_USER;
 1457    } else if (!asccasecmp(s, "gssapi")) {
 1458       ccp->cc_auth = "GSS-API";
 1459       ccp->cc_authtype = AUTHTYPE_GSSAPI;
 1460       ware = REQ_USER;
 1461    } /* no else */
 1462 
 1463    /* Verify method */
 1464    if (!(ccp->cc_authtype & authmask)) {
 1465       n_err(_("Unsupported %s authentication method: %s\n"), pstr, s);
 1466       ccp = NULL;
 1467       goto jleave;
 1468    }
 1469 # ifndef HAVE_MD5
 1470    if (ccp->cc_authtype == AUTHTYPE_CRAM_MD5) {
 1471       n_err(_("No CRAM-MD5 support compiled in\n"));
 1472       ccp = NULL;
 1473       goto jleave;
 1474    }
 1475 # endif
 1476 # ifndef HAVE_GSSAPI
 1477    if (ccp->cc_authtype == AUTHTYPE_GSSAPI) {
 1478       n_err(_("No GSS-API support compiled in\n"));
 1479       ccp = NULL;
 1480       goto jleave;
 1481    }
 1482 # endif
 1483 
 1484    /* Password */
 1485    ccp->cc_pass = urlp->url_pass;
 1486    if (ccp->cc_pass.s != NULL)
 1487       goto jleave;
 1488 
 1489    if ((s = xok_vlook(password, urlp, OXM_ALL)) != NULL)
 1490       goto js2pass;
 1491 # ifdef HAVE_AGENT /* TODO v15-compat obsolete */
 1492    if ((s = xok_vlook(agent_shell_lookup, urlp, OXM_ALL)) != NULL) {
 1493       n_OBSOLETE(_("*agent-shell-lookup* will vanish.  Please use encrypted "
 1494          "~/.netrc (via *netrc-pipe*), or simply `source' an encrypted file"));
 1495       if (!_agent_shell_lookup(urlp, s)) {
 1496          ccp = NULL;
 1497          goto jleave;
 1498       } else if (urlp->url_pass.s != NULL) {
 1499          ccp->cc_pass = urlp->url_pass;
 1500          goto jleave;
 1501       }
 1502    }
 1503 # endif
 1504 # ifdef HAVE_NETRC
 1505    if (xok_blook(netrc_lookup, urlp, OXM_ALL) && _nrc_lookup(urlp, TRU1)) {
 1506       ccp->cc_pass = urlp->url_pass;
 1507       goto jleave;
 1508    }
 1509 # endif
 1510    if (ware & REQ_PASS) {
 1511       if((s = getpassword(savecat(urlp->url_u_h.s, _(" requires a password: ")))
 1512             ) != NULL)
 1513 js2pass:
 1514          ccp->cc_pass.l = strlen(ccp->cc_pass.s = savestr(s));
 1515       else {
 1516          n_err(_("A password is necessary for %s authentication\n"), pstr);
 1517          ccp = NULL;
 1518       }
 1519    }
 1520 
 1521 jleave:
 1522    if(ccp != NULL && (n_poption & n_PO_D_VV))
 1523       n_err(_("Credentials: host %s, user %s, pass %s\n"),
 1524          urlp->url_h_p.s, (ccp->cc_user.s != NULL ? ccp->cc_user.s : n_empty),
 1525          (ccp->cc_pass.s != NULL ? ccp->cc_pass.s : n_empty));
 1526    NYD_LEAVE;
 1527    return (ccp != NULL);
 1528 }
 1529 #endif /* HAVE_SOCKETS */
 1530 
 1531 #ifdef HAVE_NETRC
 1532 FL int
 1533 c_netrc(void *v)
 1534 {
 1535    char **argv = v;
 1536    struct nrc_node *nrc;
 1537    bool_t load_only;
 1538    NYD_ENTER;
 1539 
 1540    load_only = FAL0;
 1541    if (*argv == NULL)
 1542       goto jlist;
 1543    if (argv[1] != NULL)
 1544       goto jerr;
 1545    if (!asccasecmp(*argv, "show"))
 1546       goto jlist;
 1547    load_only = TRU1;
 1548    if (!asccasecmp(*argv, "load"))
 1549       goto jlist;
 1550    if (!asccasecmp(*argv, "clear"))
 1551       goto jclear;
 1552 jerr:
 1553    n_err(_("Synopsis: netrc: (<show> or) <clear> the .netrc cache\n"));
 1554    v = NULL;
 1555 jleave:
 1556    NYD_LEAVE;
 1557    return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
 1558 
 1559 jlist:   {
 1560    FILE *fp;
 1561    size_t l;
 1562 
 1563    if (_nrc_list == NULL)
 1564       _nrc_init();
 1565    if (_nrc_list == NRC_NODE_ERR) {
 1566       n_err(_("Interpolate what file?\n"));
 1567       v = NULL;
 1568       goto jleave;
 1569    }
 1570    if (load_only)
 1571       goto jleave;
 1572 
 1573    if ((fp = Ftmp(NULL, "netrc", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL) {
 1574       n_perr(_("tmpfile"), 0);
 1575       v = NULL;
 1576       goto jleave;
 1577    }
 1578 
 1579    for (l = 0, nrc = _nrc_list; nrc != NULL; ++l, nrc = nrc->nrc_next) {
 1580       fprintf(fp, _("machine %s "), nrc->nrc_dat); /* XXX quote? */
 1581       if (nrc->nrc_ulen > 0)
 1582          fprintf(fp, _("login \"%s\" "),
 1583             string_quote(nrc->nrc_dat + nrc->nrc_mlen +1));
 1584       if (nrc->nrc_plen > 0)
 1585          fprintf(fp, _("password \"%s\"\n"),
 1586             string_quote(nrc->nrc_dat + nrc->nrc_mlen +1 + nrc->nrc_ulen +1));
 1587       else
 1588          putc('\n', fp);
 1589    }
 1590 
 1591    page_or_print(fp, l);
 1592    Fclose(fp);
 1593    }
 1594    goto jleave;
 1595 
 1596 jclear:
 1597    if (_nrc_list == NRC_NODE_ERR)
 1598       _nrc_list = NULL;
 1599    while ((nrc = _nrc_list) != NULL) {
 1600       _nrc_list = nrc->nrc_next;
 1601       free(nrc);
 1602    }
 1603    goto jleave;
 1604 }
 1605 #endif /* HAVE_NETRC */
 1606 
 1607 #ifdef HAVE_MD5
 1608 FL char *
 1609 md5tohex(char hex[MD5TOHEX_SIZE], void const *vp)
 1610 {
 1611    char const *cp = vp;
 1612    size_t i, j;
 1613    NYD_ENTER;
 1614 
 1615    for (i = 0; i < MD5TOHEX_SIZE / 2; ++i) {
 1616       j = i << 1;
 1617 # define __hex(n) ((n) > 9 ? (n) - 10 + 'a' : (n) + '0')
 1618       hex[j] = __hex((cp[i] & 0xF0) >> 4);
 1619       hex[++j] = __hex(cp[i] & 0x0F);
 1620 # undef __hex
 1621    }
 1622    NYD_LEAVE;
 1623    return hex;
 1624 }
 1625 
 1626 FL char *
 1627 cram_md5_string(struct str const *user, struct str const *pass,
 1628    char const *b64)
 1629 {
 1630    struct str in, out;
 1631    char digest[16], *cp;
 1632    NYD_ENTER;
 1633 
 1634    out.s = NULL;
 1635    if(user->l >= UIZ_MAX - 1 - MD5TOHEX_SIZE - 1)
 1636       goto jleave;
 1637    if(pass->l >= INT_MAX)
 1638       goto jleave;
 1639 
 1640    in.s = n_UNCONST(b64);
 1641    in.l = strlen(in.s);
 1642    if(!b64_decode(&out, &in))
 1643       goto jleave;
 1644    if(out.l >= INT_MAX){
 1645       free(out.s);
 1646       out.s = NULL;
 1647       goto jleave;
 1648    }
 1649 
 1650    hmac_md5((uc_i*)out.s, out.l, (uc_i*)pass->s, pass->l, digest);
 1651    free(out.s);
 1652    cp = md5tohex(salloc(MD5TOHEX_SIZE +1), digest);
 1653 
 1654    in.l = user->l + MD5TOHEX_SIZE +1;
 1655    in.s = ac_alloc(user->l + 1 + MD5TOHEX_SIZE +1);
 1656    memcpy(in.s, user->s, user->l);
 1657    in.s[user->l] = ' ';
 1658    memcpy(&in.s[user->l + 1], cp, MD5TOHEX_SIZE);
 1659    if(b64_encode(&out, &in, B64_SALLOC | B64_CRLF) == NULL)
 1660       out.s = NULL;
 1661    ac_free(in.s);
 1662 jleave:
 1663    NYD_LEAVE;
 1664    return out.s;
 1665 }
 1666 
 1667 FL void
 1668 hmac_md5(unsigned char *text, int text_len, unsigned char *key, int key_len,
 1669    void *digest)
 1670 {
 1671    /*
 1672     * This code is taken from
 1673     *
 1674     * Network Working Group                                       H. Krawczyk
 1675     * Request for Comments: 2104                                          IBM
 1676     * Category: Informational                                      M. Bellare
 1677     *                                                                    UCSD
 1678     *                                                              R. Canetti
 1679     *                                                                     IBM
 1680     *                                                           February 1997
 1681     *
 1682     *
 1683     *             HMAC: Keyed-Hashing for Message Authentication
 1684     */
 1685    md5_ctx context;
 1686    unsigned char k_ipad[65]; /* inner padding - key XORd with ipad */
 1687    unsigned char k_opad[65]; /* outer padding - key XORd with opad */
 1688    unsigned char tk[16];
 1689    int i;
 1690    NYD_ENTER;
 1691 
 1692    /* if key is longer than 64 bytes reset it to key=MD5(key) */
 1693    if (key_len > 64) {
 1694       md5_ctx tctx;
 1695 
 1696       md5_init(&tctx);
 1697       md5_update(&tctx, key, key_len);
 1698       md5_final(tk, &tctx);
 1699 
 1700       key = tk;
 1701       key_len = 16;
 1702    }
 1703 
 1704    /* the HMAC_MD5 transform looks like:
 1705     *
 1706     * MD5(K XOR opad, MD5(K XOR ipad, text))
 1707     *
 1708     * where K is an n byte key
 1709     * ipad is the byte 0x36 repeated 64 times
 1710     * opad is the byte 0x5c repeated 64 times
 1711     * and text is the data being protected */
 1712 
 1713    /* start out by storing key in pads */
 1714    memset(k_ipad, 0, sizeof k_ipad);
 1715    memset(k_opad, 0, sizeof k_opad);
 1716    memcpy(k_ipad, key, key_len);
 1717    memcpy(k_opad, key, key_len);
 1718 
 1719    /* XOR key with ipad and opad values */
 1720    for (i=0; i<64; i++) {
 1721       k_ipad[i] ^= 0x36;
 1722       k_opad[i] ^= 0x5c;
 1723    }
 1724 
 1725    /* perform inner MD5 */
 1726    md5_init(&context);                    /* init context for 1st pass */
 1727    md5_update(&context, k_ipad, 64);      /* start with inner pad */
 1728    md5_update(&context, text, text_len);  /* then text of datagram */
 1729    md5_final(digest, &context);           /* finish up 1st pass */
 1730 
 1731    /* perform outer MD5 */
 1732    md5_init(&context);                 /* init context for 2nd pass */
 1733    md5_update(&context, k_opad, 64);   /* start with outer pad */
 1734    md5_update(&context, digest, 16);   /* then results of 1st hash */
 1735    md5_final(digest, &context);        /* finish up 2nd pass */
 1736    NYD_LEAVE;
 1737 }
 1738 #endif /* HAVE_MD5 */
 1739 
 1740 /* s-it-mode */