diff --git a/cmdline.c b/cmdline.c index 773fb9b1..682b5979 100644 --- a/cmdline.c +++ b/cmdline.c @@ -31,7 +31,7 @@ #define NPRIORITIES 2 struct cmdline_saved_param { - char *p, *value; + CmdlineArg *p, *value; }; struct cmdline_saved_param_set { struct cmdline_saved_param *params; @@ -44,11 +44,11 @@ struct cmdline_saved_param_set { */ static struct cmdline_saved_param_set saves[NPRIORITIES]; -static void cmdline_save_param(const char *p, const char *value, int pri) +static void cmdline_save_param(CmdlineArg *p, CmdlineArg *value, int pri) { sgrowarray(saves[pri].params, saves[pri].savesize, saves[pri].nsaved); - saves[pri].params[saves[pri].nsaved].p = dupstr(p); - saves[pri].params[saves[pri].nsaved].value = dupstr(value); + saves[pri].params[saves[pri].nsaved].p = p; + saves[pri].params[saves[pri].nsaved].value = value; saves[pri].nsaved++; } @@ -73,7 +73,7 @@ void cmdline_cleanup(void) } #define SAVEABLE(pri) do { \ - if (need_save) { cmdline_save_param(p, value, pri); return ret; } \ + if (need_save) { cmdline_save_param(arg, nextarg, pri); return ret; } \ } while (0) /* @@ -190,10 +190,12 @@ static void set_port(Conf *conf, int port) conf_set_int(conf, CONF_port, port); } -int cmdline_process_param(const char *p, char *value, +int cmdline_process_param(CmdlineArg *arg, CmdlineArg *nextarg, int need_save, Conf *conf) { int ret = 0; + const char *p = cmdline_arg_to_str(arg); + const char *value = cmdline_arg_to_str(nextarg); if (p[0] != '-') { if (need_save < 0) @@ -408,9 +410,8 @@ int cmdline_process_param(const char *p, char *value, * pretend we received a -P argument, so that it will be * deferred until it's a good moment to run it. */ - char *dup = dupstr(p); /* 'value' is not a const char * */ - int retd = cmdline_process_param("-P", dup, 1, conf); - sfree(dup); + int retd = cmdline_process_param( + cmdline_arg_from_str(arg->list, "-P"), arg, 1, conf); assert(retd == 2); seen_port_argument = true; return 1; @@ -608,11 +609,9 @@ int cmdline_process_param(const char *p, char *value, } cmdline_password = dupstr(value); - /* Assuming that `value' is directly from argv, make a good faith - * attempt to trample it, to stop it showing up in `ps' output - * on Unix-like systems. Not guaranteed, of course. */ - smemclr(value, strlen(value)); } + + cmdline_arg_wipe(nextarg); } if (!strcmp(p, "-pwfile")) { @@ -779,7 +778,7 @@ int cmdline_process_param(const char *p, char *value, conf_set_int(conf, CONF_addressfamily, ADDRTYPE_IPV6); } if (!strcmp(p, "-sercfg")) { - char* nextitem; + const char *nextitem; RETURN(2); UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); SAVEABLE(1); @@ -796,7 +795,6 @@ int cmdline_process_param(const char *p, char *value, skip = 0; } else { length = end - nextitem; - nextitem[length] = '\0'; skip = 1; } if (length == 1) { @@ -851,7 +849,9 @@ int cmdline_process_param(const char *p, char *value, /* Messy special case */ conf_set_int(conf, CONF_serstopbits, 3); } else { - int serspeed = atoi(nextitem); + char *speedstr = dupprintf("%.*s", length, nextitem); + int serspeed = atoi(speedstr); + sfree(speedstr); if (serspeed != 0) { conf_set_int(conf, CONF_serspeed, serspeed); } else { @@ -957,12 +957,9 @@ int cmdline_process_param(const char *p, char *value, void cmdline_run_saved(Conf *conf) { for (size_t pri = 0; pri < NPRIORITIES; pri++) { - for (size_t i = 0; i < saves[pri].nsaved; i++) { + for (size_t i = 0; i < saves[pri].nsaved; i++) cmdline_process_param(saves[pri].params[i].p, saves[pri].params[i].value, 0, conf); - sfree(saves[pri].params[i].p); - sfree(saves[pri].params[i].value); - } saves[pri].nsaved = 0; } } diff --git a/defs.h b/defs.h index 3192f384..d8bfe02a 100644 --- a/defs.h +++ b/defs.h @@ -77,6 +77,8 @@ typedef struct conf_tag Conf; typedef struct ConfKeyInfo ConfKeyInfo; typedef struct ConfSaveEnumValue ConfSaveEnumValue; typedef struct ConfSaveEnumType ConfSaveEnumType; +typedef struct CmdlineArgList CmdlineArgList; +typedef struct CmdlineArg CmdlineArg; typedef struct terminal_tag Terminal; typedef struct term_utf8_decode term_utf8_decode; diff --git a/pscp.c b/pscp.c index d240ddd6..7edee6ab 100644 --- a/pscp.c +++ b/pscp.c @@ -535,10 +535,20 @@ static void print_stats(const char *name, uint64_t size, uint64_t done, } /* - * Find a colon in str and return a pointer to the colon. - * This is used to separate hostname from filename. + * Find a colon in str and return a pointer to the colon. + * This is used to separate hostname from filename. + * + * Colons in bracketed IPv6 address literals are ignored, because + * they're logically part of the hostname. + * + * Like strchr in the C standard library, we accept a const char * as + * input, and produce a mutable char * as output. The intention is + * that you EITHER pass a mutable char * input and use the mutability + * of the output, OR pass a const char * as input and don't use the + * mutability, but don't use this to silently launder consts off + * things. */ -static char *colon(char *str) +static char *colon(const char *str) { /* We ignore a leading colon, since the hostname cannot be empty. We also ignore a colon as second character because @@ -548,7 +558,7 @@ static char *colon(char *str) return (NULL); str += host_strcspn(str, ":/\\"); if (*str == ':') - return (str); + return (char *)str; else return (NULL); } @@ -1966,16 +1976,16 @@ static void sink(const char *targ, const char *src) /* * We will copy local files to a remote server. */ -static void toremote(int argc, char *argv[]) +static void toremote(CmdlineArg **args, size_t nargs) { - char *src, *wtarg, *host, *user; - const char *targ; + char *wtarg, *host, *user; + const char *src, *targ; char *cmd; - int i, wc_type; + int wc_type; uploading = true; - wtarg = argv[argc - 1]; + wtarg = dupstr(cmdline_arg_to_str(args[nargs - 1])); /* Separate host from filename */ host = wtarg; @@ -2001,13 +2011,14 @@ static void toremote(int argc, char *argv[]) user = NULL; } - if (argc == 2) { - if (colon(argv[0]) != NULL) - bump("%s: Remote to remote not supported", argv[0]); + if (nargs == 2) { + const char *arg0 = cmdline_arg_to_str(args[0]); + if (colon(arg0) != NULL) + bump("%s: Remote to remote not supported", arg0); - wc_type = test_wildcard(argv[0], true); + wc_type = test_wildcard(arg0, true); if (wc_type == WCTYPE_NONEXISTENT) - bump("%s: No such file or directory\n", argv[0]); + bump("%s: No such file or directory\n", arg0); else if (wc_type == WCTYPE_WILDCARD) targetshouldbedirectory = true; } @@ -2023,8 +2034,8 @@ static void toremote(int argc, char *argv[]) if (scp_source_setup(targ, targetshouldbedirectory)) return; - for (i = 0; i < argc - 1; i++) { - src = argv[i]; + for (size_t i = 0; i < nargs - 1; i++) { + src = cmdline_arg_to_str(args[i]); if (colon(src) != NULL) { tell_user(stderr, "%s: Remote to remote not supported\n", src); errs++; @@ -2061,19 +2072,19 @@ static void toremote(int argc, char *argv[]) /* * We will copy files from a remote server to the local machine. */ -static void tolocal(int argc, char *argv[]) +static void tolocal(CmdlineArg **args, size_t nargs) { - char *wsrc, *host, *user; + char *wsrc_orig, *wsrc, *host, *user; const char *src, *targ; char *cmd; uploading = false; - if (argc != 2) + if (nargs != 2) bump("More than one remote source not supported"); - wsrc = argv[0]; - targ = argv[1]; + wsrc = wsrc_orig = dupstr(cmdline_arg_to_str(args[0])); + targ = cmdline_arg_to_str(args[1]); /* Separate host from filename */ host = wsrc; @@ -2111,19 +2122,20 @@ static void tolocal(int argc, char *argv[]) return; sink(targ, src); + sfree(wsrc_orig); } /* * We will issue a list command to get a remote directory. */ -static void get_dir_list(int argc, char *argv[]) +static void get_dir_list(CmdlineArg **args, size_t nargs) { char *wsrc, *host, *user; const char *src; const char *q; char c; - wsrc = argv[0]; + wsrc = dupstr(cmdline_arg_to_str(args[0])); /* Separate host from filename */ host = wsrc; @@ -2173,6 +2185,8 @@ static void get_dir_list(int argc, char *argv[]) put_byte(scc, c); stripctrl_free(scc); } + + sfree(wsrc); } /* @@ -2256,9 +2270,8 @@ const unsigned cmdline_tooltype = TOOLTYPE_FILETRANSFER; * Main program. (Called `psftp_main' because it gets called from * *sftp.c; bit silly, I know, but it had to be called _something_.) */ -int psftp_main(int argc, char *argv[]) +int psftp_main(CmdlineArgList *arglist) { - int i; bool sanitise_stderr = true; sk_init(); @@ -2267,56 +2280,59 @@ int psftp_main(int argc, char *argv[]) conf = conf_new(); do_defaults(NULL, conf); - for (i = 1; i < argc; i++) { - int ret; - if (argv[i][0] != '-') + size_t arglistpos = 0; + while (arglist->args[arglistpos]) { + CmdlineArg *arg = arglist->args[arglistpos++]; + CmdlineArg *nextarg = arglist->args[arglistpos]; + const char *argstr = cmdline_arg_to_str(arg); + if (argstr[0] != '-') { + arglistpos--; /* logically push that argument back on the list */ break; - ret = cmdline_process_param(argv[i], i+1args + arglistpos; + size_t nscpargs = 0; + while (scpargs[nscpargs]) + nscpargs++; + if (list) { - if (argc != 1) + if (nscpargs != 1) usage(); - get_dir_list(argc, argv); + get_dir_list(scpargs, nscpargs); } else { - - if (argc < 2) + if (nscpargs < 2) usage(); - if (argc > 2) + if (nscpargs > 2) targetshouldbedirectory = true; - if (colon(argv[argc - 1]) != NULL) - toremote(argc, argv); + if (colon(cmdline_arg_to_str(scpargs[nscpargs - 1])) != NULL) + toremote(scpargs, nscpargs); else - tolocal(argc, argv); + tolocal(scpargs, nscpargs); } if (backend && backend_connected(backend)) { @@ -2355,6 +2375,7 @@ int psftp_main(int argc, char *argv[]) random_save_seed(); cmdline_cleanup(); + cmdline_arg_list_free(arglist); if (backend) { backend_free(backend); backend = NULL; diff --git a/psftp.c b/psftp.c index a33bb35a..7ac0bcfd 100644 --- a/psftp.c +++ b/psftp.c @@ -2393,7 +2393,7 @@ static void do_sftp_cleanup(void) } } -int do_sftp(int mode, int modeflags, char *batchfile) +int do_sftp(int mode, int modeflags, const char *batchfile) { FILE *fp; int ret; @@ -2793,15 +2793,15 @@ const unsigned cmdline_tooltype = TOOLTYPE_FILETRANSFER; /* * Main program. Parse arguments etc. */ -int psftp_main(int argc, char *argv[]) +int psftp_main(CmdlineArgList *arglist) { - int i, toret; + int toret; int portnumber = 0; char *userhost, *user; int mode = 0; int modeflags = 0; bool sanitise_stderr = true; - char *batchfile = NULL; + const char *batchfile = NULL; sk_init(); @@ -2811,55 +2811,57 @@ int psftp_main(int argc, char *argv[]) conf = conf_new(); do_defaults(NULL, conf); - for (i = 1; i < argc; i++) { - int retd; - if (argv[i][0] != '-') { + size_t arglistpos = 0; + while (arglist->args[arglistpos]) { + CmdlineArg *arg = arglist->args[arglistpos++]; + CmdlineArg *nextarg = arglist->args[arglistpos]; + const char *argstr = cmdline_arg_to_str(arg); + + if (argstr[0] != '-') { if (userhost) usage(); else - userhost = dupstr(argv[i]); + userhost = dupstr(argstr); continue; } - retd = cmdline_process_param( - argv[i], i+1 < argc ? argv[i+1] : NULL, 1, conf); + int retd = cmdline_process_param(arg, nextarg, 1, conf); if (retd == -2) { - cmdline_error("option \"%s\" requires an argument", argv[i]); + cmdline_error("option \"%s\" requires an argument", argstr); } else if (retd == 2) { - i++; /* skip next argument */ + arglistpos++; /* skip next argument */ } else if (retd == 1) { /* We have our own verbosity in addition to `flags'. */ if (cmdline_verbose()) verbose = true; - } else if (strcmp(argv[i], "-h") == 0 || - strcmp(argv[i], "-?") == 0 || - strcmp(argv[i], "--help") == 0) { + } else if (strcmp(argstr, "-h") == 0 || + strcmp(argstr, "-?") == 0 || + strcmp(argstr, "--help") == 0) { usage(); - } else if (strcmp(argv[i], "-pgpfp") == 0) { + } else if (strcmp(argstr, "-pgpfp") == 0) { pgp_fingerprints(); return 1; - } else if (strcmp(argv[i], "-V") == 0 || - strcmp(argv[i], "--version") == 0) { + } else if (strcmp(argstr, "-V") == 0 || + strcmp(argstr, "--version") == 0) { version(); - } else if (strcmp(argv[i], "-b") == 0 && i + 1 < argc) { + } else if (strcmp(argstr, "-b") == 0 && nextarg) { mode = 1; - batchfile = argv[++i]; - } else if (strcmp(argv[i], "-bc") == 0) { + batchfile = cmdline_arg_to_str(nextarg); + arglistpos++; + } else if (strcmp(argstr, "-bc") == 0) { modeflags = modeflags | 1; - } else if (strcmp(argv[i], "-be") == 0) { + } else if (strcmp(argstr, "-be") == 0) { modeflags = modeflags | 2; - } else if (strcmp(argv[i], "-sanitise-stderr") == 0) { + } else if (strcmp(argstr, "-sanitise-stderr") == 0) { sanitise_stderr = true; - } else if (strcmp(argv[i], "-no-sanitise-stderr") == 0) { + } else if (strcmp(argstr, "-no-sanitise-stderr") == 0) { sanitise_stderr = false; - } else if (strcmp(argv[i], "--") == 0) { - i++; + } else if (strcmp(argstr, "--") == 0) { + arglistpos++; break; } else { - cmdline_error("unknown option \"%s\"", argv[i]); + cmdline_error("unknown option \"%s\"", argstr); } } - argc -= i; - argv += i; backend = NULL; stdio_sink_init(&stderr_ss, stderr); @@ -2897,6 +2899,8 @@ int psftp_main(int argc, char *argv[]) " to connect\n"); } + cmdline_arg_list_free(arglist); + toret = do_sftp(mode, modeflags, batchfile); if (backend && backend_connected(backend)) { diff --git a/psftp.h b/psftp.h index 79327e7a..a253f508 100644 --- a/psftp.h +++ b/psftp.h @@ -55,7 +55,7 @@ void platform_psftp_pre_conn_setup(LogPolicy *lp); * The main program in psftp.c. Called from main() in the platform- * specific code, after doing any platform-specific initialisation. */ -int psftp_main(int argc, char *argv[]); +int psftp_main(CmdlineArgList *arglist); /* * These functions are used by PSCP to transmit progress updates diff --git a/psocks.c b/psocks.c index e15ef9d7..a71a0c98 100644 --- a/psocks.c +++ b/psocks.c @@ -50,7 +50,7 @@ struct psocks_state { unsigned log_flags; RecordDestination rec_dest; char *rec_cmd; - strbuf *subcmd; + bool got_subcmd; ConnectionLayer cl; }; @@ -409,7 +409,6 @@ psocks_state *psocks_new(const PsocksPlatform *platform) ps->log_flags = LOG_CONNSTATUS; ps->rec_dest = REC_NONE; ps->platform = platform; - ps->subcmd = strbuf_new(); return ps; } @@ -417,19 +416,19 @@ psocks_state *psocks_new(const PsocksPlatform *platform) void psocks_free(psocks_state *ps) { portfwdmgr_free(ps->portfwdmgr); - strbuf_free(ps->subcmd); sfree(ps->rec_cmd); sfree(ps); } -void psocks_cmdline(psocks_state *ps, int argc, char **argv) +void psocks_cmdline(psocks_state *ps, CmdlineArgList *arglist) { bool doing_opts = true; - bool accumulating_exec_args = false; + size_t arglistpos = 0; size_t args_seen = 0; - while (--argc > 0) { - const char *p = *++argv; + while (arglist->args[arglistpos]) { + CmdlineArg *arg = arglist->args[arglistpos++]; + const char *p = cmdline_arg_to_str(arg); if (doing_opts && p[0] == '-' && p[1]) { if (!strcmp(p, "--")) { @@ -446,8 +445,9 @@ void psocks_cmdline(psocks_state *ps, int argc, char **argv) "platform\n"); exit(1); } - if (--argc > 0) { - ps->rec_cmd = dupstr(*++argv); + if (arglist->args[arglistpos] > 0) { + ps->rec_cmd = dupstr( + cmdline_arg_to_str(arglist->args[arglistpos++])); } else { fprintf(stderr, "psocks: expected an argument to '-p'\n"); exit(1); @@ -459,10 +459,15 @@ void psocks_cmdline(psocks_state *ps, int argc, char **argv) "supported on this platform\n"); exit(1); } - accumulating_exec_args = true; + if (!arglist->args[arglistpos]) { + fprintf(stderr, "psocks: --exec requires a command\n"); + exit(1); + } /* Now consume all further argv words for the * subcommand, even if they look like options */ - doing_opts = false; + ps->platform->found_subcommand(arglist->args[arglistpos]); + ps->got_subcmd = true; + break; } else if (!strcmp(p, "--help")) { printf("usage: psocks [ -d ] [ -f"); if (ps->platform->open_pipes) @@ -490,9 +495,7 @@ void psocks_cmdline(psocks_state *ps, int argc, char **argv) exit(1); } } else { - if (accumulating_exec_args) { - put_asciz(ps->subcmd, p); - } else switch (args_seen++) { + switch (args_seen++) { case 0: ps->listen_port = atoi(p); break; @@ -515,8 +518,8 @@ void psocks_start(psocks_state *ps) portfwdmgr_config(ps->portfwdmgr, conf); - if (ps->subcmd->len) - ps->platform->start_subcommand(ps->subcmd); + if (ps->got_subcmd) + ps->platform->start_subcommand(); conf_free(conf); } diff --git a/psocks.h b/psocks.h index 8ba3b2f2..99ee646e 100644 --- a/psocks.h +++ b/psocks.h @@ -19,10 +19,11 @@ struct PsocksPlatform { PsocksDataSink *(*open_pipes)( const char *cmd, const char *const *direction_args, const char *index_arg, char **err); - void (*start_subcommand)(strbuf *args); + void (*found_subcommand)(CmdlineArg *arg); + void (*start_subcommand)(void); }; psocks_state *psocks_new(const PsocksPlatform *); void psocks_free(psocks_state *ps); -void psocks_cmdline(psocks_state *ps, int argc, char **argv); +void psocks_cmdline(psocks_state *ps, CmdlineArgList *arglist); void psocks_start(psocks_state *ps); diff --git a/putty.h b/putty.h index d240e132..97ecb60f 100644 --- a/putty.h +++ b/putty.h @@ -2402,17 +2402,11 @@ void printer_finish_job(printer_job *); * Exports from cmdline.c (and also cmdline_error(), which is * defined differently in various places and required _by_ * cmdline.c). - * - * Note that cmdline_process_param takes a const option string, but a - * writable argument string. That's not a mistake - that's so it can - * zero out password arguments in the hope of not having them show up - * avoidably in Unix 'ps'. */ struct cmdline_get_passwd_input_state { bool tried; }; #define CMDLINE_GET_PASSWD_INPUT_STATE_INIT { .tried = false } extern const cmdline_get_passwd_input_state cmdline_get_passwd_input_state_new; - -int cmdline_process_param(const char *, char *, int, Conf *); +int cmdline_process_param(CmdlineArg *, CmdlineArg *, int, Conf *); void cmdline_run_saved(Conf *); void cmdline_cleanup(void); SeatPromptResult cmdline_get_passwd_input( @@ -2421,6 +2415,32 @@ bool cmdline_host_ok(Conf *); bool cmdline_verbose(void); bool cmdline_loaded_session(void); +/* + * Abstraction provided by each platform to represent a command-line + * argument. May not be as simple as a default-encoded string: on + * Windows, command lines can be Unicode representing characters not + * in the system codepage, so you might need to retrieve the argument + * in a richer form. + */ +struct CmdlineArgList { + /* args[0], args[1], ... represent the original arguments in the + * command line. Then there's a null pointer. Further arguments + * can be invented to add to the array after that, in which case + * they'll be freed with the rest of the CmdlineArgList, but + * aren't logically part of the original command line. */ + CmdlineArg **args; + size_t nargs, argssize; +}; +struct CmdlineArg { + CmdlineArgList *list; +}; +const char *cmdline_arg_to_utf8(CmdlineArg *arg); /* may fail */ +const char *cmdline_arg_to_str(CmdlineArg *arg); /* must not fail */ +void cmdline_arg_wipe(CmdlineArg *arg); +CmdlineArg *cmdline_arg_from_str(CmdlineArgList *list, const char *string); +/* Platforms provide their own constructors for CmdlineArgList */ +void cmdline_arg_list_free(CmdlineArgList *list); + /* * Here we have a flags word provided by each tool, which describes * the capabilities of that tool that cmdline.c needs to know about. diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt index c24e96b5..4d8ef964 100644 --- a/unix/CMakeLists.txt +++ b/unix/CMakeLists.txt @@ -4,6 +4,7 @@ add_sources_from_current_dir(utils utils/arm_arch_queries.c utils/block_signal.c utils/cloexec.c + utils/cmdline_arg.c utils/dputs.c utils/filename.c utils/fontspec.c diff --git a/unix/main-gtk-simple.c b/unix/main-gtk-simple.c index f477dfff..4ffd9ecf 100644 --- a/unix/main-gtk-simple.c +++ b/unix/main-gtk-simple.c @@ -312,7 +312,6 @@ void window_setup_error(const char *errmsg) bool do_cmdline(int argc, char **argv, bool do_everything, Conf *conf) { bool err = false; - char *val; /* * Macros to make argument handling easier. @@ -323,20 +322,26 @@ bool do_cmdline(int argc, char **argv, bool do_everything, Conf *conf) * {...} else ((void)0). */ #define EXPECTS_ARG if (1) { \ - if (--argc <= 0) { \ + if (!nextarg) { \ err = true; \ fprintf(stderr, "%s: %s expects an argument\n", appname, p); \ continue; \ - } else \ - val = *++argv; \ + } else { \ + arglistpos++; \ + } \ } else ((void)0) #define SECOND_PASS_ONLY if (1) { \ if (!do_everything) \ continue; \ } else ((void)0) - while (--argc > 0) { - const char *p = *++argv; + CmdlineArgList *arglist = cmdline_arg_list_from_argv(argc, argv); + size_t arglistpos = 0; + while (arglist->args[arglistpos]) { + CmdlineArg *arg = arglist->args[arglistpos++]; + CmdlineArg *nextarg = arglist->args[arglistpos]; + const char *p = cmdline_arg_to_str(arg); + const char *val = cmdline_arg_to_str(nextarg); int ret; /* @@ -350,13 +355,13 @@ bool do_cmdline(int argc, char **argv, bool do_everything, Conf *conf) !strcmp(p, "-T")) p = "-title"; - ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL), - do_everything ? 1 : -1, conf); + ret = cmdline_process_param( + arg, nextarg, do_everything ? 1 : -1, conf); if (ret == -2) { cmdline_error("option \"%s\" requires an argument", p); } else if (ret == 2) { - --argc, ++argv; /* skip next argument */ + arglistpos++; continue; } else if (ret == 1) { continue; @@ -458,13 +463,8 @@ bool do_cmdline(int argc, char **argv, bool do_everything, Conf *conf) if (!do_everything) break; - if (--argc > 0) { - int i; - pty_argv = snewn(argc+1, char *); - ++argv; - for (i = 0; i < argc; i++) - pty_argv[i] = argv[i]; - pty_argv[argc] = NULL; + if (nextarg) { + pty_argv = cmdline_arg_remainder(nextarg); break; /* finished command-line processing */ } else err = true, fprintf(stderr, "%s: -e expects an argument\n", @@ -552,6 +552,8 @@ bool do_cmdline(int argc, char **argv, bool do_everything, Conf *conf) } } + cmdline_arg_list_free(arglist); + return err; } diff --git a/unix/platform.h b/unix/platform.h index 7b9a7ccf..44c7986c 100644 --- a/unix/platform.h +++ b/unix/platform.h @@ -477,4 +477,8 @@ void plug_closing_errno(Plug *plug, int error); SeatPromptResult make_spr_sw_abort_errno(const char *prefix, int errno_value); +/* Unix-specific extra functions in cmdline_arg.c */ +CmdlineArgList *cmdline_arg_list_from_argv(int argc, char **argv); +char **cmdline_arg_remainder(CmdlineArg *argp); + #endif /* PUTTY_UNIX_PLATFORM_H */ diff --git a/unix/plink.c b/unix/plink.c index 76f4c936..c316793c 100644 --- a/unix/plink.c +++ b/unix/plink.c @@ -723,16 +723,19 @@ int main(int argc, char **argv) } } } - while (--argc) { - char *p = *++argv; - int ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL), - 1, conf); + CmdlineArgList *arglist = cmdline_arg_list_from_argv(argc, argv); + size_t arglistpos = 0; + while (arglist->args[arglistpos]) { + CmdlineArg *arg = arglist->args[arglistpos++]; + CmdlineArg *nextarg = arglist->args[arglistpos]; + const char *p = cmdline_arg_to_str(arg); + int ret = cmdline_process_param(arg, nextarg, 1, conf); if (ret == -2) { fprintf(stderr, "plink: option \"%s\" requires an argument\n", p); errors = true; } else if (ret == 2) { - --argc, ++argv; + arglistpos++; } else if (ret == 1) { continue; } else if (!strcmp(p, "-s")) { @@ -781,12 +784,11 @@ int main(int argc, char **argv) } else if (*p != '-') { strbuf *cmdbuf = strbuf_new(); - while (argc > 0) { + while (arg) { if (cmdbuf->len > 0) put_byte(cmdbuf, ' '); /* add space separator */ - put_dataz(cmdbuf, p); - if (--argc > 0) - p = *++argv; + put_dataz(cmdbuf, cmdline_arg_to_str(arg)); + arg = arglist->args[arglistpos++]; } conf_set_str(conf, CONF_remote_cmd, cmdbuf->s); @@ -815,6 +817,8 @@ int main(int argc, char **argv) */ cmdline_run_saved(conf); + cmdline_arg_list_free(arglist); + /* * If we have no better ideas for the remote username, use the local * one, as 'ssh' does. diff --git a/unix/psocks.c b/unix/psocks.c index 6b30dc16..8f1ddf46 100644 --- a/unix/psocks.c +++ b/unix/psocks.c @@ -82,7 +82,14 @@ static pid_t subcommand_pid = -1; static bool still_running = true; -static void start_subcommand(strbuf *args) +static char **exec_args = NULL; + +static void found_subcommand(CmdlineArg *arg) +{ + exec_args = cmdline_arg_remainder(arg); +} + +static void start_subcommand(void) { pid_t pid; @@ -95,24 +102,6 @@ static void start_subcommand(strbuf *args) } putty_signal(SIGCHLD, sigchld); - /* - * Make an array of argument pointers that execvp will like. - */ - size_t nargs = 0; - for (size_t i = 0; i < args->len; i++) - if (args->s[i] == '\0') - nargs++; - - char **exec_args = snewn(nargs + 1, char *); - char *p = args->s; - for (size_t a = 0; a < nargs; a++) { - exec_args[a] = p; - size_t len = strlen(p); - assert(len < args->len - (p - args->s)); - p += 1 + len; - } - exec_args[nargs] = NULL; - pid = fork(); if (pid < 0) { perror("fork"); @@ -123,12 +112,12 @@ static void start_subcommand(strbuf *args) _exit(127); } else { subcommand_pid = pid; - sfree(exec_args); } } static const PsocksPlatform platform = { open_pipes, + found_subcommand, start_subcommand, }; @@ -163,11 +152,13 @@ static bool psocks_continue(void *ctx, bool found_any_fd, int main(int argc, char **argv) { psocks_state *ps = psocks_new(&platform); - psocks_cmdline(ps, argc, argv); + CmdlineArgList *arglist = cmdline_arg_list_from_argv(argc, argv); + psocks_cmdline(ps, arglist); sk_init(); uxsel_init(); psocks_start(ps); + cmdline_arg_list_free(arglist); cli_main_loop(psocks_pw_setup, psocks_pw_check, psocks_continue, NULL); } diff --git a/unix/sftp.c b/unix/sftp.c index dd25fa20..9fb0bf6e 100644 --- a/unix/sftp.c +++ b/unix/sftp.c @@ -577,5 +577,6 @@ const bool buildinfo_gtk_relevant = false; int main(int argc, char *argv[]) { uxsel_init(); - return psftp_main(argc, argv); + CmdlineArgList *arglist = cmdline_arg_list_from_argv(argc, argv); + return psftp_main(arglist); } diff --git a/unix/utils/cmdline_arg.c b/unix/utils/cmdline_arg.c new file mode 100644 index 00000000..a33df45e --- /dev/null +++ b/unix/utils/cmdline_arg.c @@ -0,0 +1,158 @@ +/* + * Implementation of the CmdlineArg abstraction for Unix + */ + +#include "putty.h" + +typedef struct CmdlineArgUnix CmdlineArgUnix; +struct CmdlineArgUnix { + /* + * This is a writable char *, because the arguments received by + * main() really are writable, and moreover, you _want_ to write + * over them in some circumstances, to manipulate how your program + * shows up in ps(1). Our example is wiping out the argument to + * the -pw option. This isn't robust - you need to not use that + * option at all if you want zero risk of password exposure + * through ps - but we do the best we can. + * + * Some CmdlineArg structures are invented after the program + * starts, in which case they don't correspond to real argv words + * at all, and this pointer is NULL. + */ + char *argv_word; + + /* + * A CmdlineArg invented later might need to store a string that + * will be freed when it goes away. This pointer is non-NULL if + * freeing needs to happen. + */ + char *to_free; + + /* + * This const char * is the real string value of the argument. + */ + const char *value; + + /* + * Our index in the CmdlineArgList, or (size_t)-1 if we don't have + * one and are an argument invented later. + */ + size_t index; + + /* + * Public part of the structure. + */ + CmdlineArg argp; +}; + +static CmdlineArgUnix *cmdline_arg_new_in_list(CmdlineArgList *list) +{ + CmdlineArgUnix *arg = snew(CmdlineArgUnix); + arg->argv_word = NULL; + arg->to_free = NULL; + arg->value = NULL; + arg->index = (size_t)-1; + arg->argp.list = list; + sgrowarray(list->args, list->argssize, list->nargs); + list->args[list->nargs++] = &arg->argp; + return arg; +} + +static CmdlineArg *cmdline_arg_from_argv_word(CmdlineArgList *list, char *word) +{ + CmdlineArgUnix *arg = cmdline_arg_new_in_list(list); + arg->argv_word = word; + arg->value = arg->argv_word; + return &arg->argp; +} + +CmdlineArgList *cmdline_arg_list_from_argv(int argc, char **argv) +{ + CmdlineArgList *list = snew(CmdlineArgList); + list->args = NULL; + list->nargs = list->argssize = 0; + for (int i = 1; i < argc; i++) { + CmdlineArg *argp = cmdline_arg_from_argv_word(list, argv[i]); + CmdlineArgUnix *arg = container_of(argp, CmdlineArgUnix, argp); + arg->index = i - 1; /* index in list->args[], not in argv[] */ + } + sgrowarray(list->args, list->argssize, list->nargs); + list->args[list->nargs++] = NULL; + return list; +} + +void cmdline_arg_free(CmdlineArg *argp) +{ + if (!argp) + return; + + CmdlineArgUnix *arg = container_of(argp, CmdlineArgUnix, argp); + if (arg->to_free) + burnstr(arg->to_free); + sfree(arg); +} + +void cmdline_arg_list_free(CmdlineArgList *list) +{ + for (size_t i = 0; i < list->nargs; i++) + cmdline_arg_free(list->args[i]); + sfree(list->args); + sfree(list); +} + +CmdlineArg *cmdline_arg_from_str(CmdlineArgList *list, const char *string) +{ + CmdlineArgUnix *arg = cmdline_arg_new_in_list(list); + arg->to_free = dupstr(string); + arg->value = arg->to_free; + return &arg->argp; +} + +const char *cmdline_arg_to_str(CmdlineArg *argp) +{ + if (!argp) + return NULL; + + CmdlineArgUnix *arg = container_of(argp, CmdlineArgUnix, argp); + return arg->value; +} + +const char *cmdline_arg_to_utf8(CmdlineArg *argp) +{ + /* For the moment, return NULL. But perhaps it makes sense to + * convert from the default locale into UTF-8? */ + return NULL; +} + +void cmdline_arg_wipe(CmdlineArg *argp) +{ + if (!argp) + return; + + CmdlineArgUnix *arg = container_of(argp, CmdlineArgUnix, argp); + if (arg->argv_word) + smemclr(arg->argv_word, strlen(arg->argv_word)); +} + +char **cmdline_arg_remainder(CmdlineArg *argp) +{ + CmdlineArgUnix *arg = container_of(argp, CmdlineArgUnix, argp); + CmdlineArgList *list = argp->list; + + size_t index = arg->index; + assert(index != (size_t)-1); + + size_t nargs = 0; + while (list->args[index + nargs]) + nargs++; + + char **argv = snewn(nargs + 1, char *); + for (size_t i = 0; i < nargs; i++) { + CmdlineArg *ith_argp = list->args[index + i]; + CmdlineArgUnix *ith_arg = container_of(ith_argp, CmdlineArgUnix, argp); + argv[i] = ith_arg->argv_word; + } + argv[nargs] = NULL; + + return argv; +} diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 18f8b442..7e16b0f4 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -6,6 +6,7 @@ add_sources_from_current_dir(utils utils/arm_arch_queries.c utils/aux_match_opt.c utils/centre_window.c + utils/cmdline_arg.c utils/cryptoapi.c utils/defaults.c utils/dll_hijacking_protection.c diff --git a/windows/pageant.c b/windows/pageant.c index 31468301..1f71bd6d 100644 --- a/windows/pageant.c +++ b/windows/pageant.c @@ -1537,8 +1537,6 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) const char *command = NULL; const char *unixsocket = NULL; bool show_keylist_on_startup = false; - int argc; - char **argv, **argstart; const char *openssh_config_file = NULL; typedef struct CommandLineKey { @@ -1598,24 +1596,23 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) * started up the main agent. Details of keys to be added are * stored in the 'clkeys' array. */ - split_into_argv(cmdline, false, &argc, &argv, &argstart); bool add_keys_encrypted = false; - AuxMatchOpt amo = aux_match_opt_init(argc, argv, 0, opt_error); + AuxMatchOpt amo = aux_match_opt_init(opt_error); while (!aux_match_done(&amo)) { - char *val; + CmdlineArg *valarg; #define match_opt(...) aux_match_opt( \ &amo, NULL, __VA_ARGS__, (const char *)NULL) #define match_optval(...) aux_match_opt( \ - &amo, &val, __VA_ARGS__, (const char *)NULL) + &amo, &valarg, __VA_ARGS__, (const char *)NULL) - if (aux_match_arg(&amo, &val)) { + if (aux_match_arg(&amo, &valarg)) { /* * Non-option arguments are expected to be key files, and * added to clkeys. */ sgrowarray(clkeys, clkeysize, nclkeys); CommandLineKey *clkey = &clkeys[nclkeys++]; - clkey->fn = filename_from_str(val); + clkey->fn = filename_from_str(cmdline_arg_to_str(valarg)); clkey->add_encrypted = add_keys_encrypted; } else if (match_opt("-pgpfp")) { pgp_fingerprints_msgbox(NULL); @@ -1631,21 +1628,26 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) } else if (match_opt("-keylist")) { show_keylist_on_startup = true; } else if (match_optval("-openssh-config", "-openssh_config")) { - openssh_config_file = val; + openssh_config_file = cmdline_arg_to_str(valarg); } else if (match_optval("-unix")) { - unixsocket = val; + unixsocket = cmdline_arg_to_str(valarg); } else if (match_opt("-c")) { /* * If we see `-c', then the rest of the command line * should be treated as a command to be spawned. */ - if (amo.index < amo.argc) - command = argstart[amo.index]; - else + if (amo.arglist->args[amo.index]) { + /* UNICODE: should use the UTF-8 or wide version, and + * CreateProcessW, to pass through arbitrary command lines */ + command = cmdline_arg_remainder_acp( + amo.arglist->args[amo.index]); + } else { command = ""; + } break; } else { - opt_error("unrecognised option '%s'\n", amo.argv[amo.index]); + opt_error("unrecognised option '%s'\n", + cmdline_arg_to_str(amo.arglist->args[amo.index])); } } diff --git a/windows/platform.h b/windows/platform.h index f5abd39b..034b3c28 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -848,15 +848,15 @@ void unlock_interprocess_mutex(HANDLE mutex); typedef void (*aux_opt_error_fn_t)(const char *, ...); typedef struct AuxMatchOpt { - int index, argc; - char **argv; + CmdlineArgList *arglist; + size_t index; bool doing_opts; aux_opt_error_fn_t error; } AuxMatchOpt; -AuxMatchOpt aux_match_opt_init(int argc, char **argv, int start_index, - aux_opt_error_fn_t opt_error); -bool aux_match_arg(AuxMatchOpt *amo, char **val); -bool aux_match_opt(AuxMatchOpt *amo, char **val, const char *optname, ...); +AuxMatchOpt aux_match_opt_init(aux_opt_error_fn_t opt_error); +bool aux_match_arg(AuxMatchOpt *amo, CmdlineArg **val); +bool aux_match_opt(AuxMatchOpt *amo, CmdlineArg **val, + const char *optname, ...); bool aux_match_done(AuxMatchOpt *amo); char *save_screenshot(HWND hwnd, const char *outfile); @@ -864,4 +864,11 @@ void gui_terminal_ready(HWND hwnd, Seat *seat, Backend *backend); void setup_gui_timing(void); +/* Windows-specific extra functions in cmdline_arg.c */ +CmdlineArgList *cmdline_arg_list_from_GetCommandLineW(void); +const wchar_t *cmdline_arg_remainder_wide(CmdlineArg *); +char *cmdline_arg_remainder_acp(CmdlineArg *); +char *cmdline_arg_remainder_utf8(CmdlineArg *); +CmdlineArg *cmdline_arg_from_utf8(CmdlineArgList *list, const char *string); + #endif /* PUTTY_WINDOWS_PLATFORM_H */ diff --git a/windows/plink.c b/windows/plink.c index a84f0e4b..8184d619 100644 --- a/windows/plink.c +++ b/windows/plink.c @@ -328,16 +328,19 @@ int main(int argc, char **argv) } } } - while (--argc) { - char *p = *++argv; - int ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL), - 1, conf); + CmdlineArgList *arglist = cmdline_arg_list_from_GetCommandLineW(); + size_t arglistpos = 0; + while (arglist->args[arglistpos]) { + CmdlineArg *arg = arglist->args[arglistpos++]; + CmdlineArg *nextarg = arglist->args[arglistpos]; + const char *p = cmdline_arg_to_str(arg); + int ret = cmdline_process_param(arg, nextarg, 1, conf); if (ret == -2) { fprintf(stderr, "plink: option \"%s\" requires an argument\n", p); errors = true; } else if (ret == 2) { - --argc, ++argv; + arglistpos++; } else if (ret == 1) { continue; } else if (!strcmp(p, "-s")) { @@ -369,12 +372,11 @@ int main(int argc, char **argv) } else if (*p != '-') { strbuf *cmdbuf = strbuf_new(); - while (argc > 0) { + while (arg) { if (cmdbuf->len > 0) put_byte(cmdbuf, ' '); /* add space separator */ - put_dataz(cmdbuf, p); - if (--argc > 0) - p = *++argv; + put_dataz(cmdbuf, cmdline_arg_to_str(arg)); + arg = arglist->args[arglistpos++]; } conf_set_str(conf, CONF_remote_cmd, cmdbuf->s); diff --git a/windows/psocks.c b/windows/psocks.c index 83ba364c..c0d8cb5b 100644 --- a/windows/psocks.c +++ b/windows/psocks.c @@ -8,13 +8,15 @@ static const PsocksPlatform platform = { NULL /* open_pipes */, + NULL /* found_subcommand */, NULL /* start_subcommand */, }; int main(int argc, char **argv) { psocks_state *ps = psocks_new(&platform); - psocks_cmdline(ps, argc, argv); + CmdlineArgList *arglist = cmdline_arg_list_from_GetCommandLineW(); + psocks_cmdline(ps, arglist); sk_init(); winselcli_setup(); diff --git a/windows/pterm.c b/windows/pterm.c index 13face06..fd921f09 100644 --- a/windows/pterm.c +++ b/windows/pterm.c @@ -15,33 +15,34 @@ void gui_term_process_cmdline(Conf *conf, char *cmdline) handle_special_filemapping_cmdline(cmdline, conf)) return; - int argc; - char **argv, **argstart; - split_into_argv(cmdline, false, &argc, &argv, &argstart); - - for (int i = 0; i < argc; i++) { - char *arg = argv[i]; - int retd = cmdline_process_param( - arg, i+1args[arglistpos]) { + CmdlineArg *arg = arglist->args[arglistpos++]; + CmdlineArg *nextarg = arglist->args[arglistpos]; + const char *argstr = cmdline_arg_to_str(arg); + int retd = cmdline_process_param(arg, nextarg, 1, conf); if (retd == -2) { - cmdline_error("option \"%s\" requires an argument", arg); + cmdline_error("option \"%s\" requires an argument", argstr); } else if (retd == 2) { - i++; /* skip next argument */ + arglistpos++; /* skip next argument */ } else if (retd == 1) { continue; /* nothing further needs doing */ - } else if (!strcmp(arg, "-e")) { - if (i+1 < argc) { + } else if (!strcmp(argstr, "-e")) { + if (nextarg) { /* The command to execute is taken to be the unparsed * version of the whole remainder of the command line. */ - conf_set_str(conf, CONF_remote_cmd, argstart[i+1]); + char *cmd = cmdline_arg_remainder_acp(nextarg); + conf_set_str(conf, CONF_remote_cmd, cmd); + sfree(cmd); return; } else { - cmdline_error("option \"%s\" requires an argument", arg); + cmdline_error("option \"%s\" requires an argument", argstr); } - } else if (arg[0] == '-') { - cmdline_error("unrecognised option \"%s\"", arg); + } else if (argstr[0] == '-') { + cmdline_error("unrecognised option \"%s\"", argstr); } else { - cmdline_error("unexpected non-option argument \"%s\"", arg); + cmdline_error("unexpected non-option argument \"%s\"", argstr); } } diff --git a/windows/putty.c b/windows/putty.c index 9c8ba001..299a381f 100644 --- a/windows/putty.c +++ b/windows/putty.c @@ -48,21 +48,17 @@ void gui_term_process_cmdline(Conf *conf, char *cmdline) * Otherwise, break up the command line and deal with * it sensibly. */ - int argc, i; - char **argv; - - split_into_argv(cmdline, false, &argc, &argv, NULL); - - for (i = 0; i < argc; i++) { - char *p = argv[i]; - int ret; - - ret = cmdline_process_param(p, i+1args[arglistpos]) { + CmdlineArg *arg = arglist->args[arglistpos++]; + CmdlineArg *nextarg = arglist->args[arglistpos]; + const char *p = cmdline_arg_to_str(arg); + int ret = cmdline_process_param(arg, nextarg, 1, conf); if (ret == -2) { cmdline_error("option \"%s\" requires an argument", p); } else if (ret == 2) { - i++; /* skip next argument */ + arglistpos++; /* skip next argument */ } else if (ret == 1) { continue; /* nothing further needs doing */ } else if (!strcmp(p, "-cleanup")) { @@ -98,18 +94,22 @@ void gui_term_process_cmdline(Conf *conf, char *cmdline) show_ca_config_box(NULL); exit(0); } else if (!strcmp(p, "-demo-config-box")) { - if (i+1 >= argc) { + if (!arglist->args[arglistpos]) { cmdline_error("%s expects an output filename", p); } else { demo_config_box = true; - dialog_box_demo_screenshot_filename = argv[++i]; + dialog_box_demo_screenshot_filename = + cmdline_arg_to_str(arglist->args[arglistpos++]); } } else if (!strcmp(p, "-demo-terminal")) { - if (i+2 >= argc) { + if (!arglist->args[arglistpos] || + !arglist->args[arglistpos+1]) { cmdline_error("%s expects input and output filenames", p); } else { - const char *infile = argv[++i]; - terminal_demo_screenshot_filename = argv[++i]; + const char *infile = + cmdline_arg_to_str(arglist->args[arglistpos++]); + terminal_demo_screenshot_filename = + cmdline_arg_to_str(arglist->args[arglistpos++]); FILE *fp = fopen(infile, "rb"); if (!fp) cmdline_error("can't open input file '%s'", infile); diff --git a/windows/puttygen.c b/windows/puttygen.c index bfd732ac..51574139 100644 --- a/windows/puttygen.c +++ b/windows/puttygen.c @@ -26,7 +26,7 @@ #define DEFAULT_ECCURVE_INDEX 0 #define DEFAULT_EDCURVE_INDEX 0 -static char *cmdline_keyfile = NULL; +static const char *cmdline_keyfile = NULL; static ptrlen cmdline_demo_keystr; static const char *demo_screenshot_filename = NULL; @@ -2392,8 +2392,6 @@ static NORETURN void opt_error(const char *fmt, ...) int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) { - int argc; - char **argv; int ret; struct InitialParams params[1]; @@ -2417,27 +2415,26 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) save_params = ppk_save_default_parameters; - split_into_argv(cmdline, false, &argc, &argv, NULL); - int argbits = -1; - AuxMatchOpt amo = aux_match_opt_init(argc, argv, 0, opt_error); + AuxMatchOpt amo = aux_match_opt_init(opt_error); while (!aux_match_done(&amo)) { - char *val; + CmdlineArg *valarg; #define match_opt(...) aux_match_opt( \ &amo, NULL, __VA_ARGS__, (const char *)NULL) #define match_optval(...) aux_match_opt( \ - &amo, &val, __VA_ARGS__, (const char *)NULL) + &amo, &valarg, __VA_ARGS__, (const char *)NULL) - if (aux_match_arg(&amo, &val)) { + if (aux_match_arg(&amo, &valarg)) { if (!cmdline_keyfile) { /* * Assume the first argument to be a private key file, and * attempt to load it. */ - cmdline_keyfile = val; + cmdline_keyfile = cmdline_arg_to_str(valarg); continue; } else { - opt_error("unexpected extra argument '%s'\n", val); + opt_error("unexpected extra argument '%s'\n", + cmdline_arg_to_str(valarg)); } } else if (match_opt("-pgpfp")) { pgp_fingerprints_msgbox(NULL); @@ -2446,6 +2443,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) "-restrictacl")) { restrict_process_acl(); } else if (match_optval("-t")) { + const char *val = cmdline_arg_to_str(valarg); if (!strcmp(val, "rsa") || !strcmp(val, "rsa2")) { params->keybutton = IDC_KEYSSH2RSA; } else if (!strcmp(val, "rsa1")) { @@ -2466,8 +2464,9 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) opt_error("unknown key type '%s'\n", val); } } else if (match_optval("-b")) { - argbits = atoi(val); + argbits = atoi(cmdline_arg_to_str(valarg)); } else if (match_optval("-E")) { + const char *val = cmdline_arg_to_str(valarg); if (!strcmp(val, "md5")) params->fptype = SSH_FPTYPE_MD5; else if (!strcmp(val, "sha256")) @@ -2475,6 +2474,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) else opt_error("unknown fingerprint type '%s'\n", val); } else if (match_optval("-primes")) { + const char *val = cmdline_arg_to_str(valarg); if (!strcmp(val, "probable") || !strcmp(val, "probabilistic")) { params->primepolicybutton = IDC_PRIMEGEN_PROB; @@ -2495,6 +2495,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) } else if (match_opt("-strong-rsa")) { params->rsa_strong = true; } else if (match_optval("-ppk-param", "-ppk-params")) { + char *val = dupstr(cmdline_arg_to_str(valarg)); char *nextval; for (; val; val = nextval) { nextval = strchr(val, ','); @@ -2547,8 +2548,9 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) opt_error("unrecognised PPK parameter '%s'\n", val); } } + sfree(val); } else if (match_optval("-demo-screenshot")) { - demo_screenshot_filename = val; + demo_screenshot_filename = cmdline_arg_to_str(valarg); cmdline_demo_keystr = PTRLEN_LITERAL( "PuTTY-User-Key-File-3: ssh-ed25519\n" "Encryption: none\n" @@ -2564,7 +2566,8 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) params->keybutton = IDC_KEYSSH2EDDSA; argbits = 255; } else { - opt_error("unrecognised option '%s'\n", amo.argv[amo.index]); + opt_error("unrecognised option '%s'\n", + cmdline_arg_to_str(amo.arglist->args[amo.index])); } } diff --git a/windows/sftp.c b/windows/sftp.c index 9e98bbd0..fb3658d7 100644 --- a/windows/sftp.c +++ b/windows/sftp.c @@ -650,7 +650,8 @@ int main(int argc, char *argv[]) dll_hijacking_protection(); - ret = psftp_main(argc, argv); + CmdlineArgList *arglist = cmdline_arg_list_from_GetCommandLineW(); + ret = psftp_main(arglist); return ret; } diff --git a/windows/test/test_screenshot.c b/windows/test/test_screenshot.c index 1e3a20d7..3d558bc8 100644 --- a/windows/test/test_screenshot.c +++ b/windows/test/test_screenshot.c @@ -17,20 +17,21 @@ int main(int argc, char **argv) { const char *outfile = NULL; - AuxMatchOpt amo = aux_match_opt_init(argc-1, argv+1, 0, fatal_error); + AuxMatchOpt amo = aux_match_opt_init(fatal_error); while (!aux_match_done(&amo)) { - char *val; + CmdlineArg *val; #define match_opt(...) aux_match_opt( \ &amo, NULL, __VA_ARGS__, (const char *)NULL) #define match_optval(...) aux_match_opt( \ &amo, &val, __VA_ARGS__, (const char *)NULL) if (aux_match_arg(&amo, &val)) { - fatal_error("unexpected argument '%s'", val); + fatal_error("unexpected argument '%s'", cmdline_arg_to_str(val)); } else if (match_optval("-o", "--output")) { - outfile = val; + outfile = cmdline_arg_to_str(val); } else { - fatal_error("unrecognised option '%s'\n", amo.argv[amo.index]); + fatal_error("unrecognised option '%s'\n", + cmdline_arg_to_str(amo.arglist->args[amo.index])); } } diff --git a/windows/utils/aux_match_opt.c b/windows/utils/aux_match_opt.c index 190eddac..93080e7e 100644 --- a/windows/utils/aux_match_opt.c +++ b/windows/utils/aux_match_opt.c @@ -13,14 +13,12 @@ /* * Call this to initialise the state structure. */ -AuxMatchOpt aux_match_opt_init(int argc, char **argv, int start_index, - aux_opt_error_fn_t opt_error) +AuxMatchOpt aux_match_opt_init(aux_opt_error_fn_t opt_error) { AuxMatchOpt amo[1]; - amo->argc = argc; - amo->argv = argv; - amo->index = start_index; + amo->arglist = cmdline_arg_list_from_GetCommandLineW(); + amo->index = 0; amo->doing_opts = true; amo->error = opt_error; @@ -32,12 +30,14 @@ AuxMatchOpt aux_match_opt_init(int argc, char **argv, int start_index, * Point 'val' at a char * to receive the option argument, if one is * expected. Set 'val' to NULL if no argument is expected. */ -bool aux_match_opt(AuxMatchOpt *amo, char **val, const char *optname, ...) +bool aux_match_opt(AuxMatchOpt *amo, CmdlineArg **val, + const char *optname, ...) { - assert(amo->index < amo->argc); - /* Find the end of the option name */ - char *opt = amo->argv[amo->index]; + CmdlineArg *optarg = amo->arglist->args[amo->index]; + assert(optarg); + const char *opt = cmdline_arg_to_utf8(optarg); + ptrlen argopt; argopt.ptr = opt; argopt.len = strcspn(opt, "="); @@ -72,14 +72,14 @@ bool aux_match_opt(AuxMatchOpt *amo, char **val, const char *optname, ...) if (opt[argopt.len]) { if (!val) amo->error("option '%s' does not expect a value", opt); - *val = opt + argopt.len + 1; + *val = cmdline_arg_from_utf8(optarg->list, opt + argopt.len + 1); amo->index++; } else if (!val) { amo->index++; } else { - if (amo->index + 1 >= amo->argc) + if (!amo->arglist->args[amo->index + 1]) amo->error("option '%s' expects a value", opt); - *val = amo->argv[amo->index + 1]; + *val = amo->arglist->args[amo->index + 1]; amo->index += 2; } @@ -89,13 +89,14 @@ bool aux_match_opt(AuxMatchOpt *amo, char **val, const char *optname, ...) /* * Call this to return a non-option argument in *val. */ -bool aux_match_arg(AuxMatchOpt *amo, char **val) +bool aux_match_arg(AuxMatchOpt *amo, CmdlineArg **val) { - assert(amo->index < amo->argc); - char *opt = amo->argv[amo->index]; + CmdlineArg *optarg = amo->arglist->args[amo->index]; + assert(optarg); + const char *opt = cmdline_arg_to_utf8(optarg); if (!amo->doing_opts || opt[0] != '-' || !strcmp(opt, "-")) { - *val = opt; + *val = optarg; amo->index++; return true; } @@ -108,10 +109,12 @@ bool aux_match_arg(AuxMatchOpt *amo, char **val) */ bool aux_match_done(AuxMatchOpt *amo) { - if (amo->index < amo->argc && !strcmp(amo->argv[amo->index], "--")) { + CmdlineArg *optarg = amo->arglist->args[amo->index]; + const char *opt = cmdline_arg_to_utf8(optarg); + if (opt && !strcmp(opt, "--")) { amo->doing_opts = false; amo->index++; } - return amo->index >= amo->argc; + return amo->arglist->args[amo->index] == NULL; } diff --git a/windows/utils/cmdline_arg.c b/windows/utils/cmdline_arg.c new file mode 100644 index 00000000..55c105f1 --- /dev/null +++ b/windows/utils/cmdline_arg.c @@ -0,0 +1,209 @@ +/* + * Implementation of the CmdlineArg abstraction for Windows + */ + +#include +#include "putty.h" + +typedef struct CmdlineArgWin CmdlineArgWin; +struct CmdlineArgWin { + /* + * The original wide-character argument. + */ + wchar_t *wide; + + /* + * Two translations of the wide-character argument into UTF-8 + * (maximally faithful to the original) and CP_ACP (the normal + * system code page). + */ + char *utf8, *acp; + + /* + * Our index in the CmdlineArgList, or (size_t)-1 if we don't have + * one and are an argument invented later. + */ + size_t index; + + /* + * Public part of the structure. + */ + CmdlineArg argp; +}; + +typedef struct CmdlineArgListWin CmdlineArgListWin; +struct CmdlineArgListWin { + /* + * Wide string pointer returned from GetCommandLineW. This points + * to the 'official' version of the command line, in the sense + * that overwriting it causes different text to show up in the + * Task Manager display of the process's command line. (So this is + * what we'll need to overwrite _on purpose_ for cmdline_arg_wipe.) + */ + wchar_t *cmdline; + + /* + * Data returned from split_into_argv_w. + */ + size_t argc; + wchar_t **argv, **argstart; + + /* + * Public part of the structure. + */ + CmdlineArgList listp; +}; + +static CmdlineArgWin *cmdline_arg_new_in_list(CmdlineArgList *listp) +{ + CmdlineArgWin *arg = snew(CmdlineArgWin); + arg->wide = NULL; + arg->utf8 = arg->acp = NULL; + arg->index = (size_t)-1; + arg->argp.list = listp; + sgrowarray(listp->args, listp->argssize, listp->nargs); + listp->args[listp->nargs++] = &arg->argp; + return arg; +} + +static CmdlineArg *cmdline_arg_from_wide_argv_word( + CmdlineArgList *list, wchar_t *word) +{ + CmdlineArgWin *arg = cmdline_arg_new_in_list(list); + arg->wide = dupwcs(word); + arg->utf8 = dup_wc_to_mb(CP_UTF8, word, ""); + arg->acp = dup_wc_to_mb(CP_ACP, word, ""); + return &arg->argp; +} + +CmdlineArgList *cmdline_arg_list_from_GetCommandLineW(void) +{ + CmdlineArgListWin *list = snew(CmdlineArgListWin); + CmdlineArgList *listp = &list->listp; + + list->cmdline = GetCommandLineW(); + + int argc; + split_into_argv_w(list->cmdline, true, + &argc, &list->argv, &list->argstart); + list->argc = (size_t)argc; + + listp->args = NULL; + listp->nargs = listp->argssize = 0; + for (size_t i = 1; i < list->argc; i++) { + CmdlineArg *argp = cmdline_arg_from_wide_argv_word( + listp, list->argv[i]); + CmdlineArgWin *arg = container_of(argp, CmdlineArgWin, argp); + arg->index = i - 1; /* index in list->args[], not in argv[] */ + } + sgrowarray(listp->args, listp->argssize, listp->nargs); + listp->args[listp->nargs++] = NULL; + return listp; +} + +void cmdline_arg_free(CmdlineArg *argp) +{ + if (!argp) + return; + + CmdlineArgWin *arg = container_of(argp, CmdlineArgWin, argp); + burnwcs(arg->wide); + burnstr(arg->utf8); + burnstr(arg->acp); + sfree(arg); +} + +void cmdline_arg_list_free(CmdlineArgList *listp) +{ + CmdlineArgListWin *list = container_of(listp, CmdlineArgListWin, listp); + for (size_t i = 0; i < listp->nargs; i++) + cmdline_arg_free(listp->args[i]); + /* list->argv[0] points at the start of the string allocated by + * split_into_argv_w */ + sfree(list->argv[0]); + sfree(list->argv); + sfree(list->argstart); + sfree(list); +} + +CmdlineArg *cmdline_arg_from_str(CmdlineArgList *listp, const char *string) +{ + CmdlineArgWin *arg = cmdline_arg_new_in_list(listp); + arg->acp = dupstr(string); + arg->wide = dup_mb_to_wc(CP_ACP, string); + arg->utf8 = dup_wc_to_mb(CP_UTF8, arg->wide, ""); + return &arg->argp; +} + +CmdlineArg *cmdline_arg_from_utf8(CmdlineArgList *listp, const char *string) +{ + CmdlineArgWin *arg = cmdline_arg_new_in_list(listp); + arg->acp = dupstr(string); + arg->wide = dup_mb_to_wc(CP_UTF8, string); + arg->utf8 = dup_wc_to_mb(CP_ACP, arg->wide, ""); + return &arg->argp; +} + +const char *cmdline_arg_to_str(CmdlineArg *argp) +{ + if (!argp) + return NULL; + + CmdlineArgWin *arg = container_of(argp, CmdlineArgWin, argp); + return arg->acp; +} + +const char *cmdline_arg_to_utf8(CmdlineArg *argp) +{ + if (!argp) + return NULL; + + CmdlineArgWin *arg = container_of(argp, CmdlineArgWin, argp); + return arg->utf8; +} + +void cmdline_arg_wipe(CmdlineArg *argp) +{ + if (!argp) + return; + + CmdlineArgWin *arg = container_of(argp, CmdlineArgWin, argp); + if (arg->index != (size_t)-1) { + CmdlineArgList *listp = argp->list; + CmdlineArgListWin *list = container_of( + listp, CmdlineArgListWin, listp); + + /* arg->index starts from the first argument _after_ program + * name, whereas argstart is indexed from argv[0] */ + wchar_t *p = list->argstart[arg->index + 1]; + wchar_t *end = (arg->index + 2 < list->argc ? + list->argstart[arg->index + 2] : + p + wcslen(p)); + while (p < end) + *p++ = L' '; + } +} + +const wchar_t *cmdline_arg_remainder_wide(CmdlineArg *argp) +{ + CmdlineArgWin *arg = container_of(argp, CmdlineArgWin, argp); + CmdlineArgList *listp = argp->list; + CmdlineArgListWin *list = container_of(listp, CmdlineArgListWin, listp); + + size_t index = arg->index; + assert(index != (size_t)-1); + + /* arg->index starts from the first argument _after_ program + * name, whereas argstart is indexed from argv[0] */ + return list->argstart[index + 1]; +} + +char *cmdline_arg_remainder_acp(CmdlineArg *argp) +{ + return dup_wc_to_mb(CP_ACP, cmdline_arg_remainder_wide(argp), ""); +} + +char *cmdline_arg_remainder_utf8(CmdlineArg *argp) +{ + return dup_wc_to_mb(CP_UTF8, cmdline_arg_remainder_wide(argp), ""); +}