]> andersk Git - openssh.git/blame - sftp-int.c
- (djm) Sync sftp and scp stuff from OpenBSD:
[openssh.git] / sftp-int.c
CommitLineData
61e96248 1/*
2 * Copyright (c) 2001 Damien Miller. All rights reserved.
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
25/* XXX: finish implementation of all commands */
26/* XXX: do fnmatch() instead of using raw pathname */
0426a3b4 27/* XXX: globbed ls */
61e96248 28/* XXX: recursive operations */
29
30#include "includes.h"
0426a3b4 31RCSID("$OpenBSD: sftp-int.c,v 1.19 2001/02/09 11:46:24 djm Exp $");
61e96248 32
33#include "buffer.h"
34#include "xmalloc.h"
35#include "log.h"
36#include "pathnames.h"
37
38#include "sftp.h"
39#include "sftp-common.h"
40#include "sftp-client.h"
41#include "sftp-int.h"
42
43/* Seperators for interactive commands */
44#define WHITESPACE " \t\r\n"
45
46/* Commands for interactive mode */
47#define I_CHDIR 1
48#define I_CHGRP 2
49#define I_CHMOD 3
50#define I_CHOWN 4
51#define I_GET 5
52#define I_HELP 6
53#define I_LCHDIR 7
54#define I_LLS 8
55#define I_LMKDIR 9
56#define I_LPWD 10
57#define I_LS 11
58#define I_LUMASK 12
59#define I_MKDIR 13
60#define I_PUT 14
61#define I_PWD 15
62#define I_QUIT 16
63#define I_RENAME 17
64#define I_RM 18
65#define I_RMDIR 19
66#define I_SHELL 20
67
68struct CMD {
61e96248 69 const char *c;
ec2a033a 70 const int n;
61e96248 71};
72
73const struct CMD cmds[] = {
0426a3b4 74 { "cd", I_CHDIR },
75 { "chdir", I_CHDIR },
76 { "chgrp", I_CHGRP },
77 { "chmod", I_CHMOD },
78 { "chown", I_CHOWN },
79 { "dir", I_LS },
80 { "exit", I_QUIT },
81 { "get", I_GET },
82 { "help", I_HELP },
83 { "lcd", I_LCHDIR },
84 { "lchdir", I_LCHDIR },
85 { "lls", I_LLS },
86 { "lmkdir", I_LMKDIR },
87 { "lpwd", I_LPWD },
88 { "ls", I_LS },
89 { "lumask", I_LUMASK },
90 { "mkdir", I_MKDIR },
91 { "put", I_PUT },
92 { "pwd", I_PWD },
93 { "quit", I_QUIT },
94 { "rename", I_RENAME },
95 { "rm", I_RM },
96 { "rmdir", I_RMDIR },
ec2a033a 97 { "!", I_SHELL },
98 { "?", I_HELP },
99 { NULL, -1}
61e96248 100};
101
102void
103help(void)
104{
105 printf("Available commands:\n");
0426a3b4 106 printf("cd path Change remote directory to 'path'\n");
107 printf("lcd path Change local directory to 'path'\n");
108 printf("chgrp grp path Change group of file 'path' to 'grp'\n");
109 printf("chmod mode path Change permissions of file 'path' to 'mode'\n");
110 printf("chown own path Change owner of file 'path' to 'own'\n");
111 printf("help Display this help text\n");
112 printf("get remote-path [local-path] Download file\n");
113 printf("lls [ls-options [path]] Display local directory listing\n");
114 printf("lmkdir path Create local directory\n");
115 printf("lpwd Print local working directory\n");
116 printf("ls [path] Display remote directory listing\n");
117 printf("lumask umask Set local umask to 'umask'\n");
118 printf("mkdir path Create remote directory\n");
119 printf("put local-path [remote-path] Upload file\n");
120 printf("pwd Display remote working directory\n");
121 printf("exit Quit sftp\n");
122 printf("quit Quit sftp\n");
123 printf("rename oldpath newpath Rename remote file\n");
124 printf("rmdir path Remove remote directory\n");
125 printf("rm path Delete remote file\n");
61e96248 126 printf("!command Execute 'command' in local shell\n");
127 printf("! Escape to local shell\n");
0426a3b4 128 printf("? Synonym for help\n");
61e96248 129}
130
131void
132local_do_shell(const char *args)
133{
134 int ret, status;
135 char *shell;
136 pid_t pid;
2b87da3b 137
61e96248 138 if (!*args)
139 args = NULL;
2b87da3b 140
61e96248 141 if ((shell = getenv("SHELL")) == NULL)
142 shell = _PATH_BSHELL;
143
144 if ((pid = fork()) == -1)
145 fatal("Couldn't fork: %s", strerror(errno));
146
147 if (pid == 0) {
148 /* XXX: child has pipe fds to ssh subproc open - issue? */
149 if (args) {
150 debug3("Executing %s -c \"%s\"", shell, args);
151 ret = execl(shell, shell, "-c", args, NULL);
152 } else {
153 debug3("Executing %s", shell);
154 ret = execl(shell, shell, NULL);
155 }
2b87da3b 156 fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
61e96248 157 strerror(errno));
158 _exit(1);
159 }
160 if (waitpid(pid, &status, 0) == -1)
161 fatal("Couldn't wait for child: %s", strerror(errno));
162 if (!WIFEXITED(status))
163 error("Shell exited abormally");
164 else if (WEXITSTATUS(status))
165 error("Shell exited with status %d", WEXITSTATUS(status));
166}
167
2b87da3b 168void
61e96248 169local_do_ls(const char *args)
170{
171 if (!args || !*args)
0426a3b4 172 local_do_shell(_PATH_LS);
61e96248 173 else {
0426a3b4 174 int len = strlen(_PATH_LS " ") + strlen(args) + 1;
175 char *buf = xmalloc(len);
61e96248 176
177 /* XXX: quoting - rip quoting code from ftp? */
0426a3b4 178 snprintf(buf, len, _PATH_LS " %s", args);
61e96248 179 local_do_shell(buf);
0426a3b4 180 xfree(buf);
61e96248 181 }
182}
183
184char *
185make_absolute(char *p, char *pwd)
186{
187 char buf[2048];
188
189 /* Derelativise */
190 if (p && p[0] != '/') {
191 snprintf(buf, sizeof(buf), "%s/%s", pwd, p);
192 xfree(p);
193 p = xstrdup(buf);
194 }
195
196 return(p);
197}
198
199int
200parse_getput_flags(const char **cpp, int *pflag)
201{
202 const char *cp = *cpp;
203
204 /* Check for flags */
205 if (cp[0] == '-' && cp[1] && strchr(WHITESPACE, cp[2])) {
0426a3b4 206 switch (cp[1]) {
61e96248 207 case 'P':
208 *pflag = 1;
209 break;
210 default:
211 error("Invalid flag -%c", *cp);
212 return(-1);
213 }
214 cp += 2;
215 *cpp = cp + strspn(cp, WHITESPACE);
216 }
217
218 return(0);
219}
220
221int
222get_pathname(const char **cpp, char **path)
223{
0426a3b4 224 const char *cp = *cpp, *end;
225 char quot;
61e96248 226 int i;
227
228 cp += strspn(cp, WHITESPACE);
229 if (!*cp) {
230 *cpp = cp;
231 *path = NULL;
0426a3b4 232 return (0);
61e96248 233 }
234
235 /* Check for quoted filenames */
236 if (*cp == '\"' || *cp == '\'') {
0426a3b4 237 quot = *cp++;
238
239 end = strchr(cp, quot);
240 if (end == NULL) {
61e96248 241 error("Unterminated quote");
0426a3b4 242 goto fail;
61e96248 243 }
0426a3b4 244 if (cp == end) {
61e96248 245 error("Empty quotes");
0426a3b4 246 goto fail;
61e96248 247 }
0426a3b4 248 *cpp = end + 1 + strspn(end + 1, WHITESPACE);
249 } else {
250 /* Read to end of filename */
251 end = strpbrk(cp, WHITESPACE);
252 if (end == NULL)
253 end = strchr(cp, '\0');
254 *cpp = end + strspn(end, WHITESPACE);
61e96248 255 }
256
0426a3b4 257 i = end - cp;
61e96248 258
259 *path = xmalloc(i + 1);
260 memcpy(*path, cp, i);
261 (*path)[i] = '\0';
61e96248 262 return(0);
0426a3b4 263
264 fail:
265 *path = NULL;
266 return (-1);
61e96248 267}
268
269int
270infer_path(const char *p, char **ifp)
271{
272 char *cp;
273
274 debug("XXX: P = \"%s\"", p);
275
276 cp = strrchr(p, '/');
61e96248 277 if (cp == NULL) {
278 *ifp = xstrdup(p);
279 return(0);
280 }
281
282 if (!cp[1]) {
283 error("Invalid path");
284 return(-1);
285 }
286
287 *ifp = xstrdup(cp + 1);
288 return(0);
289}
290
291int
292parse_args(const char **cpp, int *pflag, unsigned long *n_arg,
293 char **path1, char **path2)
294{
295 const char *cmd, *cp = *cpp;
ec2a033a 296 int base = 0;
61e96248 297 int i, cmdnum;
298
299 /* Skip leading whitespace */
300 cp = cp + strspn(cp, WHITESPACE);
301
302 /* Ignore blank lines */
303 if (!*cp)
304 return(-1);
305
306 /* Figure out which command we have */
307 for(i = 0; cmds[i].c; i++) {
308 int cmdlen = strlen(cmds[i].c);
309
310 /* Check for command followed by whitespace */
311 if (!strncasecmp(cp, cmds[i].c, cmdlen) &&
312 strchr(WHITESPACE, cp[cmdlen])) {
313 cp += cmdlen;
314 cp = cp + strspn(cp, WHITESPACE);
315 break;
316 }
317 }
318 cmdnum = cmds[i].n;
319 cmd = cmds[i].c;
320
321 /* Special case */
322 if (*cp == '!') {
323 cp++;
324 cmdnum = I_SHELL;
325 } else if (cmdnum == -1) {
326 error("Invalid command.");
327 return(-1);
328 }
329
330 /* Get arguments and parse flags */
331 *pflag = *n_arg = 0;
332 *path1 = *path2 = NULL;
333 switch (cmdnum) {
334 case I_GET:
335 case I_PUT:
336 if (parse_getput_flags(&cp, pflag))
337 return(-1);
338 /* Get first pathname (mandatory) */
339 if (get_pathname(&cp, path1))
340 return(-1);
341 if (*path1 == NULL) {
342 error("You must specify at least one path after a "
343 "%s command.", cmd);
344 return(-1);
345 }
346 /* Try to get second pathname (optional) */
347 if (get_pathname(&cp, path2))
348 return(-1);
349 /* Otherwise try to guess it from first path */
350 if (*path2 == NULL && infer_path(*path1, path2))
351 return(-1);
352 break;
353 case I_RENAME:
354 /* Get first pathname (mandatory) */
355 if (get_pathname(&cp, path1))
356 return(-1);
357 if (get_pathname(&cp, path2))
358 return(-1);
359 if (!*path1 || !*path2) {
360 error("You must specify two paths after a %s "
361 "command.", cmd);
362 return(-1);
363 }
364 break;
365 case I_RM:
366 case I_MKDIR:
367 case I_RMDIR:
368 case I_CHDIR:
369 case I_LCHDIR:
370 case I_LMKDIR:
371 /* Get pathname (mandatory) */
372 if (get_pathname(&cp, path1))
373 return(-1);
374 if (*path1 == NULL) {
2b87da3b 375 error("You must specify a path after a %s command.",
61e96248 376 cmd);
377 return(-1);
378 }
379 break;
380 case I_LS:
381 /* Path is optional */
382 if (get_pathname(&cp, path1))
383 return(-1);
384 break;
385 case I_LLS:
386 case I_SHELL:
387 /* Uses the rest of the line */
388 break;
389 case I_LUMASK:
390 case I_CHMOD:
ec2a033a 391 base = 8;
61e96248 392 case I_CHOWN:
393 case I_CHGRP:
394 /* Get numeric arg (mandatory) */
395 if (*cp < '0' && *cp > '9') {
396 error("You must supply a numeric argument "
397 "to the %s command.", cmd);
398 return(-1);
399 }
ec2a033a 400 *n_arg = strtoul(cp, (char**)&cp, base);
61e96248 401 if (!*cp || !strchr(WHITESPACE, *cp)) {
402 error("You must supply a numeric argument "
403 "to the %s command.", cmd);
404 return(-1);
405 }
406 cp += strspn(cp, WHITESPACE);
407
408 /* Get pathname (mandatory) */
409 if (get_pathname(&cp, path1))
410 return(-1);
411 if (*path1 == NULL) {
2b87da3b 412 error("You must specify a path after a %s command.",
61e96248 413 cmd);
414 return(-1);
415 }
416 break;
417 case I_QUIT:
418 case I_PWD:
419 case I_LPWD:
420 case I_HELP:
421 break;
422 default:
423 fatal("Command not implemented");
424 }
425
426 *cpp = cp;
61e96248 427 return(cmdnum);
428}
429
430int
431parse_dispatch_command(int in, int out, const char *cmd, char **pwd)
432{
0426a3b4 433 char *path1, *path2, *tmp;
61e96248 434 int pflag, cmdnum;
435 unsigned long n_arg;
436 Attrib a, *aa;
437 char path_buf[PATH_MAX];
438
439 path1 = path2 = NULL;
440 cmdnum = parse_args(&cmd, &pflag, &n_arg, &path1, &path2);
441
442 /* Perform command */
443 switch (cmdnum) {
444 case -1:
445 break;
446 case I_GET:
447 path1 = make_absolute(path1, *pwd);
448 do_download(in, out, path1, path2, pflag);
449 break;
450 case I_PUT:
451 path2 = make_absolute(path2, *pwd);
452 do_upload(in, out, path1, path2, pflag);
453 break;
454 case I_RENAME:
455 path1 = make_absolute(path1, *pwd);
456 path2 = make_absolute(path2, *pwd);
457 do_rename(in, out, path1, path2);
458 break;
459 case I_RM:
460 path1 = make_absolute(path1, *pwd);
461 do_rm(in, out, path1);
462 break;
463 case I_MKDIR:
464 path1 = make_absolute(path1, *pwd);
465 attrib_clear(&a);
466 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
467 a.perm = 0777;
468 do_mkdir(in, out, path1, &a);
469 break;
470 case I_RMDIR:
471 path1 = make_absolute(path1, *pwd);
472 do_rmdir(in, out, path1);
473 break;
474 case I_CHDIR:
475 path1 = make_absolute(path1, *pwd);
0426a3b4 476 if ((tmp = do_realpath(in, out, path1)) == NULL)
477 break;
478 if ((aa = do_stat(in, out, tmp)) == NULL) {
479 xfree(tmp);
480 break;
481 }
482 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
483 error("Can't change directory: Can't check target");
484 xfree(tmp);
485 break;
486 }
487 if (!S_ISDIR(aa->perm)) {
488 error("Can't change directory: \"%s\" is not "
489 "a directory", tmp);
490 xfree(tmp);
491 break;
492 }
61e96248 493 xfree(*pwd);
0426a3b4 494 *pwd = tmp;
61e96248 495 break;
496 case I_LS:
0426a3b4 497 if (!path1) {
498 do_ls(in, out, *pwd);
499 break;
500 }
61e96248 501 path1 = make_absolute(path1, *pwd);
0426a3b4 502 if ((tmp = do_realpath(in, out, path1)) == NULL)
503 break;
504 xfree(path1);
505 path1 = tmp;
506 if ((aa = do_stat(in, out, path1)) == NULL)
507 break;
508 if ((aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
509 !S_ISDIR(aa->perm)) {
510 error("Can't ls: \"%s\" is not a directory", path1);
511 break;
512 }
513 do_ls(in, out, path1);
61e96248 514 break;
515 case I_LCHDIR:
516 if (chdir(path1) == -1)
517 error("Couldn't change local directory to "
518 "\"%s\": %s", path1, strerror(errno));
519 break;
520 case I_LMKDIR:
521 if (mkdir(path1, 0777) == -1)
0426a3b4 522 error("Couldn't create local directory "
61e96248 523 "\"%s\": %s", path1, strerror(errno));
524 break;
525 case I_LLS:
526 local_do_ls(cmd);
527 break;
528 case I_SHELL:
529 local_do_shell(cmd);
530 break;
531 case I_LUMASK:
532 umask(n_arg);
533 break;
534 case I_CHMOD:
535 path1 = make_absolute(path1, *pwd);
536 attrib_clear(&a);
537 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
538 a.perm = n_arg;
539 do_setstat(in, out, path1, &a);
ec2a033a 540 break;
61e96248 541 case I_CHOWN:
542 path1 = make_absolute(path1, *pwd);
0426a3b4 543 if (!(aa = do_stat(in, out, path1)))
544 break;
ec2a033a 545 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
61e96248 546 error("Can't get current ownership of "
547 "remote file \"%s\"", path1);
548 break;
549 }
0426a3b4 550 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
61e96248 551 aa->uid = n_arg;
552 do_setstat(in, out, path1, aa);
553 break;
554 case I_CHGRP:
555 path1 = make_absolute(path1, *pwd);
0426a3b4 556 if (!(aa = do_stat(in, out, path1)))
557 break;
ec2a033a 558 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
61e96248 559 error("Can't get current ownership of "
560 "remote file \"%s\"", path1);
561 break;
562 }
0426a3b4 563 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
61e96248 564 aa->gid = n_arg;
565 do_setstat(in, out, path1, aa);
566 break;
567 case I_PWD:
568 printf("Remote working directory: %s\n", *pwd);
569 break;
570 case I_LPWD:
571 if (!getcwd(path_buf, sizeof(path_buf)))
572 error("Couldn't get local cwd: %s\n",
573 strerror(errno));
574 else
575 printf("Local working directory: %s\n",
576 path_buf);
577 break;
578 case I_QUIT:
579 return(-1);
580 case I_HELP:
581 help();
582 break;
583 default:
584 fatal("%d is not implemented", cmdnum);
585 }
586
587 if (path1)
588 xfree(path1);
589 if (path2)
590 xfree(path2);
61e96248 591 return(0);
592}
593
594void
595interactive_loop(int fd_in, int fd_out)
596{
597 char *pwd;
598 char cmd[2048];
599
600 pwd = do_realpath(fd_in, fd_out, ".");
601 if (pwd == NULL)
602 fatal("Need cwd");
603
0426a3b4 604 setvbuf(stdout, NULL, _IOLBF, 0);
605 setvbuf(stdin, NULL, _IOLBF, 0);
61e96248 606
607 for(;;) {
608 char *cp;
609
610 printf("sftp> ");
611
612 /* XXX: use libedit */
613 if (fgets(cmd, sizeof(cmd), stdin) == NULL) {
614 printf("\n");
615 break;
616 }
617 cp = strrchr(cmd, '\n');
618 if (cp)
619 *cp = '\0';
620 if (parse_dispatch_command(fd_in, fd_out, cmd, &pwd))
621 break;
622 }
623 xfree(pwd);
624}
This page took 0.145746 seconds and 5 git commands to generate.