Remember "subfolder" as correctly spelled
[TortoiseGit.git] / src / TortoisePlink / CMDLINE.C
blobc5961c3d8eb5c016ae73b8325d4bf5fb8ede0f80
1 /*\r
2  * cmdline.c - command-line parsing shared between many of the\r
3  * PuTTY applications\r
4  */\r
5 \r
6 #include <stdio.h>\r
7 #include <assert.h>\r
8 #include <stdlib.h>\r
9 #include "putty.h"\r
11 /*\r
12  * Some command-line parameters need to be saved up until after\r
13  * we've loaded the saved session which will form the basis of our\r
14  * eventual running configuration. For this we use the macro\r
15  * SAVEABLE, which notices if the `need_save' parameter is set and\r
16  * saves the parameter and value on a list.\r
17  *\r
18  * We also assign priorities to saved parameters, just to slightly\r
19  * ameliorate silly ordering problems. For example, if you specify\r
20  * a saved session to load, it will be loaded _before_ all your\r
21  * local modifications such as -L are evaluated; and if you specify\r
22  * a protocol and a port, the protocol is set up first so that the\r
23  * port can override its choice of port number.\r
24  *\r
25  * (In fact -load is not saved at all, since in at least Plink the\r
26  * processing of further command-line options depends on whether or\r
27  * not the loaded session contained a hostname. So it must be\r
28  * executed immediately.)\r
29  */\r
31 #define NPRIORITIES 2\r
33 struct cmdline_saved_param {\r
34     char *p, *value;\r
35 };\r
36 struct cmdline_saved_param_set {\r
37     struct cmdline_saved_param *params;\r
38     size_t nsaved, savesize;\r
39 };\r
41 /*\r
42  * C guarantees this structure will be initialised to all zero at\r
43  * program start, which is exactly what we want.\r
44  */\r
45 static struct cmdline_saved_param_set saves[NPRIORITIES];\r
47 static void cmdline_save_param(const char *p, const char *value, int pri)\r
48 {\r
49     sgrowarray(saves[pri].params, saves[pri].savesize, saves[pri].nsaved);\r
50     saves[pri].params[saves[pri].nsaved].p = dupstr(p);\r
51     saves[pri].params[saves[pri].nsaved].value = dupstr(value);\r
52     saves[pri].nsaved++;\r
53 }\r
55 static char *cmdline_password = NULL;\r
57 void cmdline_cleanup(void)\r
58 {\r
59     int pri;\r
61     if (cmdline_password) {\r
62         smemclr(cmdline_password, strlen(cmdline_password));\r
63         sfree(cmdline_password);\r
64         cmdline_password = NULL;\r
65     }\r
67     for (pri = 0; pri < NPRIORITIES; pri++) {\r
68         sfree(saves[pri].params);\r
69         saves[pri].params = NULL;\r
70         saves[pri].savesize = 0;\r
71         saves[pri].nsaved = 0;\r
72     }\r
73 }\r
75 #define SAVEABLE(pri) do { \\r
76     if (need_save) { cmdline_save_param(p, value, pri); return ret; } \\r
77 } while (0)\r
79 /*\r
80  * Similar interface to seat_get_userpass_input(), except that here a\r
81  * SPR(K)_INCOMPLETE return means that we aren't capable of processing\r
82  * the prompt and someone else should do it.\r
83  */\r
84 SeatPromptResult cmdline_get_passwd_input(\r
85     prompts_t *p, cmdline_get_passwd_input_state *state, bool restartable)\r
86 {\r
87     /*\r
88      * We only handle prompts which don't echo (which we assume to be\r
89      * passwords), and (currently) we only cope with a password prompt\r
90      * that comes in a prompt-set on its own. Also, we don't use a\r
91      * command-line password for any kind of prompt which is destined\r
92      * for local use rather than to be sent to the server: the idea is\r
93      * to pre-fill _passwords_, not private-key passphrases (for which\r
94      * there are better alternatives available).\r
95      */\r
96     if (p->n_prompts != 1 || p->prompts[0]->echo || !p->to_server) {\r
97         return SPR_INCOMPLETE;\r
98     }\r
100     /*\r
101      * If we've tried once, return utter failure (no more passwords left\r
102      * to try).\r
103      */\r
104     if (state->tried)\r
105         return SPR_SW_ABORT("Configured password was not accepted");\r
107     /*\r
108      * If we never had a password available in the first place, we\r
109      * can't do anything in any case. (But we delay this test until\r
110      * after trying once, so that even if we free cmdline_password\r
111      * below, we'll still remember that we _used_ to have one.)\r
112      */\r
113     if (!cmdline_password)\r
114         return SPR_INCOMPLETE;\r
116     prompt_set_result(p->prompts[0], cmdline_password);\r
117     state->tried = true;\r
119     if (!restartable) {\r
120         /*\r
121          * If there's no possibility of needing to do this again after\r
122          * a 'Restart Session' event, then wipe our copy of the\r
123          * password out of memory.\r
124          */\r
125         smemclr(cmdline_password, strlen(cmdline_password));\r
126         sfree(cmdline_password);\r
127         cmdline_password = NULL;\r
128     }\r
130     return SPR_OK;\r
133 static bool cmdline_check_unavailable(int flag, const char *p)\r
135     if (cmdline_tooltype & flag) {\r
136         cmdline_error("option \"%s\" not available in this tool", p);\r
137         return true;\r
138     }\r
139     return false;\r
142 #define UNAVAILABLE_IN(flag) do { \\r
143     if (cmdline_check_unavailable(flag, p)) return ret; \\r
144 } while (0)\r
146 /*\r
147  * Process a standard command-line parameter. `p' is the parameter\r
148  * in question; `value' is the subsequent element of argv, which\r
149  * may or may not be required as an operand to the parameter.\r
150  * If `need_save' is 1, arguments which need to be saved as\r
151  * described at this top of this file are, for later execution;\r
152  * if 0, they are processed normally. (-1 is a special value used\r
153  * by pterm to count arguments for a preliminary pass through the\r
154  * argument list; it causes immediate return with an appropriate\r
155  * value with no action taken.)\r
156  * Return value is 2 if both arguments were used; 1 if only p was\r
157  * used; 0 if the parameter wasn't one we recognised; -2 if it\r
158  * should have been 2 but value was NULL.\r
159  */\r
161 #define RETURN(x) do { \\r
162     if ((x) == 2 && !value) return -2; \\r
163     ret = x; \\r
164     if (need_save < 0) return x; \\r
165 } while (0)\r
167 static bool seen_hostname_argument = false;\r
168 static bool seen_port_argument = false;\r
169 static bool seen_verbose_option = false;\r
170 static bool loaded_session = false;\r
171 bool cmdline_verbose(void) { return seen_verbose_option; }\r
172 bool cmdline_seat_verbose(Seat *seat) { return cmdline_verbose(); }\r
173 bool cmdline_lp_verbose(LogPolicy *lp) { return cmdline_verbose(); }\r
174 bool cmdline_loaded_session(void) { return loaded_session; }\r
176 static void set_protocol(Conf *conf, int protocol)\r
178     settings_set_default_protocol(protocol);\r
179     conf_set_int(conf, CONF_protocol, protocol);\r
182 static void set_port(Conf *conf, int port)\r
184     settings_set_default_port(port);\r
185     conf_set_int(conf, CONF_port, port);\r
188 int cmdline_process_param(const char *p, char *value,\r
189                           int need_save, Conf *conf, bool ignoreFurtherParameters)\r
191     int ret = 0;\r
193     if (p[0] != '-' || ignoreFurtherParameters) {\r
194         if (need_save < 0)\r
195             return 0;\r
197         /*\r
198          * Common handling for the tools whose initial command-line\r
199          * arguments specify a hostname to connect to, i.e. PuTTY and\r
200          * Plink. Doesn't count the file transfer tools, because their\r
201          * hostname specification appears as part of a more\r
202          * complicated scheme.\r
203          */\r
205         if ((cmdline_tooltype & TOOLTYPE_HOST_ARG) &&\r
206             !seen_hostname_argument &&\r
207             (!(cmdline_tooltype & TOOLTYPE_HOST_ARG_FROM_LAUNCHABLE_LOAD) ||\r
208              !loaded_session || !conf_launchable(conf))) {\r
209             /*\r
210              * Treat this argument as a host name, if we have not yet\r
211              * seen a host name argument or -load.\r
212              *\r
213              * Exception, in some tools (Plink): if we have seen -load\r
214              * but it didn't create a launchable session, then we\r
215              * still accept a hostname argument following that -load.\r
216              * This allows you to make saved sessions that configure\r
217              * lots of other stuff (colour schemes, terminal settings\r
218              * etc) and then say 'putty -load sessionname hostname'.\r
219              *\r
220              * Also, we carefully _don't_ test conf for launchability\r
221              * if we haven't been explicitly told to load a session\r
222              * (otherwise saving a host name into Default Settings\r
223              * would cause 'putty' on its own to immediately launch\r
224              * the default session and never be able to do anything\r
225              * else).\r
226              */\r
227             if (!strncmp(p, "telnet:", 7)) {\r
228                 /*\r
229                  * If the argument starts with "telnet:", set the\r
230                  * protocol to Telnet and process the string as a\r
231                  * Telnet URL.\r
232                  */\r
234                 /*\r
235                  * Skip the "telnet:" or "telnet://" prefix.\r
236                  */\r
237                 p += 7;\r
238                 if (p[0] == '/' && p[1] == '/')\r
239                     p += 2;\r
240                 conf_set_int(conf, CONF_protocol, PROT_TELNET);\r
242                 /*\r
243                  * The next thing we expect is a host name.\r
244                  */\r
245                 {\r
246                     const char *host = p;\r
247                     char *buf;\r
249                     p += host_strcspn(p, ":/");\r
250                     buf = dupprintf("%.*s", (int)(p - host), host);\r
251                     conf_set_str(conf, CONF_host, buf);\r
252                     sfree(buf);\r
253                     seen_hostname_argument = true;\r
254                 }\r
256                 /*\r
257                  * If the host name is followed by a colon, then\r
258                  * expect a port number after it.\r
259                  */\r
260                 if (*p == ':') {\r
261                     p++;\r
263                     conf_set_int(conf, CONF_port, atoi(p));\r
264                     /*\r
265                      * Set the flag that will stop us from treating\r
266                      * the next argument as a separate port; this one\r
267                      * counts as explicitly provided.\r
268                      */\r
269                     seen_port_argument = true;\r
270                 } else {\r
271                     conf_set_int(conf, CONF_port, -1);\r
272                 }\r
273             } else {\r
274                 char *user = NULL, *hostname = NULL;\r
275                 const char *hostname_after_user;\r
276                 int port_override = -1;\r
277                 size_t len;\r
279                 /*\r
280                  * Otherwise, treat it as a bare host name.\r
281                  */\r
283                 if (cmdline_tooltype & TOOLTYPE_HOST_ARG_PROTOCOL_PREFIX) {\r
284                     /*\r
285                      * Here Plink checks for a comma-separated\r
286                      * protocol prefix, e.g. 'ssh,hostname' or\r
287                      * 'ssh,user@hostname'.\r
288                      *\r
289                      * I'm not entirely sure why; this behaviour dates\r
290                      * from 2000 and isn't explained. But I _think_ it\r
291                      * has to do with CVS transport or similar use\r
292                      * cases, in which the end user invokes the SSH\r
293                      * client indirectly, via some means that only\r
294                      * lets them pass a single string argument, and it\r
295                      * was occasionally useful to shoehorn the choice\r
296                      * of protocol into that argument.\r
297                      */\r
298                     const char *comma = strchr(p, ',');\r
299                     if (comma) {\r
300                         char *prefix = dupprintf("%.*s", (int)(comma - p), p);\r
301                         const struct BackendVtable *vt =\r
302                             backend_vt_from_name(prefix);\r
304                         if (vt) {\r
305                             set_protocol(conf, vt->protocol);\r
306                             port_override = vt->default_port;\r
307                         } else {\r
308                             cmdline_error("unrecognised protocol prefix '%s'",\r
309                                           prefix);\r
310                         }\r
312                         sfree(prefix);\r
313                         p = comma + 1;\r
314                     }\r
315                 }\r
317                 hostname_after_user = p;\r
318                 if (cmdline_tooltype & TOOLTYPE_HOST_ARG_CAN_BE_SESSION) {\r
319                     /*\r
320                      * If the hostname argument can also be a saved\r
321                      * session (see below), then here we also check\r
322                      * for a user@ prefix, which will override the\r
323                      * username from the saved session.\r
324                      *\r
325                      * (If the hostname argument _isn't_ a saved\r
326                      * session, we don't do this.)\r
327                      */\r
328                     const char *at = strrchr(p, '@');\r
329                     if (at) {\r
330                         user = dupprintf("%.*s", (int)(at - p), p);\r
331                         hostname_after_user = at + 1;\r
332                     }\r
333                 }\r
335                 /*\r
336                  * Write the whole hostname argument (minus only that\r
337                  * optional protocol prefix) into the existing Conf,\r
338                  * for tools that don't treat it as a saved session\r
339                  * and as a fallback for those that do.\r
340                  */\r
341                 hostname = dupstr(p + strspn(p, " \t"));\r
342                 len = strlen(hostname);\r
343                 while (len > 0 && (hostname[len-1] == ' ' ||\r
344                                    hostname[len-1] == '\t'))\r
345                     hostname[--len] = '\0';\r
346                 seen_hostname_argument = true;\r
347                 conf_set_str(conf, CONF_host, hostname);\r
349                 if ((cmdline_tooltype & TOOLTYPE_HOST_ARG_CAN_BE_SESSION) &&\r
350                     !loaded_session) {\r
351                     /*\r
352                      * For some tools, we equivocate between a\r
353                      * hostname argument and an argument naming a\r
354                      * saved session. Here we attempt to load a\r
355                      * session with the specified name, and if that\r
356                      * session exists and is launchable, we overwrite\r
357                      * the entire Conf with it.\r
358                      *\r
359                      * We skip this check if a -load option has\r
360                      * already happened, so that\r
361                      *\r
362                      *   plink -load non-launchable-session hostname\r
363                      *\r
364                      * will treat 'hostname' as a hostname _even_ if a\r
365                      * saved session called 'hostname' exists. (This\r
366                      * doesn't lose any functionality someone could\r
367                      * have needed, because if 'hostname' did cause a\r
368                      * session to be loaded, then it would overwrite\r
369                      * everything from the previously loaded session.\r
370                      * So if that was the behaviour someone wanted,\r
371                      * then they could get it by leaving off the\r
372                      * -load completely.)\r
373                      */\r
374                     Conf *conf2 = conf_new();\r
375                     if (do_defaults(hostname_after_user, conf2) &&\r
376                         conf_launchable(conf2)) {\r
377                         conf_copy_into(conf, conf2);\r
378                         loaded_session = true;\r
379                         /* And override the username if one was given. */\r
380                         if (user)\r
381                             conf_set_str(conf, CONF_username, user);\r
382                     }\r
383                     conf_free(conf2);\r
384                 }\r
386                 sfree(hostname);\r
387                 sfree(user);\r
389                 if (port_override >= 0)\r
390                     conf_set_int(conf, CONF_port, port_override);\r
391             }\r
393             return 1;\r
394         } else if ((cmdline_tooltype & TOOLTYPE_PORT_ARG) &&\r
395                    !seen_port_argument) {\r
396             /*\r
397              * If we've already got a host name from the command line\r
398              * (either as a hostname argument or a qualifying -load),\r
399              * but not a port number, then treat the next argument as\r
400              * a port number.\r
401              *\r
402              * We handle this by calling ourself recursively to\r
403              * pretend we received a -P argument, so that it will be\r
404              * deferred until it's a good moment to run it.\r
405              */\r
406             char *dup = dupstr(p);     /* 'value' is not a const char * */\r
407             int retd = cmdline_process_param("-P", dup, 1, conf, false);\r
408             sfree(dup);\r
409             assert(retd == 2);\r
410             seen_port_argument = true;\r
411             return 1;\r
412         } else {\r
413             /*\r
414              * Refuse to recognise this argument, and give it back to\r
415              * the tool's own command-line processing.\r
416              */\r
417             return 0;\r
418         }\r
419     }\r
421     if (ignoreFurtherParameters)\r
422         return ret;\r
424     if (!strcmp(p, "-load")) {\r
425         RETURN(2);\r
426         /* This parameter must be processed immediately rather than being\r
427          * saved. */\r
428         do_defaults(value, conf);\r
429         loaded_session = true;\r
430         return 2;\r
431     }\r
432     for (size_t i = 0; backends[i]; i++) {\r
433         if (p[0] == '-' && !strcmp(p+1, backends[i]->id)) {\r
434             RETURN(1);\r
435             UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);\r
436             SAVEABLE(0);\r
437             set_protocol(conf, backends[i]->protocol);\r
438             if (backends[i]->default_port)\r
439                 set_port(conf, backends[i]->default_port);\r
440             if (backends[i]->protocol == PROT_SERIAL) {\r
441                 /* Special handling: the 'where to connect to' argument will\r
442                  * have been placed into CONF_host, but for this protocol, it\r
443                  * needs to be in CONF_serline */\r
444                 conf_set_str(conf, CONF_serline,\r
445                              conf_get_str(conf, CONF_host));\r
446             }\r
447             return 1;\r
448         }\r
449     }\r
450     if (!strcmp(p, "-v")) {\r
451         RETURN(1);\r
452         UNAVAILABLE_IN(TOOLTYPE_NO_VERBOSE_OPTION);\r
453         seen_verbose_option = true;\r
454     }\r
455     if (!strcmp(p, "-l")) {\r
456         RETURN(2);\r
457         UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);\r
458         SAVEABLE(0);\r
459         conf_set_str(conf, CONF_username, value);\r
460     }\r
461     if (!strcmp(p, "-loghost")) {\r
462         RETURN(2);\r
463         UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);\r
464         SAVEABLE(0);\r
465         conf_set_str(conf, CONF_loghost, value);\r
466     }\r
467     if (!strcmp(p, "-hostkey")) {\r
468         char *dup;\r
469         RETURN(2);\r
470         UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);\r
471         SAVEABLE(0);\r
472         dup = dupstr(value);\r
473         if (!validate_manual_hostkey(dup)) {\r
474             cmdline_error("'%s' is not a valid format for a manual host "\r
475                           "key specification", value);\r
476             sfree(dup);\r
477             return ret;\r
478         }\r
479         conf_set_str_str(conf, CONF_ssh_manual_hostkeys, dup, "");\r
480         sfree(dup);\r
481     }\r
482     if ((!strcmp(p, "-L") || !strcmp(p, "-R") || !strcmp(p, "-D"))) {\r
483         char type, *q, *qq, *key, *val;\r
484         RETURN(2);\r
485         UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);\r
486         SAVEABLE(0);\r
487         if (strcmp(p, "-D")) {\r
488             /*\r
489              * For -L or -R forwarding types:\r
490              *\r
491              * We expect _at least_ two colons in this string. The\r
492              * possible formats are `sourceport:desthost:destport',\r
493              * or `sourceip:sourceport:desthost:destport' if you're\r
494              * specifying a particular loopback address. We need to\r
495              * replace the one between source and dest with a \t;\r
496              * this means we must find the second-to-last colon in\r
497              * the string.\r
498              *\r
499              * (This looks like a foolish way of doing it given the\r
500              * existence of strrchr, but it's more efficient than\r
501              * two strrchrs - not to mention that the second strrchr\r
502              * would require us to modify the input string!)\r
503              */\r
505             type = p[1];               /* 'L' or 'R' */\r
507             q = qq = host_strchr(value, ':');\r
508             while (qq) {\r
509                 char *qqq = host_strchr(qq+1, ':');\r
510                 if (qqq)\r
511                     q = qq;\r
512                 qq = qqq;\r
513             }\r
515             if (!q) {\r
516                 cmdline_error("-%c expects at least two colons in its"\r
517                               " argument", type);\r
518                 return ret;\r
519             }\r
521             key = dupprintf("%c%.*s", type, (int)(q - value), value);\r
522             val = dupstr(q+1);\r
523         } else {\r
524             /*\r
525              * Dynamic port forwardings are entered under the same key\r
526              * as if they were local (because they occupy the same\r
527              * port space - a local and a dynamic forwarding on the\r
528              * same local port are mutually exclusive), with the\r
529              * special value "D" (which can be distinguished from\r
530              * anything in the ordinary -L case by containing no\r
531              * colon).\r
532              */\r
533             key = dupprintf("L%s", value);\r
534             val = dupstr("D");\r
535         }\r
536         conf_set_str_str(conf, CONF_portfwd, key, val);\r
537         sfree(key);\r
538         sfree(val);\r
539     }\r
540     if ((!strcmp(p, "-nc"))) {\r
541         char *host, *portp;\r
543         RETURN(2);\r
544         UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);\r
545         SAVEABLE(0);\r
547         portp = host_strchr(value, ':');\r
548         if (!portp) {\r
549             cmdline_error("-nc expects argument of form 'host:port'");\r
550             return ret;\r
551         }\r
553         host = dupprintf("%.*s", (int)(portp - value), value);\r
554         conf_set_str(conf, CONF_ssh_nc_host, host);\r
555         conf_set_int(conf, CONF_ssh_nc_port, atoi(portp + 1));\r
556         sfree(host);\r
557     }\r
558     if (!strcmp(p, "-m")) {\r
559         const char *filename;\r
560         FILE *fp;\r
562         RETURN(2);\r
563         UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);\r
564         SAVEABLE(0);\r
566         filename = value;\r
568         fp = fopen(filename, "r");\r
569         if (!fp) {\r
570             cmdline_error("unable to open command file \"%s\"", filename);\r
571             return ret;\r
572         }\r
573         strbuf *command = strbuf_new();\r
574         char readbuf[4096];\r
575         while (1) {\r
576             size_t nread = fread(readbuf, 1, sizeof(readbuf), fp);\r
577             if (nread == 0)\r
578                 break;\r
579             put_data(command, readbuf, nread);\r
580         }\r
581         fclose(fp);\r
582         conf_set_str(conf, CONF_remote_cmd, command->s);\r
583         conf_set_str(conf, CONF_remote_cmd2, "");\r
584         conf_set_bool(conf, CONF_nopty, true);   /* command => no terminal */\r
585         strbuf_free(command);\r
586     }\r
587     if (!strcmp(p, "-P") || !strcmp(p, "-p")) {\r
588         RETURN(2);\r
589         UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);\r
590         SAVEABLE(1);            /* lower priority than -ssh, -telnet, etc */\r
591         conf_set_int(conf, CONF_port, atoi(value));\r
592     }\r
593     if (!strcmp(p, "-o")) {\r
594         RETURN(2);\r
595         UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);\r
596         SAVEABLE(0);\r
597         if (stricmp(value, "SendEnv=GIT_PROTOCOL")) {\r
598             cmdline_error("Unrecognised suboption \"-o %s\"", value);\r
599             return -1;\r
600         }\r
601         const char *env = getenv("GIT_PROTOCOL");\r
602         if (env)\r
603             conf_set_str_str(conf, CONF_environmt, "GIT_PROTOCOL", env);\r
604     }\r
605     if (!strcmp(p, "-pw")) {\r
606         RETURN(2);\r
607         UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);\r
608         SAVEABLE(1);\r
609         /* We delay evaluating this until after the protocol is decided,\r
610          * so that we can warn if it's of no use with the selected protocol */\r
611         if (conf_get_int(conf, CONF_protocol) != PROT_SSH)\r
612             cmdline_error("the -pw option can only be used with the "\r
613                           "SSH protocol");\r
614         else {\r
615             if (cmdline_password) {\r
616                 smemclr(cmdline_password, strlen(cmdline_password));\r
617                 sfree(cmdline_password);\r
618             }\r
620             cmdline_password = dupstr(value);\r
621             /* Assuming that `value' is directly from argv, make a good faith\r
622              * attempt to trample it, to stop it showing up in `ps' output\r
623              * on Unix-like systems. Not guaranteed, of course. */\r
624             smemclr(value, strlen(value));\r
625         }\r
626     }\r
628     if (!strcmp(p, "-pwfile")) {\r
629         RETURN(2);\r
630         UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);\r
631         SAVEABLE(1);\r
632         /* We delay evaluating this until after the protocol is decided,\r
633          * so that we can warn if it's of no use with the selected protocol */\r
634         if (conf_get_int(conf, CONF_protocol) != PROT_SSH)\r
635             cmdline_error("the -pwfile option can only be used with the "\r
636                           "SSH protocol");\r
637         else {\r
638             Filename *fn = filename_from_str(value);\r
639             FILE *fp = f_open(fn, "r", false);\r
640             if (!fp) {\r
641                 cmdline_error("unable to open password file '%s'", value);\r
642             } else {\r
643                 if (cmdline_password) {\r
644                     smemclr(cmdline_password, strlen(cmdline_password));\r
645                     sfree(cmdline_password);\r
646                 }\r
648                 cmdline_password = chomp(fgetline(fp));\r
649                 if (!cmdline_password) {\r
650                     cmdline_error("unable to read a password from file '%s'",\r
651                                   value);\r
652                 }\r
653                 fclose(fp);\r
654             }\r
655             filename_free(fn);\r
656         }\r
657     }\r
659     if (!strcmp(p, "-agent") || !strcmp(p, "-pagent") ||\r
660         !strcmp(p, "-pageant")) {\r
661         RETURN(1);\r
662         UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);\r
663         SAVEABLE(0);\r
664         conf_set_bool(conf, CONF_tryagent, true);\r
665     }\r
666     if (!strcmp(p, "-noagent") || !strcmp(p, "-nopagent") ||\r
667         !strcmp(p, "-nopageant")) {\r
668         RETURN(1);\r
669         UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);\r
670         SAVEABLE(0);\r
671         conf_set_bool(conf, CONF_tryagent, false);\r
672     }\r
674     if (!strcmp(p, "-no-trivial-auth")) {\r
675         RETURN(1);\r
676         UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);\r
677         SAVEABLE(0);\r
678         conf_set_bool(conf, CONF_ssh_no_trivial_userauth, true);\r
679     }\r
681     if (!strcmp(p, "-share")) {\r
682         RETURN(1);\r
683         UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);\r
684         SAVEABLE(0);\r
685         conf_set_bool(conf, CONF_ssh_connection_sharing, true);\r
686     }\r
687     if (!strcmp(p, "-noshare")) {\r
688         RETURN(1);\r
689         UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);\r
690         SAVEABLE(0);\r
691         conf_set_bool(conf, CONF_ssh_connection_sharing, false);\r
692     }\r
693     if (!strcmp(p, "-A")) {\r
694         RETURN(1);\r
695         UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);\r
696         SAVEABLE(0);\r
697         conf_set_bool(conf, CONF_agentfwd, true);\r
698     }\r
699     if (!strcmp(p, "-a")) {\r
700         RETURN(1);\r
701         UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);\r
702         SAVEABLE(0);\r
703         conf_set_bool(conf, CONF_agentfwd, false);\r
704     }\r
706     if (!strcmp(p, "-X")) {\r
707         RETURN(1);\r
708         UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);\r
709         SAVEABLE(0);\r
710         conf_set_bool(conf, CONF_x11_forward, true);\r
711     }\r
712     if (!strcmp(p, "-x")) {\r
713         RETURN(1);\r
714         UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);\r
715         SAVEABLE(0);\r
716         conf_set_bool(conf, CONF_x11_forward, false);\r
717     }\r
719     if (!strcmp(p, "-t")) {\r
720         RETURN(1);\r
721         UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);\r
722         SAVEABLE(1);    /* lower priority than -m */\r
723         conf_set_bool(conf, CONF_nopty, false);\r
724     }\r
725     if (!strcmp(p, "-T")) {\r
726         RETURN(1);\r
727         UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);\r
728         SAVEABLE(1);\r
729         conf_set_bool(conf, CONF_nopty, true);\r
730     }\r
732     if (!strcmp(p, "-N")) {\r
733         RETURN(1);\r
734         UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);\r
735         SAVEABLE(0);\r
736         conf_set_bool(conf, CONF_ssh_no_shell, true);\r
737     }\r
739     if (!strcmp(p, "-C")) {\r
740         RETURN(1);\r
741         UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);\r
742         SAVEABLE(0);\r
743         conf_set_bool(conf, CONF_compression, true);\r
744     }\r
746     if (!strcmp(p, "-1")) {\r
747         RETURN(1);\r
748         UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);\r
749         SAVEABLE(0);\r
750         conf_set_int(conf, CONF_sshprot, 0);   /* ssh protocol 1 only */\r
751     }\r
752     if (!strcmp(p, "-2")) {\r
753         RETURN(1);\r
754         UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);\r
755         SAVEABLE(0);\r
756         conf_set_int(conf, CONF_sshprot, 3);   /* ssh protocol 2 only */\r
757     }\r
759     if (!strcmp(p, "-i")) {\r
760         Filename *fn;\r
761         RETURN(2);\r
762         UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);\r
763         SAVEABLE(0);\r
764         fn = filename_from_str(value);\r
765         conf_set_filename(conf, CONF_keyfile, fn);\r
766         filename_free(fn);\r
767     }\r
769     if (!strcmp(p, "-cert")) {\r
770         Filename *fn;\r
771         RETURN(2);\r
772         UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);\r
773         SAVEABLE(0);\r
774         fn = filename_from_str(value);\r
775         conf_set_filename(conf, CONF_detached_cert, fn);\r
776         filename_free(fn);\r
777     }\r
779     if (!strcmp(p, "-4") || !strcmp(p, "-ipv4")) {\r
780         RETURN(1);\r
781         UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);\r
782         SAVEABLE(1);\r
783         conf_set_int(conf, CONF_addressfamily, ADDRTYPE_IPV4);\r
784     }\r
785     if (!strcmp(p, "-6") || !strcmp(p, "-ipv6")) {\r
786         RETURN(1);\r
787         UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);\r
788         SAVEABLE(1);\r
789         conf_set_int(conf, CONF_addressfamily, ADDRTYPE_IPV6);\r
790     }\r
791     if (!strcmp(p, "-sercfg")) {\r
792         char* nextitem;\r
793         RETURN(2);\r
794         UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK);\r
795         SAVEABLE(1);\r
796         if (conf_get_int(conf, CONF_protocol) != PROT_SERIAL)\r
797             cmdline_error("the -sercfg option can only be used with the "\r
798                           "serial protocol");\r
799         /* Value[0] contains one or more , separated values, like 19200,8,n,1,X */\r
800         nextitem = value;\r
801         while (nextitem[0] != '\0') {\r
802             int length, skip;\r
803             char *end = strchr(nextitem, ',');\r
804             if (!end) {\r
805                 length = strlen(nextitem);\r
806                 skip = 0;\r
807             } else {\r
808                 length = end - nextitem;\r
809                 nextitem[length] = '\0';\r
810                 skip = 1;\r
811             }\r
812             if (length == 1) {\r
813                 switch (*nextitem) {\r
814                   case '1':\r
815                   case '2':\r
816                     conf_set_int(conf, CONF_serstopbits, 2 * (*nextitem-'0'));\r
817                     break;\r
819                   case '5':\r
820                   case '6':\r
821                   case '7':\r
822                   case '8':\r
823                   case '9':\r
824                     conf_set_int(conf, CONF_serdatabits, *nextitem-'0');\r
825                     break;\r
827                   case 'n':\r
828                     conf_set_int(conf, CONF_serparity, SER_PAR_NONE);\r
829                     break;\r
830                   case 'o':\r
831                     conf_set_int(conf, CONF_serparity, SER_PAR_ODD);\r
832                     break;\r
833                   case 'e':\r
834                     conf_set_int(conf, CONF_serparity, SER_PAR_EVEN);\r
835                     break;\r
836                   case 'm':\r
837                     conf_set_int(conf, CONF_serparity, SER_PAR_MARK);\r
838                     break;\r
839                   case 's':\r
840                     conf_set_int(conf, CONF_serparity, SER_PAR_SPACE);\r
841                     break;\r
843                   case 'N':\r
844                     conf_set_int(conf, CONF_serflow, SER_FLOW_NONE);\r
845                     break;\r
846                   case 'X':\r
847                     conf_set_int(conf, CONF_serflow, SER_FLOW_XONXOFF);\r
848                     break;\r
849                   case 'R':\r
850                     conf_set_int(conf, CONF_serflow, SER_FLOW_RTSCTS);\r
851                     break;\r
852                   case 'D':\r
853                     conf_set_int(conf, CONF_serflow, SER_FLOW_DSRDTR);\r
854                     break;\r
856                   default:\r
857                     cmdline_error("Unrecognised suboption \"-sercfg %c\"",\r
858                                   *nextitem);\r
859                 }\r
860             } else if (length == 3 && !strncmp(nextitem,"1.5",3)) {\r
861                 /* Messy special case */\r
862                 conf_set_int(conf, CONF_serstopbits, 3);\r
863             } else {\r
864                 int serspeed = atoi(nextitem);\r
865                 if (serspeed != 0) {\r
866                     conf_set_int(conf, CONF_serspeed, serspeed);\r
867                 } else {\r
868                     cmdline_error("Unrecognised suboption \"-sercfg %s\"",\r
869                                   nextitem);\r
870                 }\r
871             }\r
872             nextitem += length + skip;\r
873         }\r
874     }\r
876     if (!strcmp(p, "-sessionlog")) {\r
877         Filename *fn;\r
878         RETURN(2);\r
879         UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER);\r
880         /* but available even in TOOLTYPE_NONNETWORK, cf pterm "-log" */\r
881         SAVEABLE(0);\r
882         fn = filename_from_str(value);\r
883         conf_set_filename(conf, CONF_logfilename, fn);\r
884         conf_set_int(conf, CONF_logtype, LGTYP_DEBUG);\r
885         filename_free(fn);\r
886     }\r
888     if (!strcmp(p, "-sshlog") ||\r
889         !strcmp(p, "-sshrawlog")) {\r
890         Filename *fn;\r
891         RETURN(2);\r
892         UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);\r
893         SAVEABLE(0);\r
894         fn = filename_from_str(value);\r
895         conf_set_filename(conf, CONF_logfilename, fn);\r
896         conf_set_int(conf, CONF_logtype,\r
897                      !strcmp(p, "-sshlog") ? LGTYP_PACKETS :\r
898                      /* !strcmp(p, "-sshrawlog") ? */ LGTYP_SSHRAW);\r
899         filename_free(fn);\r
900     }\r
902     if (!strcmp(p, "-logoverwrite")) {\r
903         RETURN(1);\r
904         UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);\r
905         SAVEABLE(0);\r
906         conf_set_int(conf, CONF_logxfovr, LGXF_OVR);\r
907     }\r
909     if (!strcmp(p, "-logappend")) {\r
910         RETURN(1);\r
911         UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);\r
912         SAVEABLE(0);\r
913         conf_set_int(conf, CONF_logxfovr, LGXF_APN);\r
914     }\r
916     if (!strcmp(p, "-proxycmd")) {\r
917         RETURN(2);\r
918         UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);\r
919         SAVEABLE(0);\r
920         conf_set_int(conf, CONF_proxy_type, PROXY_CMD);\r
921         conf_set_str(conf, CONF_proxy_telnet_command, value);\r
922     }\r
924 #ifdef _WINDOWS\r
925     /*\r
926      * Cross-tool options only available on Windows.\r
927      */\r
928     if (!strcmp(p, "-restrict-acl") || !strcmp(p, "-restrict_acl") ||\r
929         !strcmp(p, "-restrictacl")) {\r
930         RETURN(1);\r
931         restrict_process_acl();\r
932     }\r
933 #endif\r
935     return ret;                        /* unrecognised */\r
938 void cmdline_run_saved(Conf *conf)\r
940     for (size_t pri = 0; pri < NPRIORITIES; pri++) {\r
941         for (size_t i = 0; i < saves[pri].nsaved; i++) {\r
942             cmdline_process_param(saves[pri].params[i].p,\r
943                                   saves[pri].params[i].value, 0, conf, false);\r
944             sfree(saves[pri].params[i].p);\r
945             sfree(saves[pri].params[i].value);\r
946         }\r
947         saves[pri].nsaved = 0;\r
948     }\r
951 bool cmdline_host_ok(Conf *conf)\r
953     /*\r
954      * Return true if the command-line arguments we've processed in\r
955      * TOOLTYPE_HOST_ARG mode are sufficient to justify launching a\r
956      * session.\r
957      */\r
958     assert(cmdline_tooltype & TOOLTYPE_HOST_ARG);\r
960     /*\r
961      * Of course, if we _can't_ launch a session, the answer is\r
962      * clearly no.\r
963      */\r
964     if (!conf_launchable(conf))\r
965         return false;\r
967     /*\r
968      * But also, if we haven't seen either a -load option or a\r
969      * hostname argument, i.e. the only saved settings we've loaded\r
970      * are Default Settings plus any non-hostname-based stuff from the\r
971      * command line, then the answer is still no, _even_ if this Conf\r
972      * is launchable. Otherwise, if you saved your favourite hostname\r
973      * into Default Settings, then just running 'putty' without\r
974      * arguments would connect to it without ever offering you the\r
975      * option to connect to something else or change the setting.\r
976      */\r
977     if (!seen_hostname_argument && !loaded_session)\r
978         return false;\r
980     return true;\r