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 <sys/param.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/acl.h>
#include <dirent.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.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 <sys/wait.h>
#include <aclutils.h>
/*
* It seems like Berkeley got these from pathnames.h?
*/
#define _PATH_RSH "/usr/bin/rsh"
#define _PATH_CP "/usr/bin/cp"
#define _PATH_BSHELL "/usr/bin/sh"
#define ACL_FAIL 1
#define ACL_OK 0
#define RCP_BUFSIZE (64 * 1024)
#define RCP_ACL "/usr/lib/sunw,rcp"
/* see PSARC/1993/004/opinion */
typedef struct _buf {
int cnt;
char *buf;
} BUF;
static char *cmd_sunw;
static struct passwd *pwd;
static int errs;
static int pflag;
static uid_t userid;
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 void rsource(char *, struct stat *);
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);
static BUF *allocbuf(BUF *, int, int);
static void error(char *fmt, ...);
/*
* 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>
#define NULLBUF (BUF *) 0
static int sock;
static char *cmd, *cmd_orig, *cmd_sunw_orig;
static char *krb_realm = NULL;
static char *krb_cache = NULL;
static char *krb_config = NULL;
static char des_inbuf[2 * RCP_BUFSIZE];
/* needs to be > largest read size */
static char des_outbuf[2 * RCP_BUFSIZE];
/* needs to be > largest write size */
static krb5_data desinbuf, desoutbuf;
static krb5_encrypt_block eblock; /* eblock for encrypt/decrypt */
static krb5_keyblock *session_key; /* static key for session */
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.
*/
extern errcode_t profile_get_options_boolean(profile_t, char **,
profile_options_boolean *);
extern errcode_t profile_get_options_string(profile_t, char **,
profile_option_strings *);
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 */
static enum kcmd_proto kcmd_proto = KCMD_NEW_PROTOCOL;
/* Flag set, if -PN / -PO is specified */
static boolean_t rcmdoption_done = B_FALSE;
static profile_options_boolean option[] = {
{ "encrypt", &encrypt_flag, 0 },
{ NULL, NULL, 0 }
};
static char *rcmdproto = NULL;
static profile_option_strings rcmdversion[] = {
{ "rcmd_protocol", &rcmdproto, 0 },
{ NULL, NULL, 0 }
};
static char *realmdef[] = { "realms", NULL, "rcp", NULL };
static char *appdef[] = { "appdefaults", "rcp", NULL };
static char **prev_argv;
static int prev_argc;
int
main(int argc, char *argv[])
{
int ch, fflag, tflag;
char *targ;
size_t cmdsiz;
(void) setlocale(LC_ALL, "");
if (strcmp(argv[0], RCP_ACL) == 0)
aclflag = 1;
if (!(pwd = getpwuid(userid = getuid()))) {
(void) fprintf(stderr, "rcp: unknown user %d.\n",
(uint_t)userid);
return (1);
}
fflag = tflag = 0;
while ((ch = getopt(argc, argv, "axdfprtz:D:k:P:Z")) != EOF) {
switch (ch) {
case 'd':
targetshouldbedirectory = 1;
break;
case 'f': /* "from" */
fflag = 1;
if (aclflag | acl_aclflag)
/* ok response */
(void) desrcpwrite(rem, "", 1);
break;
case 'p': /* preserve access/mod times */
++pflag;
break;
case 'r':
++iamrecursive;
break;
case 't': /* "to" */
tflag = 1;
break;
case 'Z':
acl_aclflag++;
break;
case 'x':
if (!krb5_privacy_allowed()) {
(void) fprintf(stderr, gettext("rcp: "
"Encryption not supported.\n"));
return (1);
}
encrypt_flag++;
krb5auth_flag++;
encrypt_done++;
break;
case 'k':
if ((krb_realm = (char *)strdup(optarg)) == NULL) {
(void) fprintf(stderr, gettext("rcp:"
" Cannot malloc.\n"));
return (1);
}
krb5auth_flag++;
break;
case 'P':
if (strncmp(optarg, "O", 1) == 0) {
if (rcmdoption_done == B_TRUE) {
(void) fprintf(stderr, gettext("rcp: "
"Only one of -PN and -PO "
"allowed.\n"));
usage();
}
kcmd_proto = KCMD_OLD_PROTOCOL;
rcmdoption_done = B_TRUE;
} else if (strncmp(optarg, "N", 1) == 0) {
if (rcmdoption_done == B_TRUE) {
(void) fprintf(stderr, gettext("rcp: "
"Only one of -PN and -PO "
"allowed.\n"));
usage();
}
kcmd_proto = KCMD_NEW_PROTOCOL;
rcmdoption_done = B_TRUE;
} else {
usage();
}
krb5auth_flag++;
break;
case 'a':
krb5auth_flag++;
break;
#ifdef DEBUG
case 'D':
portnumber = htons(atoi(optarg));
krb5auth_flag++;
break;
#endif /* DEBUG */
case '?':
default:
usage();
}
}
argc -= optind;
argv += optind;
if (krb5auth_flag > 0) {
status = krb5_init_context(&bsd_context);
if (status) {
com_err("rcp", status,
gettext("while initializing krb5"));
return (1);
}
/*
* Set up buffers for desread and deswrite.
*/
desinbuf.data = des_inbuf;
desoutbuf.data = des_outbuf;
desinbuf.length = sizeof (des_inbuf);
desoutbuf.length = sizeof (des_outbuf);
}
if (fflag || tflag)
if (encrypt_flag > 0)
(void) answer_auth(krb_config, krb_cache);
if (fflag) {
iamremote = 1;
(void) response();
(void) setuid(userid);
source(argc, argv);
return (errs);
}
if (tflag) {
iamremote = 1;
(void) setuid(userid);
sink(argc, argv);
return (errs);
}
if (argc < 2)
usage();
/* This will make "rcmd_af()" magically get the proper privilege */
if (__init_suid_priv(0, PRIV_NET_PRIVADDR, (char *)NULL) == -1) {
(void) fprintf(stderr, "rcp: must be set-uid root\n");
exit(1);
}
if (krb5auth_flag > 0) {
/*
* Get our local realm to look up local realm options.
*/
status = krb5_get_default_realm(bsd_context, &realmdef[1]);
if (status) {
com_err("rcp", status,
gettext("while getting default realm"));
return (1);
}
/*
* See if encryption should be done for this realm
*/
profile_get_options_boolean(bsd_context->profile, realmdef,
option);
/*
* Check the appdefaults section
*/
profile_get_options_boolean(bsd_context->profile, appdef,
option);
profile_get_options_string(bsd_context->profile, appdef,
rcmdversion);
if ((encrypt_done > 0) || (encrypt_flag > 0)) {
if (krb5_privacy_allowed() == TRUE) {
encrypt_flag++;
} else {
(void) fprintf(stderr, gettext("rcp: Encryption"
" not supported.\n"));
return (1);
}
}
if ((rcmdoption_done == B_FALSE) && (rcmdproto != NULL)) {
if (strncmp(rcmdproto, "rcmdv2", 6) == 0) {
kcmd_proto = KCMD_NEW_PROTOCOL;
} else if (strncmp(rcmdproto, "rcmdv1", 6) == 0) {
kcmd_proto = KCMD_OLD_PROTOCOL;
} else {
(void) fprintf(stderr, gettext("Unrecognized "
"KCMD protocol (%s)"), rcmdproto);
return (1);
}
}
}
if (argc > 2)
targetshouldbedirectory = 1;
rem = -1;
if (portnumber == 0) {
if (krb5auth_flag > 0) {
retval = init_service(krb5auth_flag);
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) {
(void) fprintf(stderr, gettext("Kerberized rcp session, "
"port %d in use "), portnumber);
if (kcmd_proto == KCMD_OLD_PROTOCOL)
(void) fprintf(stderr, gettext("[kcmd ver.1]\n"));
else
(void) fprintf(stderr, gettext("[kcmd ver.2]\n"));
} else {
(void) fprintf(stderr, gettext("Normal rcp session, port %d "
"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.
*/
cmdsiz = MAX(sizeof ("-x rcp -r -p -d -k ") +
strlen(krb_realm != NULL ? krb_realm : ""),
sizeof (RCP_ACL " -r -p -z -d"));
if (((cmd = (char *)malloc(cmdsiz)) == NULL) ||
((cmd_sunw_orig = (char *)malloc(cmdsiz)) == NULL) ||
((cmd_orig = (char *)malloc(cmdsiz)) == NULL)) {
(void) fprintf(stderr, gettext("rcp: Cannot "
"malloc.\n"));
return (1);
}
(void) snprintf(cmd, cmdsiz, "%srcp %s%s%s%s%s",
encrypt_flag ? "-x " : "",
iamrecursive ? " -r" : "", pflag ? " -p" : "",
targetshouldbedirectory ? " -d" : "",
krb_realm != NULL ? " -k " : "",
krb_realm != NULL ? krb_realm : "");
/*
* 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
*/
(void) snprintf(cmd_orig, cmdsiz, "rcp%s%s%s%s",
iamrecursive ? " -r" : "",
pflag ? " -p" : "",
zflag ? " -z" : "",
targetshouldbedirectory ? " -d" : "");
(void) snprintf(cmd_sunw_orig, cmdsiz, "%s%s%s%s%s", RCP_ACL,
iamrecursive ? " -r" : "",
pflag ? " -p" : "",
zflag ? " -z" : "",
targetshouldbedirectory ? " -d" : "");
prev_argc = argc;
prev_argv = save_argv(argc, argv);
} else {
cmdsiz = sizeof ("rcp -r -p -z -d");
if (((cmd = (char *)malloc(cmdsiz)) == NULL)) {
(void) fprintf(stderr, gettext("rcp: Cannot "
"malloc.\n"));
return (1);
}
(void) snprintf(cmd, cmdsiz, "rcp%s%s%s%s",
iamrecursive ? " -r" : "",
pflag ? " -p" : "",
zflag ? " -z" : "",
targetshouldbedirectory ? " -d" : "");
}
cmdsiz = sizeof (RCP_ACL " -r -p -z -d");
if ((cmd_sunw = (char *)malloc(cmdsiz)) == NULL) {
(void) fprintf(stderr, gettext("rcp: Cannot malloc.\n"));
return (1);
}
(void) snprintf(cmd_sunw, cmdsiz, "%s%s%s%s%s", RCP_ACL,
iamrecursive ? " -r" : "",
pflag ? " -p" : "",
zflag ? " -z" : "",
targetshouldbedirectory ? " -d" : "");
(void) signal(SIGPIPE, (void (*)(int))lostconn);
if (targ = colon(argv[argc - 1]))
toremote(targ, argc, argv);
else {
tolocal(argc, argv);
if (targetshouldbedirectory)
verifydir(argv[argc - 1]);
}
return (errs > 0 ? EXIT_FAILURE : EXIT_SUCCESS);
}
static void
toremote(char *targ, int argc, char *argv[])
{
int i;
char *host, *src, *suser, *thost, *tuser;
char resp;
size_t buffersize;
char bp[RCP_BUFSIZE];
krb5_creds *cred;
buffersize = RCP_BUFSIZE;
*targ++ = 0;
if (*targ == 0)
targ = ".";
if (thost = search_char((unsigned char *)argv[argc - 1], '@')) {
*thost++ = 0;
tuser = argv[argc - 1];
if (*tuser == '\0')
tuser = NULL;
else if (!okname(tuser))
exit(1);
} else {
thost = argv[argc - 1];
tuser = NULL;
}
thost = removebrackets(thost);
for (i = 0; i < argc - 1; i++) {
src = colon(argv[i]);
if (src) { /* remote to remote */
*src++ = 0;
if (*src == 0)
src = ".";
host = search_char((unsigned char *)argv[i], '@');
if (host) {
*host++ = 0;
host = removebrackets(host);
suser = argv[i];
if (*suser == '\0') {
suser = pwd->pw_name;
} else if (!okname(suser)) {
errs++;
continue;
}
(void) snprintf(bp, buffersize,
"%s %s -l %s -n %s %s '%s%s%s:%s'",
_PATH_RSH, host, suser, cmd, src,
tuser ? tuser : "", tuser ? "@" : "",
thost, targ);
} else {
host = removebrackets(argv[i]);
(void) snprintf(bp, buffersize,
"%s %s -n %s %s '%s%s%s:%s'",
_PATH_RSH, host, cmd, src,
tuser ? tuser : "", tuser ? "@" : "",
thost, targ);
}
if (susystem(bp) == -1)
errs++;
} else { /* local to remote */
if (rem == -1) {
host = thost;
if (krb5auth_flag > 0) {
(void) snprintf(bp, buffersize,
"%s -t %s", cmd, targ);
authopts = AP_OPTS_MUTUAL_REQUIRED;
status = kcmd(&sock, &host,
portnumber,
pwd->pw_name,
tuser ? tuser :
pwd->pw_name,
bp,
0,
"host",
krb_realm,
bsd_context,
&auth_context,
&cred,
0, /* No seq # */
0, /* No server seq # */
authopts,
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) {
(void) fprintf(stderr,
gettext("rcp: kcmdv2 "
"to host %s failed - %s"
"\nFallback to normal "
"rcp denied."), host,
error_message(status));
exit(1);
}
if (status != -1) {
(void) fprintf(stderr,
gettext("rcp: kcmd to host "
"%s failed - %s,\n"
"trying normal rcp...\n\n"),
host, error_message(status));
} else {
(void) fprintf(stderr,
gettext("trying normal"
" rcp...\n"));
}
/*
* kcmd() failed, so we have to
* fallback to normal rcp
*/
try_normal_rcp(prev_argc, prev_argv);
} else {
rem = sock;
session_key = &cred->keyblock;
if (kcmd_proto == KCMD_NEW_PROTOCOL) {
/* CSTYLED */
status = krb5_auth_con_getlocalsubkey(bsd_context, auth_context, &session_key);
if (status) {
com_err("rcp", status,
"determining "
"subkey for "
"session");
exit(1);
}
if (!session_key) {
com_err("rcp", 0,
"no subkey "
"negotiated for"
" connection");
exit(1);
}
}
eblock.crypto_entry =
session_key->enctype;
eblock.key =
(krb5_keyblock *)session_key;
init_encrypt(encrypt_flag,
bsd_context, kcmd_proto,
&desinbuf, &desoutbuf, CLIENT,
&eblock);
if (encrypt_flag > 0) {
char *s = gettext("This rcp "
"session is using "
"encryption for all "
"data transmissions."
"\r\n");
(void) write(2, s, strlen(s));
}
}
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?
*/
(void) snprintf(bp, buffersize, "%s -tZ %s",
cmd_sunw, targ);
rem = rcmd_af(&host, portnumber, pwd->pw_name,
tuser ? tuser : pwd->pw_name,
bp, 0, AF_INET6);
if (rem < 0)
exit(1);
/*
* This is similar to routine response().
* If response is not ok, treat the other
* side as non-acl rcp.
*/
if (read(rem, &resp, sizeof (resp))
!= sizeof (resp))
lostconn();
if (resp != 0) {
acl_aclflag = 0;
(void) snprintf(bp, buffersize,
"%s -t %s", cmd_sunw, targ);
(void) close(rem);
host = thost;
rem = rcmd_af(&host, portnumber,
pwd->pw_name,
tuser ? tuser : pwd->pw_name,
bp, 0, AF_INET6);
if (rem < 0)
exit(1);
if (read(rem, &resp, sizeof (resp))
!= sizeof (resp))
lostconn();
if (resp != 0) {
/*
* Not OK:
* The other side is running
* non-acl rcp. Try again with
* normal stuff
*/
aclflag = 0;
(void) snprintf(bp, buffersize,
"%s -t %s", cmd, targ);
(void) close(rem);
host = thost;
rem = rcmd_af(&host, portnumber,
pwd->pw_name,
tuser ? tuser :
pwd->pw_name, bp, 0,
AF_INET6);
if (rem < 0)
exit(1);
if (response() < 0)
exit(1);
}
}
/* everything should be fine now */
(void) setuid(userid);
}
}
source(1, argv + i);
}
}
}
static void
tolocal(int argc, char *argv[])
{
int i;
char *host, *src, *suser, *lhost;
char resp;
size_t buffersize;
char bp[RCP_BUFSIZE];
krb5_creds *cred;
buffersize = RCP_BUFSIZE;
for (i = 0; i < argc - 1; i++) {
if (!(src = colon(argv[i]))) { /* local to local */
(void) snprintf(bp, buffersize, "%s%s%s%s %s %s",
_PATH_CP, iamrecursive ? " -r" : "",
pflag ? " -p" : "",
zflag ? " -z" : "",
argv[i], argv[argc - 1]);
if (susystem(bp) == -1)
errs++;
continue;
}
*src++ = 0;
if (*src == 0)
src = ".";
host = search_char((unsigned char *)argv[i], '@');
if (host) {
*host++ = 0;
suser = argv[i];
if (*suser == '\0') {
suser = pwd->pw_name;
} else if (!okname(suser)) {
errs++;
continue;
}
} else {
host = argv[i];
suser = pwd->pw_name;
}
host = removebrackets(host);
lhost = host;
if (krb5auth_flag > 0) {
(void) snprintf(bp, buffersize, "%s -f %s", cmd, src);
authopts = AP_OPTS_MUTUAL_REQUIRED;
status = kcmd(&sock, &host,
portnumber,
pwd->pw_name, suser,
bp,
0, /* &rfd2 */
"host",
krb_realm,
bsd_context,
&auth_context,
&cred,
0, /* No seq # */
0, /* No server seq # */
authopts,
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) {
(void) fprintf(stderr, gettext("rcp: kcmdv2 "
"to host %s failed - %s\n"
"Fallback to normal rcp denied."),
host, error_message(status));
exit(1);
}
if (status != -1) {
(void) fprintf(stderr, gettext("rcp: kcmd "
"to host %s failed - %s,\n"
"trying normal rcp...\n\n"),
host, error_message(status));
} else {
(void) fprintf(stderr,
gettext("trying normal rcp...\n"));
}
/*
* kcmd() failed, so we have to
* fallback to normal rcp
*/
try_normal_rcp(prev_argc, prev_argv);
} else {
rem = sock;
session_key = &cred->keyblock;
if (kcmd_proto == KCMD_NEW_PROTOCOL) {
status = krb5_auth_con_getlocalsubkey(
bsd_context, auth_context,
&session_key);
if (status) {
com_err("rcp", status, "determining "
"subkey for session");
exit(1);
}
if (!session_key) {
com_err("rcp", 0, "no subkey negotiated"
" for connection");
exit(1);
}
}
eblock.crypto_entry = session_key->enctype;
eblock.key = (krb5_keyblock *)session_key;
init_encrypt(encrypt_flag, bsd_context, kcmd_proto,
&desinbuf, &desoutbuf, CLIENT,
&eblock);
if (encrypt_flag > 0) {
char *s = gettext("This rcp "
"session is using DES "
"encryption for all "
"data transmissions."
"\r\n");
(void) write(2, s, strlen(s));
}
}
}
else
{
/*
* ACL support: try to find out if the remote site is
* running acl cognizant version of rcp.
*/
aclflag = 1;
acl_aclflag = 1;
(void) snprintf(bp, buffersize, "%s -Zf %s", cmd_sunw, src);
rem = rcmd_af(&host, portnumber, pwd->pw_name, suser,
bp, 0, AF_INET6);
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.
*/
if (read(rem, &resp, sizeof (resp)) != sizeof (resp))
lostconn();
if (resp != 0) {
/*
* Try again without ace_acl support
*/
acl_aclflag = 0;
(void) snprintf(bp, buffersize, "%s -f %s",
cmd_sunw, src);
rem = rcmd_af(&host, portnumber, pwd->pw_name, suser,
bp, 0, AF_INET6);
if (rem < 0) {
++errs;
continue;
}
if (read(rem, &resp, sizeof (resp)) != sizeof (resp))
lostconn();
/*
* NOT ok:
* The other side is running non-acl rcp.
* Try again with normal stuff
*/
aclflag = 0;
(void) snprintf(bp, buffersize, "%s -f %s", cmd, src);
(void) close(rem);
host = lhost;
rem = rcmd_af(&host, portnumber, pwd->pw_name,
suser, bp, 0, AF_INET6);
if (rem < 0) {
++errs;
continue;
}
}
}
sink(1, argv + argc - 1);
(void) close(rem);
rem = -1;
}
}
static void
verifydir(char *cp)
{
struct stat stb;
if (stat(cp, &stb) >= 0) {
if ((stb.st_mode & S_IFMT) == S_IFDIR)
return;
errno = ENOTDIR;
}
error("rcp: %s: %s.\n", cp, strerror(errno));
exit(1);
}
static char *
colon(char *cp)
{
boolean_t is_bracket_open = B_FALSE;
for (; *cp; ++cp) {
if (*cp == '[')
is_bracket_open = B_TRUE;
else if (*cp == ']')
is_bracket_open = B_FALSE;
else if (*cp == ':' && !is_bracket_open)
return (cp);
else if (*cp == '/')
return (0);
}
return (0);
}
static int
okname(char *cp0)
{
register char *cp = cp0;
register int c;
do {
c = *cp;
if (c & 0200)
goto bad;
if (!isalpha(c) && !isdigit(c) && c != '_' && c != '-')
goto bad;
} while (*++cp);
return (1);
bad:
(void) fprintf(stderr, "rcp: invalid user name %s\n", cp0);
return (0);
}
static char *
removebrackets(char *str)
{
char *newstr = str;
if ((str[0] == '[') && (str[strlen(str) - 1] == ']')) {
newstr = str + 1;
str[strlen(str) - 1] = '\0';
}
return (newstr);
}
static int
susystem(char *s)
{
int status, pid, w;
register void (*istat)(), (*qstat)();
int pfds[2];
char buf[BUFSIZ];
int cnt;
boolean_t seen_stderr_traffic;
/*
* 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.
*/
if (pipe(pfds) == -1) {
(void) fprintf(stderr, "Couldn't create pipe: %s\n",
strerror(errno));
return (-1);
}
if ((pid = vfork()) < 0) {
(void) close(pfds[0]);
(void) close(pfds[1]);
(void) fprintf(stderr, "Couldn't fork child process: %s\n",
strerror(errno));
return (-1);
} else if (pid == 0) {
/*
* Child.
*/
(void) close(pfds[0]);
/*
* Send stderr messages down the pipe so that we can detect
* them in the parent process.
*/
if (pfds[1] != STDERR_FILENO) {
(void) dup2(pfds[1], STDERR_FILENO);
(void) close(pfds[1]);
}
/*
* This shell does not inherit the additional privilege
* we have in our Permitted set.
*/
(void) execl(_PATH_BSHELL, "sh", "-c", s, (char *)0);
_exit(127);
}
/*
* Parent.
*/
istat = signal(SIGINT, SIG_IGN);
qstat = signal(SIGQUIT, SIG_IGN);
(void) close(pfds[1]);
seen_stderr_traffic = B_FALSE;
while ((cnt = read(pfds[0], buf, sizeof (buf))) > 0) {
/*
* 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.
*/
(void) write(STDERR_FILENO, buf, cnt);
seen_stderr_traffic = B_TRUE;
}
(void) close(pfds[0]);
while ((w = wait(&status)) != pid && w != -1)
;
if (w == -1)
status = -1;
(void) signal(SIGINT, istat);
(void) signal(SIGQUIT, qstat);
return (seen_stderr_traffic ? -1 : status);
}
static void
source(int argc, char *argv[])
{
struct stat stb;
static BUF buffer;
BUF *bp;
int x, readerr, f, amt;
char *last, *name, buf[RCP_BUFSIZE];
off_t off, size, i;
ssize_t cnt;
for (x = 0; x < argc; x++) {
name = argv[x];
if ((f = open(name, O_RDONLY, 0)) < 0) {
error("rcp: %s: %s\n", name, strerror(errno));
continue;
}
if (fstat(f, &stb) < 0)
goto notreg;
switch (stb.st_mode&S_IFMT) {
case S_IFREG:
break;
case S_IFDIR:
if (iamrecursive) {
(void) close(f);
rsource(name, &stb);
continue;
}
/* FALLTHROUGH */
default:
notreg:
(void) close(f);
error("rcp: %s: not a plain file\n", name);
continue;
}
last = rindex(name, '/');
if (last == 0)
last = name;
else
last++;
if (pflag) {
time_t mtime, atime;
time_t now;
/*
* Make it compatible with possible future
* versions expecting microseconds.
*/
mtime = stb.st_mtime;
atime = stb.st_atime;
if ((mtime < 0) || (atime < 0)) {
now = time(NULL);
if (mtime < 0) {
mtime = now;
error("negative modification time on "
"%s; not preserving\n", name);
}
if (atime < 0) {
atime = now;
error("negative access time on "
"%s; not preserving\n", name);
}
}
(void) snprintf(buf, sizeof (buf), "T%ld 0 %ld 0\n",
mtime, atime);
(void) desrcpwrite(rem, buf, strlen(buf));
if (response() < 0) {
(void) close(f);
continue;
}
}
(void) snprintf(buf, sizeof (buf), "C%04o %lld %s\n",
(uint_t)(stb.st_mode & 07777), (longlong_t)stb.st_size,
last);
(void) desrcpwrite(rem, buf, strlen(buf));
if (response() < 0) {
(void) close(f);
continue;
}
/* ACL support: send */
if (aclflag | acl_aclflag) {
/* get acl from f and send it over */
if (sendacl(f) == ACL_FAIL) {
(void) close(f);
continue;
}
}
if ((krb5auth_flag > 0) || (iamremote == 1)) {
bp = allocbuf(&buffer, f, RCP_BUFSIZE);
if (bp == NULLBUF) {
(void) close(f);
continue;
}
readerr = 0;
for (i = 0; i < stb.st_size; i += bp->cnt) {
amt = bp->cnt;
if (i + amt > stb.st_size)
amt = stb.st_size - i;
if (readerr == 0 &&
read(f, bp->buf, amt) != amt)
readerr = errno;
(void) desrcpwrite(rem, bp->buf, amt);
}
(void) close(f);
if (readerr == 0)
(void) desrcpwrite(rem, "", 1);
else
error("rcp: %s: %s\n", name,
error_message(readerr));
} else {
cnt = off = 0;
size = stb.st_size;
while (size != 0) {
amt = MIN(size, SENDFILE_SIZE);
cnt = sendfile(rem, f, &off, amt);
if (cnt == -1) {
if (errno == EINTR) {
continue;
} else {
break;
}
}
size -= cnt;
}
if (cnt == -1) {
error("rcp: %s: %s\n", name, strerror(errno));
} else {
(void) write(rem, "", 1);
}
(void) close(f);
}
(void) response();
}
}
static void
rsource(char *name, struct stat *statp)
{
DIR *d;
struct dirent *dp;
char *last, *vect[1];
char path[MAXPATHLEN];
if (!(d = opendir(name))) {
error("rcp: %s: %s\n", name, strerror(errno));
return;
}
last = rindex(name, '/');
if (last == 0)
last = name;
else
last++;
if (pflag) {
(void) snprintf(path, sizeof (path), "T%ld 0 %ld 0\n",
statp->st_mtime, statp->st_atime);
(void) desrcpwrite(rem, path, strlen(path));
if (response() < 0) {
(void) closedir(d);
return;
}
}
(void) snprintf(path, sizeof (path), "D%04o %d %s\n",
(uint_t)(statp->st_mode & 07777), 0, last);
(void) desrcpwrite(rem, path, strlen(path));
/* acl support for directory */
if (aclflag) {
/* get acl from f and send it over */
if (sendacl(d->dd_fd) == ACL_FAIL) {
(void) closedir(d);
return;
}
}
if (response() < 0) {
(void) closedir(d);
return;
}
while (dp = readdir(d)) {
if (dp->d_ino == 0)
continue;
if ((strcmp(dp->d_name, ".") == 0) ||
(strcmp(dp->d_name, "..") == 0))
continue;
if ((uint_t)strlen(name) + 1 + strlen(dp->d_name) >=
MAXPATHLEN - 1) {
error("%s/%s: name too long.\n", name, dp->d_name);
continue;
}
(void) snprintf(path, sizeof (path), "%s/%s",
name, dp->d_name);
vect[0] = path;
source(1, vect);
}
(void) closedir(d);
(void) desrcpwrite(rem, "E\n", 2);
(void) response();
}
static int
response(void)
{
register char *cp;
char ch, resp, rbuf[RCP_BUFSIZE];
if (desrcpread(rem, &resp, 1) != 1)
lostconn();
cp = rbuf;
switch (resp) {
case 0: /* ok */
return (0);
default:
*cp++ = resp;
/* FALLTHROUGH */
case 1: /* error, followed by err msg */
case 2: /* fatal error, "" */
do {
if (desrcpread(rem, &ch, sizeof (ch)) != sizeof (ch))
lostconn();
*cp++ = ch;
} while (cp < &rbuf[RCP_BUFSIZE] && ch != '\n');
if (!iamremote)
(void) write(STDERR_FILENO, rbuf, cp - rbuf);
++errs;
if (resp == 1)
return (-1);
exit(1);
}
/*NOTREACHED*/
}
static void
lostconn(void)
{
if (!iamremote)
(void) fprintf(stderr, "rcp: lost connection\n");
exit(1);
}
static void
sink(int argc, char *argv[])
{
char *cp;
static BUF buffer;
struct stat stb;
struct timeval tv[2];
BUF *bp;
off_t i, j;
char ch, *targ, *why;
int amt, count, exists, first, mask, mode;
off_t size;
int ofd, setimes, targisdir, wrerr;
char *np, *vect[1], buf[RCP_BUFSIZE];
char *namebuf = NULL;
size_t namebuf_sz = 0;
size_t need;
#define atime tv[0]
#define mtime tv[1]
#define SCREWUP(str) { why = str; goto screwup; }
setimes = targisdir = 0;
mask = umask(0);
if (!pflag)
(void) umask(mask);
if (argc != 1) {
error("rcp: ambiguous target\n");
exit(1);
}
targ = *argv;
if (targetshouldbedirectory)
verifydir(targ);
(void) desrcpwrite(rem, "", 1);
if (stat(targ, &stb) == 0 && (stb.st_mode & S_IFMT) == S_IFDIR)
targisdir = 1;
for (first = 1; ; first = 0) {
cp = buf;
if (desrcpread(rem, cp, 1) <= 0) {
if (namebuf != NULL)
free(namebuf);
return;
}
if (*cp++ == '\n')
SCREWUP("unexpected <newline>");
do {
if (desrcpread(rem, &ch, sizeof (ch)) != sizeof (ch))
SCREWUP("lost connection");
*cp++ = ch;
} while (cp < &buf[RCP_BUFSIZE - 1] && ch != '\n');
*cp = 0;
if (buf[0] == '\01' || buf[0] == '\02') {
if (iamremote == 0)
(void) write(STDERR_FILENO, buf + 1,
strlen(buf + 1));
if (buf[0] == '\02')
exit(1);
errs++;
continue;
}
if (buf[0] == 'E') {
(void) desrcpwrite(rem, "", 1);
if (namebuf != NULL)
free(namebuf);
return;
}
if (ch == '\n')
*--cp = 0;
cp = buf;
if (*cp == 'T') {
setimes++;
cp++;
mtime.tv_sec = strtol(cp, &cp, 0);
if (*cp++ != ' ')
SCREWUP("mtime.sec not delimited");
mtime.tv_usec = strtol(cp, &cp, 0);
if (*cp++ != ' ')
SCREWUP("mtime.usec not delimited");
atime.tv_sec = strtol(cp, &cp, 0);
if (*cp++ != ' ')
SCREWUP("atime.sec not delimited");
atime.tv_usec = strtol(cp, &cp, 0);
if (*cp++ != '\0')
SCREWUP("atime.usec not delimited");
(void) desrcpwrite(rem, "", 1);
continue;
}
if (*cp != 'C' && *cp != 'D') {
/*
* 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) {
error("%s\n", cp);
exit(1);
}
SCREWUP("expected control record");
}
mode = 0;
for (++cp; cp < buf + 5; cp++) {
if (*cp < '0' || *cp > '7')
SCREWUP("bad mode");
mode = (mode << 3) | (*cp - '0');
}
if (*cp++ != ' ')
SCREWUP("mode not delimited");
size = 0;
while (isdigit(*cp))
size = size * 10 + (*cp++ - '0');
if (*cp++ != ' ')
SCREWUP("size not delimited");
if (targisdir) {
need = strlen(targ) + sizeof ("/") + strlen(cp);
if (need > namebuf_sz) {
if ((namebuf = realloc(namebuf, need)) == NULL) {
error("rcp: out of memory\n");
exit(1);
}
namebuf_sz = need;
}
(void) snprintf(namebuf, need, "%s%s%s", targ,
*targ ? "/" : "", cp);
np = namebuf;
} else {
np = targ;
}
exists = stat(np, &stb) == 0;
if (buf[0] == 'D') {
if (exists) {
if ((stb.st_mode&S_IFMT) != S_IFDIR) {
if (aclflag | acl_aclflag) {
/*
* consume acl in the pipe
* fd = -1 to indicate the
* special case
*/
if (recvacl(-1, exists, pflag)
== ACL_FAIL) {
goto bad;
}
}
errno = ENOTDIR;
goto bad;
}
if (pflag)
(void) chmod(np, mode);
} else if (mkdir(np, mode) < 0) {
if (aclflag) {
/* consume acl in the pipe */
(void) recvacl(-1, exists, pflag);
}
goto bad;
}
/* acl support for directories */
if (aclflag | acl_aclflag) {
int dfd;
if ((dfd = open(np, O_RDONLY)) == -1)
goto bad;
/* get acl and set it to ofd */
if (recvacl(dfd, exists, pflag) == ACL_FAIL) {
(void) close(dfd);
if (!exists)
(void) rmdir(np);
goto bad;
}
(void) close(dfd);
}
vect[0] = np;
sink(1, vect);
if (setimes) {
setimes = 0;
if (utimes(np, tv) < 0)
error("rcp: can't set times on %s: %s\n",
np, strerror(errno));
}
continue;
}
if ((ofd = open(np, O_WRONLY|O_CREAT, mode)) < 0) {
bad:
error("rcp: %s: %s\n", np, strerror(errno));
continue;
}
/*
* If the output file exists we have to force zflag off
* to avoid erroneously seeking past old data.
*/
zopen(ofd, zflag && !exists);
if (exists && pflag)
(void) fchmod(ofd, mode);
(void) desrcpwrite(rem, "", 1);
/*
* ACL support: receiving
*/
if (aclflag | acl_aclflag) {
/* get acl and set it to ofd */
if (recvacl(ofd, exists, pflag) == ACL_FAIL) {
(void) close(ofd);
if (!exists)
(void) unlink(np);
continue;
}
}
if ((bp = allocbuf(&buffer, ofd, RCP_BUFSIZE)) == 0) {
(void) close(ofd);
continue;
}
cp = bp->buf;
count = 0;
wrerr = 0;
for (i = 0; i < size; i += RCP_BUFSIZE) {
amt = RCP_BUFSIZE;
if (i + amt > size)
amt = size - i;
count += amt;
do {
j = desrcpread(rem, cp, amt);
if (j <= 0) {
int sverrno = errno;
/*
* 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).
*/
size = lseek(ofd, 0, SEEK_CUR);
if ((ftruncate(ofd, size) == -1) &&
(errno != EINVAL) &&
(errno != EACCES))
#define TRUNCERR "rcp: can't truncate %s: %s\n"
error(TRUNCERR, np,
strerror(errno));
error("rcp: %s\n",
j ? strerror(sverrno) :
"dropped connection");
(void) close(ofd);
exit(1);
}
amt -= j;
cp += j;
} while (amt > 0);
if (count == bp->cnt) {
cp = bp->buf;
if (wrerr == 0 &&
zwrite(ofd, cp, count) < 0)
wrerr++;
count = 0;
}
}
if (count != 0 && wrerr == 0 &&
zwrite(ofd, bp->buf, count) < 0)
wrerr++;
if (zclose(ofd) < 0)
wrerr++;
if ((ftruncate(ofd, size) == -1) && (errno != EINVAL) &&
(errno != EACCES)) {
error(TRUNCERR, np, strerror(errno));
}
(void) close(ofd);
(void) response();
if (setimes) {
setimes = 0;
if (utimes(np, tv) < 0)
error("rcp: can't set times on %s: %s\n",
np, strerror(errno));
}
if (wrerr)
error("rcp: %s: %s\n", np, strerror(errno));
else
(void) desrcpwrite(rem, "", 1);
}
screwup:
error("rcp: protocol screwup: %s\n", why);
exit(1);
}
#ifndef roundup
#define roundup(x, y) ((((x)+((y)-1))/(y))*(y))
#endif /* !roundup */
static BUF *
allocbuf(BUF *bp, int fd, int blksize)
{
struct stat stb;
int size;
if (fstat(fd, &stb) < 0) {
error("rcp: fstat: %s\n", strerror(errno));
return (0);
}
size = roundup(stb.st_blksize, blksize);
if (size == 0)
size = blksize;
if (bp->cnt < size) {
if (bp->buf != 0)
free(bp->buf);
bp->buf = (char *)malloc((uint_t)size);
if (!bp->buf) {
error("rcp: malloc: out of memory\n");
return (0);
}
}
bp->cnt = size;
return (bp);
}
static void
usage(void)
{
(void) fprintf(stderr, "%s: \t%s\t%s", gettext("Usage"),
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
*/
static off_t zbsize;
static off_t zlastseek;
/* is it ok to try to create holes? */
static void
zopen(int fd, int flag)
{
struct stat st;
zbsize = 0;
zlastseek = 0;
if (flag &&
fstat(fd, &st) == 0 &&
(st.st_mode & S_IFMT) == S_IFREG)
zbsize = st.st_blksize;
}
/* write and/or seek */
static int
zwrite(int fd, char *buf, int nbytes)
{
off_t block = zbsize ? zbsize : nbytes;
do {
if (block > nbytes)
block = nbytes;
nbytes -= block;
if (!zbsize || notzero(buf, block)) {
register int n, count = block;
do {
if ((n = write(fd, buf, count)) < 0)
return (-1);
buf += n;
} while ((count -= n) > 0);
zlastseek = 0;
} else {
if (lseek(fd, (off_t)block, SEEK_CUR) < 0)
return (-1);
buf += block;
zlastseek = 1;
}
} while (nbytes > 0);
return (0);
}
/* write last byte of file if necessary */
static int
zclose(int fd)
{
zbsize = 0;
if (zlastseek && (lseek(fd, (off_t)-1, SEEK_CUR) < 0 ||
zwrite(fd, "", 1) < 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 buf[BUFSIZ];
acl_t *aclp;
char acltype;
int aclerror;
int trivial;
aclerror = facl_get(f, ACL_NO_TRIVIAL, &aclp);
if (aclerror != 0) {
error("can't retrieve ACL: %s \n", acl_strerror(aclerror));
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.
*/
if (aclp && (acl_type(aclp) != ACLENT_T) && (acl_aclflag == 0)) {
aclcnt = MIN_ACL_ENTRIES;
acltype = 'A';
trivial = ACL_IS_TRIVIAL;
} else {
aclcnt = (aclp != NULL) ? acl_cnt(aclp) : 0;
if (aclp) {
acltype = (acl_type(aclp) != ACLENT_T) ? 'Z' : 'A';
aclcnt = acl_cnt(aclp);
trivial = (acl_flags(aclp) & ACL_IS_TRIVIAL);
} else {
acltype = 'A';
aclcnt = MIN_ACL_ENTRIES;
trivial = ACL_IS_TRIVIAL;
}
}
/* send the acl count over */
(void) snprintf(buf, sizeof (buf), "%c%d\n", acltype, aclcnt);
(void) desrcpwrite(rem, buf, strlen(buf));
/*
* only send acl when we have an aclp, which would
* imply its not trivial.
*/
if (aclp && (trivial != ACL_IS_TRIVIAL)) {
acltext = acl_totext(aclp, 0);
if (acltext == NULL) {
error("rcp: failed to convert to text\n");
acl_free(aclp);
return (ACL_FAIL);
}
/* send ACLs over: send the length first */
(void) snprintf(buf, sizeof (buf), "%c%d\n",
acltype, strlen(acltext));
(void) desrcpwrite(rem, buf, strlen(buf));
(void) desrcpwrite(rem, acltext, strlen(acltext));
free(acltext);
if (response() < 0) {
acl_free(aclp);
return (ACL_FAIL);
}
}
if (aclp)
acl_free(aclp);
return (ACL_OK);
}
/*
* Use this routine to get acl entry count and acl text size (in bytes)
*/
static int
getaclinfo(int *cnt, int *acltype)
{
char buf[BUFSIZ];
char *cp;
char ch;
/* get acl count */
cp = buf;
if (desrcpread(rem, cp, 1) <= 0)
return (ACL_FAIL);
switch (*cp++) {
case 'A':
*acltype = 0;
break;
case 'Z':
*acltype = 1;
break;
default:
error("rcp: expect an ACL record, but got %c\n", *cp);
return (ACL_FAIL);
}
do {
if (desrcpread(rem, &ch, sizeof (ch)) != sizeof (ch)) {
error("rcp: lost connection ..\n");
return (ACL_FAIL);
}
*cp++ = ch;
} while (cp < &buf[BUFSIZ - 1] && ch != '\n');
if (ch != '\n') {
error("rcp: ACL record corrupted \n");
return (ACL_FAIL);
}
cp = &buf[1];
*cnt = strtol(cp, &cp, 0);
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
recvacl(int f, int exists, int preserve)
{
int aclcnt; /* acl entry count */
int aclsize; /* acl text length */
int j;
char *tp;
char *acltext; /* external format */
acl_t *aclp;
int acltype;
int min_entries;
int aclerror;
/* get acl count */
if (getaclinfo(&aclcnt, &acltype) != ACL_OK)
return (ACL_FAIL);
if (acltype == 0) {
min_entries = MIN_ACL_ENTRIES;
} else {
min_entries = 1;
}
if (aclcnt > min_entries) {
/* get acl text size */
if (getaclinfo(&aclsize, &acltype) != ACL_OK)
return (ACL_FAIL);
if ((acltext = malloc(aclsize + 1)) == NULL) {
error("rcp: cant allocate memory: %d\n", aclsize);
return (ACL_FAIL);
}
tp = acltext;
do {
j = desrcpread(rem, tp, aclsize);
if (j <= 0) {
error("rcp: %s\n", j ? strerror(errno) :
"dropped connection");
exit(1);
}
aclsize -= j;
tp += j;
} while (aclsize > 0);
*tp = '\0';
if (preserve || !exists) {
aclerror = acl_fromtext(acltext, &aclp);
if (aclerror != 0) {
error("rcp: failed to parse acl : %s\n",
acl_strerror(aclerror));
return (ACL_FAIL);
}
if (f != -1) {
if (facl_set(f, aclp) < 0) {
error("rcp: failed to set acl\n");
return (ACL_FAIL);
}
}
/* -1 means that just consume the data in the pipe */
acl_free(aclp);
}
free(acltext);
(void) desrcpwrite(rem, "", 1);
}
return (ACL_OK);
}
static char *
search_char(unsigned char *cp, unsigned char chr)
{
int len;
while (*cp) {
if (*cp == chr)
return ((char *)cp);
if ((len = mblen((char *)cp, MB_CUR_MAX)) <= 0)
len = 1;
cp += len;
}
return (0);
}
static int
desrcpread(int fd, char *buf, int len)
{
return ((int)desread(fd, buf, len, 0));
}
static int
desrcpwrite(int fd, char *buf, int len)
{
/*
* 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;
return ((int)deswrite(fd, buf, len, 0));
}
static char **
save_argv(int argc, char **argv)
{
int i;
char **local_argv = (char **)calloc((unsigned)argc + 1,
(unsigned)sizeof (char *));
/*
* allocate an extra pointer, so that it is initialized to NULL and
* execv() will work
*/
for (i = 0; i < argc; i++) {
local_argv[i] = strsave(argv[i]);
}
return (local_argv);
}
#define SIZEOF_INADDR sizeof (struct in_addr)
static void
answer_auth(char *config_file, char *ccache_file)
{
krb5_data pname_data, msg;
krb5_creds creds, *new_creds;
krb5_ccache cc;
krb5_auth_context auth_context = NULL;
if (config_file) {
const char *filenames[2];
filenames[1] = NULL;
filenames[0] = config_file;
if (krb5_set_config_files(bsd_context, filenames))
exit(1);
}
(void) memset((char *)&creds, 0, sizeof (creds));
if (krb5_read_message(bsd_context, (krb5_pointer) &rem, &pname_data))
exit(1);
if (krb5_read_message(bsd_context, (krb5_pointer) &rem,
&creds.second_ticket))
exit(1);
if (ccache_file == NULL) {
if (krb5_cc_default(bsd_context, &cc))
exit(1);
} else {
if (krb5_cc_resolve(bsd_context, ccache_file, &cc))
exit(1);
}
if (krb5_cc_get_principal(bsd_context, cc, &creds.client))
exit(1);
if (krb5_parse_name(bsd_context, pname_data.data, &creds.server))
exit(1);
krb5_xfree(pname_data.data);
if (krb5_get_credentials(bsd_context, KRB5_GC_USER_USER, cc, &creds,
&new_creds))
exit(1);
if (krb5_mk_req_extended(bsd_context, &auth_context,
AP_OPTS_USE_SESSION_KEY, NULL, new_creds, &msg))
exit(1);
if (krb5_write_message(bsd_context, (krb5_pointer) & rem, &msg)) {
krb5_xfree(msg.data);
exit(1);
}
/* setup eblock for des_read and write */
krb5_copy_keyblock(bsd_context, &new_creds->keyblock, &session_key);
/* OK process key */
eblock.crypto_entry = session_key->enctype;
eblock.key = (krb5_keyblock *)session_key;
init_encrypt(encrypt_flag, bsd_context, KCMD_OLD_PROTOCOL,
&desinbuf, &desoutbuf, CLIENT, &eblock);
/* cleanup */
krb5_free_cred_contents(bsd_context, &creds);
krb5_free_creds(bsd_context, new_creds);
krb5_xfree(msg.data);
}
static void
try_normal_rcp(int cur_argc, char **cur_argv)
{
char *target;
/*
* Reset all KRB5 relevant flags and set the
* cmd-buffer so that normal rcp works
*/
krb5auth_flag = encrypt_flag = encrypt_done = 0;
cmd = cmd_orig;
cmd_sunw = cmd_sunw_orig;
if (cur_argc < 2)
usage();
if (cur_argc > 2)
targetshouldbedirectory = 1;
rem = -1;
prev_argc = cur_argc;
prev_argv = save_argv(cur_argc, cur_argv);
(void) init_service(krb5auth_flag);
if (target = colon(cur_argv[cur_argc - 1])) {
toremote(target, cur_argc, cur_argv);
} else {
tolocal(cur_argc, cur_argv);
if (targetshouldbedirectory)
verifydir(cur_argv[cur_argc - 1]);
}
exit(errs);
/* NOTREACHED */
}
static int
init_service(int krb5flag)
{
struct servent *sp;
boolean_t success = B_FALSE;
if (krb5flag > 0) {
sp = getservbyname("kshell", "tcp");
if (sp == NULL) {
(void) fprintf(stderr,
gettext("rcp: kshell/tcp: unknown service.\n"
"trying normal shell/tcp service\n"));
} else {
portnumber = sp->s_port;
success = B_TRUE;
}
} else {
portnumber = htons(IPPORT_CMDSERVER);
success = B_TRUE;
}
return (success);
}
/*PRINTFLIKE1*/
static void
error(char *fmt, ...)
{
va_list ap;
char buf[RCP_BUFSIZE];
char *cp = buf;
va_start(ap, fmt);
errs++;
*cp++ = 1;
(void) vsnprintf(cp, sizeof (buf) - 1, fmt, ap);
va_end(ap);
(void) desrcpwrite(rem, buf, strlen(buf));
if (iamremote == 0)
(void) write(2, buf + 1, strlen(buf + 1));
}