]> andersk Git - openssh.git/blob - sftp-int.c
- djm@cvs.openbsd.org 2001/02/14 9:46:03
[openssh.git] / sftp-int.c
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 */
27 /* XXX: globbed ls */
28 /* XXX: recursive operations */
29
30 #include "includes.h"
31 RCSID("$OpenBSD: sftp-int.c,v 1.22 2001/02/14 09:46:03 djm Exp $");
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
68 struct CMD {
69         const char *c;
70         const int n;
71 };
72
73 const struct CMD cmds[] = {
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 },
97         { "!",          I_SHELL },
98         { "?",          I_HELP },
99         { NULL,                 -1}
100 };
101
102 void
103 help(void)
104 {
105         printf("Available commands:\n");
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");
126         printf("!command                      Execute 'command' in local shell\n");
127         printf("!                             Escape to local shell\n");
128         printf("?                             Synonym for help\n");
129 }
130
131 void
132 local_do_shell(const char *args)
133 {
134         int ret, status;
135         char *shell;
136         pid_t pid;
137
138         if (!*args)
139                 args = NULL;
140
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                 }
156                 fprintf(stderr, "Couldn't execute \"%s\": %s\n", shell,
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
168 void
169 local_do_ls(const char *args)
170 {
171         if (!args || !*args)
172                 local_do_shell(_PATH_LS);
173         else {
174                 int len = strlen(_PATH_LS " ") + strlen(args) + 1;
175                 char *buf = xmalloc(len);
176
177                 /* XXX: quoting - rip quoting code from ftp? */
178                 snprintf(buf, len, _PATH_LS " %s", args);
179                 local_do_shell(buf);
180                 xfree(buf);
181         }
182 }
183
184 char *
185 make_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
199 int
200 parse_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])) {
206                 switch (cp[1]) {
207                 case 'p':
208                 case 'P':
209                         *pflag = 1;
210                         break;
211                 default:
212                         error("Invalid flag -%c", cp[1]);
213                         return(-1);
214                 }
215                 cp += 2;
216                 *cpp = cp + strspn(cp, WHITESPACE);
217         }
218
219         return(0);
220 }
221
222 int
223 get_pathname(const char **cpp, char **path)
224 {
225         const char *cp = *cpp, *end;
226         char quot;
227         int i;
228
229         cp += strspn(cp, WHITESPACE);
230         if (!*cp) {
231                 *cpp = cp;
232                 *path = NULL;
233                 return (0);
234         }
235
236         /* Check for quoted filenames */
237         if (*cp == '\"' || *cp == '\'') {
238                 quot = *cp++;
239                 
240                 end = strchr(cp, quot);
241                 if (end == NULL) {
242                         error("Unterminated quote");
243                         goto fail;
244                 }
245                 if (cp == end) {
246                         error("Empty quotes");
247                         goto fail;
248                 }
249                 *cpp = end + 1 + strspn(end + 1, WHITESPACE);
250         } else {
251                 /* Read to end of filename */
252                 end = strpbrk(cp, WHITESPACE);
253                 if (end == NULL)
254                         end = strchr(cp, '\0');
255                 *cpp = end + strspn(end, WHITESPACE);
256         }
257
258         i = end - cp;
259
260         *path = xmalloc(i + 1);
261         memcpy(*path, cp, i);
262         (*path)[i] = '\0';
263         return(0);
264
265  fail:
266         *path = NULL;
267         return (-1);
268 }
269
270 int
271 infer_path(const char *p, char **ifp)
272 {
273         char *cp;
274
275         debug("XXX: P = \"%s\"", p);
276
277         cp = strrchr(p, '/');
278         if (cp == NULL) {
279                 *ifp = xstrdup(p);
280                 return(0);
281         }
282
283         if (!cp[1]) {
284                 error("Invalid path");
285                 return(-1);
286         }
287
288         *ifp = xstrdup(cp + 1);
289         return(0);
290 }
291
292 int
293 parse_args(const char **cpp, int *pflag, unsigned long *n_arg,
294     char **path1, char **path2)
295 {
296         const char *cmd, *cp = *cpp;
297         char *cp2;
298         int base = 0;
299         long l;
300         int i, cmdnum;
301
302         /* Skip leading whitespace */
303         cp = cp + strspn(cp, WHITESPACE);
304
305         /* Ignore blank lines */
306         if (!*cp)
307                 return(-1);
308
309         /* Figure out which command we have */
310         for(i = 0; cmds[i].c; i++) {
311                 int cmdlen = strlen(cmds[i].c);
312
313                 /* Check for command followed by whitespace */
314                 if (!strncasecmp(cp, cmds[i].c, cmdlen) &&
315                     strchr(WHITESPACE, cp[cmdlen])) {
316                         cp += cmdlen;
317                         cp = cp + strspn(cp, WHITESPACE);
318                         break;
319                 }
320         }
321         cmdnum = cmds[i].n;
322         cmd = cmds[i].c;
323
324         /* Special case */
325         if (*cp == '!') {
326                 cp++;
327                 cmdnum = I_SHELL;
328         } else if (cmdnum == -1) {
329                 error("Invalid command.");
330                 return(-1);
331         }
332
333         /* Get arguments and parse flags */
334         *pflag = *n_arg = 0;
335         *path1 = *path2 = NULL;
336         switch (cmdnum) {
337         case I_GET:
338         case I_PUT:
339                 if (parse_getput_flags(&cp, pflag))
340                         return(-1);
341                 /* Get first pathname (mandatory) */
342                 if (get_pathname(&cp, path1))
343                         return(-1);
344                 if (*path1 == NULL) {
345                         error("You must specify at least one path after a "
346                             "%s command.", cmd);
347                         return(-1);
348                 }
349                 /* Try to get second pathname (optional) */
350                 if (get_pathname(&cp, path2))
351                         return(-1);
352                 /* Otherwise try to guess it from first path */
353                 if (*path2 == NULL && infer_path(*path1, path2))
354                         return(-1);
355                 break;
356         case I_RENAME:
357                 /* Get first pathname (mandatory) */
358                 if (get_pathname(&cp, path1))
359                         return(-1);
360                 if (get_pathname(&cp, path2))
361                         return(-1);
362                 if (!*path1 || !*path2) {
363                         error("You must specify two paths after a %s "
364                             "command.", cmd);
365                         return(-1);
366                 }
367                 break;
368         case I_RM:
369         case I_MKDIR:
370         case I_RMDIR:
371         case I_CHDIR:
372         case I_LCHDIR:
373         case I_LMKDIR:
374                 /* Get pathname (mandatory) */
375                 if (get_pathname(&cp, path1))
376                         return(-1);
377                 if (*path1 == NULL) {
378                         error("You must specify a path after a %s command.",
379                             cmd);
380                         return(-1);
381                 }
382                 break;
383         case I_LS:
384                 /* Path is optional */
385                 if (get_pathname(&cp, path1))
386                         return(-1);
387                 break;
388         case I_LLS:
389         case I_SHELL:
390                 /* Uses the rest of the line */
391                 break;
392         case I_LUMASK:
393                 base = 8;
394         case I_CHMOD:
395                 base = 8;
396         case I_CHOWN:
397         case I_CHGRP:
398                 /* Get numeric arg (mandatory) */
399                 l = strtol(cp, &cp2, base);
400                 if (cp2 == cp || ((l == LONG_MIN || l == LONG_MAX) &&
401                     errno == ERANGE) || l < 0) {
402                         error("You must supply a numeric argument "
403                             "to the %s command.", cmd);
404                         return(-1);
405                 }
406                 cp = cp2;
407                 *n_arg = l;
408                 if (cmdnum == I_LUMASK && strchr(WHITESPACE, *cp))
409                         break;
410                 if (cmdnum == I_LUMASK || !strchr(WHITESPACE, *cp)) {
411                         error("You must supply a numeric argument "
412                             "to the %s command.", cmd);
413                         return(-1);
414                 }
415                 cp += strspn(cp, WHITESPACE);
416
417                 /* Get pathname (mandatory) */
418                 if (get_pathname(&cp, path1))
419                         return(-1);
420                 if (*path1 == NULL) {
421                         error("You must specify a path after a %s command.",
422                             cmd);
423                         return(-1);
424                 }
425                 break;
426         case I_QUIT:
427         case I_PWD:
428         case I_LPWD:
429         case I_HELP:
430                 break;
431         default:
432                 fatal("Command not implemented");
433         }
434
435         *cpp = cp;
436         return(cmdnum);
437 }
438
439 int
440 parse_dispatch_command(int in, int out, const char *cmd, char **pwd)
441 {
442         char *path1, *path2, *tmp;
443         int pflag, cmdnum;
444         unsigned long n_arg;
445         Attrib a, *aa;
446         char path_buf[PATH_MAX];
447
448         path1 = path2 = NULL;
449         cmdnum = parse_args(&cmd, &pflag, &n_arg, &path1, &path2);
450
451         /* Perform command */
452         switch (cmdnum) {
453         case -1:
454                 break;
455         case I_GET:
456                 path1 = make_absolute(path1, *pwd);
457                 do_download(in, out, path1, path2, pflag);
458                 break;
459         case I_PUT:
460                 path2 = make_absolute(path2, *pwd);
461                 do_upload(in, out, path1, path2, pflag);
462                 break;
463         case I_RENAME:
464                 path1 = make_absolute(path1, *pwd);
465                 path2 = make_absolute(path2, *pwd);
466                 do_rename(in, out, path1, path2);
467                 break;
468         case I_RM:
469                 path1 = make_absolute(path1, *pwd);
470                 do_rm(in, out, path1);
471                 break;
472         case I_MKDIR:
473                 path1 = make_absolute(path1, *pwd);
474                 attrib_clear(&a);
475                 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
476                 a.perm = 0777;
477                 do_mkdir(in, out, path1, &a);
478                 break;
479         case I_RMDIR:
480                 path1 = make_absolute(path1, *pwd);
481                 do_rmdir(in, out, path1);
482                 break;
483         case I_CHDIR:
484                 path1 = make_absolute(path1, *pwd);
485                 if ((tmp = do_realpath(in, out, path1)) == NULL)
486                         break;
487                 if ((aa = do_stat(in, out, tmp)) == NULL) {
488                         xfree(tmp);
489                         break;
490                 }
491                 if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) {
492                         error("Can't change directory: Can't check target");
493                         xfree(tmp);
494                         break;
495                 }
496                 if (!S_ISDIR(aa->perm)) {
497                         error("Can't change directory: \"%s\" is not "
498                             "a directory", tmp);
499                         xfree(tmp);
500                         break;
501                 }
502                 xfree(*pwd);
503                 *pwd = tmp;
504                 break;
505         case I_LS:
506                 if (!path1) {
507                         do_ls(in, out, *pwd);
508                         break;
509                 }
510                 path1 = make_absolute(path1, *pwd);
511                 if ((tmp = do_realpath(in, out, path1)) == NULL)
512                         break;
513                 xfree(path1);
514                 path1 = tmp;
515                 if ((aa = do_stat(in, out, path1)) == NULL)
516                         break;
517                 if ((aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) && 
518                     !S_ISDIR(aa->perm)) {
519                         error("Can't ls: \"%s\" is not a directory", path1);
520                         break;
521                 }
522                 do_ls(in, out, path1);
523                 break;
524         case I_LCHDIR:
525                 if (chdir(path1) == -1)
526                         error("Couldn't change local directory to "
527                             "\"%s\": %s", path1, strerror(errno));
528                 break;
529         case I_LMKDIR:
530                 if (mkdir(path1, 0777) == -1)
531                         error("Couldn't create local directory "
532                             "\"%s\": %s", path1, strerror(errno));
533                 break;
534         case I_LLS:
535                 local_do_ls(cmd);
536                 break;
537         case I_SHELL:
538                 local_do_shell(cmd);
539                 break;
540         case I_LUMASK:
541                 umask(n_arg);
542                 printf("Local umask: %03lo\n", n_arg);
543                 break;
544         case I_CHMOD:
545                 path1 = make_absolute(path1, *pwd);
546                 attrib_clear(&a);
547                 a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
548                 a.perm = n_arg;
549                 do_setstat(in, out, path1, &a);
550                 break;
551         case I_CHOWN:
552                 path1 = make_absolute(path1, *pwd);
553                 if (!(aa = do_stat(in, out, path1)))
554                         break;
555                 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
556                         error("Can't get current ownership of "
557                             "remote file \"%s\"", path1);
558                         break;
559                 }
560                 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
561                 aa->uid = n_arg;
562                 do_setstat(in, out, path1, aa);
563                 break;
564         case I_CHGRP:
565                 path1 = make_absolute(path1, *pwd);
566                 if (!(aa = do_stat(in, out, path1)))
567                         break;
568                 if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) {
569                         error("Can't get current ownership of "
570                             "remote file \"%s\"", path1);
571                         break;
572                 }
573                 aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
574                 aa->gid = n_arg;
575                 do_setstat(in, out, path1, aa);
576                 break;
577         case I_PWD:
578                 printf("Remote working directory: %s\n", *pwd);
579                 break;
580         case I_LPWD:
581                 if (!getcwd(path_buf, sizeof(path_buf)))
582                         error("Couldn't get local cwd: %s\n",
583                             strerror(errno));
584                 else
585                         printf("Local working directory: %s\n",
586                             path_buf);
587                 break;
588         case I_QUIT:
589                 return(-1);
590         case I_HELP:
591                 help();
592                 break;
593         default:
594                 fatal("%d is not implemented", cmdnum);
595         }
596
597         if (path1)
598                 xfree(path1);
599         if (path2)
600                 xfree(path2);
601         return(0);
602 }
603
604 void
605 interactive_loop(int fd_in, int fd_out)
606 {
607         char *pwd;
608         char cmd[2048];
609
610         pwd = do_realpath(fd_in, fd_out, ".");
611         if (pwd == NULL)
612                 fatal("Need cwd");
613
614         setvbuf(stdout, NULL, _IOLBF, 0);
615         setvbuf(stdin, NULL, _IOLBF, 0);
616
617         for(;;) {
618                 char *cp;
619
620                 printf("sftp> ");
621
622                 /* XXX: use libedit */
623                 if (fgets(cmd, sizeof(cmd), stdin) == NULL) {
624                         printf("\n");
625                         break;
626                 }
627                 cp = strrchr(cmd, '\n');
628                 if (cp)
629                         *cp = '\0';
630                 if (parse_dispatch_command(fd_in, fd_out, cmd, &pwd))
631                         break;
632         }
633         xfree(pwd);
634 }
This page took 0.090061 seconds and 5 git commands to generate.