rcp.c revision 9760e1c48d45ca95f530c947bf07c62061b86c18
/*
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Copyright (c) 1983 The Regents of the University of California.
* All rights reserved.
*
* Redistribution and use in source and binary forms are permitted
* provided that the above copyright notice and this paragraph are
* duplicated in all such forms and that any documentation,
* advertising materials, and other materials related to such
* distribution and use acknowledge that the software was developed
* by the University of California, Berkeley. The name of the
* University may not be used to endorse or promote products derived
* from this software without specific prior written permission.
*
*/
#define _FILE_OFFSET_BITS 64
/*
* rcp
*/
#include <dirent.h>
#include <signal.h>
#include <pwd.h>
#include <netdb.h>
#include <wchar.h>
#include <stdlib.h>
#include <errno.h>
#include <locale.h>
#include <strings.h>
#include <stdio.h>
#include <ctype.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
#include <priv_utils.h>
#include <sys/sendfile.h>
#include <sys/sysmacros.h>
#include <aclutils.h>
/*
* It seems like Berkeley got these from pathnames.h?
*/
#define _PATH_BSHELL "/usr/bin/sh"
#define ACL_FAIL 1
#define ACL_OK 0
/* see PSARC/1993/004/opinion */
typedef struct _buf {
int cnt;
char *buf;
} BUF;
static char *cmd_sunw;
static int errs;
static int pflag;
static int rem;
static int zflag;
static int iamremote;
static int iamrecursive;
static int targetshouldbedirectory;
static int aclflag;
static int acl_aclflag;
static int retval = 0;
static int portnumber = 0;
static void lostconn(void);
static char *search_char(unsigned char *, unsigned char);
static char *removebrackets(char *);
static char *colon(char *);
static int response(void);
static void usage(void);
static void source(int, char **);
static void sink(int, char **);
static void toremote(char *, int, char **);
static void tolocal(int, char **);
static void verifydir(char *);
static int okname(char *);
static int susystem(char *);
static int sendacl(int);
static int recvacl(int, int, int);
static int zwrite(int, char *, int);
static void zopen(int, int);
static int zclose(int);
static int notzero(char *, int);
/*
* As a 32 bit application, we can only transfer (2gb - 1) i.e 0x7FFFFFFF
* bytes of data. We would like the size to be aligned to the nearest
* MAXBOFFSET (8192) boundary for optimal performance.
*/
#define SENDFILE_SIZE 0x7FFFE000
#include <k5-int.h>
#include <profile/prof_int.h>
#include <com_err.h>
#include <kcmd.h>
static int sock;
static char *krb_config = NULL;
/* needs to be > largest read size */
/* needs to be > largest write size */
static krb5_context bsd_context;
static krb5_auth_context auth_context;
static krb5_flags authopts;
static krb5_error_code status;
static void try_normal_rcp(int, char **);
static int init_service(int);
static char **save_argv(int, char **);
static void answer_auth(char *, char *);
static int desrcpwrite(int, char *, int);
static int desrcpread(int, char *, int);
/*
* Not sure why these two don't have their own header file declarations, but
* lint complains about absent declarations so place some here. Sigh.
*/
static int krb5auth_flag = 0; /* Flag set, when KERBEROS is enabled */
static int encrypt_flag = 0; /* Flag set, when encryption is enabled */
static int encrypt_done = 0; /* Flag set, if "-x" is specified */
/* Flag set, if -PN / -PO is specified */
static profile_options_boolean option[] = {
{ "encrypt", &encrypt_flag, 0 },
};
static profile_option_strings rcmdversion[] = {
{ "rcmd_protocol", &rcmdproto, 0 },
};
static char **prev_argv;
static int prev_argc;
int
{
char *targ;
aclflag = 1;
return (1);
}
switch (ch) {
case 'd':
break;
case 'f': /* "from" */
fflag = 1;
if (aclflag | acl_aclflag)
/* ok response */
break;
++pflag;
break;
case 'r':
++iamrecursive;
break;
case 't': /* "to" */
tflag = 1;
break;
case 'Z':
acl_aclflag++;
break;
case 'x':
if (!krb5_privacy_allowed()) {
"Encryption not supported.\n"));
return (1);
}
encrypt_flag++;
encrypt_done++;
break;
case 'k':
" Cannot malloc.\n"));
return (1);
}
break;
case 'P':
if (rcmdoption_done == B_TRUE) {
"Only one of -PN and -PO "
"allowed.\n"));
usage();
}
if (rcmdoption_done == B_TRUE) {
"Only one of -PN and -PO "
"allowed.\n"));
usage();
}
} else {
usage();
}
break;
case 'a':
break;
#ifdef DEBUG
case 'D':
break;
#endif /* DEBUG */
case '?':
default:
usage();
}
}
if (krb5auth_flag > 0) {
if (status) {
gettext("while initializing krb5"));
return (1);
}
/*
* Set up buffers for desread and deswrite.
*/
}
if (encrypt_flag > 0)
if (fflag) {
iamremote = 1;
(void) response();
return (errs);
}
if (tflag) {
iamremote = 1;
return (errs);
}
if (argc < 2)
usage();
/* This will make "rcmd_af()" magically get the proper privilege */
exit(1);
}
if (krb5auth_flag > 0) {
/*
* Get our local realm to look up local realm options.
*/
if (status) {
gettext("while getting default realm"));
return (1);
}
/*
* See if encryption should be done for this realm
*/
option);
/*
* Check the appdefaults section
*/
option);
if ((encrypt_done > 0) || (encrypt_flag > 0)) {
if (krb5_privacy_allowed() == TRUE) {
encrypt_flag++;
} else {
" not supported.\n"));
return (1);
}
}
} else {
"KCMD protocol (%s)"), rcmdproto);
return (1);
}
}
}
if (argc > 2)
rem = -1;
if (portnumber == 0) {
if (krb5auth_flag > 0) {
if (!retval) {
/*
* Connecting to the kshell service failed,
* fallback to normal rcp & reset KRB5 flags.
*/
krb5auth_flag = encrypt_flag = 0;
encrypt_done = 0;
(void) init_service(krb5auth_flag);
}
}
else
(void) init_service(krb5auth_flag);
}
#ifdef DEBUG
if (retval || krb5auth_flag) {
"port %d in use "), portnumber);
if (kcmd_proto == KCMD_OLD_PROTOCOL)
else
} else {
"in use.\n"), portnumber);
}
#endif /* DEBUG */
if (krb5auth_flag > 0) {
/*
* We calculate here a buffer size that can be used in the
* allocation of the three buffers cmd, cmd_orig and
* cmd_sunw_orig that are used to hold different incantations
* of rcp.
*/
sizeof (RCP_ACL " -r -p -z -d"));
"malloc.\n"));
return (1);
}
/*
* We would use cmd-orig as the 'cmd-buffer' if kerberized
* rcp fails, in which case we fallback to normal rcp. We also
* save argc & argv for the same purpose
*/
} else {
cmdsiz = sizeof ("rcp -r -p -z -d");
"malloc.\n"));
return (1);
}
}
return (1);
}
else {
}
}
static void
{
int i;
char resp;
char bp[RCP_BUFSIZE];
*targ++ = 0;
if (*targ == 0)
targ = ".";
*thost++ = 0;
if (*tuser == '\0')
exit(1);
} else {
}
for (i = 0; i < argc - 1; i++) {
if (src) { /* remote to remote */
*src++ = 0;
if (*src == 0)
src = ".";
if (host) {
*host++ = 0;
if (*suser == '\0') {
errs++;
continue;
}
"%s %s -l %s -n %s %s '%s%s%s:%s'",
} else {
"%s %s -n %s %s '%s%s%s:%s'",
}
errs++;
} else { /* local to remote */
if (rem == -1) {
if (krb5auth_flag > 0) {
bp,
0,
"host",
&cred,
0, /* No seq # */
0, /* No server seq # */
0, /* Not any port # */
&kcmd_proto);
if (status) {
/*
* If new protocol requested, we dont
* fallback to less secure ones.
*/
if (kcmd_proto == KCMD_NEW_PROTOCOL) {
gettext("rcp: kcmdv2 "
"to host %s failed - %s"
"\nFallback to normal "
"rcp denied."), host,
exit(1);
}
if (status != -1) {
gettext("rcp: kcmd to host "
"%s failed - %s,\n"
"trying normal rcp...\n\n"),
} else {
gettext("trying normal"
" rcp...\n"));
}
/*
* kcmd() failed, so we have to
* fallback to normal rcp
*/
} else {
if (kcmd_proto == KCMD_NEW_PROTOCOL) {
/* CSTYLED */
if (status) {
"determining "
"subkey for "
"session");
exit(1);
}
if (!session_key) {
com_err("rcp", 0,
"no subkey "
"negotiated for"
" connection");
exit(1);
}
}
&eblock);
if (encrypt_flag > 0) {
char *s = gettext("This rcp "
"session is using "
"encryption for all "
"data transmissions."
"\r\n");
}
}
if (response() < 0)
exit(1);
} else {
/*
* ACL support: try to find out if the remote
* site is running acl cognizant version of
* rcp. A special binary name is used for this
* purpose.
*/
aclflag = 1;
acl_aclflag = 1;
/*
* First see if the remote side will support
* both aclent_t and ace_t acl's?
*/
if (rem < 0)
exit(1);
/*
* This is similar to routine response().
* If response is not ok, treat the other
* side as non-acl rcp.
*/
!= sizeof (resp))
lostconn();
if (resp != 0) {
acl_aclflag = 0;
if (rem < 0)
exit(1);
!= sizeof (resp))
lostconn();
if (resp != 0) {
/*
* Not OK:
* The other side is running
* non-acl rcp. Try again with
* normal stuff
*/
aclflag = 0;
AF_INET6);
if (rem < 0)
exit(1);
if (response() < 0)
exit(1);
}
}
/* everything should be fine now */
}
}
}
}
}
static void
{
int i;
char resp;
char bp[RCP_BUFSIZE];
for (i = 0; i < argc - 1; i++) {
errs++;
continue;
}
*src++ = 0;
if (*src == 0)
src = ".";
if (host) {
*host++ = 0;
if (*suser == '\0') {
errs++;
continue;
}
} else {
}
if (krb5auth_flag > 0) {
bp,
0, /* &rfd2 */
"host",
&cred,
0, /* No seq # */
0, /* No server seq # */
1, /* Not any port # */
&kcmd_proto);
if (status) {
/*
* If new protocol requested, we dont
* fallback to less secure ones.
*/
if (kcmd_proto == KCMD_NEW_PROTOCOL) {
"to host %s failed - %s\n"
"Fallback to normal rcp denied."),
exit(1);
}
if (status != -1) {
"to host %s failed - %s,\n"
"trying normal rcp...\n\n"),
} else {
gettext("trying normal rcp...\n"));
}
/*
* kcmd() failed, so we have to
* fallback to normal rcp
*/
} else {
if (kcmd_proto == KCMD_NEW_PROTOCOL) {
&session_key);
if (status) {
"subkey for session");
exit(1);
}
if (!session_key) {
" for connection");
exit(1);
}
}
&eblock);
if (encrypt_flag > 0) {
char *s = gettext("This rcp "
"session is using DES "
"encryption for all "
"data transmissions."
"\r\n");
}
}
}
else
{
/*
* ACL support: try to find out if the remote site is
* running acl cognizant version of rcp.
*/
aclflag = 1;
acl_aclflag = 1;
if (rem < 0) {
++errs;
continue;
}
/*
* The remote system is supposed to send an ok response.
* If there are any data other than "ok", it must be error
* messages from the remote system. We can assume the
* remote system is running non-acl version rcp.
*/
lostconn();
if (resp != 0) {
/*
* Try again without ace_acl support
*/
acl_aclflag = 0;
if (rem < 0) {
++errs;
continue;
}
lostconn();
/*
* NOT ok:
* The other side is running non-acl rcp.
* Try again with normal stuff
*/
aclflag = 0;
if (rem < 0) {
++errs;
continue;
}
}
}
rem = -1;
}
}
static void
{
return;
}
exit(1);
}
static char *
{
if (*cp == '[')
else if (*cp == ']')
return (cp);
else if (*cp == '/')
return (0);
}
return (0);
}
static int
{
register int c;
do {
c = *cp;
if (c & 0200)
goto bad;
goto bad;
} while (*++cp);
return (1);
bad:
return (0);
}
static char *
removebrackets(char *str)
{
}
return (newstr);
}
static int
susystem(char *s)
{
int pfds[2];
int cnt;
/*
* Due to the fact that rcp uses rsh to copy between 2 remote
* machines, rsh doesn't return the exit status of the remote
* command, and we can't modify the rcmd protocol used by rsh
* (for interoperability reasons) we use the hack of using any
* output on stderr as indication that an error occurred and
* that we should return a non-zero error code.
*/
return (-1);
}
return (-1);
} else if (pid == 0) {
/*
* Child.
*/
/*
* Send stderr messages down the pipe so that we can detect
* them in the parent process.
*/
}
/*
* This shell does not inherit the additional privilege
* we have in our Permitted set.
*/
_exit(127);
}
/*
* Parent.
*/
/*
* If any data is read from the pipe the child process
* has output something on stderr so we set the boolean
* 'seen_stderr_traffic' to true, which will cause the
* function to return -1.
*/
}
;
if (w == -1)
status = -1;
}
static void
{
for (x = 0; x < argc; x++) {
continue;
}
goto notreg;
case S_IFREG:
break;
case S_IFDIR:
if (iamrecursive) {
(void) close(f);
continue;
}
/* FALLTHROUGH */
default:
(void) close(f);
continue;
}
if (last == 0)
else
last++;
if (pflag) {
/*
* Make it compatible with possible future
* versions expecting microseconds.
*/
if (mtime < 0) {
error("negative modification time on "
"%s; not preserving\n", name);
}
if (atime < 0) {
error("negative access time on "
"%s; not preserving\n", name);
}
}
if (response() < 0) {
(void) close(f);
continue;
}
}
last);
if (response() < 0) {
(void) close(f);
continue;
}
/* ACL support: send */
if (aclflag | acl_aclflag) {
/* get acl from f and send it over */
(void) close(f);
continue;
}
}
(void) close(f);
continue;
}
readerr = 0;
if (readerr == 0 &&
}
(void) close(f);
if (readerr == 0)
else
} else {
while (size != 0) {
if (cnt == -1) {
continue;
} else {
break;
}
}
}
if (cnt == -1) {
} else {
}
(void) close(f);
}
(void) response();
}
}
static void
{
DIR *d;
char path[MAXPATHLEN];
return;
}
if (last == 0)
else
last++;
if (pflag) {
if (response() < 0) {
(void) closedir(d);
return;
}
}
/* acl support for directory */
if (aclflag) {
/* get acl from f and send it over */
(void) closedir(d);
return;
}
}
if (response() < 0) {
(void) closedir(d);
return;
}
continue;
continue;
MAXPATHLEN - 1) {
continue;
}
}
(void) closedir(d);
(void) response();
}
static int
response(void)
{
register char *cp;
lostconn();
switch (resp) {
case 0: /* ok */
return (0);
default:
/* FALLTHROUGH */
case 1: /* error, followed by err msg */
case 2: /* fatal error, "" */
do {
lostconn();
if (!iamremote)
++errs;
if (resp == 1)
return (-1);
exit(1);
}
/*NOTREACHED*/
}
static void
lostconn(void)
{
if (!iamremote)
exit(1);
}
static void
{
char *cp;
off_t i, j;
size_t namebuf_sz = 0;
if (!pflag)
if (argc != 1) {
error("rcp: ambiguous target\n");
exit(1);
}
targisdir = 1;
return;
}
if (*cp++ == '\n')
SCREWUP("unexpected <newline>");
do {
SCREWUP("lost connection");
*cp = 0;
if (iamremote == 0)
if (buf[0] == '\02')
exit(1);
errs++;
continue;
}
if (buf[0] == 'E') {
return;
}
if (ch == '\n')
*--cp = 0;
if (*cp == 'T') {
setimes++;
cp++;
if (*cp++ != ' ')
if (*cp++ != ' ')
SCREWUP("mtime.usec not delimited");
if (*cp++ != ' ')
if (*cp++ != '\0')
SCREWUP("atime.usec not delimited");
continue;
}
/*
* Check for the case "rcp remote:foo\* local:bar".
* In this case, the line "No match." can be returned
* by the shell before the rcp command on the remote is
* executed so the ^Aerror_message convention isn't
* followed.
*/
if (first) {
exit(1);
}
SCREWUP("expected control record");
}
mode = 0;
SCREWUP("bad mode");
}
if (*cp++ != ' ')
SCREWUP("mode not delimited");
size = 0;
if (*cp++ != ' ')
SCREWUP("size not delimited");
if (targisdir) {
if (need > namebuf_sz) {
error("rcp: out of memory\n");
exit(1);
}
namebuf_sz = need;
}
} else {
}
if (buf[0] == 'D') {
if (exists) {
if (aclflag | acl_aclflag) {
/*
* consume acl in the pipe
* fd = -1 to indicate the
* special case
*/
== ACL_FAIL) {
goto bad;
}
}
goto bad;
}
if (pflag)
if (aclflag) {
/* consume acl in the pipe */
}
goto bad;
}
/* acl support for directories */
if (aclflag | acl_aclflag) {
int dfd;
goto bad;
/* get acl and set it to ofd */
if (!exists)
goto bad;
}
}
if (setimes) {
setimes = 0;
error("rcp: can't set times on %s: %s\n",
}
continue;
}
bad:
continue;
}
/*
* If the output file exists we have to force zflag off
* to avoid erroneously seeking past old data.
*/
/*
* ACL support: receiving
*/
if (aclflag | acl_aclflag) {
/* get acl and set it to ofd */
if (!exists)
continue;
}
}
continue;
}
count = 0;
wrerr = 0;
for (i = 0; i < size; i += RCP_BUFSIZE) {
amt = RCP_BUFSIZE;
do {
if (j <= 0) {
/*
* Connection to supplier lost.
* Truncate file to correspond
* to amount already transferred.
*
* Note that we must call ftruncate()
* before any call to error() (which
* might result in a SIGPIPE and
* sudden death before we have a chance
* to correct the file's size).
*/
#define TRUNCERR "rcp: can't truncate %s: %s\n"
error("rcp: %s\n",
"dropped connection");
exit(1);
}
amt -= j;
cp += j;
} while (amt > 0);
if (wrerr == 0 &&
wrerr++;
count = 0;
}
}
wrerr++;
wrerr++;
}
(void) response();
if (setimes) {
setimes = 0;
error("rcp: can't set times on %s: %s\n",
}
if (wrerr)
else
}
exit(1);
}
#ifndef roundup
#define roundup(x, y) ((((x)+((y)-1))/(y))*(y))
#endif /* !roundup */
static BUF *
{
int size;
return (0);
}
if (size == 0)
error("rcp: malloc: out of memory\n");
return (0);
}
}
return (bp);
}
static void
usage(void)
{
gettext("\trcp [-p] [-a] [-x] [-k realm] [-PN / -PO] "
#ifdef DEBUG
"[-D port] "
#endif /* DEBUG */
"f1 f2; or:\n"),
gettext("\trcp [-r] [-p] [-a] [-x] "
#ifdef DEBUG
"[-D port] "
#endif /* DEBUG */
"[-k realm] [-PN / -PO] f1...fn d2\n"));
exit(1);
}
/*
* sparse file support
*/
/* is it ok to try to create holes? */
static void
{
zbsize = 0;
zlastseek = 0;
if (flag &&
}
static int
{
do {
do {
return (-1);
buf += n;
} while ((count -= n) > 0);
zlastseek = 0;
} else {
return (-1);
zlastseek = 1;
}
} while (nbytes > 0);
return (0);
}
/* write last byte of file if necessary */
static int
{
zbsize = 0;
return (-1);
else
return (0);
}
/* return true if buffer is not all zeros */
static int
notzero(char *p, int n)
{
register int result = 0;
while ((int)p & 3 && --n >= 0)
result |= *p++;
while ((n -= 4 * sizeof (int)) >= 0) {
/* LINTED */
result |= ((int *)p)[0];
/* LINTED */
result |= ((int *)p)[1];
/* LINTED */
result |= ((int *)p)[2];
/* LINTED */
result |= ((int *)p)[3];
if (result)
return (result);
p += 4 * sizeof (int);
}
n += 4 * sizeof (int);
while (--n >= 0)
result |= *p++;
return (result);
}
/*
* New functions to support ACLs
*/
/*
* Get acl from f and send it over.
* ACL record includes acl entry count, acl text length, and acl text.
*/
static int
sendacl(int f)
{
int aclcnt;
char *acltext;
char acltype;
int aclerror;
int trivial;
if (aclerror != 0) {
return (ACL_FAIL);
}
/*
* if acl type is not ACLENT_T and were operating in acl_aclflag == 0
* then don't do the malloc and facl(fd, getcntcmd,...);
* since the remote side doesn't support alternate style ACL's.
*/
acltype = 'A';
} else {
if (aclp) {
} else {
acltype = 'A';
}
}
/* send the acl count over */
/*
* only send acl when we have an aclp, which would
* imply its not trivial.
*/
error("rcp: failed to convert to text\n");
return (ACL_FAIL);
}
/* send ACLs over: send the length first */
if (response() < 0) {
return (ACL_FAIL);
}
}
if (aclp)
return (ACL_OK);
}
/*
* Use this routine to get acl entry count and acl text size (in bytes)
*/
static int
{
char *cp;
char ch;
/* get acl count */
return (ACL_FAIL);
switch (*cp++) {
case 'A':
*acltype = 0;
break;
case 'Z':
*acltype = 1;
break;
default:
return (ACL_FAIL);
}
do {
error("rcp: lost connection ..\n");
return (ACL_FAIL);
}
if (ch != '\n') {
error("rcp: ACL record corrupted \n");
return (ACL_FAIL);
}
if (*cp != '\n') {
error("rcp: ACL record corrupted \n");
return (ACL_FAIL);
}
return (ACL_OK);
}
/*
* Receive acl from the pipe and set it to f
*/
static int
{
int aclcnt; /* acl entry count */
int aclsize; /* acl text length */
int j;
char *tp;
char *acltext; /* external format */
int acltype;
int min_entries;
int aclerror;
/* get acl count */
return (ACL_FAIL);
if (acltype == 0) {
} else {
min_entries = 1;
}
if (aclcnt > min_entries) {
/* get acl text size */
return (ACL_FAIL);
return (ACL_FAIL);
}
do {
if (j <= 0) {
"dropped connection");
exit(1);
}
aclsize -= j;
tp += j;
} while (aclsize > 0);
*tp = '\0';
if (aclerror != 0) {
error("rcp: failed to parse acl : %s\n",
return (ACL_FAIL);
}
if (f != -1) {
error("rcp: failed to set acl\n");
return (ACL_FAIL);
}
}
/* -1 means that just consume the data in the pipe */
}
}
return (ACL_OK);
}
static char *
{
int len;
while (*cp) {
return ((char *)cp);
len = 1;
}
return (0);
}
static int
{
}
static int
{
/*
* Note that rcp depends on the same file descriptor being both
* input and output to the remote side. This is bogus, especially
* when rcp is being run by a rsh that pipes. Fix it here because
* it would require significantly more work in other places.
* --hartmans 1/96
*/
if (fd == 0)
fd = 1;
}
static char **
{
int i;
(unsigned)sizeof (char *));
/*
* allocate an extra pointer, so that it is initialized to NULL and
* execv() will work
*/
for (i = 0; i < argc; i++) {
}
return (local_argv);
}
#define SIZEOF_INADDR sizeof (struct in_addr)
static void
{
if (config_file) {
const char *filenames[2];
filenames[0] = config_file;
exit(1);
}
exit(1);
exit(1);
if (ccache_file == NULL) {
exit(1);
} else {
exit(1);
}
exit(1);
exit(1);
&new_creds))
exit(1);
exit(1);
exit(1);
}
/* setup eblock for des_read and write */
/* OK process key */
/* cleanup */
}
static void
{
char *target;
/*
* Reset all KRB5 relevant flags and set the
* cmd-buffer so that normal rcp works
*/
if (cur_argc < 2)
usage();
if (cur_argc > 2)
rem = -1;
(void) init_service(krb5auth_flag);
} else {
}
/* NOTREACHED */
}
static int
init_service(int krb5flag)
{
if (krb5flag > 0) {
} else {
}
} else {
}
return (success);
}
/*PRINTFLIKE1*/
static void
{
char buf[RCP_BUFSIZE];
errs++;
*cp++ = 1;
if (iamremote == 0)
}