source: trunk/third/ssh/scp.c @ 12646

Revision 12646, 34.0 KB checked in by danw, 26 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r12645, which included commits to RCS files with non-trunk default branches.
Line 
1/*
2
3scp - secure remote copy.  This is basically patched BSD rcp which uses ssh
4to do the data transfer (instead of using rcmd).
5
6NOTE: This version should NOT be suid root.  (This uses ssh to do the transfer
7and ssh has the necessary privileges.)
8
91995 Timo Rinne <tri@iki.fi>, Tatu Ylonen <ylo@cs.hut.fi>
10     
11*/
12
13/*
14 * $Id: scp.c,v 1.1.1.3 1999-03-08 17:43:28 danw Exp $
15 * $Log: not supported by cvs2svn $
16 * Revision 1.14  1998/07/08 01:14:25  kivinen
17 *      Changed scp to run ssh1 instead of ssh.
18 *
19 * Revision 1.13  1998/07/08 00:49:40  kivinen
20 *      Added all kind of possible (and impossible) ways to
21 *      disable/enable scp statistics. Added -L option.
22 *
23 * Revision 1.12  1998/06/12 08:04:20  kivinen
24 *      Added disablation of statistics if stdout is not a tty or -B
25 *      option is given.
26 *
27 * Revision 1.11  1998/06/11 00:10:25  kivinen
28 *      Added -q option. Added statistics output.
29 *
30 * Revision 1.10  1998/05/23  20:24:07  kivinen
31 *      Changed () -> (void).
32 *
33 * Revision 1.9  1998/03/30  22:23:06  kivinen
34 *      Changed size variable to be off_t instead of int when reading
35 *      file. This should fix the 2GB file size limit if your system
36 *      supports > 2GB files.
37 *
38 * Revision 1.8  1997/06/04 13:52:52  kivinen
39 *      Moved ssh_options before other options so you can override
40 *      options given by scp.
41 *
42 * Revision 1.7  1997/04/23 00:03:04  kivinen
43 *      Added -S flag
44 *
45 * Revision 1.6  1997/04/17 04:20:06  kivinen
46 * *** empty log message ***
47 *
48 * Revision 1.5  1997/03/26 07:15:57  kivinen
49 *      Use last @ to separate user from host in the user@host@host:
50 *
51 * Revision 1.4  1997/03/19 17:37:37  kivinen
52 *      If configured ssh_program isn't found try if ssh can be found
53 *      in the same directory as scp and if so use that one. Fixed
54 *      typo:  execvp("ss", ...) -> execvp("ssh", ...).
55 *
56 * Revision 1.3  1996/11/24 08:24:36  kivinen
57 *      Added code that will try to run ssh from path if builtin path
58 *      fails.
59 *
60 * Revision 1.2  1996/11/07 18:17:56  kivinen
61 *      Added #ifndef _PATH_CP around #define _PATH_CP.
62 *
63 * Revision 1.1.1.1  1996/02/18 21:38:11  ylo
64 *      Imported ssh-1.2.13.
65 *
66 * Revision 1.9  1995/10/02  01:26:02  ylo
67 *      Fixed code for no HAVE_FCHMOD case.
68 *
69 * Revision 1.8  1995/09/27  02:14:56  ylo
70 *      Added support for SCO.
71 *
72 * Revision 1.7  1995/09/13  12:00:30  ylo
73 *      Don't use -l unless user name is explicitly given (so that
74 *      User works in .ssh/config).
75 *
76 * Revision 1.6  1995/08/18  22:55:53  ylo
77 *      Added utimbuf kludges for NextStep.
78 *      Added "-P port" option.
79 *
80 * Revision 1.5  1995/07/27  00:40:02  ylo
81 *      Include utime.h only if it exists.
82 *      Disable FallBackToRsh when running ssh from scp.
83 *
84 * Revision 1.4  1995/07/13  09:54:37  ylo
85 *      Added Snabb's patches for IRIX 4 (SVR3) (HAVE_ST_BLKSIZE code).
86 *
87 * Revision 1.3  1995/07/13  01:37:41  ylo
88 *      Added cvs log.
89 *
90 * $Endlog$
91 */
92
93/*
94 * Copyright (c) 1983, 1990, 1992, 1993, 1995
95 *      The Regents of the University of California.  All rights reserved.
96 *
97 * Redistribution and use in source and binary forms, with or without
98 * modification, are permitted provided that the following conditions
99 * are met:
100 * 1. Redistributions of source code must retain the above copyright
101 *    notice, this list of conditions and the following disclaimer.
102 * 2. Redistributions in binary form must reproduce the above copyright
103 *    notice, this list of conditions and the following disclaimer in the
104 *    documentation and/or other materials provided with the distribution.
105 * 3. All advertising materials mentioning features or use of this software
106 *    must display the following acknowledgement:
107 *      This product includes software developed by the University of
108 *      California, Berkeley and its contributors.
109 * 4. Neither the name of the University nor the names of its contributors
110 *    may be used to endorse or promote products derived from this software
111 *    without specific prior written permission.
112 *
113 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
114 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
115 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
116 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
117 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
118 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
119 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
120 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
121 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
122 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
123 * SUCH DAMAGE.
124 *
125 *      $Id: scp.c,v 1.1.1.3 1999-03-08 17:43:28 danw Exp $
126 */
127
128#ifndef lint
129char scp_berkeley_copyright[] =
130"@(#) Copyright (c) 1983, 1990, 1992, 1993\n\
131        The Regents of the University of California.  All rights reserved.\n";
132#endif /* not lint */
133
134#include "includes.h"
135#include "ssh.h"
136#include "xmalloc.h"
137#ifdef HAVE_UTIME_H
138#include <utime.h>
139#if defined(_NEXT_SOURCE) && !defined(_POSIX_SOURCE)
140struct utimbuf {
141  time_t actime;
142  time_t modtime;
143};
144#endif /* _NEXT_SOURCE */
145#else
146struct utimbuf
147{
148  long actime;
149  long modtime;
150};
151#endif
152
153#ifndef _PATH_CP
154#define _PATH_CP "cp"
155#endif
156
157#ifndef STDIN_FILENO
158#define STDIN_FILENO 0
159#endif
160#ifndef STDOUT_FILENO
161#define STDOUT_FILENO 1
162#endif
163#ifndef STDERR_FILENO
164#define STDERR_FILENO 2
165#endif
166
167/* This is set to non-zero to enable verbose mode. */
168int verbose = 0;
169
170/* This is set to non-zero to enable statistics mode. */
171#ifdef SCP_STATISTICS_ENABLED
172int statistics = 1;
173#else /* SCP_STATISTICS_ENABLED */
174int statistics = 0;
175#endif /* SCP_STATISTICS_ENABLED */
176
177/* This is set to non-zero to enable printing statistics for each file */
178#ifdef SCP_ALL_STATISTICS_ENABLED
179int all_statistics = 1;
180#else /* SCP_ALL_STATISTICS_ENABLED */
181int all_statistics = 0;
182#endif /* SCP_ALL_STATISTICS_ENABLED */
183
184/* This is set to non-zero if compression is desired. */
185int compress = 0;
186
187/* This is set to non-zero if running in batch mode (that is, password
188   and passphrase queries are not allowed). */
189int batchmode = 0;
190
191/* This is to call ssh with argument -P (for using non-privileged
192   ports to get through some firewalls.) */
193int use_privileged_port = 1;
194
195/* This is set to the cipher type string if given on the command line. */
196char *cipher = NULL;
197
198/* This is set to the RSA authentication identity file name if given on
199   the command line. */
200char *identity = NULL;
201
202/* This is the port to use in contacting the remote site (is non-NULL). */
203char *port = NULL;
204
205char *ssh_program = SSH_PROGRAM;
206
207#ifdef WITH_SCP_STATS
208
209#define SOME_STATS_FILE stderr
210
211#define ssh_max(a,b) (((a) > (b)) ? (a) : (b))
212
213unsigned long statbytes = 0;
214time_t stat_starttime = 0;
215time_t stat_lasttime = 0;
216double ratebs = 0.0;
217
218void stats_fixlen(int bytes);
219char *stat_eta(int secs);
220#endif /* WITH_SCP_STATS */
221
222/* Ssh options */
223char **ssh_options = NULL;
224int ssh_options_cnt = 0;
225int ssh_options_alloc = 0;
226
227/* This function executes the given command as the specified user on the given
228   host.  This returns < 0 if execution fails, and >= 0 otherwise.
229   This assigns the input and output file descriptors on success. */
230
231int do_cmd(char *host, char *remuser, char *cmd, int *fdin, int *fdout)
232{
233  int pin[2], pout[2], reserved[2];
234
235  if (verbose)
236    fprintf(stderr, "Executing: host %s, user %s, command %s\n",
237            host, remuser ? remuser : "(unspecified)", cmd);
238
239  /* Reserve two descriptors so that the real pipes won't get descriptors
240     0 and 1 because that will screw up dup2 below. */
241  pipe(reserved);
242
243  /* Create a socket pair for communicating with ssh. */
244  if (pipe(pin) < 0)
245    fatal("pipe: %s", strerror(errno));
246  if (pipe(pout) < 0)
247    fatal("pipe: %s", strerror(errno));
248
249  /* Free the reserved descriptors. */
250  close(reserved[0]);
251  close(reserved[1]);
252
253  /* For a child to execute the command on the remote host using ssh. */
254  if (fork() == 0)
255    {
256      char *args[256];
257      unsigned int i, j;
258
259      /* Child. */
260      close(pin[1]);
261      close(pout[0]);
262      dup2(pin[0], 0);
263      dup2(pout[1], 1);
264      close(pin[0]);
265      close(pout[1]);
266
267      i = 0;
268      args[i++] = ssh_program;
269      for(j = 0; j < ssh_options_cnt; j++)
270        {
271          args[i++] = "-o";
272          args[i++] = ssh_options[j];
273          if (i > 250)
274            fatal("Too many -o options (total number of arguments is more than 256)");
275        }
276      args[i++] = "-x";
277      args[i++] = "-a";
278      args[i++] = "-oFallBackToRsh no";
279      args[i++] = "-oClearAllForwardings yes";
280      if (verbose)
281        args[i++] = "-v";
282      if (compress)
283        args[i++] = "-C";
284      if (!use_privileged_port)
285        args[i++] = "-P";
286      if (batchmode)
287        args[i++] = "-oBatchMode yes";
288      if (cipher != NULL)
289        {
290          args[i++] = "-c";
291          args[i++] = cipher;
292        }
293      if (identity != NULL)
294        {
295          args[i++] = "-i";
296          args[i++] = identity;
297        }
298      if (port != NULL)
299        {
300          args[i++] = "-p";
301          args[i++] = port;
302        }
303      if (remuser != NULL)
304        {
305          args[i++] = "-l";
306          args[i++] = remuser;
307        }
308      args[i++] = host;
309      args[i++] = cmd;
310      args[i++] = NULL;
311
312      execvp(ssh_program, args);
313      if (errno == ENOENT)
314        {
315          args[0] = "ssh1";
316          execvp("ssh1", args);
317        }
318      perror(ssh_program);
319      exit(1);
320    }
321  /* Parent.  Close the other side, and return the local side. */
322  close(pin[0]);
323  *fdout = pin[1];
324  close(pout[1]);
325  *fdin = pout[0];
326  return 0;
327}
328
329void fatal(const char *fmt, ...)
330{
331  va_list ap;
332  char buf[1024];
333
334  va_start(ap, fmt);
335  vsprintf(buf, fmt, ap);
336  va_end(ap);
337  fprintf(stderr, "%s\n", buf);
338  exit(255);
339}
340
341/* This stuff used to be in BSD rcp extern.h. */
342
343typedef struct {
344        int cnt;
345        char *buf;
346} BUF;
347
348extern int iamremote;
349
350BUF     *allocbuf(BUF *, int, int);
351char    *colon(char *);
352void     lostconn(int);
353void     nospace(void);
354int      okname(char *);
355void     run_err(const char *, ...);
356void     verifydir(char *);
357
358/* Stuff from BSD rcp.c continues. */
359
360struct passwd *pwd;
361uid_t   userid;
362int errs, remin, remout;
363int pflag, iamremote, iamrecursive, targetshouldbedirectory;
364
365#define CMDNEEDS        64
366char cmd[CMDNEEDS];             /* must hold "rcp -r -p -d\0" */
367
368int      response(void);
369void     rsource(char *, struct stat *);
370void     sink(int, char *[]);
371void     source(int, char *[]);
372void     tolocal(int, char *[]);
373void     toremote(char *, int, char *[]);
374void     usage(void);
375
376int
377main(argc, argv)
378        int argc;
379        char *argv[];
380{
381        int ch, fflag, tflag;
382        char *targ;
383        extern char *optarg;
384        extern int optind;
385        struct stat st;
386
387        if (stat(ssh_program, &st) < 0)
388          {
389            int len;
390            len = strlen(argv[0]);
391            if (len >= 3 && strcmp(argv[0] + len - 3, "scp") == 0)
392              {
393                char *p;
394                p = xstrdup(argv[0]);
395                strcpy(p + len - 3, "ssh");
396                if (stat(p, &st) < 0)
397                  xfree(p);
398                else
399                  ssh_program = p;
400              }
401          }
402
403        if (getenv("SSH_SCP_STATS") != NULL)
404          {
405            statistics = 1;
406          }
407        if (getenv("SSH_ALL_SCP_STATS") != NULL)
408          {
409            all_statistics = 1;
410          }
411        if (getenv("SSH_NO_SCP_STATS") != NULL)
412          {
413            statistics = 0;
414          }
415        if (getenv("SSH_NO_ALL_SCP_STATS") != NULL)
416          {
417            all_statistics = 0;
418          }
419       
420        if (!isatty(fileno(stdout)))
421            statistics = 0;
422
423        fflag = tflag = 0;
424        while ((ch = getopt(argc, argv, "aAqQdfprtvBCLc:i:P:o:S:")) != EOF)
425                switch(ch) {                    /* User-visible flags. */
426                case 'S':
427                        ssh_program = optarg;
428                        break;
429                case 'a':
430                        all_statistics = 1;
431                        break;
432                case 'A':
433                        all_statistics = 0;
434                        break;
435                case 'q':
436                        statistics = 0;
437                        break;
438                case 'Q':
439                        statistics = 1;
440                        break;
441                case 'p':
442                        pflag = 1;
443                        break;
444                case 'P':
445                        port = optarg;
446                        break;
447                case 'o':
448                        if (ssh_options_cnt >= ssh_options_alloc)
449                          {
450                            if (ssh_options_alloc == 0)
451                              {
452                                ssh_options_alloc = 10;
453                                ssh_options = xmalloc(sizeof(char *) *
454                                                      ssh_options_alloc);
455                              }
456                            else
457                              {
458                                ssh_options_alloc += 10;
459                                ssh_options = xrealloc(ssh_options,
460                                                       sizeof(char *) *
461                                                       ssh_options_alloc);
462                              }
463                          }
464                        ssh_options[ssh_options_cnt++] = optarg;
465                        break;
466                       
467                case 'r':
468                        iamrecursive = 1;
469                        break;
470                                                /* Server options. */
471                case 'd':
472                        targetshouldbedirectory = 1;
473                        break;
474                case 'f':                       /* "from" */
475                        iamremote = 1;
476                        fflag = 1;
477                        break;
478                case 't':                       /* "to" */
479                        iamremote = 1;
480                        tflag = 1;
481                        break;
482                case 'c':
483                        cipher = optarg;
484                        break;
485                case 'i':
486                        identity = optarg;
487                        break;
488                case 'v':
489                        verbose = 1;
490                        break;
491                case 'B':
492                        batchmode = 1;
493                        statistics = 0;
494                        break;
495               case 'L':
496                        /* -L ("large local ports" or something) means
497                           ssh -P. Since both -p and -P are already used
498                           such a non-intuitive letter had to be used. */
499                        use_privileged_port = 0;
500                        break;
501                case 'C':
502                        compress = 1;
503                        break;
504                case '?':
505                default:
506                        usage();
507                }
508        argc -= optind;
509        argv += optind;
510
511        if ((pwd = getpwuid(userid = getuid())) == NULL)
512                fatal("unknown user %d", (int)userid);
513
514        remin = STDIN_FILENO;
515        remout = STDOUT_FILENO;
516
517        if (fflag) {                    /* Follow "protocol", send data. */
518                (void)response();
519                source(argc, argv);
520                exit(errs != 0);
521        }
522
523        if (tflag) {                    /* Receive data. */
524                sink(argc, argv);
525                exit(errs != 0);
526        }
527
528        if (argc < 2)
529                usage();
530        if (argc > 2)
531                targetshouldbedirectory = 1;
532
533        remin = remout = -1;
534        /* Command to be executed on remote system using "ssh". */
535        (void)sprintf(cmd, "scp%s%s%s%s", verbose ? " -v" : "",
536            iamrecursive ? " -r" : "", pflag ? " -p" : "",
537            targetshouldbedirectory ? " -d" : "");
538
539        (void)signal(SIGPIPE, lostconn);
540
541        if ((targ = colon(argv[argc - 1])))     /* Dest is remote host. */
542          {
543            toremote(targ, argc, argv);
544#ifdef WITH_SCP_STATS
545            if (!iamremote && statistics)
546              {
547                fprintf(SOME_STATS_FILE,"\n");
548              }
549#endif /* WITH_SCP_STATS */
550          }
551        else
552          {
553            tolocal(argc, argv);                /* Dest is local host. */
554#ifdef WITH_SCP_STATS
555            if (!iamremote && statistics)
556              {
557                fprintf(SOME_STATS_FILE,"\n");
558              }
559#endif /* WITH_SCP_STATS */
560            if (targetshouldbedirectory)
561              verifydir(argv[argc - 1]);
562          }
563        exit(errs != 0);
564}
565
566void
567toremote(targ, argc, argv)
568        char *targ, *argv[];
569        int argc;
570{
571        int i, len;
572        char *bp, *host, *src, *suser, *thost, *tuser;
573
574        *targ++ = 0;
575        if (*targ == 0)
576                targ = ".";
577
578        if ((thost = strrchr(argv[argc - 1], '@'))) {
579                /* user@host */
580                *thost++ = 0;
581                tuser = argv[argc - 1];
582                if (*tuser == '\0')
583                        tuser = NULL;
584                else if (!okname(tuser))
585                        exit(1);
586        } else {
587                thost = argv[argc - 1];
588                tuser = NULL;
589        }
590
591        for (i = 0; i < argc - 1; i++) {
592                src = colon(argv[i]);
593                if (src) {                      /* remote to remote */
594                        int j, options_len;
595                        char *options;
596                       
597                        options_len = 0;
598                        for(j = 0; j < ssh_options_cnt; j++)
599                          options_len = 5 + strlen(ssh_options[j]);
600                       
601                        options = xmalloc(options_len + 1);
602                        options[0] = '\0';
603                       
604                        for(j = 0; j < ssh_options_cnt; j++)
605                          {
606                            strcat(options, "-o'");
607                            strcat(options, ssh_options[j]);
608                            strcat(options, "' ");
609                          }
610                        *src++ = 0;
611                        if (*src == 0)
612                                src = ".";
613                        host = strrchr(argv[i], '@');
614                        len = strlen(ssh_program) + strlen(argv[i]) +
615                            strlen(src) + (tuser ? strlen(tuser) : 0) +
616                            strlen(thost) + strlen(targ) + CMDNEEDS + 32 +
617                            options_len;
618                        bp = xmalloc(len);
619                        if (host) {
620                                *host++ = 0;
621                                suser = argv[i];
622                                if (*suser == '\0')
623                                        suser = pwd->pw_name;
624                                else if (!okname(suser))
625                                        continue;
626                                (void)sprintf(bp,
627                                    "%s%s %s -x -o'FallBackToRsh no' -o'ClearAllForwardings yes' -n -l %s %s %s %s '%s%s%s:%s'",
628                                    ssh_program, verbose ? " -v" : "", options,
629                                    suser, host, cmd, src,
630                                    tuser ? tuser : "", tuser ? "@" : "",
631                                    thost, targ);
632                        } else
633                                (void)sprintf(bp,
634                                    "exec %s%s %s -x -o'FallBackToRsh no' -o'ClearAllForwardings yes' -n %s %s %s '%s%s%s:%s'",
635                                    ssh_program, verbose ? " -v" : "", options,
636                                    argv[i], cmd, src,
637                                    tuser ? tuser : "", tuser ? "@" : "",
638                                    thost, targ);
639                        if (verbose)
640                          fprintf(stderr, "Executing: %s\n", bp);
641                        if (system(bp)) errs++;
642                        (void)xfree(bp);
643                } else {                        /* local to remote */
644                        if (remin == -1) {
645                                len = strlen(targ) + CMDNEEDS + 20;
646                                bp = xmalloc(len);
647                                (void)sprintf(bp, "%s -t %s", cmd, targ);
648                                host = thost;
649                                if (do_cmd(host,  tuser,
650                                           bp, &remin, &remout) < 0)
651                                  exit(1);
652                                if (response() < 0)
653                                        exit(1);
654                                (void)xfree(bp);
655                        }
656                        source(1, argv+i);
657                }
658        }
659}
660
661void
662tolocal(argc, argv)
663        int argc;
664        char *argv[];
665{
666        int i, len;
667        char *bp, *host, *src, *suser;
668
669        for (i = 0; i < argc - 1; i++) {
670                if (!(src = colon(argv[i]))) {          /* Local to local. */
671                        len = strlen(_PATH_CP) + strlen(argv[i]) +
672                            strlen(argv[argc - 1]) + 20;
673                        bp = xmalloc(len);
674                        (void)sprintf(bp, "exec %s%s%s %s %s", _PATH_CP,
675                            iamrecursive ? " -r" : "", pflag ? " -p" : "",
676                            argv[i], argv[argc - 1]);
677                        if (verbose)
678                          fprintf(stderr, "Executing: %s\n", bp);
679                        if (system(bp))
680                                ++errs;
681                        (void)xfree(bp);
682                        continue;
683                }
684                *src++ = 0;
685                if (*src == 0)
686                        src = ".";
687                if ((host = strrchr(argv[i], '@')) == NULL) {
688                        host = argv[i];
689                        suser = NULL;
690                } else {
691                        *host++ = 0;
692                        suser = argv[i];
693                        if (*suser == '\0')
694                                suser = pwd->pw_name;
695                        else if (!okname(suser))
696                                continue;
697                }
698                len = strlen(src) + CMDNEEDS + 20;
699                bp = xmalloc(len);
700                (void)sprintf(bp, "%s -f %s", cmd, src);
701                if (do_cmd(host, suser, bp, &remin, &remout) < 0) {
702                  (void)xfree(bp);
703                  ++errs;
704                  continue;
705                }
706                xfree(bp);
707                sink(1, argv + argc - 1);
708                (void)close(remin);
709                remin = remout = -1;
710        }
711}
712
713void
714source(argc, argv)
715        int argc;
716        char *argv[];
717{
718        struct stat stb;
719        static BUF buffer;
720        BUF *bp;
721        off_t i;
722        int amt, fd, haderr, indx, result;
723        char *last, *name, buf[2048];
724
725        for (indx = 0; indx < argc; ++indx) {
726                name = argv[indx];
727                if ((fd = open(name, O_RDONLY, 0)) < 0)
728                        goto syserr;
729                if (fstat(fd, &stb) < 0) {
730syserr:                 run_err("%s: %s", name, strerror(errno));
731                        goto next;
732                }
733                switch (stb.st_mode & S_IFMT) {
734                case S_IFREG:
735                        break;
736                case S_IFDIR:
737                        if (iamrecursive) {
738                                rsource(name, &stb);
739                                goto next;
740                        }
741                        /* FALLTHROUGH */
742                default:
743                        run_err("%s: not a regular file", name);
744                        goto next;
745                }
746                if ((last = strrchr(name, '/')) == NULL)
747                        last = name;
748                else
749                        ++last;
750                if (pflag) {
751                        /*
752                         * Make it compatible with possible future
753                         * versions expecting microseconds.
754                         */
755                        (void)sprintf(buf, "T%lu 0 %lu 0\n",
756                                      (unsigned long)stb.st_mtime,
757                                      (unsigned long)stb.st_atime);
758                        (void)write(remout, buf, strlen(buf));
759                        if (response() < 0)
760                                goto next;
761                }
762#define FILEMODEMASK    (S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO)
763                (void)sprintf(buf, "C%04o %lu %s\n",
764                              (unsigned int)(stb.st_mode & FILEMODEMASK),
765                              (unsigned long)stb.st_size,
766                              last);
767                if (verbose)
768                  {
769                    fprintf(stderr, "Sending file modes: %s", buf);
770                    fflush(stderr);
771                  }
772                (void)write(remout, buf, strlen(buf));
773                if (response() < 0)
774                        goto next;
775                if ((bp = allocbuf(&buffer, fd, 2048)) == NULL) {
776next:                   (void)close(fd);
777                        continue;
778                }
779#ifdef WITH_SCP_STATS
780                if (!iamremote && statistics)
781                  {
782                    statbytes = 0;
783                    ratebs = 0.0;
784                    stat_starttime = time(NULL);
785                  }
786#endif /* WITH_SCP_STATS */
787
788                /* Keep writing after an error so that we stay sync'd up. */
789                for (haderr = i = 0; i < stb.st_size; i += bp->cnt) {
790                        amt = bp->cnt;
791                        if (i + amt > stb.st_size)
792                                amt = stb.st_size - i;
793                        if (!haderr) {
794                                result = read(fd, bp->buf, amt);
795                                if (result != amt)
796                                        haderr = result >= 0 ? EIO : errno;
797                        }
798                        if (haderr)
799                          {
800                            (void)write(remout, bp->buf, amt);
801#ifdef WITH_SCP_STATS
802                            if (!iamremote && statistics)
803                              {
804                                if ((time(NULL) - stat_lasttime) > 0)
805                                  {
806                                    int bwritten;
807                                    bwritten = fprintf(SOME_STATS_FILE,
808                                                       "\r%s : ERROR..continuing to end of file anyway", last);
809                                    stats_fixlen(bwritten);
810                                    fflush(SOME_STATS_FILE);
811                                    stat_lasttime = time(NULL);
812                                  }
813                              }
814#endif /* WITH_SCP_STATS */
815                          }
816                        else {
817                                result = write(remout, bp->buf, amt);
818                                if (result != amt)
819                                        haderr = result >= 0 ? EIO : errno;
820#ifdef WITH_SCP_STATS
821                                if (!iamremote && statistics)
822                                  {
823                                    statbytes += result;
824                                    /* At least one second delay between
825                                       outputs, or if finished */
826                                    if (time(NULL) - stat_lasttime > 0 ||
827                                        (result + i) == stb.st_size)
828                                      {
829                                        int bwritten;
830                                       
831                                        if (time(NULL) == stat_starttime)
832                                          {
833                                            stat_starttime -= 1;
834                                          }
835                                        ratebs = ssh_max(1.0,
836                                                         (double) statbytes /
837                                                         (time(NULL) -
838                                                          stat_starttime));
839                                        bwritten =
840                                          fprintf(SOME_STATS_FILE,
841                                                  "\r%-25.25s | %10ld KB | %5.1f kB/s | ETA: %s | %3d%%",
842                                                  last,
843                                                  statbytes / 1024,
844                                                  ratebs / 1024,
845                                                  stat_eta((int) ((stb.st_size
846                                                                   - statbytes)
847                                                                  / ratebs)),
848                                                  (int) (100.0 *
849                                                         (double) statbytes /
850                                                         stb.st_size));
851                                        if (all_statistics && (result + i) ==
852                                            stb.st_size)
853                                          bwritten += fprintf(SOME_STATS_FILE,
854                                                              "\n");
855                                        stats_fixlen(bwritten);
856                                        stat_lasttime = time(NULL);
857                                      }
858                                  }
859#endif /* WITH_SCP_STATS */
860                        }
861                }
862                if (close(fd) < 0 && !haderr)
863                        haderr = errno;
864                if (!haderr)
865                        (void)write(remout, "", 1);
866                else
867                        run_err("%s: %s", name, strerror(haderr));
868                (void)response();
869        }
870}
871
872void
873rsource(name, statp)
874        char *name;
875        struct stat *statp;
876{
877        DIR *dirp;
878        struct dirent *dp;
879        char *last, *vect[1], path[1100];
880
881        if (!(dirp = opendir(name))) {
882                run_err("%s: %s", name, strerror(errno));
883                return;
884        }
885        last = strrchr(name, '/');
886        if (last == 0)
887                last = name;
888        else
889                last++;
890        if (pflag) {
891                (void)sprintf(path, "T%lu 0 %lu 0\n",
892                              (unsigned long)statp->st_mtime,
893                              (unsigned long)statp->st_atime);
894                (void)write(remout, path, strlen(path));
895                if (response() < 0) {
896                        closedir(dirp);
897                        return;
898                }
899        }
900        (void)sprintf(path,
901            "D%04o %d %.1024s\n", (unsigned int)(statp->st_mode & FILEMODEMASK),
902                      0, last);
903        if (verbose)
904          fprintf(stderr, "Entering directory: %s", path);
905        (void)write(remout, path, strlen(path));
906        if (response() < 0) {
907                closedir(dirp);
908                return;
909        }
910        while ((dp = readdir(dirp))) {
911                if (dp->d_ino == 0)
912                        continue;
913                if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
914                        continue;
915                if (strlen(name) + 1 + strlen(dp->d_name) >= sizeof(path) - 1) {
916                        run_err("%s/%s: name too long", name, dp->d_name);
917                        continue;
918                }
919                (void)sprintf(path, "%s/%s", name, dp->d_name);
920                vect[0] = path;
921                source(1, vect);
922        }
923        (void)closedir(dirp);
924        (void)write(remout, "E\n", 2);
925        (void)response();
926}
927
928void
929sink(argc, argv)
930        int argc;
931        char *argv[];
932{
933        static BUF buffer;
934        struct stat stb;
935        enum { YES, NO, DISPLAYED } wrerr;
936        BUF *bp;
937        off_t i, j, size;
938        int amt, count, exists, first, mask, mode, ofd, omode;
939        int setimes, targisdir, wrerrno = 0;
940        char ch, *cp, *np, *targ, *why, *vect[1], buf[2048];
941        struct utimbuf ut;
942        int dummy_usec;
943#ifdef WITH_SCP_STATS
944        char *statslast;
945#endif /* WITH_SCP_STATS */
946
947#define SCREWUP(str)    { why = str; goto screwup; }
948
949        setimes = targisdir = 0;
950        mask = umask(0);
951        if (!pflag)
952                (void)umask(mask);
953        if (argc != 1) {
954                run_err("ambiguous target");
955                exit(1);
956        }
957        targ = *argv;
958        if (targetshouldbedirectory)
959                verifydir(targ);
960       
961        (void)write(remout, "", 1);
962        if (stat(targ, &stb) == 0 && S_ISDIR(stb.st_mode))
963                targisdir = 1;
964        for (first = 1;; first = 0) {
965                cp = buf;
966                if (read(remin, cp, 1) <= 0)
967                        return;
968                if (*cp++ == '\n')
969                        SCREWUP("unexpected <newline>");
970                do {
971                        if (read(remin, &ch, sizeof(ch)) != sizeof(ch))
972                                SCREWUP("lost connection");
973                        *cp++ = ch;
974                } while (cp < &buf[sizeof(buf) - 1] && ch != '\n');
975                *cp = 0;
976
977                if (buf[0] == '\01' || buf[0] == '\02') {
978                        if (iamremote == 0)
979                                (void)write(STDERR_FILENO,
980                                    buf + 1, strlen(buf + 1));
981                        if (buf[0] == '\02')
982                                exit(1);
983                        ++errs;
984                        continue;
985                }
986                if (buf[0] == 'E') {
987                        (void)write(remout, "", 1);
988                        return;
989                }
990
991                if (ch == '\n')
992                        *--cp = 0;
993
994#define getnum(t) (t) = 0; \
995  while (*cp >= '0' && *cp <= '9') (t) = (t) * 10 + (*cp++ - '0');
996                cp = buf;
997                if (*cp == 'T') {
998                        setimes++;
999                        cp++;
1000                        getnum(ut.modtime);
1001                        if (*cp++ != ' ')
1002                                SCREWUP("mtime.sec not delimited");
1003                        getnum(dummy_usec);
1004                        if (*cp++ != ' ')
1005                                SCREWUP("mtime.usec not delimited");
1006                        getnum(ut.actime);
1007                        if (*cp++ != ' ')
1008                                SCREWUP("atime.sec not delimited");
1009                        getnum(dummy_usec);
1010                        if (*cp++ != '\0')
1011                                SCREWUP("atime.usec not delimited");
1012                        (void)write(remout, "", 1);
1013                        continue;
1014                }
1015                if (*cp != 'C' && *cp != 'D') {
1016                        /*
1017                         * Check for the case "rcp remote:foo\* local:bar".
1018                         * In this case, the line "No match." can be returned
1019                         * by the shell before the rcp command on the remote is
1020                         * executed so the ^Aerror_message convention isn't
1021                         * followed.
1022                         */
1023                        if (first) {
1024                                run_err("%s", cp);
1025                                exit(1);
1026                        }
1027                        SCREWUP("expected control record");
1028                }
1029                mode = 0;
1030                for (++cp; cp < buf + 5; cp++) {
1031                        if (*cp < '0' || *cp > '7')
1032                                SCREWUP("bad mode");
1033                        mode = (mode << 3) | (*cp - '0');
1034                }
1035                if (*cp++ != ' ')
1036                        SCREWUP("mode not delimited");
1037
1038                for (size = 0; *cp >= '0' && *cp <= '9';)
1039                        size = size * 10 + (*cp++ - '0');
1040                if (*cp++ != ' ')
1041                        SCREWUP("size not delimited");
1042                if (targisdir) {
1043                        static char *namebuf;
1044                        static int cursize;
1045                        size_t need;
1046
1047                        need = strlen(targ) + strlen(cp) + 250;
1048                        if (need > cursize)
1049                          namebuf = xmalloc(need);
1050                        (void)sprintf(namebuf, "%s%s%s", targ,
1051                            *targ ? "/" : "", cp);
1052                        np = namebuf;
1053                } else
1054                        np = targ;
1055                exists = stat(np, &stb) == 0;
1056                if (buf[0] == 'D') {
1057                        int mod_flag = pflag;
1058                        if (exists) {
1059                                if (!S_ISDIR(stb.st_mode)) {
1060                                        errno = ENOTDIR;
1061                                        goto bad;
1062                                }
1063                                if (pflag)
1064                                        (void)chmod(np, mode);
1065                        } else {
1066                                /* Handle copying from a read-only directory */
1067                                mod_flag = 1;
1068                                if (mkdir(np, mode | S_IRWXU) < 0)
1069                                        goto bad;
1070                        }
1071                        vect[0] = np;
1072                        sink(1, vect);
1073                        if (setimes) {
1074                                setimes = 0;
1075                                if (utime(np, &ut) < 0)
1076                                    run_err("%s: set times: %s",
1077                                        np, strerror(errno));
1078                        }
1079                        if (mod_flag)
1080                                (void)chmod(np, mode);
1081                        continue;
1082                }
1083                omode = mode;
1084                mode |= S_IWRITE;
1085#ifdef HAVE_FTRUNCATE
1086                /* Don't use O_TRUNC so the file doesn't get corrupted if
1087                   copying on itself. */
1088                ofd = open(np, O_WRONLY|O_CREAT, mode);
1089#else /* HAVE_FTRUNCATE */
1090                ofd = open(np, O_WRONLY|O_CREAT|O_TRUNC, mode);
1091#endif /* HAVE_FTRUNCATE */
1092                if (ofd < 0) {
1093bad:                    run_err("%s: %s", np, strerror(errno));
1094                        continue;
1095                }
1096                (void)write(remout, "", 1);
1097                if ((bp = allocbuf(&buffer, ofd, 4096)) == NULL) {
1098                        (void)close(ofd);
1099                        continue;
1100                }
1101                cp = bp->buf;
1102                wrerr = NO;
1103#ifdef WITH_SCP_STATS
1104                if (!iamremote && statistics)
1105                  {
1106                    statbytes = 0;
1107                    ratebs = 0.0;
1108                    stat_starttime = time(NULL);
1109
1110                    if ((statslast = strrchr(np, '/')) == NULL)
1111                      statslast = np;
1112                    else
1113                      ++statslast;
1114                  }
1115#endif /* WITH_SCP_STATS */
1116                for (count = i = 0; i < size; i += 4096) {
1117                        amt = 4096;
1118                        if (i + amt > size)
1119                                amt = size - i;
1120                        count += amt;
1121                        do {
1122                                j = read(remin, cp, amt);
1123                                if (j <= 0) {
1124                                        run_err("%s", j ? strerror(errno) :
1125                                            "dropped connection");
1126                                        exit(1);
1127                                }
1128#ifdef WITH_SCP_STATS
1129                                if (!iamremote && statistics)
1130                                  {
1131                                    int bwritten;
1132                                    statbytes += j;
1133                                    if (time(NULL) - stat_lasttime > 0 ||
1134                                        (j + i) == size) {
1135                                      if (time(NULL) == stat_starttime)
1136                                        {
1137                                          stat_starttime -= 1;
1138                                        }
1139                                      ratebs = ssh_max(1.0,
1140                                                       (double)
1141                                                       statbytes /
1142                                                       (time(NULL) -
1143                                                        stat_starttime));
1144                                      bwritten =
1145                                        fprintf(SOME_STATS_FILE,
1146                                                "\r%-25.25s | %10ld KB | %5.1f kB/s | ETA: %s | %3d%%",
1147                                                statslast,
1148                                                statbytes / 1024,
1149                                                ratebs / 1024,
1150                                                stat_eta((int)
1151                                                         ((size - statbytes)
1152                                                          / ratebs)),
1153                                                100 * statbytes / size);
1154                                      if (all_statistics && (i + j) == size)
1155                                        bwritten += fprintf(SOME_STATS_FILE, "\n");
1156                                      stats_fixlen(bwritten);
1157                                      stat_lasttime = time(NULL);
1158                                    }
1159                                  }
1160#endif /* WITH_SCP_STATS */
1161                                amt -= j;
1162                                cp += j;
1163                        } while (amt > 0);
1164                        if (count == bp->cnt) {
1165                                /* Keep reading so we stay sync'd up. */
1166                                if (wrerr == NO) {
1167                                        j = write(ofd, bp->buf, count);
1168                                        if (j != count) {
1169                                                wrerr = YES;
1170                                                wrerrno = j >= 0 ? EIO : errno;
1171                                        }
1172                                }
1173                                count = 0;
1174                                cp = bp->buf;
1175                        }
1176                }
1177                if (count != 0 && wrerr == NO &&
1178                    (j = write(ofd, bp->buf, count)) != count) {
1179                        wrerr = YES;
1180                        wrerrno = j >= 0 ? EIO : errno;
1181                }
1182#ifdef HAVE_FTRUNCATE
1183                if (ftruncate(ofd, size)) {
1184                        run_err("%s: truncate: %s", np, strerror(errno));
1185                        wrerr = DISPLAYED;
1186                }
1187#endif /* HAVE_FTRUNCATE */
1188                if (pflag) {
1189                        if (exists || omode != mode)
1190#ifdef HAVE_FCHMOD
1191                                if (fchmod(ofd, omode))
1192#else /* HAVE_FCHMOD */
1193                                if (chmod(np, omode))
1194#endif /* HAVE_FCHMOD */
1195                                        run_err("%s: set mode: %s",
1196                                            np, strerror(errno));
1197                } else {
1198                        if (!exists && omode != mode)
1199#ifdef HAVE_FCHMOD
1200                                if (fchmod(ofd, omode & ~mask))
1201#else /* HAVE_FCHMOD */
1202                                if (chmod(np, omode & ~mask))
1203#endif /* HAVE_FCHMOD */
1204                                        run_err("%s: set mode: %s",
1205                                            np, strerror(errno));
1206                }
1207                (void)close(ofd);
1208                (void)response();
1209                if (setimes && wrerr == NO) {
1210                        setimes = 0;
1211                        if (utime(np, &ut) < 0) {
1212                                run_err("%s: set times: %s",
1213                                    np, strerror(errno));
1214                                wrerr = DISPLAYED;
1215                        }
1216                }
1217                switch(wrerr) {
1218                case YES:
1219                        run_err("%s: %s", np, strerror(wrerrno));
1220                        break;
1221                case NO:
1222                        (void)write(remout, "", 1);
1223                        break;
1224                case DISPLAYED:
1225                        break;
1226                }
1227        }
1228screwup:
1229        run_err("protocol error: %s", why);
1230        exit(1);
1231}
1232
1233int
1234response(void)
1235{
1236        char ch, *cp, resp, rbuf[2048];
1237
1238        if (read(remin, &resp, sizeof(resp)) != sizeof(resp))
1239                lostconn(0);
1240
1241        cp = rbuf;
1242        switch(resp) {
1243        case 0:                         /* ok */
1244                return (0);
1245        default:
1246                *cp++ = resp;
1247                /* FALLTHROUGH */
1248        case 1:                         /* error, followed by error msg */
1249        case 2:                         /* fatal error, "" */
1250                do {
1251                        if (read(remin, &ch, sizeof(ch)) != sizeof(ch))
1252                                lostconn(0);
1253                        *cp++ = ch;
1254                } while (cp < &rbuf[sizeof(rbuf) - 1] && ch != '\n');
1255
1256                if (!iamremote)
1257                        (void)write(STDERR_FILENO, rbuf, cp - rbuf);
1258                ++errs;
1259                if (resp == 1)
1260                        return (-1);
1261                exit(1);
1262        }
1263        /* NOTREACHED */
1264}
1265
1266void
1267usage(void)
1268{
1269        (void)fprintf(stderr,
1270            "usage: scp [-qQaAprvBCL] [-S path-to-ssh] [-o ssh-options] [-P port] [-c cipher] [-i identity] f1 f2; or: scp [options] f1 ... fn directory\n");
1271        exit(1);
1272}
1273
1274void
1275run_err(const char *fmt, ...)
1276{
1277        static FILE *fp;
1278        va_list ap;
1279        va_start(ap, fmt);
1280
1281        ++errs;
1282        if (fp == NULL && !(fp = fdopen(remout, "w")))
1283                return;
1284        (void)fprintf(fp, "%c", 0x01);
1285        (void)fprintf(fp, "scp: ");
1286        (void)vfprintf(fp, fmt, ap);
1287        (void)fprintf(fp, "\n");
1288        (void)fflush(fp);
1289
1290        if (!iamremote)
1291          {
1292            vfprintf(stderr, fmt, ap);
1293            fprintf(stderr, "\n");
1294          }
1295
1296        va_end(ap);
1297}
1298
1299/* Stuff below is from BSD rcp util.c. */
1300
1301/*-
1302 * Copyright (c) 1992, 1993
1303 *      The Regents of the University of California.  All rights reserved.
1304 *
1305 * Redistribution and use in source and binary forms, with or without
1306 * modification, are permitted provided that the following conditions
1307 * are met:
1308 * 1. Redistributions of source code must retain the above copyright
1309 *    notice, this list of conditions and the following disclaimer.
1310 * 2. Redistributions in binary form must reproduce the above copyright
1311 *    notice, this list of conditions and the following disclaimer in the
1312 *    documentation and/or other materials provided with the distribution.
1313 * 3. All advertising materials mentioning features or use of this software
1314 *    must display the following acknowledgement:
1315 *      This product includes software developed by the University of
1316 *      California, Berkeley and its contributors.
1317 * 4. Neither the name of the University nor the names of its contributors
1318 *    may be used to endorse or promote products derived from this software
1319 *    without specific prior written permission.
1320 *
1321 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
1322 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1323 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1324 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
1325 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
1326 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
1327 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
1328 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
1329 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
1330 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
1331 * SUCH DAMAGE.
1332 *
1333 *      $Id: scp.c,v 1.1.1.3 1999-03-08 17:43:28 danw Exp $
1334 */
1335
1336char *
1337colon(cp)
1338        char *cp;
1339{
1340        if (*cp == ':')         /* Leading colon is part of file name. */
1341                return (0);
1342
1343        for (; *cp; ++cp) {
1344                if (*cp == ':')
1345                        return (cp);
1346                if (*cp == '/')
1347                        return (0);
1348        }
1349        return (0);
1350}
1351
1352void
1353verifydir(cp)
1354        char *cp;
1355{
1356        struct stat stb;
1357
1358        if (!stat(cp, &stb)) {
1359                if (S_ISDIR(stb.st_mode))
1360                        return;
1361                errno = ENOTDIR;
1362        }
1363        run_err("%s: %s", cp, strerror(errno));
1364        exit(1);
1365}
1366
1367int
1368okname(cp0)
1369        char *cp0;
1370{
1371        int c;
1372        char *cp;
1373
1374        cp = cp0;
1375        do {
1376                c = *cp;
1377                if (c & 0200)
1378                        goto bad;
1379                if (!isalpha(c) && !isdigit(c) && c != '_' && c != '-' &&
1380                    c != '@' && c != '%' && c != '.' && c != '/')
1381                        goto bad;
1382        } while (*++cp);
1383        return (1);
1384
1385bad:    fprintf(stderr, "%s: invalid user name", cp0);
1386        return (0);
1387}
1388
1389BUF *
1390allocbuf(bp, fd, blksize)
1391        BUF *bp;
1392        int fd, blksize;
1393{
1394        size_t size;
1395#ifdef HAVE_ST_BLKSIZE
1396        struct stat stb;
1397
1398        if (fstat(fd, &stb) < 0) {
1399                run_err("fstat: %s", strerror(errno));
1400                return (0);
1401        }
1402        if (stb.st_blksize == 0)
1403          size = blksize;
1404        else
1405          size = blksize + (stb.st_blksize - blksize % stb.st_blksize) %
1406          stb.st_blksize;
1407#else /* HAVE_ST_BLKSIZE */
1408        size = blksize;
1409#endif /* HAVE_ST_BLKSIZE */
1410        if (bp->cnt >= size)
1411                return (bp);
1412        if (bp->buf == NULL)
1413          bp->buf = xmalloc(size);
1414        else
1415          bp->buf = xrealloc(bp->buf, size);
1416        bp->cnt = size;
1417        return (bp);
1418}
1419
1420void
1421lostconn(signo)
1422        int signo;
1423{
1424        if (!iamremote)
1425                fprintf(stderr, "lost connection\n");
1426        exit(1);
1427}
1428
1429#ifdef WITH_SCP_STATS
1430void stats_fixlen(int bwritten)
1431{
1432  char rest[80];
1433  int i = 0;
1434 
1435  while (bwritten++ < 77)
1436    {
1437      rest[i++]=' ';
1438    }
1439  rest[i]='\0';
1440  fputs(rest, SOME_STATS_FILE);
1441  fflush(SOME_STATS_FILE);
1442}
1443
1444char *stat_eta(int secs)
1445{
1446  static char stat_result[9];
1447  int hours, mins;
1448
1449   hours = secs / 3600;
1450   secs %= 3600;
1451   mins = secs / 60;
1452   secs %= 60;
1453
1454   sprintf(stat_result, "%02d:%02d:%02d", hours, mins, secs);
1455   return(stat_result);
1456}
1457#endif /* WITH_SCP_STATS */
Note: See TracBrowser for help on using the repository browser.