xref: /illumos-gate/usr/src/cmd/cmd-inet/usr.bin/rcp.c (revision 7c478bd9)
1 /*
2  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 #pragma ident	"%Z%%M%	%I%	%E% SMI"
7 
8 /*
9  * Copyright (c) 1983 The Regents of the University of California.
10  * All rights reserved.
11  *
12  * Redistribution and use in source and binary forms are permitted
13  * provided that the above copyright notice and this paragraph are
14  * duplicated in all such forms and that any documentation,
15  * advertising materials, and other materials related to such
16  * distribution and use acknowledge that the software was developed
17  * by the University of California, Berkeley.  The name of the
18  * University may not be used to endorse or promote products derived
19  * from this software without specific prior written permission.
20  *
21  */
22 
23 #define	_FILE_OFFSET_BITS  64
24 
25 /*
26  * rcp
27  */
28 #include <sys/param.h>
29 #include <sys/file.h>
30 #include <sys/stat.h>
31 #include <sys/time.h>
32 #include <sys/types.h>
33 #include <sys/ioctl.h>
34 #include <sys/acl.h>
35 #include <dirent.h>
36 #include <signal.h>
37 #include <sys/socket.h>
38 #include <netinet/in.h>
39 #include <pwd.h>
40 #include <netdb.h>
41 #include <wchar.h>
42 #include <stdlib.h>
43 #include <errno.h>
44 #include <locale.h>
45 #include <strings.h>
46 #include <stdio.h>
47 #include <ctype.h>
48 #include <fcntl.h>
49 #include <unistd.h>
50 #include <limits.h>
51 #include <priv_utils.h>
52 #include <sys/sendfile.h>
53 #include <sys/sysmacros.h>
54 #include <sys/wait.h>
55 
56 /*
57  * It seems like Berkeley got these from pathnames.h?
58  */
59 #define	_PATH_RSH	"/usr/bin/rsh"
60 #define	_PATH_CP	"/usr/bin/cp"
61 #define	_PATH_BSHELL	"/usr/bin/sh"
62 
63 #define	ACL_FAIL	1
64 #define	ACL_OK		0
65 #define	RCP_BUFSIZE	(64 * 1024)
66 
67 #define	RCP_ACL	"/usr/lib/sunw,rcp"
68 		/* see PSARC/1993/004/opinion */
69 
70 typedef struct _buf {
71 	int	cnt;
72 	char	*buf;
73 } BUF;
74 
75 static char *cmd_sunw;
76 static struct passwd *pwd;
77 static int errs;
78 static int pflag;
79 static uid_t userid;
80 static int rem;
81 static int zflag;
82 static int iamremote;
83 static int iamrecursive;
84 static int targetshouldbedirectory;
85 static int aclflag;
86 static int retval = 0;
87 static int portnumber = 0;
88 
89 static void lostconn(void);
90 static char *search_char(unsigned char *, unsigned char);
91 static char *removebrackets(char *);
92 static char *colon(char *);
93 static int response(void);
94 static void usage(void);
95 static void source(int, char **);
96 static void sink(int, char **);
97 static void toremote(char *, int, char **);
98 static void tolocal(int, char **);
99 static void verifydir(char *);
100 static int okname(char *);
101 static int susystem(char *);
102 static void rsource(char *, struct stat *);
103 static int sendacl(int);
104 static int recvacl(int, int, int);
105 static int zwrite(int, char *, int);
106 static void zopen(int, int);
107 static int zclose(int);
108 static int notzero(char *, int);
109 static BUF *allocbuf(BUF *, int, int);
110 static void error(char *fmt, ...);
111 
112 /*
113  * As a 32 bit application, we can only transfer (2gb - 1) i.e 0x7FFFFFFF
114  * bytes of data. We would like the size to be aligned to the nearest
115  * MAXBOFFSET (8192) boundary for optimal performance.
116  */
117 #define	SENDFILE_SIZE	0x7FFFE000
118 
119 #include <k5-int.h>
120 #include <profile/prof_int.h>
121 #include <com_err.h>
122 #include <kcmd.h>
123 
124 #define	NULLBUF	(BUF *) 0
125 
126 static int sock;
127 static char *cmd, *cmd_orig, *cmd_sunw_orig;
128 static char *krb_realm = NULL;
129 static char *krb_cache = NULL;
130 static char *krb_config = NULL;
131 static char des_inbuf[2 * RCP_BUFSIZE];
132 				/* needs to be > largest read size */
133 static char des_outbuf[2 * RCP_BUFSIZE];
134 				/* needs to be > largest write size */
135 
136 static krb5_data desinbuf, desoutbuf;
137 static krb5_encrypt_block eblock;	/* eblock for encrypt/decrypt */
138 static krb5_keyblock *session_key;	/* static key for session */
139 static krb5_context bsd_context;
140 static krb5_auth_context auth_context;
141 static krb5_flags authopts;
142 static krb5_error_code status;
143 
144 static void try_normal_rcp(int, char **);
145 static int init_service(int);
146 static char **save_argv(int, char **);
147 static void answer_auth(char *, char *);
148 static int desrcpwrite(int, char *, int);
149 static int desrcpread(int, char *, int);
150 
151 /*
152  * Not sure why these two don't have their own header file declarations, but
153  * lint complains about absent declarations so place some here. Sigh.
154  */
155 extern errcode_t	profile_get_options_boolean(profile_t, char **,
156     profile_options_boolean *);
157 extern errcode_t	profile_get_options_string(profile_t, char **,
158     profile_option_strings *);
159 
160 static int krb5auth_flag = 0;	/* Flag set, when KERBEROS is enabled */
161 static int encrypt_flag = 0;	/* Flag set, when encryption is enabled */
162 static int encrypt_done = 0;	/* Flag set, if "-x" is specified */
163 static enum kcmd_proto kcmd_proto = KCMD_NEW_PROTOCOL;
164 
165 /* Flag set, if -PN / -PO is specified */
166 static boolean_t rcmdoption_done = B_FALSE;
167 
168 static profile_options_boolean option[] = {
169 	{ "encrypt", &encrypt_flag, 0 },
170 	{ NULL, NULL, 0 }
171 };
172 
173 static char *rcmdproto = NULL;
174 static profile_option_strings rcmdversion[] = {
175 	{ "rcmd_protocol", &rcmdproto, 0 },
176 	{ NULL, NULL, 0 }
177 };
178 
179 static char *realmdef[] = { "realms", NULL, "rcp", NULL };
180 static char *appdef[] = { "appdefaults", "rcp", NULL };
181 static char **prev_argv;
182 static int prev_argc;
183 
184 int
185 main(int argc, char *argv[])
186 {
187 	int ch, fflag, tflag;
188 	char *targ;
189 	size_t cmdsiz;
190 
191 	(void) setlocale(LC_ALL, "");
192 
193 	if (strcmp(argv[0], RCP_ACL) == 0)
194 		aclflag = 1;
195 
196 	if (!(pwd = getpwuid(userid = getuid()))) {
197 		(void) fprintf(stderr, "rcp: unknown user %d.\n",
198 		    (uint_t)userid);
199 		return (1);
200 	}
201 
202 	fflag = tflag = 0;
203 	while ((ch = getopt(argc, argv, "axdfprtz:D:k:P:")) != EOF) {
204 		switch (ch) {
205 		case 'd':
206 			targetshouldbedirectory = 1;
207 			break;
208 		case 'f':			/* "from" */
209 			fflag = 1;
210 			if (aclflag)
211 				/* ok response */
212 				(void) desrcpwrite(rem, "", 1);
213 			break;
214 		case 'p':			/* preserve access/mod times */
215 			++pflag;
216 			break;
217 		case 'r':
218 			++iamrecursive;
219 			break;
220 		case 't':			/* "to" */
221 			tflag = 1;
222 			break;
223 		case 'x':
224 			if (!krb5_privacy_allowed()) {
225 				(void) fprintf(stderr, gettext("rcp: "
226 					"Encryption not supported.\n"));
227 				return (1);
228 			}
229 			encrypt_flag++;
230 			krb5auth_flag++;
231 			encrypt_done++;
232 			break;
233 		case 'k':
234 			if ((krb_realm = (char *)strdup(optarg)) == NULL) {
235 				(void) fprintf(stderr, gettext("rcp:"
236 					" Cannot malloc.\n"));
237 				return (1);
238 			}
239 			krb5auth_flag++;
240 			break;
241 		case 'P':
242 			if (strncmp(optarg, "O", 1) == 0) {
243 				if (rcmdoption_done == B_TRUE) {
244 					(void) fprintf(stderr, gettext("rcp: "
245 						"Only one of -PN and -PO "
246 						"allowed.\n"));
247 					usage();
248 				}
249 				kcmd_proto = KCMD_OLD_PROTOCOL;
250 				rcmdoption_done = B_TRUE;
251 			} else if (strncmp(optarg, "N", 1) == 0) {
252 				if (rcmdoption_done == B_TRUE) {
253 					(void) fprintf(stderr, gettext("rcp: "
254 						"Only one of -PN and -PO "
255 						"allowed.\n"));
256 					usage();
257 				}
258 				kcmd_proto = KCMD_NEW_PROTOCOL;
259 				rcmdoption_done = B_TRUE;
260 			} else {
261 				usage();
262 			}
263 			krb5auth_flag++;
264 			break;
265 		case 'a':
266 			krb5auth_flag++;
267 			break;
268 #ifdef DEBUG
269 		case 'D':
270 			portnumber = htons(atoi(optarg));
271 			krb5auth_flag++;
272 			break;
273 #endif /* DEBUG */
274 		case '?':
275 		default:
276 			usage();
277 		}
278 	}
279 	argc -= optind;
280 	argv += optind;
281 
282 	if (krb5auth_flag > 0) {
283 		status = krb5_init_context(&bsd_context);
284 		if (status) {
285 			com_err("rcp", status,
286 				gettext("while initializing krb5"));
287 			return (1);
288 		}
289 
290 		/*
291 		 * Set up buffers for desread and deswrite.
292 		 */
293 		desinbuf.data = des_inbuf;
294 		desoutbuf.data = des_outbuf;
295 		desinbuf.length = sizeof (des_inbuf);
296 		desoutbuf.length = sizeof (des_outbuf);
297 	}
298 
299 	if (fflag || tflag)
300 		if (encrypt_flag > 0)
301 			(void) answer_auth(krb_config, krb_cache);
302 
303 	if (fflag) {
304 		iamremote = 1;
305 		(void) response();
306 		(void) setuid(userid);
307 		source(argc, argv);
308 		return (errs);
309 	}
310 
311 	if (tflag) {
312 		iamremote = 1;
313 		(void) setuid(userid);
314 		sink(argc, argv);
315 		return (errs);
316 	}
317 
318 	if (argc < 2)
319 		usage();
320 
321 	/* This will make "rcmd_af()" magically get the proper privilege */
322 	if (__init_suid_priv(0, PRIV_NET_PRIVADDR, (char *)NULL) == -1) {
323 		(void) fprintf(stderr, "rcp: must be set-uid root\n");
324 		exit(1);
325 	}
326 
327 	if (krb5auth_flag > 0) {
328 		/*
329 		 * Get our local realm to look up local realm options.
330 		 */
331 		status = krb5_get_default_realm(bsd_context, &realmdef[1]);
332 		if (status) {
333 			com_err("rcp", status,
334 				gettext("while getting default realm"));
335 			return (1);
336 		}
337 		/*
338 		 * See if encryption should be done for this realm
339 		 */
340 		profile_get_options_boolean(bsd_context->profile, realmdef,
341 						option);
342 		/*
343 		 * Check the appdefaults section
344 		 */
345 		profile_get_options_boolean(bsd_context->profile, appdef,
346 						option);
347 		profile_get_options_string(bsd_context->profile, appdef,
348 						rcmdversion);
349 		if ((encrypt_done > 0) || (encrypt_flag > 0)) {
350 			if (krb5_privacy_allowed() == TRUE) {
351 				encrypt_flag++;
352 			} else {
353 				(void) fprintf(stderr, gettext("rcp: Encryption"
354 							" not supported.\n"));
355 				return (1);
356 			}
357 		}
358 
359 		if ((rcmdoption_done == B_FALSE) && (rcmdproto != NULL)) {
360 			if (strncmp(rcmdproto, "rcmdv2", 6) == 0) {
361 				kcmd_proto = KCMD_NEW_PROTOCOL;
362 			} else if (strncmp(rcmdproto, "rcmdv1", 6) == 0) {
363 				kcmd_proto = KCMD_OLD_PROTOCOL;
364 			} else {
365 				(void) fprintf(stderr, gettext("Unrecognized "
366 					"KCMD protocol (%s)"), rcmdproto);
367 				return (1);
368 			}
369 		}
370 	}
371 
372 	if (argc > 2)
373 		targetshouldbedirectory = 1;
374 
375 	rem = -1;
376 
377 	if (portnumber == 0) {
378 		if (krb5auth_flag > 0) {
379 			retval = init_service(krb5auth_flag);
380 			if (!retval) {
381 				/*
382 				 * Connecting to the kshell service failed,
383 				 * fallback to normal rcp & reset KRB5 flags.
384 				 */
385 				krb5auth_flag = encrypt_flag = 0;
386 				encrypt_done = 0;
387 				(void) init_service(krb5auth_flag);
388 			}
389 		}
390 		else
391 			(void) init_service(krb5auth_flag);
392 	}
393 
394 #ifdef DEBUG
395 	if (retval || krb5auth_flag) {
396 		(void) fprintf(stderr, gettext("Kerberized rcp session, "
397 				"port %d in use "), portnumber);
398 		if (kcmd_proto == KCMD_OLD_PROTOCOL)
399 			(void) fprintf(stderr, gettext("[kcmd ver.1]\n"));
400 		else
401 			(void) fprintf(stderr, gettext("[kcmd ver.2]\n"));
402 	} else {
403 		(void) fprintf(stderr, gettext("Normal rcp session, port %d "
404 				"in use.\n"), portnumber);
405 	}
406 #endif /* DEBUG */
407 
408 	if (krb5auth_flag > 0) {
409 		/*
410 		 * We calculate here a buffer size that can be used in the
411 		 * allocation of the three buffers cmd, cmd_orig and
412 		 * cmd_sunw_orig that are used to hold different incantations
413 		 * of rcp.
414 		 */
415 		cmdsiz = MAX(sizeof ("-x rcp  -r -p -d -k ") +
416 		    strlen(krb_realm != NULL ? krb_realm : ""),
417 		    sizeof (RCP_ACL " -r -p -z -d"));
418 
419 		if (((cmd = (char *)malloc(cmdsiz)) == NULL) ||
420 			((cmd_sunw_orig = (char *)malloc(cmdsiz)) == NULL) ||
421 			((cmd_orig = (char *)malloc(cmdsiz)) == NULL)) {
422 			(void) fprintf(stderr, gettext("rcp: Cannot "
423 					"malloc.\n"));
424 			return (1);
425 		}
426 
427 		(void) snprintf(cmd, cmdsiz, "%srcp %s%s%s%s%s",
428 			encrypt_flag ? "-x " : "",
429 
430 			iamrecursive ? " -r" : "", pflag ? " -p" : "",
431 			targetshouldbedirectory ? " -d" : "",
432 			krb_realm != NULL ? " -k " : "",
433 			krb_realm != NULL ? krb_realm : "");
434 
435 		/*
436 		 * We would use cmd-orig as the 'cmd-buffer' if kerberized
437 		 * rcp fails, in which case we fallback to normal rcp. We also
438 		 * save argc & argv for the same purpose
439 		 */
440 		(void) snprintf(cmd_orig, cmdsiz, "rcp%s%s%s%s",
441 			iamrecursive ? " -r" : "",
442 			pflag ? " -p" : "",
443 			zflag ? " -z" : "",
444 			targetshouldbedirectory ? " -d" : "");
445 
446 		(void) snprintf(cmd_sunw_orig, cmdsiz, "%s%s%s%s%s", RCP_ACL,
447 			iamrecursive ? " -r" : "",
448 			pflag ? " -p" : "",
449 			zflag ? " -z" : "",
450 			targetshouldbedirectory ? " -d" : "");
451 
452 		prev_argc = argc;
453 		prev_argv = save_argv(argc, argv);
454 
455 	} else {
456 		cmdsiz = sizeof ("rcp -r -p -z -d");
457 		if (((cmd = (char *)malloc(cmdsiz)) == NULL)) {
458 			(void) fprintf(stderr, gettext("rcp: Cannot "
459 					"malloc.\n"));
460 			return (1);
461 		}
462 
463 		(void) snprintf(cmd, cmdsiz, "rcp%s%s%s%s",
464 			iamrecursive ? " -r" : "",
465 			pflag ? " -p" : "",
466 			zflag ? " -z" : "",
467 			targetshouldbedirectory ? " -d" : "");
468 	}
469 
470 	cmdsiz = sizeof (RCP_ACL " -r -p -z -d");
471 	if ((cmd_sunw = (char *)malloc(cmdsiz)) == NULL) {
472 		(void) fprintf(stderr, gettext("rcp: Cannot malloc.\n"));
473 		return (1);
474 	}
475 
476 	(void) snprintf(cmd_sunw, cmdsiz, "%s%s%s%s%s", RCP_ACL,
477 	    iamrecursive ? " -r" : "",
478 	    pflag ? " -p" : "",
479 	    zflag ? " -z" : "",
480 	    targetshouldbedirectory ? " -d" : "");
481 
482 	(void) signal(SIGPIPE, (void (*)(int))lostconn);
483 
484 	if (targ = colon(argv[argc - 1]))
485 		toremote(targ, argc, argv);
486 	else {
487 		tolocal(argc, argv);
488 		if (targetshouldbedirectory)
489 			verifydir(argv[argc - 1]);
490 	}
491 
492 	return (errs > 0 ? EXIT_FAILURE : EXIT_SUCCESS);
493 }
494 
495 
496 static void
497 toremote(char *targ, int argc, char *argv[])
498 {
499 	int i;
500 	char *host, *src, *suser, *thost, *tuser;
501 	char resp;
502 	size_t buffersize;
503 	char bp[RCP_BUFSIZE];
504 	krb5_creds *cred;
505 	buffersize = RCP_BUFSIZE;
506 
507 	*targ++ = 0;
508 	if (*targ == 0)
509 		targ = ".";
510 
511 	if (thost = search_char((unsigned char *)argv[argc - 1], '@')) {
512 		*thost++ = 0;
513 		tuser = argv[argc - 1];
514 		if (*tuser == '\0')
515 			tuser = NULL;
516 		else if (!okname(tuser))
517 			exit(1);
518 	} else {
519 		thost = argv[argc - 1];
520 		tuser = NULL;
521 	}
522 	thost = removebrackets(thost);
523 
524 	for (i = 0; i < argc - 1; i++) {
525 		src = colon(argv[i]);
526 		if (src) {			/* remote to remote */
527 			*src++ = 0;
528 			if (*src == 0)
529 				src = ".";
530 			host = search_char((unsigned char *)argv[i], '@');
531 			if (host) {
532 				*host++ = 0;
533 				host = removebrackets(host);
534 				suser = argv[i];
535 				if (*suser == '\0') {
536 					suser = pwd->pw_name;
537 				} else if (!okname(suser)) {
538 					errs++;
539 					continue;
540 				}
541 				(void) snprintf(bp, buffersize,
542 				    "%s %s -l %s -n %s %s '%s%s%s:%s'",
543 				    _PATH_RSH, host, suser, cmd, src,
544 				    tuser ? tuser : "", tuser ? "@" : "",
545 				    thost, targ);
546 			} else {
547 				host = removebrackets(argv[i]);
548 				(void) snprintf(bp, buffersize,
549 					"%s %s -n %s %s '%s%s%s:%s'",
550 					_PATH_RSH, host, cmd, src,
551 					tuser ? tuser : "", tuser ? "@" : "",
552 					thost, targ);
553 			}
554 			if (susystem(bp) == -1)
555 				errs++;
556 		} else {			/* local to remote */
557 			if (rem == -1) {
558 				host = thost;
559 				if (krb5auth_flag > 0) {
560 
561 				(void) snprintf(bp, buffersize,
562 						"%s -t %s", cmd, targ);
563 				authopts = AP_OPTS_MUTUAL_REQUIRED;
564 				status = kcmd(&sock, &host,
565 					    portnumber,
566 					    pwd->pw_name,
567 					    tuser ? tuser :
568 					    pwd->pw_name,
569 					    bp,
570 					    0,
571 					    "host",
572 					    krb_realm,
573 					    bsd_context,
574 					    &auth_context,
575 					    &cred,
576 					    0,	/* No seq # */
577 					    0,	/* No server seq # */
578 					    authopts,
579 					    0,	/* Not any port # */
580 					    &kcmd_proto);
581 				if (status) {
582 					/*
583 					 * If new protocol requested, we dont
584 					 * fallback to less secure ones.
585 					 */
586 
587 					if (kcmd_proto == KCMD_NEW_PROTOCOL) {
588 						(void) fprintf(stderr,
589 							gettext("rcp: kcmdv2 "
590 							"to host %s failed - %s"
591 							"\nFallback to normal "
592 							"rcp denied."), host,
593 							error_message(status));
594 						exit(1);
595 					}
596 					if (status != -1) {
597 						(void) fprintf(stderr,
598 						gettext("rcp: kcmd to host "
599 						"%s failed - %s,\n"
600 						"trying normal rcp...\n\n"),
601 						host, error_message(status));
602 					} else {
603 						(void) fprintf(stderr,
604 							gettext("trying normal"
605 							" rcp...\n"));
606 					}
607 					/*
608 					 * kcmd() failed, so we have to
609 					 * fallback to normal rcp
610 					 */
611 					try_normal_rcp(prev_argc, prev_argv);
612 				} else {
613 					rem = sock;
614 					session_key = &cred->keyblock;
615 					if (kcmd_proto == KCMD_NEW_PROTOCOL) {
616 						/* CSTYLED */
617 						status = krb5_auth_con_getlocalsubkey(bsd_context, auth_context, &session_key);
618 						if (status) {
619 							com_err("rcp", status,
620 								"determining "
621 								"subkey for "
622 								"session");
623 							exit(1);
624 						}
625 						if (!session_key) {
626 							com_err("rcp", 0,
627 								"no subkey "
628 								"negotiated for"
629 								" connection");
630 							exit(1);
631 						}
632 					}
633 					eblock.crypto_entry =
634 						session_key->enctype;
635 					eblock.key =
636 						(krb5_keyblock *)session_key;
637 
638 					init_encrypt(encrypt_flag,
639 						bsd_context, kcmd_proto,
640 						&desinbuf, &desoutbuf, CLIENT,
641 						&eblock);
642 					if (encrypt_flag > 0) {
643 						char *s = gettext("This rcp "
644 							"session is using "
645 							"encryption for all "
646 							"data transmissions."
647 							"\r\n");
648 
649 						(void) write(2, s, strlen(s));
650 					}
651 				}
652 				if (response() < 0)
653 					exit(1);
654 
655 				}
656 				else
657 				{
658 
659 				/*
660 				 * ACL support: try to find out if the remote
661 				 * site is running acl cognizant version of
662 				 * rcp. A special binary name is used for this
663 				 * purpose.
664 				 */
665 				aclflag = 1;
666 
667 				(void) snprintf(bp, buffersize, "%s -t %s",
668 							cmd_sunw, targ);
669 				rem = rcmd_af(&host, portnumber, pwd->pw_name,
670 					    tuser ? tuser : pwd->pw_name,
671 					    bp, 0, AF_INET6);
672 				if (rem < 0)
673 					exit(1);
674 
675 				/*
676 				 * This is similar to routine response().
677 				 * If response is not ok, treat the other
678 				 * side as non-acl rcp.
679 				 */
680 				if (read(rem, &resp, sizeof (resp))
681 				    != sizeof (resp))
682 					lostconn();
683 				if (resp != 0) {
684 					/*
685 					 * Not OK:
686 					 * The other side is running
687 					 * non-acl rcp. Try again with
688 					 * normal stuff
689 					 */
690 					aclflag = 0;
691 					(void) snprintf(bp, buffersize,
692 						"%s -t %s", cmd, targ);
693 					(void) close(rem);
694 					host = thost;
695 					rem = rcmd_af(&host, portnumber,
696 							pwd->pw_name,
697 							tuser ? tuser :
698 							pwd->pw_name, bp, 0,
699 							AF_INET6);
700 					if (rem < 0)
701 						exit(1);
702 					if (response() < 0)
703 						exit(1);
704 				}
705 				/* everything should be fine now */
706 				(void) setuid(userid);
707 
708 				}
709 
710 			}
711 			source(1, argv + i);
712 		}
713 	}
714 }
715 
716 static void
717 tolocal(int argc, char *argv[])
718 {
719 	int i;
720 	char *host, *src, *suser, *lhost;
721 	char resp;
722 	size_t buffersize;
723 	char bp[RCP_BUFSIZE];
724 	krb5_creds *cred;
725 	buffersize = RCP_BUFSIZE;
726 
727 	for (i = 0; i < argc - 1; i++) {
728 		if (!(src = colon(argv[i]))) {	/* local to local */
729 			(void) snprintf(bp, buffersize, "%s%s%s%s %s %s",
730 			    _PATH_CP, iamrecursive ? " -r" : "",
731 			    pflag ? " -p" : "",
732 			    zflag ? " -z" : "",
733 			    argv[i], argv[argc - 1]);
734 			if (susystem(bp) == -1)
735 				errs++;
736 			continue;
737 		}
738 		*src++ = 0;
739 		if (*src == 0)
740 			src = ".";
741 		host = search_char((unsigned char *)argv[i], '@');
742 		if (host) {
743 			*host++ = 0;
744 			suser = argv[i];
745 			if (*suser == '\0') {
746 				suser = pwd->pw_name;
747 			} else if (!okname(suser)) {
748 				errs++;
749 				continue;
750 			}
751 		} else {
752 			host = argv[i];
753 			suser = pwd->pw_name;
754 		}
755 		host = removebrackets(host);
756 		lhost = host;
757 		if (krb5auth_flag > 0) {
758 
759 		(void) snprintf(bp, buffersize, "%s -f %s", cmd, src);
760 		authopts = AP_OPTS_MUTUAL_REQUIRED;
761 		status = kcmd(&sock, &host,
762 				portnumber,
763 				pwd->pw_name, suser,
764 				bp,
765 				0,	/* &rfd2 */
766 				"host",
767 				krb_realm,
768 				bsd_context,
769 				&auth_context,
770 				&cred,
771 				0,	/* No seq # */
772 				0,	/* No server seq # */
773 				authopts,
774 				1,	/* Not any port # */
775 				&kcmd_proto);
776 		if (status) {
777 			/*
778 			 * If new protocol requested, we dont
779 			 * fallback to less secure ones.
780 			 */
781 			if (kcmd_proto == KCMD_NEW_PROTOCOL) {
782 				(void) fprintf(stderr, gettext("rcp: kcmdv2 "
783 					"to host %s failed - %s\n"
784 					"Fallback to normal rcp denied."),
785 					host, error_message(status));
786 				exit(1);
787 			}
788 			if (status != -1) {
789 				(void) fprintf(stderr, gettext("rcp: kcmd "
790 						"to host %s failed - %s,\n"
791 						"trying normal rcp...\n\n"),
792 						host, error_message(status));
793 			} else {
794 				(void) fprintf(stderr,
795 					gettext("trying normal rcp...\n"));
796 			}
797 			/*
798 			 * kcmd() failed, so we have to
799 			 * fallback to normal rcp
800 			 */
801 			try_normal_rcp(prev_argc, prev_argv);
802 		} else {
803 			rem = sock;
804 			session_key = &cred->keyblock;
805 			if (kcmd_proto == KCMD_NEW_PROTOCOL) {
806 				status = krb5_auth_con_getlocalsubkey(
807 						bsd_context, auth_context,
808 						&session_key);
809 				if (status) {
810 					com_err("rcp", status, "determining "
811 						"subkey for session");
812 					exit(1);
813 				}
814 				if (!session_key) {
815 					com_err("rcp", 0, "no subkey negotiated"
816 						" for connection");
817 					exit(1);
818 				}
819 			}
820 			eblock.crypto_entry = session_key->enctype;
821 			eblock.key = (krb5_keyblock *)session_key;
822 
823 			init_encrypt(encrypt_flag, bsd_context, kcmd_proto,
824 					&desinbuf, &desoutbuf, CLIENT,
825 					&eblock);
826 			if (encrypt_flag > 0) {
827 				char *s = gettext("This rcp "
828 					"session is using DES "
829 					"encryption for all "
830 					"data transmissions."
831 					"\r\n");
832 
833 				(void) write(2, s, strlen(s));
834 			}
835 		}
836 
837 		}
838 		else
839 		{
840 
841 		/*
842 		 * ACL support: try to find out if the remote site is
843 		 * running acl cognizant version of rcp.
844 		 */
845 		aclflag = 1;
846 
847 		(void) snprintf(bp, buffersize, "%s -f %s", cmd_sunw, src);
848 		rem = rcmd_af(&host, portnumber, pwd->pw_name, suser,
849 			    bp, 0, AF_INET6);
850 
851 		if (rem < 0) {
852 			++errs;
853 			continue;
854 		}
855 
856 		/*
857 		 * The remote system is supposed to send an ok response.
858 		 * If there are any data other than "ok", it must be error
859 		 * messages from the remote system. We can assume the
860 		 * remote system is running non-acl version rcp.
861 		 */
862 		if (read(rem, &resp, sizeof (resp)) != sizeof (resp))
863 			lostconn();
864 		if (resp != 0) {
865 			/*
866 			 * NOT ok:
867 			 * The other side is running non-acl rcp.
868 			 * Try again with normal stuff
869 			 */
870 			aclflag = 0;
871 			(void) snprintf(bp, buffersize, "%s -f %s", cmd, src);
872 				(void) close(rem);
873 				host = lhost;
874 				rem = rcmd_af(&host, portnumber, pwd->pw_name,
875 						suser, bp, 0, AF_INET6);
876 			if (rem < 0) {
877 				++errs;
878 				continue;
879 			}
880 		}
881 		}
882 
883 		sink(1, argv + argc - 1);
884 
885 		(void) close(rem);
886 		rem = -1;
887 	}
888 }
889 
890 
891 static void
892 verifydir(char *cp)
893 {
894 	struct stat stb;
895 
896 	if (stat(cp, &stb) >= 0) {
897 		if ((stb.st_mode & S_IFMT) == S_IFDIR)
898 			return;
899 		errno = ENOTDIR;
900 	}
901 	error("rcp: %s: %s.\n", cp, strerror(errno));
902 	exit(1);
903 }
904 
905 static char *
906 colon(char *cp)
907 {
908 	boolean_t is_bracket_open = B_FALSE;
909 
910 	for (; *cp; ++cp) {
911 		if (*cp == '[')
912 			is_bracket_open = B_TRUE;
913 		else if (*cp == ']')
914 			is_bracket_open = B_FALSE;
915 		else if (*cp == ':' && !is_bracket_open)
916 			return (cp);
917 		else if (*cp == '/')
918 			return (0);
919 	}
920 	return (0);
921 }
922 
923 static int
924 okname(char *cp0)
925 {
926 	register char *cp = cp0;
927 	register int c;
928 
929 	do {
930 		c = *cp;
931 		if (c & 0200)
932 			goto bad;
933 		if (!isalpha(c) && !isdigit(c) && c != '_' && c != '-')
934 			goto bad;
935 	} while (*++cp);
936 	return (1);
937 bad:
938 	(void) fprintf(stderr, "rcp: invalid user name %s\n", cp0);
939 	return (0);
940 }
941 
942 
943 static char *
944 removebrackets(char *str)
945 {
946 	char *newstr = str;
947 
948 	if ((str[0] == '[') && (str[strlen(str) - 1] == ']')) {
949 		newstr = str + 1;
950 		str[strlen(str) - 1] = '\0';
951 	}
952 	return (newstr);
953 }
954 
955 static int
956 susystem(char *s)
957 {
958 	int status, pid, w;
959 	register void (*istat)(), (*qstat)();
960 	int pfds[2];
961 	char buf[BUFSIZ];
962 	int cnt;
963 	boolean_t seen_stderr_traffic;
964 
965 	/*
966 	 * Due to the fact that rcp uses rsh to copy between 2 remote
967 	 * machines, rsh doesn't return the exit status of the remote
968 	 * command, and we can't modify the rcmd protocol used by rsh
969 	 * (for interoperability reasons) we use the hack of using any
970 	 * output on stderr as indication that an error occurred and
971 	 * that we should return a non-zero error code.
972 	 */
973 
974 	if (pipe(pfds) == -1) {
975 		(void) fprintf(stderr, "Couldn't create pipe: %s\n",
976 		    strerror(errno));
977 		return (-1);
978 	}
979 
980 	if ((pid = vfork()) < 0) {
981 		(void) close(pfds[0]);
982 		(void) close(pfds[1]);
983 		(void) fprintf(stderr, "Couldn't fork child process: %s\n",
984 		    strerror(errno));
985 		return (-1);
986 	} else if (pid == 0) {
987 		/*
988 		 * Child.
989 		 */
990 		(void) close(pfds[0]);
991 		/*
992 		 * Send stderr messages down the pipe so that we can detect
993 		 * them in the parent process.
994 		 */
995 		if (pfds[1] != STDERR_FILENO) {
996 			(void) dup2(pfds[1], STDERR_FILENO);
997 			(void) close(pfds[1]);
998 		}
999 		/*
1000 		 * This shell does not inherit the additional privilege
1001 		 * we have in our Permitted set.
1002 		 */
1003 		(void) execl(_PATH_BSHELL, "sh", "-c", s, (char *)0);
1004 		_exit(127);
1005 	}
1006 	/*
1007 	 * Parent.
1008 	 */
1009 	istat = signal(SIGINT, SIG_IGN);
1010 	qstat = signal(SIGQUIT, SIG_IGN);
1011 
1012 	(void) close(pfds[1]);
1013 	seen_stderr_traffic = B_FALSE;
1014 	while ((cnt = read(pfds[0], buf, sizeof (buf))) > 0) {
1015 		/*
1016 		 * If any data is read from the pipe the child process
1017 		 * has output something on stderr so we set the boolean
1018 		 * 'seen_stderr_traffic' to true, which will cause the
1019 		 * function to return -1.
1020 		 */
1021 		(void) write(STDERR_FILENO, buf, cnt);
1022 		seen_stderr_traffic = B_TRUE;
1023 	}
1024 	(void) close(pfds[0]);
1025 	while ((w = wait(&status)) != pid && w != -1)
1026 		;
1027 	if (w == -1)
1028 		status = -1;
1029 
1030 	(void) signal(SIGINT, istat);
1031 	(void) signal(SIGQUIT, qstat);
1032 
1033 	return (seen_stderr_traffic ? -1 : status);
1034 }
1035 
1036 static void
1037 source(int argc, char *argv[])
1038 {
1039 	struct stat stb;
1040 	static BUF buffer;
1041 	BUF *bp;
1042 	int x, readerr, f, amt;
1043 	char *last, *name, buf[RCP_BUFSIZE];
1044 	off_t off, size, i;
1045 	ssize_t cnt;
1046 
1047 	for (x = 0; x < argc; x++) {
1048 		name = argv[x];
1049 		if ((f = open(name, O_RDONLY, 0)) < 0) {
1050 			error("rcp: %s: %s\n", name, strerror(errno));
1051 			continue;
1052 		}
1053 		if (fstat(f, &stb) < 0)
1054 			goto notreg;
1055 		switch (stb.st_mode&S_IFMT) {
1056 
1057 		case S_IFREG:
1058 			break;
1059 
1060 		case S_IFDIR:
1061 			if (iamrecursive) {
1062 				(void) close(f);
1063 				rsource(name, &stb);
1064 				continue;
1065 			}
1066 			/* FALLTHROUGH */
1067 		default:
1068 notreg:
1069 			(void) close(f);
1070 			error("rcp: %s: not a plain file\n", name);
1071 			continue;
1072 		}
1073 		last = rindex(name, '/');
1074 		if (last == 0)
1075 			last = name;
1076 		else
1077 			last++;
1078 		if (pflag) {
1079 			time_t mtime, atime;
1080 			time_t now;
1081 
1082 			/*
1083 			 * Make it compatible with possible future
1084 			 * versions expecting microseconds.
1085 			 */
1086 			mtime = stb.st_mtime;
1087 			atime = stb.st_atime;
1088 
1089 			if ((mtime < 0) || (atime < 0)) {
1090 				now = time(NULL);
1091 
1092 				if (mtime < 0) {
1093 					mtime = now;
1094 					error("negative modification time on "
1095 					    "%s; not preserving\n", name);
1096 				}
1097 				if (atime < 0) {
1098 					atime = now;
1099 					error("negative access time on "
1100 					    "%s; not preserving\n", name);
1101 				}
1102 			}
1103 			(void) snprintf(buf, sizeof (buf), "T%ld 0 %ld 0\n",
1104 							mtime, atime);
1105 			(void) desrcpwrite(rem, buf, strlen(buf));
1106 			if (response() < 0) {
1107 				(void) close(f);
1108 				continue;
1109 			}
1110 		}
1111 		(void) snprintf(buf, sizeof (buf), "C%04o %lld %s\n",
1112 			(uint_t)(stb.st_mode & 07777), (longlong_t)stb.st_size,
1113 			last);
1114 		(void) desrcpwrite(rem, buf, strlen(buf));
1115 		if (response() < 0) {
1116 			(void) close(f);
1117 			continue;
1118 		}
1119 
1120 		/* ACL support: send */
1121 		if (aclflag) {
1122 			/* get acl from f and send it over */
1123 			if (sendacl(f) == ACL_FAIL) {
1124 				(void) close(f);
1125 				continue;
1126 			}
1127 		}
1128 		if ((krb5auth_flag > 0) || (iamremote == 1)) {
1129 			bp = allocbuf(&buffer, f, RCP_BUFSIZE);
1130 			if (bp == NULLBUF) {
1131 				(void) close(f);
1132 				continue;
1133 			}
1134 			readerr = 0;
1135 			for (i = 0; i < stb.st_size; i += bp->cnt) {
1136 				amt = bp->cnt;
1137 				if (i + amt > stb.st_size)
1138 					amt = stb.st_size - i;
1139 				if (readerr == 0 &&
1140 				    read(f, bp->buf, amt) != amt)
1141 					readerr = errno;
1142 				(void) desrcpwrite(rem, bp->buf, amt);
1143 			}
1144 			(void) close(f);
1145 			if (readerr == 0)
1146 				(void) desrcpwrite(rem, "", 1);
1147 			else
1148 				error("rcp: %s: %s\n", name,
1149 				    error_message(readerr));
1150 		} else {
1151 			cnt = off = 0;
1152 			size = stb.st_size;
1153 			while (size != 0) {
1154 				amt = MIN(size, SENDFILE_SIZE);
1155 				cnt = sendfile(rem, f, &off, amt);
1156 				if (cnt == -1)
1157 					break;
1158 				size -= cnt;
1159 			}
1160 			if (cnt == -1) {
1161 				error("rcp: %s: %s\n", name, strerror(errno));
1162 			} else {
1163 				(void) write(rem, "", 1);
1164 			}
1165 			(void) close(f);
1166 		}
1167 		(void) response();
1168 	}
1169 }
1170 
1171 
1172 static void
1173 rsource(char *name, struct stat *statp)
1174 {
1175 	DIR *d;
1176 	struct dirent *dp;
1177 	char *last, *vect[1];
1178 	char path[MAXPATHLEN];
1179 
1180 	if (!(d = opendir(name))) {
1181 		error("rcp: %s: %s\n", name, strerror(errno));
1182 		return;
1183 	}
1184 	last = rindex(name, '/');
1185 	if (last == 0)
1186 		last = name;
1187 	else
1188 		last++;
1189 	if (pflag) {
1190 		(void) snprintf(path, sizeof (path), "T%ld 0 %ld 0\n",
1191 				statp->st_mtime, statp->st_atime);
1192 		(void) desrcpwrite(rem, path, strlen(path));
1193 		if (response() < 0) {
1194 			(void) closedir(d);
1195 			return;
1196 		}
1197 	}
1198 	(void) snprintf(path, sizeof (path), "D%04o %d %s\n",
1199 	    (uint_t)(statp->st_mode & 07777), 0, last);
1200 	(void) desrcpwrite(rem, path, strlen(path));
1201 
1202 	/* acl support for directory */
1203 	if (aclflag) {
1204 		/* get acl from f and send it over */
1205 		if (sendacl(d->dd_fd) == ACL_FAIL) {
1206 			(void) closedir(d);
1207 			return;
1208 		}
1209 	}
1210 
1211 	if (response() < 0) {
1212 		(void) closedir(d);
1213 		return;
1214 	}
1215 
1216 	while (dp = readdir(d)) {
1217 		if (dp->d_ino == 0)
1218 			continue;
1219 		if ((strcmp(dp->d_name, ".") == 0) ||
1220 		    (strcmp(dp->d_name, "..") == 0))
1221 			continue;
1222 		if ((uint_t)strlen(name) + 1 + strlen(dp->d_name) >=
1223 			MAXPATHLEN - 1) {
1224 			error("%s/%s: name too long.\n", name, dp->d_name);
1225 			continue;
1226 		}
1227 		(void) snprintf(path, sizeof (path), "%s/%s",
1228 					name, dp->d_name);
1229 		vect[0] = path;
1230 		source(1, vect);
1231 	}
1232 	(void) closedir(d);
1233 	(void) desrcpwrite(rem, "E\n", 2);
1234 	(void) response();
1235 }
1236 
1237 static int
1238 response(void)
1239 {
1240 	register char *cp;
1241 	char ch, resp, rbuf[RCP_BUFSIZE];
1242 
1243 	if (desrcpread(rem, &resp, 1) != 1)
1244 		lostconn();
1245 	cp = rbuf;
1246 	switch (resp) {
1247 	case 0:				/* ok */
1248 		return (0);
1249 	default:
1250 		*cp++ = resp;
1251 		/* FALLTHROUGH */
1252 	case 1:				/* error, followed by err msg */
1253 	case 2:				/* fatal error, "" */
1254 		do {
1255 			if (desrcpread(rem, &ch, sizeof (ch)) != sizeof (ch))
1256 				lostconn();
1257 			*cp++ = ch;
1258 		} while (cp < &rbuf[RCP_BUFSIZE] && ch != '\n');
1259 
1260 		if (!iamremote)
1261 			(void) write(STDERR_FILENO, rbuf, cp - rbuf);
1262 		++errs;
1263 		if (resp == 1)
1264 			return (-1);
1265 		exit(1);
1266 	}
1267 	/*NOTREACHED*/
1268 }
1269 
1270 static void
1271 lostconn(void)
1272 {
1273 	if (!iamremote)
1274 		(void) fprintf(stderr, "rcp: lost connection\n");
1275 	exit(1);
1276 }
1277 
1278 
1279 static void
1280 sink(int argc, char *argv[])
1281 {
1282 	char *cp;
1283 	static BUF buffer;
1284 	struct stat stb;
1285 	struct timeval tv[2];
1286 	BUF *bp;
1287 	off_t i, j;
1288 	char ch, *targ, *why;
1289 	int amt, count, exists, first, mask, mode;
1290 	off_t size;
1291 	int ofd, setimes, targisdir, wrerr;
1292 	char *np, *vect[1], buf[RCP_BUFSIZE];
1293 	char *namebuf = NULL;
1294 	size_t namebuf_sz = 0;
1295 	size_t need;
1296 
1297 #define	atime	tv[0]
1298 #define	mtime	tv[1]
1299 #define	SCREWUP(str)	{ why = str; goto screwup; }
1300 
1301 	setimes = targisdir = 0;
1302 	mask = umask(0);
1303 	if (!pflag)
1304 		(void) umask(mask);
1305 	if (argc != 1) {
1306 		error("rcp: ambiguous target\n");
1307 		exit(1);
1308 	}
1309 	targ = *argv;
1310 	if (targetshouldbedirectory)
1311 		verifydir(targ);
1312 	(void) desrcpwrite(rem, "", 1);
1313 
1314 	if (stat(targ, &stb) == 0 && (stb.st_mode & S_IFMT) == S_IFDIR)
1315 		targisdir = 1;
1316 	for (first = 1; ; first = 0) {
1317 		cp = buf;
1318 		if (desrcpread(rem, cp, 1) <= 0) {
1319 			if (namebuf != NULL)
1320 				free(namebuf);
1321 			return;
1322 		}
1323 
1324 		if (*cp++ == '\n')
1325 			SCREWUP("unexpected <newline>");
1326 		do {
1327 			if (desrcpread(rem, &ch, sizeof (ch)) != sizeof (ch))
1328 				SCREWUP("lost connection");
1329 			*cp++ = ch;
1330 		} while (cp < &buf[RCP_BUFSIZE - 1] && ch != '\n');
1331 		*cp = 0;
1332 
1333 		if (buf[0] == '\01' || buf[0] == '\02') {
1334 			if (iamremote == 0)
1335 				(void) write(STDERR_FILENO, buf + 1,
1336 				    strlen(buf + 1));
1337 			if (buf[0] == '\02')
1338 				exit(1);
1339 			errs++;
1340 			continue;
1341 		}
1342 		if (buf[0] == 'E') {
1343 			(void) desrcpwrite(rem, "", 1);
1344 			if (namebuf != NULL)
1345 				free(namebuf);
1346 			return;
1347 		}
1348 
1349 		if (ch == '\n')
1350 			*--cp = 0;
1351 		cp = buf;
1352 		if (*cp == 'T') {
1353 			setimes++;
1354 			cp++;
1355 			mtime.tv_sec = strtol(cp, &cp, 0);
1356 			if (*cp++ != ' ')
1357 				SCREWUP("mtime.sec not delimited");
1358 			mtime.tv_usec = strtol(cp, &cp, 0);
1359 			if (*cp++ != ' ')
1360 				SCREWUP("mtime.usec not delimited");
1361 			atime.tv_sec = strtol(cp, &cp, 0);
1362 			if (*cp++ != ' ')
1363 				SCREWUP("atime.sec not delimited");
1364 			atime.tv_usec = strtol(cp, &cp, 0);
1365 			if (*cp++ != '\0')
1366 				SCREWUP("atime.usec not delimited");
1367 			(void) desrcpwrite(rem, "", 1);
1368 			continue;
1369 		}
1370 		if (*cp != 'C' && *cp != 'D') {
1371 			/*
1372 			 * Check for the case "rcp remote:foo\* local:bar".
1373 			 * In this case, the line "No match." can be returned
1374 			 * by the shell before the rcp command on the remote is
1375 			 * executed so the ^Aerror_message convention isn't
1376 			 * followed.
1377 			 */
1378 			if (first) {
1379 				error("%s\n", cp);
1380 				exit(1);
1381 			}
1382 			SCREWUP("expected control record");
1383 		}
1384 		mode = 0;
1385 		for (++cp; cp < buf + 5; cp++) {
1386 			if (*cp < '0' || *cp > '7')
1387 				SCREWUP("bad mode");
1388 			mode = (mode << 3) | (*cp - '0');
1389 		}
1390 		if (*cp++ != ' ')
1391 			SCREWUP("mode not delimited");
1392 		size = 0;
1393 		while (isdigit(*cp))
1394 			size = size * 10 + (*cp++ - '0');
1395 		if (*cp++ != ' ')
1396 			SCREWUP("size not delimited");
1397 		if (targisdir) {
1398 			need = strlen(targ) + sizeof ("/") + strlen(cp);
1399 			if (need > namebuf_sz) {
1400 			    if ((namebuf = realloc(namebuf, need)) == NULL) {
1401 					error("rcp: out of memory\n");
1402 					exit(1);
1403 			    }
1404 			    namebuf_sz = need;
1405 			}
1406 			(void) snprintf(namebuf, need, "%s%s%s", targ,
1407 			    *targ ? "/" : "", cp);
1408 			np = namebuf;
1409 		} else {
1410 			np = targ;
1411 		}
1412 
1413 		exists = stat(np, &stb) == 0;
1414 		if (buf[0] == 'D') {
1415 			if (exists) {
1416 				if ((stb.st_mode&S_IFMT) != S_IFDIR) {
1417 					if (aclflag) {
1418 						/*
1419 						 * consume acl in the pipe
1420 						 * fd = -1 to indicate the
1421 						 * special case
1422 						 */
1423 						if (recvacl(-1, exists, pflag)
1424 						    == ACL_FAIL) {
1425 							goto bad;
1426 						}
1427 					}
1428 					errno = ENOTDIR;
1429 					goto bad;
1430 				}
1431 				if (pflag)
1432 					(void) chmod(np, mode);
1433 			} else if (mkdir(np, mode) < 0) {
1434 				if (aclflag) {
1435 					/* consume acl in the pipe */
1436 					(void) recvacl(-1, exists, pflag);
1437 				}
1438 				goto bad;
1439 			}
1440 
1441 			/* acl support for directories */
1442 			if (aclflag) {
1443 				int dfd;
1444 
1445 				if ((dfd = open(np, O_RDONLY)) == -1)
1446 					goto bad;
1447 
1448 				/* get acl and set it to ofd */
1449 				if (recvacl(dfd, exists, pflag) == ACL_FAIL) {
1450 					(void) close(dfd);
1451 					if (!exists)
1452 						(void) rmdir(np);
1453 					goto bad;
1454 				}
1455 				(void) close(dfd);
1456 			}
1457 
1458 			vect[0] = np;
1459 			sink(1, vect);
1460 			if (setimes) {
1461 				setimes = 0;
1462 				if (utimes(np, tv) < 0)
1463 				    error("rcp: can't set times on %s: %s\n",
1464 					np, strerror(errno));
1465 			}
1466 			continue;
1467 		}
1468 
1469 		if ((ofd = open(np, O_WRONLY|O_CREAT, mode)) < 0) {
1470 bad:
1471 			error("rcp: %s: %s\n", np, strerror(errno));
1472 			continue;
1473 		}
1474 
1475 		/*
1476 		 * If the output file exists we have to force zflag off
1477 		 * to avoid erroneously seeking past old data.
1478 		 */
1479 		zopen(ofd, zflag && !exists);
1480 
1481 		if (exists && pflag)
1482 			(void) fchmod(ofd, mode);
1483 
1484 		(void) desrcpwrite(rem, "", 1);
1485 
1486 		/*
1487 		 * ACL support: receiving
1488 		 */
1489 		if (aclflag) {
1490 			/* get acl and set it to ofd */
1491 			if (recvacl(ofd, exists, pflag) == ACL_FAIL) {
1492 				(void) close(ofd);
1493 				if (!exists)
1494 					(void) unlink(np);
1495 				continue;
1496 			}
1497 		}
1498 
1499 		if ((bp = allocbuf(&buffer, ofd, RCP_BUFSIZE)) == 0) {
1500 			(void) close(ofd);
1501 			continue;
1502 		}
1503 		cp = bp->buf;
1504 		count = 0;
1505 		wrerr = 0;
1506 		for (i = 0; i < size; i += RCP_BUFSIZE) {
1507 			amt = RCP_BUFSIZE;
1508 			if (i + amt > size)
1509 				amt = size - i;
1510 			count += amt;
1511 			do {
1512 				j = desrcpread(rem, cp, amt);
1513 				if (j <= 0) {
1514 					int sverrno = errno;
1515 
1516 					/*
1517 					 * Connection to supplier lost.
1518 					 * Truncate file to correspond
1519 					 * to amount already transferred.
1520 					 *
1521 					 * Note that we must call ftruncate()
1522 					 * before any call to error() (which
1523 					 * might result in a SIGPIPE and
1524 					 * sudden death before we have a chance
1525 					 * to correct the file's size).
1526 					 */
1527 					size = lseek(ofd, 0, SEEK_CUR);
1528 					if ((ftruncate(ofd, size)  == -1) &&
1529 					    (errno != EINVAL) &&
1530 					    (errno != EACCES))
1531 #define		TRUNCERR	"rcp: can't truncate %s: %s\n"
1532 						error(TRUNCERR, np,
1533 						    strerror(errno));
1534 					error("rcp: %s\n",
1535 					    j ? strerror(sverrno) :
1536 					    "dropped connection");
1537 					(void) close(ofd);
1538 					exit(1);
1539 				}
1540 				amt -= j;
1541 				cp += j;
1542 			} while (amt > 0);
1543 			if (count == bp->cnt) {
1544 				cp = bp->buf;
1545 				if (wrerr == 0 &&
1546 				    zwrite(ofd, cp, count) < 0)
1547 					wrerr++;
1548 				count = 0;
1549 			}
1550 		}
1551 		if (count != 0 && wrerr == 0 &&
1552 		    zwrite(ofd, bp->buf, count) < 0)
1553 			wrerr++;
1554 		if (zclose(ofd) < 0)
1555 			wrerr++;
1556 
1557 
1558 		if ((ftruncate(ofd, size)  == -1) && (errno != EINVAL) &&
1559 		    (errno != EACCES)) {
1560 			error(TRUNCERR, np, strerror(errno));
1561 		}
1562 		(void) close(ofd);
1563 		(void) response();
1564 		if (setimes) {
1565 			setimes = 0;
1566 			if (utimes(np, tv) < 0)
1567 				error("rcp: can't set times on %s: %s\n",
1568 				    np, strerror(errno));
1569 		}
1570 		if (wrerr)
1571 			error("rcp: %s: %s\n", np, strerror(errno));
1572 		else
1573 			(void) desrcpwrite(rem, "", 1);
1574 	}
1575 screwup:
1576 	error("rcp: protocol screwup: %s\n", why);
1577 	exit(1);
1578 }
1579 
1580 #ifndef roundup
1581 #define	roundup(x, y)   ((((x)+((y)-1))/(y))*(y))
1582 #endif /* !roundup */
1583 
1584 static BUF *
1585 allocbuf(BUF *bp, int fd, int blksize)
1586 {
1587 	struct stat stb;
1588 	int size;
1589 
1590 	if (fstat(fd, &stb) < 0) {
1591 		error("rcp: fstat: %s\n", strerror(errno));
1592 		return (0);
1593 	}
1594 	size = roundup(stb.st_blksize, blksize);
1595 	if (size == 0)
1596 		size = blksize;
1597 	if (bp->cnt < size) {
1598 		if (bp->buf != 0)
1599 			free(bp->buf);
1600 		bp->buf = (char *)malloc((uint_t)size);
1601 		if (!bp->buf) {
1602 			error("rcp: malloc: out of memory\n");
1603 			return (0);
1604 		}
1605 	}
1606 	bp->cnt = size;
1607 	return (bp);
1608 }
1609 
1610 static void
1611 usage(void)
1612 {
1613 	(void) fprintf(stderr, "%s: \t%s\t%s", gettext("Usage"),
1614 		gettext("\trcp [-p] [-a] [-x] [-k realm] [-PN / -PO] "
1615 #ifdef DEBUG
1616 			"[-D port] "
1617 #endif /* DEBUG */
1618 			"f1 f2; or:\n"),
1619 		gettext("\trcp [-r] [-p] [-a] [-x] "
1620 #ifdef DEBUG
1621 			"[-D port] "
1622 #endif /* DEBUG */
1623 			"[-k realm] [-PN / -PO] f1...fn d2\n"));
1624 	exit(1);
1625 }
1626 
1627 
1628 /*
1629  * sparse file support
1630  */
1631 
1632 static off_t zbsize;
1633 static off_t zlastseek;
1634 
1635 /* is it ok to try to create holes? */
1636 static void
1637 zopen(int fd, int flag)
1638 {
1639 	struct stat st;
1640 
1641 	zbsize = 0;
1642 	zlastseek = 0;
1643 
1644 	if (flag &&
1645 		fstat(fd, &st) == 0 &&
1646 		(st.st_mode & S_IFMT) == S_IFREG)
1647 		zbsize = st.st_blksize;
1648 }
1649 
1650 /* write and/or seek */
1651 static int
1652 zwrite(int fd, char *buf, int nbytes)
1653 {
1654 	off_t block = zbsize ? zbsize : nbytes;
1655 
1656 	do {
1657 		if (block > nbytes)
1658 			block = nbytes;
1659 		nbytes -= block;
1660 
1661 		if (!zbsize || notzero(buf, block)) {
1662 			register int n, count = block;
1663 
1664 			do {
1665 				if ((n = write(fd, buf, count)) < 0)
1666 					return (-1);
1667 				buf += n;
1668 			} while ((count -= n) > 0);
1669 			zlastseek = 0;
1670 		} else {
1671 			if (lseek(fd, (off_t)block, SEEK_CUR) < 0)
1672 				return (-1);
1673 			buf += block;
1674 			zlastseek = 1;
1675 		}
1676 	} while (nbytes > 0);
1677 
1678 	return (0);
1679 }
1680 
1681 /* write last byte of file if necessary */
1682 static int
1683 zclose(int fd)
1684 {
1685 	zbsize = 0;
1686 
1687 	if (zlastseek && (lseek(fd, (off_t)-1, SEEK_CUR) < 0 ||
1688 		zwrite(fd, "", 1) < 0))
1689 		return (-1);
1690 	else
1691 		return (0);
1692 }
1693 
1694 /* return true if buffer is not all zeros */
1695 static int
1696 notzero(char *p, int n)
1697 {
1698 	register int result = 0;
1699 
1700 	while ((int)p & 3 && --n >= 0)
1701 		result |= *p++;
1702 
1703 	while ((n -= 4 * sizeof (int)) >= 0) {
1704 		/* LINTED */
1705 		result |= ((int *)p)[0];
1706 		/* LINTED */
1707 		result |= ((int *)p)[1];
1708 		/* LINTED */
1709 		result |= ((int *)p)[2];
1710 		/* LINTED */
1711 		result |= ((int *)p)[3];
1712 		if (result)
1713 			return (result);
1714 		p += 4 * sizeof (int);
1715 	}
1716 	n += 4 * sizeof (int);
1717 
1718 	while (--n >= 0)
1719 		result |= *p++;
1720 
1721 	return (result);
1722 }
1723 
1724 /*
1725  * New functions to support ACLs
1726  */
1727 
1728 /*
1729  * Get acl from f and send it over.
1730  * ACL record includes acl entry count, acl text length, and acl text.
1731  */
1732 static int
1733 sendacl(int f)
1734 {
1735 	int		aclcnt;
1736 	aclent_t	*aclbufp;
1737 	int		aclsize;
1738 	char		*acltext;
1739 	char		buf[BUFSIZ];
1740 
1741 	if ((aclcnt = facl(f, GETACLCNT, 0, NULL)) < 0) {
1742 		error("can't get acl count \n");
1743 		return (ACL_FAIL);
1744 	}
1745 
1746 	/* send the acl count over */
1747 	(void) snprintf(buf, sizeof (buf), "A%d\n", aclcnt);
1748 	(void) desrcpwrite(rem, buf, strlen(buf));
1749 
1750 	/* only send acl when it is non-trivial */
1751 	if (aclcnt > MIN_ACL_ENTRIES) {
1752 		aclsize = aclcnt * sizeof (aclent_t);
1753 		if ((aclbufp = (aclent_t *)malloc(aclsize)) == NULL) {
1754 			error("rcp: cant allocate memory: aclcnt %d\n",
1755 					aclcnt);
1756 			exit(1);
1757 		}
1758 		if (facl(f, GETACL, aclcnt, aclbufp) < 0) {
1759 			error("rcp: failed to get acl\n");
1760 			return (ACL_FAIL);
1761 		}
1762 		acltext = acltotext(aclbufp, aclcnt);
1763 		if (acltext == NULL) {
1764 			error("rcp: failed to convert to text\n");
1765 			return (ACL_FAIL);
1766 		}
1767 
1768 		/* send ACLs over: send the length first */
1769 		(void) snprintf(buf, sizeof (buf), "A%d\n", strlen(acltext));
1770 
1771 		(void) desrcpwrite(rem, buf, strlen(buf));
1772 		(void) desrcpwrite(rem, acltext, strlen(acltext));
1773 		free(acltext);
1774 		free(aclbufp);
1775 		if (response() < 0)
1776 			return (ACL_FAIL);
1777 
1778 	}
1779 	return (ACL_OK);
1780 }
1781 
1782 /*
1783  * Use this routine to get acl entry count and acl text size (in bytes)
1784  */
1785 static int
1786 getaclinfo(int *cnt)
1787 {
1788 	char		buf[BUFSIZ];
1789 	char		*cp;
1790 	char		ch;
1791 
1792 	/* get acl count */
1793 	cp = buf;
1794 	if (desrcpread(rem, cp, 1) <= 0)
1795 		return (ACL_FAIL);
1796 	if (*cp++ != 'A') {
1797 		error("rcp: expect an ACL record, but got %c\n", *cp);
1798 		return (ACL_FAIL);
1799 	}
1800 	do {
1801 		if (desrcpread(rem, &ch, sizeof (ch)) != sizeof (ch)) {
1802 			error("rcp: lost connection ..\n");
1803 			return (ACL_FAIL);
1804 		}
1805 		*cp++ = ch;
1806 	} while (cp < &buf[BUFSIZ - 1] && ch != '\n');
1807 	if (ch != '\n') {
1808 		error("rcp: ACL record corrupted \n");
1809 		return (ACL_FAIL);
1810 	}
1811 	cp = &buf[1];
1812 	*cnt = strtol(cp, &cp, 0);
1813 	if (*cp != '\n') {
1814 		error("rcp: ACL record corrupted \n");
1815 		return (ACL_FAIL);
1816 	}
1817 	return (ACL_OK);
1818 }
1819 
1820 
1821 /*
1822  * Receive acl from the pipe and set it to f
1823  */
1824 static int
1825 recvacl(int f, int exists, int preserve)
1826 {
1827 	int		aclcnt;		/* acl entry count */
1828 	int		aclsize;	/* acl text length */
1829 	int		j;
1830 	char		*tp;
1831 	char		*acltext;	/* external format */
1832 	aclent_t	*aclbufp;	/* internal format */
1833 
1834 	/* get acl count */
1835 	if (getaclinfo(&aclcnt) != ACL_OK)
1836 		return (ACL_FAIL);
1837 
1838 	if (aclcnt > MIN_ACL_ENTRIES) {
1839 		/* get acl text size */
1840 		if (getaclinfo(&aclsize) != ACL_OK)
1841 			return (ACL_FAIL);
1842 		if ((acltext = malloc(aclsize + 1)) == NULL) {
1843 			error("rcp: cant allocate memory: %d\n", aclsize);
1844 			return (ACL_FAIL);
1845 		}
1846 
1847 		tp = acltext;
1848 		do {
1849 			j = desrcpread(rem, tp, aclsize);
1850 			if (j <= 0) {
1851 				error("rcp: %s\n", j ? strerror(errno) :
1852 				    "dropped connection");
1853 				exit(1);
1854 			}
1855 			aclsize -= j;
1856 			tp += j;
1857 		} while (aclsize > 0);
1858 		*tp = '\0';
1859 
1860 		if (preserve || !exists) {
1861 			aclbufp = aclfromtext(acltext, &aclcnt);
1862 			if (aclbufp == NULL) {
1863 				error("rcp: failed to parse acl\n");
1864 				return (ACL_FAIL);
1865 			}
1866 			if (f != -1) {
1867 				if (facl(f, SETACL, aclcnt, aclbufp) < 0) {
1868 					error("rcp: failed to set acl\n");
1869 					return (ACL_FAIL);
1870 				}
1871 			}
1872 			/* -1 means that just consume the data in the pipe */
1873 			free(aclbufp);
1874 		}
1875 		free(acltext);
1876 		(void) desrcpwrite(rem, "", 1);
1877 	}
1878 	return (ACL_OK);
1879 }
1880 
1881 
1882 static char *
1883 search_char(unsigned char *cp, unsigned char chr)
1884 {
1885 	int	len;
1886 
1887 	while (*cp) {
1888 		if (*cp == chr)
1889 			return ((char *)cp);
1890 		if ((len = mblen((char *)cp, MB_CUR_MAX)) <= 0)
1891 			len = 1;
1892 		cp += len;
1893 	}
1894 	return (0);
1895 }
1896 
1897 
1898 static int
1899 desrcpread(int fd, char *buf, int len)
1900 {
1901 	return ((int)desread(fd, buf, len, 0));
1902 }
1903 
1904 static int
1905 desrcpwrite(int fd, char *buf, int len)
1906 {
1907 	/*
1908 	 * Note that rcp depends on the same file descriptor being both
1909 	 * input and output to the remote side.  This is bogus, especially
1910 	 * when rcp is being run by a rsh that pipes. Fix it here because
1911 	 * it would require significantly more work in other places.
1912 	 * --hartmans 1/96
1913 	 */
1914 
1915 	if (fd == 0)
1916 		fd = 1;
1917 	return ((int)deswrite(fd, buf, len, 0));
1918 }
1919 
1920 static char **
1921 save_argv(int argc, char **argv)
1922 {
1923 	int i;
1924 
1925 	char **local_argv = (char **)calloc((unsigned)argc + 1,
1926 	    (unsigned)sizeof (char *));
1927 
1928 	/*
1929 	 * allocate an extra pointer, so that it is initialized to NULL and
1930 	 * execv() will work
1931 	 */
1932 	for (i = 0; i < argc; i++) {
1933 		local_argv[i] = strsave(argv[i]);
1934 	}
1935 
1936 	return (local_argv);
1937 }
1938 
1939 #define	SIZEOF_INADDR sizeof (struct in_addr)
1940 
1941 static void
1942 answer_auth(char *config_file, char *ccache_file)
1943 {
1944 	krb5_data pname_data, msg;
1945 	krb5_creds creds, *new_creds;
1946 	krb5_ccache cc;
1947 	krb5_auth_context auth_context = NULL;
1948 
1949 	if (config_file) {
1950 		const char *filenames[2];
1951 
1952 		filenames[1] = NULL;
1953 		filenames[0] = config_file;
1954 		if (krb5_set_config_files(bsd_context, filenames))
1955 			exit(1);
1956 	}
1957 	(void) memset((char *)&creds, 0, sizeof (creds));
1958 
1959 	if (krb5_read_message(bsd_context, (krb5_pointer) &rem, &pname_data))
1960 		exit(1);
1961 
1962 	if (krb5_read_message(bsd_context, (krb5_pointer) &rem,
1963 	    &creds.second_ticket))
1964 		exit(1);
1965 
1966 	if (ccache_file == NULL) {
1967 		if (krb5_cc_default(bsd_context, &cc))
1968 			exit(1);
1969 	} else {
1970 		if (krb5_cc_resolve(bsd_context, ccache_file, &cc))
1971 			exit(1);
1972 	}
1973 
1974 	if (krb5_cc_get_principal(bsd_context, cc, &creds.client))
1975 		exit(1);
1976 
1977 	if (krb5_parse_name(bsd_context, pname_data.data, &creds.server))
1978 		exit(1);
1979 
1980 	krb5_xfree(pname_data.data);
1981 	if (krb5_get_credentials(bsd_context, KRB5_GC_USER_USER, cc, &creds,
1982 	    &new_creds))
1983 		exit(1);
1984 
1985 	if (krb5_mk_req_extended(bsd_context, &auth_context,
1986 	    AP_OPTS_USE_SESSION_KEY, NULL, new_creds, &msg))
1987 		exit(1);
1988 
1989 	if (krb5_write_message(bsd_context, (krb5_pointer) & rem, &msg)) {
1990 		krb5_xfree(msg.data);
1991 		exit(1);
1992 	}
1993 	/* setup eblock for des_read and write */
1994 	krb5_copy_keyblock(bsd_context, &new_creds->keyblock, &session_key);
1995 
1996 	/* OK process key */
1997 	eblock.crypto_entry = session_key->enctype;
1998 	eblock.key = (krb5_keyblock *)session_key;
1999 
2000 	init_encrypt(encrypt_flag, bsd_context, KCMD_OLD_PROTOCOL,
2001 	    &desinbuf, &desoutbuf, CLIENT, &eblock);
2002 	/* cleanup */
2003 	krb5_free_cred_contents(bsd_context, &creds);
2004 	krb5_free_creds(bsd_context, new_creds);
2005 	krb5_xfree(msg.data);
2006 }
2007 
2008 
2009 static void
2010 try_normal_rcp(int cur_argc, char **cur_argv)
2011 {
2012 	char *target;
2013 
2014 	/*
2015 	 * Reset all KRB5 relevant flags and set the
2016 	 * cmd-buffer so that normal rcp works
2017 	 */
2018 	krb5auth_flag = encrypt_flag = encrypt_done = 0;
2019 	cmd = cmd_orig;
2020 	cmd_sunw = cmd_sunw_orig;
2021 
2022 	if (cur_argc < 2)
2023 		usage();
2024 
2025 	if (cur_argc > 2)
2026 		targetshouldbedirectory = 1;
2027 
2028 	rem = -1;
2029 
2030 	prev_argc = cur_argc;
2031 	prev_argv = save_argv(cur_argc, cur_argv);
2032 
2033 	(void) init_service(krb5auth_flag);
2034 
2035 	if (target = colon(cur_argv[cur_argc - 1])) {
2036 		toremote(target, cur_argc, cur_argv);
2037 	} else {
2038 		tolocal(cur_argc, cur_argv);
2039 		if (targetshouldbedirectory)
2040 			verifydir(cur_argv[cur_argc - 1]);
2041 	}
2042 	exit(errs);
2043 	/* NOTREACHED */
2044 }
2045 
2046 
2047 static int
2048 init_service(int krb5flag)
2049 {
2050 	struct servent *sp;
2051 	boolean_t success = B_FALSE;
2052 
2053 	if (krb5flag > 0) {
2054 		sp = getservbyname("kshell", "tcp");
2055 		if (sp == NULL) {
2056 			(void) fprintf(stderr,
2057 				gettext("rcp: kshell/tcp: unknown service.\n"
2058 				"trying normal shell/tcp service\n"));
2059 		} else {
2060 			portnumber = sp->s_port;
2061 			success = B_TRUE;
2062 		}
2063 	} else {
2064 		portnumber = htons(IPPORT_CMDSERVER);
2065 		success = B_TRUE;
2066 	}
2067 	return (success);
2068 }
2069 
2070 /*PRINTFLIKE1*/
2071 static void
2072 error(char *fmt, ...)
2073 {
2074 	va_list ap;
2075 	char buf[RCP_BUFSIZE];
2076 	char *cp = buf;
2077 
2078 	va_start(ap, fmt);
2079 	errs++;
2080 	*cp++ = 1;
2081 	(void) vsnprintf(cp, sizeof (buf) - 1, fmt, ap);
2082 	va_end(ap);
2083 
2084 	(void) desrcpwrite(rem, buf, strlen(buf));
2085 	if (iamremote == 0)
2086 		(void) write(2, buf + 1, strlen(buf + 1));
2087 }
2088