+/*
+ * In psftp, all agent requests should be synchronous, so this is a
+ * never-called stub.
+ */
+void agent_schedule_callback(void (*callback)(void *, void *, int),
+ void *callback_ctx, void *data, int len)
+{
+ assert(!"We shouldn't be here");
+}
+
+/*
+ * Receive a block of data from the SSH link. Block until all data
+ * is available.
+ *
+ * To do this, we repeatedly call the SSH protocol module, with our
+ * own trap in from_backend() to catch the data that comes back. We
+ * do this until we have enough data.
+ */
+
+static unsigned char *outptr; /* where to put the data */
+static unsigned outlen; /* how much data required */
+static unsigned char *pending = NULL; /* any spare data */
+static unsigned pendlen = 0, pendsize = 0; /* length and phys. size of buffer */
+int from_backend(void *frontend, int is_stderr, const char *data, int datalen)
+{
+ unsigned char *p = (unsigned char *) data;
+ unsigned len = (unsigned) datalen;
+
+ /*
+ * stderr data is just spouted to local stderr and otherwise
+ * ignored.
+ */
+ if (is_stderr) {
+ if (len > 0)
+ fwrite(data, 1, len, stderr);
+ return 0;
+ }
+
+ /*
+ * If this is before the real session begins, just return.
+ */
+ if (!outptr)
+ return 0;
+
+ if ((outlen > 0) && (len > 0)) {
+ unsigned used = outlen;
+ if (used > len)
+ used = len;
+ memcpy(outptr, p, used);
+ outptr += used;
+ outlen -= used;
+ p += used;
+ len -= used;
+ }
+
+ if (len > 0) {
+ if (pendsize < pendlen + len) {
+ pendsize = pendlen + len + 4096;
+ pending = sresize(pending, pendsize, unsigned char);
+ }
+ memcpy(pending + pendlen, p, len);
+ pendlen += len;
+ }
+
+ return 0;
+}
+int sftp_recvdata(char *buf, int len)
+{
+ outptr = (unsigned char *) buf;
+ outlen = len;
+
+ /*
+ * See if the pending-input block contains some of what we
+ * need.
+ */
+ if (pendlen > 0) {
+ unsigned pendused = pendlen;
+ if (pendused > outlen)
+ pendused = outlen;
+ memcpy(outptr, pending, pendused);
+ memmove(pending, pending + pendused, pendlen - pendused);
+ outptr += pendused;
+ outlen -= pendused;
+ pendlen -= pendused;
+ if (pendlen == 0) {
+ pendsize = 0;
+ sfree(pending);
+ pending = NULL;
+ }
+ if (outlen == 0)
+ return 1;
+ }
+
+ while (outlen > 0) {
+ if (ssh_sftp_loop_iteration() < 0)
+ return 0; /* doom */
+ }
+
+ return 1;
+}
+int sftp_senddata(char *buf, int len)
+{
+ back->send(backhandle, buf, len);
+ return 1;
+}
+
+/*
+ * Short description of parameters.
+ */
+static void usage(void)
+{
+ printf("PuTTY Secure File Transfer (SFTP) client\n");
+ printf("%s\n", ver);
+ printf("Usage: psftp [options] [user@]host\n");
+ printf("Options:\n");
+ printf(" -b file use specified batchfile\n");
+ printf(" -bc output batchfile commands\n");
+ printf(" -be don't stop batchfile processing if errors\n");
+ printf(" -v show verbose messages\n");
+ printf(" -load sessname Load settings from saved session\n");
+ printf(" -l user connect with specified username\n");
+ printf(" -P port connect to specified port\n");
+ printf(" -pw passw login with specified password\n");
+ printf(" -1 -2 force use of particular SSH protocol version\n");
+ printf(" -C enable compression\n");
+ printf(" -i key private key file for authentication\n");
+ printf(" -batch disable all interactive prompts\n");
+ printf(" -V print version information\n");
+ cleanup_exit(1);
+}
+
+static void version(void)
+{
+ printf("psftp: %s\n", ver);
+ cleanup_exit(1);
+}
+
+/*
+ * Connect to a host.
+ */
+static int psftp_connect(char *userhost, char *user, int portnumber)
+{
+ char *host, *realhost;
+ const char *err;
+ void *logctx;
+
+ /* Separate host and username */
+ host = userhost;
+ host = strrchr(host, '@');
+ if (host == NULL) {
+ host = userhost;
+ } else {
+ *host++ = '\0';
+ if (user) {
+ printf("psftp: multiple usernames specified; using \"%s\"\n",
+ user);
+ } else
+ user = userhost;
+ }
+
+ /*
+ * If we haven't loaded session details already (e.g., from -load),
+ * try looking for a session called "host".
+ */
+ if (!loaded_session) {
+ /* Try to load settings for `host' into a temporary config */
+ Config cfg2;
+ cfg2.host[0] = '\0';
+ do_defaults(host, &cfg2);
+ if (cfg2.host[0] != '\0') {
+ /* Settings present and include hostname */
+ /* Re-load data into the real config. */
+ do_defaults(host, &cfg);
+ } else {
+ /* Session doesn't exist or mention a hostname. */
+ /* Use `host' as a bare hostname. */
+ strncpy(cfg.host, host, sizeof(cfg.host) - 1);
+ cfg.host[sizeof(cfg.host) - 1] = '\0';
+ }
+ } else {
+ /* Patch in hostname `host' to session details. */
+ strncpy(cfg.host, host, sizeof(cfg.host) - 1);
+ cfg.host[sizeof(cfg.host) - 1] = '\0';
+ }
+
+ /*
+ * Force use of SSH. (If they got the protocol wrong we assume the
+ * port is useless too.)
+ */
+ if (cfg.protocol != PROT_SSH) {
+ cfg.protocol = PROT_SSH;
+ cfg.port = 22;
+ }
+
+ /*
+ * If saved session / Default Settings says SSH-1 (`1 only' or `1'),
+ * then change it to SSH-2, on the grounds that that's more likely to
+ * work for SFTP. (Can be overridden with `-1' option.)
+ * But if it says `2 only' or `2', respect which.
+ */
+ if (cfg.sshprot != 2 && cfg.sshprot != 3)
+ cfg.sshprot = 2;
+
+ /*
+ * Enact command-line overrides.
+ */
+ cmdline_run_saved(&cfg);
+
+ /*
+ * Trim leading whitespace off the hostname if it's there.
+ */
+ {
+ int space = strspn(cfg.host, " \t");
+ memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
+ }
+
+ /* See if host is of the form user@host */
+ if (cfg.host[0] != '\0') {
+ char *atsign = strrchr(cfg.host, '@');
+ /* Make sure we're not overflowing the user field */
+ if (atsign) {
+ if (atsign - cfg.host < sizeof cfg.username) {
+ strncpy(cfg.username, cfg.host, atsign - cfg.host);
+ cfg.username[atsign - cfg.host] = '\0';
+ }
+ memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
+ }
+ }
+
+ /*
+ * Trim a colon suffix off the hostname if it's there.
+ */
+ cfg.host[strcspn(cfg.host, ":")] = '\0';
+
+ /*
+ * Remove any remaining whitespace from the hostname.
+ */
+ {
+ int p1 = 0, p2 = 0;
+ while (cfg.host[p2] != '\0') {
+ if (cfg.host[p2] != ' ' && cfg.host[p2] != '\t') {
+ cfg.host[p1] = cfg.host[p2];
+ p1++;
+ }
+ p2++;
+ }
+ cfg.host[p1] = '\0';
+ }
+
+ /* Set username */
+ if (user != NULL && user[0] != '\0') {
+ strncpy(cfg.username, user, sizeof(cfg.username) - 1);
+ cfg.username[sizeof(cfg.username) - 1] = '\0';
+ }
+ if (!cfg.username[0]) {
+ if (!console_get_line("login as: ",
+ cfg.username, sizeof(cfg.username), FALSE)) {
+ fprintf(stderr, "psftp: no username, aborting\n");
+ cleanup_exit(1);
+ } else {
+ int len = strlen(cfg.username);
+ if (cfg.username[len - 1] == '\n')
+ cfg.username[len - 1] = '\0';
+ }
+ }
+
+ if (portnumber)
+ cfg.port = portnumber;
+
+ /*
+ * Disable scary things which shouldn't be enabled for simple
+ * things like SCP and SFTP: agent forwarding, port forwarding,
+ * X forwarding.
+ */
+ cfg.x11_forward = 0;
+ cfg.agentfwd = 0;
+ cfg.portfwd[0] = cfg.portfwd[1] = '\0';
+
+ /* Set up subsystem name. */
+ strcpy(cfg.remote_cmd, "sftp");
+ cfg.ssh_subsys = TRUE;
+ cfg.nopty = TRUE;
+
+ /*
+ * Set up fallback option, for SSH1 servers or servers with the
+ * sftp subsystem not enabled but the server binary installed
+ * in the usual place. We only support fallback on Unix
+ * systems, and we use a kludgy piece of shellery which should
+ * try to find sftp-server in various places (the obvious
+ * systemwide spots /usr/lib and /usr/local/lib, and then the
+ * user's PATH) and finally give up.
+ *
+ * test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server
+ * test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server
+ * exec sftp-server
+ *
+ * the idea being that this will attempt to use either of the
+ * obvious pathnames and then give up, and when it does give up
+ * it will print the preferred pathname in the error messages.
+ */
+ cfg.remote_cmd_ptr2 =
+ "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n"
+ "test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n"
+ "exec sftp-server";
+ cfg.ssh_subsys2 = FALSE;
+
+ back = &ssh_backend;
+
+ err = back->init(NULL, &backhandle, &cfg, cfg.host, cfg.port, &realhost,
+ 0, cfg.tcp_keepalives);
+ if (err != NULL) {
+ fprintf(stderr, "ssh_init: %s\n", err);
+ return 1;
+ }
+ logctx = log_init(NULL, &cfg);
+ back->provide_logctx(backhandle, logctx);
+ console_provide_logctx(logctx);
+ while (!back->sendok(backhandle)) {
+ if (ssh_sftp_loop_iteration() < 0) {
+ fprintf(stderr, "ssh_init: error during SSH connection setup\n");
+ return 1;
+ }
+ }
+ if (verbose && realhost != NULL)
+ printf("Connected to %s\n", realhost);
+ if (realhost != NULL)
+ sfree(realhost);
+ return 0;
+}
+
+void cmdline_error(char *p, ...)
+{
+ va_list ap;
+ fprintf(stderr, "psftp: ");
+ va_start(ap, p);
+ vfprintf(stderr, p, ap);
+ va_end(ap);
+ fprintf(stderr, "\n try typing \"psftp -h\" for help\n");
+ exit(1);
+}
+
+/*
+ * Main program. Parse arguments etc.
+ */
+int psftp_main(int argc, char *argv[])
+{
+ int i;
+ int portnumber = 0;
+ char *userhost, *user;
+ int mode = 0;
+ int modeflags = 0;
+ char *batchfile = NULL;
+ int errors = 0;
+
+ flags = FLAG_STDERR | FLAG_INTERACTIVE
+#ifdef FLAG_SYNCAGENT
+ | FLAG_SYNCAGENT
+#endif
+ ;
+ cmdline_tooltype = TOOLTYPE_FILETRANSFER;
+ ssh_get_line = &console_get_line;
+ sk_init();
+
+ userhost = user = NULL;
+
+ /* Load Default Settings before doing anything else. */
+ do_defaults(NULL, &cfg);
+ loaded_session = FALSE;
+
+ errors = 0;
+ for (i = 1; i < argc; i++) {
+ int ret;
+ if (argv[i][0] != '-') {
+ if (userhost)
+ usage();
+ else
+ userhost = dupstr(argv[i]);
+ continue;
+ }
+ ret = cmdline_process_param(argv[i], i+1<argc?argv[i+1]:NULL, 1, &cfg);
+ if (ret == -2) {
+ cmdline_error("option \"%s\" requires an argument", argv[i]);
+ } else if (ret == 2) {
+ i++; /* skip next argument */
+ } else if (ret == 1) {
+ /* We have our own verbosity in addition to `flags'. */
+ if (flags & FLAG_VERBOSE)
+ verbose = 1;
+ } else if (strcmp(argv[i], "-h") == 0 ||
+ strcmp(argv[i], "-?") == 0) {
+ usage();
+ } else if (strcmp(argv[i], "-V") == 0) {
+ version();
+ } else if (strcmp(argv[i], "-batch") == 0) {
+ console_batch_mode = 1;
+ } else if (strcmp(argv[i], "-b") == 0 && i + 1 < argc) {
+ mode = 1;
+ batchfile = argv[++i];
+ } else if (strcmp(argv[i], "-bc") == 0) {
+ modeflags = modeflags | 1;
+ } else if (strcmp(argv[i], "-be") == 0) {
+ modeflags = modeflags | 2;
+ } else if (strcmp(argv[i], "--") == 0) {
+ i++;
+ break;
+ } else {
+ cmdline_error("unknown option \"%s\"", argv[i]);
+ }
+ }
+ argc -= i;
+ argv += i;
+ back = NULL;
+
+ /*
+ * If the loaded session provides a hostname, and a hostname has not
+ * otherwise been specified, pop it in `userhost' so that
+ * `psftp -load sessname' is sufficient to start a session.
+ */
+ if (!userhost && cfg.host[0] != '\0') {
+ userhost = dupstr(cfg.host);
+ }
+
+ /*
+ * If a user@host string has already been provided, connect to
+ * it now.
+ */
+ if (userhost) {
+ int ret;
+ ret = psftp_connect(userhost, user, portnumber);
+ sfree(userhost);
+ if (ret)
+ return 1;
+ if (do_sftp_init())
+ return 1;
+ } else {
+ printf("psftp: no hostname specified; use \"open host.name\""
+ " to connect\n");
+ }
+
+ do_sftp(mode, modeflags, batchfile);
+
+ if (back != NULL && back->socket(backhandle) != NULL) {
+ char ch;
+ back->special(backhandle, TS_EOF);
+ sftp_recvdata(&ch, 1);
+ }
+ random_save_seed();
+ cmdline_cleanup();
+ console_provide_logctx(NULL);
+ do_sftp_cleanup();
+ backhandle = NULL;
+ back = NULL;
+ sk_cleanup();
+