1 /*
2    sitecopy, for managing remote web sites.
3    Copyright (C) 1998-2006, Joe Orton <joe@manyfish.co.uk>
4 
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2 of the License, or
8    (at your option) any later version.
9 
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software
17    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 
19 */
20 
21 #include <config.h>
22 
23 #include <sys/types.h>
24 
25 #include <sys/stat.h>
26 
27 #include <ctype.h>
28 #include <errno.h>
29 #ifdef HAVE_STRING_H
30 #include <string.h>
31 #endif
32 #ifdef HAVE_STRINGS_H
33 #include <strings.h>
34 #endif
35 #ifdef HAVE_STDLIB_H
36 #include <stdlib.h>
37 #endif /* HAVE_STDLIB_H */
38 #ifdef HAVE_UNISTD_H
39 #include <unistd.h>
40 #endif /* HAVE_UNISTD_H */
41 
42 #include <ne_string.h>
43 #include <ne_alloc.h>
44 
45 #include "common.h"
46 #include "netrc.h"
47 #include "rcfile.h"
48 #include "sites.h"
49 
50 /** Global variables **/
51 char *copypath;
52 char *rcfile;
53 char *netrcfile;
54 char *home;
55 int havenetrc;
56 
57 /* These are used for reporting errors back to the calling procedures. */
58 int rcfile_linenum;
59 char *rcfile_err;
60 
61 /** Not quite so global variables **/
62 
63 /* These are appended to $HOME */
64 #define RCNAME "/.sitecopyrc"
65 #define COPYNAME "/.sitecopy/"
66 #define NETRCNAME "/.netrc"
67 
68 /* Stores the list of entries in the ~/.netrc */
69 netrc_entry *netrc_list;
70 
71 const char *rc_get_netrc_password(const char *server, const char *username);
72 
73 /* The driver definitions */
74 #ifdef USE_FTP
75 extern const struct proto_driver ftp_driver;
76 #endif /* USE_FTP */
77 #ifdef USE_DAV
78 extern const struct proto_driver dav_driver;
79 #endif /* USE_DAV */
80 #ifdef USE_RSH
81 extern const struct proto_driver rsh_driver;
82 #endif /* USE_RSH */
83 #ifdef USE_SFTP
84 extern const struct proto_driver sftp_driver;
85 #endif /* USE_SFTP */
86 
87 /* rcfile_read will read the rcfile and fill given sites list.
88  * This returns 0 on success, RC_OPENFILE if the rcfile could not
89  * be read, or RC_CORRUPT if the rcfile was corrupt.
90  * If it is corrupt, rcfile_linenum and rcfile_line are set to the
91  * the corrupt line.
92  */
93 #define LINESIZE 128
rcfile_read(struct site ** sites)94 int rcfile_read(struct site **sites)
95 {
96     FILE *fp;
97     int state, last_state=8, ret=0;
98     int alpha, hash;
99     char buf[LINESIZE];
100     char *ch;
101     char *ptr, key[LINESIZE], val[LINESIZE], val2[LINESIZE];
102     /* Holders for the site info, and default site settings */
103     struct site *this_site, *last_site, default_site = {0};
104 
105     if ((fp = fopen(rcfile, "r")) == NULL) {
106 	rcfile_err = strerror(errno);
107 	return RC_OPENFILE;
108     }
109 
110     default_site.perms = sitep_ignore;
111     default_site.symlinks = sitesym_follow;
112     default_site.protocol = siteproto_ftp;
113     default_site.proto_string = ne_strdup("ftp");
114 
115     default_site.ftp_pasv_mode = true;
116     default_site.ftp_use_cwd = false;
117 
118     last_site = this_site = NULL;
119     rcfile_linenum = 0;
120     rcfile_err = NULL;
121 
122     while ((ret==0) && (fgets(buf, sizeof(buf), fp) != NULL)) {
123 	rcfile_linenum++;
124 	/* Put the line without the LF into the error buffer */
125 	if (rcfile_err != NULL) free(rcfile_err);
126 	rcfile_err = ne_strdup(buf);
127 	ptr = strchr(rcfile_err, '\n');
128 	if (ptr != NULL) *ptr = '\0';
129 	state = 0;
130 	ptr = key;
131 	memset(key, 0, LINESIZE);
132 	memset(val, 0, LINESIZE);
133 	memset(val2, 0, LINESIZE);
134 	for (ch=buf; *ch!='\0'; ch++) {
135 	    alpha = !isspace((unsigned)*ch); /* well, alphaish */
136 	    hash = (*ch == '#');
137 	    switch (state) {
138 	    case 0: /* whitespace at beginning of line */
139 		if (hash) {
140 		    state = 8;
141 		} else if (alpha) {
142 		    *(ptr++) = *ch;
143 		    state = 1;
144 		}
145 		break;
146 	    case 1: /* key */
147 		if (hash) {
148 		    state = 8;
149 		} else if (!alpha) {
150 		    ptr = val;
151 		    state = 2;
152 		} else {
153 		    *(ptr++) = *ch;
154 		}
155 		break;
156 	    case 2: /* whitespace after key */
157 		if (hash) {
158 		    state = 8;
159 		} else if (*ch == '"') {
160 		    state = 4; /* begin quoted value */
161 		} else if (alpha) {
162 		    *(ptr++) = *ch;
163 		    state = 3;
164 		}
165 		break;
166 	    case 3: /* unquoted value 1 */
167 		if (hash) {
168 		    state = 8;
169 		} else if (!alpha) {
170 		    ptr = val2;
171 		    state = 5;
172 		} else {
173 		    *(ptr++) = *ch;
174 		}
175 		break;
176 	    case 4: /* quoted value 1 */
177 		if (*ch == '"') {
178 		    ptr = val2;
179 		    state = 5;
180 		} else if (*ch == '\\') {
181 		    last_state = 4;
182 		    state = 9;
183 		} else {
184 		    *(ptr++) = *ch;
185 		}
186 		break;
187 	    case 5: /* whitespace after value 1 */
188 		if (hash) {
189 		    state = 8;
190 		} else if (*ch == '"') {
191 		    state = 6; /* begin quoted value 2 */
192 		} else if (alpha) {
193 		    *(ptr++) = *ch;
194 		    state = 7; /* begin unquoted value 2 */
195 		}
196 		break;
197 	    case 6: /* quoted value 2 */
198 		if (*ch == '"') {
199 		    state = 8;
200 		} else if (*ch == '\\') {
201 		    last_state = 4;
202 		    state = 9;
203 		} else {
204 		    *(ptr++) = *ch;
205 		}
206 		break;
207 	    case 7: /* unquoted value 2 */
208 		if (hash) {
209 		    state = 8;
210 		} else if (!alpha) {
211 		    state = 8;
212 		} else {
213 		    *(ptr++) = *ch;
214 		}
215 		break;
216 	    case 8: /* ignore till end of line */
217 		break;
218 	    case 9: /* a literal (\-slashed) in a value */
219 		*(ptr++) = *ch;
220 		state = last_state;
221 		break;
222 	    }
223 	}
224 
225 	NE_DEBUG(DEBUG_RCFILE, "Key [%s] Value: [%s] Value2: [%s]\n", key, val, val2);
226 
227 	if (strlen(key) == 0) {
228 	    continue;
229 	}
230 	if (strlen(val) == 0) {
231 	    /* A key with no value. */
232 	    if (this_site == NULL) {
233 		if (strcmp(key, "default") == 0) {
234 		    /* Setting up the default site */
235 		    NE_DEBUG(DEBUG_RCFILE, "Default site entry:\n");
236 		    this_site = &default_site;
237 		} else {
238 		    /* Need to be in a site! */
239 		    ret = RC_CORRUPT;
240 		}
241 	    } else if (strcmp(key, "nodelete") == 0) {
242 		this_site->nodelete = true;
243 	    } else if (strcmp(key, "checkmoved") == 0) {
244 		this_site->checkmoved = true;
245 	    } else if (strcmp(key, "nooverwrite") == 0) {
246 		this_site->nooverwrite = true;
247 	    } else if (strcmp(key, "lowercase") == 0) {
248 		this_site->lowercase = true;
249 	    } else if (strcmp(key, "safe") == 0) {
250 		this_site->safemode = true;
251 	    } else if (strcmp(key, "tempupload") == 0) {
252 		this_site->tempupload = true;
253 	    } else {
254 		ret = RC_CORRUPT;
255 	    }
256 	} else if (strlen(val2) == 0) {
257 	    /* A key with a single value. */
258 	    if (strcmp(key, "site") == 0) {
259 		/* Beginning of a new Site */
260 		if (this_site != &default_site)
261 		    last_site = this_site;
262 		/* Allocate new item */
263 		this_site = ne_malloc(sizeof(struct site));
264 		/* Copy over the defaults */
265 		memcpy(this_site, &default_site, sizeof(struct site));
266 		/* Deep-copy the string lists */
267 		this_site->excludes = fnlist_deep_copy(default_site.excludes);
268 		this_site->ignores = fnlist_deep_copy(default_site.ignores);
269 		this_site->asciis = fnlist_deep_copy(default_site.asciis);
270 		this_site->prev = last_site;
271 		if (last_site != NULL) { /* next site */
272 		    last_site->next = this_site;
273 		} else { /* First site */
274 		    *sites = this_site;
275 		}
276 		this_site->name = ne_strdup(val);
277 		this_site->files = NULL;
278 		this_site->proto_string = ne_strdup(default_site.proto_string);
279 		/* Now work out the info filename */
280 		this_site->infofile = ne_concat(copypath, val, NULL);
281                 this_site->certfile = ne_concat(copypath, val, ".crt", NULL);
282 	    } else if (this_site == NULL) {
283 		ret = RC_CORRUPT;
284 	    } else if (strcmp(key, "username") == 0) {
285 		/* username */
286 		this_site->server.username = ne_strdup(val);
287 	    } else if (strcmp(key, "server") == 0) {
288 		this_site->server.hostname = ne_strdup(val);
289 	    } else if (strcmp(key, "port") == 0) {
290 		this_site->server.port = atoi(val);
291 	    } else if (strcmp(key, "proxy-server") == 0) {
292 		this_site->proxy.hostname = ne_strdup(val);
293 	    } else if (strcmp(key, "proxy-port") == 0) {
294 		this_site->proxy.port = atoi(val);
295 	    } else if (strcmp(key, "proxy-password") == 0) {
296 		this_site->proxy.password = ne_strdup(val);
297 	    } else if (strcmp(key, "proxy-username") == 0) {
298 		this_site->proxy.username = ne_strdup(val);
299 	    } else if (strcmp(key, "password") == 0) {
300 		this_site->server.password = ne_strdup(val);
301 	    } else if (strcmp(key, "url") == 0) {
302 	        this_site->url = ne_strdup(val);
303 	    } else if (strcmp(key, "remote") == 0) {
304 		/* Relative filenames must start with "~/" */
305 		if (val[0] == '~') {
306 		    if (val[1] == '/') {
307 			this_site->remote_isrel = true;
308 		    } else {
309 			ret = RC_CORRUPT;
310 		    }
311 		} else {
312 		    /* Dirname doesn't begin with "~/" */
313 		    this_site->remote_isrel = false;
314 		}
315 		if (val[strlen(val)-1] != '/')
316 		    strcat(val, "/");
317 		this_site->remote_root_user = ne_strdup(val);
318 	    } else if (strcmp(key, "local") == 0) {
319 		/* Relative filenames must start with "~/" */
320 		if (val[0] == '~') {
321 		    if (val[1] == '/') {
322 			this_site->local_isrel = true;
323 		    } else {
324 			ret = RC_CORRUPT;
325 		    }
326 		} else {
327 		    /* Dirname doesn't begin with a "~/" */
328 		    this_site->local_isrel = false;
329 		}
330 		if (val[strlen(val)-1] != '/')
331 		    strcat(val, "/");
332 		this_site->local_root_user = ne_strdup(val);
333 	    } else if (strcmp(key, "permissions") == 0) {
334 		if (strcmp(val, "ignore") == 0) {
335 		    this_site->perms = sitep_ignore;
336                     this_site->dirperms = 0;
337 		} else if (strcmp(val, "exec") == 0) {
338 		    this_site->perms = sitep_exec;
339 		} else if (strcmp(val, "all") == 0) {
340 		    this_site->perms = sitep_all;
341                 } else if (strcmp(val, "dir") == 0) {
342                     this_site->dirperms = 1;
343 		} else {
344 		    ret = RC_CORRUPT;
345 		}
346 	    } else if (strcmp(key, "symlinks") == 0) {
347 		if (strcmp(val, "follow") == 0) {
348 		    this_site->symlinks = sitesym_follow;
349 		} else if (strcmp(val, "maintain") == 0) {
350 		    this_site->symlinks = sitesym_maintain;
351 		} else if (strcmp(val, "ignore") == 0) {
352 		    this_site->symlinks = sitesym_ignore;
353 		} else {
354 		    ret = RC_CORRUPT;
355 		}
356 	    } else if (strcmp(key, "exclude") == 0) {
357 		struct fnlist *f = fnlist_prepend(&this_site->excludes);
358 		if (val[0] == '/') {
359 		    f->pattern = ne_strdup(val+1);
360 		    f->haspath = true;
361 		} else {
362 		    f->pattern = ne_strdup(val);
363 		    f->haspath = false;
364 		}
365 	    } else if (strcmp(key, "ignore") == 0) {
366 		struct fnlist *f = fnlist_prepend(&this_site->ignores);
367 		if (val[0] == '/') {
368 		    f->pattern = ne_strdup(val+1);
369 		    f->haspath = true;
370 		} else {
371 		    f->pattern = ne_strdup(val);
372 		    f->haspath = false;
373 		}
374 	    } else if (strcmp(key, "ascii") == 0) {
375 		struct fnlist *f = fnlist_prepend(&this_site->asciis);
376 		if (val[0] == '/') {
377 		    f->pattern = ne_strdup(val+1);
378 		    f->haspath = true;
379 		} else {
380 		    f->pattern = ne_strdup(val);
381 		    f->haspath = false;
382 		}
383 	    } else if (strcmp(key, "protocol") == 0) {
384 		if (strcasecmp(val, "ftp") == 0) {
385 		    this_site->protocol = siteproto_ftp;
386 		} else if (strcasecmp(val, "http") == 0 ||
387 			   strcasecmp(val, "dav") == 0 ||
388 			   strcasecmp(val, "webdav") == 0) {
389 		    this_site->protocol = siteproto_dav;
390 		} else if (strcasecmp(val, "rsh") == 0) {
391 		    this_site->protocol = siteproto_rsh;
392 		} else if (strcasecmp(val, "ssh") == 0) {
393                     this_site->protocol = siteproto_rsh;
394                     if (this_site->rsh_cmd == NULL)
395                         this_site->rsh_cmd = ne_strdup("ssh");
396                     if (this_site->rcp_cmd == NULL)
397                         this_site->rcp_cmd = ne_strdup("scp");
398 		} else if (strcasecmp(val, "sftp") == 0) {
399 		    this_site->protocol = siteproto_sftp;
400                 } else {
401 		    this_site->protocol = siteproto_unknown;
402 		}
403 		free(this_site->proto_string);
404 		this_site->proto_string = ne_strdup(val);
405 	    } else if (strcmp(key, "ftp") == 0) {
406 		if (strcmp(val, "nopasv") == 0) {
407 		    this_site->ftp_pasv_mode = false;
408 		} else if (strcmp(val, "showquit") == 0) {
409 		    this_site->ftp_echo_quit = true;
410 		} else if (strcmp(val, "usecwd") == 0) {
411 		    this_site->ftp_use_cwd = true;
412 		} else if (strcmp(val, "nousecwd") == 0) {
413 		    this_site->ftp_use_cwd = false;
414 		} else {
415 		    ret = RC_CORRUPT;
416 		}
417 	    } else if (strcmp(key, "http") == 0) {
418 		if (strcmp(val, "expect") == 0) {
419 		    this_site->http_use_expect = true;
420 		} else if (strcmp(val, "limit") == 0) {
421 		    this_site->http_limit = true;
422 		} else if (strcmp(val, "secure") == 0) {
423 		    this_site->http_secure = true;
424 		} else if (strcmp(val, "tolerant") == 0) {
425 		    this_site->http_tolerant = true;
426 		} else {
427 		    ret = RC_CORRUPT;
428 		}
429             } else if (strcmp(key, "client-cert") == 0) {
430                 this_site->client_cert = ne_strdup(val);
431 	    } else if (strcmp(key, "rsh") == 0) {
432 		this_site->rsh_cmd = ne_strdup(val);
433 	    } else if (strcmp(key, "rcp") == 0) {
434 		this_site->rcp_cmd = ne_strdup(val);
435 	    } else if (strcmp(key, "state") == 0) {
436 		if (strcmp(val, "checksum") == 0) {
437 		    this_site->state_method = state_checksum;
438 		} else if (strcmp(val, "timesize") == 0) {
439 		    this_site->state_method = state_timesize;
440 		} else {
441 		    ret = RC_CORRUPT;
442 		}
443 	    } else if (strcmp(key, "checkmoved") == 0) {
444 		if (strcmp(val, "renames") == 0) {
445 		    this_site->checkrenames = true;
446 		    this_site->checkmoved = true;
447 		} else {
448 		    ret = RC_CORRUPT;
449 		}
450 	    } else if (strcmp(key, "charset") == 0) {
451                 NE_DEBUG(DEBUG_RCFILE, "Ignored key %s\n", key);
452             } else {
453 		/* Unknown key! */
454 		ret = RC_CORRUPT;
455 	    }
456 	} else {
457 	    {
458 		ret = RC_CORRUPT;
459 	    }
460 	}
461     }
462 
463     fclose(fp);
464     return ret;
465 }
466 #undef LINESIZE
467 
rc_get_netrc_password(const char * server,const char * username)468 const char *rc_get_netrc_password(const char *server, const char *username) {
469     netrc_entry *found;
470     found = search_netrc(netrc_list, server);
471     if (found == NULL) {
472 	return NULL;
473     }
474     if (strcmp(found->account, username) == 0) {
475 	return found->password;
476     } else {
477 	return NULL;
478     }
479 }
480 
481 /* Returns zero if site is properly defined, else non-zero */
rcfile_verify(struct site * any_site)482 int rcfile_verify(struct site *any_site)
483 {
484     struct stat localst;
485     char *temp;
486     int ret;
487 
488     /* Protocol-specific checks first, since if a new protocol driver is used,
489      * any of the other checks may be irrelevant. */
490     switch (any_site->protocol) {
491     case siteproto_ftp:
492 #ifdef USE_FTP
493 	any_site->driver = &ftp_driver;
494 	/* FTP checks */
495 	if (any_site->symlinks == sitesym_maintain) {
496 	    return SITE_NOMAINTAIN;
497 	}
498 	break;
499 #else /* !USE_FTP */
500 	return SITE_UNSUPPORTED;
501 #endif /* USE_FTP */
502     case siteproto_dav:
503 #ifdef USE_DAV
504 	any_site->driver = &dav_driver;
505 	/* HTTP checks */
506 	if (any_site->remote_isrel) {
507 	    return SITE_NOREMOTEREL;
508 	}
509 	if (any_site->perms == sitep_all || any_site->dirperms) {
510 	    return SITE_NOPERMS;
511 	}
512 	if (any_site->symlinks == sitesym_maintain) {
513 	    return SITE_NOMAINTAIN;
514 	}
515 	break;
516 #else /* !USE_DAV */
517 	return SITE_UNSUPPORTED;
518 #endif /* USE_DAV */
519     case siteproto_rsh:
520 #ifdef USE_RSH
521 	any_site->driver = &rsh_driver;
522 	/* FIXME: rsh checks? */
523 	break;
524 #else /* !USE_RSH */
525 	return SITE_UNSUPPORTED;
526 #endif /* USE_RSH */
527     case siteproto_sftp:
528 #ifdef USE_SFTP
529 	any_site->driver = &sftp_driver;
530 	/* FIXME: sftp checks? */
531 	break;
532 #else /* !USE_SFTP */
533 	return SITE_UNSUPPORTED;
534 #endif /* USE_SFTP */
535     case siteproto_unknown:
536 	return SITE_UNSUPPORTED;
537     }
538 
539     /* Valid options check */
540     if (any_site->checkrenames && (any_site->state_method != state_checksum)) {
541 	return SITE_NORENAMES;
542     }
543 
544     /* Check they specified everything in the rcfile */
545     if (any_site->server.hostname == NULL) {
546 	return SITE_NOSERVER;
547     }
548 
549     if (any_site->server.username != NULL && any_site->server.password == NULL) {
550 	if (havenetrc) {
551 	    const char *pass;
552 	    NE_DEBUG(DEBUG_RCFILE, "Checking netrc for password for %s@%s...",
553 		   any_site->server.username, any_site->server.hostname);
554 	    pass = rc_get_netrc_password(any_site->server.hostname,
555 					  any_site->server.username);
556 	    if (pass != NULL) {
557 		NE_DEBUG(DEBUG_RCFILE, "found!\n");
558 		any_site->server.password = (char *) pass;
559 	    } else {
560 		NE_DEBUG(DEBUG_RCFILE, "none found.\n");
561 	    }
562 	}
563     }
564     /* TODO: lookup proxy username/password in netrc too */
565 
566     if (any_site->remote_root_user == NULL) {
567 	return SITE_NOREMOTEDIR;
568     } else if (any_site->local_root_user == NULL) {
569 	return SITE_NOLOCALDIR;
570     }
571 
572     /* Need a home directory if we're using relative local root */
573     if (home == NULL && any_site->local_root)
574 	return SITE_NOLOCALREL;
575 
576     /* Can't use safe mode and nooverwrite mode */
577     if (any_site->safemode && any_site->nooverwrite)
578 	return SITE_NOSAFEOVER;
579 
580     if (any_site->safemode && any_site->tempupload)
581 	return SITE_NOSAFETEMPUP;
582 
583     if (any_site->remote_isrel) {
584 	any_site->remote_root = ne_strdup(any_site->remote_root_user + 2);
585     } else {
586 	any_site->remote_root = ne_strdup(any_site->remote_root_user);
587     }
588     if (any_site->local_isrel) {
589 	/* We skip the first char ('~') of l_r_u */
590 	any_site->local_root = ne_concat(home, any_site->local_root_user + 1,
591 					 NULL);
592     } else {
593 	any_site->local_root = any_site->local_root_user;
594     }
595 
596     /* Now check the local directory actually exists.
597      * To do this, stat `/the/local/root/.', which will fail if the
598      * can't read the directory or if it's a file not a directory */
599     temp = ne_concat(any_site->local_root, ".", NULL);
600     ret = stat(temp, &localst);
601     free(temp);
602     if (ret != 0) {
603 	return SITE_ACCESSLOCALDIR;
604     }
605 
606     if (any_site->client_cert && strncmp(any_site->client_cert, "~/", 2) == 0) {
607         temp = ne_concat(home, any_site->client_cert + 1, NULL);
608         ne_free(any_site->client_cert);
609         any_site->client_cert = temp;
610     }
611 
612     /* Assign default ports if they didn't bother to */
613     if (any_site->server.port == 0) {
614 	NE_DEBUG(DEBUG_RCFILE, "Lookup up default port:\n");
615 	any_site->server.port = (*any_site->driver->get_server_port)(any_site);
616 	NE_DEBUG(DEBUG_RCFILE, "Using port: %d\n", any_site->server.port);
617     }
618 
619     if (any_site->proxy.port == 0) {
620 	NE_DEBUG(DEBUG_RCFILE, "Lookup default proxy port...\n");
621 	any_site->proxy.port = (*any_site->driver->get_proxy_port)(any_site);
622 	NE_DEBUG(DEBUG_RCFILE, "Using port %d\n", any_site->proxy.port);
623     }
624 
625     /* TODO: ditto for proxy server */
626     return 0;
627 }
628 
init_netrc()629 int init_netrc() {
630     if (!havenetrc) return 0;
631     netrc_list = parse_netrc(netrcfile);
632     if (netrc_list == NULL) {
633 	/* Couldn't parse it */
634 	return 1;
635     } else {
636 	/* Could parse it */
637 	return 0;
638     }
639 }
640 
641 /* Checks the perms of the rcfile and site storage directory. */
init_paths()642 int init_paths()
643 {
644     struct stat st;
645     if (stat(rcfile, &st) < 0) {
646 	NE_DEBUG(DEBUG_RCFILE, "stat failed on %s: %s\n",
647 	       rcfile, strerror(errno));
648 	return RC_OPENFILE;
649     }
650 #if !defined (__EMX__) && !defined(__CYGWIN__)
651     if (!S_ISREG(st.st_mode)) {
652         return RC_OPENFILE;
653     }
654     if ((st.st_mode & ~(S_IFREG | S_IREAD | S_IWRITE)) > 0) {
655 	return RC_PERMS;
656     }
657 #endif
658     if ((netrcfile == 0) || (stat(netrcfile, &st) < 0)) {
659 	havenetrc = false;
660 #if !defined (__EMX__) && !defined(__CYGWIN__)
661     } else if ((st.st_mode & ~(S_IFREG | S_IREAD | S_IWRITE)) > 0) {
662 	return RC_NETRCPERMS;
663 #endif
664     } else {
665 	havenetrc = true;
666     }
667     if (stat(copypath, &st) < 0) {
668 	NE_DEBUG(DEBUG_RCFILE, "stat failed on %s: %s\n",
669 	       copypath, strerror(errno));
670 	return RC_DIROPEN;
671     }
672 #if !defined (__EMX__) && !defined(__CYGWIN__)
673     if (st.st_mode & (S_IRWXG | S_IRWXO)) {
674 	return RC_DIRPERMS;
675     }
676 #endif
677     return 0;
678 }
679 
init_env()680 int init_env() {
681     /* Assign default filenames if they didn't give us any */
682     home = getenv("HOME");
683     if (home == NULL) {
684 	if ((rcfile == NULL) || (copypath == NULL)) {
685 	    /* We need a $HOME or both rcfile and info dir path */
686 	    return 1;
687 	} else {
688 	    /* No $HOME, but we've got the rcfile and info dir path */
689 	    return 0;
690 	}
691     }
692     if (rcfile == NULL) {
693 	rcfile = ne_concat(home, RCNAME, NULL);
694     }
695     if (copypath == NULL) {
696 	copypath = ne_concat(home, COPYNAME, NULL);
697     }
698     netrcfile = ne_concat(home, NETRCNAME, NULL);
699     return 0;
700 }
701 
702 /* rcfile_write() by Lee Mallabone, cleaned by JO.
703  * Write the contents of list_of_sites to the specified 'filename'
704  * in the standard sitecopy rc format.
705  *
706  * Any data already in 'filename' is over-written.
707  */
rcfile_write(char * filename,struct site * list_of_sites)708 int rcfile_write (char *filename, struct site *list_of_sites)
709 {
710     struct site *current;
711     struct fnlist *item;
712     FILE *fp;
713 
714     fp = fopen (filename, "w");
715     if (fp == NULL) {
716 	printf ("There was a problem writing to the sitecopy configuration file.\n\nCheck permissions on %s.", filename);
717 	return RC_OPENFILE;
718    }
719 
720     /* Set rcfile permissions properly */
721 #if !defined (__EMX__) && !defined(__CYGWIN__)
722     if (fchmod (fileno(fp), 00600) == -1) {
723 	return RC_PERMS;
724     }
725 #endif
726 
727     for (current=list_of_sites; current!=NULL; current=current->next) {
728 	/* Okay so this maybe isn't the most intuitive thing to look at.
729 	 * With any luck though, the rcfile's it produces will be. :) */
730 	if (fprintf (fp, "site %s\n", current->name) == -1) {
731 	    return RC_CORRUPT;
732 	}
733 	if (fprintf (fp, "  server %s\n", current->server.hostname) == -1) {
734 	    return RC_CORRUPT;
735 	}
736 
737 	if ((current->server.username != NULL) &&
738 	    (strlen(current->server.username) > 0))
739 	    if (fprintf(fp, "  username %s\n",
740 			current->server.username) == -1) {
741 		return RC_CORRUPT;
742 	    }
743 
744 	if ((current->server.password != NULL)
745 	    && (strlen(current->server.password) > 0))
746 	    if (fprintf(fp, "  password %s\n",
747 			current->server.password) == -1) {
748 		return RC_CORRUPT;
749 	    }
750 
751         if (fprintf(fp, "  remote %s\n  local %s\n",
752 		    current->remote_root_user,
753 		    current->local_root_user) == -1) {
754 	    return RC_CORRUPT;
755 	}
756 
757 	if (fprintf (fp, "  protocol %s\n", current->proto_string) == -1) {
758 	    return RC_CORRUPT;
759 	}
760 
761 	/* Makes sense to have protocol (ish) options after we specify
762 	 * the protocol.  Warning, if the http declarations in site_t
763 	 * are ever surrounded by an ifdef USE_DAV, then this will need
764 	 * to be changed.  */
765 
766 	/* Write out the boolean fields */
767 
768 #define RCWRITEBOOL(field,name) \
769       if ((field) && (fprintf(fp, "  %s\n", name) == -1)) return RC_CORRUPT;
770 
771 	RCWRITEBOOL(current->nodelete, "nodelete");
772 	if (current->checkmoved) {
773 	    if (current->checkrenames) {
774 		if (fprintf(fp, "  checkmoved renames\n") == -1)
775 		    return RC_CORRUPT;
776 	    } else {
777 		if (fprintf(fp, "  checkmoved\n") == -1)
778 		    return RC_CORRUPT;
779 	    }
780 	}
781 
782 	RCWRITEBOOL(current->nooverwrite, "nooverwrite");
783 	RCWRITEBOOL(current->safemode, "safe");
784 	RCWRITEBOOL(current->lowercase, "lowercase");
785 	RCWRITEBOOL(current->tempupload, "tempupload");
786 
787 	RCWRITEBOOL(!current->ftp_pasv_mode, "ftp nopasv");
788 	RCWRITEBOOL(current->ftp_echo_quit, "ftp showquit");
789 	RCWRITEBOOL(current->ftp_use_cwd, "ftp usecwd");
790 	RCWRITEBOOL(current->http_limit, "http limit");
791 	RCWRITEBOOL(current->http_use_expect, "http expect");
792 
793 #undef RCWRITEBOOL
794 
795 	if (current->server.port > 0) { /* Sanity check */
796 	    if (fprintf (fp, "  port %d\n", current->server.port) == -1) {
797 		return RC_CORRUPT;
798 	    }
799 	}
800 
801 	/* Add the site's URL if one has been supplied. */
802 	if (current->url) {
803 	    if (fprintf (fp, "  url %s\n", current->url) == -1) {
804 		return RC_CORRUPT;
805 	    }
806 	}
807 
808 	/* State method */
809 	switch (current->state_method) {
810 	case (state_timesize):
811 	    if (fprintf (fp, "  state timesize\n") == -1) {
812 		return RC_CORRUPT;
813 	    }
814 	    break;
815 	case (state_checksum):
816 	    if (fprintf (fp, "  state checksum\n") == -1) {
817 		return RC_CORRUPT;
818 	    }
819 	    break;
820 	}
821 
822 	/* Permissions now */
823 	switch (current->perms) {
824 	case (sitep_ignore):
825 	    if (fprintf (fp, "  permissions ignore\n") == -1) {
826 		return RC_CORRUPT;
827 	    }
828 	    break;
829 	case (sitep_exec):
830 	    if (fprintf (fp, "  permissions exec\n") == -1) {
831 		return RC_CORRUPT;
832 	    }
833 	    break;
834 	case (sitep_all):
835 	    if (fprintf (fp, "  permissions all\n") == -1) {
836 		return RC_CORRUPT;
837 	    }
838 	    break;
839 	}
840 
841         if (current->dirperms) {
842             if (fprintf(fp, "  permissions dir\n") == -1) {
843                 return RC_CORRUPT;
844             }
845         }
846 
847 	/* Sym link mode */
848 	switch (current->symlinks) {
849 	case (sitesym_ignore):
850 	    if (fprintf (fp, "  symlinks ignore\n") == -1) {
851 		return RC_CORRUPT;
852 	    }
853 	    break;
854 	case (sitesym_follow):
855 	    if (fprintf (fp, "  symlinks follow\n") == -1) {
856 		return RC_CORRUPT;
857 	    }
858 	    break;
859 	case (sitesym_maintain):
860 	    if (fprintf (fp, "  symlinks maintain\n") == -1) {
861 		return RC_CORRUPT;
862 	    }
863 	    break;
864 	}
865 
866 #define DUMP_FNLIST(list, name) 					  \
867 do {\
868 for (item = list; item != NULL; item = item->next)		  	  \
869     if (fprintf(fp, "  " name " \"%s%s\"\n", item->haspath?"/":"",	  \
870 	item->pattern) == -1)						  \
871     return RC_CORRUPT;					     \
872 } while(0)
873 
874 	DUMP_FNLIST(current->excludes, "exclude");
875 	DUMP_FNLIST(current->asciis, "ascii");
876 	DUMP_FNLIST(current->ignores, "ignore");
877 
878 #undef DUMP_FNLIST
879 
880     }
881     if (fclose (fp) != 0)
882 	return RC_CORRUPT;
883 
884     return 0;
885 }
886