svnno****@sourc*****
svnno****@sourc*****
2012年 8月 6日 (月) 19:41:43 JST
Revision: 4973 http://sourceforge.jp/projects/ttssh2/scm/svn/commits/4973 Author: yutakapon Date: 2012-08-06 19:41:42 +0900 (Mon, 06 Aug 2012) Log Message: ----------- * SFTP: コマンドラインヘルプを追加した。 Modified Paths: -------------- trunk/ttssh2/ttxssh/sftp.c -------------- next part -------------- Modified: trunk/ttssh2/ttxssh/sftp.c =================================================================== --- trunk/ttssh2/ttxssh/sftp.c 2012-07-25 11:29:28 UTC (rev 4972) +++ trunk/ttssh2/ttxssh/sftp.c 2012-08-06 10:41:42 UTC (rev 4973) @@ -54,13 +54,96 @@ #define WM_USER_CONSOLE (WM_USER + 1) +/* Separators for interactive commands */ +#define WHITESPACE " \t\r\n" + +/* Commands for interactive mode */ +#define I_CHDIR 1 +#define I_CHGRP 2 +#define I_CHMOD 3 +#define I_CHOWN 4 +#define I_DF 24 +#define I_GET 5 +#define I_HELP 6 +#define I_LCHDIR 7 +#define I_LINK 25 +#define I_LLS 8 +#define I_LMKDIR 9 +#define I_LPWD 10 +#define I_LS 11 +#define I_LUMASK 12 +#define I_MKDIR 13 +#define I_PUT 14 +#define I_PWD 15 +#define I_QUIT 16 +#define I_RENAME 17 +#define I_RM 18 +#define I_RMDIR 19 +#define I_SHELL 20 +#define I_SYMLINK 21 +#define I_VERSION 22 +#define I_PROGRESS 23 + +/* Type of completion */ +#define NOARGS 0 +#define REMOTE 1 +#define LOCAL 2 + +struct CMD { + char *c; + int n; + int t; +}; + +static const struct CMD cmds[] = { + { "bye", I_QUIT, NOARGS }, + { "cd", I_CHDIR, REMOTE }, + { "chdir", I_CHDIR, REMOTE }, + { "chgrp", I_CHGRP, REMOTE }, + { "chmod", I_CHMOD, REMOTE }, + { "chown", I_CHOWN, REMOTE }, + { "df", I_DF, REMOTE }, + { "dir", I_LS, REMOTE }, + { "exit", I_QUIT, NOARGS }, + { "get", I_GET, REMOTE }, + { "help", I_HELP, NOARGS }, + { "lcd", I_LCHDIR, LOCAL }, + { "lchdir", I_LCHDIR, LOCAL }, + { "lls", I_LLS, LOCAL }, + { "lmkdir", I_LMKDIR, LOCAL }, + { "ln", I_LINK, REMOTE }, + { "lpwd", I_LPWD, LOCAL }, + { "ls", I_LS, REMOTE }, + { "lumask", I_LUMASK, NOARGS }, + { "mkdir", I_MKDIR, REMOTE }, + { "mget", I_GET, REMOTE }, + { "mput", I_PUT, LOCAL }, + { "progress", I_PROGRESS, NOARGS }, + { "put", I_PUT, LOCAL }, + { "pwd", I_PWD, REMOTE }, + { "quit", I_QUIT, NOARGS }, + { "rename", I_RENAME, REMOTE }, + { "rm", I_RM, REMOTE }, + { "rmdir", I_RMDIR, REMOTE }, + { "symlink", I_SYMLINK, REMOTE }, + { "version", I_VERSION, NOARGS }, + { "!", I_SHELL, NOARGS }, + { "?", I_HELP, NOARGS }, + { NULL, -1, -1 } +}; + + + +static PTInstVar g_pvar; +static Channel_t *g_channel; + static void sftp_console_message(PTInstVar pvar, Channel_t *c, char *fmt, ...) { char tmp[1024]; va_list arg; va_start(arg, fmt); - _vsnprintf(tmp, sizeof(tmp), fmt, arg); + _vsnprintf_s(tmp, sizeof(tmp), _TRUNCATE, fmt, arg); va_end(arg); SendMessage(c->sftp.console_window, WM_USER_CONSOLE, 0, (LPARAM)tmp); @@ -73,7 +156,7 @@ va_list arg; va_start(arg, fmt); - _vsnprintf(tmp, sizeof(tmp), fmt, arg); + _vsnprintf_s(tmp, sizeof(tmp), _TRUNCATE, fmt, arg); va_end(arg); notify_verbose_message(pvar, tmp, level); @@ -85,7 +168,7 @@ va_list arg; va_start(arg, fmt); - _vsnprintf(tmp, sizeof(tmp), fmt, arg); + _vsnprintf_s(tmp, sizeof(tmp), _TRUNCATE, fmt, arg); va_end(arg); notify_verbose_message(pvar, tmp, LOG_LEVEL_VERBOSE); @@ -344,6 +427,384 @@ } +u_int +sftp_proto_version(struct sftp *conn) +{ + return conn->version; +} + +static void +help(void) +{ + sftp_console_message(g_pvar, g_channel, + "Available commands:\r\n" + "bye Quit sftp\r\n" + "cd path Change remote directory to 'path'\r\n" + "chgrp grp path Change group of file 'path' to 'grp'\r\n" + "chmod mode path Change permissions of file 'path' to 'mode'\r\n" + "chown own path Change owner of file 'path' to 'own'\r\n" + "df [-hi] [path] Display statistics for current directory or\r\n" + " filesystem containing 'path'\r\n" + "exit Quit sftp\r\n" + "get [-Ppr] remote [local] Download file\r\n" + "help Display this help text\r\n" + "lcd path Change local directory to 'path'\r\n" + "lls [ls-options [path]] Display local directory listing\r\n" + "lmkdir path Create local directory\r\n" + "ln [-s] oldpath newpath Link remote file (-s for symlink)\r\n" + "lpwd Print local working directory\r\n" + "ls [-1afhlnrSt] [path] Display remote directory listing\r\n" + "lumask umask Set local umask to 'umask'\r\n" + "mkdir path Create remote directory\r\n" + "progress Toggle display of progress meter\r\n" + "put [-Ppr] local [remote] Upload file\r\n" + "pwd Display remote working directory\r\n" + "quit Quit sftp\r\n" + "rename oldpath newpath Rename remote file\r\n" + "rm path Delete remote file\r\n" + "rmdir path Remove remote directory\r\n" + "symlink oldpath newpath Symlink remote file\r\n" + "version Show SFTP version\r\n" + "!command Execute 'command' in local shell\r\n" + "! Escape to local shell\r\n" + "? Synonym for help\r\n"); +} + +/* + * Split a string into an argument vector using sh(1)-style quoting, + * comment and escaping rules, but with some tweaks to handle glob(3) + * wildcards. + * The "sloppy" flag allows for recovery from missing terminating quote, for + * use in parsing incomplete commandlines during tab autocompletion. + * + * Returns NULL on error or a NULL-terminated array of arguments. + * + * If "lastquote" is not NULL, the quoting character used for the last + * argument is placed in *lastquote ("\0", "'" or "\""). + * + * If "terminated" is not NULL, *terminated will be set to 1 when the + * last argument's quote has been properly terminated or 0 otherwise. + * This parameter is only of use if "sloppy" is set. + */ +#define MAXARGS 128 +#define MAXARGLEN 8192 +static char ** +makeargv(const char *arg, int *argcp, int sloppy, char *lastquote, + u_int *terminated) +{ + int argc, quot; + size_t i, j; + static char argvs[MAXARGLEN]; + static char *argv[MAXARGS + 1]; + enum { MA_START, MA_SQUOTE, MA_DQUOTE, MA_UNQUOTED } state, q; + + *argcp = argc = 0; + if (strlen(arg) > sizeof(argvs) - 1) { + args_too_longs: + sftp_syslog(g_pvar, "string too long"); + return NULL; + } + if (terminated != NULL) + *terminated = 1; + if (lastquote != NULL) + *lastquote = '\0'; + state = MA_START; + i = j = 0; + for (;;) { + if (isspace(arg[i])) { + if (state == MA_UNQUOTED) { + /* Terminate current argument */ + argvs[j++] = '\0'; + argc++; + state = MA_START; + } else if (state != MA_START) + argvs[j++] = arg[i]; + } else if (arg[i] == '"' || arg[i] == '\'') { + q = arg[i] == '"' ? MA_DQUOTE : MA_SQUOTE; + if (state == MA_START) { + argv[argc] = argvs + j; + state = q; + if (lastquote != NULL) + *lastquote = arg[i]; + } else if (state == MA_UNQUOTED) + state = q; + else if (state == q) + state = MA_UNQUOTED; + else + argvs[j++] = arg[i]; + } else if (arg[i] == '\\') { + if (state == MA_SQUOTE || state == MA_DQUOTE) { + quot = state == MA_SQUOTE ? '\'' : '"'; + /* Unescape quote we are in */ + /* XXX support \n and friends? */ + if (arg[i + 1] == quot) { + i++; + argvs[j++] = arg[i]; + } else if (arg[i + 1] == '?' || + arg[i + 1] == '[' || arg[i + 1] == '*') { + /* + * Special case for sftp: append + * double-escaped glob sequence - + * glob will undo one level of + * escaping. NB. string can grow here. + */ + if (j >= sizeof(argvs) - 5) + goto args_too_longs; + argvs[j++] = '\\'; + argvs[j++] = arg[i++]; + argvs[j++] = '\\'; + argvs[j++] = arg[i]; + } else { + argvs[j++] = arg[i++]; + argvs[j++] = arg[i]; + } + } else { + if (state == MA_START) { + argv[argc] = argvs + j; + state = MA_UNQUOTED; + if (lastquote != NULL) + *lastquote = '\0'; + } + if (arg[i + 1] == '?' || arg[i + 1] == '[' || + arg[i + 1] == '*' || arg[i + 1] == '\\') { + /* + * Special case for sftp: append + * escaped glob sequence - + * glob will undo one level of + * escaping. + */ + argvs[j++] = arg[i++]; + argvs[j++] = arg[i]; + } else { + /* Unescape everything */ + /* XXX support \n and friends? */ + i++; + argvs[j++] = arg[i]; + } + } + } else if (arg[i] == '#') { + if (state == MA_SQUOTE || state == MA_DQUOTE) + argvs[j++] = arg[i]; + else + goto string_done; + } else if (arg[i] == '\0') { + if (state == MA_SQUOTE || state == MA_DQUOTE) { + if (sloppy) { + state = MA_UNQUOTED; + if (terminated != NULL) + *terminated = 0; + goto string_done; + } + sftp_syslog(g_pvar, "Unterminated quoted argument"); + return NULL; + } + string_done: + if (state == MA_UNQUOTED) { + argvs[j++] = '\0'; + argc++; + } + break; + } else { + if (state == MA_START) { + argv[argc] = argvs + j; + state = MA_UNQUOTED; + if (lastquote != NULL) + *lastquote = '\0'; + } + if ((state == MA_SQUOTE || state == MA_DQUOTE) && + (arg[i] == '?' || arg[i] == '[' || arg[i] == '*')) { + /* + * Special case for sftp: escape quoted + * glob(3) wildcards. NB. string can grow + * here. + */ + if (j >= sizeof(argvs) - 3) + goto args_too_longs; + argvs[j++] = '\\'; + argvs[j++] = arg[i]; + } else + argvs[j++] = arg[i]; + } + i++; + } + *argcp = argc; + return argv; +} + +static int parse_args(const char **cpp, int *pflag, int *rflag, int *lflag, int *iflag, + int *hflag, int *sflag, unsigned long *n_arg, char **path1, char **path2) +{ + const char *cmd, *cp = *cpp; + char *cp2 = NULL, **argv; + int base = 0; + long l = 0; + int i, cmdnum, optidx, argc; + + /* Skip leading whitespace */ + cp = cp + strspn(cp, WHITESPACE); + + /* Check for leading '-' (disable error processing) */ + *iflag = 0; + if (*cp == '-') { + *iflag = 1; + cp++; + cp = cp + strspn(cp, WHITESPACE); + } + + /* Ignore blank lines and lines which begin with comment '#' char */ + if (*cp == '\0' || *cp == '#') + return (0); + + if ((argv = makeargv(cp, &argc, 0, NULL, NULL)) == NULL) + return -1; + + /* Figure out which command we have */ + for (i = 0; cmds[i].c != NULL; i++) { + if (_strcmpi(cmds[i].c, argv[0]) == 0) + break; + } + cmdnum = cmds[i].n; + cmd = cmds[i].c; + + /* Special case */ + if (*cp == '!') { + cp++; + cmdnum = I_SHELL; + } else if (cmdnum == -1) { + sftp_syslog(g_pvar, "Invalid command."); + return -1; + } + + /* Get arguments and parse flags */ + *lflag = *pflag = *rflag = *hflag = *n_arg = 0; + *path1 = *path2 = NULL; + optidx = 1; + switch (cmdnum) { +#if 0 + case I_GET: + case I_PUT: + if ((optidx = parse_getput_flags(cmd, argv, argc, + pflag, rflag)) == -1) + return -1; + /* Get first pathname (mandatory) */ + if (argc - optidx < 1) { + error("You must specify at least one path after a " + "%s command.", cmd); + return -1; + } + *path1 = xstrdup(argv[optidx]); + /* Get second pathname (optional) */ + if (argc - optidx > 1) { + *path2 = xstrdup(argv[optidx + 1]); + /* Destination is not globbed */ + undo_glob_escape(*path2); + } + break; + case I_LINK: + if ((optidx = parse_link_flags(cmd, argv, argc, sflag)) == -1) + return -1; + case I_SYMLINK: + case I_RENAME: + if (argc - optidx < 2) { + error("You must specify two paths after a %s " + "command.", cmd); + return -1; + } + *path1 = xstrdup(argv[optidx]); + *path2 = xstrdup(argv[optidx + 1]); + /* Paths are not globbed */ + undo_glob_escape(*path1); + undo_glob_escape(*path2); + break; + case I_RM: + case I_MKDIR: + case I_RMDIR: + case I_CHDIR: + case I_LCHDIR: + case I_LMKDIR: + /* Get pathname (mandatory) */ + if (argc - optidx < 1) { + error("You must specify a path after a %s command.", + cmd); + return -1; + } + *path1 = xstrdup(argv[optidx]); + /* Only "rm" globs */ + if (cmdnum != I_RM) + undo_glob_escape(*path1); + break; + case I_DF: + if ((optidx = parse_df_flags(cmd, argv, argc, hflag, + iflag)) == -1) + return -1; + /* Default to current directory if no path specified */ + if (argc - optidx < 1) + *path1 = NULL; + else { + *path1 = xstrdup(argv[optidx]); + undo_glob_escape(*path1); + } + break; + case I_LS: + if ((optidx = parse_ls_flags(argv, argc, lflag)) == -1) + return(-1); + /* Path is optional */ + if (argc - optidx > 0) + *path1 = xstrdup(argv[optidx]); + break; + case I_LLS: + /* Skip ls command and following whitespace */ + cp = cp + strlen(cmd) + strspn(cp, WHITESPACE); + case I_SHELL: + /* Uses the rest of the line */ + break; + case I_LUMASK: + case I_CHMOD: + base = 8; + case I_CHOWN: + case I_CHGRP: + /* Get numeric arg (mandatory) */ + if (argc - optidx < 1) + goto need_num_arg; + errno = 0; + l = strtol(argv[optidx], &cp2, base); + if (cp2 == argv[optidx] || *cp2 != '\0' || + ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE) || + l < 0) { + need_num_arg: + error("You must supply a numeric argument " + "to the %s command.", cmd); + return -1; + } + *n_arg = l; + if (cmdnum == I_LUMASK) + break; + /* Get pathname (mandatory) */ + if (argc - optidx < 2) { + error("You must specify a path after a %s command.", + cmd); + return -1; + } + *path1 = xstrdup(argv[optidx + 1]); + break; +#endif + case I_QUIT: + case I_PWD: + case I_LPWD: + case I_HELP: + case I_VERSION: + case I_PROGRESS: + break; + default: + //fatal("Command not implemented"); + return -1; + } + + *cpp = cp; + return(cmdnum); +} + + /* * SFTP \x83R\x83}\x83\x93\x83h\x83\x89\x83C\x83\x93\x83R\x83\x93\x83\\x81[\x83\x8B */ @@ -351,24 +812,266 @@ static LRESULT CALLBACK EditProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + char buf[512]; + char *cmd; + char *path1, *path2, *tmp = NULL; + int pflag = 0, rflag = 0, lflag = 0, iflag = 0, hflag = 0, sflag = 0; + int cmdnum, i = 0; + unsigned long n_arg = 0; + //Attrib a, *aa; + char path_buf[1024] = {0}; + int err = 0; + //glob_t g; + int err_abort; + switch (uMsg) { case WM_KEYDOWN: if ((int)wParam == VK_RETURN) { - char buf[512]; - GetWindowText(hwnd, buf, sizeof(buf)); SetWindowText(hwnd, ""); if (buf[0] != '\0') { SendDlgItemMessage(GetParent(hwnd), IDC_SFTP_CONSOLE, EM_REPLACESEL, 0, (LPARAM) buf); SendDlgItemMessage(GetParent(hwnd), IDC_SFTP_CONSOLE, EM_REPLACESEL, 0, (LPARAM) (char FAR *) "\r\n"); + goto cmd_parsed; } } break; default: return (CallWindowProc(hEditProc, hwnd, uMsg, wParam, lParam)); } + return 0L; +cmd_parsed: + // \x83R\x83}\x83\x93\x83h\x83\x89\x83C\x83\x93\x89\xF0\x90\xCD + path1 = path2 = NULL; + cmd = buf; + cmdnum = parse_args(&cmd, &pflag, &rflag, &lflag, &iflag, &hflag, + &sflag, &n_arg, &path1, &path2); + + if (iflag != 0) + err_abort = 0; + + //memset(&g, 0, sizeof(g)); + + /* Perform command */ + switch (cmdnum) { + case 0: + /* Blank line */ + break; + case -1: + /* Unrecognized command */ + err = -1; + break; +#if 0 + case I_GET: + err = process_get(conn, path1, path2, *pwd, pflag, rflag); + break; + case I_PUT: + err = process_put(conn, path1, path2, *pwd, pflag, rflag); + break; + case I_RENAME: + path1 = make_absolute(path1, *pwd); + path2 = make_absolute(path2, *pwd); + err = do_rename(conn, path1, path2); + break; + case I_SYMLINK: + sflag = 1; + case I_LINK: + path1 = make_absolute(path1, *pwd); + path2 = make_absolute(path2, *pwd); + err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2); + break; + case I_RM: + path1 = make_absolute(path1, *pwd); + remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); + for (i = 0; g.gl_pathv[i] && !interrupted; i++) { + printf("Removing %s\n", g.gl_pathv[i]); + err = do_rm(conn, g.gl_pathv[i]); + if (err != 0 && err_abort) + break; + } + break; + case I_MKDIR: + path1 = make_absolute(path1, *pwd); + attrib_clear(&a); + a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS; + a.perm = 0777; + err = do_mkdir(conn, path1, &a, 1); + break; + case I_RMDIR: + path1 = make_absolute(path1, *pwd); + err = do_rmdir(conn, path1); + break; + case I_CHDIR: + path1 = make_absolute(path1, *pwd); + if ((tmp = do_realpath(conn, path1)) == NULL) { + err = 1; + break; + } + if ((aa = do_stat(conn, tmp, 0)) == NULL) { + xfree(tmp); + err = 1; + break; + } + if (!(aa->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) { + error("Can't change directory: Can't check target"); + xfree(tmp); + err = 1; + break; + } + if (!S_ISDIR(aa->perm)) { + error("Can't change directory: \"%s\" is not " + "a directory", tmp); + xfree(tmp); + err = 1; + break; + } + xfree(*pwd); + *pwd = tmp; + break; + case I_LS: + if (!path1) { + do_ls_dir(conn, *pwd, *pwd, lflag); + break; + } + + /* Strip pwd off beginning of non-absolute paths */ + tmp = NULL; + if (*path1 != '/') + tmp = *pwd; + + path1 = make_absolute(path1, *pwd); + err = do_globbed_ls(conn, path1, tmp, lflag); + break; + case I_DF: + /* Default to current directory if no path specified */ + if (path1 == NULL) + path1 = xstrdup(*pwd); + path1 = make_absolute(path1, *pwd); + err = do_df(conn, path1, hflag, iflag); + break; + case I_LCHDIR: + if (chdir(path1) == -1) { + error("Couldn't change local directory to " + "\"%s\": %s", path1, strerror(errno)); + err = 1; + } + break; + case I_LMKDIR: + if (mkdir(path1, 0777) == -1) { + error("Couldn't create local directory " + "\"%s\": %s", path1, strerror(errno)); + err = 1; + } + break; + case I_LLS: + local_do_ls(cmd); + break; + case I_SHELL: + local_do_shell(cmd); + break; + case I_LUMASK: + umask(n_arg); + printf("Local umask: %03lo\n", n_arg); + break; + case I_CHMOD: + path1 = make_absolute(path1, *pwd); + attrib_clear(&a); + a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS; + a.perm = n_arg; + remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); + for (i = 0; g.gl_pathv[i] && !interrupted; i++) { + printf("Changing mode on %s\n", g.gl_pathv[i]); + err = do_setstat(conn, g.gl_pathv[i], &a); + if (err != 0 && err_abort) + break; + } + break; + case I_CHOWN: + case I_CHGRP: + path1 = make_absolute(path1, *pwd); + remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g); + for (i = 0; g.gl_pathv[i] && !interrupted; i++) { + if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) { + if (err_abort) { + err = -1; + break; + } else + continue; + } + if (!(aa->flags & SSH2_FILEXFER_ATTR_UIDGID)) { + error("Can't get current ownership of " + "remote file \"%s\"", g.gl_pathv[i]); + if (err_abort) { + err = -1; + break; + } else + continue; + } + aa->flags &= SSH2_FILEXFER_ATTR_UIDGID; + if (cmdnum == I_CHOWN) { + printf("Changing owner on %s\n", g.gl_pathv[i]); + aa->uid = n_arg; + } else { + printf("Changing group on %s\n", g.gl_pathv[i]); + aa->gid = n_arg; + } + err = do_setstat(conn, g.gl_pathv[i], aa); + if (err != 0 && err_abort) + break; + } + break; + case I_PWD: + printf("Remote working directory: %s\n", *pwd); + break; + case I_LPWD: + if (!getcwd(path_buf, sizeof(path_buf))) { + error("Couldn't get local cwd: %s", strerror(errno)); + err = -1; + break; + } + printf("Local working directory: %s\n", path_buf); + break; +#endif + case I_QUIT: + /* Processed below */ + break; + case I_HELP: + help(); + break; + case I_VERSION: + printf("SFTP protocol version %u\n", sftp_proto_version(&g_channel->sftp)); + break; +#if 0 + case I_PROGRESS: + showprogress = !showprogress; + if (showprogress) + printf("Progress meter enabled\n"); + else + printf("Progress meter disabled\n"); + break; +#endif + default: + //fatal("%d is not implemented", cmdnum); + err = -1; + } + +#if 0 + if (g.gl_pathc) + globfree(&g); + if (path1) + xfree(path1); + if (path2) + xfree(path2); + + /* If an unignored error occurs in batch mode we should abort. */ + if (err_abort && err != 0) + return (-1); + else if (cmdnum == I_QUIT) + return (1); +#endif + return 0L; } @@ -462,6 +1165,10 @@ sftp_get_msg(pvar, c, data, buflen, &msg); if (c->sftp.state == SFTP_INIT) { + // \x83O\x83\x8D\x81[\x83o\x83\x8B\x95ϐ\x94\x82ɕۑ\xB6\x82\xB7\x82\xE9\x81B + g_pvar = pvar; + g_channel = c; + sftp_do_init_recv(pvar, c, msg); // \x83R\x83\x93\x83\\x81[\x83\x8B\x82\xF0\x8BN\x93\xAE\x82\xB7\x82\xE9\x81B