#define _FILE_OFFSET_BITS 64
#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>
#include <sys/varargs.h>
#define _PATH_RSH "/usr/bin/rsh"
#define _PATH_CP "/usr/bin/cp"
#define ACL_FAIL 1
#define ACL_OK 0
#define RCP_BUFSIZE (64 * 1024)
#define RCP_ACL "/usr/lib/sunw,rcp"
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 *, 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, ...);
static void addargs(char **, ...);
#define SENDFILE_SIZE 0x7FFFE000
#include <k5-int.h>
#include <profile/prof_int.h>
#include <com_err.h>
#include <kcmd.h>
#define NULLBUF (BUF *) 0
#define MAXARGS 10
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];
static char des_outbuf[2 * RCP_BUFSIZE];
static krb5_data desinbuf, desoutbuf;
static krb5_encrypt_block eblock;
static krb5_keyblock *session_key;
static krb5_context bsd_context = NULL;
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);
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;
static profile_options_boolean autologin_option[] = {
{ "autologin", &krb5auth_flag, 0 },
{ NULL, NULL, 0 }
};
static int no_krb5auth_flag = 0;
static int encrypt_flag = 0;
static int encrypt_done = 0;
static enum kcmd_proto kcmd_proto = KCMD_NEW_PROTOCOL;
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:ZK")) != EOF) {
switch (ch) {
case 'd':
targetshouldbedirectory = 1;
break;
case 'f':
fflag = 1;
if (aclflag | acl_aclflag)
(void) desrcpwrite(rem, "", 1);
break;
case 'p':
++pflag;
break;
case 'r':
++iamrecursive;
break;
case 't':
tflag = 1;
break;
case 'Z':
acl_aclflag++;
break;
case 'K':
no_krb5auth_flag++;
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
case '?':
default:
usage();
}
}
argc -= optind;
argv += optind;
if (no_krb5auth_flag) {
krb5auth_flag = 0;
fflag = encrypt_flag = 0;
} else if (!krb5auth_flag) {
status = krb5_init_context(&bsd_context);
if (!status) {
(void) profile_get_options_boolean(bsd_context->profile,
appdef,
autologin_option);
}
}
if (krb5auth_flag > 0) {
if (!bsd_context) {
status = krb5_init_context(&bsd_context);
if (status) {
com_err("rcp", status,
gettext("while initializing krb5"));
return (1);
}
}
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();
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) {
status = krb5_get_default_realm(bsd_context, &realmdef[1]);
if (status) {
com_err("rcp", status,
gettext("while getting default realm"));
return (1);
}
(void) profile_get_options_boolean(bsd_context->profile,
realmdef, option);
(void) profile_get_options_boolean(bsd_context->profile,
appdef, option);
(void) 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) {
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
if (krb5auth_flag > 0) {
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 : "");
(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;
char *arglist[MAXARGS+1];
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) {
*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%s:%s'",
tuser ? tuser : "", tuser ? "@" : "",
thost, targ);
(void) addargs(arglist, "rsh", host, "-l",
suser, "-n", cmd, src, bp, (char *)NULL);
} else {
host = removebrackets(argv[i]);
(void) snprintf(bp, buffersize, "'%s%s%s:%s'",
tuser ? tuser : "", tuser ? "@" : "",
thost, targ);
(void) addargs(arglist, "rsh", host, "-n", cmd,
src, bp, (char *)NULL);
}
if (susystem(_PATH_RSH, arglist) == -1)
errs++;
} else {
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,
0,
authopts,
0,
&kcmd_proto);
if (status) {
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"));
}
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 "
"encryption for all "
"data transmissions."
"\r\n");
(void) write(2, s, strlen(s));
}
}
if (response() < 0)
exit(1);
} else {
aclflag = 1;
acl_aclflag = 1;
(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);
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) {
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);
}
}
(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;
char *arglist[MAXARGS+1];
buffersize = RCP_BUFSIZE;
for (i = 0; i < argc - 1; i++) {
if (!(src = colon(argv[i]))) {
(void) addargs(arglist, "cp",
iamrecursive ? "-r" : "", pflag ? "-p" : "",
zflag ? "-z" : "", argv[i], argv[argc - 1],
(char *)NULL);
if (susystem(_PATH_CP, arglist) == -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,
"host",
krb_realm,
bsd_context,
&auth_context,
&cred,
0,
0,
authopts,
1,
&kcmd_proto);
if (status) {
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"));
}
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
{
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;
}
if (read(rem, &resp, sizeof (resp)) != sizeof (resp))
lostconn();
if (resp != 0) {
acl_aclflag = 0;
(void) snprintf(bp, buffersize, "%s -f %s",
cmd_sunw, src);
(void) close(rem);
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();
if (resp != 0) {
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 *path, char **arglist)
{
int status, pid, w;
register void (*istat)(), (*qstat)();
int pfds[2];
char buf[BUFSIZ];
int cnt;
boolean_t seen_stderr_traffic;
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) {
(void) close(pfds[0]);
if (pfds[1] != STDERR_FILENO) {
(void) dup2(pfds[1], STDERR_FILENO);
(void) close(pfds[1]);
}
(void) execv(path, arglist);
_exit(127);
}
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) {
(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;
struct linger lingerbuf;
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;
}
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;
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;
}
if (aclflag | acl_aclflag) {
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;
}
}
if (cnt == 0)
break;
size -= cnt;
}
if (cnt < 0) {
error("rcp: %s: %s\n", name, strerror(errno));
} else if (cnt == 0 && size != 0) {
error("rcp: %s: unexpected end of file\n",
name);
lingerbuf.l_onoff = 1;
lingerbuf.l_linger = 0;
(void) setsockopt(rem, SOL_SOCKET, SO_LINGER,
&lingerbuf, sizeof (lingerbuf));
(void) close(rem);
rem = -1;
} 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));
if (aclflag) {
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:
return (0);
default:
*cp++ = resp;
case 1:
case 2:
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);
}
}
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') {
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) {
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) {
(void) recvacl(-1, exists, pflag);
}
goto bad;
}
if (aclflag | acl_aclflag) {
int dfd;
if ((dfd = open(np, O_RDONLY)) == -1)
goto bad;
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;
}
zopen(ofd, zflag && !exists);
if (exists && pflag)
(void) fchmod(ofd, mode);
(void) desrcpwrite(rem, "", 1);
if (aclflag | acl_aclflag) {
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;
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
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
"f1 f2; or:\n"),
gettext("\trcp [-r] [-p] [-a] [-x] "
#ifdef DEBUG
"[-D port] "
#endif
"[-k realm] [-PN / -PO] f1...fn d2\n"));
exit(1);
}
static off_t zbsize;
static off_t zlastseek;
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;
}
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);
}
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);
}
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) {
result |= ((int *)p)[0];
result |= ((int *)p)[1];
result |= ((int *)p)[2];
result |= ((int *)p)[3];
if (result)
return (result);
p += 4 * sizeof (int);
}
n += 4 * sizeof (int);
while (--n >= 0)
result |= *p++;
return (result);
}
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 (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;
}
}
(void) snprintf(buf, sizeof (buf), "%c%d\n", acltype, aclcnt);
(void) desrcpwrite(rem, buf, strlen(buf));
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);
}
(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);
}
static int
getaclinfo(int *cnt, int *acltype)
{
char buf[BUFSIZ];
char *cp;
char ch;
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);
}
static int
recvacl(int f, int exists, int preserve)
{
int aclcnt;
int aclsize;
int j;
char *tp;
char *acltext;
acl_t *aclp;
int acltype;
int min_entries;
int aclerror;
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) {
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));
free(acltext);
return (ACL_FAIL);
}
if (f != -1) {
if (facl_set(f, aclp) < 0) {
error("rcp: failed to set acl\n");
acl_free(aclp);
free(acltext);
return (ACL_FAIL);
}
}
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)
{
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 *));
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);
}
(void) krb5_copy_keyblock(bsd_context,
&new_creds->keyblock, &session_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);
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;
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);
}
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);
}
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));
}
static void
addargs(char **arglist, ...)
{
va_list ap;
int i = 0;
char *pm;
va_start(ap, arglist);
while (i < MAXARGS && (pm = va_arg(ap, char *)) != NULL)
if (strcmp(pm, ""))
arglist[i++] = pm;
arglist[i] = NULL;
va_end(ap);
}