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