run_editor(): also compare size when deciding "has changed"
[s-mailx.git] / urlcrecry.c
blob7872b02d076e21aef446b88b3ace92fd6111204d
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 $
6 * Copyright (c) 2014 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
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.
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.
21 #ifndef HAVE_AMALGAMATION
22 # include "nail.h"
23 #endif
25 #ifdef HAVE_NETRC
26 /* NetBSD usr.bin/ftp/ruserpass.c uses 100 bytes for that, we need four
27 * concurrently (dummy, host, user, pass), so make it a KB */
28 # define NRC_TOKEN_MAXLEN (1024 / 4)
30 enum nrc_token {
31 NRC_ERROR = -1,
32 NRC_NONE = 0,
33 NRC_DEFAULT,
34 NRC_LOGIN,
35 NRC_PASSWORD,
36 NRC_ACCOUNT,
37 NRC_MACDEF,
38 NRC_MACHINE,
39 NRC_INPUT
42 enum nrc_result {
43 NRC_RESERROR = -1,
44 NRC_RESOK,
45 NRC_RESNONE
48 struct nrc_node {
49 struct nrc_node *nrc_next;
50 struct nrc_node *nrc_result; /* In match phase, former possible one */
51 ui32_t nrc_mlen; /* Length of machine name */
52 ui32_t nrc_ulen; /* Length of user name */
53 ui32_t nrc_plen; /* Length of password */
54 char nrc_dat[VFIELD_SIZE(4)];
56 # define NRC_NODE_ERR ((struct nrc_node*)-1)
58 static struct nrc_node *_nrc_list;
60 /* Initialize .netrc cache */
61 static void _nrc_init(void);
62 static enum nrc_token __nrc_token(FILE *fi, char buffer[NRC_TOKEN_MAXLEN]);
64 /* We shall lookup a machine in .netrc says ok_blook(netrc_lookup).
65 * only_pass is true then the lookup is for the password only, otherwise we
66 * look for a user (and add password only if we have an exact machine match) */
67 static enum nrc_result _nrc_lookup(struct url *urlp, bool_t only_pass);
69 /* 0=no match; 1=exact match; -1=wildcard match */
70 static int __nrc_host_match(struct nrc_node const *nrc,
71 struct url const *urlp);
72 static bool_t __nrc_find_user(struct url *urlp,
73 struct nrc_node const *nrc);
74 static bool_t __nrc_find_pass(struct url *urlp, bool_t user_match,
75 struct nrc_node const *nrc);
76 #endif /* HAVE_NETRC */
78 #ifdef HAVE_NETRC
79 static void
80 _nrc_init(void)
82 char buffer[NRC_TOKEN_MAXLEN], host[NRC_TOKEN_MAXLEN],
83 user[NRC_TOKEN_MAXLEN], pass[NRC_TOKEN_MAXLEN], *netrc_load;
84 struct stat sb;
85 FILE *fi;
86 enum nrc_token t;
87 bool_t seen_default;
88 struct nrc_node *ntail = NULL /* CC happy */, *nhead = NULL,
89 *nrc = NRC_NODE_ERR;
90 NYD_ENTER;
92 if ((netrc_load = getenv("NETRC")/* TODO */) == NULL)
93 netrc_load = UNCONST(NETRC);
94 if ((netrc_load = file_expand(netrc_load)) == NULL)
95 goto jleave;
97 if ((fi = Fopen(netrc_load, "r")) == NULL) {
98 fprintf(stderr, _("Cannot open `%s'\n"), netrc_load);
99 goto jleave;
102 /* Be simple and apply rigid (permission) check(s) */
103 if (fstat(fileno(fi), &sb) == -1 || !S_ISREG(sb.st_mode) ||
104 (sb.st_mode & (S_IRWXG | S_IRWXO))) {
105 fprintf(stderr,
106 _("Not a regular file, or accessible by non-user: `%s'\n"),
107 netrc_load);
108 goto jleave;
111 seen_default = FAL0;
112 jnext:
113 switch((t = __nrc_token(fi, buffer))) {
114 case NRC_ERROR:
115 goto jerr;
116 case NRC_NONE:
117 break;
118 default: /* Doesn't happen (but on error?), keep CC happy */
119 case NRC_DEFAULT:
120 jdef:
121 seen_default = TRU1;
122 /* FALLTHRU */
123 case NRC_MACHINE:
124 jm_h:
125 *host = '\0';
126 if (!seen_default && (t = __nrc_token(fi, host)) != NRC_INPUT)
127 goto jerr;
128 *user = *pass = '\0';
129 while ((t = __nrc_token(fi, buffer)) != NRC_NONE && t != NRC_MACHINE &&
130 t != NRC_DEFAULT) {
131 switch(t) {
132 case NRC_LOGIN:
133 if ((t = __nrc_token(fi, user)) != NRC_INPUT)
134 goto jerr;
135 break;
136 case NRC_PASSWORD:
137 if ((t = __nrc_token(fi, pass)) != NRC_INPUT)
138 goto jerr;
139 break;
140 case NRC_ACCOUNT:
141 if ((t = __nrc_token(fi, buffer)) != NRC_INPUT)
142 goto jerr;
143 break;
144 case NRC_MACDEF:
145 if ((t = __nrc_token(fi, buffer)) != NRC_INPUT)
146 goto jerr;
147 else {
148 int i = 0, c;
149 while ((c = getc(fi)) != EOF)
150 if (c == '\n') { /* xxx */
151 if (i)
152 break;
153 i = 1;
154 } else
155 i = 0;
157 break;
158 default:
159 case NRC_ERROR:
160 goto jerr;
164 if (!seen_default && (*user != '\0' || *pass != '\0')) {
165 size_t hl = strlen(host), ul = strlen(user), pl = strlen(pass);
166 struct nrc_node *nx = smalloc(sizeof(*nx) -
167 VFIELD_SIZEOF(struct nrc_node, nrc_dat) + hl +1 + ul +1 + pl +1);
169 if (nhead != NULL)
170 ntail->nrc_next = nx;
171 else
172 nhead = nx;
173 ntail = nx;
174 nx->nrc_next = NULL;
175 nx->nrc_mlen = hl;
176 nx->nrc_ulen = ul;
177 nx->nrc_plen = pl;
178 memcpy(nx->nrc_dat, host, ++hl);
179 memcpy(nx->nrc_dat + hl, user, ++ul);
180 memcpy(nx->nrc_dat + hl + ul, pass, ++pl);
182 if (t == NRC_MACHINE)
183 goto jm_h;
184 if (t == NRC_DEFAULT)
185 goto jdef;
186 if (t != NRC_NONE)
187 goto jnext;
188 break;
191 if (nhead != NULL)
192 nrc = nhead;
193 else
194 jerr:
195 if (options & OPT_D_V)
196 fprintf(stderr, _("Errors occurred while parsing `%s'\n"), netrc_load);
197 Fclose(fi);
198 jleave:
199 if (nrc == NRC_NODE_ERR)
200 while (nhead != NULL) {
201 ntail = nhead;
202 nhead = nhead->nrc_next;
203 free(ntail);
205 _nrc_list = nrc;
206 NYD_LEAVE;
209 static enum nrc_token
210 __nrc_token(FILE *fi, char buffer[NRC_TOKEN_MAXLEN])
212 int c;
213 char *cp;
214 enum nrc_token rv = NRC_NONE;
215 NYD_ENTER;
217 c = EOF;
218 if (feof(fi) || ferror(fi))
219 goto jleave;
221 while ((c = getc(fi)) != EOF && whitechar(c))
223 if (c == EOF)
224 goto jleave;
226 cp = buffer;
227 if (c == '"') {
228 /* Not requiring the closing QM is the portable way */
229 while ((c = getc(fi)) != EOF && c != '"') {
230 if (c == '\\')
231 if ((c = getc(fi)) == EOF)
232 break;
233 *cp++ = c;
234 if (PTRCMP(cp, ==, buffer + NRC_TOKEN_MAXLEN)) {
235 rv = NRC_ERROR;
236 goto jleave;
239 } else {
240 *cp++ = c;
241 while ((c = getc(fi)) != EOF && !whitechar(c)) {
242 if (c == '\\' && (c = getc(fi)) == EOF)
243 break;
244 *cp++ = c;
245 if (PTRCMP(cp, ==, buffer + NRC_TOKEN_MAXLEN)) {
246 rv = NRC_ERROR;
247 goto jleave;
251 *cp = '\0';
253 if (*buffer == '\0')
254 do {/*rv = NRC_NONE*/} while (0);
255 else if (!strcmp(buffer, "default"))
256 rv = NRC_DEFAULT;
257 else if (!strcmp(buffer, "login"))
258 rv = NRC_LOGIN;
259 else if (!strcmp(buffer, "password") || !strcmp(buffer, "passwd"))
260 rv = NRC_PASSWORD;
261 else if (!strcmp(buffer, "account"))
262 rv = NRC_ACCOUNT;
263 else if (!strcmp(buffer, "macdef"))
264 rv = NRC_MACDEF;
265 else if (!strcmp(buffer, "machine"))
266 rv = NRC_MACHINE;
267 else
268 rv = NRC_INPUT;
269 jleave:
270 if (c == EOF && !feof(fi))
271 rv = NRC_ERROR;
272 NYD_LEAVE;
273 return rv;
276 static enum nrc_result
277 _nrc_lookup(struct url *urlp, bool_t only_pass) /* TODO optimize; too tricky!! */
279 struct nrc_node *nrc, *nrc_wild, *nrc_exact;
280 enum nrc_result rv = NRC_RESNONE;
281 NYD_ENTER;
283 assert(!only_pass || urlp->url_user.s != NULL);
284 assert(only_pass || urlp->url_user.s == NULL);
286 if (_nrc_list == NULL)
287 _nrc_init();
288 if (_nrc_list == NRC_NODE_ERR)
289 goto jleave;
291 nrc_wild = nrc_exact = NULL;
292 for (nrc = _nrc_list; nrc != NULL; nrc = nrc->nrc_next)
293 switch (__nrc_host_match(nrc, urlp)) {
294 case 1:
295 nrc->nrc_result = nrc_exact;
296 nrc_exact = nrc;
297 continue;
298 case -1:
299 nrc->nrc_result = nrc_wild;
300 nrc_wild = nrc;
301 /* FALLTHRU */
302 case 0:
303 continue;
306 /* TODO _nrc_lookup(): PAIN! init: build sorted tree, single walk that!!
307 * TODO then: verify .netrc (unique fallback entries etc.) */
308 if (!only_pass && !__nrc_find_user(urlp, nrc_exact) &&
309 !__nrc_find_user(urlp, nrc_wild))
310 goto jleave;
312 if (__nrc_find_pass(urlp, TRU1, nrc_exact) ||
313 __nrc_find_pass(urlp, TRU1, nrc_wild) ||
314 /* Do not try to find a password without exact user match unless we've
315 * been called during credential lookup, a.k.a. the second time */
316 !only_pass ||
317 __nrc_find_pass(urlp, FAL0, nrc_exact) ||
318 __nrc_find_pass(urlp, FAL0, nrc_wild))
319 rv = NRC_RESOK;
320 jleave:
321 NYD_LEAVE;
322 return rv;
325 static int
326 __nrc_host_match(struct nrc_node const *nrc, struct url const *urlp)
328 char const *d2, *d1;
329 size_t l2, l1;
330 int rv = 0;
331 NYD_ENTER;
333 /* Find a matching machine entry -- entries are lowercase normalized */
334 if (nrc->nrc_mlen == urlp->url_host.l) {
335 if (LIKELY(!memcmp(nrc->nrc_dat, urlp->url_host.s, urlp->url_host.l)))
336 rv = 1;
337 goto jleave;
340 /* Cannot be an exact match, but maybe the .netrc machine starts with
341 * a `*.' glob, which we recognize as an extension, meaning "skip
342 * a single subdomain, then match the rest" */
343 d1 = nrc->nrc_dat + 2;
344 l1 = nrc->nrc_mlen;
345 if (l1 <= 2 || d1[-1] != '.' || d1[-2] != '*')
346 goto jleave;
347 l1 -= 2;
349 /* Brute skipping over one subdomain, no RFC 1035 or RFC 1122 checks;
350 * in fact this even succeeds for `.host.com', but - why care, here? */
351 d2 = urlp->url_host.s;
352 l2 = urlp->url_host.l;
353 while (l2 > 0) {
354 --l2;
355 if (*d2++ == '.')
356 break;
359 if (l2 == l1 && !memcmp(d1, d2, l1))
360 /* This matches, but we won't use it directly but watch out for an
361 * exact match first! */
362 rv = -1;
363 jleave:
364 NYD_LEAVE;
365 return rv;
368 static bool_t
369 __nrc_find_user(struct url *urlp, struct nrc_node const *nrc)
371 NYD_ENTER;
373 for (; nrc != NULL; nrc = nrc->nrc_result)
374 if (nrc->nrc_ulen > 0 && urlp->url_user.s == NULL) {
375 /* Fake it was part of URL otherwise XXX */
376 urlp->url_had_user = TRU1;
377 /* That buffer will be duplicated by url_parse() in this case! */
378 urlp->url_user.s = UNCONST(nrc->nrc_dat + nrc->nrc_mlen +1);
379 urlp->url_user.l = nrc->nrc_ulen;
380 break;
383 NYD_LEAVE;
384 return (nrc != NULL);
387 static bool_t
388 __nrc_find_pass(struct url *urlp, bool_t user_match, struct nrc_node const *nrc)
390 NYD_ENTER;
392 for (; nrc != NULL; nrc = nrc->nrc_result) {
393 if (user_match && (nrc->nrc_ulen != urlp->url_user.l ||
394 memcmp(nrc->nrc_dat + nrc->nrc_mlen +1, urlp->url_user.s,
395 urlp->url_user.l)))
396 continue;
397 if (nrc->nrc_plen == 0)
398 continue;
400 /* We are responsible for duplicating this buffer! */
401 urlp->url_pass.s = savestrbuf(nrc->nrc_dat + nrc->nrc_mlen +1 +
402 nrc->nrc_ulen + 1, (urlp->url_pass.l = nrc->nrc_plen));
403 break;
406 NYD_LEAVE;
407 return (nrc != NULL);
409 #endif /* HAVE_NETRC */
411 FL bool_t
412 url_parse(struct url *urlp, enum cproto cproto, char const *data)
414 #if defined HAVE_SMTP && defined HAVE_POP3 && defined HAVE_IMAP
415 # define __ALLPROTO
416 #endif
417 #if defined HAVE_SMTP || defined HAVE_POP3 || defined HAVE_IMAP
418 # define __ANYPROTO
419 char *cp, *x;
420 #endif
421 bool_t rv = FAL0;
422 NYD_ENTER;
423 UNUSED(data);
425 memset(urlp, 0, sizeof *urlp);
426 urlp->url_input = data;
427 urlp->url_cproto = cproto;
429 /* Network protocol */
430 #define _protox(X,Y) \
431 urlp->url_portno = Y;\
432 memcpy(urlp->url_proto, X "://", sizeof(X "://"));\
433 urlp->url_proto[sizeof(X) -1] = '\0';\
434 urlp->url_proto_len = sizeof(X) -1;\
435 urlp->url_proto_xlen = sizeof(X "://") -1
436 #define __if(X,Y,Z) \
437 if (!ascncasecmp(data, X "://", sizeof(X "://") -1)) {\
438 _protox(X, Y);\
439 data += sizeof(X "://") -1;\
440 do { Z; } while (0);\
441 goto juser;\
443 #define _if(X,Y) __if(X, Y, (void)0)
444 #ifdef HAVE_SSL
445 # define _ifs(X,Y) __if(X, Y, urlp->url_needs_tls = TRU1)
446 #else
447 # define _ifs(X,Y) goto jeproto;
448 #endif
450 switch (cproto) {
451 case CPROTO_SMTP:
452 #ifdef HAVE_SMTP
453 _if ("smtp", 25)
454 _if ("submission", 587)
455 _ifs ("smtps", 465)
456 _protox("smtp", 25);
457 break;
458 #else
459 goto jeproto;
460 #endif
461 case CPROTO_POP3:
462 #ifdef HAVE_POP3
463 _if ("pop3", 110)
464 _ifs ("pop3s", 995)
465 _protox("pop3", 110);
466 break;
467 #else
468 goto jeproto;
469 #endif
470 case CPROTO_IMAP:
471 #ifdef HAVE_IMAP
472 _if ("imap", 143)
473 _ifs ("imaps", 993)
474 _protox("imap", 143);
475 break;
476 #else
477 goto jeproto;
478 #endif
481 #undef _ifs
482 #undef _if
483 #undef __if
484 #undef _protox
486 if (strstr(data, "://") != NULL) {
487 #if !defined __ALLPROTO || !defined HAVE_SSL
488 jeproto:
489 #endif
490 fprintf(stderr, _("URL `proto://' prefix invalid: `%s'\n"),
491 urlp->url_input);
492 goto jleave;
494 #ifdef __ANYPROTO
496 /* User and password, I */
497 juser:
498 if ((cp = UNCONST(last_at_before_slash(data))) != NULL) {
499 size_t l = PTR2SIZE(cp - data);
500 char const *d = data;
501 char *ub = ac_alloc(l +1);
503 urlp->url_had_user = TRU1;
504 data = cp + 1;
506 /* And also have a password? */
507 if ((cp = memchr(d, ':', l)) != NULL) {
508 size_t i = PTR2SIZE(cp - d);
510 l -= i + 1;
511 memcpy(ub, cp + 1, l);
512 ub[l] = '\0';
513 urlp->url_pass.l = strlen(urlp->url_pass.s = urlxdec(ub));
515 if (strcmp(ub, urlxenc(urlp->url_pass.s, FAL0))) {
516 fprintf(stderr,
517 _("String is not properly URL percent encoded: `%s'\n"), ub);
518 goto jleave;
520 l = i;
523 memcpy(ub, d, l);
524 ub[l] = '\0';
525 urlp->url_user.l = strlen(urlp->url_user.s = urlxdec(ub));
526 urlp->url_user_enc.l = strlen(
527 urlp->url_user_enc.s = urlxenc(urlp->url_user.s, FAL0));
529 if (urlp->url_user_enc.l != l || memcmp(urlp->url_user_enc.s, ub, l)) {
530 fprintf(stderr,
531 _("String is not properly URL percent encoded: `%s'\n"), ub);
532 goto jleave;
535 ac_free(ub);
538 /* Servername and port -- and possible path suffix */
539 if ((cp = strchr(data, ':')) != NULL) { /* TODO URL parse, IPv6 support */
540 char *eptr;
541 long l;
543 urlp->url_port = x = savestr(x = cp + 1);
544 if ((x = strchr(x, '/')) != NULL)
545 *x = '\0';
546 l = strtol(urlp->url_port, &eptr, 10);
547 if (*eptr != '\0' || l <= 0 || UICMP(32, l, >=, 0xFFFFu)) {
548 fprintf(stderr, _("URL with invalid port number: `%s'\n"),
549 urlp->url_input);
550 goto jleave;
552 urlp->url_portno = (ui16_t)l;
553 } else {
554 if ((x = strchr(data, '/')) != NULL)
555 data = savestrbuf(data, PTR2SIZE(x - data));
556 cp = UNCONST(data + strlen(data));
559 /* A (non-empty) path may only occur with IMAP */
560 if (x != NULL && x[1] != '\0') {
561 if (cproto != CPROTO_IMAP) {
562 fprintf(stderr, _("URL protocol doesn't support paths: `%s'\n"),
563 urlp->url_input);
564 goto jleave;
566 urlp->url_path.l = strlen(++x);
567 urlp->url_path.s = savestrbuf(x, urlp->url_path.l);
570 urlp->url_host.s = savestrbuf(data, urlp->url_host.l = PTR2SIZE(cp - data));
571 { size_t i;
572 for (cp = urlp->url_host.s, i = urlp->url_host.l; i != 0; ++cp, --i)
573 *cp = lowerconv(*cp);
576 /* .url_h_p: HOST:PORT */
577 { size_t i;
578 struct str *s = &urlp->url_h_p;
580 s->s = salloc(urlp->url_host.l + 1 + sizeof("65536")-1 +1);
581 memcpy(s->s, urlp->url_host.s, i = urlp->url_host.l);
582 if (urlp->url_port != NULL) {
583 size_t j = strlen(urlp->url_port);
584 s->s[i++] = ':';
585 memcpy(s->s + i, urlp->url_port, j);
586 i += j;
588 s->s[i] = '\0';
589 s->l = i;
592 /* User, II
593 * If there was no user in the URL, do we have *user-HOST* or *user*? */
594 if (!urlp->url_had_user) {
595 if ((urlp->url_user.s = xok_vlook(user, urlp, OXM_H_P)) == NULL) {
596 /* No *user-HOST*, check wether .netrc lookup is desired */
597 #ifdef HAVE_NETRC
598 if (!ok_blook(v15_compat) || !ok_blook(netrc_lookup) ||
599 _nrc_lookup(urlp, FAL0) != NRC_RESOK)
600 #endif
601 if ((urlp->url_user.s = ok_vlook(user)) == NULL)
602 urlp->url_user.s = UNCONST(myname);
605 urlp->url_user.l = strlen(urlp->url_user.s);
606 urlp->url_user.s = savestrbuf(urlp->url_user.s, urlp->url_user.l);
607 urlp->url_user_enc.l = strlen(
608 urlp->url_user_enc.s = urlxenc(urlp->url_user.s, FAL0));
611 /* And then there are a lot of prebuild string combinations TODO do lazy */
613 /* .url_u_h: .url_user@.url_host
614 * For SMTP we apply ridiculously complicated *v15-compat* plus
615 * *smtp-hostname* / *hostname* dependent rules */
616 { struct str h, *s;
617 size_t i;
619 if (cproto == CPROTO_SMTP && ok_blook(v15_compat) &&
620 (cp = ok_vlook(smtp_hostname)) != NULL) {
621 if (*cp == '\0')
622 cp = nodename(1);
623 h.s = savestrbuf(cp, h.l = strlen(cp));
624 } else
625 h = urlp->url_host;
627 s = &urlp->url_u_h;
628 i = urlp->url_user.l;
630 s->s = salloc(i + 1 + h.l +1);
631 if (i > 0) {
632 memcpy(s->s, urlp->url_user.s, i);
633 s->s[i++] = '@';
635 memcpy(s->s + i, h.s, h.l +1);
636 i += h.l;
637 s->l = i;
640 /* .url_u_h_p: .url_user@.url_host[:.url_port] */
641 { struct str *s = &urlp->url_u_h_p;
642 size_t i = urlp->url_user.l;
644 s->s = salloc(i + 1 + urlp->url_h_p.l +1);
645 if (i > 0) {
646 memcpy(s->s, urlp->url_user.s, i);
647 s->s[i++] = '@';
649 memcpy(s->s + i, urlp->url_h_p.s, urlp->url_h_p.l +1);
650 i += urlp->url_h_p.l;
651 s->l = i;
654 /* .url_eu_h_p: .url_user_enc@.url_host[:.url_port] */
655 { struct str *s = &urlp->url_eu_h_p;
656 size_t i = urlp->url_user_enc.l;
658 s->s = salloc(i + 1 + urlp->url_h_p.l +1);
659 if (i > 0) {
660 memcpy(s->s, urlp->url_user_enc.s, i);
661 s->s[i++] = '@';
663 memcpy(s->s + i, urlp->url_h_p.s, urlp->url_h_p.l +1);
664 i += urlp->url_h_p.l;
665 s->l = i;
668 /* .url_p_u_h_p: .url_proto://.url_u_h_p */
669 { size_t i;
670 char *ud = salloc((i = urlp->url_proto_xlen + urlp->url_u_h_p.l) +1);
672 urlp->url_proto[urlp->url_proto_len] = ':';
673 memcpy(sstpcpy(ud, urlp->url_proto), urlp->url_u_h_p.s,
674 urlp->url_u_h_p.l +1);
675 urlp->url_proto[urlp->url_proto_len] = '\0';
677 urlp->url_p_u_h_p = ud;
680 /* .url_p_eu_h_p, .url_p_eu_h_p_p: .url_proto://.url_eu_h_p[/.url_path] */
681 { size_t i;
682 char *ud = salloc((i = urlp->url_proto_xlen + urlp->url_eu_h_p.l) +
683 1 + urlp->url_path.l +1);
685 urlp->url_proto[urlp->url_proto_len] = ':';
686 memcpy(sstpcpy(ud, urlp->url_proto), urlp->url_eu_h_p.s,
687 urlp->url_eu_h_p.l +1);
688 urlp->url_proto[urlp->url_proto_len] = '\0';
690 if (urlp->url_path.l == 0)
691 urlp->url_p_eu_h_p = urlp->url_p_eu_h_p_p = ud;
692 else {
693 urlp->url_p_eu_h_p = savestrbuf(ud, i);
694 urlp->url_p_eu_h_p_p = ud;
695 ud += i;
696 *ud++ = '/';
697 memcpy(ud, urlp->url_path.s, urlp->url_path.l +1);
701 rv = TRU1;
702 #endif /* __ANYPROTO */
703 jleave:
704 NYD_LEAVE;
705 return rv;
706 #undef __ANYPROTO
707 #undef __ALLPROTO
710 FL bool_t
711 ccred_lookup_old(struct ccred *ccp, enum cproto cproto, char const *addr)
713 char const *pname, *pxstr, *authdef;
714 size_t pxlen, addrlen, i;
715 char *vbuf, *s;
716 ui8_t authmask;
717 enum {NONE=0, WANT_PASS=1<<0, REQ_PASS=1<<1, WANT_USER=1<<2, REQ_USER=1<<3}
718 ware = NONE;
719 bool_t addr_is_nuser = FAL0; /* XXX v15.0 legacy! v15_compat */
720 NYD_ENTER;
722 memset(ccp, 0, sizeof *ccp);
724 switch (cproto) {
725 default:
726 case CPROTO_SMTP:
727 pname = "SMTP";
728 pxstr = "smtp-auth";
729 pxlen = sizeof("smtp-auth") -1;
730 authmask = AUTHTYPE_NONE | AUTHTYPE_PLAIN | AUTHTYPE_LOGIN |
731 AUTHTYPE_CRAM_MD5 | AUTHTYPE_GSSAPI;
732 authdef = "none";
733 addr_is_nuser = TRU1;
734 break;
735 case CPROTO_POP3:
736 pname = "POP3";
737 pxstr = "pop3-auth";
738 pxlen = sizeof("pop3-auth") -1;
739 authmask = AUTHTYPE_PLAIN;
740 authdef = "plain";
741 break;
742 case CPROTO_IMAP:
743 pname = "IMAP";
744 pxstr = "imap-auth";
745 pxlen = sizeof("imap-auth") -1;
746 authmask = AUTHTYPE_LOGIN | AUTHTYPE_CRAM_MD5 | AUTHTYPE_GSSAPI;
747 authdef = "login";
748 break;
751 ccp->cc_cproto = cproto;
752 addrlen = strlen(addr);
753 vbuf = ac_alloc(pxlen + addrlen + sizeof("-password-")-1 +1);
754 memcpy(vbuf, pxstr, pxlen);
756 /* Authentication type */
757 vbuf[pxlen] = '-';
758 memcpy(vbuf + pxlen + 1, addr, addrlen +1);
759 if ((s = vok_vlook(vbuf)) == NULL) {
760 vbuf[pxlen] = '\0';
761 if ((s = vok_vlook(vbuf)) == NULL)
762 s = UNCONST(authdef);
765 if (!asccasecmp(s, "none")) {
766 ccp->cc_auth = "NONE";
767 ccp->cc_authtype = AUTHTYPE_NONE;
768 /*ware = NONE;*/
769 } else if (!asccasecmp(s, "plain")) {
770 ccp->cc_auth = "PLAIN";
771 ccp->cc_authtype = AUTHTYPE_PLAIN;
772 ware = REQ_PASS | REQ_USER;
773 } else if (!asccasecmp(s, "login")) {
774 ccp->cc_auth = "LOGIN";
775 ccp->cc_authtype = AUTHTYPE_LOGIN;
776 ware = REQ_PASS | REQ_USER;
777 } else if (!asccasecmp(s, "cram-md5")) {
778 ccp->cc_auth = "CRAM-MD5";
779 ccp->cc_authtype = AUTHTYPE_CRAM_MD5;
780 ware = REQ_PASS | REQ_USER;
781 } else if (!asccasecmp(s, "gssapi")) {
782 ccp->cc_auth = "GSS-API";
783 ccp->cc_authtype = AUTHTYPE_GSSAPI;
784 ware = REQ_USER;
785 } /* no else */
787 /* Verify method */
788 if (!(ccp->cc_authtype & authmask)) {
789 fprintf(stderr, _("Unsupported %s authentication method: %s\n"),
790 pname, s);
791 ccp = NULL;
792 goto jleave;
794 #ifndef HAVE_MD5
795 if (ccp->cc_authtype == AUTHTYPE_CRAM_MD5) {
796 fprintf(stderr, _("No CRAM-MD5 support compiled in.\n"));
797 ccp = NULL;
798 goto jleave;
800 #endif
801 #ifndef HAVE_GSSAPI
802 if (ccp->cc_authtype == AUTHTYPE_GSSAPI) {
803 fprintf(stderr, _("No GSS-API support compiled in.\n"));
804 ccp = NULL;
805 goto jleave;
807 #endif
809 /* User name */
810 if (!(ware & (WANT_USER | REQ_USER)))
811 goto jpass;
813 if (!addr_is_nuser) {
814 if ((s = UNCONST(last_at_before_slash(addr))) != NULL) {
815 ccp->cc_user.s = urlxdec(savestrbuf(addr, PTR2SIZE(s - addr)));
816 ccp->cc_user.l = strlen(ccp->cc_user.s);
817 } else if (ware & REQ_USER)
818 goto jgetuser;
819 goto jpass;
822 memcpy(vbuf + pxlen, "-user-", i = sizeof("-user-") -1);
823 i += pxlen;
824 memcpy(vbuf + i, addr, addrlen +1);
825 if ((s = vok_vlook(vbuf)) == NULL) {
826 vbuf[--i] = '\0';
827 if ((s = vok_vlook(vbuf)) == NULL && (ware & REQ_USER)) {
828 if ((s = getuser(NULL)) == NULL) {
829 jgetuser: /* TODO v15.0: today we simply bail, but we should call getuser().
830 * TODO even better: introduce `PROTO-user' and `PROTO-pass' and
831 * TODO check that first, then! change control flow, grow `vbuf' */
832 fprintf(stderr, _("A user is necessary for %s authentication.\n"),
833 pname);
834 ccp = NULL;
835 goto jleave;
839 ccp->cc_user.l = strlen(ccp->cc_user.s = savestr(s));
841 /* Password */
842 jpass:
843 if (!(ware & (WANT_PASS | REQ_PASS)))
844 goto jleave;
846 if (!addr_is_nuser) {
847 memcpy(vbuf, "password-", i = sizeof("password-") -1);
848 } else {
849 memcpy(vbuf + pxlen, "-password-", i = sizeof("-password-") -1);
850 i += pxlen;
852 memcpy(vbuf + i, addr, addrlen +1);
853 if ((s = vok_vlook(vbuf)) == NULL) {
854 vbuf[--i] = '\0';
855 if ((!addr_is_nuser || (s = vok_vlook(vbuf)) == NULL) &&
856 (ware & REQ_PASS)) {
857 if ((s = getpassword(NULL)) == NULL) {
858 fprintf(stderr,
859 _("A password is necessary for %s authentication.\n"), pname);
860 ccp = NULL;
861 goto jleave;
865 if (s != NULL)
866 ccp->cc_pass.l = strlen(ccp->cc_pass.s = savestr(s));
868 jleave:
869 ac_free(vbuf);
870 if (ccp != NULL && (options & OPT_D_VV))
871 fprintf(stderr, _("Credentials: host `%s', user `%s', pass `%s'\n"),
872 addr, (ccp->cc_user.s != NULL ? ccp->cc_user.s : ""),
873 (ccp->cc_pass.s != NULL ? ccp->cc_pass.s : ""));
874 NYD_LEAVE;
875 return (ccp != NULL);
878 FL bool_t
879 ccred_lookup(struct ccred *ccp, struct url *urlp)
881 char const *pstr, *authdef;
882 size_t plen, i;
883 char *vbuf, *s;
884 ui8_t authmask;
885 enum {NONE=0, WANT_PASS=1<<0, REQ_PASS=1<<1, WANT_USER=1<<2, REQ_USER=1<<3}
886 ware = NONE;
887 NYD_ENTER;
889 memset(ccp, 0, sizeof *ccp);
890 ccp->cc_user = urlp->url_user;
892 switch ((ccp->cc_cproto = urlp->url_cproto)) {
893 default:
894 case CPROTO_SMTP:
895 pstr = "smtp";
896 plen = sizeof("smtp") -1;
897 authmask = AUTHTYPE_NONE | AUTHTYPE_PLAIN | AUTHTYPE_LOGIN |
898 AUTHTYPE_CRAM_MD5 | AUTHTYPE_GSSAPI;
899 authdef = "none";
900 break;
901 case CPROTO_POP3:
902 pstr = "pop3";
903 plen = sizeof("pop3") -1;
904 authmask = AUTHTYPE_PLAIN;
905 authdef = "plain";
906 break;
907 case CPROTO_IMAP:
908 pstr = "imap";
909 plen = sizeof("imap") -1;
910 authmask = AUTHTYPE_LOGIN | AUTHTYPE_CRAM_MD5 | AUTHTYPE_GSSAPI;
911 authdef = "login";
912 break;
915 /* Note: "password-" is longer than "-auth-", .url_u_h_p and .url_h_p */
916 vbuf = ac_alloc(plen + sizeof("password-")-1 + urlp->url_u_h_p.l +1);
917 memcpy(vbuf, pstr, plen);
919 /* Authentication type */
920 memcpy(vbuf + plen, "-auth-", i = sizeof("-auth-") -1);
921 i += plen;
922 /* -USER@HOST, -HOST, '' */
923 memcpy(vbuf + i, urlp->url_u_h_p.s, urlp->url_u_h_p.l +1);
924 if ((s = vok_vlook(vbuf)) == NULL) {
925 memcpy(vbuf + i, urlp->url_h_p.s, urlp->url_h_p.l +1);
926 if ((s = vok_vlook(vbuf)) == NULL) {
927 vbuf[plen + sizeof("-auth") -1] = '\0';
928 if ((s = vok_vlook(vbuf)) == NULL)
929 s = UNCONST(authdef);
933 if (!asccasecmp(s, "none")) {
934 ccp->cc_auth = "NONE";
935 ccp->cc_authtype = AUTHTYPE_NONE;
936 /*ware = NONE;*/
937 } else if (!asccasecmp(s, "plain")) {
938 ccp->cc_auth = "PLAIN";
939 ccp->cc_authtype = AUTHTYPE_PLAIN;
940 ware = REQ_PASS | REQ_USER;
941 } else if (!asccasecmp(s, "login")) {
942 ccp->cc_auth = "LOGIN";
943 ccp->cc_authtype = AUTHTYPE_LOGIN;
944 ware = REQ_PASS | REQ_USER;
945 } else if (!asccasecmp(s, "cram-md5")) {
946 ccp->cc_auth = "CRAM-MD5";
947 ccp->cc_authtype = AUTHTYPE_CRAM_MD5;
948 ware = REQ_PASS | REQ_USER;
949 } else if (!asccasecmp(s, "gssapi")) {
950 ccp->cc_auth = "GSS-API";
951 ccp->cc_authtype = AUTHTYPE_GSSAPI;
952 ware = REQ_USER;
953 } /* no else */
955 /* Verify method */
956 if (!(ccp->cc_authtype & authmask)) {
957 fprintf(stderr, _("Unsupported %s authentication method: %s\n"), pstr, s);
958 ccp = NULL;
959 goto jleave;
961 #ifndef HAVE_MD5
962 if (ccp->cc_authtype == AUTHTYPE_CRAM_MD5) {
963 fprintf(stderr, _("No CRAM-MD5 support compiled in.\n"));
964 ccp = NULL;
965 goto jleave;
967 #endif
968 #ifndef HAVE_GSSAPI
969 if (ccp->cc_authtype == AUTHTYPE_GSSAPI) {
970 fprintf(stderr, _("No GSS-API support compiled in.\n"));
971 ccp = NULL;
972 goto jleave;
974 #endif
976 /* Password */
977 if ((ccp->cc_pass = urlp->url_pass).s != NULL)
978 goto jleave;
980 memcpy(vbuf, "password-", i = sizeof("password-") -1);
981 /* -USER@HOST, -HOST, '' */
982 memcpy(vbuf + i, urlp->url_u_h_p.s, urlp->url_u_h_p.l +1);
983 if ((s = vok_vlook(vbuf)) == NULL) {
984 memcpy(vbuf + i, urlp->url_h_p.s, urlp->url_h_p.l +1);
985 if ((s = vok_vlook(vbuf)) == NULL) {
986 /* But before we go and deal with the absolute fallbacks, check wether
987 * we may look into .netrc */
988 #ifdef HAVE_NETRC
989 if (ok_blook(netrc_lookup))
990 switch (_nrc_lookup(urlp, TRU1)) {
991 default:
992 break;
993 case NRC_RESOK:
994 ccp->cc_pass = urlp->url_pass;
995 goto jleave;
996 case NRC_RESERROR:
997 fprintf(stderr, _(".netrc authentification failed "
998 "(missing password or user mismatch)\n"));
999 ccp = NULL;
1000 goto jleave;
1002 #endif
1003 vbuf[--i] = '\0';
1004 if ((s = vok_vlook(vbuf)) == NULL && (ware & REQ_PASS) &&
1005 (s = getpassword(NULL)) == NULL) {
1006 fprintf(stderr,
1007 _("A password is necessary for %s authentication.\n"), pstr);
1008 ccp = NULL;
1009 goto jleave;
1013 if (s != NULL)
1014 ccp->cc_pass.l = strlen(ccp->cc_pass.s = savestr(s));
1016 jleave:
1017 ac_free(vbuf);
1018 if (ccp != NULL && (options & OPT_D_VV))
1019 fprintf(stderr, _("Credentials: host `%s', user `%s', pass `%s'\n"),
1020 urlp->url_h_p.s, (ccp->cc_user.s != NULL ? ccp->cc_user.s : ""),
1021 (ccp->cc_pass.s != NULL ? ccp->cc_pass.s : ""));
1022 NYD_LEAVE;
1023 return (ccp != NULL);
1026 #ifdef HAVE_NETRC
1027 FL int
1028 c_netrc(void *v)
1030 char **argv = v;
1031 struct nrc_node *nrc;
1032 NYD_ENTER;
1034 if (*argv == NULL)
1035 goto jlist;
1036 if (argv[1] != NULL)
1037 goto jerr;
1038 if (!asccasecmp(*argv, "show"))
1039 goto jlist;
1040 if (!asccasecmp(*argv, "clear"))
1041 goto jclear;
1042 jerr:
1043 fprintf(stderr, "Synopsis: netrc: %s\n",
1044 _("Either <show> (default) or <clear> the .netrc cache"));
1045 v = NULL;
1046 jleave:
1047 NYD_LEAVE;
1048 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
1050 jlist: {
1051 FILE *fp;
1052 size_t l;
1054 if (_nrc_list == NULL)
1055 _nrc_init();
1056 if (_nrc_list == NRC_NODE_ERR) {
1057 fprintf(stderr, _("Interpolate what file?\n"));
1058 v = NULL;
1059 goto jleave;
1062 if ((fp = Ftmp(NULL, "netrc", OF_RDWR | OF_UNLINK | OF_REGISTER, 0600)
1063 ) == NULL) {
1064 perror("tmpfile");
1065 v = NULL;
1066 goto jleave;
1069 for (l = 0, nrc = _nrc_list; nrc != NULL; ++l, nrc = nrc->nrc_next) {
1070 fprintf(fp, _("Host %s: "), nrc->nrc_dat);
1071 if (nrc->nrc_ulen > 0)
1072 fprintf(fp, _("user %s, "), nrc->nrc_dat + nrc->nrc_mlen +1);
1073 else
1074 fputs(_("no user, "), fp);
1075 if (nrc->nrc_plen > 0)
1076 fprintf(fp, _("password %s.\n"),
1077 nrc->nrc_dat + nrc->nrc_mlen +1 + nrc->nrc_ulen +1);
1078 else
1079 fputs(_("no password.\n"), fp);
1082 page_or_print(fp, l);
1083 Fclose(fp);
1085 goto jleave;
1087 jclear:
1088 if (_nrc_list == NRC_NODE_ERR)
1089 _nrc_list = NULL;
1090 while ((nrc = _nrc_list) != NULL) {
1091 _nrc_list = nrc->nrc_next;
1092 free(nrc);
1094 goto jleave;
1096 #endif /* HAVE_NETRC */
1098 #ifdef HAVE_MD5
1099 FL char *
1100 md5tohex(char hex[MD5TOHEX_SIZE], void const *vp)
1102 char const *cp = vp;
1103 size_t i, j;
1104 NYD_ENTER;
1106 for (i = 0; i < MD5TOHEX_SIZE / 2; ++i) {
1107 j = i << 1;
1108 # define __hex(n) ((n) > 9 ? (n) - 10 + 'a' : (n) + '0')
1109 hex[j] = __hex((cp[i] & 0xF0) >> 4);
1110 hex[++j] = __hex(cp[i] & 0x0F);
1111 # undef __hex
1113 NYD_LEAVE;
1114 return hex;
1117 FL char *
1118 cram_md5_string(struct str const *user, struct str const *pass,
1119 char const *b64)
1121 struct str in, out;
1122 char digest[16], *cp;
1123 NYD_ENTER;
1125 out.s = NULL;
1126 in.s = UNCONST(b64);
1127 in.l = strlen(in.s);
1128 b64_decode(&out, &in, NULL);
1129 assert(out.s != NULL);
1131 hmac_md5((uc_it*)out.s, out.l, (uc_it*)pass->s, pass->l, digest);
1132 free(out.s);
1133 cp = md5tohex(salloc(MD5TOHEX_SIZE +1), digest);
1135 in.l = user->l + MD5TOHEX_SIZE +1;
1136 in.s = ac_alloc(user->l + 1 + MD5TOHEX_SIZE +1);
1137 memcpy(in.s, user->s, user->l);
1138 in.s[user->l] = ' ';
1139 memcpy(in.s + user->l + 1, cp, MD5TOHEX_SIZE);
1140 b64_encode(&out, &in, B64_SALLOC | B64_CRLF);
1141 ac_free(in.s);
1142 NYD_LEAVE;
1143 return out.s;
1146 FL void
1147 hmac_md5(unsigned char *text, int text_len, unsigned char *key, int key_len,
1148 void *digest)
1151 * This code is taken from
1153 * Network Working Group H. Krawczyk
1154 * Request for Comments: 2104 IBM
1155 * Category: Informational M. Bellare
1156 * UCSD
1157 * R. Canetti
1158 * IBM
1159 * February 1997
1162 * HMAC: Keyed-Hashing for Message Authentication
1164 md5_ctx context;
1165 unsigned char k_ipad[65]; /* inner padding - key XORd with ipad */
1166 unsigned char k_opad[65]; /* outer padding - key XORd with opad */
1167 unsigned char tk[16];
1168 int i;
1169 NYD_ENTER;
1171 /* if key is longer than 64 bytes reset it to key=MD5(key) */
1172 if (key_len > 64) {
1173 md5_ctx tctx;
1175 md5_init(&tctx);
1176 md5_update(&tctx, key, key_len);
1177 md5_final(tk, &tctx);
1179 key = tk;
1180 key_len = 16;
1183 /* the HMAC_MD5 transform looks like:
1185 * MD5(K XOR opad, MD5(K XOR ipad, text))
1187 * where K is an n byte key
1188 * ipad is the byte 0x36 repeated 64 times
1189 * opad is the byte 0x5c repeated 64 times
1190 * and text is the data being protected */
1192 /* start out by storing key in pads */
1193 memset(k_ipad, 0, sizeof k_ipad);
1194 memset(k_opad, 0, sizeof k_opad);
1195 memcpy(k_ipad, key, key_len);
1196 memcpy(k_opad, key, key_len);
1198 /* XOR key with ipad and opad values */
1199 for (i=0; i<64; i++) {
1200 k_ipad[i] ^= 0x36;
1201 k_opad[i] ^= 0x5c;
1204 /* perform inner MD5 */
1205 md5_init(&context); /* init context for 1st pass */
1206 md5_update(&context, k_ipad, 64); /* start with inner pad */
1207 md5_update(&context, text, text_len); /* then text of datagram */
1208 md5_final(digest, &context); /* finish up 1st pass */
1210 /* perform outer MD5 */
1211 md5_init(&context); /* init context for 2nd pass */
1212 md5_update(&context, k_opad, 64); /* start with outer pad */
1213 md5_update(&context, digest, 16); /* then results of 1st hash */
1214 md5_final(digest, &context); /* finish up 2nd pass */
1215 NYD_LEAVE;
1217 #endif /* HAVE_MD5 */
1219 /* vim:set fenc=utf-8:s-it-mode */