2 * scp.c - Scp (Secure Copy) client for PuTTY.
3 * Joris van Rantwijk, Simon Tatham
5 * This is mainly based on ssh-1.2.26/scp.c by Timo Rinne & Tatu Ylonen.
6 * They, in turn, used stuff from BSD rcp.
8 * Adaptations to enable connecting a GUI by L. Gunnarsson - Sept 2000
17 /* GUI Adaptation - Sept 2000 */
21 #define PUTTY_DO_GLOBALS
25 #define TIME_POSIX_TO_WIN(t, ft) (*(LONGLONG*)&(ft) = \
26 ((LONGLONG) (t) + (LONGLONG) 11644473600) * (LONGLONG) 10000000)
27 #define TIME_WIN_TO_POSIX(ft, t) ((t) = (unsigned long) \
28 ((*(LONGLONG*)&(ft)) / (LONGLONG) 10000000 - (LONGLONG) 11644473600))
30 /* GUI Adaptation - Sept 2000 */
31 #define WM_APP_BASE 0x8000
32 #define WM_STD_OUT_CHAR ( WM_APP_BASE+400 )
33 #define WM_STD_ERR_CHAR ( WM_APP_BASE+401 )
34 #define WM_STATS_CHAR ( WM_APP_BASE+402 )
35 #define WM_STATS_SIZE ( WM_APP_BASE+403 )
36 #define WM_STATS_PERCENT ( WM_APP_BASE+404 )
37 #define WM_STATS_ELAPSED ( WM_APP_BASE+405 )
38 #define WM_RET_ERR_CNT ( WM_APP_BASE+406 )
39 #define WM_LS_RET_ERR_CNT ( WM_APP_BASE+407 )
41 static int verbose = 0;
42 static int recursive = 0;
43 static int preserve = 0;
44 static int targetshouldbedirectory = 0;
45 static int statistics = 1;
46 static int portnumber = 0;
47 static char *password = NULL;
49 static int connection_open = 0;
50 /* GUI Adaptation - Sept 2000 */
51 #define NAME_STR_MAX 2048
52 static char statname[NAME_STR_MAX+1];
53 static unsigned long statsize = 0;
54 static int statperct = 0;
55 static time_t statelapsed = 0;
56 static int gui_mode = 0;
57 static char *gui_hwnd = NULL;
59 static void source(char *src);
60 static void rsource(char *src);
61 static void sink(char *targ);
62 /* GUI Adaptation - Sept 2000 */
63 static void tell_char(FILE *stream, char c);
64 static void tell_str(FILE *stream, char *str);
65 static void tell_user(FILE *stream, char *fmt, ...);
66 static void send_char_msg(unsigned int msg_id, char c);
67 static void send_str_msg(unsigned int msg_id, char *str);
68 static void gui_update_stats(char *name, unsigned long size, int percentage, time_t elapsed);
71 * This function is needed to link with ssh.c, but it never gets called.
78 /* GUI Adaptation - Sept 2000 */
79 void send_msg(HWND h, UINT message, WPARAM wParam)
81 while (!PostMessage( h, message, wParam, 0))
85 void tell_char(FILE *stream, char c)
91 unsigned int msg_id = WM_STD_OUT_CHAR;
92 if (stream = stderr) msg_id = WM_STD_ERR_CHAR;
93 send_msg( (HWND)atoi(gui_hwnd), msg_id, (WPARAM)c );
97 void tell_str(FILE *stream, char *str)
101 for( i = 0; i < strlen(str); ++i )
102 tell_char(stream, str[i]);
105 void tell_user(FILE *stream, char *fmt, ...)
107 char str[0x100]; /* Make the size big enough */
110 vsprintf(str, fmt, ap);
113 tell_str(stream, str);
116 void gui_update_stats(char *name, unsigned long size, int percentage, time_t elapsed)
120 if (strcmp(name,statname) != 0)
122 for( i = 0; i < strlen(name); ++i )
123 send_msg( (HWND)atoi(gui_hwnd), WM_STATS_CHAR, (WPARAM)name[i]);
124 send_msg( (HWND)atoi(gui_hwnd), WM_STATS_CHAR, (WPARAM)'\n' );
125 strcpy(statname,name);
127 if (statsize != size)
129 send_msg( (HWND)atoi(gui_hwnd), WM_STATS_SIZE, (WPARAM)size );
132 if (statelapsed != elapsed)
134 send_msg( (HWND)atoi(gui_hwnd), WM_STATS_ELAPSED, (WPARAM)elapsed );
135 statelapsed = elapsed;
137 if (statperct != percentage)
139 send_msg( (HWND)atoi(gui_hwnd), WM_STATS_PERCENT, (WPARAM)percentage );
140 statperct = percentage;
145 * Print an error message and perform a fatal exit.
147 void fatalbox(char *fmt, ...)
149 char str[0x100]; /* Make the size big enough */
152 strcpy(str, "Fatal:");
153 vsprintf(str+strlen(str), fmt, ap);
156 tell_str(stderr, str);
162 * Print an error message and exit after closing the SSH link.
164 static void bump(char *fmt, ...)
166 char str[0x100]; /* Make the size big enough */
169 strcpy(str, "Fatal:");
170 vsprintf(str+strlen(str), fmt, ap);
173 tell_str(stderr, str);
175 if (connection_open) {
178 ssh_scp_recv(&ch, 1);
183 static int get_password(const char *prompt, char *str, int maxlen)
189 static int tried_once = 0;
194 strncpy(str, password, maxlen);
195 str[maxlen-1] = '\0';
201 /* GUI Adaptation - Sept 2000 */
203 if (maxlen>0) str[0] = '\0';
205 hin = GetStdHandle(STD_INPUT_HANDLE);
206 hout = GetStdHandle(STD_OUTPUT_HANDLE);
207 if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE)
208 bump("Cannot get standard input/output handles");
210 GetConsoleMode(hin, &savemode);
211 SetConsoleMode(hin, (savemode & (~ENABLE_ECHO_INPUT)) |
212 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT);
214 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
215 ReadFile(hin, str, maxlen-1, &i, NULL);
217 SetConsoleMode(hin, savemode);
219 if ((int)i > maxlen) i = maxlen-1; else i = i - 2;
222 WriteFile(hout, "\r\n", 2, &i, NULL);
229 * Open an SSH connection to user@host and execute cmd.
231 static void do_cmd(char *host, char *user, char *cmd)
233 char *err, *realhost;
235 if (host == NULL || host[0] == '\0')
236 bump("Empty host name");
238 /* Try to load settings for this host */
240 if (cfg.host[0] == '\0') {
241 /* No settings for this host; use defaults */
242 strncpy(cfg.host, host, sizeof(cfg.host)-1);
243 cfg.host[sizeof(cfg.host)-1] = '\0';
248 if (user != NULL && user[0] != '\0') {
249 strncpy(cfg.username, user, sizeof(cfg.username)-1);
250 cfg.username[sizeof(cfg.username)-1] = '\0';
251 } else if (cfg.username[0] == '\0') {
252 bump("Empty user name");
255 if (cfg.protocol != PROT_SSH)
259 cfg.port = portnumber;
261 err = ssh_scp_init(cfg.host, cfg.port, cmd, &realhost);
263 bump("ssh_init: %s", err);
264 if (verbose && realhost != NULL)
265 tell_user(stderr, "Connected to %s\n", realhost);
271 * Update statistic information about current file.
273 static void print_stats(char *name, unsigned long size, unsigned long done,
274 time_t start, time_t now)
281 /* GUI Adaptation - Sept 2000 */
283 gui_update_stats(name, size, ((done *100) / size), now-start);
286 ratebs = (float) done / (now - start);
288 ratebs = (float) done;
293 eta = (unsigned long) ((size - done) / ratebs);
294 sprintf(etastr, "%02ld:%02ld:%02ld",
295 eta / 3600, (eta % 3600) / 60, eta % 60);
297 pct = (int) (100.0 * (float) done / size);
299 printf("\r%-25.25s | %10ld kB | %5.1f kB/s | ETA: %8s | %3d%%",
300 name, done / 1024, ratebs / 1024.0,
309 * Find a colon in str and return a pointer to the colon.
310 * This is used to separate hostname from filename.
312 static char * colon(char *str)
314 /* We ignore a leading colon, since the hostname cannot be
315 empty. We also ignore a colon as second character because
316 of filenames like f:myfile.txt. */
317 if (str[0] == '\0' ||
321 while (*str != '\0' &&
333 * Wait for a response from the other side.
334 * Return 0 if ok, -1 if error.
336 static int response(void)
338 char ch, resp, rbuf[2048];
341 if (ssh_scp_recv(&resp, 1) <= 0)
342 bump("Lost connection");
352 case 2: /* fatal error */
354 if (ssh_scp_recv(&ch, 1) <= 0)
355 bump("Protocol error: Lost connection");
357 } while (p < sizeof(rbuf) && ch != '\n');
360 tell_user(stderr, "%s\n", rbuf);
369 * Send an error message to the other side and to the screen.
370 * Increment error counter.
372 static void run_err(const char *fmt, ...)
378 strcpy(str, "\01scp: ");
379 vsprintf(str+strlen(str), fmt, ap);
381 ssh_scp_send(str, strlen(str));
382 tell_user(stderr, "%s",str);
387 * Execute the source part of the SCP protocol.
389 static void source(char *src)
397 unsigned long stat_bytes;
398 time_t stat_starttime, stat_lasttime;
400 attr = GetFileAttributes(src);
401 if (attr == (DWORD)-1) {
402 run_err("%s: No such file or directory", src);
406 if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) {
409 * Avoid . and .. directories.
412 p = strrchr(src, '/');
414 p = strrchr(src, '\\');
419 if (!strcmp(p, ".") || !strcmp(p, ".."))
424 run_err("%s: not a regular file", src);
429 if ((last = strrchr(src, '/')) == NULL)
433 if (strrchr(last, '\\') != NULL)
434 last = strrchr(last, '\\') + 1;
435 if (last == src && strchr(src, ':') != NULL)
436 last = strchr(src, ':') + 1;
438 f = CreateFile(src, GENERIC_READ, FILE_SHARE_READ, NULL,
439 OPEN_EXISTING, 0, 0);
440 if (f == INVALID_HANDLE_VALUE) {
441 run_err("%s: Cannot open file", src);
446 FILETIME actime, wrtime;
447 unsigned long mtime, atime;
448 GetFileTime(f, NULL, &actime, &wrtime);
449 TIME_WIN_TO_POSIX(actime, atime);
450 TIME_WIN_TO_POSIX(wrtime, mtime);
451 sprintf(buf, "T%lu 0 %lu 0\n", mtime, atime);
452 ssh_scp_send(buf, strlen(buf));
457 size = GetFileSize(f, NULL);
458 sprintf(buf, "C0644 %lu %s\n", size, last);
460 tell_user(stderr, "Sending file modes: %s", buf);
461 ssh_scp_send(buf, strlen(buf));
467 stat_starttime = time(NULL);
471 for (i = 0; i < size; i += 4096) {
474 if (i + k > size) k = size - i;
475 if (! ReadFile(f, transbuf, k, &j, NULL) || j != k) {
476 if (statistics) printf("\n");
477 bump("%s: Read error", src);
479 ssh_scp_send(transbuf, k);
482 if (time(NULL) != stat_lasttime ||
484 stat_lasttime = time(NULL);
485 print_stats(last, size, stat_bytes,
486 stat_starttime, stat_lasttime);
497 * Recursively send the contents of a directory.
499 static void rsource(char *src)
504 WIN32_FIND_DATA fdat;
507 if ((last = strrchr(src, '/')) == NULL)
511 if (strrchr(last, '\\') != NULL)
512 last = strrchr(last, '\\') + 1;
513 if (last == src && strchr(src, ':') != NULL)
514 last = strchr(src, ':') + 1;
516 /* maybe send filetime */
518 sprintf(buf, "D0755 0 %s\n", last);
520 tell_user(stderr, "Entering directory: %s", buf);
521 ssh_scp_send(buf, strlen(buf));
525 sprintf(buf, "%s/*", src);
526 dir = FindFirstFile(buf, &fdat);
527 ok = (dir != INVALID_HANDLE_VALUE);
529 if (strcmp(fdat.cFileName, ".") == 0 ||
530 strcmp(fdat.cFileName, "..") == 0) {
531 } else if (strlen(src) + 1 + strlen(fdat.cFileName) >=
533 run_err("%s/%s: Name too long", src, fdat.cFileName);
535 sprintf(buf, "%s/%s", src, fdat.cFileName);
538 ok = FindNextFile(dir, &fdat);
543 ssh_scp_send(buf, strlen(buf));
548 * Execute the sink part of the SCP protocol.
550 static void sink(char *targ)
560 unsigned long mtime, atime;
562 unsigned long size, i;
564 unsigned long stat_bytes;
565 time_t stat_starttime, stat_lasttime;
568 attr = GetFileAttributes(targ);
569 if (attr != (DWORD)-1 && (attr & FILE_ATTRIBUTE_DIRECTORY) != 0)
572 if (targetshouldbedirectory && !targisdir)
573 bump("%s: Not a directory", targ);
579 if (ssh_scp_recv(&ch, 1) <= 0)
582 bump("Protocol error: Unexpected newline");
586 if (ssh_scp_recv(&ch, 1) <= 0)
587 bump("Lost connection");
589 } while (i < sizeof(buf) && ch != '\n');
592 case '\01': /* error */
593 tell_user(stderr, "%s\n", buf+1);
596 case '\02': /* fatal error */
602 if (sscanf(buf, "T%ld %*d %ld %*d",
603 &mtime, &atime) == 2) {
608 bump("Protocol error: Illegal time format");
613 bump("Protocol error: Expected control record");
616 if (sscanf(buf+1, "%u %lu %[^\n]", &mode, &size, namebuf) != 3)
617 bump("Protocol error: Illegal file descriptor format");
626 strcpy(namebuf, targ);
628 attr = GetFileAttributes(namebuf);
629 exists = (attr != (DWORD)-1);
632 if (exists && (attr & FILE_ATTRIBUTE_DIRECTORY) == 0) {
633 run_err("%s: Not a directory", namebuf);
637 if (! CreateDirectory(namebuf, NULL)) {
638 run_err("%s: Cannot create directory",
644 /* can we set the timestamp for directories ? */
648 f = CreateFile(namebuf, GENERIC_WRITE, 0, NULL,
649 CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
650 if (f == INVALID_HANDLE_VALUE) {
651 run_err("%s: Cannot create file", namebuf);
659 stat_starttime = time(NULL);
661 if ((stat_name = strrchr(namebuf, '/')) == NULL)
665 if (strrchr(stat_name, '\\') != NULL)
666 stat_name = strrchr(stat_name, '\\') + 1;
669 for (i = 0; i < size; i += 4096) {
672 if (i + k > size) k = size - i;
673 if (ssh_scp_recv(transbuf, k) == 0)
674 bump("Lost connection");
675 if (wrerror) continue;
676 if (! WriteFile(f, transbuf, k, &j, NULL) || j != k) {
679 printf("\r%-25.25s | %50s\n",
681 "Write error.. waiting for end of file");
686 if (time(NULL) > stat_lasttime ||
688 stat_lasttime = time(NULL);
689 print_stats(stat_name, size, stat_bytes,
690 stat_starttime, stat_lasttime);
697 FILETIME actime, wrtime;
698 TIME_POSIX_TO_WIN(atime, actime);
699 TIME_POSIX_TO_WIN(mtime, wrtime);
700 SetFileTime(f, NULL, &actime, &wrtime);
705 run_err("%s: Write error", namebuf);
713 * We will copy local files to a remote server.
715 static void toremote(int argc, char *argv[])
717 char *src, *targ, *host, *user;
723 /* Separate host from filename */
727 bump("targ == NULL in toremote()");
731 /* Substitute "." for emtpy target */
733 /* Separate host and username */
735 host = strrchr(host, '@');
746 /* Find out if the source filespec covers multiple files
747 if so, we should set the targetshouldbedirectory flag */
749 WIN32_FIND_DATA fdat;
750 if (colon(argv[0]) != NULL)
751 bump("%s: Remote to remote not supported", argv[0]);
752 fh = FindFirstFile(argv[0], &fdat);
753 if (fh == INVALID_HANDLE_VALUE)
754 bump("%s: No such file or directory\n", argv[0]);
755 if (FindNextFile(fh, &fdat))
756 targetshouldbedirectory = 1;
760 cmd = smalloc(strlen(targ) + 100);
761 sprintf(cmd, "scp%s%s%s%s -t %s",
762 verbose ? " -v" : "",
763 recursive ? " -r" : "",
764 preserve ? " -p" : "",
765 targetshouldbedirectory ? " -d" : "",
767 do_cmd(host, user, cmd);
772 for (i = 0; i < argc - 1; i++) {
774 WIN32_FIND_DATA fdat;
776 if (colon(src) != NULL) {
777 tell_user(stderr, "%s: Remote to remote not supported\n", src);
781 dir = FindFirstFile(src, &fdat);
782 if (dir == INVALID_HANDLE_VALUE) {
783 run_err("%s: No such file or directory", src);
789 if (strlen(src) + strlen(fdat.cFileName) >=
791 tell_user(stderr, "%s: Name too long", src);
794 strcpy(namebuf, src);
795 if ((last = strrchr(namebuf, '/')) == NULL)
799 if (strrchr(last, '\\') != NULL)
800 last = strrchr(last, '\\') + 1;
801 if (last == namebuf && strrchr(namebuf, ':') != NULL)
802 last = strchr(namebuf, ':') + 1;
803 strcpy(last, fdat.cFileName);
805 } while (FindNextFile(dir, &fdat));
811 * We will copy files from a remote server to the local machine.
813 static void tolocal(int argc, char *argv[])
815 char *src, *targ, *host, *user;
819 bump("More than one remote source not supported");
824 /* Separate host from filename */
828 bump("Local to local copy not supported");
832 /* Substitute "." for empty filename */
834 /* Separate username and hostname */
836 host = strrchr(host, '@');
846 cmd = smalloc(strlen(src) + 100);
847 sprintf(cmd, "scp%s%s%s%s -f %s",
848 verbose ? " -v" : "",
849 recursive ? " -r" : "",
850 preserve ? " -p" : "",
851 targetshouldbedirectory ? " -d" : "",
853 do_cmd(host, user, cmd);
860 * We will issue a list command to get a remote directory.
862 static void get_dir_list(int argc, char *argv[])
864 char *src, *host, *user;
870 /* Separate host from filename */
874 bump("Local to local copy not supported");
878 /* Substitute "." for empty filename */
880 /* Separate username and hostname */
882 host = strrchr(host, '@');
892 cmd = smalloc(4*strlen(src) + 100);
893 strcpy(cmd, "ls -la '");
894 p = cmd + strlen(cmd);
895 for (q = src; *q; q++) {
897 *p++ = '\''; *p++ = '\\'; *p++ = '\''; *p++ = '\'';
905 do_cmd(host, user, cmd);
908 while (ssh_scp_recv(&c, 1) > 0)
909 tell_char(stdout, c);
913 * Initialize the Win$ock driver.
915 static void init_winsock(void)
920 winsock_ver = MAKEWORD(1, 1);
921 if (WSAStartup(winsock_ver, &wsadata))
922 bump("Unable to initialise WinSock");
923 if (LOBYTE(wsadata.wVersion) != 1 ||
924 HIBYTE(wsadata.wVersion) != 1)
925 bump("WinSock version is incompatible with 1.1");
929 * Short description of parameters.
931 static void usage(void)
933 printf("PuTTY Secure Copy client\n");
935 printf("Usage: pscp [options] [user@]host:source target\n");
936 printf(" pscp [options] source [source...] [user@]host:target\n");
937 printf(" pscp [options] -ls user@host:filespec\n");
938 printf("Options:\n");
939 printf(" -p preserve file attributes\n");
940 printf(" -q quiet, don't show statistics\n");
941 printf(" -r copy directories recursively\n");
942 printf(" -v show verbose messages\n");
943 printf(" -P port connect to specified port\n");
944 printf(" -pw passw login with specified password\n");
945 /* GUI Adaptation - Sept 2000 */
946 printf(" -gui hWnd GUI mode with the windows handle for receiving messages\n");
951 * Main program (no, really?)
953 int main(int argc, char *argv[])
958 default_protocol = PROT_TELNET;
961 ssh_get_password = &get_password;
964 for (i = 1; i < argc; i++) {
965 if (argv[i][0] != '-')
967 if (strcmp(argv[i], "-v") == 0)
968 verbose = 1, flags |= FLAG_VERBOSE;
969 else if (strcmp(argv[i], "-r") == 0)
971 else if (strcmp(argv[i], "-p") == 0)
973 else if (strcmp(argv[i], "-q") == 0)
975 else if (strcmp(argv[i], "-h") == 0 ||
976 strcmp(argv[i], "-?") == 0)
978 else if (strcmp(argv[i], "-P") == 0 && i+1 < argc)
979 portnumber = atoi(argv[++i]);
980 else if (strcmp(argv[i], "-pw") == 0 && i+1 < argc)
981 password = argv[++i];
982 else if (strcmp(argv[i], "-gui") == 0 && i+1 < argc) {
983 gui_hwnd = argv[++i];
985 } else if (strcmp(argv[i], "-ls") == 0)
987 else if (strcmp(argv[i], "--") == 0)
998 get_dir_list(argc, argv);
1005 targetshouldbedirectory = 1;
1007 if (colon(argv[argc-1]) != NULL)
1008 toremote(argc, argv);
1010 tolocal(argc, argv);
1013 if (connection_open) {
1016 ssh_scp_recv(&ch, 1);
1021 /* GUI Adaptation - August 2000 */
1023 unsigned int msg_id = WM_RET_ERR_CNT;
1024 if (list) msg_id = WM_LS_RET_ERR_CNT;
1025 while (!PostMessage( (HWND)atoi(gui_hwnd), msg_id, (WPARAM)errs, 0/*lParam*/ ) )
1028 return (errs == 0 ? 0 : 1);