diff options
Diffstat (limited to 'usr/src/cmd/cmd-inet/usr.bin/rcp.c')
-rw-r--r-- | usr/src/cmd/cmd-inet/usr.bin/rcp.c | 2087 |
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)); +} |