1 /*
2              *** cmdftp, command line ftp client ***
3 
4              Copyright (C) 2003-2009 Claudio Fontana
5 
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 3 of the License, or
9    (at your option) any later version.
10 
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15 
16    You should have received a copy of the GNU General Public License
17    along with this program (look for the file called COPYING);
18    if not, write to the Free Software Foundation, Inc.,
19    51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20 
21    You can contact the author (Claudio Fontana) by sending a mail
22    to claudio@gnu.org
23 */
24 #include "cmdftp.h"
25 
26 /*******************************/
27 /* ERROR, WARNING, ... STRINGS */
28 /*******************************/
29 
30 char* msg_err[] = {
31     0, "bad server response",
32 
33     "Usage: cmdftp [OPTION]... HOSTNAME\n"
34 
35     "Try '"
36 #if HAVE_GETOPT_LONG
37     "cmdftp --help"
38 #else
39     "cmdftp -h"
40 #endif
41     "' for more information.",
42 
43     "connection failed", "giving up on login", "unexpected error", "heap allocation failed", "lost working directory", "double SIGINT, aborting", "no usable tmpdir"
44 };
45 
46 char* msg_war[] = {
47     "bye", "cannot open:", "error writing to:", "error reading from:", "transfer not confirmed", "file not found:", "cannot open temp file:", "unknown command:", "missing argument", "ignoring argument", "login failed", "operation failed or incomplete", "missing environment variable:", "resuming connection lost during", "connection timeout during", "interrupted during", "could not resume", "invalid source:", "invalid target:", "skipping:", "(local)", "(remote)"
48 };
49 
50 #if HAVE_GETOPT_LONG
51 
52 char* msg_usage = "cmdftp [OPTION]... HOSTNAME\n\n"
53     "options: \n"
54     " -h, --help         show this help and quit\n"
55     " -v, --version      show version and quit\n"
56     " -p, --port N       remote TCP port of the server (def:21)\n"
57     " -b, --buffer N     size of the transfer buffer in KB (def:64)\n"
58     " -t, --timeout N    broken connection check timeout in sec (def:60)\n"
59     " -a, --attempts N   number of manual login attempts\n"
60     " -q, --quiet        be (very) quiet\n"
61     " -D, --debug        show detailed information about ftp commands\n"
62     " -n, --no-auto      disable autologin (~/.netrc)\n"
63     " -m, --no-manual    disable manual login\n"
64     " -g, --no-paging    disable output paging\n"
65     " -d, --dotfiles     do not ignore dot-files in transfers\n"
66     " -P, --no-path      disable path in prompt\n";
67 
68 #else
69 char* msg_usage = "cmdftp [OPTION]... HOSTNAME\n\n"
70     "options: \n"
71     " -h             show this help and quit\n"
72     " -v             show version and quit\n"
73     " -p N           remote TCP port of the server (def:21)\n"
74     " -b N           size of the transfer buffer in KB (def:64)\n"
75     " -t N           broken connection check timeout in sec (def:60)\n"
76     " -a N           number of manual login attempts\n"
77     " -q             be (very) quiet\n"
78     " -D             show detailed information about ftp commands\n"
79     " -n             disable autologin (~/.netrc)\n"
80     " -m             disable manual login\n"
81     " -g             disable output paging\n"
82     " -d             do not ignore dot-files in transfers\n"
83     " -P             disable path in prompt\n";
84 #endif
85 
86 char* msg_version = PACKAGE_STRING " Copyright (c) 2003-2009 Claudio Fontana";
87 
88 char* msg_intro =
89     "cmdftp comes with ABSOLUTELY NO WARRANTY; this is free software, and you\n"
90     "are welcome to redistribute it under certain conditions specified by the\n"
91     "GPL (GNU General Public License) version 3 or later.\n"
92     "Look at the COPYING file for the details.\n"
93     "\nType h after the login to get help about internal commands.\n";
94 
95 char* msg_help[] = {
96     "h", "display this help",
97     "l", "switch to local mode, following commands refer to local",
98     "r", "switch to remote mode, following commands refer to remote",
99     "pwd", "prompt working directory",
100     "cd  PATH", "change working directory to PATH",
101     "md  PATH", "make new directory PATH",
102     "rd  PATH", "remove empty directory PATH",
103     "rm  MASK", "delete regular files matching MASK, skip directories",
104     "ls", "list current directory contents",
105     "ls  MASK", "list files/dirs matching MASK",
106     "dir", "pretty list of current directory",
107     "cp  SRC TRG", "copy SRC to TRG. Behaves like /bin/cp -r",
108     "mv  SRC TRG", "move SRC to TRG. Behaves like /bin/mv",
109     "u   MASK DIR", "upload files/dirs matching MASK into remote DIR (recurs)",
110     "d   MASK DIR", "download files/dirs matching MASK into local DIR (recurs)",
111     "dr  MASK DIR", "same as above. If local file already exists, resume",
112     "ren MASK FR TO", "replace FR to TO once in all filenames matching MASK",
113     "p   FILE", "print contents of the FILE on the terminal",
114     "e   FILE", "edit FILE",
115     "q", "quit client",
116     "quit|exit|bye", "aliases for q command",
117     "<TAB>", "tab-completion"
118 };
119 
120 
121 /***********/
122 /* GLOBALS */
123 /***********/
124 
125 struct cmdftp_options o = {
126     0,                          /* hostname */
127     64 * 1024,                  /* b */
128     21,                         /* p */
129     1,                          /* a */
130     60,                         /* t */
131     0, 0, 0, 0, 0, 0, 0         /* FLAGS */
132 };
133 
134 int cmdftp_control = 0;                               /* control connection */
135 int cmdftp_data = 0;                                  /* data connection */
136 
137 char* user = 0; char* pass = 0;
138 char logging_in = 0;                                  /* user_logging_in? */
139 volatile int transfer_interrupted = TRAN_INTR_NO;     /* Why? */
140 
141 struct hostent* server = 0;                           /* the remote host */
142 struct termios cmdftp_termios;                        /* terminal data */
143 
144 char* buffer;                                         /* in/out file transf */
145 
146 char cmd_buffer[CMD_BUF_SIZE + 1];                    /* in/out commands */
147 char cmd_line[CMD_BUF_SIZE + 1];                      /* in     line sep */
148 char cmd_userinput[CMD_BUF_SIZE + 1];                 /* buf for term in */
149 char* cmd_ptr;                                        /* ->cmd_buffer */
150 
151 char* env[N_ENV];                                     /* used env vars */
152 char* cwd[2] = { 0 };                                 /* working dir {l, r} */
153 
154 int mode = CMDFTP_REMOTE;                             /* current mode {l, r} */
155 
156 struct sigaction sa;
157 char localhost[256] = { 0 };                          /* local host name */
158 
159 struct {
160     int mlst;
161 } feat;
162 
163 /*********************************************/
164 /* USER COMMANDS and corresponding functions */
165 /*********************************************/
166 
167 char* commands[] = { "bye", "cat", "cd", "cp", "d", "dir", "dr", "e", "exit", "h", "l", "ls", "md", "mkdir", "mv", "p", "pwd", "q", "quit", "r", "rd", "ren", "rename", "rm", "rmdir", "u" };
168 
169 #define N_COMMANDS (sizeof(commands) / sizeof(char*))
170 
171 fun_ptr cmd_functions[N_COMMANDS] =
172     { cmd_quit, cmd_p, cmd_cd, cmd_cp, cmd_d, cmd_dir, cmd_dr, cmd_e, cmd_quit, cmd_h, cmd_l, cmd_ls, cmd_md, cmd_md, cmd_mv, cmd_p, cmd_pwd, cmd_quit, cmd_quit, cmd_r, cmd_rd, cmd_ren, cmd_ren, cmd_rm, cmd_rd, cmd_u };
173 
174 fun_ptr mf[2][13] = {
175     { &local_chdir, &local_mkdir, &local_rmdir, (fun_ptr)local_getcwd,
176       &local_copy, &local_move, &local_unlink,
177       &local_file, (fun_ptr)local_size,
178       &local_fetch_list, &local_fetch_pretty_list,
179       &local_print, &local_edit },
180 
181     { &remote_chdir, &remote_mkdir, &remote_rmdir, (fun_ptr)remote_getcwd,
182       &remote_copy, &remote_move, &remote_unlink,
183       &remote_file, (fun_ptr)remote_size,
184       &remote_fetch_list, &remote_fetch_pretty_list,
185       &remote_print, &remote_edit }
186 };
187 
188 /************************************/
189 /* ERROR, WARNING, OUTPUT FUNCTIONS */
190 /************************************/
191 
192 
cmdftp_err(int code,char * msg)193 void cmdftp_err(int code, char* msg)
194 { fprintf(stderr, "cmdftp: %s %s\n", msg_err[code], msg); exit(code); }
195 
cmdftp_war(int code,char * msg)196 void cmdftp_war(int code, char* msg)
197 { if (!o.q) fprintf(stderr, "cmdftp: %s %s\n", msg_war[code], msg); }
198 
usage(void)199 void usage(void) { printf("\n%s\n", msg_usage); }
version(void)200 void version(void) { printf("%s\n", msg_version); }
201 
intro(void)202 void intro(void) {
203     int i; if (o.q) return;
204     for (i = 0; i < 79; i++) fputc('*', stdout);
205     fputc('\n', stdout);
206     version();
207     printf("\n%s\n", msg_intro);
208     for (i = 0; i < 79; i++) fputc('*', stdout);
209     fputc('\n', stdout);
210 }
211 
print_progress(char * op,char * fn,size_t fn_len,off_t cur_pos,off_t total_size)212 void print_progress(char* op, char* fn, size_t fn_len, off_t cur_pos, off_t total_size) {
213 
214     char* units[] = { "", "K", "M", "G", "T" }; int u_idx;
215     char line[WIDE_LINE_LEN]; size_t line_len;
216     double progress =
217 	total_size > 0 ? (cur_pos * 100.0 / total_size) : 100.0;
218 
219     u_idx = 0;
220 
221     while ((total_size >> 10) >= 10 && u_idx < 4) {
222 	total_size >>= 10; cur_pos >>= 10; u_idx++;
223     }
224 
225     snprintf(line, 75, "%s " OFF_FMT "%s/" OFF_FMT "%s (%4.*f%%) ",
226 	     op, cur_pos, units[u_idx], total_size, units[u_idx],
227 	     progress < 1.0 ? 2 : 1, progress);
228 
229     line[74] = '\0';		/* just to be sure that*/
230     line_len = strlen(line);	/* line_len <= 74 */
231 
232     if (line_len + fn_len < 79) {
233 	strcpy(line + line_len, fn);
234 
235     } else {
236 	sprintf(line + line_len, "[...]%s", (fn + fn_len - (74 - line_len)));
237     }
238 
239     printf("%s\r", line);
240     fflush(stdout);
241 }
242 
243 /***************************************/
244 /* MAIN and other high level functions */
245 /***************************************/
246 
print_prompt(void)247 void print_prompt(void) {
248     if (!o.q)
249 	printf("\r%s:%s>", mode == CMDFTP_LOCAL ?
250 	       localhost : o.hostname, o.P ? "" : cwd[mode]);
251     fflush(stdout);
252 }
253 
main(int argc,char * argv[])254 int main(int argc, char* argv[]) {
255 
256     if (argc < 2) cmdftp_err(CMDFTP_ERR_OPT, "");
257     getoptions(argc, argv);
258 
259     if (gethostname(localhost, sizeof(localhost)) < 0)
260 	strcpy(localhost, "local");
261 
262     intro();
263     reset_cmd_buffer(); buffer = my_malloc(o.b);
264 
265     tcgetattr(0, &cmdftp_termios); atexit(&cmdftp_canon_mode);
266     init_signals();
267     init_temp();
268 
269     if (!(cmdftp_control = cmdftp_connect(o.p, 8)))
270 	cmdftp_err(CMDFTP_ERR_CONN, "");
271 
272     greeting();
273     detect_feats();
274     login_procedure();
275 
276     do_setcwd(CMDFTP_LOCAL);
277     do_setcwd(CMDFTP_REMOTE);
278 
279     while (1) {
280 	char* cmd;
281 	print_prompt();
282 	if (!dispatch(cmd = readline(1, 1))) return 0;
283 	free(cmd);
284     }
285 }
286 
287 #if HAVE_GETOPT_LONG
288 
289 struct option long_options[] = {
290     { "help", 0, 0, 'h' },
291     { "version", 0, 0, 'v' },
292     { "attempts", 1, 0, 'a' },
293     { "port", 1, 0, 'p' },
294     { "buffer", 1, 0, 'b' },
295     { "timeout", 1, 0, 't' },
296     { "quiet", 0, 0, 'q' },
297     { "debug", 0, 0, 'D' },
298     { "no-manual", 0, 0, 'm' },
299     { "no-auto", 0, 0, 'n' },
300     { "no-paging", 0, 0, 'g' },
301     { "no-path", 0, 0, 'P' },
302     { "dotfiles", 0, 0, 'd' },
303     { 0, 0, 0, 0 }
304 };
305 
306 #endif
307 
getoptions(int argc,char * argv[])308 void getoptions(int argc, char* argv[]) {
309     int i, rv;
310 
311     while ((i =
312 
313 #if HAVE_GETOPT_LONG
314 	    getopt_long(argc, argv, "DPa:b:dghmnp:t:qv", long_options, 0)
315 #else
316 	    getopt(argc, argv, "DPa:b:dghmnp:t:qv")
317 #endif
318 
319 	    ) != -1) {
320 	rv = 1;
321 
322 	switch (i) {
323 	case 'h' : usage(); cleanexit();
324 	case '?' : case ':' : cmdftp_err(CMDFTP_ERR_OPT, "");
325 	case 'v' : version(); cleanexit();
326 
327 	case 'a' : rv = sscanf(optarg, "%hu", &o.a); break;
328 	case 'p' : rv = sscanf(optarg, "%hu", &o.p); break;
329 	case 'b' : rv = sscanf(optarg, "%u", &o.b); o.b *= 1024; break;
330 	case 't' : rv = sscanf(optarg, "%u", &o.t); break;
331 
332 	case 'q' : o.q = 1; break;
333 	case 'D' : o.D = 1; break;
334 	case 'm' : o.m = 1; break;
335 	case 'n' : o.n = 1; break;
336 	case 'g' : o.g = 1; break;
337 	case 'd' : o.d = 1; break;
338 	case 'P' : o.P = 1; break;
339 	}
340 	if (rv != 1) cmdftp_err(CMDFTP_ERR_OPT, "");
341     }
342 
343     if (optind >= argc) cmdftp_err(CMDFTP_ERR_OPT, "");
344 
345     o.hostname = my_strdup(argv[optind]);
346 
347     env[CMDFTP_ENV_PAGER] = o.g ? 0 : getenv("PAGER");
348     env[CMDFTP_ENV_EDITOR] = getenv("EDITOR");
349     env[CMDFTP_ENV_HOME] = getenv("HOME");
350     env[CMDFTP_ENV_TMPDIR] = getenv("TMPDIR");
351 }
352 
greeting(void)353 void greeting(void) {
354     if (recv_answer(0, 0) != 220)
355 	cmdftp_err(CMDFTP_ERR_SERV, "(greeting)");
356 
357     if (!o.q)
358 	fprintf(stdout, "%s\n", cmd_buffer);
359 }
360 
detect_feats(void)361 void detect_feats(void) {
362     int i; struct line_data d;
363 
364     i = send_command_and_fetch("FEAT", &d, 0);
365     if (!i)
366 	return;
367 
368     if (i >= 300) {
369 	free_lines(&d);
370 	return;
371     }
372 
373     for (i = 0; i < d.count; i++) {
374 	if (strncmp(" MLST", d.lines[i], 5) == 0)
375 	    feat.mlst = 1;
376     }
377 
378     free_lines(&d);
379 }
380 
381 
382 /*********************************/
383 /* LOGIN AND AUTOLOGIN FUNCTIONS */
384 /*********************************/
385 
login_procedure(void)386 void login_procedure(void) {
387     int attempt;
388     user = pass = 0;
389 
390     if (!o.n) {
391 	if (auto_login()) return;
392     }
393 
394     if (!o.m) {
395 	for (attempt = 0; attempt < o.a; attempt++) {
396 	    if (user) free(user); if (pass) free(pass);
397 	    user = pass = 0;
398 	    if (manual_login()) return;
399 	    cmdftp_war(CMDFTP_WAR_LGIN, "");
400 	}
401     }
402 
403     cmdftp_err(CMDFTP_ERR_LGIN, "");
404 }
405 
login(char * user,char * pass)406 int login(char* user, char* pass) {
407     int rv = 0;
408 
409     logging_in = 1;
410     snprintf(cmd_buffer, CMD_BUF_SIZE, "USER %s", user);
411     rv = send_command(cmd_buffer, 0);
412     if (rv == 230) return 1;
413     if (rv != 331) return 0;
414 
415     snprintf(cmd_buffer, CMD_BUF_SIZE, "PASS %s", pass);
416     if (send_command(cmd_buffer, 0) == 230) rv = 1;
417     logging_in = 0;
418     return rv;
419 }
420 
manual_login(void)421 int manual_login(void) {
422     printf("Username: ");
423     if (!(user = readline(0, 1))) return 0;
424 
425     cmdftp_pwd_start(); printf("Password: ");
426     pass = readline(0, 0);
427     cmdftp_pwd_end();
428 
429     if (!pass) return 0;
430     return login(user, pass);
431 }
432 
433 const char *netrc_skeleton = ""
434 "default\n"
435 "login anonymous\n"
436 "password cmdftp@example.com\n";
437 
create_netrc(char * fname)438 static FILE *create_netrc(char *fname)
439 {
440     int fd; int len;
441 
442     if ((fd = creat(fname, 0600)) < 0)
443 	return 0;
444 
445     len = strlen(netrc_skeleton);
446 
447     if (write(fd, netrc_skeleton, len) != len) {
448 	close(fd);
449 	return 0;
450     }
451 
452     if (close(fd) < 0)
453 	return 0;
454 
455     return fopen(fname, "r");
456 }
457 
auto_login(void)458 int auto_login(void) {
459     FILE* f; char* fname; char line[WIDE_LINE_LEN]; char token[WIDE_LINE_LEN];
460     int state, rv;
461     char* rc = ".netrc"; state = rv = 0;
462 
463     if (!env[CMDFTP_ENV_HOME])
464 	return 0;
465 
466     fname = fullpath(env[CMDFTP_ENV_HOME], rc);
467 
468     if (local_file(fname) == FILE_NEXIST)
469 	f = create_netrc(fname);
470     else
471 	f = fopen(fname, "r");
472 
473     free(fname);
474 
475     if (!f)
476 	return 0;
477 
478     while ((fgets(line, WIDE_LINE_LEN, f))) {
479 	char* ptr, *ptr_new, *ptr_tmp; int l;
480 	l = strlen(line); if (line[l - 1] == '\n') line[l - 1] = 0;
481 	if ((ptr = strchr(line, '#')))
482 	    if (ptr == line || ptr[-1] != '\\') *ptr = 0;
483 	ptr = ptr_new = ptr_tmp = line;
484 
485     tokenize:
486 	if ((l = auto_login_next_token(&ptr, token)) == -1) continue;
487 	if (l == 0) {
488 	    if (!state)
489 		{ if (strcmp(o.hostname, token) == 0) state++; goto tokenize; }
490 	    break;
491 	} else if (l == 1) {
492 	    if (!state) { state++; goto tokenize; }
493 	    break;
494 	} else if (l == 2) {
495 	    if (state && !user) user = my_strdup(token); goto tokenize;
496 	} else if (l == 3) {
497 	    if (state && !pass) pass = my_strdup(token); goto tokenize;
498 	}
499     }
500     if (user && !pass) pass = my_strdup("");
501     if (user) rv = login(user, pass);
502     if (!rv) {
503 	if (user) { free(user); user = 0; }
504 	if (pass) { free(pass); pass = 0; }
505     }
506     return rv;
507 }
508 
auto_login_next_token(char ** hay,char * store)509 int auto_login_next_token(char** hay, char* store) {
510 #define AUTO_N_KEYS 4
511     char* keys[] = { "machine", "default", "login", "password" };
512     int keys_len[] = { 7, 7, 5, 8 };
513     char* ptr, *minptr; int i, min;
514 
515     min = -1; minptr = 0;
516 
517     for (i = 0; i < AUTO_N_KEYS; i++)
518 	if ((ptr = strstr(*hay, keys[i])))
519 	    if (!minptr || ptr < minptr) { minptr = ptr; min = i; }
520 
521     if (min != -1) {
522 	*hay = minptr + keys_len[min];
523 	if (min != 1) {
524 	    if (!isspace(**hay)) {
525 		*store = 0;
526 	    } else {
527 		while (isspace(**hay)) (*hay)++;
528 		read_token(hay, store);
529 	    }
530 	}
531     }
532     return min;
533 #undef AUTO_N_KEYS
534 }
535 
read_token(char ** hay,char * store)536 void read_token(char** hay, char* store) {
537     int i, c;
538 
539     for (i = 0; ; i++) {
540 	c = **hay;
541 	if (!c || isspace(c)) break;
542 
543 	if (c == '\\') {
544 	    int n = 0; (*hay)++;
545 	    if (!(c = **hay)) break;
546 
547 	    if (isdigit(c)) {
548 		sscanf(*hay, "%3o%n", (unsigned int*)&c, &n); *hay += n - 1;
549 
550 	    } else if (c == 'x') {
551 		(*hay)++; sscanf(*hay, "%2x%n", (unsigned int*)&c, &n); *hay += n - 1;
552 
553 	    } else {
554 		if (c == 'a') c = '\a'; else if (c == 'b') c = '\b';
555 		else if (c == 'f') c = '\f'; else if (c == 'n') c = '\n';
556 		else if (c == 't') c = '\t'; else if (c == 'v') c = '\v';
557 		else {
558 		    /* get character as if no backslash present, good to escape spaces */
559 		}
560 	    }
561 	}
562 	store[i] = c;
563 	(*hay)++;
564     }
565 
566     store[i] = 0;
567 }
568 
569 
570 /***********************/
571 /* FUNCTION DISPATCHER */
572 /***********************/
573 
574 
dispatch(char * cmd)575 int dispatch(char* cmd) {
576     int idx;
577     char* argv[4];
578 
579     if (!cmd) return 0;
580     split_cmd(cmd, argv);
581 
582     if (*argv[0] == 0) return 1; /* the empty, do nothing command */
583 
584     if ((idx = str_binsearch(argv[0])) == -1)
585 	cmdftp_war(CMDFTP_WAR_BADC, cmd);
586     else if (!cmd_functions[idx](&argv[1]))
587 	cmdftp_war(CMDFTP_WAR_FAIL, "");
588 
589     free_cmd(argv);
590     return 1;
591 }
592 
593 /*************************************************/
594 /* LOCAL MODE SPECIFIC UTILITY FUNCTIONS: local_ */
595 /*************************************************/
596 
local_chdir(char * arg)597 int local_chdir(char* arg) {
598     /* watch return value! */
599     int rv;
600 
601     if ((rv = chdir(arg)) < 0)
602 	cmdftp_war(CMDFTP_WAR_LERR, strerror(errno));
603 
604     return rv;
605 }
606 
local_mkdir(char * arg)607 int local_mkdir(char* arg) {
608     int rv;
609 
610     if (!(rv = (mkdir(arg, 0777) == 0)))
611 	cmdftp_war(CMDFTP_WAR_LERR, strerror(errno));
612 
613     return rv;
614 }
615 
local_rmdir(char * arg)616 int local_rmdir(char* arg) {
617     int rv;
618 
619     if (!(rv = (rmdir(arg) == 0)))
620 	cmdftp_war(CMDFTP_WAR_LERR, strerror(errno));
621 
622     return rv;
623 }
624 
local_getcwd(void)625 char* local_getcwd(void) {
626     return getcwd(cmd_buffer, CMD_BUF_SIZE);
627 }
628 
local_copy(char * target,char * source)629 int local_copy(char* target, char* source) {
630     FILE* t, *s; off_t start_pos, total_size;
631     char* csource; char* op;
632     size_t csource_len;
633     int rv = 0;
634 
635     if (!(s = fopen(source, "rb"))) return 0;
636 
637     if ((total_size = local_size(s)) >= 0 && (t = fopen(target, "wb"))) {
638 
639 	op = "Copying";
640 	csource = clean_fn(source); csource_len = strlen(csource);
641 
642     start_transfer:
643 	start_pos = 0;
644 
645 	if (!o.q)
646 	    print_progress(op, csource, csource_len, start_pos, total_size);
647 
648 	while (start_pos < total_size) {
649 	    int toread, bytes, written;
650 	    toread = ((total_size - start_pos) < o.b) ?
651 		total_size - start_pos : o.b;
652 
653 	    if (!(bytes = fread(buffer, 1, toread, s)))
654 		break;
655 	    if ((written = fwrite(buffer, 1, bytes, t)) != bytes)
656 		break;
657 
658 	    start_pos += bytes;
659 
660 	    if (!o.q)
661 		print_progress(op, csource, csource_len, start_pos, total_size);
662 	}
663 
664 	if (!o.q) fputc('\n', stdout);
665 
666 	if (start_pos < total_size) {
667 	    if (ferror(s)) cmdftp_war(CMDFTP_WAR_READ, source);
668 	    else if (ferror(t)) cmdftp_war(CMDFTP_WAR_WRIT, target);
669 	    else if TRANSFER_INTERRUPTED_CHECK("local copy", 0)
670 	    else { cmdftp_err(CMDFTP_ERR_UNER, strerror(errno)); }
671 
672 	} else {
673 	    rv = 1;
674 	}
675 	fclose(t);
676     }
677     fclose(s);
678     return rv;
679 }
680 
local_move(char * new,char * old)681 int local_move(char* new, char* old) {
682     int rv;
683 
684     if (!(rv = (rename(old, new) == 0)))
685 	cmdftp_war(CMDFTP_WAR_LERR, strerror(errno));
686 
687     return rv;
688 }
689 
local_unlink(char * name)690 int local_unlink(char* name) {
691     int rv;
692 
693     if (!(rv = (unlink(name) == 0)))
694 	cmdftp_war(CMDFTP_WAR_LERR, strerror(errno));
695 
696     return rv;
697 }
698 
local_file(char * name)699 int local_file(char* name) {
700     struct stat buf;
701 
702     if (stat(name, &buf) != 0)
703 	return FILE_NEXIST;
704 
705     return (S_ISREG(buf.st_mode) ? FILE_ISREG :
706 	    S_ISDIR(buf.st_mode) ? FILE_ISDIR :
707 	    FILE_OTHER);
708 }
709 
local_fetch_list(char * filemask,struct list_data * d)710 int local_fetch_list(char* filemask, struct list_data* d) {
711     char* fn_mask[3], *tmpmask;
712     struct dirent* entry;
713     DIR* dir;
714     int rv = 0;
715 
716     if (*filemask == 0) tmpmask = my_strdup("*");
717     else tmpmask = my_strdup(filemask);
718 
719     canonized_fn(fn_mask, tmpmask);
720 
721     if (!(dir = opendir(filemask ? fn_mask[0] : ".")))
722 	goto end_proc;
723 
724     while ((entry = readdir(dir))) {
725 	if (fnmatch(fn_mask[1], entry->d_name, 0) == 0) {
726 	    char* full_name = fullpath(fn_mask[0], entry->d_name);
727 	    store_list(clean_fn(full_name), d);
728 	    free(full_name);
729 	}
730     }
731 
732     closedir(dir); rv = 1;
733 
734  end_proc:
735     free(tmpmask); free_fn(fn_mask);
736     return rv;
737 }
738 
local_fetch_pretty_list(struct list_data * d)739 int local_fetch_pretty_list(struct list_data* d) {
740     int pipe_fd[2]; FILE* target; char line[CMD_BUF_SIZE];
741 
742     if (pipe(pipe_fd) < 0) return 0;
743 
744     if (!cmdftp_execute("/bin/ls -l", 0, -1, pipe_fd[1])) {
745 	close(pipe_fd[0]); close(pipe_fd[1]);
746 	return local_fetch_list("", d);
747     }
748 
749     close(pipe_fd[1]);
750 
751     if (!(target = fdopen(pipe_fd[0], "r")))
752 	{ close(pipe_fd[0]); return 0; }
753 
754     while (fgets(line, CMD_BUF_SIZE, target)) {
755 	size_t len = strlen(line);
756 	if (line[len - 1] == '\n') line[len - 1] = 0;
757 	store_pretty_list(line, d);
758     }
759     fclose(target);
760     return 1;
761 }
762 
local_size(FILE * f)763 off_t local_size(FILE* f) {
764     off_t rv;
765     fseeko(f, 0, SEEK_END); rv = ftello(f); fseeko(f, 0, SEEK_SET);
766     return rv;
767 }
768 
local_print(char * arg)769 int local_print(char* arg) {
770     if (env[CMDFTP_ENV_PAGER])
771 	return cmdftp_execute(env[CMDFTP_ENV_PAGER], arg, -1, -1);
772     else {
773 	FILE* f; int c;
774 	if (!(f = fopen(arg, "r")))
775 	    { cmdftp_war(CMDFTP_WAR_OPEN, arg); return 0; }
776 	while ((c = getc(f)) != EOF) fputc(c, stdout); fputc('\n', stdout);
777 	fclose(f);
778     }
779     return 1;
780 }
781 
local_edit(char * arg)782 int local_edit(char* arg) {
783     return cmdftp_execute(env[CMDFTP_ENV_EDITOR], arg, -1, -1);
784 }
785 
786 /***************************************************/
787 /* REMOTE MODE SPECIFIC UTILITY FUNCTIONS: remote_ */
788 /***************************************************/
789 
remote_chdir(char * arg)790 int remote_chdir(char* arg) {
791     return remote_chdir_aux(arg, 0);
792 }
793 
remote_chdir_aux(char * arg,char suppress_err)794 int remote_chdir_aux(char* arg, char suppress_err) {
795     /* watch the return value! */
796     int rv;
797 
798     snprintf(cmd_buffer, CMD_BUF_SIZE, "CWD %s", arg);
799     if (!(rv = send_command(cmd_buffer, suppress_err)))
800 	return -2;			/* bad err, intr */
801 
802     if (rv == 250) return 0;	/* ok */
803     return -1;			/* fail */
804 }
805 
remote_mkdir(char * arg)806 int remote_mkdir(char* arg) {
807     snprintf(cmd_buffer, CMD_BUF_SIZE, "MKD %s", arg);
808     return (send_command(cmd_buffer, 0) == 257);
809 }
810 
remote_rmdir(char * arg)811 int remote_rmdir(char* arg) {
812     snprintf(cmd_buffer, CMD_BUF_SIZE, "RMD %s", arg);
813     return (send_command(cmd_buffer, 0) == 250);
814 }
815 
remote_getcwd(void)816 char* remote_getcwd(void) {
817     char* start, *end;
818     if (!send_command("PWD", 1)) return 0;
819     if ((end = strrchr(cmd_buffer, '"')) == 0 ||
820 	(start = strchr(cmd_buffer, '"')) == 0) return 0;
821     *end = 0; start++;
822     return start;
823 }
824 
remote_copy(char * target,char * source)825 int remote_copy(char* target, char* source) {
826     FILE* t; char* t_fn;
827     int rv = 0;
828 
829     if (!(t = cmdftp_temp(&t_fn)))
830 	{ cmdftp_war(CMDFTP_WAR_TEMP, t_fn); return 0; }
831 
832     if (download(t, source) && upload(target, t))
833 	rv = 1;
834 
835     fclose(t); unlink(t_fn); free(t_fn);
836     return rv;
837 }
838 
remote_move(char * to,char * from)839 int remote_move(char* to, char* from) {
840     snprintf(cmd_buffer, CMD_BUF_SIZE, "RNFR %s", from);
841     if (send_command(cmd_buffer, 0) != 350) return 0;
842 
843     snprintf(cmd_buffer, CMD_BUF_SIZE, "RNTO %s", to);
844     if (send_command(cmd_buffer, 0) != 250) return 0;
845 
846     return 1;
847 }
848 
remote_unlink(char * arg)849 int remote_unlink(char* arg) {
850     snprintf(cmd_buffer, CMD_BUF_SIZE, "DELE %s", arg);
851     return (send_command(cmd_buffer, 0) == 250);
852 }
853 
remote_file_legacy(char * name)854 static int remote_file_legacy(char* name) {
855     off_t size; int rv;
856 
857     rv = remote_chdir_aux(name, 1);
858 
859     if (rv == 0) { /* ok */
860 	return do_home(CMDFTP_REMOTE) ? FILE_ISDIR : -1;
861     }
862 
863     if (rv == -2)
864 	return -2;
865 
866     if (!send_command("TYPE I", 1))
867 	return 0;
868 
869     if ((size = remote_size(name)) >= 0) {
870 	return FILE_ISREG;
871 
872     } else if (size == -1) {
873 	return FILE_NEXIST;
874 
875     } else /* (size == -2) */ {
876 	return -1;
877     }
878 }
879 
touppercase(char * s)880 static void touppercase(char *s)
881 {
882     while ((*s = toupper(*s)))
883 	s++;
884 }
885 
remote_file(char * name)886 int remote_file(char* name) {
887     int rv; struct line_data d;
888     char *start, *end;
889 
890     if (feat.mlst == 0)
891 	return remote_file_legacy(name);
892 
893     snprintf(cmd_buffer, CMD_BUF_SIZE, "MLST %s", name);
894     rv = send_command_and_fetch(cmd_buffer, &d, 1);
895 
896     if (!rv)
897 	return -2;
898 
899     if (rv >= 300) {
900 	rv = FILE_NEXIST;
901 	goto end_proc;
902     }
903 
904     if (d.count == 0)
905 	return -1;
906 
907     touppercase(d.lines[0]);
908     start = d.lines[0];
909 
910     while (*start == ' ')
911 	start++;
912 
913     if ((end = strchr(start, ' '))) {
914 	*end = '\0';
915     }
916 
917     if (!(start = strstr(start, "TYPE="))) {
918 	rv = -1;
919 	goto end_proc;
920     }
921     start += 5;
922 
923     if (!(end = strchr(start, ';'))) {
924 	rv = -1;
925 	goto end_proc;
926     }
927     *end = '\0';
928 
929     if (strcmp(start, "FILE") == 0) {
930 	rv = FILE_ISREG;
931 	goto end_proc;
932     }
933 
934     if (strcmp(start, "CDIR") == 0 ||
935 	strcmp(start, "DIR") == 0) {
936 	rv = FILE_ISDIR;
937 	goto end_proc;
938     }
939 
940     rv = FILE_OTHER;
941  end_proc:
942     free_lines(&d);
943     return rv;
944 }
945 
remote_fetch_list(char * filemask,struct list_data * d)946 int remote_fetch_list(char* filemask, struct list_data* d) {
947     return remote_fetch_list_aux(filemask, d, 0);
948 }
949 
remote_fetch_pretty_list(struct list_data * d)950 int remote_fetch_pretty_list(struct list_data* d) {
951     return remote_fetch_list_aux(0, d, 1);
952 }
953 
remote_fetch_list_aux(char * filemask,struct list_data * d,char pretty)954 int remote_fetch_list_aux(char* filemask, struct list_data* d, char pretty) {
955     char* tmpmask, *fn_mask[3], *line;
956     int cmdres, rv = 0;
957 
958 #ifdef CMDFTP_LIST_HACK
959     int hack = 0;
960 #endif
961 
962     int port;
963 
964     if (filemask) {
965 	if (*filemask == 0) tmpmask = my_strdup("*");
966 	else tmpmask = my_strdup(filemask);
967 
968 	canonized_fn(fn_mask, tmpmask);
969     }
970 
971  start_transfer:
972 
973     if (!send_command("TYPE A", 0)) goto end_proc;
974 
975     if (!(port = getport())) goto end_proc;
976     if (!(cmdftp_data = cmdftp_connect(port, 4))) goto end_proc;
977 
978 #ifdef CMDFTP_LIST_HACK
979     if (filemask) {
980 	hack = 1;
981 	if (remote_chdir(fn_mask[0]) != 0) goto end_proc;
982     }
983 
984     sprintf(cmd_buffer, pretty ? "LIST" : "NLST");
985 #else
986     if (filemask) {
987 	snprintf(cmd_buffer, CMD_BUF_SIZE, pretty ? "LIST %s" : "NLST %s",
988 		 fn_mask[0]);
989     } else {
990 	sprintf(cmd_buffer, pretty ? "LIST" : "NLST");
991     }
992 #endif /* CMDFTP_LIST_HACK */
993 
994     if ((cmdres = (send_command(cmd_buffer, 1))) != 150 &&
995 	cmdres != 125 && cmdres != 226)
996 	{ close(cmdftp_data); if (cmdres == 450) rv = 1; goto end_proc; }
997 
998     reset_cmd_buffer();
999 
1000     transfer_interrupted = TRAN_INTR_NO;
1001 
1002     while ((line = recv_line(cmdftp_data))) {
1003 	if (!pretty) {
1004 	    char* fn;
1005 	    if (!(fn = strrchr(line, '/'))) fn = line;
1006 	    else fn++;
1007 
1008 	    if (!filemask || fnmatch(fn_mask[1], fn, 0) == 0) {
1009 		char* full_name = filemask ? fullpath(fn_mask[0], fn) : fn;
1010 		store_list(clean_fn(full_name), d);
1011 		if (filemask) free(full_name);
1012 	    }
1013 	} else {
1014 	    store_pretty_list(line, d);
1015 	}
1016     }
1017 
1018     close(cmdftp_data);
1019 
1020     if TRANSFER_INTERRUPTED_CHECK("data transfer", 1)
1021     else {
1022 	rv = 1;
1023 	if (cmdres != 226 && !recv_confirm())
1024 	    cmdftp_war(CMDFTP_WAR_NOCT, "");
1025     }
1026 
1027  end_proc:
1028     if (filemask) { free(tmpmask); free_fn(fn_mask); }
1029 
1030 #ifdef CMDFTP_LIST_HACK
1031     if (hack) {
1032 	rv = (remote_chdir(cwd[CMDFTP_REMOTE]) == 0) && rv;
1033     }
1034 #endif
1035 
1036     return rv;
1037 }
1038 
remote_size(char * filename)1039 off_t remote_size(char* filename) {
1040     off_t size; int rv;
1041     snprintf(cmd_buffer, CMD_BUF_SIZE, "SIZE %s", filename);
1042     if (!(rv = send_command(cmd_buffer, 1))) return -2;
1043     else if (rv != 213) return -1;
1044 
1045     sscanf(cmd_buffer + 4, OFF_FMT, &size);
1046     return size;
1047 }
1048 
remote_print(char * arg)1049 int remote_print(char* arg) {
1050     int c; char* fn; FILE* f; int rv = 0;
1051 
1052     if (!(f = cmdftp_temp(&fn))) { cmdftp_war(CMDFTP_WAR_TEMP, fn); return 0; }
1053 
1054     if (download(f, arg)) {
1055 	if (env[CMDFTP_ENV_PAGER]) {
1056 	    fclose(f); f = 0;
1057 	    rv = cmdftp_execute(env[CMDFTP_ENV_PAGER], fn, -1, -1);
1058 	} else {
1059 	    fseeko(f, 0, SEEK_SET);
1060 	    while ((c = getc(f)) != EOF) fputc(c, stdout); fputc('\n', stdout);
1061 	    rv = 1;
1062 	}
1063     }
1064 
1065     if (f) fclose(f); unlink(fn); free(fn); return rv;
1066 }
1067 
remote_edit(char * arg)1068 int remote_edit(char* arg) {
1069     char* fn; FILE* f; int rv = 0;
1070 
1071     if (!(f = cmdftp_temp(&fn))) { cmdftp_war(CMDFTP_WAR_TEMP, fn); return 0; }
1072 
1073     if (download(f, arg)) {
1074 	fclose(f); f = 0;
1075 	if (cmdftp_execute(env[CMDFTP_ENV_EDITOR], fn, -1, -1)) {
1076 	    if ((f = fopen(fn, "r")) && upload(arg, f)) rv = 1;
1077 	}
1078     }
1079     if (f) fclose(f); unlink(fn); free(fn); return rv;
1080 }
1081 
1082 
1083 /***********************************/
1084 /* COMPOSITE ACTIONS: do_          */
1085 /***********************************/
1086 
do_home(int mode)1087 int do_home(int mode) {
1088     /* home to current directory of mode */
1089 
1090     if (mf[mode][MODE_CHDIR](cwd[mode]) == 0)
1091 	return 1;			/* ok */
1092     else if (mf[mode][MODE_CHDIR](cwd[mode]) == 0)
1093 	return 0;			/* ok, but stop current op */
1094     else
1095 	cmdftp_err(CMDFTP_ERR_PWD, "");
1096 
1097     return 0;			/* never reached */
1098 }
1099 
do_copy_dir(char * target_dir,char * source_mask)1100 int do_copy_dir(char* target_dir, char* source_mask) {
1101     struct list_data d;
1102     int i, rv;
1103 
1104     if ((rv = mf[mode][MODE_FILE](target_dir)) == FILE_NEXIST) {
1105 	if (!mf[mode][MODE_MKDIR](target_dir))
1106 	    return 0;
1107 
1108     } else if (rv != FILE_ISDIR) {
1109 	return 0;
1110     }
1111 
1112     init_list(&d);
1113 
1114     if (!mf[mode][MODE_FETCH_LIST](source_mask, &d)) return 0;
1115     if (d.count == 0) return 1;	/* nothing to do but ok */
1116 
1117     rv = 1;
1118     for (i = 0; i < d.count; i++) {
1119 	char* target; int st, tt;
1120 	target = fullpath(target_dir, d.data[i].basename);
1121 
1122 	if ((tt = mf[mode][MODE_FILE](target)) < 0 ||
1123 	    (st = mf[mode][MODE_FILE](d.data[i].fullname)) < 0) {
1124 	    rv = 0;
1125 	} else if (tt == FILE_OTHER ||
1126 		   (st == FILE_ISDIR && tt == FILE_ISREG) ||
1127 		   (st == FILE_ISREG && tt == FILE_ISDIR) ||
1128 		   (st != FILE_ISREG && st != FILE_ISDIR)) {
1129 
1130 	    cmdftp_war(CMDFTP_WAR_SKIP, d.data[i].fullname);
1131 
1132 	} else if (st == FILE_ISREG) {
1133 	    if (!(rv = mf[mode][MODE_COPY](target, d.data[i].fullname)))
1134 		rv = 0;
1135 
1136 	} else /* (st == FILE_ISDIR) */ {
1137 	    char* new_source = fullpath(d.data[i].fullname, "*");
1138 	    if (!do_copy_dir(target, new_source)) rv = 0;
1139 	    free(new_source);
1140 	}
1141 	free(target);
1142 	if (!rv) break;
1143     }
1144     free_list(&d);
1145     return rv;
1146 }
1147 
do_move_dir(char * target_dir,char * source_mask)1148 int do_move_dir(char* target_dir, char* source_mask) {
1149     struct list_data d;
1150     int i, rv;
1151 
1152     init_list(&d);
1153 
1154     if (!mf[mode][MODE_FETCH_LIST](source_mask, &d)) return 0;
1155     if (d.count == 0) return 1;	/* nothing to do but ok */
1156 
1157     rv = 1;
1158     for (i = 0; i < d.count; i++) {
1159 	char* target; int st, tt;
1160 	target = fullpath(target_dir, d.data[i].basename);
1161 
1162 	if ((tt = mf[mode][MODE_FILE](target)) < 0 ||
1163 	    (st = mf[mode][MODE_FILE](d.data[i].fullname)) < 0) {
1164 	    rv = 0;
1165 
1166 	} else if (tt == FILE_ISDIR ||
1167 		   tt == FILE_OTHER ||
1168 		   (st == FILE_ISDIR && tt == FILE_ISREG) ||
1169 		   (st != FILE_ISREG && st != FILE_ISDIR)) {
1170 
1171 	    cmdftp_war(CMDFTP_WAR_SKIP, d.data[i].fullname);
1172 
1173 	} else {
1174 	    if (!mf[mode][MODE_MOVE](target, d.data[i].fullname))
1175 		rv = 0;
1176 	}
1177 	free(target);
1178 	if (!rv) break;
1179     }
1180     free_list(&d);
1181     return rv;
1182 }
1183 
cmd_cp(char ** paths)1184 int cmd_cp(char** paths) {
1185     struct list_data ld;
1186     char* source[3]; char* target[3];
1187     int source_type, target_type;
1188     int rv = 0;
1189 
1190     if (*paths[0] == 0 || *paths[1] == 0)
1191 	{ cmdftp_war(CMDFTP_WAR_MARG, ""); return 0; }
1192 
1193     canonized_fn(source, paths[0]);
1194     canonized_fn(target, paths[1]);
1195 
1196     init_list(&ld);
1197 
1198     if (!mf[mode][MODE_FETCH_LIST](source[2], &ld)) goto end_proc;
1199 
1200     if (ld.count == 0)
1201 	{ cmdftp_war(CMDFTP_WAR_MASK, paths[0]); rv = 1; goto end_proc; }
1202 
1203     if ((target_type = mf[mode][MODE_FILE](target[2])) < 0) goto end_proc;
1204 
1205     if (ld.count == 1) {
1206 	if ((source_type = mf[mode][MODE_FILE](ld.data[0].fullname)) < 0)
1207 	    goto end_proc;
1208 
1209 	else if (source_type == FILE_ISREG) {
1210 	    /* source is a single regular file */
1211 
1212 	    if (target_type == FILE_NEXIST || target_type == FILE_ISREG) {
1213 		rv = mf[mode][MODE_COPY](target[2], ld.data[0].fullname);
1214 
1215 	    } else if (target_type == FILE_ISDIR) {
1216 		char* new_target = fullpath(target[2], ld.data[0].basename);
1217 		free(target[2]); target[2] = new_target;
1218 		rv = mf[mode][MODE_COPY](target[2], ld.data[0].fullname);
1219 
1220 	    } else {
1221 		cmdftp_war(CMDFTP_WAR_TRG, "exists (not a regfile or a dir)");
1222 	    }
1223 	} else if (source_type == FILE_ISDIR) {
1224 	    /* source is a single directory */
1225 	    if (target_type == FILE_ISDIR) {
1226 		rv = do_copy_dir(target[2], ld.data[0].fullname);
1227 	    } else {
1228 		char* new_source = fullpath(ld.data[0].fullname, "*");
1229 		rv = do_copy_dir(target[2], new_source);
1230 		free(new_source);
1231 	    }
1232 
1233 	} else {
1234 	    cmdftp_war(CMDFTP_WAR_SRC, "not a regular file or a dir");
1235 	}
1236 
1237     } else if (target_type == FILE_ISDIR) {
1238 	/* source is multiple entries, target is an existing directory */
1239 	rv = do_copy_dir(target[2], source[2]);
1240 
1241     } else {
1242 	cmdftp_war(CMDFTP_WAR_TRG, "multiple source needs a dir target");
1243     }
1244 
1245  end_proc:
1246     free_list(&ld);
1247     free_fn(source); free_fn(target);
1248 
1249     return rv;
1250 }
1251 
cmd_mv(char ** paths)1252 int cmd_mv(char** paths) {
1253     struct list_data ld;
1254     char* source[3]; char* target[3];
1255     int source_type, target_type;
1256     int rv = 0;
1257 
1258     if (*paths[0] == 0 || *paths[1] == 0)
1259 	{ cmdftp_war(CMDFTP_WAR_MARG, ""); return 0; }
1260 
1261     canonized_fn(source, paths[0]);
1262     canonized_fn(target, paths[1]);
1263 
1264     init_list(&ld);
1265 
1266     if (!mf[mode][MODE_FETCH_LIST](source[2], &ld)) goto end_proc;
1267 
1268     if (ld.count == 0)
1269 	{ cmdftp_war(CMDFTP_WAR_MASK, paths[0]); rv = 1; goto end_proc; }
1270 
1271     if ((target_type = mf[mode][MODE_FILE](target[2])) < 0) goto end_proc;
1272 
1273     if (ld.count == 1) {
1274 	if ((source_type = mf[mode][MODE_FILE](ld.data[0].fullname)) < 0)
1275 	    goto end_proc;
1276 
1277 	else if (source_type == FILE_ISREG) {
1278 	    if (target_type == FILE_NEXIST || target_type == FILE_ISREG) {
1279 		rv = mf[mode][MODE_MOVE](target[2], ld.data[0].fullname);
1280 
1281 	    } else if (target_type == FILE_ISDIR) {
1282 		char* new_target = fullpath(target[2], ld.data[0].basename);
1283 		free(target[2]); target[2] = new_target;
1284 		rv = mf[mode][MODE_MOVE](target[2], ld.data[0].fullname);
1285 
1286 	    } else {
1287 		cmdftp_war(CMDFTP_WAR_TRG, "exists (not a regfile or a dir)");
1288 	    }
1289 
1290 	} else if (source_type == FILE_ISDIR) {
1291 	    if (target_type == FILE_NEXIST) {
1292 		rv = mf[mode][MODE_MOVE](target[2], ld.data[0].fullname);
1293 
1294 	    } else if (target_type == FILE_ISDIR) {
1295 		rv = do_move_dir(target[2], ld.data[0].fullname);
1296 
1297 	    } else {
1298 		cmdftp_war(CMDFTP_WAR_TRG, "exists (not a dir)");
1299 	    }
1300 
1301 	} else {
1302 	    cmdftp_war(CMDFTP_WAR_SRC, "not a regular file or a dir");
1303 	}
1304 
1305     } else if (target_type == FILE_ISDIR) {
1306 	/* source is multiple entries, target is an existing directory */
1307 	rv = do_move_dir(target[2], source[2]);
1308 
1309     } else {
1310 	cmdftp_war(CMDFTP_WAR_TRG, "multiple source needs a dir target");
1311     }
1312 
1313  end_proc:
1314     free_list(&ld);
1315     free_fn(source); free_fn(target);
1316 
1317     return rv;
1318 }
1319 
1320 
do_ren(char * mask,char * from,char * to)1321 int do_ren(char* mask, char* from, char* to) {
1322     struct list_data ld;
1323     int i, rv;
1324     size_t len_from, len_to;
1325 
1326     len_from = strlen(from); len_to = strlen(to);
1327 
1328     init_list(&ld);
1329 
1330     if (!mf[mode][MODE_FETCH_LIST](mask, &ld)) return 0;
1331     if (ld.count == 0) { cmdftp_war(CMDFTP_WAR_MASK, mask); return 1; }
1332 
1333     rv = 1;
1334     for (i = 0; i < ld.count; i++) {
1335 	char* match = strstr(ld.data[i].basename, from);
1336 
1337 	if (match) {
1338 	    char* new_name; size_t new_len;
1339 
1340 	    new_len = strlen(ld.data[i].dirname) + 1 +
1341 		strlen(ld.data[i].basename) - len_from + len_to;
1342 	    new_name = my_malloc(new_len + 1);
1343 
1344 	    *match = 0;
1345 
1346 	    sprintf(new_name, "%s/%s%s%s", ld.data[i].dirname, ld.data[i].basename,
1347 		    to, match + len_from);
1348 
1349 	    if (!mf[mode][MODE_MOVE](new_name, ld.data[i].fullname))
1350 		rv = 0;
1351 
1352 	    free(new_name);
1353 	}
1354 	if (!rv) break;
1355     }
1356     free_list(&ld);
1357     return rv;
1358 }
1359 
do_setcwd(int mode)1360 void do_setcwd(int mode) {
1361     /* cast between function pointer types is always ok, as soon as
1362        we remember to cast back to the right type before using one. */
1363     char* (*fun_getcwd)(void) = (char* (*)(void))mf[mode][MODE_GETCWD];
1364 
1365     if (cwd[mode]) free(cwd[mode]);
1366 
1367     if ((cwd[mode] = fun_getcwd()))
1368 	cwd[mode] = my_strdup(cwd[mode]);
1369     else if ((cwd[mode] = fun_getcwd()))
1370 	cwd[mode] = my_strdup(cwd[mode]);
1371     else
1372 	cmdftp_err(CMDFTP_ERR_PWD, "");
1373 }
1374 
1375 /**************************************/
1376 /* DISPATCHED COMMANDS                */
1377 /**************************************/
1378 
cmd_quit(char ** argv)1379 int cmd_quit(char** argv) {
1380     argv = 0;
1381     cmdftp_war(CMDFTP_WAR_BYE, "");
1382     cleanexit();
1383     return 0;			/* never reached */
1384 }
1385 
cmd_h(char ** argv)1386 int cmd_h(char** argv) {
1387     char* arg;
1388     unsigned int i;
1389     arg = argv[0];
1390 
1391     if (*arg != 0) cmdftp_war(CMDFTP_WAR_IARG, arg);
1392 
1393     for (i = 0; i < (sizeof(msg_help) / sizeof(char*)); i += 2)
1394 	printf("%-14s %s\n", msg_help[i], msg_help[i + 1]);
1395     return 1;
1396 }
1397 
cmd_l(char ** argv)1398 int cmd_l(char** argv) {
1399     char* arg;
1400     arg = argv[0];
1401 
1402     if (*arg != 0) cmdftp_war(CMDFTP_WAR_IARG, arg);
1403 
1404     mode = CMDFTP_LOCAL;
1405     return 1;
1406 }
1407 
cmd_r(char ** argv)1408 int cmd_r(char** argv) {
1409     char* arg;
1410     arg = argv[0];
1411 
1412     if (*arg != 0) cmdftp_war(CMDFTP_WAR_IARG, arg);
1413 
1414     mode = CMDFTP_REMOTE;
1415     return 1;
1416 }
1417 
cmd_pwd(char ** argv)1418 int cmd_pwd(char** argv) {
1419     char* arg;
1420     arg = argv[0];
1421 
1422     if (*arg != 0) cmdftp_war(CMDFTP_WAR_IARG, arg);
1423 
1424     printf("%s\n", cwd[mode]);
1425     return 1;
1426 }
1427 
cmd_cd(char ** argv)1428 int cmd_cd(char** argv) {
1429     char* arg;
1430     int rv;
1431     arg = argv[0];
1432 
1433     if (*arg == 0) { cmdftp_war(CMDFTP_WAR_MARG, ""); return 0; }
1434 
1435     if ((rv = mf[mode][MODE_CHDIR](arg)) == -2)
1436 	return 0;
1437 
1438     do_setcwd(mode);
1439     return (rv == 0);
1440 }
1441 
cmd_ren(char ** argv)1442 int cmd_ren(char** argv) {
1443     int rv;
1444 
1445     if (*argv[0] == 0 || *argv[1] == 0)
1446 	{ cmdftp_war(CMDFTP_WAR_MARG, ""); return 0; }
1447 
1448     rv = do_ren(argv[0], argv[1], argv[2]);
1449     return rv;
1450 }
1451 
cmd_rm(char ** argv)1452 int cmd_rm(char** argv) {
1453     char* arg;
1454     struct list_data d;
1455     int i, rv = 0;
1456     arg = argv[0];
1457 
1458     if (*arg == 0) { cmdftp_war(CMDFTP_WAR_MARG, ""); return 0; }
1459 
1460     init_list(&d);
1461     if (!mf[mode][MODE_FETCH_LIST](arg, &d)) return 0;
1462     if (d.count == 0) { cmdftp_war(CMDFTP_WAR_MASK, arg); return 1; }
1463 
1464     for (i = 0; i < d.count; i++) {
1465 	int ft;
1466 	if ((ft = mf[mode][MODE_FILE](d.data[i].fullname)) < 0 ||
1467 	    (ft == FILE_ISREG && !mf[mode][MODE_UNLINK](d.data[i].fullname)))
1468 	    break;
1469 	if (i == d.count - 1) rv = 1;
1470     }
1471     free_list(&d);
1472     return rv;
1473 }
1474 
cmd_ls(char ** argv)1475 int cmd_ls(char** argv) {
1476     char* arg;
1477     struct list_data d;
1478     int rv;
1479     arg = argv[0];
1480 
1481     init_list(&d);
1482     if (!mf[mode][MODE_FETCH_LIST](arg, &d)) return 0;
1483     if (d.count == 0) { cmdftp_war(CMDFTP_WAR_MASK, arg); return 1; }
1484 
1485     rv = ls(&d); free_list(&d); return rv;
1486 }
1487 
cmd_dir(char ** argv)1488 int cmd_dir(char** argv) {
1489     char* arg;
1490     struct list_data d;
1491     int rv;
1492     arg = argv[0];
1493 
1494     if (*arg != 0) cmdftp_war(CMDFTP_WAR_IARG, arg);
1495 
1496     init_list(&d);
1497     if (!mf[mode][MODE_FETCH_PRETTY_LIST](&d)) return 0;
1498     if (d.count == 0) { cmdftp_war(CMDFTP_WAR_MASK, ""); return 1; }
1499 
1500     rv = ls(&d); free_list(&d); return rv;
1501 }
1502 
cmd_md(char ** argv)1503 int cmd_md(char** argv) {
1504     char* arg;
1505     arg = argv[0];
1506 
1507     if (*arg == 0) { cmdftp_war(CMDFTP_WAR_MARG, ""); return 0; }
1508     return mf[mode][MODE_MKDIR](arg);
1509 }
1510 
cmd_rd(char ** argv)1511 int cmd_rd(char** argv) {
1512     char* arg;
1513     arg = argv[0];
1514 
1515     if (*arg == 0) { cmdftp_war(CMDFTP_WAR_MARG, ""); return 0; }
1516     return mf[mode][MODE_RMDIR](arg);
1517 }
1518 
1519 
cmd_p(char ** argv)1520 int cmd_p(char** argv) {
1521     char* arg;
1522     arg = argv[0];
1523 
1524     if (*arg == 0) { cmdftp_war(CMDFTP_WAR_MARG, ""); return 0; }
1525     return mf[mode][MODE_PRINT](arg);
1526 }
1527 
cmd_e(char ** argv)1528 int cmd_e(char** argv) {
1529     char* arg;
1530     arg = argv[0];
1531 
1532     if (*arg == 0) { cmdftp_war(CMDFTP_WAR_MARG, ""); return 0; }
1533 
1534     if (!env[CMDFTP_ENV_EDITOR])
1535 	{ cmdftp_war(CMDFTP_WAR_MENV, "EDITOR"); return 0; }
1536 
1537     return mf[mode][MODE_EDIT](arg);
1538 }
1539 
1540 
1541 
1542 /**************************************/
1543 /* UPLOAD AND DOWNLOAD                */
1544 /**************************************/
1545 
1546 
cmd_u(char ** paths)1547 int cmd_u(char** paths) {
1548     char* source_mask;
1549     int rv; int tt; struct list_data ld;
1550 
1551     if (*paths[0] == 0 || *paths[1] == 0)
1552 	{ cmdftp_war(CMDFTP_WAR_MARG, ""); return 0; }
1553 
1554     if ((tt = remote_file(paths[1])) < 0)
1555 	return 0;
1556 
1557     if (tt != FILE_NEXIST && tt != FILE_ISDIR)
1558 	{ cmdftp_war(CMDFTP_WAR_TRG, "exists (not a dir)"); return 0; }
1559 
1560     source_mask = my_strdup(paths[0]);
1561 
1562     init_list(&ld);
1563     if (!local_fetch_list(paths[0], &ld)) return 0;
1564 
1565     if (ld.count == 0)
1566 	{ cmdftp_war(CMDFTP_WAR_MASK, paths[0]); return 1; }
1567 
1568     if (ld.count == 1) {
1569 	int st = local_file(ld.data[0].fullname);
1570 	if (st == FILE_ISDIR && tt == FILE_NEXIST) {
1571 	    free(source_mask);
1572 	    source_mask = fullpath(paths[0], "*");
1573 	}
1574     }
1575 
1576     free_list(&ld);
1577     rv = u_aux(paths[1], source_mask);
1578     free(source_mask);
1579     return rv;
1580 }
1581 
u_aux(char * target_dir,char * source_mask)1582 int u_aux(char* target_dir, char* source_mask) {
1583     struct list_data ld;
1584     char* target[3];
1585     int i, rv;
1586 
1587     if ((rv = remote_file(target_dir)) == FILE_NEXIST) {
1588 	if (!remote_mkdir(target_dir)) return 0;
1589 
1590     } else if (rv != FILE_ISDIR) {
1591 	return 0;
1592     }
1593 
1594     init_list(&ld);
1595 
1596     if (!local_fetch_list(source_mask, &ld)) return 0;
1597     if (ld.count == 0) return 1;
1598 
1599     canonized_fn(target, target_dir);
1600 
1601     rv = 1;
1602     for (i = 0; i < ld.count; i++) {
1603 	int file_type = local_file(ld.data[i].fullname);
1604 
1605 	if (file_type == FILE_ISREG) {
1606 	    FILE* f = fopen(ld.data[i].fullname, "rb");
1607 	    if (!f)
1608 		cmdftp_war(CMDFTP_WAR_OPEN, ld.data[i].fullname);
1609 	    else {
1610 		char* fn;
1611 		fn = fullpath(target[2], ld.data[i].basename);
1612 		rv = upload(fn, f);
1613 		fclose(f); free(fn);
1614 	    }
1615 	} else if (file_type == FILE_ISDIR) {
1616 	    char* tmp_src, *tmp_trg;
1617 
1618 	    tmp_src = fullpath(ld.data[i].fullname, "*");
1619 	    tmp_trg = fullpath(target[2], ld.data[i].basename);
1620 
1621 	    rv = u_aux(tmp_trg, tmp_src);
1622 	    free(tmp_trg); free(tmp_src);
1623 
1624 	} else {
1625 	    cmdftp_war(CMDFTP_WAR_SKIP, ld.data[i].fullname);
1626 	}
1627 	if (!rv) break;
1628     }
1629 
1630     free_list(&ld); free_fn(target);
1631     return rv;
1632 }
1633 
upload(char * target,FILE * source)1634 int upload(char* target, FILE* source) {
1635     off_t start_pos, total_size;
1636     char* ctarget; char* op;
1637     size_t ctarget_len;
1638     int port, rv, cmdres;
1639 
1640     rv = 0;
1641 
1642     op = "Uploading";
1643     ctarget = clean_fn(target); ctarget_len = strlen(ctarget);
1644 
1645  start_transfer:
1646 
1647     if (!send_command("TYPE I", 0)) return 0;
1648     if ((total_size = local_size(source)) < 0) return 0;
1649 
1650     start_pos = 0;
1651 
1652     if (!(port = getport())) return 0;
1653     if (!(cmdftp_data = cmdftp_connect(port, 4))) return 0;
1654 
1655     snprintf(cmd_buffer, CMD_BUF_SIZE, "STOR %s", target);
1656     if ((cmdres = send_command(cmd_buffer, 0)) != 150 &&
1657 	cmdres != 125 && cmdres != 226)
1658 	{ close(cmdftp_data); return 0; }
1659 
1660     if (!o.q)
1661 	print_progress(op, ctarget, ctarget_len, start_pos, total_size);
1662 
1663     transfer_interrupted = TRAN_INTR_NO;
1664 
1665     while (start_pos < total_size) {
1666 	int toread, bytes, written;
1667 	toread = ((total_size - start_pos) < o.b) ?
1668 	    total_size - start_pos : o.b;
1669 
1670 	if (!(bytes = fread(buffer, 1, toread, source)))
1671 	    break;
1672 	if ((written = my_raw_write(buffer, bytes, cmdftp_data)) != bytes)
1673 	    break;
1674 
1675 	start_pos += bytes;
1676 
1677 	if (!o.q)
1678 	    print_progress(op, ctarget, ctarget_len, start_pos, total_size);
1679     }
1680 
1681     if (!o.q) fputc('\n', stdout);
1682 
1683     close(cmdftp_data);
1684 
1685     if (start_pos < total_size) {
1686 	if (ferror(source)) cmdftp_war(CMDFTP_WAR_READ, basename(target));
1687 	else if TRANSFER_INTERRUPTED_CHECK("data transfer", 1)
1688 	else { cmdftp_err(CMDFTP_ERR_UNER, strerror(errno)); }
1689 
1690     } else {
1691 	rv = 1;
1692 	if (cmdres != 226 && !recv_confirm())
1693 	    cmdftp_war(CMDFTP_WAR_NOCT, "");
1694     }
1695 
1696     return rv;
1697 }
1698 
cmd_d(char ** argv)1699 int cmd_d(char** argv) {
1700     return do_d(argv, 0);
1701 }
1702 
cmd_dr(char ** argv)1703 int cmd_dr(char** argv) {
1704     return do_d(argv, 1);
1705 }
1706 
do_d(char ** paths,int resume)1707 int do_d(char** paths, int resume) {
1708     char* source_mask;
1709     int rv; int tt; struct list_data ld;
1710 
1711     if (*paths[0] == 0 || *paths[1] == 0)
1712 	{ cmdftp_war(CMDFTP_WAR_MARG, ""); return 0; }
1713 
1714     if ((tt = local_file(paths[1])) != FILE_NEXIST && tt != FILE_ISDIR)
1715 	{ cmdftp_war(CMDFTP_WAR_TRG, "exists (not a dir)"); return 0; }
1716 
1717     source_mask = my_strdup(paths[0]);
1718 
1719     init_list(&ld);
1720     if (!remote_fetch_list(paths[0], &ld)) return 0;
1721 
1722     if (ld.count == 0)
1723 	{ cmdftp_war(CMDFTP_WAR_MASK, paths[0]); return 1; }
1724 
1725     if (ld.count == 1) {
1726 	int st = remote_file(ld.data[0].fullname);
1727 	if (st < 0) { free_list(&ld); return 0; }
1728 	if (st == FILE_ISDIR && tt == FILE_NEXIST) {
1729 	    free(source_mask);
1730 	    source_mask = fullpath(paths[0], "*");
1731 	}
1732     }
1733 
1734     free_list(&ld);
1735     rv = d_aux(paths[1], source_mask, resume);
1736     free(source_mask);
1737     return rv;
1738 }
1739 
d_aux(char * target_dir,char * source_mask,int resume)1740 int d_aux(char* target_dir, char* source_mask, int resume) {
1741     struct list_data ld;
1742     char* target[3];
1743     int i, rv;
1744 
1745     if ((rv = local_file(target_dir)) == FILE_NEXIST) {
1746 	if (!local_mkdir(target_dir)) return 0;
1747 
1748     } else if (rv != FILE_ISDIR) {
1749 	return 0;
1750     }
1751 
1752     init_list(&ld);
1753 
1754     if (!remote_fetch_list(source_mask, &ld)) return 0;
1755     if (ld.count == 0) return 1;
1756 
1757     canonized_fn(target, target_dir);
1758 
1759     rv = 1;
1760     for (i = 0; i < ld.count; i++) {
1761 	int file_type;
1762 	if ((file_type = remote_file(ld.data[i].fullname)) < 0) {
1763 	    rv = 0;
1764 
1765 	} else if (file_type == FILE_ISREG) {
1766 	    char* fn; FILE* f;
1767 
1768 	    fn = fullpath(target[2], ld.data[i].basename);
1769 	    if (!(f = fopen(fn, resume ? "ab" : "wb")))
1770 		cmdftp_war(CMDFTP_WAR_OPEN, fn);
1771 	    else {
1772 		rv = download(f, ld.data[i].fullname);
1773 		fclose(f);
1774 	    }
1775 	    free(fn);
1776 
1777 	} else if (file_type == FILE_ISDIR) {
1778 	    char* tmp_src, *tmp_trg;
1779 
1780 	    tmp_src = fullpath(ld.data[i].fullname, "*");
1781 	    tmp_trg = fullpath(target[2], ld.data[i].basename);
1782 	    rv = d_aux(tmp_trg, tmp_src, resume);
1783 	    free(tmp_trg); free(tmp_src);
1784 
1785 	} else {
1786 	    cmdftp_war(CMDFTP_WAR_SKIP, ld.data[i].fullname);
1787 	}
1788 
1789 	if (!rv) break;
1790     }
1791 
1792     free_list(&ld); free_fn(target);
1793     return rv;
1794 }
1795 
download(FILE * target,char * source)1796 int download(FILE* target, char* source) {
1797     off_t start_pos, total_size;
1798     char* csource; char* op;
1799     size_t csource_len;
1800     int answer, port, rv, cmdres;
1801 
1802     rv = 0;
1803 
1804     op = "Downloading";
1805     csource = clean_fn(source); csource_len = strlen(csource);
1806 
1807  start_transfer:
1808 
1809     if (!send_command("TYPE I", 0)) return 0;
1810     if ((total_size = remote_size(source)) < 0) return 0;
1811     if ((start_pos = ftello(target)) == -1) return 0;
1812 
1813     if (start_pos > 0) {
1814 	snprintf(cmd_buffer, CMD_BUF_SIZE, "REST " OFF_FMT, start_pos);
1815 	if ((answer = send_command(cmd_buffer, 0)) != 350) {
1816 	    cmdftp_war(CMDFTP_WAR_REST, "");
1817 	    fseeko(target, 0, SEEK_SET); start_pos = 0;
1818 	}
1819     }
1820 
1821     if (!(port = getport())) return 0;
1822     if (!(cmdftp_data = cmdftp_connect(port, 4))) return 0;
1823 
1824     snprintf(cmd_buffer, CMD_BUF_SIZE, "RETR %s", source);
1825     if ((cmdres = send_command(cmd_buffer, 0)) != 150 &&
1826 	cmdres != 125 && cmdres != 226)
1827 	{ close(cmdftp_data); return 0; }
1828 
1829     if (!o.q)
1830 	print_progress(op, csource, csource_len, start_pos, total_size);
1831 
1832     transfer_interrupted = TRAN_INTR_NO;
1833 
1834     while (start_pos < total_size) {
1835 	int bytes, toread, written;
1836 	toread = ((total_size - start_pos) < o.b) ?
1837 	    total_size - start_pos : o.b;
1838 
1839 	if ((bytes = my_raw_read(buffer, toread, cmdftp_data)) <= 0) break;
1840 	if ((written = fwrite(buffer, 1, bytes, target)) != bytes) break;
1841 
1842 	start_pos += bytes;
1843 
1844 	if (!o.q)
1845 	    print_progress(op, csource, csource_len, start_pos, total_size);
1846     }
1847 
1848     if (!o.q) fputc('\n', stdout);
1849 
1850     close(cmdftp_data);
1851 
1852     if (start_pos < total_size) {
1853 	if (ferror(target))
1854 	    cmdftp_war(CMDFTP_WAR_WRIT, basename(source));
1855 	else if TRANSFER_INTERRUPTED_CHECK("data transfer", 1)
1856 	else { cmdftp_err(CMDFTP_ERR_UNER, strerror(errno)); }
1857 
1858     } else {
1859 	rv = 1;
1860 	if (cmdres != 226 && !recv_confirm())
1861 	    cmdftp_war(CMDFTP_WAR_NOCT, "");
1862     }
1863 
1864     return rv;
1865 }
1866 
1867 /***********/
1868 /* listing */
1869 /***********/
1870 
ls(struct list_data * d)1871 int ls(struct list_data* d) {
1872     FILE* target; int pipe_fd[2];
1873     int i, rv = 0;
1874 
1875     if (env[CMDFTP_ENV_PAGER]) {
1876 	if (pipe(pipe_fd) < 0) return 0;
1877 	if (!(target = fdopen(pipe_fd[1], "w")))
1878 	    { close(pipe_fd[0]); close(pipe_fd[1]); return 0; }
1879     } else target = stdout;
1880 
1881     for (i = 0; i < d->count; i++)
1882 	fprintf(target, "%s\n", d->data[i].fullname);
1883 
1884     if (!env[CMDFTP_ENV_PAGER]) return 1;
1885 
1886     fclose(target);
1887     rv = cmdftp_execute(env[CMDFTP_ENV_PAGER], 0, pipe_fd[0], -1);
1888 
1889     close(pipe_fd[0]);
1890     return rv;
1891 }
1892 
1893 /*****************************/
1894 /* NETWORK RELATED FUNCTIONS */
1895 /*****************************/
1896 
1897 
cmdftp_connect(int port,int tos)1898 int cmdftp_connect(int port, int tos) {
1899     struct sockaddr_in address; int s;
1900     socklen_t slen; slen = sizeof(tos);
1901     memset(&address, 0, sizeof(address));
1902 
1903     if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0 ||
1904 	setsockopt(s, IPPROTO_IP, IP_TOS, &tos, slen) < 0)
1905 	return 0;
1906 
1907     address.sin_family = AF_INET;
1908     address.sin_port = htons((unsigned short)port);
1909 
1910     if (!server)
1911 	if (!(server = gethostbyname(o.hostname))) return 0;
1912 
1913     memcpy(&address.sin_addr.s_addr, server->h_addr, server->h_length);
1914 
1915     if (connect(s, (struct sockaddr*) &address, sizeof(address)) == -1)
1916 	return 0;
1917 
1918     return s;
1919 }
1920 
cmdftp_reconnect(void)1921 void cmdftp_reconnect(void) {
1922     if (!(cmdftp_control = cmdftp_connect(o.p, 8)))
1923 	cmdftp_err(CMDFTP_ERR_CONN, "");
1924     greeting();
1925     if (!logging_in) {
1926 	if (!login(user, pass)) cmdftp_err(CMDFTP_ERR_LGIN, "");
1927 	if (cwd[CMDFTP_LOCAL])
1928 	    do_home(CMDFTP_LOCAL);
1929 	if (cwd[CMDFTP_REMOTE])
1930 	    do_home(CMDFTP_REMOTE);
1931     }
1932 }
1933 
my_raw_read(char * buf,size_t n,int sc)1934 ssize_t my_raw_read(char* buf, size_t n, int sc) {
1935     ssize_t rv;
1936 
1937     alarm(o.t);
1938     rv = recv(sc, buf, n, 0);
1939     alarm(0);
1940 
1941     return rv;
1942 }
1943 
my_raw_write(char * buf,size_t n,int sc)1944 ssize_t my_raw_write(char* buf, size_t n, int sc) {
1945     ssize_t rv;
1946 
1947     alarm(o.t);
1948     rv = send(sc, buf, n, 0);
1949     alarm(0);
1950 
1951     return rv;
1952 }
1953 
send_command(char * cmd,char suppress_err)1954 int send_command(char* cmd, char suppress_err) {
1955     char* s; size_t len, rv = 0;
1956 
1957     s = my_malloc((len = strlen(cmd) + 2) + 1);
1958     sprintf(s, "%s\r\n", cmd);
1959 
1960  start_transfer:
1961     transfer_interrupted = TRAN_INTR_NO;
1962     if (o.D)
1963 	fprintf(stderr, "DEBUG command: %s\n", s);
1964 
1965     if (my_raw_write(s, len, cmdftp_control) < 0) {
1966 	s[len - 2] = 0;
1967 	if TRANSFER_INTERRUPTED_CHECK(s, 0)
1968     } else {
1969 	s[len - 2] = 0;
1970 	if (!(rv = recv_answer(0, suppress_err))) {
1971 	    if TRANSFER_INTERRUPTED_CHECK(s, 1)
1972         }
1973     }
1974     free(s);
1975     return rv;
1976 }
1977 
send_command_and_fetch(char * cmd,struct line_data * d,char suppress_err)1978 int send_command_and_fetch(char* cmd, struct line_data* d,
1979 			   char suppress_err)
1980 {
1981     char* s; size_t len, rv = 0;
1982 
1983     init_lines(d);
1984 
1985     s = my_malloc((len = strlen(cmd) + 2) + 1);
1986     sprintf(s, "%s\r\n", cmd);
1987 
1988  start_transfer:
1989     transfer_interrupted = TRAN_INTR_NO;
1990     if (o.D)
1991 	fprintf(stderr, "DEBUG command: %s\n", s);
1992 
1993     if (my_raw_write(s, len, cmdftp_control) < 0) {
1994 	s[len - 2] = 0;
1995 	if TRANSFER_INTERRUPTED_CHECK(s, 0)
1996     } else {
1997 	s[len - 2] = 0;
1998 	if (!(rv = recv_answer(d, suppress_err))) {
1999 	    if TRANSFER_INTERRUPTED_CHECK(s, 1)
2000         }
2001     }
2002     free(s);
2003     return rv;
2004 }
2005 
reset_cmd_buffer(void)2006 void reset_cmd_buffer(void) {
2007     cmd_ptr = cmd_buffer + CMD_BUF_SIZE;
2008 }
2009 
recv_line_next(char * from)2010 static char *recv_line_next(char *from)
2011 {
2012     char *new_ptr;
2013 
2014     if ((new_ptr = strchr(from + 1, '\n'))) {
2015 	if (new_ptr[-1] == '\r')
2016 	    new_ptr--;
2017 
2018     } else if (!(new_ptr = strchr(from + 1, '\r'))) {
2019 	new_ptr = cmd_buffer + CMD_BUF_SIZE;
2020     }
2021     return new_ptr;
2022 }
2023 
recv_line(int sc)2024 char* recv_line(int sc) {
2025     char* new_ptr;
2026     ptrdiff_t n; size_t len;
2027 
2028     if (*cmd_ptr == 0) {
2029 	ssize_t bytes = my_raw_read(cmd_buffer, CMD_BUF_SIZE, sc);
2030 	if (bytes <= 0)
2031 	    return 0;
2032 
2033 	cmd_buffer[bytes] = 0;
2034 	cmd_ptr = cmd_buffer;
2035 	if (*cmd_ptr == '\n')
2036 	    cmd_ptr++;
2037     }
2038 
2039     new_ptr = recv_line_next(cmd_ptr + 1);
2040     n = new_ptr - cmd_ptr;
2041 
2042     strncpy(cmd_line, cmd_ptr, n); cmd_line[n] = 0;
2043     cmd_ptr = new_ptr;
2044 
2045     if (cmd_ptr == cmd_buffer + CMD_BUF_SIZE) {
2046 	ssize_t bytes = my_raw_read(cmd_buffer, CMD_BUF_SIZE, sc);
2047 	if (bytes <= 0) return 0;
2048 	cmd_buffer[bytes] = 0;
2049 
2050 	cmd_ptr = cmd_buffer;
2051 
2052 	new_ptr = recv_line_next(cmd_ptr + 1);
2053 	n = new_ptr - cmd_ptr; len = strlen(cmd_line);
2054 
2055 	strncat(cmd_line, cmd_ptr, n); cmd_line[len + n] = 0;
2056 	cmd_ptr = new_ptr;
2057     }
2058 
2059     if (*cmd_ptr == '\r' && cmd_ptr[1] == '\n')
2060 	cmd_ptr += 2;
2061     else if (*cmd_ptr == '\n')
2062 	cmd_ptr++;
2063 
2064     return cmd_line;
2065 }
2066 
recv_confirm(void)2067 int recv_confirm(void) {
2068     int rv = 0;
2069 
2070     if (recv_answer(0, 0) == 226) rv = 1;
2071     else if TRANSFER_INTERRUPTED_CHECK("recv_confirm", 1);
2072 
2073  start_transfer:
2074     return rv;
2075 }
2076 
recv_answer(struct line_data * d,char suppress_err)2077 int recv_answer(struct line_data* d, char suppress_err) {
2078     int len; char* answer; int code;
2079 
2080     reset_cmd_buffer();
2081 
2082     answer = recv_line(cmdftp_control);
2083     if (!answer || (len = strlen(answer)) < 4)
2084 	return 0;
2085 
2086     if (o.D)
2087 	fprintf(stderr, "DEBUG answer:  %s\n", answer);
2088 
2089     while (*cmd_ptr != 0 || len < 4 ||
2090 	   !isdigit(answer[0]) || !isdigit(answer[1]) || !isdigit(answer[2]) ||
2091 	   answer[3] == '-') {
2092 
2093 	if (!(answer = recv_line(cmdftp_control)))
2094 	    return 0;
2095 
2096 	len = strlen(answer);
2097 
2098 	if (d && *cmd_ptr != 0)
2099 	    store_line(answer, d);
2100     }
2101 
2102     if (sscanf(answer, "%i", &code) != 1)
2103 	return 0;
2104 
2105     if (code >= 400 && !suppress_err) {
2106 	cmdftp_war(CMDFTP_WAR_RERR, answer);
2107     }
2108 
2109     return code;
2110 }
2111 
getport()2112 int getport() {
2113     unsigned int b[6];
2114     int i, answer, len; char* port_str;
2115 
2116     if ((answer = send_command("PASV", 0)) != 227) return 0;
2117 
2118     port_str = cmd_buffer + 4;
2119     len = strlen(port_str += 4);
2120 
2121     for (i = 0; i < len; i++)
2122 	if (!isdigit(port_str[i])) port_str[i] = ' ';
2123 
2124     if (sscanf(port_str, "%u %u %u %u %u %u",
2125 	       b, b + 1, b + 2, b + 3, b + 4, b + 5) != 6)
2126 	return 0;
2127 
2128     return b[4] * 256 + b[5];
2129 }
2130 
2131 
2132 
2133 /*******************************/
2134 /* MEMORY AND STRING FUNCTIONS */
2135 /*******************************/
2136 
2137 
my_malloc(size_t s)2138 void* my_malloc(size_t s) {
2139     void* rv;
2140     if (!(rv = malloc(s))) cmdftp_err(CMDFTP_ERR_HEAP, "");
2141     return rv;
2142 }
2143 
my_realloc(void * ptr,size_t s)2144 void* my_realloc(void* ptr, size_t s) {
2145     void* rv;
2146     if (!(rv = realloc(ptr, s))) cmdftp_err(CMDFTP_ERR_HEAP, "");
2147     return rv;
2148 }
2149 
2150 #if !HAVE_STRDUP
2151 
strdup(char * s)2152 char* strdup(char* s) {
2153     char* rv = malloc(strlen(s) + 1);
2154     strcpy(rv, s);
2155     return rv;
2156 }
2157 
2158 #endif
2159 
my_strdup(char * s)2160 char* my_strdup(char* s) {
2161     char* rv;
2162     if (!(rv = strdup(s))) cmdftp_err(CMDFTP_ERR_HEAP, "");
2163     return rv;
2164 }
2165 
2166 
2167 /***************************/
2168 /* OTHER UTILITY FUNCTIONS */
2169 /***************************/
2170 
cleanexit(void)2171 void cleanexit(void) {
2172     exit(fclose(stdout) == 0 ? 0 : -1);
2173 }
2174 
split_cmd(char * cmd,char ** argv)2175 void split_cmd(char* cmd, char** argv) {
2176     size_t len; int i;
2177     len = strlen(cmd);
2178 
2179     for (i = 0; i < 4; i++) {
2180 	argv[i] = my_malloc(len + 1);
2181 	while (isspace(*cmd)) cmd++;
2182 	read_token(&cmd, argv[i]);
2183     }
2184 }
2185 
free_cmd(char ** argv)2186 void free_cmd(char** argv) {
2187     int i;
2188 
2189     for (i = 0; i < 4; i++) {
2190 	if (argv[i]) {
2191 	    free(argv[i]); argv[i] = 0;
2192 	}
2193     }
2194 }
2195 
str_binsearch(char * key)2196 int str_binsearch(char* key) {
2197     int low, high, mid, chk;
2198 
2199     low = 0; high = N_COMMANDS - 1;
2200 
2201     while (low <= high) {
2202 	mid = (low + high) / 2;
2203 	if ((chk = strcmp(key, commands[mid])) > 0) low = mid + 1;
2204 	else if (chk < 0) high = mid - 1;
2205 	else return mid;
2206     }
2207 
2208     return -1;
2209 }
2210 
readline(int enable_tab,int enable_echo)2211 char* readline(int enable_tab, int enable_echo) {
2212     char* ptr; int c;
2213 
2214     ptr = cmd_userinput; memset(cmd_userinput, 0, sizeof(cmd_userinput));
2215     cmdftp_raw_mode();
2216 
2217     while (1) {
2218 	c = fgetc(stdin);
2219 	if (c == '\n')
2220 	    { *ptr = 0; fputc('\n', stdout); break; }
2221 	else if ((c == '\t') && (enable_tab) &&
2222 		 (ptr - cmd_userinput) < (CMD_BUF_SIZE - 10))
2223 	    { readline_tab(); ptr = cmd_userinput + strlen(cmd_userinput); }
2224 	else if (c == cmdftp_termios.c_cc[VERASE] || c == 8)
2225 	    readline_bs(&ptr, enable_echo);
2226 	else if ((c >= 32) && (c <= 127)) {
2227 	    if ((ptr - cmd_userinput) < (CMD_BUF_SIZE - 10))
2228 		{ *ptr++ = c; if (enable_echo) fputc(c, stdout); }
2229 	}
2230 	else if (c == EOF) break;
2231     }
2232     cmdftp_canon_mode();
2233     return (c != EOF) ? my_strdup(cmd_userinput) : 0;
2234 }
2235 
readline_bs(char ** ptr,int enable_echo)2236 void readline_bs(char** ptr, int enable_echo) {
2237     if (*ptr > cmd_userinput) {
2238 	*(--(*ptr)) = 0;
2239 	if (enable_echo) fprintf(stdout, "\b \b");
2240     }
2241 }
2242 
readline_tab(void)2243 void readline_tab(void) {
2244     char* argv[4];
2245     char* mask, *completion; struct list_data ld;
2246     int c, i, j, lc_mode, len, len_buffer;
2247 
2248     split_cmd(cmd_userinput, argv);
2249     if (!*argv[1])
2250 	{ free_cmd(argv); return; }
2251 
2252     mask = 0;
2253     c = i = j = len = len_buffer = 0;
2254 
2255     init_list(&ld);
2256 
2257     if (strcmp(argv[0], "d") == 0 || strcmp(argv[0], "dr") == 0) {
2258 	lc_mode = *argv[2] ? CMDFTP_LOCAL : CMDFTP_REMOTE;
2259     } else if (strcmp(argv[0], "u") == 0) {
2260 	lc_mode = *argv[2] ? CMDFTP_REMOTE : CMDFTP_LOCAL;
2261     } else lc_mode = mode;
2262 
2263     if (*(mask = argv[2]) != '\0') {
2264 	char* tmp; tmp = escape_string(argv[1]);
2265 	free(argv[1]); argv[1] = tmp;
2266 
2267     } else {
2268 	mask = argv[1];
2269     }
2270 
2271     strcat(mask, "*");
2272     len_buffer = strlen(mask) - 1;
2273 
2274     if (!mf[lc_mode][MODE_FETCH_LIST](mask, &ld) || ld.count == 0) goto endtab;
2275     escape_list(&ld);
2276     len = strlen(ld.data[0].escaped_fullname);
2277 
2278     for (i = 0; i < len; i++) {
2279 	c = ld.data[0].escaped_fullname[i];
2280 	for (j = 1; j < ld.count; j++) {
2281 	    if (ld.data[j].escaped_fullname[i] != c) goto endcommon;
2282 	}
2283     }
2284 
2285  endcommon:
2286 
2287  endtab:
2288     if (ld.count) {
2289 	completion = my_malloc(i + 2);
2290 	strncpy(completion, ld.data[0].escaped_fullname, i);
2291 
2292 	if (ld.count == 1 &&
2293 	    mf[lc_mode][MODE_FILE](ld.data[0].fullname) == FILE_ISDIR)
2294 	    completion[i++] = '/';
2295 
2296 	completion[i] = 0;
2297 
2298     } else {
2299 	completion = my_malloc(len_buffer + 1);
2300 	strncpy(completion, mask, len_buffer);
2301 	completion[len_buffer] = 0;
2302     }
2303 
2304     snprintf(cmd_userinput, CMD_BUF_SIZE, "%s%s%s%s%s", argv[0],
2305 	     *argv[1] ? " " : "", *argv[2] ? argv[1] : "", *argv[2] ? " " : "",
2306 	     completion);
2307     free(completion);
2308 
2309     if (ld.count > 1) {
2310 	fputc('\n', stdout);
2311 	for (j = 0; j < ld.count; j++) {
2312 	    printf("%s\n", ld.data[j].escaped_fullname);
2313 	}
2314     } else {
2315 	fputc('\r', stdout);
2316     }
2317 
2318     print_prompt(); printf("%s", cmd_userinput);
2319 
2320     free_list(&ld);
2321     free_cmd(argv);
2322 }
2323 
cmdftp_pwd_start(void)2324 void cmdftp_pwd_start(void) {
2325     cmdftp_termios.c_lflag &= ~ECHO; tcsetattr(0, TCSANOW, &cmdftp_termios);
2326 }
2327 
cmdftp_pwd_end(void)2328 void cmdftp_pwd_end(void) {
2329     cmdftp_termios.c_lflag |= ECHO; tcsetattr(0, TCSANOW, &cmdftp_termios);
2330 }
2331 
cmdftp_raw_mode(void)2332 void cmdftp_raw_mode(void) {
2333     cmdftp_termios.c_lflag &= ~ICANON;
2334     cmdftp_termios.c_lflag &= ~ECHO;
2335     cmdftp_termios.c_cc[VMIN] = 1;
2336     cmdftp_termios.c_cc[VTIME] = 0;
2337     tcsetattr(0, TCSANOW, &cmdftp_termios);
2338 }
2339 
cmdftp_canon_mode(void)2340 void cmdftp_canon_mode(void) {
2341     cmdftp_termios.c_lflag |= ICANON; cmdftp_termios.c_lflag |= ECHO;
2342     tcsetattr(0, TCSANOW, &cmdftp_termios);
2343 }
2344 
cmdftp_execute(char * p1,char * p2,int read_fd,int write_fd)2345 int cmdftp_execute(char* p1, char* p2, int read_fd, int write_fd) {
2346     char** argv = 0; char* argptr;
2347     pid_t pid; int status, rv, count;
2348     rv = count = 0;
2349 
2350     argptr = p1 = my_strdup(p1);
2351 
2352     do {
2353 	argv = my_realloc(argv, sizeof(char*) * (count + 1));
2354 	argv[count++] = argptr;
2355 	argptr = strchr(argptr + 1, ' ');
2356 
2357 	if (argptr) *argptr++ = 0;
2358 
2359     } while (argptr);
2360 
2361     count += (p2 ? 2 : 1); argv = my_realloc(argv, sizeof(char*) * count);
2362     argv[count - 1] = 0; if (p2) argv[count - 2] = p2;
2363 
2364     if ((pid = fork()) < 0) goto end_proc;
2365     else if (pid > 0) {
2366 	if (waitpid(pid, &status, 0) > 0) {
2367 	    if (WIFEXITED(status) && (WEXITSTATUS(status) == 0)) rv = 1;
2368 	}
2369     } else {
2370 	if (read_fd != -1) { close(0); dup(read_fd); close(read_fd); }
2371 	if (write_fd != -1) { close(1); dup(write_fd); close(write_fd); }
2372 	execvp(argv[0], argv);
2373 	exit(-1);
2374     }
2375 
2376  end_proc:
2377     free(p1); free(argv);
2378     return rv;
2379 }
2380 
2381 /* temporary file handling */
2382 
2383 char* cmdftp_temp_fn;
2384 int cmdftp_temp_fn_len;
2385 
is_good_tmpdir(char * candidate)2386 int is_good_tmpdir(char* candidate) {
2387     struct stat buf;
2388 
2389     if (stat(candidate, &buf) != 0)
2390 	return 0;
2391 
2392     return (S_ISDIR(buf.st_mode) && (buf.st_mode & S_IRWXU));
2393 }
2394 
init_temp(void)2395 void init_temp(void) {
2396     if (env[CMDFTP_ENV_TMPDIR] && is_good_tmpdir(env[CMDFTP_ENV_TMPDIR])) {
2397 	cmdftp_temp_fn = fullpath(env[CMDFTP_ENV_TMPDIR], "cmdftpXXXXXX");
2398 
2399 #ifdef P_tmpdir
2400     } else if (is_good_tmpdir(P_tmpdir)) {
2401 	cmdftp_temp_fn = fullpath(P_tmpdir, "cmdftpXXXXXX");
2402 #endif
2403 
2404     } else if (is_good_tmpdir("/tmp")) {
2405 	cmdftp_temp_fn = fullpath("/tmp", "cmdftpXXXXXX");
2406 
2407     } else {
2408 	cmdftp_err(CMDFTP_ERR_TMPD, "");
2409     }
2410 
2411     cmdftp_temp_fn_len = strlen(cmdftp_temp_fn);
2412 }
2413 
2414 #if !HAVE_MKSTEMP
2415 
mkstemp(char * template)2416 int mkstemp(char* template) {
2417 #define MKSTEMP_ATTEMPTS 10
2418     int rv, attempt; size_t len; char *ptr; off_t n;
2419     char ascii[] =
2420 	"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2421 
2422     len = strlen(template);	/* must be >= 6 */
2423     ptr = template + len - 6;     /* point to XXXXXX part */
2424     n = *((off_t*)o.hostname);	/* a crazy thing */
2425     if (n < 0) n = -n;
2426 
2427     for (attempt = 0; attempt < MKSTEMP_ATTEMPTS; attempt++) {
2428 	int i;
2429 	for (i = 0; i < 6; i++) {
2430 	    ptr[i] = ascii[n % (sizeof(ascii) - 1)]; n /= sizeof(ascii) - 1;
2431 	}
2432 	rv = open(template, O_RDWR | O_CREAT | O_EXCL, 0600);
2433 	if (rv >= 0 || errno != EEXIST) return rv;
2434     }
2435     errno = EEXIST;
2436     return -1;
2437 #undef MKSTEMP_ATTEMPTS
2438 }
2439 
2440 #endif
2441 
cmdftp_temp(char ** fn)2442 FILE* cmdftp_temp(char** fn) {
2443     int i; FILE* rv;
2444 
2445     *fn = 0;
2446 
2447     for (i = cmdftp_temp_fn_len - 6; i < cmdftp_temp_fn_len; i++)
2448 	cmdftp_temp_fn[i] = 'X';
2449 
2450     if ((i = mkstemp(cmdftp_temp_fn)) < 0) return 0;
2451     if (!(rv = fdopen(i, "r+"))) return 0;
2452     *fn = my_strdup(cmdftp_temp_fn);
2453     return rv;
2454 }
2455 
canonized_fn(char * des[3],char * arg)2456 void canonized_fn(char* des[3], char* arg) {
2457     char* tmpmask;
2458 
2459     tmpmask = my_strdup(arg);
2460     des[0] = my_strdup(dirname(tmpmask));
2461     free(tmpmask);
2462 
2463     tmpmask = my_strdup(arg);
2464     des[1] = my_strdup(basename(tmpmask));
2465     free(tmpmask);
2466 
2467     des[2] = fullpath(des[0], des[1]);
2468 }
2469 
2470 /*****************************************************************************/
2471 
free_fn(char * des[3])2472 void free_fn(char* des[3]) {
2473     int i;
2474     for (i = 0; i < 3; i++) free(des[i]);
2475 }
2476 
2477 /*****************************************************************************/
2478 
clean_fn(char * fn)2479 char* clean_fn(char* fn) {
2480     if (strncmp(fn, "./", 2) == 0) return fn + 2;
2481     else if(strncmp(fn, "//", 2) == 0) return fn + 1;
2482     else return fn;
2483 }
2484 
2485 /*****************************************************************************/
2486 
fullpath(char * s,char * q)2487 char* fullpath(char* s, char* q) {
2488     char* rv = my_malloc(strlen(s) + strlen(q) + 2);
2489     sprintf(rv, "%s/%s", s, q);
2490     return rv;
2491 }
2492 
2493 /********************/
2494 /* SIGNAL FUNCTIONS */
2495 /********************/
2496 
init_signals(void)2497 void init_signals(void) {
2498     memset(&sa, 0, sizeof(sa));
2499     sa.sa_handler = SIG_IGN; sigaction(SIGPIPE, &sa, 0);
2500     sa.sa_handler = &handler_INT; sigaction(SIGINT, &sa, 0);
2501     sa.sa_handler = &handler_ALRM; sigaction(SIGALRM, &sa, 0);
2502 }
2503 
handler_INT(int i)2504 void handler_INT(int i) {
2505     i = 0;
2506 
2507     if (transfer_interrupted == TRAN_INTR_INT) {
2508 	cmdftp_err(CMDFTP_ERR_INTR, "");
2509 
2510     } else {
2511 	transfer_interrupted = TRAN_INTR_INT;
2512     }
2513 }
2514 
handler_ALRM(int i)2515 void handler_ALRM(int i) {
2516     i = 0;
2517     transfer_interrupted = TRAN_INTR_ALRM;
2518 }
2519 
2520 /******************************/
2521 /* STRUCT LINE_DATA FUNCTIONS */
2522 /******************************/
2523 
2524 
init_lines(struct line_data * d)2525 void init_lines(struct line_data* d) {
2526     d->count = 0; d->lines = 0;
2527 }
2528 
init_list(struct list_data * d)2529 void init_list(struct list_data* d) {
2530     d->count = 0; d->data = 0;
2531 }
2532 
escape_list(struct list_data * d)2533 void escape_list(struct list_data* d) {
2534     int i;
2535     for (i = 0; i < d->count; i++) {
2536 	d->data[i].escaped_fullname =
2537 	    escape_string(d->data[i].fullname);
2538     }
2539 }
2540 
escape_string(char * filestring)2541 char* escape_string(char* filestring) {
2542     char* src, *dst, *fs_new;
2543     size_t len;
2544 
2545     len = strlen(src = filestring);
2546     dst = fs_new = my_malloc(len * 2 + 1);
2547 
2548     do {
2549 	if (*src == '\\') {
2550 	    *dst++ = '\\';
2551 	} else if (*src == ' ') {
2552 	    *dst++ = '\\';
2553 	}
2554     } while ((*dst++ = *src++));
2555 
2556     return fs_new;
2557 }
2558 
store_line(char * line,struct line_data * d)2559 void store_line(char* line, struct line_data* d) {
2560     int i = d->count;
2561     d->lines = my_realloc(d->lines, sizeof(char*) * (++d->count));
2562     d->lines[i] = my_malloc(CMD_BUF_SIZE - 9);
2563     strncpy(d->lines[i], line, CMD_BUF_SIZE - 10);
2564     d->lines[i][CMD_BUF_SIZE - 10] = 0;
2565 }
2566 
store_list(char * fullname,struct list_data * d)2567 void store_list(char* fullname, struct list_data* d) {
2568     int i; char* base;
2569     base = basename(fullname);
2570 
2571     if ((o.d && (strcmp(base, ".") == 0 || strcmp(base, "..") == 0)) ||
2572 	(!o.d && *base == '.')) return;
2573 
2574     i = d->count;
2575     d->data = my_realloc(d->data, sizeof(struct list_entry) * (++d->count));
2576     d->data[i].escaped_fullname = 0;
2577     d->data[i].fullname = my_strdup(fullname);
2578     d->data[i].basename = my_strdup(base);
2579     d->data[i].dirname = my_strdup(dirname(fullname));
2580 }
2581 
store_pretty_list(char * fullname,struct list_data * d)2582 void store_pretty_list(char* fullname, struct list_data* d) {
2583     int i = d->count;
2584     d->data = my_realloc(d->data, sizeof(struct list_entry) * (++d->count));
2585     d->data[i].escaped_fullname = 0;
2586     d->data[i].fullname = my_strdup(fullname);
2587     d->data[i].basename = d->data[i].dirname = 0;
2588 }
2589 
free_lines(struct line_data * d)2590 void free_lines(struct line_data* d) {
2591     int i;
2592     if (d->lines) {
2593 	for (i = 0; i < d->count; i++) free(d->lines[i]);
2594 	free(d->lines); d->lines = 0;
2595     }
2596     d->count = 0;
2597 }
2598 
free_list(struct list_data * d)2599 void free_list(struct list_data* d) {
2600     int i;
2601     if (d->data) {
2602 	for (i = 0; i < d->count; i++) {
2603 	    if (d->data[i].escaped_fullname) free(d->data[i].escaped_fullname);
2604 	    if (d->data[i].fullname) free(d->data[i].fullname);
2605 	    if (d->data[i].basename) free(d->data[i].basename);
2606 	    if (d->data[i].dirname) free(d->data[i].dirname);
2607 	}
2608 	free(d->data); d->data = 0;
2609     }
2610     d->count = 0;
2611 }
2612 
2613