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