]> andersk Git - openssh.git/blame - sftp-int.c
- (djm) OpenBSD CVS Sync
[openssh.git] / sftp-int.c
CommitLineData
61e96248 1/*
22e6c827 2 * Copyright (c) 2001,2002 Damien Miller. All rights reserved.
61e96248 3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
14 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
15 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
16 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
17 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
18 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
19 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
20 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 */
24
61e96248 25/* XXX: recursive operations */
26
27#include "includes.h"
9a36208d 28RCSID("$OpenBSD: sftp-int.c,v 1.51 2003/01/08 23:53:26 djm Exp $");
2e4fb373 29
61e96248 30#include "buffer.h"
31#include "xmalloc.h"
32#include "log.h"
33#include "pathnames.h"
34
35#include "sftp.h"
36#include "sftp-common.h"
2e4fb373 37#include "sftp-glob.h"
61e96248 38#include "sftp-client.h"
39#include "sftp-int.h"
40
3a7fe5ba 41/* File to read commands from */
42extern FILE *infile;
43
375f867e 44/* Size of buffer used when copying files */
45extern size_t copy_buffer_len;
46
c25d3df7 47/* Number of concurrent outstanding requests */
48extern int num_requests;
49
61e96248 50/* Seperators for interactive commands */
51#define WHITESPACE " \t\r\n"
52
53/* Commands for interactive mode */
54#define I_CHDIR 1
55#define I_CHGRP 2
56#define I_CHMOD 3
57#define I_CHOWN 4
58#define I_GET 5
59#define I_HELP 6
60#define I_LCHDIR 7
61#define I_LLS 8
62#define I_LMKDIR 9
63#define I_LPWD 10
64#define I_LS 11
65#define I_LUMASK 12
66#define I_MKDIR 13
67#define I_PUT 14
68#define I_PWD 15
69#define I_QUIT 16
70#define I_RENAME 17
71#define I_RM 18
72#define I_RMDIR 19
73#define I_SHELL 20
3a7fe5ba 74#define I_SYMLINK 21
85cf5827 75#define I_VERSION 22
61e96248 76
77struct CMD {
61e96248 78 const char *c;
ec2a033a 79 const int n;
61e96248 80};
81
82const struct CMD cmds[] = {
68874d2b 83 { "bye", I_QUIT },
0426a3b4 84 { "cd", I_CHDIR },
85 { "chdir", I_CHDIR },
86 { "chgrp", I_CHGRP },
87 { "chmod", I_CHMOD },
88 { "chown", I_CHOWN },
89 { "dir", I_LS },
90 { "exit", I_QUIT },
91 { "get", I_GET },
45a2e669 92 { "mget", I_GET },
0426a3b4 93 { "help", I_HELP },
94 { "lcd", I_LCHDIR },
95 { "lchdir", I_LCHDIR },
96 { "lls", I_LLS },
97 { "lmkdir", I_LMKDIR },
3a7fe5ba 98 { "ln", I_SYMLINK },
0426a3b4 99 { "lpwd", I_LPWD },
100 { "ls", I_LS },
101 { "lumask", I_LUMASK },
102 { "mkdir", I_MKDIR },
103 { "put", I_PUT },
45a2e669 104 { "mput", I_PUT },
0426a3b4 105 { "pwd", I_PWD },
106 { "quit", I_QUIT },
107 { "rename", I_RENAME },
108 { "rm", I_RM },
109 { "rmdir", I_RMDIR },
3a7fe5ba 110 { "symlink", I_SYMLINK },
85cf5827 111 { "version", I_VERSION },
ec2a033a 112 { "!", I_SHELL },
113 { "?", I_HELP },
114 { NULL, -1}
61e96248 115};
116
396c147e 117static void
61e96248 118help(void)
119{
120 printf("Available commands:\n");
0426a3b4 121 printf("cd path Change remote directory to 'path'\n");
122 printf("lcd path Change local directory to 'path'\n");
123 printf("chgrp grp path Change group of file 'path' to 'grp'\n");
124 printf("chmod mode path Change permissions of file 'path' to 'mode'\n");
125 printf("chown own path Change owner of file 'path' to 'own'\n");
126 printf("help Display this help text\n");
127 printf("get remote-path [local-path] Download file\n");
128 printf("lls [ls-options [path]] Display local directory listing\n");
3a7fe5ba 129 printf("ln oldpath newpath Symlink remote file\n");
0426a3b4 130 printf("lmkdir path Create local directory\n");
131 printf("lpwd Print local working directory\n");
132 printf("ls [path] Display remote directory listing\n");
133 printf("lumask umask Set local umask to 'umask'\n");
134 printf("mkdir path Create remote directory\n");
135 printf("put local-path [remote-path] Upload file\n");
136 printf("pwd Display remote working directory\n");
137 printf("exit Quit sftp\n");
138 printf("quit Quit sftp\n");
139 printf("rename oldpath newpath Rename remote file\n");
140 printf("rmdir path Remove remote directory\n");
141 printf("rm path Delete remote file\n");
3a7fe5ba 142 printf("symlink oldpath newpath Symlink remote file\n");
85cf5827 143 printf("version Show SFTP version\n");
61e96248 144 printf("!command Execute 'command' in local shell\n");
145 printf("! Escape to local shell\n");
0426a3b4 146 printf("? Synonym for help\n");
61e96248 147}
148
396c147e 149static void
61e96248 150local_do_shell(const char *args)
151{
ec1f12d3 152 int status;
61e96248 153 char *shell;
154 pid_t pid;
2b87da3b 155
61e96248 156 if (!*args)
157 args = NULL;
2b87da3b 158
61e96248 159 if ((shell = getenv("SHELL")) == NULL)
160 shell = _PATH_BSHELL;
161
162 if ((pid = fork()) == -1)
163 fatal("Couldn't fork: %s", strerror(errno));
164
165 if (pid == 0) {
166 /* XXX: child has pipe fds to ssh subproc open - issue? */
167 if (args) {
168 debug3("Executing %s -c \"%s\"", shell, args);
ed916b28 169 execl(shell, shell, "-c", args, (char *)NULL);
61e96248 170 } else {
171 debug3("Executing %s", shell);
ed916b28 172 execl(shell, shell, (char *)NULL);
61e96248 173 }
2b87da3b 174 fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
61e96248 175 strerror(errno));
176 _exit(1);
177 }
8c38e88b 178 while (waitpid(pid, &status, 0) == -1)
179 if (errno != EINTR)
180 fatal("Couldn't wait for child: %s", strerror(errno));
61e96248 181 if (!WIFEXITED(status))
182 error("Shell exited abormally");
183 else if (WEXITSTATUS(status))
184 error("Shell exited with status %d", WEXITSTATUS(status));
185}
186
396c147e 187static void
61e96248 188local_do_ls(const char *args)
189{
190 if (!args || !*args)
0426a3b4 191 local_do_shell(_PATH_LS);
61e96248 192 else {
0426a3b4 193 int len = strlen(_PATH_LS " ") + strlen(args) + 1;
194 char *buf = xmalloc(len);
61e96248 195
196 /* XXX: quoting - rip quoting code from ftp? */
0426a3b4 197 snprintf(buf, len, _PATH_LS " %s", args);
61e96248 198 local_do_shell(buf);
0426a3b4 199 xfree(buf);
61e96248 200 }
201}
202
00b3ad3e 203/* Strip one path (usually the pwd) from the start of another */
204static char *
205path_strip(char *path, char *strip)
206{
207 size_t len;
00b3ad3e 208
209 if (strip == NULL)
210 return (xstrdup(path));
211
212 len = strlen(strip);
213 if (strip != NULL && strncmp(path, strip, len) == 0) {
214 if (strip[len - 1] != '/' && path[len] == '/')
215 len++;
216 return (xstrdup(path + len));
217 }
218
219 return (xstrdup(path));
220}
221
396c147e 222static char *
d50d9b63 223path_append(char *p1, char *p2)
224{
225 char *ret;
6a8496e4 226 int len = strlen(p1) + strlen(p2) + 2;
d50d9b63 227
6a8496e4 228 ret = xmalloc(len);
229 strlcpy(ret, p1, len);
00b3ad3e 230 if (p1[strlen(p1) - 1] != '/')
88690211 231 strlcat(ret, "/", len);
6a8496e4 232 strlcat(ret, p2, len);
d50d9b63 233
234 return(ret);
235}
236
396c147e 237static char *
61e96248 238make_absolute(char *p, char *pwd)
239{
d50d9b63 240 char *abs;
61e96248 241
242 /* Derelativise */
243 if (p && p[0] != '/') {
d50d9b63 244 abs = path_append(pwd, p);
61e96248 245 xfree(p);
d50d9b63 246 return(abs);
247 } else
248 return(p);
249}
250
396c147e 251static int
d50d9b63 252infer_path(const char *p, char **ifp)
253{
254 char *cp;
255
256 cp = strrchr(p, '/');
257 if (cp == NULL) {
258 *ifp = xstrdup(p);
259 return(0);
260 }
261
262 if (!cp[1]) {
263 error("Invalid path");
264 return(-1);
61e96248 265 }
266
d50d9b63 267 *ifp = xstrdup(cp + 1);
268 return(0);
61e96248 269}
270
396c147e 271static int
61e96248 272parse_getput_flags(const char **cpp, int *pflag)
273{
274 const char *cp = *cpp;
275
276 /* Check for flags */
277 if (cp[0] == '-' && cp[1] && strchr(WHITESPACE, cp[2])) {
0426a3b4 278 switch (cp[1]) {
58c54a79 279 case 'p':
61e96248 280 case 'P':
281 *pflag = 1;
282 break;
283 default:
58c54a79 284 error("Invalid flag -%c", cp[1]);
61e96248 285 return(-1);
286 }
287 cp += 2;
288 *cpp = cp + strspn(cp, WHITESPACE);
289 }
290
291 return(0);
292}
293
00b3ad3e 294static int
295parse_ls_flags(const char **cpp, int *lflag)
296{
297 const char *cp = *cpp;
298
299 /* Check for flags */
300 if (cp++[0] == '-') {
301 for(; strchr(WHITESPACE, *cp) == NULL; cp++) {
302 switch (*cp) {
303 case 'l':
304 *lflag = 1;
305 break;
306 default:
307 error("Invalid flag -%c", *cp);
308 return(-1);
309 }
310 }
311 *cpp = cp + strspn(cp, WHITESPACE);
312 }
313
314 return(0);
315}
316
396c147e 317static int
61e96248 318get_pathname(const char **cpp, char **path)
319{
0426a3b4 320 const char *cp = *cpp, *end;
321 char quot;
61e96248 322 int i;
323
324 cp += strspn(cp, WHITESPACE);
325 if (!*cp) {
326 *cpp = cp;
327 *path = NULL;
0426a3b4 328 return (0);
61e96248 329 }
330
331 /* Check for quoted filenames */
332 if (*cp == '\"' || *cp == '\'') {
0426a3b4 333 quot = *cp++;
f55d1b5f 334
0426a3b4 335 end = strchr(cp, quot);
336 if (end == NULL) {
61e96248 337 error("Unterminated quote");
0426a3b4 338 goto fail;
61e96248 339 }
0426a3b4 340 if (cp == end) {
61e96248 341 error("Empty quotes");
0426a3b4 342 goto fail;
61e96248 343 }
0426a3b4 344 *cpp = end + 1 + strspn(end + 1, WHITESPACE);
345 } else {
346 /* Read to end of filename */
347 end = strpbrk(cp, WHITESPACE);
348 if (end == NULL)
349 end = strchr(cp, '\0');
350 *cpp = end + strspn(end, WHITESPACE);
61e96248 351 }
352
0426a3b4 353 i = end - cp;
61e96248 354
355 *path = xmalloc(i + 1);
356 memcpy(*path, cp, i);
357 (*path)[i] = '\0';
61e96248 358 return(0);
0426a3b4 359
360 fail:
361 *path = NULL;
362 return (-1);
61e96248 363}
364
396c147e 365static int
d50d9b63 366is_dir(char *path)
61e96248 367{
d50d9b63 368 struct stat sb;
61e96248 369
d50d9b63 370 /* XXX: report errors? */
371 if (stat(path, &sb) == -1)
61e96248 372 return(0);
d50d9b63 373
374 return(sb.st_mode & S_IFDIR);
375}
376
396c147e 377static int
22e6c827 378remote_is_dir(struct sftp_conn *conn, char *path)
d50d9b63 379{
380 Attrib *a;
381
382 /* XXX: report errors? */
22e6c827 383 if ((a = do_stat(conn, path, 1)) == NULL)
d50d9b63 384 return(0);
385 if (!(a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS))
386 return(0);
387 return(a->perm & S_IFDIR);
388}
389
396c147e 390static int
22e6c827 391process_get(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
d50d9b63 392{
393 char *abs_src = NULL;
394 char *abs_dst = NULL;
395 char *tmp;
396 glob_t g;
397 int err = 0;
398 int i;
f55d1b5f 399
d50d9b63 400 abs_src = xstrdup(src);
401 abs_src = make_absolute(abs_src, pwd);
402
f55d1b5f 403 memset(&g, 0, sizeof(g));
d50d9b63 404 debug3("Looking up %s", abs_src);
22e6c827 405 if (remote_glob(conn, abs_src, 0, NULL, &g)) {
d50d9b63 406 error("File \"%s\" not found.", abs_src);
407 err = -1;
408 goto out;
61e96248 409 }
410
d50d9b63 411 /* Only one match, dst may be file, directory or unspecified */
412 if (g.gl_pathv[0] && g.gl_matchc == 1) {
413 if (dst) {
414 /* If directory specified, append filename */
415 if (is_dir(dst)) {
416 if (infer_path(g.gl_pathv[0], &tmp)) {
417 err = 1;
418 goto out;
419 }
420 abs_dst = path_append(dst, tmp);
421 xfree(tmp);
422 } else
423 abs_dst = xstrdup(dst);
424 } else if (infer_path(g.gl_pathv[0], &abs_dst)) {
425 err = -1;
426 goto out;
427 }
428 printf("Fetching %s to %s\n", g.gl_pathv[0], abs_dst);
22e6c827 429 err = do_download(conn, g.gl_pathv[0], abs_dst, pflag);
d50d9b63 430 goto out;
61e96248 431 }
432
d50d9b63 433 /* Multiple matches, dst may be directory or unspecified */
434 if (dst && !is_dir(dst)) {
f55d1b5f 435 error("Multiple files match, but \"%s\" is not a directory",
d50d9b63 436 dst);
437 err = -1;
438 goto out;
439 }
f55d1b5f 440
184eed6a 441 for (i = 0; g.gl_pathv[i]; i++) {
d50d9b63 442 if (infer_path(g.gl_pathv[i], &tmp)) {
443 err = -1;
444 goto out;
445 }
446 if (dst) {
447 abs_dst = path_append(dst, tmp);
448 xfree(tmp);
449 } else
450 abs_dst = tmp;
451
452 printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
22e6c827 453 if (do_download(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
d50d9b63 454 err = -1;
455 xfree(abs_dst);
456 abs_dst = NULL;
457 }
458
459out:
460 xfree(abs_src);
461 if (abs_dst)
462 xfree(abs_dst);
463 globfree(&g);
464 return(err);
465}
466
396c147e 467static int
22e6c827 468process_put(struct sftp_conn *conn, char *src, char *dst, char *pwd, int pflag)
d50d9b63 469{
470 char *tmp_dst = NULL;
471 char *abs_dst = NULL;
472 char *tmp;
473 glob_t g;
474 int err = 0;
475 int i;
476
477 if (dst) {
478 tmp_dst = xstrdup(dst);
479 tmp_dst = make_absolute(tmp_dst, pwd);
480 }
481
f55d1b5f 482 memset(&g, 0, sizeof(g));
d50d9b63 483 debug3("Looking up %s", src);
484 if (glob(src, 0, NULL, &g)) {
485 error("File \"%s\" not found.", src);
486 err = -1;
487 goto out;
488 }
489
490 /* Only one match, dst may be file, directory or unspecified */
491 if (g.gl_pathv[0] && g.gl_matchc == 1) {
492 if (tmp_dst) {
493 /* If directory specified, append filename */
22e6c827 494 if (remote_is_dir(conn, tmp_dst)) {
d50d9b63 495 if (infer_path(g.gl_pathv[0], &tmp)) {
496 err = 1;
497 goto out;
498 }
499 abs_dst = path_append(tmp_dst, tmp);
500 xfree(tmp);
501 } else
502 abs_dst = xstrdup(tmp_dst);
e285053e 503 } else {
504 if (infer_path(g.gl_pathv[0], &abs_dst)) {
505 err = -1;
506 goto out;
507 }
508 abs_dst = make_absolute(abs_dst, pwd);
d50d9b63 509 }
510 printf("Uploading %s to %s\n", g.gl_pathv[0], abs_dst);
22e6c827 511 err = do_upload(conn, g.gl_pathv[0], abs_dst, pflag);
d50d9b63 512 goto out;
513 }
514
515 /* Multiple matches, dst may be directory or unspecified */
22e6c827 516 if (tmp_dst && !remote_is_dir(conn, tmp_dst)) {
f55d1b5f 517 error("Multiple files match, but \"%s\" is not a directory",
d50d9b63 518 tmp_dst);
519 err = -1;
520 goto out;
521 }
522
184eed6a 523 for (i = 0; g.gl_pathv[i]; i++) {
d50d9b63 524 if (infer_path(g.gl_pathv[i], &tmp)) {
525 err = -1;
526 goto out;
527 }
528 if (tmp_dst) {
529 abs_dst = path_append(tmp_dst, tmp);
530 xfree(tmp);
531 } else
532 abs_dst = make_absolute(tmp, pwd);
533
534 printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
22e6c827 535 if (do_upload(conn, g.gl_pathv[i], abs_dst, pflag) == -1)
d50d9b63 536 err = -1;
537 }
538
539out:
540 if (abs_dst)
541 xfree(abs_dst);
542 if (tmp_dst)
543 xfree(tmp_dst);
544 return(err);
61e96248 545}
546
396c147e 547static int
00b3ad3e 548sdirent_comp(const void *aa, const void *bb)
549{
550 SFTP_DIRENT *a = *(SFTP_DIRENT **)aa;
551 SFTP_DIRENT *b = *(SFTP_DIRENT **)bb;
552
b77a87e5 553 return (strcmp(a->filename, b->filename));
00b3ad3e 554}
555
556/* sftp ls.1 replacement for directories */
557static int
558do_ls_dir(struct sftp_conn *conn, char *path, char *strip_path, int lflag)
559{
560 int n;
561 SFTP_DIRENT **d;
562
563 if ((n = do_readdir(conn, path, &d)) != 0)
564 return (n);
565
b77a87e5 566 /* Count entries for sort */
00b3ad3e 567 for (n = 0; d[n] != NULL; n++)
568 ;
569
570 qsort(d, n, sizeof(*d), sdirent_comp);
571
572 for (n = 0; d[n] != NULL; n++) {
573 char *tmp, *fname;
b77a87e5 574
00b3ad3e 575 tmp = path_append(path, d[n]->filename);
576 fname = path_strip(tmp, strip_path);
577 xfree(tmp);
578
579 if (lflag) {
580 char *lname;
581 struct stat sb;
582
583 memset(&sb, 0, sizeof(sb));
584 attrib_to_stat(&d[n]->a, &sb);
585 lname = ls_file(fname, &sb, 1);
586 printf("%s\n", lname);
587 xfree(lname);
588 } else {
589 /* XXX - multicolumn display would be nice here */
590 printf("%s\n", fname);
591 }
b77a87e5 592
00b3ad3e 593 xfree(fname);
594 }
595
596 free_sftp_dirents(d);
597 return (0);
598}
599
600/* sftp ls.1 replacement which handles path globs */
601static int
b77a87e5 602do_globbed_ls(struct sftp_conn *conn, char *path, char *strip_path,
00b3ad3e 603 int lflag)
604{
605 glob_t g;
606 int i;
607 Attrib *a;
608 struct stat sb;
609
610 memset(&g, 0, sizeof(g));
611
b77a87e5 612 if (remote_glob(conn, path, GLOB_MARK|GLOB_NOCHECK|GLOB_BRACE,
00b3ad3e 613 NULL, &g)) {
614 error("Can't ls: \"%s\" not found", path);
615 return (-1);
616 }
617
618 /*
b77a87e5 619 * If the glob returns a single match, which is the same as the
00b3ad3e 620 * input glob, and it is a directory, then just list its contents
621 */
b77a87e5 622 if (g.gl_pathc == 1 &&
00b3ad3e 623 strncmp(path, g.gl_pathv[0], strlen(g.gl_pathv[0]) - 1) == 0) {
624 if ((a = do_lstat(conn, path, 1)) == NULL) {
625 globfree(&g);
626 return (-1);
627 }
b77a87e5 628 if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
00b3ad3e 629 S_ISDIR(a->perm)) {
630 globfree(&g);
631 return (do_ls_dir(conn, path, strip_path, lflag));
632 }
633 }
634
635 for (i = 0; g.gl_pathv[i]; i++) {
636 char *fname, *lname;
637
638 fname = path_strip(g.gl_pathv[i], strip_path);
639
640 if (lflag) {
641 /*
642 * XXX: this is slow - 1 roundtrip per path
b77a87e5 643 * A solution to this is to fork glob() and
644 * build a sftp specific version which keeps the
00b3ad3e 645 * attribs (which currently get thrown away)
646 * that the server returns as well as the filenames.
647 */
648 memset(&sb, 0, sizeof(sb));
649 a = do_lstat(conn, g.gl_pathv[i], 1);
650 if (a != NULL)
651 attrib_to_stat(a, &sb);
652 lname = ls_file(fname, &sb, 1);
653 printf("%s\n", lname);
654 xfree(lname);
655 } else {
656 /* XXX - multicolumn display would be nice here */
657 printf("%s\n", fname);
658 }
659 xfree(fname);
660 }
661
662 if (g.gl_pathc)
663 globfree(&g);
664
665 return (0);
666}
667
668static int
9a36208d 669parse_args(const char **cpp, int *pflag, int *lflag, int *iflag,
00b3ad3e 670 unsigned long *n_arg, char **path1, char **path2)
61e96248 671{
672 const char *cmd, *cp = *cpp;
877546eb 673 char *cp2;
ec2a033a 674 int base = 0;
877546eb 675 long l;
61e96248 676 int i, cmdnum;
677
678 /* Skip leading whitespace */
679 cp = cp + strspn(cp, WHITESPACE);
680
9a36208d 681 /* Ignore blank lines and lines which begin with comment '#' char */
682 if (*cp == '\0' || *cp == '#')
683 return (0);
61e96248 684
9a36208d 685 /* Check for leading '-' (disable error processing) */
686 *iflag = 0;
687 if (*cp == '-') {
688 *iflag = 1;
689 cp++;
690 }
691
61e96248 692 /* Figure out which command we have */
184eed6a 693 for (i = 0; cmds[i].c; i++) {
61e96248 694 int cmdlen = strlen(cmds[i].c);
695
696 /* Check for command followed by whitespace */
697 if (!strncasecmp(cp, cmds[i].c, cmdlen) &&
698 strchr(WHITESPACE, cp[cmdlen])) {
699 cp += cmdlen;
700 cp = cp + strspn(cp, WHITESPACE);
701 break;
702 }
703 }
704 cmdnum = cmds[i].n;
705 cmd = cmds[i].c;
706
707 /* Special case */
708 if (*cp == '!') {
709 cp++;
710 cmdnum = I_SHELL;
711 } else if (cmdnum == -1) {
712 error("Invalid command.");
9a36208d 713 return (-1);
61e96248 714 }
715
716 /* Get arguments and parse flags */
00b3ad3e 717 *lflag = *pflag = *n_arg = 0;
61e96248 718 *path1 = *path2 = NULL;
719 switch (cmdnum) {
720 case I_GET:
721 case I_PUT:
722 if (parse_getput_flags(&cp, pflag))
723 return(-1);
724 /* Get first pathname (mandatory) */
725 if (get_pathname(&cp, path1))
726 return(-1);
727 if (*path1 == NULL) {
728 error("You must specify at least one path after a "
729 "%s command.", cmd);
730 return(-1);
731 }
732 /* Try to get second pathname (optional) */
733 if (get_pathname(&cp, path2))
734 return(-1);
61e96248 735 break;
736 case I_RENAME:
3a7fe5ba 737 case I_SYMLINK:
61e96248 738 if (get_pathname(&cp, path1))
739 return(-1);
740 if (get_pathname(&cp, path2))
741 return(-1);
742 if (!*path1 || !*path2) {
743 error("You must specify two paths after a %s "
744 "command.", cmd);
745 return(-1);
746 }
747 break;
748 case I_RM:
749 case I_MKDIR:
750 case I_RMDIR:
751 case I_CHDIR:
752 case I_LCHDIR:
753 case I_LMKDIR:
754 /* Get pathname (mandatory) */
755 if (get_pathname(&cp, path1))
756 return(-1);
757 if (*path1 == NULL) {
2b87da3b 758 error("You must specify a path after a %s command.",
61e96248 759 cmd);
760 return(-1);
761 }
762 break;
763 case I_LS:
00b3ad3e 764 if (parse_ls_flags(&cp, lflag))
765 return(-1);
61e96248 766 /* Path is optional */
767 if (get_pathname(&cp, path1))
768 return(-1);
769 break;
770 case I_LLS:
771 case I_SHELL:
772 /* Uses the rest of the line */
773 break;
774 case I_LUMASK:
877546eb 775 base = 8;
61e96248 776 case I_CHMOD:
ec2a033a 777 base = 8;
61e96248 778 case I_CHOWN:
779 case I_CHGRP:
780 /* Get numeric arg (mandatory) */
877546eb 781 l = strtol(cp, &cp2, base);
782 if (cp2 == cp || ((l == LONG_MIN || l == LONG_MAX) &&
783 errno == ERANGE) || l < 0) {
61e96248 784 error("You must supply a numeric argument "
785 "to the %s command.", cmd);
786 return(-1);
787 }
877546eb 788 cp = cp2;
789 *n_arg = l;
790 if (cmdnum == I_LUMASK && strchr(WHITESPACE, *cp))
791 break;
792 if (cmdnum == I_LUMASK || !strchr(WHITESPACE, *cp)) {
61e96248 793 error("You must supply a numeric argument "
794 "to the %s command.", cmd);
795 return(-1);
796 }
797 cp += strspn(cp, WHITESPACE);
798
799 /* Get pathname (mandatory) */
800 if (get_pathname(&cp, path1))
801 return(-1);
802 if (*path1 == NULL) {
2b87da3b 803 error("You must specify a path after a %s command.",
61e96248 804 cmd);
805 return(-1);
806 }
807 break;
808 case I_QUIT:
809 case I_PWD:
810 case I_LPWD:
811 case I_HELP:
85cf5827 812 case I_VERSION:
61e96248 813 break;
814 default:
815 fatal("Command not implemented");
816 }
817
818 *cpp = cp;
61e96248 819 return(cmdnum);
820}
821
396c147e 822static int
9a36208d 823parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd,
824 int err_abort)
61e96248 825{
0426a3b4 826 char *path1, *path2, *tmp;
9a36208d 827 int pflag, lflag, iflag, cmdnum, i;
61e96248 828 unsigned long n_arg;
829 Attrib a, *aa;
7cc4cf0a 830 char path_buf[MAXPATHLEN];
a5ec8a3d 831 int err = 0;
2e4fb373 832 glob_t g;
61e96248 833
834 path1 = path2 = NULL;
9a36208d 835 cmdnum = parse_args(&cmd, &pflag, &lflag, &iflag, &n_arg,
00b3ad3e 836 &path1, &path2);
61e96248 837
9a36208d 838 if (iflag != 0)
839 err_abort = 0;
840
d50d9b63 841 memset(&g, 0, sizeof(g));
842
61e96248 843 /* Perform command */
844 switch (cmdnum) {
9a36208d 845 case 0:
846 /* Blank line */
847 break;
61e96248 848 case -1:
9a36208d 849 /* Unrecognized command */
850 err = -1;
61e96248 851 break;
852 case I_GET:
22e6c827 853 err = process_get(conn, path1, path2, *pwd, pflag);
61e96248 854 break;
855 case I_PUT:
22e6c827 856 err = process_put(conn, path1, path2, *pwd, pflag);
cd332296 857 break;
858 case I_RENAME:
61e96248 859 path1 = make_absolute(path1, *pwd);
860 path2 = make_absolute(path2, *pwd);
22e6c827 861 err = do_rename(conn, path1, path2);
61e96248 862 break;
3a7fe5ba 863 case I_SYMLINK:
22e6c827 864 path2 = make_absolute(path2, *pwd);
865 err = do_symlink(conn, path1, path2);
3a7fe5ba 866 break;
61e96248 867 case I_RM:
868 path1 = make_absolute(path1, *pwd);
22e6c827 869 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
184eed6a 870 for (i = 0; g.gl_pathv[i]; i++) {
2e4fb373 871 printf("Removing %s\n", g.gl_pathv[i]);
9a36208d 872 err = do_rm(conn, g.gl_pathv[i]);
873 if (err != 0 && err_abort)
874 break;
2e4fb373 875 }
61e96248 876 break;
877 case I_MKDIR:
878 path1 = make_absolute(path1, *pwd);
879 attrib_clear(&a);
880 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
881 a.perm = 0777;
22e6c827 882 err = do_mkdir(conn, path1, &a);
61e96248 883 break;
884 case I_RMDIR:
885 path1 = make_absolute(path1, *pwd);
22e6c827 886 err = do_rmdir(conn, path1);
61e96248 887 break;
888 case I_CHDIR:
889 path1 = make_absolute(path1, *pwd);
22e6c827 890 if ((tmp = do_realpath(conn, path1)) == NULL) {
a5ec8a3d 891 err = 1;
0426a3b4 892 break;
a5ec8a3d 893 }
22e6c827 894 if ((aa = do_stat(conn, tmp, 0)) == NULL) {
0426a3b4 895 xfree(tmp);
a5ec8a3d 896 err = 1;
0426a3b4 897 break;
898 }
899 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
900 error("Can't change directory: Can't check target");
901 xfree(tmp);
a5ec8a3d 902 err = 1;
0426a3b4 903 break;
904 }
905 if (!S_ISDIR(aa->perm)) {
906 error("Can't change directory: \"%s\" is not "
907 "a directory", tmp);
908 xfree(tmp);
a5ec8a3d 909 err = 1;
0426a3b4 910 break;
911 }
61e96248 912 xfree(*pwd);
0426a3b4 913 *pwd = tmp;
61e96248 914 break;
915 case I_LS:
0426a3b4 916 if (!path1) {
00b3ad3e 917 do_globbed_ls(conn, *pwd, *pwd, lflag);
0426a3b4 918 break;
919 }
b77a87e5 920
00b3ad3e 921 /* Strip pwd off beginning of non-absolute paths */
922 tmp = NULL;
923 if (*path1 != '/')
924 tmp = *pwd;
925
61e96248 926 path1 = make_absolute(path1, *pwd);
9a36208d 927 err = do_globbed_ls(conn, path1, tmp, lflag);
61e96248 928 break;
929 case I_LCHDIR:
a5ec8a3d 930 if (chdir(path1) == -1) {
61e96248 931 error("Couldn't change local directory to "
932 "\"%s\": %s", path1, strerror(errno));
a5ec8a3d 933 err = 1;
934 }
61e96248 935 break;
936 case I_LMKDIR:
a5ec8a3d 937 if (mkdir(path1, 0777) == -1) {
0426a3b4 938 error("Couldn't create local directory "
61e96248 939 "\"%s\": %s", path1, strerror(errno));
a5ec8a3d 940 err = 1;
941 }
61e96248 942 break;
943 case I_LLS:
944 local_do_ls(cmd);
945 break;
946 case I_SHELL:
947 local_do_shell(cmd);
948 break;
949 case I_LUMASK:
950 umask(n_arg);
877546eb 951 printf("Local umask: %03lo\n", n_arg);
61e96248 952 break;
953 case I_CHMOD:
954 path1 = make_absolute(path1, *pwd);
955 attrib_clear(&a);
956 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
957 a.perm = n_arg;
22e6c827 958 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
184eed6a 959 for (i = 0; g.gl_pathv[i]; i++) {
2e4fb373 960 printf("Changing mode on %s\n", g.gl_pathv[i]);
9a36208d 961 err = do_setstat(conn, g.gl_pathv[i], &a);
962 if (err != 0 && err_abort)
963 break;
2e4fb373 964 }
ec2a033a 965 break;
61e96248 966 case I_CHOWN:
61e96248 967 case I_CHGRP:
968 path1 = make_absolute(path1, *pwd);
22e6c827 969 remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
184eed6a 970 for (i = 0; g.gl_pathv[i]; i++) {
9a36208d 971 if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
972 if (err != 0 && err_abort)
973 break;
974 else
975 continue;
976 }
2e4fb373 977 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
978 error("Can't get current ownership of "
979 "remote file \"%s\"", g.gl_pathv[i]);
9a36208d 980 if (err != 0 && err_abort)
981 break;
982 else
983 continue;
2e4fb373 984 }
2e4fb373 985 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
9a36208d 986 if (cmdnum == I_CHOWN) {
987 printf("Changing owner on %s\n", g.gl_pathv[i]);
988 aa->uid = n_arg;
989 } else {
990 printf("Changing group on %s\n", g.gl_pathv[i]);
991 aa->gid = n_arg;
992 }
993 err = do_setstat(conn, g.gl_pathv[i], aa);
994 if (err != 0 && err_abort)
995 break;
61e96248 996 }
61e96248 997 break;
998 case I_PWD:
999 printf("Remote working directory: %s\n", *pwd);
1000 break;
1001 case I_LPWD:
9a36208d 1002 if (!getcwd(path_buf, sizeof(path_buf))) {
1003 error("Couldn't get local cwd: %s", strerror(errno));
1004 err = -1;
1005 break;
1006 }
1007 printf("Local working directory: %s\n", path_buf);
61e96248 1008 break;
1009 case I_QUIT:
9a36208d 1010 /* Processed below */
1011 break;
61e96248 1012 case I_HELP:
1013 help();
1014 break;
85cf5827 1015 case I_VERSION:
9906a836 1016 printf("SFTP protocol version %u\n", sftp_proto_version(conn));
85cf5827 1017 break;
61e96248 1018 default:
1019 fatal("%d is not implemented", cmdnum);
1020 }
1021
d50d9b63 1022 if (g.gl_pathc)
1023 globfree(&g);
61e96248 1024 if (path1)
1025 xfree(path1);
1026 if (path2)
1027 xfree(path2);
a5ec8a3d 1028
9a36208d 1029 /* If an unignored error occurs in batch mode we should abort. */
1030 if (err_abort && err != 0)
1031 return (-1);
1032 else if (cmdnum == I_QUIT)
1033 return (1);
a5ec8a3d 1034
9a36208d 1035 return (0);
61e96248 1036}
1037
9a36208d 1038int
edeeab1e 1039interactive_loop(int fd_in, int fd_out, char *file1, char *file2)
61e96248 1040{
1041 char *pwd;
edeeab1e 1042 char *dir = NULL;
61e96248 1043 char cmd[2048];
22e6c827 1044 struct sftp_conn *conn;
9a36208d 1045 int err;
61e96248 1046
22e6c827 1047 conn = do_init(fd_in, fd_out, copy_buffer_len, num_requests);
1048 if (conn == NULL)
3a7fe5ba 1049 fatal("Couldn't initialise connection to server");
1050
22e6c827 1051 pwd = do_realpath(conn, ".");
61e96248 1052 if (pwd == NULL)
1053 fatal("Need cwd");
1054
edeeab1e 1055 if (file1 != NULL) {
1056 dir = xstrdup(file1);
1057 dir = make_absolute(dir, pwd);
1058
22e6c827 1059 if (remote_is_dir(conn, dir) && file2 == NULL) {
edeeab1e 1060 printf("Changing to: %s\n", dir);
1061 snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
9a36208d 1062 if (parse_dispatch_command(conn, cmd, &pwd, 1) != 0)
1063 return (-1);
edeeab1e 1064 } else {
1065 if (file2 == NULL)
1066 snprintf(cmd, sizeof cmd, "get %s", dir);
1067 else
1068 snprintf(cmd, sizeof cmd, "get %s %s", dir,
1069 file2);
1070
9a36208d 1071 err = parse_dispatch_command(conn, cmd, &pwd, 1);
702b7dd8 1072 xfree(dir);
9a36208d 1073 return (err);
edeeab1e 1074 }
702b7dd8 1075 xfree(dir);
edeeab1e 1076 }
9a36208d 1077
b81c369b 1078#if HAVE_SETVBUF
0426a3b4 1079 setvbuf(stdout, NULL, _IOLBF, 0);
a5ec8a3d 1080 setvbuf(infile, NULL, _IOLBF, 0);
b81c369b 1081#else
1082 setlinebuf(stdout);
1083 setlinebuf(infile);
1084#endif
61e96248 1085
9a36208d 1086 err = 0;
184eed6a 1087 for (;;) {
61e96248 1088 char *cp;
1089
1090 printf("sftp> ");
1091
1092 /* XXX: use libedit */
a5ec8a3d 1093 if (fgets(cmd, sizeof(cmd), infile) == NULL) {
61e96248 1094 printf("\n");
1095 break;
a5ec8a3d 1096 } else if (infile != stdin) /* Bluff typing */
1097 printf("%s", cmd);
1098
61e96248 1099 cp = strrchr(cmd, '\n');
1100 if (cp)
1101 *cp = '\0';
a5ec8a3d 1102
9a36208d 1103 err = parse_dispatch_command(conn, cmd, &pwd, infile != stdin);
1104 if (err != 0)
61e96248 1105 break;
1106 }
1107 xfree(pwd);
9a36208d 1108
1109 /* err == 1 signifies normal "quit" exit */
1110 return (err >= 0 ? 0 : -1);
61e96248 1111}
9a36208d 1112
This page took 1.692881 seconds and 5 git commands to generate.