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.
24 #define su_FILE cred_netrc
26 #define mx_SOURCE_CRED_NETRC
28 #ifndef mx_HAVE_AMALGAMATION
35 #include <su/cs-dict.h>
40 #include "mx/file-streams.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 |\
53 #define a_NETRC_TRESHOLD_SHIFT 3
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
);
83 static struct n_strlist
*a_netrc_dump(char const *cmdname
, char const *key
,
87 static char *a_netrc_bsd_quote(char const *v
);
96 a_SEEN_DEFAULT
= 1u<<3,
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
;
104 enum a_netrc_token t
;
106 struct a_netrc_entry
*nrcep
;
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
);
120 if((netrc_load
= ok_vlook(netrc_pipe
)) != NIL
){
122 if((fi
= mx_fs_pipe_open(netrc_load
, "r", ok_vlook(SHELL
), NIL
,
123 mx_CHILD_FD_NULL
)) == NIL
)
126 if((netrc_load
= fexpand(ok_vlook(NETRC
), (FEXP_NOPROTO
|
127 FEXP_LOCAL_FILE
| FEXP_NSHELL
))) == NIL
)
130 if((fi
= mx_fs_open(netrc_load
, "r")) == NIL
)
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");
142 switch((t
= a_netrc__token(fi
, buffer
, &nl_last
))){
145 default: /* Does not happen (but on error?), keep CC happy */
146 case a_NETRC_DEFAULT
:
148 /* We ignore the default entry (require an exact host match), and we
149 * also ignore anything after such an entry (faulty syntax) */
152 case a_NETRC_MACHINE
:
154 /* Normalize HOST to lowercase */
156 if(!(f
& a_SEEN_DEFAULT
) &&
157 (t
= a_netrc__token(fi
, machine
, &nl_last
)) != a_NETRC_INPUT
)
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
){
167 if((t
= a_netrc__token(fi
, login
, &nl_last
)) != a_NETRC_INPUT
)
171 case a_NETRC_PASSWORD
:
172 if((t
= a_netrc__token(fi
, password
, &nl_last
)) != a_NETRC_INPUT
)
176 case a_NETRC_ACCOUNT
:
177 if((t
= a_netrc__token(fi
, buffer
, &nl_last
)) != a_NETRC_INPUT
)
181 if((t
= a_netrc__token(fi
, buffer
, &nl_last
)) != a_NETRC_INPUT
){
183 emsg
= N_("parse error");
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 */
201 emsg
= N_("parse error (unknown token)");
206 if(!(f
& a_SEEN_DEFAULT
) && (f
& (a_LOGIN
| a_PASSWORD
))){
207 union {void *v
; struct a_netrc_entry
*nrce
;} p
;
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
) +
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
);
222 su_mem_copy(&nrcep
->nrce_dat
[nrcep
->nrce_password_idx
= llen
],
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
;
234 if((err
= su_cs_dict_insert(a_netrc_dp
, machine
, nrcep
)
236 emsg
= su_err_doc(err
);
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
)
249 if(t
== a_NETRC_DEFAULT
)
251 ASSERT(t
== a_NETRC_NONE
);
254 emsg
= N_("parse error (unknown top level token)");
265 mx_fs_pipe_close(fi
, TRU1
);
277 emsg
= su_err_doc(su_err_no());
280 n_err(_(".netrc: %s: %s\n"), n_shexp_quote_cp(netrc_load
, FAL0
), V_(emsg
));
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
{
290 } const *ttap
, tta
[] = {
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"}
302 enum a_netrc_token rv
;
311 if(feof(fi
) || ferror(fi
))
314 for(seen_nl
= *nl_last
; (c
= getc(fi
)) != EOF
&& su_cs_is_white(c
);)
315 seen_nl
|= (c
== '\n');
319 /* fetchmail and derived parsers support comments */
320 if((*nl_last
= seen_nl
) && c
== '#'){
321 while((c
= getc(fi
)) != EOF
&& c
!= '\n')
328 /* Is it a quoted token? At least IBM syntax also supports ' quotes */
330 if(c
== '"' || 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 */
338 if((c
= getc(fi
)) == EOF
)
341 if(PCMP(cp
, ==, &buffer
[a_NETRC_TOKEN_MAXLEN
-1])){
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
)
353 if(PCMP(cp
, ==, &buffer
[a_NETRC_TOKEN_MAXLEN
-1])){
358 *nl_last
= (c
== '\n');
364 for(ttap
= &tta
[0]; ttap
< &tta
[NELEM(tta
)]; ++ttap
)
365 if(!su_cs_cmp(buffer
, ttap
->tt_name
)){
371 if(c
== EOF
&& !feof(fi
))
379 a_netrc_gut(boole gut_dp
){
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
));
391 nrcep
= nrcep
->nrce_next
;
397 su_cs_dict_gut(a_netrc_dp
);
400 su_cs_dict_clear(a_netrc_dp
);
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
;
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');
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? */
445 slp
->sl_len
= s
->s_len
;
446 n_string_drop_ownership(s
);
453 a_netrc_bsd_quote(char const *v
){
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
){
466 if(!quotes
&& c
!= '"' && c
!= '\\')
472 rv
= n_autorec_alloc(i
+1);
477 for(cp
= v
; (c
= *cp
) != '\0'; rv
[i
++] = c
, ++cp
)
478 if(c
== '"' || c
== '\\')
499 if(su_cs_starts_with_case("lookup", *argv
)){
506 if(su_cs_starts_with_case("show", *argv
))
508 if(su_cs_starts_with_case("clear", *argv
))
512 if(su_cs_starts_with_case("load", *argv
))
515 mx_cmd_print_synopsis(mx_cmd_firstfit("netrc"), NIL
);
519 return (vp
== NIL
? n_EXIT_ERR
: n_EXIT_OK
);
522 struct mx_netrc_entry nrce
;
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
));
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
);
539 fprintf(n_stdout
, _("netrc: lookup: no entry for: %s\n"), url
.url_u_h
.s
);
552 if(a_netrc_dp
== NIL
)
556 struct n_strlist
*slp
;
559 if(!(mx_xy_dump_dict("netrc", a_netrc_dp
, &slp
, NIL
,
561 mx_page_or_print_strlist("netrc", slp
, TRU1
)))
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
;
575 s
= n_string_creat_auto(&s_b
);
579 if(a_netrc_dp
== NIL
)
581 if(su_cs_dict_count(a_netrc_dp
) == 0)
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
;;){
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
)
603 /* Without user we will only return a result if unambiguous */
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
)){
613 }else if(rv
== TRUM1
&& p
.nrce
->nrce_next
== NIL
)
617 }else if(p
.nrce
->nrce_next
!= 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
];
631 /* su_string_gut(s); */
636 #include "su/code-ou.h"
637 #endif /* mx_HAVE_NETRC */