THANKS: Coverity.com (overdue)
[s-mailx.git] / src / mx / cred-netrc.c
blobc985bb39b4c055298892df8e7fcd60ee66e650af
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Implementation of cred-netrc.h.
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 *@ TODO With an on_loop_tick_event, trigger cache update once per loop max.
6 *@ TODO I.e., unless *netrc-pipe* was set, auto check for updates.
8 * Copyright (c) 2014 - 2020 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
9 * SPDX-License-Identifier: ISC
11 * Permission to use, copy, modify, and/or distribute this software for any
12 * purpose with or without fee is hereby granted, provided that the above
13 * copyright notice and this permission notice appear in all copies.
15 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
16 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
17 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
18 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
20 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
21 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23 #undef su_FILE
24 #define su_FILE cred_netrc
25 #define mx_SOURCE
26 #define mx_SOURCE_CRED_NETRC
28 #ifndef mx_HAVE_AMALGAMATION
29 # include "mx/nail.h"
30 #endif
32 su_EMPTY_FILE()
33 #ifdef mx_HAVE_NETRC
34 #include <su/cs.h>
35 #include <su/cs-dict.h>
36 #include <su/mem.h>
38 #include "mx/cmd.h"
39 #include "mx/child.h"
40 #include "mx/file-streams.h"
41 #include "mx/url.h"
43 #include "mx/cred-netrc.h"
44 #include "su/code-in.h"
46 /* NetBSD usr.bin/ftp/ruserpass.c uses 100 bytes for that, we need four
47 * concurrently (dummy, host, user, pass), so make it a KB */
48 #define a_NETRC_TOKEN_MAXLEN (1024u / 4)
50 /* Dictionary stores a_netrc_entry, not owned */
51 #define a_NETRC_FLAGS (su_CS_DICT_CASE | su_CS_DICT_HEAD_RESORT |\
52 su_CS_DICT_ERR_PASS)
53 #define a_NETRC_TRESHOLD_SHIFT 3
55 enum a_netrc_token{
56 a_NETRC_ERROR = -1,
57 a_NETRC_NONE = 0,
58 a_NETRC_DEFAULT,
59 a_NETRC_LOGIN,
60 a_NETRC_PASSWORD,
61 a_NETRC_ACCOUNT,
62 a_NETRC_MACDEF,
63 a_NETRC_MACHINE,
64 a_NETRC_INPUT
67 struct a_netrc_entry{
68 struct a_netrc_entry *nrce_next;
69 u32 nrce_password_idx; /* Offset in .nrce_dat, U32_MAX if not set */
70 boole nrce_has_login; /* Have login at .nrce_dat[0] */
71 char nrce_dat[VFIELD_SIZE(3)];
74 static struct su_cs_dict *a_netrc_dp, a_netrc__d; /* XXX atexit _gut (DVL()) */
76 /* We stop parsing and _gut(FAL0) on hard errors like NOMEM, OVERFLOW and IO */
77 static void a_netrc_create(void);
78 static enum a_netrc_token a_netrc__token(FILE *fi,
79 char buffer[a_NETRC_TOKEN_MAXLEN], boole *nl_last);
80 static void a_netrc_gut(boole gut_dp);
82 /* */
83 static struct n_strlist *a_netrc_dump(char const *cmdname, char const *key,
84 void const *dat);
86 /* */
87 static char *a_netrc_bsd_quote(char const *v);
89 static void
90 a_netrc_create(void){
91 enum{
92 a_NONE,
93 a_IS_PIPE = 1u<<0,
94 a_LOGIN = 1u<<1,
95 a_PASSWORD = 1u<<2,
96 a_SEEN_DEFAULT = 1u<<3,
97 a_ERROR = 1u<<4
100 char buffer[a_NETRC_TOKEN_MAXLEN], machine[a_NETRC_TOKEN_MAXLEN],
101 login[a_NETRC_TOKEN_MAXLEN], password[a_NETRC_TOKEN_MAXLEN], *netrc_load;
102 struct stat sb;
103 boole nl_last;
104 enum a_netrc_token t;
105 FILE *fi;
106 struct a_netrc_entry *nrcep;
107 char const *emsg;
108 u32 f;
109 NYD_IN;
111 a_netrc_dp = su_cs_dict_set_treshold_shift(
112 su_cs_dict_create(&a_netrc__d, a_NETRC_FLAGS, NIL),
113 a_NETRC_TRESHOLD_SHIFT);
115 f = a_NONE;
116 UNINIT(emsg, NIL);
117 nrcep = NIL;
118 fi = NIL;
120 if((netrc_load = ok_vlook(netrc_pipe)) != NIL){
121 f |= a_IS_PIPE;
122 if((fi = mx_fs_pipe_open(netrc_load, "r", ok_vlook(SHELL), NIL,
123 mx_CHILD_FD_NULL)) == NIL)
124 goto jerrdoc;
125 }else{
126 if((netrc_load = fexpand(ok_vlook(NETRC), (FEXP_NOPROTO |
127 FEXP_LOCAL_FILE | FEXP_NSHELL))) == NIL)
128 goto jleave;
130 if((fi = mx_fs_open(netrc_load, "r")) == NIL)
131 goto jerrdoc;
133 /* Be simple and apply rigid (permission) check(s) */
134 if(fstat(fileno(fi), &sb) == -1 || !S_ISREG(sb.st_mode) ||
135 (sb.st_mode & (S_IRWXG | S_IRWXO))){
136 emsg = N_("Not a regular file, or accessible by non-user\n");
137 goto jerr;
141 nl_last = TRU1;
142 switch((t = a_netrc__token(fi, buffer, &nl_last))){
143 case a_NETRC_NONE:
144 break;
145 default: /* Does not happen (but on error?), keep CC happy */
146 case a_NETRC_DEFAULT:
147 jdef:
148 /* We ignore the default entry (require an exact host match), and we
149 * also ignore anything after such an entry (faulty syntax) */
150 f |= a_SEEN_DEFAULT;
151 /* FALLTHRU */
152 case a_NETRC_MACHINE:
153 jm_h:
154 /* Normalize HOST to lowercase */
155 *machine = '\0';
156 if(!(f & a_SEEN_DEFAULT) &&
157 (t = a_netrc__token(fi, machine, &nl_last)) != a_NETRC_INPUT)
158 goto jenotinput;
160 *login = *password = '\0';
161 f &= ~(a_LOGIN | a_PASSWORD);
163 while((t = a_netrc__token(fi, buffer, &nl_last)) != a_NETRC_NONE &&
164 t != a_NETRC_MACHINE && t != a_NETRC_DEFAULT){
165 switch(t){
166 case a_NETRC_LOGIN:
167 if((t = a_netrc__token(fi, login, &nl_last)) != a_NETRC_INPUT)
168 goto jenotinput;
169 f |= a_LOGIN;
170 break;
171 case a_NETRC_PASSWORD:
172 if((t = a_netrc__token(fi, password, &nl_last)) != a_NETRC_INPUT)
173 goto jenotinput;
174 f |= a_PASSWORD;
175 break;
176 case a_NETRC_ACCOUNT:
177 if((t = a_netrc__token(fi, buffer, &nl_last)) != a_NETRC_INPUT)
178 goto jenotinput;
179 break;
180 case a_NETRC_MACDEF:
181 if((t = a_netrc__token(fi, buffer, &nl_last)) != a_NETRC_INPUT){
182 jenotinput:
183 emsg = N_("parse error");
184 goto jerr;
185 }else{
186 int i, c;
188 for(i = 0; (c = getc(fi)) != EOF;)
189 if(c == '\n'){ /* xxx */
190 /* Do not care about comments here, since we parse until
191 * we have seen two successive newline characters */
192 if(i)
193 break;
194 i = 1;
195 }else
196 i = 0;
198 break;
199 default:
200 case a_NETRC_ERROR:
201 emsg = N_("parse error (unknown token)");
202 goto jerr;
206 if(!(f & a_SEEN_DEFAULT) && (f & (a_LOGIN | a_PASSWORD))){
207 union {void *v; struct a_netrc_entry *nrce;} p;
208 u32 llen, plen;
210 llen = (f & a_LOGIN) ? su_cs_len(login) : 0;
211 plen = (f & a_PASSWORD) ? su_cs_len(password) : 0;
212 nrcep = su_ALLOC(VSTRUCT_SIZEOF(struct a_netrc_entry, nrce_dat) +
213 llen +1 + plen +1);
214 if(nrcep == NIL)
215 goto jerrdoc;
216 nrcep->nrce_next = NIL;
218 if((nrcep->nrce_has_login = ((f & a_LOGIN) != 0)))
219 su_mem_copy(&nrcep->nrce_dat[0], login, ++llen);
221 if(f & a_PASSWORD)
222 su_mem_copy(&nrcep->nrce_dat[nrcep->nrce_password_idx = llen],
223 password, ++plen);
224 else
225 nrcep->nrce_password_idx = U32_MAX;
227 if((p.v = su_cs_dict_lookup(a_netrc_dp, machine)) != NIL){
228 while(p.nrce->nrce_next != NIL)
229 p.nrce = p.nrce->nrce_next;
230 p.nrce->nrce_next = nrcep;
231 }else{
232 s32 err;
234 if((err = su_cs_dict_insert(a_netrc_dp, machine, nrcep)
235 ) != su_ERR_NONE){
236 emsg = su_err_doc(err);
237 goto jerr;
241 nrcep = NIL;
244 if(t != a_NETRC_NONE && (f & a_SEEN_DEFAULT) && (n_poption & n_PO_D_V))
245 n_err(_(".netrc: \"default\" must be last entry, ignoring: %s\n"),
246 n_shexp_quote_cp(netrc_load, FAL0));
247 if(t == a_NETRC_MACHINE)
248 goto jm_h;
249 if(t == a_NETRC_DEFAULT)
250 goto jdef;
251 ASSERT(t == a_NETRC_NONE);
252 break;
253 case a_NETRC_ERROR:
254 emsg = N_("parse error (unknown top level token)");
255 goto jerr;
258 nrcep = NIL;
259 jleave:
260 if(nrcep != NIL)
261 su_FREE(nrcep);
263 if(fi != NIL){
264 if(f & a_IS_PIPE)
265 mx_fs_pipe_close(fi, TRU1);
266 else
267 mx_fs_close(fi);
270 if(f & a_ERROR)
271 a_netrc_gut(FAL0);
273 NYD_OU;
274 return;
276 jerrdoc:
277 emsg = su_err_doc(su_err_no());
278 jerr:
279 UNUSED(emsg);
280 n_err(_(".netrc: %s: %s\n"), n_shexp_quote_cp(netrc_load, FAL0), V_(emsg));
281 f |= a_ERROR;
282 goto jleave;
285 static enum a_netrc_token
286 a_netrc__token(FILE *fi, char buffer[a_NETRC_TOKEN_MAXLEN], boole *nl_last){
287 static struct token_type{
288 s8 tt_token;
289 char tt_name[15];
290 } const *ttap, tta[] = {
291 {a_NETRC_NONE, ""},
292 {a_NETRC_DEFAULT, "default"},
293 {a_NETRC_LOGIN, "login"},
294 {a_NETRC_PASSWORD, "password\0"},
295 {a_NETRC_PASSWORD, "passwd"},
296 {a_NETRC_ACCOUNT, "account"},
297 {a_NETRC_MACDEF, "macdef"},
298 {a_NETRC_MACHINE, "machine"}
300 int c;
301 char *cp;
302 enum a_netrc_token rv;
303 NYD2_IN;
305 rv = a_NETRC_NONE;
307 for(;;){
308 boole seen_nl;
310 c = EOF;
311 if(feof(fi) || ferror(fi))
312 goto jleave;
314 for(seen_nl = *nl_last; (c = getc(fi)) != EOF && su_cs_is_white(c);)
315 seen_nl |= (c == '\n');
317 if(c == EOF)
318 goto jleave;
319 /* fetchmail and derived parsers support comments */
320 if((*nl_last = seen_nl) && c == '#'){
321 while((c = getc(fi)) != EOF && c != '\n')
323 continue;
325 break;
328 /* Is it a quoted token? At least IBM syntax also supports ' quotes */
329 cp = buffer;
330 if(c == '"' || c == '\''){
331 int quotec;
333 quotec = c;
334 /* Not requiring the closing QM is (Net)BSD syntax */
335 while((c = getc(fi)) != EOF && c != quotec){
336 /* Reverse solidus escaping the next character is (Net)BSD syntax */
337 if(c == '\\')
338 if((c = getc(fi)) == EOF)
339 break;
340 *cp++ = c;
341 if(PCMP(cp, ==, &buffer[a_NETRC_TOKEN_MAXLEN -1])){
342 rv = a_NETRC_ERROR;
343 goto jleave;
346 }else{
347 *cp++ = c;
348 while((c = getc(fi)) != EOF && !su_cs_is_white(c)){
349 /* Reverse solidus escaping the next character is (Net)BSD syntax */
350 if(c == '\\' && (c = getc(fi)) == EOF)
351 break;
352 *cp++ = c;
353 if(PCMP(cp, ==, &buffer[a_NETRC_TOKEN_MAXLEN -1])){
354 rv = a_NETRC_ERROR;
355 goto jleave;
358 *nl_last = (c == '\n');
360 *cp = '\0';
362 /* */
363 rv = a_NETRC_INPUT;
364 for(ttap = &tta[0]; ttap < &tta[NELEM(tta)]; ++ttap)
365 if(!su_cs_cmp(buffer, ttap->tt_name)){
366 rv = ttap->tt_token;
367 break;
370 jleave:
371 if(c == EOF && !feof(fi))
372 rv = a_NETRC_ERROR;
374 NYD2_OU;
375 return rv;
378 static void
379 a_netrc_gut(boole gut_dp){
380 NYD2_IN;
382 if(a_netrc_dp != NIL){
383 struct su_cs_dict_view csdv;
385 su_CS_DICT_FOREACH(a_netrc_dp, &csdv){
386 struct a_netrc_entry *nrcep, *tmp;
388 for(nrcep = S(struct a_netrc_entry*,su_cs_dict_view_data(&csdv));
389 nrcep != NIL;){
390 tmp = nrcep;
391 nrcep = nrcep->nrce_next;
392 su_FREE(tmp);
396 if(gut_dp){
397 su_cs_dict_gut(a_netrc_dp);
398 a_netrc_dp = NIL;
399 }else
400 su_cs_dict_clear(a_netrc_dp);
403 NYD2_OU;
406 static struct n_strlist *
407 a_netrc_dump(char const *cmdname, char const *key, void const *dat){
408 struct n_string s_b, *s;
409 struct n_strlist *slp;
410 struct a_netrc_entry const *nrcep;
411 NYD2_IN;
412 UNUSED(cmdname);
414 s = n_string_book(n_string_creat_auto(&s_b), 127);
415 s = n_string_resize(s, n_STRLIST_PLAIN_SIZE());
417 for(nrcep = S(struct a_netrc_entry const*,dat); nrcep != NIL;
418 nrcep = nrcep->nrce_next){
419 if(S(void const*,nrcep) != dat)
420 s = n_string_push_c(s, '\n');
422 s = n_string_push_buf(s, "machine ", sizeof("machine ") -1);
423 s = n_string_push_cp(s, a_netrc_bsd_quote(key));
425 if(nrcep->nrce_has_login){
426 s = n_string_push_buf(s, " login ", sizeof(" login ") -1);
427 s = n_string_push_cp(s, a_netrc_bsd_quote(&nrcep->nrce_dat[0]));
430 if(nrcep->nrce_password_idx != U32_MAX){
431 s = n_string_push_buf(s, " password ", sizeof(" password ") -1);
432 s = n_string_push_cp(s,
433 a_netrc_bsd_quote(&nrcep->nrce_dat[nrcep->nrce_password_idx]));
437 s = n_string_push_c(s, '\n');
439 n_string_cp(s);
441 slp = R(struct n_strlist*,S(void*,s->s_dat));
442 /* xxx Should we assert alignment constraint of slp is satisfied?
443 * xxx Should be, heap memory with alignment < sizeof(void*) bitter? */
444 slp->sl_next = NIL;
445 slp->sl_len = s->s_len;
446 n_string_drop_ownership(s);
448 NYD2_OU;
449 return slp;
452 static char *
453 a_netrc_bsd_quote(char const *v){
454 char const *cp;
455 uz i;
456 char c, *rv;
457 boole quotes;
458 NYD2_IN;
460 quotes = FAL0;
462 for(i = 0, cp = v; (c = *cp) != '\0'; ++i, ++cp)
463 /* \n etc. weird, granted */
464 if(su_cs_find_c("\"\\ \n\r\t\v", c) != NIL){
465 ++i;
466 if(!quotes && c != '"' && c != '\\')
467 quotes = TRU1;
469 if(quotes)
470 i += 2;
472 rv = n_autorec_alloc(i +1);
474 i = 0;
475 if(quotes)
476 rv[i++] = '"';
477 for(cp = v; (c = *cp) != '\0'; rv[i++] = c, ++cp)
478 if(c == '"' || c == '\\')
479 rv[i++] = '\\';
480 if(quotes)
481 rv[i++] = '"';
482 rv[i] = '\0';
484 NYD2_OU;
485 return rv;
489 c_netrc(void *vp){
490 boole load_only;
491 char **argv;
492 NYD_IN;
494 argv = vp;
496 load_only = FAL0;
497 if(*argv == NIL)
498 goto jlist;
499 if(su_cs_starts_with_case("lookup", *argv)){
500 if(argv[1] == NIL)
501 goto jerr;
502 goto jlookup;
504 if(argv[1] != NIL)
505 goto jerr;
506 if(su_cs_starts_with_case("show", *argv))
507 goto jlist;
508 if(su_cs_starts_with_case("clear", *argv))
509 goto jclear;
511 load_only = TRU1;
512 if(su_cs_starts_with_case("load", *argv))
513 goto jclear;
514 jerr:
515 mx_cmd_print_synopsis(mx_cmd_firstfit("netrc"), NIL);
516 vp = NIL;
517 jleave:
518 NYD_OU;
519 return (vp == NIL ? n_EXIT_ERR : n_EXIT_OK);
521 jlookup:{
522 struct mx_netrc_entry nrce;
523 struct mx_url url;
525 if(!mx_url_parse(&url, CPROTO_NONE, argv[1])){
526 n_err(_("netrc: lookup: invalid URL: %s\n"),
527 n_shexp_quote_cp(argv[1], FAL0));
528 vp = NIL;
529 }else if(mx_netrc_lookup(&nrce, &url)){
530 fprintf(n_stdout, "netrc: lookup: %s: machine %s",
531 url.url_u_h.s, a_netrc_bsd_quote(nrce.nrce_machine));
532 if(nrce.nrce_login != NIL)
533 fprintf(n_stdout, " login %s", a_netrc_bsd_quote(nrce.nrce_login));
534 if(nrce.nrce_password != NIL)
535 fprintf(n_stdout, " password %s",
536 a_netrc_bsd_quote(nrce.nrce_password));
537 putc('\n', n_stdout);
538 }else{
539 fprintf(n_stdout, _("netrc: lookup: no entry for: %s\n"), url.url_u_h.s);
540 vp = NIL;
542 goto jleave;
545 jclear:
546 a_netrc_gut(TRU1);
547 if(load_only)
548 goto jlist;
549 goto jleave;
551 jlist:
552 if(a_netrc_dp == NIL)
553 a_netrc_create();
555 if(!load_only){
556 struct n_strlist *slp;
558 slp = NIL;
559 if(!(mx_xy_dump_dict("netrc", a_netrc_dp, &slp, NIL,
560 &a_netrc_dump) &&
561 mx_page_or_print_strlist("netrc", slp, TRU1)))
562 vp = NIL;
564 goto jleave;
567 boole
568 mx_netrc_lookup(struct mx_netrc_entry *result, struct mx_url const *urlp){
569 struct n_string s_b, *s;
570 union {void *v; uz i; struct a_netrc_entry *nrce;} p;
571 char const *host;
572 boole rv;
573 NYD_IN;
575 s = n_string_creat_auto(&s_b);
577 rv = FAL0;
579 if(a_netrc_dp == NIL)
580 a_netrc_create();
581 if(su_cs_dict_count(a_netrc_dp) == 0)
582 goto jleave;
584 s = n_string_book(s, urlp->url_host.l + 2);
586 if((p.v = su_cs_dict_lookup(a_netrc_dp, host = urlp->url_host.s)) == NIL){
587 /* Cannot be an exact match, but maybe .netrc provides a matching
588 * "*." wildcard entry, which we recognize as an extension, meaning
589 * "skip a single subdomain, then match the rest" */
590 for(host = urlp->url_host.s, p.i = urlp->url_host.l;;){
591 if(--p.i <= 1)
592 goto jleave;
593 if(*host++ == '.')
594 break;
597 s = n_string_push_buf(s, "*.", sizeof("*.") -1);
598 s = n_string_push_buf(s, host, p.i);
599 if((p.v = su_cs_dict_lookup(a_netrc_dp, host = n_string_cp(s))) == NIL)
600 goto jleave;
603 /* Without user we will only return a result if unambiguous */
604 rv = TRUM1;
605 if(urlp->url_user.s != NIL){
606 /* (No do{}while() because of gcc 3.4.3 union bug (Solaris 5.10)) */
607 for(; p.nrce != NIL; p.nrce = p.nrce->nrce_next){
608 if(p.nrce->nrce_has_login){
609 if(!su_cs_cmp(&p.nrce->nrce_dat[0], urlp->url_user.s)){
610 rv = TRUM1;
611 break;
613 }else if(rv == TRUM1 && p.nrce->nrce_next == NIL)
614 break;
615 rv = TRU1;
617 }else if(p.nrce->nrce_next != NIL)
618 p.nrce = NIL;
620 if(p.nrce != NIL){
621 su_mem_set(result, 0, sizeof(*result));
622 result->nrce_machine = host;
623 if(p.nrce->nrce_has_login)
624 result->nrce_login = &p.nrce->nrce_dat[0];
625 if(p.nrce->nrce_password_idx != U32_MAX)
626 result->nrce_password = &p.nrce->nrce_dat[p.nrce->nrce_password_idx];
627 }else
628 rv = FAL0;
630 jleave:
631 /* su_string_gut(s); */
632 NYD_OU;
633 return rv;
636 #include "su/code-ou.h"
637 #endif /* mx_HAVE_NETRC */
638 /* s-it-mode */