summaryrefslogtreecommitdiff
path: root/usr/src/cmd/cmd-inet/usr.bin/rcp.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/cmd/cmd-inet/usr.bin/rcp.c')
-rw-r--r--usr/src/cmd/cmd-inet/usr.bin/rcp.c2087
1 files changed, 2087 insertions, 0 deletions
diff --git a/usr/src/cmd/cmd-inet/usr.bin/rcp.c b/usr/src/cmd/cmd-inet/usr.bin/rcp.c
new file mode 100644
index 0000000000..b21f426e69
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/usr.bin/rcp.c
@@ -0,0 +1,2087 @@
+/*
+ * Copyright 2004 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>
+
+/*
+ * 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 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:")) != EOF) {
+ switch (ch) {
+ case 'd':
+ targetshouldbedirectory = 1;
+ break;
+ case 'f': /* "from" */
+ fflag = 1;
+ if (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 '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;
+
+ (void) snprintf(bp, buffersize, "%s -t %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) {
+ /*
+ * 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;
+
+ (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;
+ }
+
+ /*
+ * 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) {
+ /*
+ * 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) {
+ /* 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)
+ 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) {
+ /*
+ * 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) {
+ 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) {
+ /* 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;
+ aclent_t *aclbufp;
+ int aclsize;
+ char *acltext;
+ char buf[BUFSIZ];
+
+ if ((aclcnt = facl(f, GETACLCNT, 0, NULL)) < 0) {
+ error("can't get acl count \n");
+ return (ACL_FAIL);
+ }
+
+ /* send the acl count over */
+ (void) snprintf(buf, sizeof (buf), "A%d\n", aclcnt);
+ (void) desrcpwrite(rem, buf, strlen(buf));
+
+ /* only send acl when it is non-trivial */
+ if (aclcnt > MIN_ACL_ENTRIES) {
+ aclsize = aclcnt * sizeof (aclent_t);
+ if ((aclbufp = (aclent_t *)malloc(aclsize)) == NULL) {
+ error("rcp: cant allocate memory: aclcnt %d\n",
+ aclcnt);
+ exit(1);
+ }
+ if (facl(f, GETACL, aclcnt, aclbufp) < 0) {
+ error("rcp: failed to get acl\n");
+ return (ACL_FAIL);
+ }
+ acltext = acltotext(aclbufp, aclcnt);
+ if (acltext == NULL) {
+ error("rcp: failed to convert to text\n");
+ return (ACL_FAIL);
+ }
+
+ /* send ACLs over: send the length first */
+ (void) snprintf(buf, sizeof (buf), "A%d\n", strlen(acltext));
+
+ (void) desrcpwrite(rem, buf, strlen(buf));
+ (void) desrcpwrite(rem, acltext, strlen(acltext));
+ free(acltext);
+ free(aclbufp);
+ if (response() < 0)
+ return (ACL_FAIL);
+
+ }
+ return (ACL_OK);
+}
+
+/*
+ * Use this routine to get acl entry count and acl text size (in bytes)
+ */
+static int
+getaclinfo(int *cnt)
+{
+ char buf[BUFSIZ];
+ char *cp;
+ char ch;
+
+ /* get acl count */
+ cp = buf;
+ if (desrcpread(rem, cp, 1) <= 0)
+ return (ACL_FAIL);
+ if (*cp++ != 'A') {
+ 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 */
+ aclent_t *aclbufp; /* internal format */
+
+ /* get acl count */
+ if (getaclinfo(&aclcnt) != ACL_OK)
+ return (ACL_FAIL);
+
+ if (aclcnt > MIN_ACL_ENTRIES) {
+ /* get acl text size */
+ if (getaclinfo(&aclsize) != 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) {
+ aclbufp = aclfromtext(acltext, &aclcnt);
+ if (aclbufp == NULL) {
+ error("rcp: failed to parse acl\n");
+ return (ACL_FAIL);
+ }
+ if (f != -1) {
+ if (facl(f, SETACL, aclcnt, aclbufp) < 0) {
+ error("rcp: failed to set acl\n");
+ return (ACL_FAIL);
+ }
+ }
+ /* -1 means that just consume the data in the pipe */
+ free(aclbufp);
+ }
+ 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));
+}