/* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * Copyright (c) 1983 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that the above copyright notice and this paragraph are * duplicated in all such forms and that any documentation, * advertising materials, and other materials related to such * distribution and use acknowledge that the software was developed * by the University of California, Berkeley. The name of the * University may not be used to endorse or promote products derived * from this software without specific prior written permission. * */ #define _FILE_OFFSET_BITS 64 /* * rcp */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * It seems like Berkeley got these from pathnames.h? */ #define _PATH_RSH "/usr/bin/rsh" #define _PATH_CP "/usr/bin/cp" #define _PATH_BSHELL "/usr/bin/sh" #define ACL_FAIL 1 #define ACL_OK 0 #define RCP_BUFSIZE (64 * 1024) #define RCP_ACL "/usr/lib/sunw,rcp" /* see PSARC/1993/004/opinion */ typedef struct _buf { int cnt; char *buf; } BUF; static char *cmd_sunw; static struct passwd *pwd; static int errs; static int pflag; static uid_t userid; static int rem; static int zflag; static int iamremote; static int iamrecursive; static int targetshouldbedirectory; static int aclflag; static int acl_aclflag; static int retval = 0; static int portnumber = 0; static void lostconn(void); static char *search_char(unsigned char *, unsigned char); static char *removebrackets(char *); static char *colon(char *); static int response(void); static void usage(void); static void source(int, char **); static void sink(int, char **); static void toremote(char *, int, char **); static void tolocal(int, char **); static void verifydir(char *); static int okname(char *); static int susystem(char *); static void rsource(char *, struct stat *); static int sendacl(int); static int recvacl(int, int, int); static int zwrite(int, char *, int); static void zopen(int, int); static int zclose(int); static int notzero(char *, int); static BUF *allocbuf(BUF *, int, int); static void error(char *fmt, ...); /* * As a 32 bit application, we can only transfer (2gb - 1) i.e 0x7FFFFFFF * bytes of data. We would like the size to be aligned to the nearest * MAXBOFFSET (8192) boundary for optimal performance. */ #define SENDFILE_SIZE 0x7FFFE000 #include #include #include #include #define NULLBUF (BUF *) 0 static int sock; static char *cmd, *cmd_orig, *cmd_sunw_orig; static char *krb_realm = NULL; static char *krb_cache = NULL; static char *krb_config = NULL; static char des_inbuf[2 * RCP_BUFSIZE]; /* needs to be > largest read size */ static char des_outbuf[2 * RCP_BUFSIZE]; /* needs to be > largest write size */ static krb5_data desinbuf, desoutbuf; static krb5_encrypt_block eblock; /* eblock for encrypt/decrypt */ static krb5_keyblock *session_key; /* static key for session */ static krb5_context bsd_context; static krb5_auth_context auth_context; static krb5_flags authopts; static krb5_error_code status; static void try_normal_rcp(int, char **); static int init_service(int); static char **save_argv(int, char **); static void answer_auth(char *, char *); static int desrcpwrite(int, char *, int); static int desrcpread(int, char *, int); /* * Not sure why these two don't have their own header file declarations, but * lint complains about absent declarations so place some here. Sigh. */ extern errcode_t profile_get_options_boolean(profile_t, char **, profile_options_boolean *); extern errcode_t profile_get_options_string(profile_t, char **, profile_option_strings *); static int krb5auth_flag = 0; /* Flag set, when KERBEROS is enabled */ static int encrypt_flag = 0; /* Flag set, when encryption is enabled */ static int encrypt_done = 0; /* Flag set, if "-x" is specified */ static enum kcmd_proto kcmd_proto = KCMD_NEW_PROTOCOL; /* Flag set, if -PN / -PO is specified */ static boolean_t rcmdoption_done = B_FALSE; static profile_options_boolean option[] = { { "encrypt", &encrypt_flag, 0 }, { NULL, NULL, 0 } }; static char *rcmdproto = NULL; static profile_option_strings rcmdversion[] = { { "rcmd_protocol", &rcmdproto, 0 }, { NULL, NULL, 0 } }; static char *realmdef[] = { "realms", NULL, "rcp", NULL }; static char *appdef[] = { "appdefaults", "rcp", NULL }; static char **prev_argv; static int prev_argc; int main(int argc, char *argv[]) { int ch, fflag, tflag; char *targ; size_t cmdsiz; (void) setlocale(LC_ALL, ""); if (strcmp(argv[0], RCP_ACL) == 0) aclflag = 1; if (!(pwd = getpwuid(userid = getuid()))) { (void) fprintf(stderr, "rcp: unknown user %d.\n", (uint_t)userid); return (1); } fflag = tflag = 0; while ((ch = getopt(argc, argv, "axdfprtz:D:k:P:Z")) != EOF) { switch (ch) { case 'd': targetshouldbedirectory = 1; break; case 'f': /* "from" */ fflag = 1; if (aclflag | acl_aclflag) /* ok response */ (void) desrcpwrite(rem, "", 1); break; case 'p': /* preserve access/mod times */ ++pflag; break; case 'r': ++iamrecursive; break; case 't': /* "to" */ tflag = 1; break; case 'Z': acl_aclflag++; break; case 'x': if (!krb5_privacy_allowed()) { (void) fprintf(stderr, gettext("rcp: " "Encryption not supported.\n")); return (1); } encrypt_flag++; krb5auth_flag++; encrypt_done++; break; case 'k': if ((krb_realm = (char *)strdup(optarg)) == NULL) { (void) fprintf(stderr, gettext("rcp:" " Cannot malloc.\n")); return (1); } krb5auth_flag++; break; case 'P': if (strncmp(optarg, "O", 1) == 0) { if (rcmdoption_done == B_TRUE) { (void) fprintf(stderr, gettext("rcp: " "Only one of -PN and -PO " "allowed.\n")); usage(); } kcmd_proto = KCMD_OLD_PROTOCOL; rcmdoption_done = B_TRUE; } else if (strncmp(optarg, "N", 1) == 0) { if (rcmdoption_done == B_TRUE) { (void) fprintf(stderr, gettext("rcp: " "Only one of -PN and -PO " "allowed.\n")); usage(); } kcmd_proto = KCMD_NEW_PROTOCOL; rcmdoption_done = B_TRUE; } else { usage(); } krb5auth_flag++; break; case 'a': krb5auth_flag++; break; #ifdef DEBUG case 'D': portnumber = htons(atoi(optarg)); krb5auth_flag++; break; #endif /* DEBUG */ case '?': default: usage(); } } argc -= optind; argv += optind; if (krb5auth_flag > 0) { status = krb5_init_context(&bsd_context); if (status) { com_err("rcp", status, gettext("while initializing krb5")); return (1); } /* * Set up buffers for desread and deswrite. */ desinbuf.data = des_inbuf; desoutbuf.data = des_outbuf; desinbuf.length = sizeof (des_inbuf); desoutbuf.length = sizeof (des_outbuf); } if (fflag || tflag) if (encrypt_flag > 0) (void) answer_auth(krb_config, krb_cache); if (fflag) { iamremote = 1; (void) response(); (void) setuid(userid); source(argc, argv); return (errs); } if (tflag) { iamremote = 1; (void) setuid(userid); sink(argc, argv); return (errs); } if (argc < 2) usage(); /* This will make "rcmd_af()" magically get the proper privilege */ if (__init_suid_priv(0, PRIV_NET_PRIVADDR, (char *)NULL) == -1) { (void) fprintf(stderr, "rcp: must be set-uid root\n"); exit(1); } if (krb5auth_flag > 0) { /* * Get our local realm to look up local realm options. */ status = krb5_get_default_realm(bsd_context, &realmdef[1]); if (status) { com_err("rcp", status, gettext("while getting default realm")); return (1); } /* * See if encryption should be done for this realm */ profile_get_options_boolean(bsd_context->profile, realmdef, option); /* * Check the appdefaults section */ profile_get_options_boolean(bsd_context->profile, appdef, option); profile_get_options_string(bsd_context->profile, appdef, rcmdversion); if ((encrypt_done > 0) || (encrypt_flag > 0)) { if (krb5_privacy_allowed() == TRUE) { encrypt_flag++; } else { (void) fprintf(stderr, gettext("rcp: Encryption" " not supported.\n")); return (1); } } if ((rcmdoption_done == B_FALSE) && (rcmdproto != NULL)) { if (strncmp(rcmdproto, "rcmdv2", 6) == 0) { kcmd_proto = KCMD_NEW_PROTOCOL; } else if (strncmp(rcmdproto, "rcmdv1", 6) == 0) { kcmd_proto = KCMD_OLD_PROTOCOL; } else { (void) fprintf(stderr, gettext("Unrecognized " "KCMD protocol (%s)"), rcmdproto); return (1); } } } if (argc > 2) targetshouldbedirectory = 1; rem = -1; if (portnumber == 0) { if (krb5auth_flag > 0) { retval = init_service(krb5auth_flag); if (!retval) { /* * Connecting to the kshell service failed, * fallback to normal rcp & reset KRB5 flags. */ krb5auth_flag = encrypt_flag = 0; encrypt_done = 0; (void) init_service(krb5auth_flag); } } else (void) init_service(krb5auth_flag); } #ifdef DEBUG if (retval || krb5auth_flag) { (void) fprintf(stderr, gettext("Kerberized rcp session, " "port %d in use "), portnumber); if (kcmd_proto == KCMD_OLD_PROTOCOL) (void) fprintf(stderr, gettext("[kcmd ver.1]\n")); else (void) fprintf(stderr, gettext("[kcmd ver.2]\n")); } else { (void) fprintf(stderr, gettext("Normal rcp session, port %d " "in use.\n"), portnumber); } #endif /* DEBUG */ if (krb5auth_flag > 0) { /* * We calculate here a buffer size that can be used in the * allocation of the three buffers cmd, cmd_orig and * cmd_sunw_orig that are used to hold different incantations * of rcp. */ cmdsiz = MAX(sizeof ("-x rcp -r -p -d -k ") + strlen(krb_realm != NULL ? krb_realm : ""), sizeof (RCP_ACL " -r -p -z -d")); if (((cmd = (char *)malloc(cmdsiz)) == NULL) || ((cmd_sunw_orig = (char *)malloc(cmdsiz)) == NULL) || ((cmd_orig = (char *)malloc(cmdsiz)) == NULL)) { (void) fprintf(stderr, gettext("rcp: Cannot " "malloc.\n")); return (1); } (void) snprintf(cmd, cmdsiz, "%srcp %s%s%s%s%s", encrypt_flag ? "-x " : "", iamrecursive ? " -r" : "", pflag ? " -p" : "", targetshouldbedirectory ? " -d" : "", krb_realm != NULL ? " -k " : "", krb_realm != NULL ? krb_realm : ""); /* * We would use cmd-orig as the 'cmd-buffer' if kerberized * rcp fails, in which case we fallback to normal rcp. We also * save argc & argv for the same purpose */ (void) snprintf(cmd_orig, cmdsiz, "rcp%s%s%s%s", iamrecursive ? " -r" : "", pflag ? " -p" : "", zflag ? " -z" : "", targetshouldbedirectory ? " -d" : ""); (void) snprintf(cmd_sunw_orig, cmdsiz, "%s%s%s%s%s", RCP_ACL, iamrecursive ? " -r" : "", pflag ? " -p" : "", zflag ? " -z" : "", targetshouldbedirectory ? " -d" : ""); prev_argc = argc; prev_argv = save_argv(argc, argv); } else { cmdsiz = sizeof ("rcp -r -p -z -d"); if (((cmd = (char *)malloc(cmdsiz)) == NULL)) { (void) fprintf(stderr, gettext("rcp: Cannot " "malloc.\n")); return (1); } (void) snprintf(cmd, cmdsiz, "rcp%s%s%s%s", iamrecursive ? " -r" : "", pflag ? " -p" : "", zflag ? " -z" : "", targetshouldbedirectory ? " -d" : ""); } cmdsiz = sizeof (RCP_ACL " -r -p -z -d"); if ((cmd_sunw = (char *)malloc(cmdsiz)) == NULL) { (void) fprintf(stderr, gettext("rcp: Cannot malloc.\n")); return (1); } (void) snprintf(cmd_sunw, cmdsiz, "%s%s%s%s%s", RCP_ACL, iamrecursive ? " -r" : "", pflag ? " -p" : "", zflag ? " -z" : "", targetshouldbedirectory ? " -d" : ""); (void) signal(SIGPIPE, (void (*)(int))lostconn); if (targ = colon(argv[argc - 1])) toremote(targ, argc, argv); else { tolocal(argc, argv); if (targetshouldbedirectory) verifydir(argv[argc - 1]); } return (errs > 0 ? EXIT_FAILURE : EXIT_SUCCESS); } static void toremote(char *targ, int argc, char *argv[]) { int i; char *host, *src, *suser, *thost, *tuser; char resp; size_t buffersize; char bp[RCP_BUFSIZE]; krb5_creds *cred; buffersize = RCP_BUFSIZE; *targ++ = 0; if (*targ == 0) targ = "."; if (thost = search_char((unsigned char *)argv[argc - 1], '@')) { *thost++ = 0; tuser = argv[argc - 1]; if (*tuser == '\0') tuser = NULL; else if (!okname(tuser)) exit(1); } else { thost = argv[argc - 1]; tuser = NULL; } thost = removebrackets(thost); for (i = 0; i < argc - 1; i++) { src = colon(argv[i]); if (src) { /* remote to remote */ *src++ = 0; if (*src == 0) src = "."; host = search_char((unsigned char *)argv[i], '@'); if (host) { *host++ = 0; host = removebrackets(host); suser = argv[i]; if (*suser == '\0') { suser = pwd->pw_name; } else if (!okname(suser)) { errs++; continue; } (void) snprintf(bp, buffersize, "%s %s -l %s -n %s %s '%s%s%s:%s'", _PATH_RSH, host, suser, cmd, src, tuser ? tuser : "", tuser ? "@" : "", thost, targ); } else { host = removebrackets(argv[i]); (void) snprintf(bp, buffersize, "%s %s -n %s %s '%s%s%s:%s'", _PATH_RSH, host, cmd, src, tuser ? tuser : "", tuser ? "@" : "", thost, targ); } if (susystem(bp) == -1) errs++; } else { /* local to remote */ if (rem == -1) { host = thost; if (krb5auth_flag > 0) { (void) snprintf(bp, buffersize, "%s -t %s", cmd, targ); authopts = AP_OPTS_MUTUAL_REQUIRED; status = kcmd(&sock, &host, portnumber, pwd->pw_name, tuser ? tuser : pwd->pw_name, bp, 0, "host", krb_realm, bsd_context, &auth_context, &cred, 0, /* No seq # */ 0, /* No server seq # */ authopts, 0, /* Not any port # */ &kcmd_proto); if (status) { /* * If new protocol requested, we dont * fallback to less secure ones. */ if (kcmd_proto == KCMD_NEW_PROTOCOL) { (void) fprintf(stderr, gettext("rcp: kcmdv2 " "to host %s failed - %s" "\nFallback to normal " "rcp denied."), host, error_message(status)); exit(1); } if (status != -1) { (void) fprintf(stderr, gettext("rcp: kcmd to host " "%s failed - %s,\n" "trying normal rcp...\n\n"), host, error_message(status)); } else { (void) fprintf(stderr, gettext("trying normal" " rcp...\n")); } /* * kcmd() failed, so we have to * fallback to normal rcp */ try_normal_rcp(prev_argc, prev_argv); } else { rem = sock; session_key = &cred->keyblock; if (kcmd_proto == KCMD_NEW_PROTOCOL) { /* CSTYLED */ status = krb5_auth_con_getlocalsubkey(bsd_context, auth_context, &session_key); if (status) { com_err("rcp", status, "determining " "subkey for " "session"); exit(1); } if (!session_key) { com_err("rcp", 0, "no subkey " "negotiated for" " connection"); exit(1); } } eblock.crypto_entry = session_key->enctype; eblock.key = (krb5_keyblock *)session_key; init_encrypt(encrypt_flag, bsd_context, kcmd_proto, &desinbuf, &desoutbuf, CLIENT, &eblock); if (encrypt_flag > 0) { char *s = gettext("This rcp " "session is using " "encryption for all " "data transmissions." "\r\n"); (void) write(2, s, strlen(s)); } } if (response() < 0) exit(1); } else { /* * ACL support: try to find out if the remote * site is running acl cognizant version of * rcp. A special binary name is used for this * purpose. */ aclflag = 1; acl_aclflag = 1; /* * First see if the remote side will support * both aclent_t and ace_t acl's? */ (void) snprintf(bp, buffersize, "%s -tZ %s", cmd_sunw, targ); rem = rcmd_af(&host, portnumber, pwd->pw_name, tuser ? tuser : pwd->pw_name, bp, 0, AF_INET6); if (rem < 0) exit(1); /* * This is similar to routine response(). * If response is not ok, treat the other * side as non-acl rcp. */ if (read(rem, &resp, sizeof (resp)) != sizeof (resp)) lostconn(); if (resp != 0) { acl_aclflag = 0; (void) snprintf(bp, buffersize, "%s -t %s", cmd_sunw, targ); (void) close(rem); host = thost; rem = rcmd_af(&host, portnumber, pwd->pw_name, tuser ? tuser : pwd->pw_name, bp, 0, AF_INET6); if (rem < 0) exit(1); if (read(rem, &resp, sizeof (resp)) != sizeof (resp)) lostconn(); if (resp != 0) { /* * Not OK: * The other side is running * non-acl rcp. Try again with * normal stuff */ aclflag = 0; (void) snprintf(bp, buffersize, "%s -t %s", cmd, targ); (void) close(rem); host = thost; rem = rcmd_af(&host, portnumber, pwd->pw_name, tuser ? tuser : pwd->pw_name, bp, 0, AF_INET6); if (rem < 0) exit(1); if (response() < 0) exit(1); } } /* everything should be fine now */ (void) setuid(userid); } } source(1, argv + i); } } } static void tolocal(int argc, char *argv[]) { int i; char *host, *src, *suser, *lhost; char resp; size_t buffersize; char bp[RCP_BUFSIZE]; krb5_creds *cred; buffersize = RCP_BUFSIZE; for (i = 0; i < argc - 1; i++) { if (!(src = colon(argv[i]))) { /* local to local */ (void) snprintf(bp, buffersize, "%s%s%s%s %s %s", _PATH_CP, iamrecursive ? " -r" : "", pflag ? " -p" : "", zflag ? " -z" : "", argv[i], argv[argc - 1]); if (susystem(bp) == -1) errs++; continue; } *src++ = 0; if (*src == 0) src = "."; host = search_char((unsigned char *)argv[i], '@'); if (host) { *host++ = 0; suser = argv[i]; if (*suser == '\0') { suser = pwd->pw_name; } else if (!okname(suser)) { errs++; continue; } } else { host = argv[i]; suser = pwd->pw_name; } host = removebrackets(host); lhost = host; if (krb5auth_flag > 0) { (void) snprintf(bp, buffersize, "%s -f %s", cmd, src); authopts = AP_OPTS_MUTUAL_REQUIRED; status = kcmd(&sock, &host, portnumber, pwd->pw_name, suser, bp, 0, /* &rfd2 */ "host", krb_realm, bsd_context, &auth_context, &cred, 0, /* No seq # */ 0, /* No server seq # */ authopts, 1, /* Not any port # */ &kcmd_proto); if (status) { /* * If new protocol requested, we dont * fallback to less secure ones. */ if (kcmd_proto == KCMD_NEW_PROTOCOL) { (void) fprintf(stderr, gettext("rcp: kcmdv2 " "to host %s failed - %s\n" "Fallback to normal rcp denied."), host, error_message(status)); exit(1); } if (status != -1) { (void) fprintf(stderr, gettext("rcp: kcmd " "to host %s failed - %s,\n" "trying normal rcp...\n\n"), host, error_message(status)); } else { (void) fprintf(stderr, gettext("trying normal rcp...\n")); } /* * kcmd() failed, so we have to * fallback to normal rcp */ try_normal_rcp(prev_argc, prev_argv); } else { rem = sock; session_key = &cred->keyblock; if (kcmd_proto == KCMD_NEW_PROTOCOL) { status = krb5_auth_con_getlocalsubkey( bsd_context, auth_context, &session_key); if (status) { com_err("rcp", status, "determining " "subkey for session"); exit(1); } if (!session_key) { com_err("rcp", 0, "no subkey negotiated" " for connection"); exit(1); } } eblock.crypto_entry = session_key->enctype; eblock.key = (krb5_keyblock *)session_key; init_encrypt(encrypt_flag, bsd_context, kcmd_proto, &desinbuf, &desoutbuf, CLIENT, &eblock); if (encrypt_flag > 0) { char *s = gettext("This rcp " "session is using DES " "encryption for all " "data transmissions." "\r\n"); (void) write(2, s, strlen(s)); } } } else { /* * ACL support: try to find out if the remote site is * running acl cognizant version of rcp. */ aclflag = 1; acl_aclflag = 1; (void) snprintf(bp, buffersize, "%s -Zf %s", cmd_sunw, src); rem = rcmd_af(&host, portnumber, pwd->pw_name, suser, bp, 0, AF_INET6); if (rem < 0) { ++errs; continue; } /* * The remote system is supposed to send an ok response. * If there are any data other than "ok", it must be error * messages from the remote system. We can assume the * remote system is running non-acl version rcp. */ if (read(rem, &resp, sizeof (resp)) != sizeof (resp)) lostconn(); if (resp != 0) { /* * Try again without ace_acl support */ acl_aclflag = 0; (void) snprintf(bp, buffersize, "%s -f %s", cmd_sunw, src); rem = rcmd_af(&host, portnumber, pwd->pw_name, suser, bp, 0, AF_INET6); if (rem < 0) { ++errs; continue; } if (read(rem, &resp, sizeof (resp)) != sizeof (resp)) lostconn(); /* * NOT ok: * The other side is running non-acl rcp. * Try again with normal stuff */ aclflag = 0; (void) snprintf(bp, buffersize, "%s -f %s", cmd, src); (void) close(rem); host = lhost; rem = rcmd_af(&host, portnumber, pwd->pw_name, suser, bp, 0, AF_INET6); if (rem < 0) { ++errs; continue; } } } sink(1, argv + argc - 1); (void) close(rem); rem = -1; } } static void verifydir(char *cp) { struct stat stb; if (stat(cp, &stb) >= 0) { if ((stb.st_mode & S_IFMT) == S_IFDIR) return; errno = ENOTDIR; } error("rcp: %s: %s.\n", cp, strerror(errno)); exit(1); } static char * colon(char *cp) { boolean_t is_bracket_open = B_FALSE; for (; *cp; ++cp) { if (*cp == '[') is_bracket_open = B_TRUE; else if (*cp == ']') is_bracket_open = B_FALSE; else if (*cp == ':' && !is_bracket_open) return (cp); else if (*cp == '/') return (0); } return (0); } static int okname(char *cp0) { register char *cp = cp0; register int c; do { c = *cp; if (c & 0200) goto bad; if (!isalpha(c) && !isdigit(c) && c != '_' && c != '-') goto bad; } while (*++cp); return (1); bad: (void) fprintf(stderr, "rcp: invalid user name %s\n", cp0); return (0); } static char * removebrackets(char *str) { char *newstr = str; if ((str[0] == '[') && (str[strlen(str) - 1] == ']')) { newstr = str + 1; str[strlen(str) - 1] = '\0'; } return (newstr); } static int susystem(char *s) { int status, pid, w; register void (*istat)(), (*qstat)(); int pfds[2]; char buf[BUFSIZ]; int cnt; boolean_t seen_stderr_traffic; /* * Due to the fact that rcp uses rsh to copy between 2 remote * machines, rsh doesn't return the exit status of the remote * command, and we can't modify the rcmd protocol used by rsh * (for interoperability reasons) we use the hack of using any * output on stderr as indication that an error occurred and * that we should return a non-zero error code. */ if (pipe(pfds) == -1) { (void) fprintf(stderr, "Couldn't create pipe: %s\n", strerror(errno)); return (-1); } if ((pid = vfork()) < 0) { (void) close(pfds[0]); (void) close(pfds[1]); (void) fprintf(stderr, "Couldn't fork child process: %s\n", strerror(errno)); return (-1); } else if (pid == 0) { /* * Child. */ (void) close(pfds[0]); /* * Send stderr messages down the pipe so that we can detect * them in the parent process. */ if (pfds[1] != STDERR_FILENO) { (void) dup2(pfds[1], STDERR_FILENO); (void) close(pfds[1]); } /* * This shell does not inherit the additional privilege * we have in our Permitted set. */ (void) execl(_PATH_BSHELL, "sh", "-c", s, (char *)0); _exit(127); } /* * Parent. */ istat = signal(SIGINT, SIG_IGN); qstat = signal(SIGQUIT, SIG_IGN); (void) close(pfds[1]); seen_stderr_traffic = B_FALSE; while ((cnt = read(pfds[0], buf, sizeof (buf))) > 0) { /* * If any data is read from the pipe the child process * has output something on stderr so we set the boolean * 'seen_stderr_traffic' to true, which will cause the * function to return -1. */ (void) write(STDERR_FILENO, buf, cnt); seen_stderr_traffic = B_TRUE; } (void) close(pfds[0]); while ((w = wait(&status)) != pid && w != -1) ; if (w == -1) status = -1; (void) signal(SIGINT, istat); (void) signal(SIGQUIT, qstat); return (seen_stderr_traffic ? -1 : status); } static void source(int argc, char *argv[]) { struct stat stb; static BUF buffer; BUF *bp; int x, readerr, f, amt; char *last, *name, buf[RCP_BUFSIZE]; off_t off, size, i; ssize_t cnt; for (x = 0; x < argc; x++) { name = argv[x]; if ((f = open(name, O_RDONLY, 0)) < 0) { error("rcp: %s: %s\n", name, strerror(errno)); continue; } if (fstat(f, &stb) < 0) goto notreg; switch (stb.st_mode&S_IFMT) { case S_IFREG: break; case S_IFDIR: if (iamrecursive) { (void) close(f); rsource(name, &stb); continue; } /* FALLTHROUGH */ default: notreg: (void) close(f); error("rcp: %s: not a plain file\n", name); continue; } last = rindex(name, '/'); if (last == 0) last = name; else last++; if (pflag) { time_t mtime, atime; time_t now; /* * Make it compatible with possible future * versions expecting microseconds. */ mtime = stb.st_mtime; atime = stb.st_atime; if ((mtime < 0) || (atime < 0)) { now = time(NULL); if (mtime < 0) { mtime = now; error("negative modification time on " "%s; not preserving\n", name); } if (atime < 0) { atime = now; error("negative access time on " "%s; not preserving\n", name); } } (void) snprintf(buf, sizeof (buf), "T%ld 0 %ld 0\n", mtime, atime); (void) desrcpwrite(rem, buf, strlen(buf)); if (response() < 0) { (void) close(f); continue; } } (void) snprintf(buf, sizeof (buf), "C%04o %lld %s\n", (uint_t)(stb.st_mode & 07777), (longlong_t)stb.st_size, last); (void) desrcpwrite(rem, buf, strlen(buf)); if (response() < 0) { (void) close(f); continue; } /* ACL support: send */ if (aclflag | acl_aclflag) { /* get acl from f and send it over */ if (sendacl(f) == ACL_FAIL) { (void) close(f); continue; } } if ((krb5auth_flag > 0) || (iamremote == 1)) { bp = allocbuf(&buffer, f, RCP_BUFSIZE); if (bp == NULLBUF) { (void) close(f); continue; } readerr = 0; for (i = 0; i < stb.st_size; i += bp->cnt) { amt = bp->cnt; if (i + amt > stb.st_size) amt = stb.st_size - i; if (readerr == 0 && read(f, bp->buf, amt) != amt) readerr = errno; (void) desrcpwrite(rem, bp->buf, amt); } (void) close(f); if (readerr == 0) (void) desrcpwrite(rem, "", 1); else error("rcp: %s: %s\n", name, error_message(readerr)); } else { cnt = off = 0; size = stb.st_size; while (size != 0) { amt = MIN(size, SENDFILE_SIZE); cnt = sendfile(rem, f, &off, amt); if (cnt == -1) 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 "); do { if (desrcpread(rem, &ch, sizeof (ch)) != sizeof (ch)) SCREWUP("lost connection"); *cp++ = ch; } while (cp < &buf[RCP_BUFSIZE - 1] && ch != '\n'); *cp = 0; if (buf[0] == '\01' || buf[0] == '\02') { if (iamremote == 0) (void) write(STDERR_FILENO, buf + 1, strlen(buf + 1)); if (buf[0] == '\02') exit(1); errs++; continue; } if (buf[0] == 'E') { (void) desrcpwrite(rem, "", 1); if (namebuf != NULL) free(namebuf); return; } if (ch == '\n') *--cp = 0; cp = buf; if (*cp == 'T') { setimes++; cp++; mtime.tv_sec = strtol(cp, &cp, 0); if (*cp++ != ' ') SCREWUP("mtime.sec not delimited"); mtime.tv_usec = strtol(cp, &cp, 0); if (*cp++ != ' ') SCREWUP("mtime.usec not delimited"); atime.tv_sec = strtol(cp, &cp, 0); if (*cp++ != ' ') SCREWUP("atime.sec not delimited"); atime.tv_usec = strtol(cp, &cp, 0); if (*cp++ != '\0') SCREWUP("atime.usec not delimited"); (void) desrcpwrite(rem, "", 1); continue; } if (*cp != 'C' && *cp != 'D') { /* * Check for the case "rcp remote:foo\* local:bar". * In this case, the line "No match." can be returned * by the shell before the rcp command on the remote is * executed so the ^Aerror_message convention isn't * followed. */ if (first) { error("%s\n", cp); exit(1); } SCREWUP("expected control record"); } mode = 0; for (++cp; cp < buf + 5; cp++) { if (*cp < '0' || *cp > '7') SCREWUP("bad mode"); mode = (mode << 3) | (*cp - '0'); } if (*cp++ != ' ') SCREWUP("mode not delimited"); size = 0; while (isdigit(*cp)) size = size * 10 + (*cp++ - '0'); if (*cp++ != ' ') SCREWUP("size not delimited"); if (targisdir) { need = strlen(targ) + sizeof ("/") + strlen(cp); if (need > namebuf_sz) { if ((namebuf = realloc(namebuf, need)) == NULL) { error("rcp: out of memory\n"); exit(1); } namebuf_sz = need; } (void) snprintf(namebuf, need, "%s%s%s", targ, *targ ? "/" : "", cp); np = namebuf; } else { np = targ; } exists = stat(np, &stb) == 0; if (buf[0] == 'D') { if (exists) { if ((stb.st_mode&S_IFMT) != S_IFDIR) { if (aclflag | acl_aclflag) { /* * consume acl in the pipe * fd = -1 to indicate the * special case */ if (recvacl(-1, exists, pflag) == ACL_FAIL) { goto bad; } } errno = ENOTDIR; goto bad; } if (pflag) (void) chmod(np, mode); } else if (mkdir(np, mode) < 0) { if (aclflag) { /* consume acl in the pipe */ (void) recvacl(-1, exists, pflag); } goto bad; } /* acl support for directories */ if (aclflag | acl_aclflag) { int dfd; if ((dfd = open(np, O_RDONLY)) == -1) goto bad; /* get acl and set it to ofd */ if (recvacl(dfd, exists, pflag) == ACL_FAIL) { (void) close(dfd); if (!exists) (void) rmdir(np); goto bad; } (void) close(dfd); } vect[0] = np; sink(1, vect); if (setimes) { setimes = 0; if (utimes(np, tv) < 0) error("rcp: can't set times on %s: %s\n", np, strerror(errno)); } continue; } if ((ofd = open(np, O_WRONLY|O_CREAT, mode)) < 0) { bad: error("rcp: %s: %s\n", np, strerror(errno)); continue; } /* * If the output file exists we have to force zflag off * to avoid erroneously seeking past old data. */ zopen(ofd, zflag && !exists); if (exists && pflag) (void) fchmod(ofd, mode); (void) desrcpwrite(rem, "", 1); /* * ACL support: receiving */ if (aclflag | acl_aclflag) { /* get acl and set it to ofd */ if (recvacl(ofd, exists, pflag) == ACL_FAIL) { (void) close(ofd); if (!exists) (void) unlink(np); continue; } } if ((bp = allocbuf(&buffer, ofd, RCP_BUFSIZE)) == 0) { (void) close(ofd); continue; } cp = bp->buf; count = 0; wrerr = 0; for (i = 0; i < size; i += RCP_BUFSIZE) { amt = RCP_BUFSIZE; if (i + amt > size) amt = size - i; count += amt; do { j = desrcpread(rem, cp, amt); if (j <= 0) { int sverrno = errno; /* * Connection to supplier lost. * Truncate file to correspond * to amount already transferred. * * Note that we must call ftruncate() * before any call to error() (which * might result in a SIGPIPE and * sudden death before we have a chance * to correct the file's size). */ size = lseek(ofd, 0, SEEK_CUR); if ((ftruncate(ofd, size) == -1) && (errno != EINVAL) && (errno != EACCES)) #define TRUNCERR "rcp: can't truncate %s: %s\n" error(TRUNCERR, np, strerror(errno)); error("rcp: %s\n", j ? strerror(sverrno) : "dropped connection"); (void) close(ofd); exit(1); } amt -= j; cp += j; } while (amt > 0); if (count == bp->cnt) { cp = bp->buf; if (wrerr == 0 && zwrite(ofd, cp, count) < 0) wrerr++; count = 0; } } if (count != 0 && wrerr == 0 && zwrite(ofd, bp->buf, count) < 0) wrerr++; if (zclose(ofd) < 0) wrerr++; if ((ftruncate(ofd, size) == -1) && (errno != EINVAL) && (errno != EACCES)) { error(TRUNCERR, np, strerror(errno)); } (void) close(ofd); (void) response(); if (setimes) { setimes = 0; if (utimes(np, tv) < 0) error("rcp: can't set times on %s: %s\n", np, strerror(errno)); } if (wrerr) error("rcp: %s: %s\n", np, strerror(errno)); else (void) desrcpwrite(rem, "", 1); } screwup: error("rcp: protocol screwup: %s\n", why); exit(1); } #ifndef roundup #define roundup(x, y) ((((x)+((y)-1))/(y))*(y)) #endif /* !roundup */ static BUF * allocbuf(BUF *bp, int fd, int blksize) { struct stat stb; int size; if (fstat(fd, &stb) < 0) { error("rcp: fstat: %s\n", strerror(errno)); return (0); } size = roundup(stb.st_blksize, blksize); if (size == 0) size = blksize; if (bp->cnt < size) { if (bp->buf != 0) free(bp->buf); bp->buf = (char *)malloc((uint_t)size); if (!bp->buf) { error("rcp: malloc: out of memory\n"); return (0); } } bp->cnt = size; return (bp); } static void usage(void) { (void) fprintf(stderr, "%s: \t%s\t%s", gettext("Usage"), gettext("\trcp [-p] [-a] [-x] [-k realm] [-PN / -PO] " #ifdef DEBUG "[-D port] " #endif /* DEBUG */ "f1 f2; or:\n"), gettext("\trcp [-r] [-p] [-a] [-x] " #ifdef DEBUG "[-D port] " #endif /* DEBUG */ "[-k realm] [-PN / -PO] f1...fn d2\n")); exit(1); } /* * sparse file support */ static off_t zbsize; static off_t zlastseek; /* is it ok to try to create holes? */ static void zopen(int fd, int flag) { struct stat st; zbsize = 0; zlastseek = 0; if (flag && fstat(fd, &st) == 0 && (st.st_mode & S_IFMT) == S_IFREG) zbsize = st.st_blksize; } /* write and/or seek */ static int zwrite(int fd, char *buf, int nbytes) { off_t block = zbsize ? zbsize : nbytes; do { if (block > nbytes) block = nbytes; nbytes -= block; if (!zbsize || notzero(buf, block)) { register int n, count = block; do { if ((n = write(fd, buf, count)) < 0) return (-1); buf += n; } while ((count -= n) > 0); zlastseek = 0; } else { if (lseek(fd, (off_t)block, SEEK_CUR) < 0) return (-1); buf += block; zlastseek = 1; } } while (nbytes > 0); return (0); } /* write last byte of file if necessary */ static int zclose(int fd) { zbsize = 0; if (zlastseek && (lseek(fd, (off_t)-1, SEEK_CUR) < 0 || zwrite(fd, "", 1) < 0)) return (-1); else return (0); } /* return true if buffer is not all zeros */ static int notzero(char *p, int n) { register int result = 0; while ((int)p & 3 && --n >= 0) result |= *p++; while ((n -= 4 * sizeof (int)) >= 0) { /* LINTED */ result |= ((int *)p)[0]; /* LINTED */ result |= ((int *)p)[1]; /* LINTED */ result |= ((int *)p)[2]; /* LINTED */ result |= ((int *)p)[3]; if (result) return (result); p += 4 * sizeof (int); } n += 4 * sizeof (int); while (--n >= 0) result |= *p++; return (result); } /* * New functions to support ACLs */ /* * Get acl from f and send it over. * ACL record includes acl entry count, acl text length, and acl text. */ static int sendacl(int f) { int aclcnt; char *acltext; char buf[BUFSIZ]; acl_t *aclp; char acltype; int aclerror; int trivial; aclerror = facl_get(f, ACL_NO_TRIVIAL, &aclp); if (aclerror != 0) { error("can't retrieve ACL: %s \n", acl_strerror(aclerror)); return (ACL_FAIL); } /* * if acl type is not ACLENT_T and were operating in acl_aclflag == 0 * then don't do the malloc and facl(fd, getcntcmd,...); * since the remote side doesn't support alternate style ACL's. */ if (aclp && (acl_type(aclp) != ACLENT_T) && (acl_aclflag == 0)) { aclcnt = MIN_ACL_ENTRIES; acltype = 'A'; trivial = ACL_IS_TRIVIAL; } else { aclcnt = (aclp != NULL) ? acl_cnt(aclp) : 0; if (aclp) { acltype = (acl_type(aclp) != ACLENT_T) ? 'Z' : 'A'; aclcnt = acl_cnt(aclp); trivial = (acl_flags(aclp) & ACL_IS_TRIVIAL); } else { acltype = 'A'; aclcnt = MIN_ACL_ENTRIES; trivial = ACL_IS_TRIVIAL; } } /* send the acl count over */ (void) snprintf(buf, sizeof (buf), "%c%d\n", acltype, aclcnt); (void) desrcpwrite(rem, buf, strlen(buf)); /* * only send acl when we have an aclp, which would * imply its not trivial. */ if (aclp && (trivial != ACL_IS_TRIVIAL)) { acltext = acl_totext(aclp, 0); if (acltext == NULL) { error("rcp: failed to convert to text\n"); acl_free(aclp); return (ACL_FAIL); } /* send ACLs over: send the length first */ (void) snprintf(buf, sizeof (buf), "%c%d\n", acltype, strlen(acltext)); (void) desrcpwrite(rem, buf, strlen(buf)); (void) desrcpwrite(rem, acltext, strlen(acltext)); free(acltext); if (response() < 0) { acl_free(aclp); return (ACL_FAIL); } } if (aclp) acl_free(aclp); return (ACL_OK); } /* * Use this routine to get acl entry count and acl text size (in bytes) */ static int getaclinfo(int *cnt, int *acltype) { char buf[BUFSIZ]; char *cp; char ch; /* get acl count */ cp = buf; if (desrcpread(rem, cp, 1) <= 0) return (ACL_FAIL); switch (*cp++) { case 'A': *acltype = 0; break; case 'Z': *acltype = 1; break; default: error("rcp: expect an ACL record, but got %c\n", *cp); return (ACL_FAIL); } do { if (desrcpread(rem, &ch, sizeof (ch)) != sizeof (ch)) { error("rcp: lost connection ..\n"); return (ACL_FAIL); } *cp++ = ch; } while (cp < &buf[BUFSIZ - 1] && ch != '\n'); if (ch != '\n') { error("rcp: ACL record corrupted \n"); return (ACL_FAIL); } cp = &buf[1]; *cnt = strtol(cp, &cp, 0); if (*cp != '\n') { error("rcp: ACL record corrupted \n"); return (ACL_FAIL); } return (ACL_OK); } /* * Receive acl from the pipe and set it to f */ static int recvacl(int f, int exists, int preserve) { int aclcnt; /* acl entry count */ int aclsize; /* acl text length */ int j; char *tp; char *acltext; /* external format */ acl_t *aclp; int acltype; int min_entries; int aclerror; /* get acl count */ if (getaclinfo(&aclcnt, &acltype) != ACL_OK) return (ACL_FAIL); if (acltype == 0) { min_entries = MIN_ACL_ENTRIES; } else { min_entries = 1; } if (aclcnt > min_entries) { /* get acl text size */ if (getaclinfo(&aclsize, &acltype) != ACL_OK) return (ACL_FAIL); if ((acltext = malloc(aclsize + 1)) == NULL) { error("rcp: cant allocate memory: %d\n", aclsize); return (ACL_FAIL); } tp = acltext; do { j = desrcpread(rem, tp, aclsize); if (j <= 0) { error("rcp: %s\n", j ? strerror(errno) : "dropped connection"); exit(1); } aclsize -= j; tp += j; } while (aclsize > 0); *tp = '\0'; if (preserve || !exists) { aclerror = acl_fromtext(acltext, &aclp); if (aclerror != 0) { error("rcp: failed to parse acl : %s\n", acl_strerror(aclerror)); return (ACL_FAIL); } if (f != -1) { if (facl_set(f, aclp) < 0) { error("rcp: failed to set acl\n"); return (ACL_FAIL); } } /* -1 means that just consume the data in the pipe */ acl_free(aclp); } free(acltext); (void) desrcpwrite(rem, "", 1); } return (ACL_OK); } static char * search_char(unsigned char *cp, unsigned char chr) { int len; while (*cp) { if (*cp == chr) return ((char *)cp); if ((len = mblen((char *)cp, MB_CUR_MAX)) <= 0) len = 1; cp += len; } return (0); } static int desrcpread(int fd, char *buf, int len) { return ((int)desread(fd, buf, len, 0)); } static int desrcpwrite(int fd, char *buf, int len) { /* * Note that rcp depends on the same file descriptor being both * input and output to the remote side. This is bogus, especially * when rcp is being run by a rsh that pipes. Fix it here because * it would require significantly more work in other places. * --hartmans 1/96 */ if (fd == 0) fd = 1; return ((int)deswrite(fd, buf, len, 0)); } static char ** save_argv(int argc, char **argv) { int i; char **local_argv = (char **)calloc((unsigned)argc + 1, (unsigned)sizeof (char *)); /* * allocate an extra pointer, so that it is initialized to NULL and * execv() will work */ for (i = 0; i < argc; i++) { local_argv[i] = strsave(argv[i]); } return (local_argv); } #define SIZEOF_INADDR sizeof (struct in_addr) static void answer_auth(char *config_file, char *ccache_file) { krb5_data pname_data, msg; krb5_creds creds, *new_creds; krb5_ccache cc; krb5_auth_context auth_context = NULL; if (config_file) { const char *filenames[2]; filenames[1] = NULL; filenames[0] = config_file; if (krb5_set_config_files(bsd_context, filenames)) exit(1); } (void) memset((char *)&creds, 0, sizeof (creds)); if (krb5_read_message(bsd_context, (krb5_pointer) &rem, &pname_data)) exit(1); if (krb5_read_message(bsd_context, (krb5_pointer) &rem, &creds.second_ticket)) exit(1); if (ccache_file == NULL) { if (krb5_cc_default(bsd_context, &cc)) exit(1); } else { if (krb5_cc_resolve(bsd_context, ccache_file, &cc)) exit(1); } if (krb5_cc_get_principal(bsd_context, cc, &creds.client)) exit(1); if (krb5_parse_name(bsd_context, pname_data.data, &creds.server)) exit(1); krb5_xfree(pname_data.data); if (krb5_get_credentials(bsd_context, KRB5_GC_USER_USER, cc, &creds, &new_creds)) exit(1); if (krb5_mk_req_extended(bsd_context, &auth_context, AP_OPTS_USE_SESSION_KEY, NULL, new_creds, &msg)) exit(1); if (krb5_write_message(bsd_context, (krb5_pointer) & rem, &msg)) { krb5_xfree(msg.data); exit(1); } /* setup eblock for des_read and write */ krb5_copy_keyblock(bsd_context, &new_creds->keyblock, &session_key); /* OK process key */ eblock.crypto_entry = session_key->enctype; eblock.key = (krb5_keyblock *)session_key; init_encrypt(encrypt_flag, bsd_context, KCMD_OLD_PROTOCOL, &desinbuf, &desoutbuf, CLIENT, &eblock); /* cleanup */ krb5_free_cred_contents(bsd_context, &creds); krb5_free_creds(bsd_context, new_creds); krb5_xfree(msg.data); } static void try_normal_rcp(int cur_argc, char **cur_argv) { char *target; /* * Reset all KRB5 relevant flags and set the * cmd-buffer so that normal rcp works */ krb5auth_flag = encrypt_flag = encrypt_done = 0; cmd = cmd_orig; cmd_sunw = cmd_sunw_orig; if (cur_argc < 2) usage(); if (cur_argc > 2) targetshouldbedirectory = 1; rem = -1; prev_argc = cur_argc; prev_argv = save_argv(cur_argc, cur_argv); (void) init_service(krb5auth_flag); if (target = colon(cur_argv[cur_argc - 1])) { toremote(target, cur_argc, cur_argv); } else { tolocal(cur_argc, cur_argv); if (targetshouldbedirectory) verifydir(cur_argv[cur_argc - 1]); } exit(errs); /* NOTREACHED */ } static int init_service(int krb5flag) { struct servent *sp; boolean_t success = B_FALSE; if (krb5flag > 0) { sp = getservbyname("kshell", "tcp"); if (sp == NULL) { (void) fprintf(stderr, gettext("rcp: kshell/tcp: unknown service.\n" "trying normal shell/tcp service\n")); } else { portnumber = sp->s_port; success = B_TRUE; } } else { portnumber = htons(IPPORT_CMDSERVER); success = B_TRUE; } return (success); } /*PRINTFLIKE1*/ static void error(char *fmt, ...) { va_list ap; char buf[RCP_BUFSIZE]; char *cp = buf; va_start(ap, fmt); errs++; *cp++ = 1; (void) vsnprintf(cp, sizeof (buf) - 1, fmt, ap); va_end(ap); (void) desrcpwrite(rem, buf, strlen(buf)); if (iamremote == 0) (void) write(2, buf + 1, strlen(buf + 1)); }