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