1 /*
2  * rc.c -- config file parser + autologin lookup
3  *
4  * Yet Another FTP Client
5  * Copyright (C) 1998-2001, Martin Hedenfalk <mhe@stacken.kth.se>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version. See COPYING for more details.
11  */
12 
13 #include "syshdr.h"
14 #include "ftp.h"
15 #include "strq.h"
16 #include "gvars.h"
17 #include "args.h"
18 #include "alias.h"
19 #include "commands.h"
20 #include "transfer.h"
21 #include "utils.h"
22 
23 static void errp(char *str, ...) YAFC_PRINTF(1, 2);
24 
25 static int nerr = 0;
26 static char *current_rcfile = NULL;
27 static char *ungetstr = 0;
28 
errp(char * str,...)29 static void errp(char *str, ...)
30 {
31 	va_list ap;
32 
33 	if(nerr == 0)
34 		fprintf(stderr, _("Error(s) while parsing '%s':\n"), current_rcfile);
35 
36 	va_start(ap, str);
37 	vfprintf(stderr, str, ap);
38 	va_end(ap);
39 	nerr++;
40 }
41 
_nextstr(FILE * fp)42 static char *_nextstr(FILE *fp)
43 {
44 	static char tmp[257];
45 	char *e;
46 	int c;
47 
48 	if(ungetstr) {
49 		e = ungetstr;
50 		ungetstr = 0;
51 		return e;
52 	}
53 
54 	if(fscanf(fp, "%256s", tmp) == EOF) {
55 		if(ferror(fp))
56 			perror(current_rcfile);
57 		return 0;
58 	}
59 	if(tmp[0] == '#') { /* skip comments */
60 		while((c = getc(fp)) != EOF) {
61 			if(c == '\n')
62 				break;
63 		}
64 		return _nextstr(fp);
65 	}
66 	return tmp;
67 }
68 
nextstr(FILE * fp)69 static char *nextstr(FILE *fp)
70 {
71 	static char tmp[257];
72 	char *e;
73 	int i;
74 
75 	e = _nextstr(fp);
76 	if(!e)
77 		return 0;
78 	if(*e=='\"' || *e=='\'') {
79 		strlcpy(tmp, e+1, sizeof(tmp));
80 		i = strlen(tmp);
81 		if(i >= 1 && (tmp[i-1] == '\'' || tmp[i-1] == '\"')) {
82 			tmp[i-1] = 0;
83 			return tmp;
84 		}
85 		while(true) {
86 			int c;
87 
88 			tmp[i] = 0;
89 
90 			c = fgetc(fp);
91 			if(c == EOF) {
92 				errp(_("unmatched quote\n"));
93 				break;
94 			}
95 			if((i == 0 || tmp[i-1] != '\\') && (c == '\"' || c == '\''))
96 				break;
97 			tmp[i++] = c;
98 			if(i == 256) {
99 				errp(_("string too long or unmatched quote, truncated\n"));
100 				break;
101 			}
102 		}
103 		return tmp;
104 	}
105 	return e;
106 }
107 
nextbool(FILE * fp)108 static bool nextbool(FILE *fp)
109 {
110 	char *e;
111 	int b;
112 
113 	if((e=nextstr(fp)) == NULL) {
114 		errp(_("Unexpected end of file encountered\n"));
115 		return false;
116 	}
117 
118 	if((b=str2bool(e)) != -1)
119 		return (bool)b;
120 	else {
121 		errp(_("Expected boolean value, but got '%s'\n"), e);
122 		ungetstr = e;
123 		return false;
124 	}
125 }
126 
127 #define NEXTSTR       if((e=nextstr(fp)) == 0) break
128 
129 #define TRIG_MACHINE 1
130 #define TRIG_LOCAL 2
131 #define TRIG_DEFAULT 3
132 
parse_host(int trig,FILE * fp)133 static void parse_host(int trig, FILE *fp)
134 {
135 	static bool has_warned_about_passwd = false;
136 	char *e;
137 	url_t *up;
138 
139 	up = url_create();
140 
141 	if(trig == TRIG_MACHINE) {
142 		if((e=nextstr(fp)) == 0)
143 			return;
144 
145 		if(e[0] == 0) {
146 			errp(_("'machine' directive needs a hostname\n"));
147 			url_destroy(up);
148 			return;
149 		}
150 		url_parse(up, e);
151 	}
152 
153 	while(!feof(fp)) {
154 		NEXTSTR;
155 
156 		if(strcasecmp(e, "login") == 0) {
157 			NEXTSTR;
158 			url_setusername(up, e);
159 		} else if(strcasecmp(e, "alias") == 0) {
160 			NEXTSTR;
161 			if(up->hostname[0] == '.')
162 				printf(_("'alias' directive not useful with domains\n"));
163 			else {
164 				unquote(e);
165 				url_setalias(up, e);
166 			}
167 		} else if(strcasecmp(e, "password") == 0) {
168 			NEXTSTR;
169 			url_setpassword(up, e);
170 			if(!has_warned_about_passwd) {
171 				struct stat sb;
172 				has_warned_about_passwd = true;
173 				if(fstat(fileno(fp), &sb)==0 && (sb.st_mode & 077)!=0) {
174 					printf(_("WARNING! Config file contains passwords "
175 							 "but is readable by others (mode %03o)\n"),
176 						   sb.st_mode&0777);
177 					sleep(3);
178 				}
179 			}
180 
181 		} else if(strcasecmp(e, "anonymous") == 0) {
182 			url_setusername(up, e);
183 			url_setpassword(up, gvAnonPasswd);    /* could be NULL */
184 		} else if(strcasecmp(e, "account") == 0) {
185 			NEXTSTR;
186 			/* FIXME: account skipped in parse_host() */
187 		} else if(strcasecmp(e, "cwd") == 0) {
188 			NEXTSTR;
189 			url_setdirectory(up, e);
190 		} else if(strcasecmp(e, "port") == 0) {
191 			NEXTSTR;
192 			url_setport(up, atoi(e));
193 		} else if(strcasecmp(e, "mech") == 0) {
194 			NEXTSTR;
195 			url_setmech(up, e);
196 		} else if(strcasecmp(e, "prot") == 0) {
197 			NEXTSTR;
198 			url_setprotlevel(up, e);
199 		} else if(strcasecmp(e, "passive") == 0) {
200 			url_setpassive(up, nextbool(fp));
201 		} else if(strcasecmp(e, "sftp") == 0) {
202 			NEXTSTR;
203 			url_setsftp(up, e);
204 		} else if(strcasecmp(e, "noupdate") == 0) {
205 			up->noupdate = true;
206 		} else if(strcasecmp(e, "macdef") == 0) {
207 			while(e) { /* FIXME: macdef: this is not really true */
208 				NEXTSTR;
209 			}
210 			break;
211 		} else {
212 			ungetstr = e;
213 			clearerr(fp);
214 			break;
215 		}
216 	}
217 
218 	if(trig == TRIG_MACHINE) {
219 		listitem *li;
220 		li = list_search(gvBookmarks, (listsearchfunc)urlcmp, up);
221 		if(li)
222 			/* bookmark already exists, overwrite it (delete and create new) */
223 			list_delitem(gvBookmarks, li);
224 		list_additem(gvBookmarks, up);
225 	} else if(trig == TRIG_LOCAL) {
226 		if(gvLocalUrl)
227 			url_destroy(gvLocalUrl);
228 		gvLocalUrl = up;
229 	} else { /* trig == TRIG_DEFAULT */
230 		if(gvDefaultUrl)
231 			url_destroy(gvDefaultUrl);
232 		gvDefaultUrl = up;
233 	}
234 }
235 
parse_rc(const char * file,bool warn)236 int parse_rc(const char *file, bool warn)
237 {
238 	FILE *fp;
239 	char *e;
240 
241 	e = tilde_expand_home(file, gvLocalHomeDir);
242 	fp = fopen(e, "r");
243 	if(!fp) {
244 		if(warn)
245 			perror(e);
246 		free(e);
247 		return -1;
248 	}
249 	current_rcfile = e;
250 
251 	nerr = 0;
252 
253 	while(!feof(fp)) {
254 		if(nerr>20) {
255 			errp(_("As a computer, I find your faith in technology amusing..."
256 				   "\nToo many errors\n"));
257 			fclose(fp);
258 			free(current_rcfile);
259 			return -1;
260 		}
261 
262 		NEXTSTR;
263 
264 		if(strcasecmp(e, "autologin") == 0)
265 			gvAutologin = nextbool(fp);
266 		else if(strcasecmp(e, "autoreconnect") == 0)
267 			gvAutoReconnect = nextbool(fp);
268 		else if(strcasecmp(e, "verbose") == 0)
269 			gvVerbose = nextbool(fp);
270 		else if(strcasecmp(e, "debug") == 0)
271 			gvDebug = nextbool(fp);
272 		else if(strcasecmp(e, "trace") == 0)
273 			gvTrace = nextbool(fp);
274 		else if(strcasecmp(e, "inhibit_startup_syst") == 0
275 				|| strcasecmp(e, "no_startup_syst") == 0)
276 			gvStartupSyst = !nextbool(fp);
277 		else if(strcasecmp(e, "prompt_on_disconnect") == 0)
278 			/* ignored for backward compat. */ nextbool(fp);
279 		else if(strcasecmp(e, "remote_completion") == 0)
280 			gvRemoteCompletion = nextbool(fp);
281 		else if(strcasecmp(e, "read_netrc") == 0)
282 			gvReadNetrc = nextbool(fp);
283 		else if(strcasecmp(e, "quit_on_eof") == 0)
284 			gvQuitOnEOF = nextbool(fp);
285 		else if(strcasecmp(e, "use_passive_mode") == 0)
286 			gvPasvmode = nextbool(fp);
287 		else if(strcasecmp(e, "use_history") == 0)
288 			gvUseHistory = nextbool(fp);
289 		else if(strcasecmp(e, "beep_after_long_command") == 0)
290 			gvBeepLongCommand = nextbool(fp);
291 		else if(strcasecmp(e, "auto_bookmark_save_passwd") == 0)
292 			gvAutoBookmarkSavePasswd = nextbool(fp);
293 		else if(strcasecmp(e, "auto_bookmark_silent") == 0)
294 			gvAutoBookmarkSilent = nextbool(fp);
295 		else if(strcasecmp(e, "tilde") == 0)
296 			gvTilde = nextbool(fp);
297 		else if(strcasecmp(e, "reverse_dns") == 0)
298 			gvReverseDNS = nextbool(fp);
299 		else if(strcasecmp(e, "waiting_dots") == 0)
300 			gvWaitingDots = nextbool(fp);
301 		else if(strcasecmp(e, "use_env_string") == 0)
302 			gvUseEnvString = nextbool(fp);
303 		else if(strcasecmp(e, "auto_bookmark") == 0) {
304 			NEXTSTR;
305 
306 			if(strcasecmp(e, "ask") == 0)
307 				gvAutoBookmark = 2;
308 			else {
309 				gvAutoBookmark = str2bool(e);
310 				if(gvAutoBookmark == -1) {
311 					errp(_("Expected boolean value or 'ask', but got '%s'\n"),
312 						 e);
313 					gvAutoBookmark = 0;
314 				}
315 			}
316 		} else if(strcasecmp(e, "auto_bookmark_update") == 0) {
317 			NEXTSTR;
318 
319 			if(strcasecmp(e, "ask") == 0)
320 				gvAutoBookmarkUpdate = 2;
321 			else {
322 				gvAutoBookmarkUpdate = str2bool(e);
323 				if(gvAutoBookmarkUpdate == -1) {
324 					errp(_("Expected boolean value or 'ask', but got '%s'\n"),
325 						 e);
326 					gvAutoBookmarkUpdate = 0;
327 				}
328 			}
329 		} else if(strcasecmp(e, "load_taglist") == 0) {
330 			NEXTSTR;
331 
332 			if(strcasecmp(e, "ask") == 0)
333 				gvLoadTaglist = 2;
334 			else {
335 				gvLoadTaglist = str2bool(e);
336 				if(gvLoadTaglist == -1) {
337 					errp(_("Expected boolean value or 'ask', but got '%s'\n"),
338 						 e);
339 					gvLoadTaglist = 2;
340 				}
341 			}
342 		} else if(strcasecmp(e, "default_type") == 0) {
343 			NEXTSTR;
344 			if(strcasecmp(e, "binary") == 0 || strcasecmp(e, "I") == 0)
345 				gvDefaultType = tmBinary;
346 			else if(strcasecmp(e, "ascii") == 0 || strcasecmp(e, "A") == 0)
347 				gvDefaultType = tmAscii;
348 			else
349 				errp(_("Unknown default_type parameter '%s'..."
350 					   " (use 'ascii' or 'binary')\n"), e);
351 		} else if(strcasecmp(e, "default_mechanism") == 0) {
352 			NEXTSTR;
353 			list_free(gvDefaultMechanism);
354 			gvDefaultMechanism = list_new((listfunc)free);
355 			listify_string(e, gvDefaultMechanism);
356 		} else if(strcasecmp(e, "anon_password") == 0) {
357 			NEXTSTR;
358 			free(gvAnonPasswd);
359 			gvAnonPasswd = xstrdup(e);
360 		} else if(strcasecmp(e, "long_command_time") == 0) {
361 			NEXTSTR;
362 			gvLongCommandTime = atoi(e);
363 			if(gvLongCommandTime < 0) {
364 				errp(_("Invalid value for long_command_time: %d\n"),
365 					 gvLongCommandTime);
366 				gvLongCommandTime = 30;
367 			}
368 		} else if(strcasecmp(e, "connect_wait_time") == 0) {
369 			NEXTSTR;
370 			gvConnectWaitTime = atoi(e);
371 			if(gvConnectWaitTime < 0) {
372 				errp(_("Invalid value for connect_wait_time: %d\n"),
373 					 gvConnectWaitTime);
374 				gvConnectWaitTime = 30;
375 			}
376 		} else if(strcasecmp(e, "cache_timeout") == 0) {
377 			NEXTSTR;
378 			gvCacheTimeout = atoi(e);
379 			if(gvCacheTimeout < 0) {
380 				errp(_("Invalid value for cache_timeout: %d\n"),
381 					 gvCacheTimeout);
382 				gvCacheTimeout = 0;
383 			}
384 		} else if(strcasecmp(e, "connect_attempts") == 0) {
385 			NEXTSTR;
386 			gvConnectAttempts = (unsigned)atoi(e);
387 			if(gvConnectAttempts == 0)
388 				gvConnectAttempts = 1;
389 		} else if(strcasecmp(e, "command_timeout") == 0) {
390 			NEXTSTR;
391 			gvCommandTimeout = (unsigned)atoi(e);
392 		} else if(strcasecmp(e, "connection_timeout") == 0) {
393 			NEXTSTR;
394 			gvConnectionTimeout = (unsigned)atoi(e);
395 		} else if(strcasecmp(e, "include") == 0) {
396 			char *rcfile;
397 			NEXTSTR;
398 			rcfile = tilde_expand_home(e, gvLocalHomeDir);
399 			if(strcmp(rcfile, current_rcfile) == 0) {
400 				free(rcfile);
401 				errp(_("Skipping circular include statement: %s\n"), e);
402 			} else {
403 				free(current_rcfile);
404 				parse_rc(e, true);
405 				current_rcfile = rcfile;
406 			}
407 		} else if(strcasecmp(e, "prompt1") == 0) {
408 			NEXTSTR;
409 			free(gvPrompt1);
410 			gvPrompt1 = xstrdup(e);
411 		} else if(strcasecmp(e, "prompt2") == 0) {
412 			NEXTSTR;
413 			free(gvPrompt2);
414 			gvPrompt2 = xstrdup(e);
415 		} else if(strcasecmp(e, "prompt3") == 0) {
416 			NEXTSTR;
417 			free(gvPrompt3);
418 			gvPrompt3 = xstrdup(e);
419 		} else if(strcasecmp(e, "ssh_options") == 0) {
420 			NEXTSTR;
421 			free(gvSSHOptions);
422 			gvSSHOptions = xstrdup(e);
423     } else if (strcasecmp(e, "ssh_try_scp") == 0) {
424       gvSSHTrySCP = nextbool(fp);
425 		} else if(strcasecmp(e, "xterm_title_terms") == 0) {
426 			NEXTSTR;
427 			free(gvXtermTitleTerms);
428 			gvXtermTitleTerms = xstrdup(e);
429 		} else if(strcasecmp(e, "xterm_title1") == 0) {
430 			NEXTSTR;
431 			free(gvXtermTitle1);
432 			gvXtermTitle1 = xstrdup(e);
433 		} else if(strcasecmp(e, "xterm_title2") == 0) {
434 			NEXTSTR;
435 			free(gvXtermTitle2);
436 			gvXtermTitle2 = xstrdup(e);
437 		} else if(strcasecmp(e, "xterm_title3") == 0) {
438 			NEXTSTR;
439 			free(gvXtermTitle3);
440 			gvXtermTitle3 = xstrdup(e);
441 		} else if(strcasecmp(e, "transfer_begin_string") == 0) {
442 			NEXTSTR;
443 			free(gvTransferBeginString);
444 			gvTransferBeginString = xstrdup(e);
445 			unquote_escapes(gvTransferBeginString);
446 		} else if(strcasecmp(e, "transfer_string") == 0) {
447 			NEXTSTR;
448 			free(gvTransferString);
449 			gvTransferString = xstrdup(e);
450 			unquote_escapes(gvTransferString);
451 		} else if(strcasecmp(e, "transfer_xterm_string") == 0) {
452 			NEXTSTR;
453 			free(gvTransferXtermString);
454 			gvTransferXtermString = xstrdup(e);
455 			unquote_escapes(gvTransferXtermString);
456 		} else if(strcasecmp(e, "transfer_end_string") == 0) {
457 			NEXTSTR;
458 			free(gvTransferEndString);
459 			gvTransferEndString = xstrdup(e);
460 			unquote_escapes(gvTransferEndString);
461 		} else if(strcasecmp(e, "nohup_mailaddress") == 0) {
462 			NEXTSTR;
463 			free(gvNohupMailAddress);
464 			gvNohupMailAddress = xstrdup(e);
465 		} else if(strcasecmp(e, "sendmail_path") == 0) {
466 			NEXTSTR;
467 			free(gvSendmailPath);
468 			gvSendmailPath = xstrdup(e);
469 		} else if(strcasecmp(e, "history_max") == 0) {
470 			NEXTSTR;
471 			gvHistoryMax = atoi(e);
472 			if(gvHistoryMax <= 0) {
473 				errp(_("Invalid value for history_max: %d\n"), gvHistoryMax);
474 				gvHistoryMax = 256;
475 			}
476 		} else if(strcasecmp(e, "ascii_transfer_mask") == 0) {
477 			NEXTSTR;
478 			listify_string(e, gvAsciiMasks);
479 		} else if(strcasecmp(e, "transfer_first_mask") == 0) {
480 			NEXTSTR;
481 			listify_string(e, gvTransferFirstMasks);
482 		} else if(strcasecmp(e, "ignore_mask") == 0) {
483 			NEXTSTR;
484 			listify_string(e, gvIgnoreMasks);
485 		} else if(strcasecmp(e, "stats_threshold") == 0) {
486 			NEXTSTR;
487 			gvStatsThreshold = atoi(e);
488 			if(gvStatsThreshold < 0) {
489 				errp(_("Invalid value for stats_threshold: %i\n"),
490 					 gvStatsThreshold);
491 				gvStatsThreshold = 20;
492 			}
493 		} else if(strcasecmp(e, "startup_local_directory") == 0) {
494 			NEXTSTR;
495 			e = tilde_expand_home(e, gvLocalHomeDir);
496 			if(chdir(e) == -1)
497 				perror(e);
498 			else
499 				cmd_lpwd(0, 0);
500 			free(e);
501 		} else if(strcasecmp(e, "alias") == 0) {
502 			args_t *args;
503 			char *name;
504 
505 			NEXTSTR;
506 			name = xstrdup(e);
507 
508 			NEXTSTR;
509 
510 			args = args_create();
511 			args_push_back(args, e);
512 			alias_define(name, args);
513 			free(name);
514 		}
515 		else if(strcasecmp(e, "proxy_type") == 0) {
516 			NEXTSTR;
517 
518 			gvProxyType = atoi(e);
519 			if(gvProxyType < 0 || gvProxyType > 7) {
520 				errp(_("Invalid value for proxy_type: %d\n"), gvProxyType);
521 				gvProxyType = 0;
522 			}
523 		} else if(strcasecmp(e, "proxy_host") == 0) {
524 			NEXTSTR;
525 			url_destroy(gvProxyUrl);
526 			gvProxyUrl = url_init(e);
527 		} else if(strcasecmp(e, "proxy_exclude") == 0) {
528 			NEXTSTR;
529 			listify_string(e, gvProxyExclude);
530 		}
531 		else if(strcasecmp(e, "machine") == 0)
532 			parse_host(TRIG_MACHINE, fp);
533 		else if(strcasecmp(e, "default") == 0)
534 			parse_host(TRIG_DEFAULT, fp);
535 		else if(strcasecmp(e, "local") == 0)
536 			parse_host(TRIG_LOCAL, fp);
537 		else
538 			errp(_("Config parse error: '%s'\n"), e);
539 	}
540 	fclose(fp);
541 	free(current_rcfile);
542 	return 0;
543 }
544 
get_autologin_url_short(const char * host)545 static url_t *get_autologin_url_short(const char *host)
546 {
547 	url_t *x, *found = 0;
548 	listitem *li;
549 
550 	li = gvBookmarks->first;
551 	while(li) {
552 		x = (url_t *)li->data;
553 		li = li->next;
554 		/* compare only strlen(host) chars, allowing aliases
555 		 * to be shortened, as long as they're not ambiguous
556 		 */
557 		if(x->alias && strncasecmp(x->alias, host, strlen(host)) == 0) {
558 			if(strlen(x->alias) == strlen(host))
559 				/* exact match */
560 				return x;
561 			if(found)
562 				found = (url_t *)-1;
563 			else
564 				found = x;
565 		}
566 	}
567 	if(found)
568 		return found;
569 
570 	/* now do the same for hostnames (skip aliases) */
571 	li = gvBookmarks->first;
572 	while(li) {
573 		x = (url_t *)li->data;
574 		li = li->next;
575 		/* compare only strlen(host) chars, allowing hostnames
576 		 * to be shortened, as long as they're not ambiguous
577 		 */
578 		if(!x->alias && strncasecmp(x->hostname, host, strlen(host)) == 0) {
579 			if(strlen(x->hostname) == strlen(host))
580 				/* exact match */
581 				return x;
582 			if(found)
583 				found = (url_t *)-1;
584 			else
585 				found = x;
586 		}
587 	}
588 
589 	return found;
590 }
591 
592 /* returns a copy of the found URL, should be deleted
593  * returns 0 if not found, and -1 if ambiguous
594  */
get_autologin_url(const char * host)595 url_t *get_autologin_url(const char *host)
596 {
597 	listitem *li;
598 	const char *dot = host;
599 	url_t *x;
600 
601 	x = get_autologin_url_short(host);
602 	if(x == (url_t *)-1)
603 		return x;
604 	if(x)
605 		return url_clone(x);
606 
607 	/* try to match the 'local' directive */
608 	if(gvLocalUrl && strchr(host, '.') == 0) {
609 		x = url_clone(gvLocalUrl);
610 		url_sethostname(x, host);
611 		return x;
612 	}
613 
614 	/* try to match as much as possible of the domain name */
615 	while((dot = strchr(dot, '.')) != 0) {
616 		li = gvBookmarks->first;
617 		while(li) {
618 			x = (url_t *)li->data;
619 
620 			if(x->hostname && strcasecmp(dot, x->hostname) == 0) {
621 				x = url_clone(x);
622 				url_sethostname(x, host);
623 				return x;
624 			}
625 			li = li->next;
626 		}
627 		dot++;
628 	}
629 
630 	/* return default, or NULL if no default */
631 	if(gvDefaultUrl) {
632 		x = url_clone(gvDefaultUrl);
633 		url_sethostname(x, host);
634 		return x;
635 	}
636 	return 0;
637 }
638