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