xref: /titanic_52/usr/src/cmd/cmd-inet/usr.bin/rcp.c (revision 65d61b8c5bb0da98e137e8d74245d8f53d690237)
1 /*
2  * Copyright 2007 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 	struct linger lingerbuf;
1091 
1092 	for (x = 0; x < argc; x++) {
1093 		name = argv[x];
1094 		if ((f = open(name, O_RDONLY, 0)) < 0) {
1095 			error("rcp: %s: %s\n", name, strerror(errno));
1096 			continue;
1097 		}
1098 		if (fstat(f, &stb) < 0)
1099 			goto notreg;
1100 		switch (stb.st_mode&S_IFMT) {
1101 
1102 		case S_IFREG:
1103 			break;
1104 
1105 		case S_IFDIR:
1106 			if (iamrecursive) {
1107 				(void) close(f);
1108 				rsource(name, &stb);
1109 				continue;
1110 			}
1111 			/* FALLTHROUGH */
1112 		default:
1113 notreg:
1114 			(void) close(f);
1115 			error("rcp: %s: not a plain file\n", name);
1116 			continue;
1117 		}
1118 		last = rindex(name, '/');
1119 		if (last == 0)
1120 			last = name;
1121 		else
1122 			last++;
1123 		if (pflag) {
1124 			time_t mtime, atime;
1125 			time_t now;
1126 
1127 			/*
1128 			 * Make it compatible with possible future
1129 			 * versions expecting microseconds.
1130 			 */
1131 			mtime = stb.st_mtime;
1132 			atime = stb.st_atime;
1133 
1134 			if ((mtime < 0) || (atime < 0)) {
1135 				now = time(NULL);
1136 
1137 				if (mtime < 0) {
1138 					mtime = now;
1139 					error("negative modification time on "
1140 					    "%s; not preserving\n", name);
1141 				}
1142 				if (atime < 0) {
1143 					atime = now;
1144 					error("negative access time on "
1145 					    "%s; not preserving\n", name);
1146 				}
1147 			}
1148 			(void) snprintf(buf, sizeof (buf), "T%ld 0 %ld 0\n",
1149 							mtime, atime);
1150 			(void) desrcpwrite(rem, buf, strlen(buf));
1151 			if (response() < 0) {
1152 				(void) close(f);
1153 				continue;
1154 			}
1155 		}
1156 		(void) snprintf(buf, sizeof (buf), "C%04o %lld %s\n",
1157 			(uint_t)(stb.st_mode & 07777), (longlong_t)stb.st_size,
1158 			last);
1159 		(void) desrcpwrite(rem, buf, strlen(buf));
1160 		if (response() < 0) {
1161 			(void) close(f);
1162 			continue;
1163 		}
1164 
1165 		/* ACL support: send */
1166 		if (aclflag | acl_aclflag) {
1167 			/* get acl from f and send it over */
1168 			if (sendacl(f) == ACL_FAIL) {
1169 				(void) close(f);
1170 				continue;
1171 			}
1172 		}
1173 		if ((krb5auth_flag > 0) || (iamremote == 1)) {
1174 			bp = allocbuf(&buffer, f, RCP_BUFSIZE);
1175 			if (bp == NULLBUF) {
1176 				(void) close(f);
1177 				continue;
1178 			}
1179 			readerr = 0;
1180 			for (i = 0; i < stb.st_size; i += bp->cnt) {
1181 				amt = bp->cnt;
1182 				if (i + amt > stb.st_size)
1183 					amt = stb.st_size - i;
1184 				if (readerr == 0 &&
1185 				    read(f, bp->buf, amt) != amt)
1186 					readerr = errno;
1187 				(void) desrcpwrite(rem, bp->buf, amt);
1188 			}
1189 			(void) close(f);
1190 			if (readerr == 0)
1191 				(void) desrcpwrite(rem, "", 1);
1192 			else
1193 				error("rcp: %s: %s\n", name,
1194 				    error_message(readerr));
1195 		} else {
1196 			cnt = off = 0;
1197 			size = stb.st_size;
1198 			while (size != 0) {
1199 				amt = MIN(size, SENDFILE_SIZE);
1200 				cnt = sendfile(rem, f, &off, amt);
1201 				if (cnt == -1) {
1202 					if (errno == EINTR) {
1203 						continue;
1204 					} else {
1205 						break;
1206 					}
1207 				}
1208 				if (cnt == 0)
1209 					break;
1210 				size -= cnt;
1211 			}
1212 			if (cnt < 0) {
1213 				error("rcp: %s: %s\n", name, strerror(errno));
1214 			} else if (cnt == 0 && size != 0) {
1215 				error("rcp: %s: unexpected end of file\n",
1216 					name);
1217 				lingerbuf.l_onoff = 1;
1218 				lingerbuf.l_linger = 0;
1219 				(void) setsockopt(rem, SOL_SOCKET, SO_LINGER,
1220 					&lingerbuf, sizeof (lingerbuf));
1221 				/*
1222 				 * When response() (see below) is invoked it
1223 				 * tries to read data from closed handle which
1224 				 * triggers error and lostconn() function.
1225 				 * lostconn() terminates the program with
1226 				 * appropriate message.
1227 				 */
1228 				(void) close(rem);
1229 				rem = -1;
1230 			} else {
1231 				(void) write(rem, "", 1);
1232 			}
1233 			(void) close(f);
1234 		}
1235 		(void) response();
1236 	}
1237 }
1238 
1239 
1240 static void
1241 rsource(char *name, struct stat *statp)
1242 {
1243 	DIR *d;
1244 	struct dirent *dp;
1245 	char *last, *vect[1];
1246 	char path[MAXPATHLEN];
1247 
1248 	if (!(d = opendir(name))) {
1249 		error("rcp: %s: %s\n", name, strerror(errno));
1250 		return;
1251 	}
1252 	last = rindex(name, '/');
1253 	if (last == 0)
1254 		last = name;
1255 	else
1256 		last++;
1257 	if (pflag) {
1258 		(void) snprintf(path, sizeof (path), "T%ld 0 %ld 0\n",
1259 				statp->st_mtime, statp->st_atime);
1260 		(void) desrcpwrite(rem, path, strlen(path));
1261 		if (response() < 0) {
1262 			(void) closedir(d);
1263 			return;
1264 		}
1265 	}
1266 	(void) snprintf(path, sizeof (path), "D%04o %d %s\n",
1267 	    (uint_t)(statp->st_mode & 07777), 0, last);
1268 	(void) desrcpwrite(rem, path, strlen(path));
1269 
1270 	/* acl support for directory */
1271 	if (aclflag) {
1272 		/* get acl from f and send it over */
1273 		if (sendacl(d->dd_fd) == ACL_FAIL) {
1274 			(void) closedir(d);
1275 			return;
1276 		}
1277 	}
1278 
1279 	if (response() < 0) {
1280 		(void) closedir(d);
1281 		return;
1282 	}
1283 
1284 	while (dp = readdir(d)) {
1285 		if (dp->d_ino == 0)
1286 			continue;
1287 		if ((strcmp(dp->d_name, ".") == 0) ||
1288 		    (strcmp(dp->d_name, "..") == 0))
1289 			continue;
1290 		if ((uint_t)strlen(name) + 1 + strlen(dp->d_name) >=
1291 			MAXPATHLEN - 1) {
1292 			error("%s/%s: name too long.\n", name, dp->d_name);
1293 			continue;
1294 		}
1295 		(void) snprintf(path, sizeof (path), "%s/%s",
1296 					name, dp->d_name);
1297 		vect[0] = path;
1298 		source(1, vect);
1299 	}
1300 	(void) closedir(d);
1301 	(void) desrcpwrite(rem, "E\n", 2);
1302 	(void) response();
1303 }
1304 
1305 static int
1306 response(void)
1307 {
1308 	register char *cp;
1309 	char ch, resp, rbuf[RCP_BUFSIZE];
1310 
1311 	if (desrcpread(rem, &resp, 1) != 1)
1312 		lostconn();
1313 	cp = rbuf;
1314 	switch (resp) {
1315 	case 0:				/* ok */
1316 		return (0);
1317 	default:
1318 		*cp++ = resp;
1319 		/* FALLTHROUGH */
1320 	case 1:				/* error, followed by err msg */
1321 	case 2:				/* fatal error, "" */
1322 		do {
1323 			if (desrcpread(rem, &ch, sizeof (ch)) != sizeof (ch))
1324 				lostconn();
1325 			*cp++ = ch;
1326 		} while (cp < &rbuf[RCP_BUFSIZE] && ch != '\n');
1327 
1328 		if (!iamremote)
1329 			(void) write(STDERR_FILENO, rbuf, cp - rbuf);
1330 		++errs;
1331 		if (resp == 1)
1332 			return (-1);
1333 		exit(1);
1334 	}
1335 	/*NOTREACHED*/
1336 }
1337 
1338 static void
1339 lostconn(void)
1340 {
1341 	if (!iamremote)
1342 		(void) fprintf(stderr, "rcp: lost connection\n");
1343 	exit(1);
1344 }
1345 
1346 
1347 static void
1348 sink(int argc, char *argv[])
1349 {
1350 	char *cp;
1351 	static BUF buffer;
1352 	struct stat stb;
1353 	struct timeval tv[2];
1354 	BUF *bp;
1355 	off_t i, j;
1356 	char ch, *targ, *why;
1357 	int amt, count, exists, first, mask, mode;
1358 	off_t size;
1359 	int ofd, setimes, targisdir, wrerr;
1360 	char *np, *vect[1], buf[RCP_BUFSIZE];
1361 	char *namebuf = NULL;
1362 	size_t namebuf_sz = 0;
1363 	size_t need;
1364 
1365 #define	atime	tv[0]
1366 #define	mtime	tv[1]
1367 #define	SCREWUP(str)	{ why = str; goto screwup; }
1368 
1369 	setimes = targisdir = 0;
1370 	mask = umask(0);
1371 	if (!pflag)
1372 		(void) umask(mask);
1373 	if (argc != 1) {
1374 		error("rcp: ambiguous target\n");
1375 		exit(1);
1376 	}
1377 	targ = *argv;
1378 	if (targetshouldbedirectory)
1379 		verifydir(targ);
1380 	(void) desrcpwrite(rem, "", 1);
1381 
1382 	if (stat(targ, &stb) == 0 && (stb.st_mode & S_IFMT) == S_IFDIR)
1383 		targisdir = 1;
1384 	for (first = 1; ; first = 0) {
1385 		cp = buf;
1386 		if (desrcpread(rem, cp, 1) <= 0) {
1387 			if (namebuf != NULL)
1388 				free(namebuf);
1389 			return;
1390 		}
1391 
1392 		if (*cp++ == '\n')
1393 			SCREWUP("unexpected <newline>");
1394 		do {
1395 			if (desrcpread(rem, &ch, sizeof (ch)) != sizeof (ch))
1396 				SCREWUP("lost connection");
1397 			*cp++ = ch;
1398 		} while (cp < &buf[RCP_BUFSIZE - 1] && ch != '\n');
1399 		*cp = 0;
1400 
1401 		if (buf[0] == '\01' || buf[0] == '\02') {
1402 			if (iamremote == 0)
1403 				(void) write(STDERR_FILENO, buf + 1,
1404 				    strlen(buf + 1));
1405 			if (buf[0] == '\02')
1406 				exit(1);
1407 			errs++;
1408 			continue;
1409 		}
1410 		if (buf[0] == 'E') {
1411 			(void) desrcpwrite(rem, "", 1);
1412 			if (namebuf != NULL)
1413 				free(namebuf);
1414 			return;
1415 		}
1416 
1417 		if (ch == '\n')
1418 			*--cp = 0;
1419 		cp = buf;
1420 		if (*cp == 'T') {
1421 			setimes++;
1422 			cp++;
1423 			mtime.tv_sec = strtol(cp, &cp, 0);
1424 			if (*cp++ != ' ')
1425 				SCREWUP("mtime.sec not delimited");
1426 			mtime.tv_usec = strtol(cp, &cp, 0);
1427 			if (*cp++ != ' ')
1428 				SCREWUP("mtime.usec not delimited");
1429 			atime.tv_sec = strtol(cp, &cp, 0);
1430 			if (*cp++ != ' ')
1431 				SCREWUP("atime.sec not delimited");
1432 			atime.tv_usec = strtol(cp, &cp, 0);
1433 			if (*cp++ != '\0')
1434 				SCREWUP("atime.usec not delimited");
1435 			(void) desrcpwrite(rem, "", 1);
1436 			continue;
1437 		}
1438 		if (*cp != 'C' && *cp != 'D') {
1439 			/*
1440 			 * Check for the case "rcp remote:foo\* local:bar".
1441 			 * In this case, the line "No match." can be returned
1442 			 * by the shell before the rcp command on the remote is
1443 			 * executed so the ^Aerror_message convention isn't
1444 			 * followed.
1445 			 */
1446 			if (first) {
1447 				error("%s\n", cp);
1448 				exit(1);
1449 			}
1450 			SCREWUP("expected control record");
1451 		}
1452 		mode = 0;
1453 		for (++cp; cp < buf + 5; cp++) {
1454 			if (*cp < '0' || *cp > '7')
1455 				SCREWUP("bad mode");
1456 			mode = (mode << 3) | (*cp - '0');
1457 		}
1458 		if (*cp++ != ' ')
1459 			SCREWUP("mode not delimited");
1460 		size = 0;
1461 		while (isdigit(*cp))
1462 			size = size * 10 + (*cp++ - '0');
1463 		if (*cp++ != ' ')
1464 			SCREWUP("size not delimited");
1465 		if (targisdir) {
1466 			need = strlen(targ) + sizeof ("/") + strlen(cp);
1467 			if (need > namebuf_sz) {
1468 			    if ((namebuf = realloc(namebuf, need)) == NULL) {
1469 					error("rcp: out of memory\n");
1470 					exit(1);
1471 			    }
1472 			    namebuf_sz = need;
1473 			}
1474 			(void) snprintf(namebuf, need, "%s%s%s", targ,
1475 			    *targ ? "/" : "", cp);
1476 			np = namebuf;
1477 		} else {
1478 			np = targ;
1479 		}
1480 
1481 		exists = stat(np, &stb) == 0;
1482 		if (buf[0] == 'D') {
1483 			if (exists) {
1484 				if ((stb.st_mode&S_IFMT) != S_IFDIR) {
1485 					if (aclflag | acl_aclflag) {
1486 						/*
1487 						 * consume acl in the pipe
1488 						 * fd = -1 to indicate the
1489 						 * special case
1490 						 */
1491 						if (recvacl(-1, exists, pflag)
1492 						    == ACL_FAIL) {
1493 							goto bad;
1494 						}
1495 					}
1496 					errno = ENOTDIR;
1497 					goto bad;
1498 				}
1499 				if (pflag)
1500 					(void) chmod(np, mode);
1501 			} else if (mkdir(np, mode) < 0) {
1502 				if (aclflag) {
1503 					/* consume acl in the pipe */
1504 					(void) recvacl(-1, exists, pflag);
1505 				}
1506 				goto bad;
1507 			}
1508 
1509 			/* acl support for directories */
1510 			if (aclflag | acl_aclflag) {
1511 				int dfd;
1512 
1513 				if ((dfd = open(np, O_RDONLY)) == -1)
1514 					goto bad;
1515 
1516 				/* get acl and set it to ofd */
1517 				if (recvacl(dfd, exists, pflag) == ACL_FAIL) {
1518 					(void) close(dfd);
1519 					if (!exists)
1520 						(void) rmdir(np);
1521 					goto bad;
1522 				}
1523 				(void) close(dfd);
1524 			}
1525 
1526 			vect[0] = np;
1527 			sink(1, vect);
1528 			if (setimes) {
1529 				setimes = 0;
1530 				if (utimes(np, tv) < 0)
1531 				    error("rcp: can't set times on %s: %s\n",
1532 					np, strerror(errno));
1533 			}
1534 			continue;
1535 		}
1536 
1537 		if ((ofd = open(np, O_WRONLY|O_CREAT, mode)) < 0) {
1538 bad:
1539 			error("rcp: %s: %s\n", np, strerror(errno));
1540 			continue;
1541 		}
1542 
1543 		/*
1544 		 * If the output file exists we have to force zflag off
1545 		 * to avoid erroneously seeking past old data.
1546 		 */
1547 		zopen(ofd, zflag && !exists);
1548 
1549 		if (exists && pflag)
1550 			(void) fchmod(ofd, mode);
1551 
1552 		(void) desrcpwrite(rem, "", 1);
1553 
1554 		/*
1555 		 * ACL support: receiving
1556 		 */
1557 		if (aclflag | acl_aclflag) {
1558 			/* get acl and set it to ofd */
1559 			if (recvacl(ofd, exists, pflag) == ACL_FAIL) {
1560 				(void) close(ofd);
1561 				if (!exists)
1562 					(void) unlink(np);
1563 				continue;
1564 			}
1565 		}
1566 
1567 		if ((bp = allocbuf(&buffer, ofd, RCP_BUFSIZE)) == 0) {
1568 			(void) close(ofd);
1569 			continue;
1570 		}
1571 		cp = bp->buf;
1572 		count = 0;
1573 		wrerr = 0;
1574 		for (i = 0; i < size; i += RCP_BUFSIZE) {
1575 			amt = RCP_BUFSIZE;
1576 			if (i + amt > size)
1577 				amt = size - i;
1578 			count += amt;
1579 			do {
1580 				j = desrcpread(rem, cp, amt);
1581 				if (j <= 0) {
1582 					int sverrno = errno;
1583 
1584 					/*
1585 					 * Connection to supplier lost.
1586 					 * Truncate file to correspond
1587 					 * to amount already transferred.
1588 					 *
1589 					 * Note that we must call ftruncate()
1590 					 * before any call to error() (which
1591 					 * might result in a SIGPIPE and
1592 					 * sudden death before we have a chance
1593 					 * to correct the file's size).
1594 					 */
1595 					size = lseek(ofd, 0, SEEK_CUR);
1596 					if ((ftruncate(ofd, size)  == -1) &&
1597 					    (errno != EINVAL) &&
1598 					    (errno != EACCES))
1599 #define		TRUNCERR	"rcp: can't truncate %s: %s\n"
1600 						error(TRUNCERR, np,
1601 						    strerror(errno));
1602 					error("rcp: %s\n",
1603 					    j ? strerror(sverrno) :
1604 					    "dropped connection");
1605 					(void) close(ofd);
1606 					exit(1);
1607 				}
1608 				amt -= j;
1609 				cp += j;
1610 			} while (amt > 0);
1611 			if (count == bp->cnt) {
1612 				cp = bp->buf;
1613 				if (wrerr == 0 &&
1614 				    zwrite(ofd, cp, count) < 0)
1615 					wrerr++;
1616 				count = 0;
1617 			}
1618 		}
1619 		if (count != 0 && wrerr == 0 &&
1620 		    zwrite(ofd, bp->buf, count) < 0)
1621 			wrerr++;
1622 		if (zclose(ofd) < 0)
1623 			wrerr++;
1624 
1625 
1626 		if ((ftruncate(ofd, size)  == -1) && (errno != EINVAL) &&
1627 		    (errno != EACCES)) {
1628 			error(TRUNCERR, np, strerror(errno));
1629 		}
1630 		(void) close(ofd);
1631 		(void) response();
1632 		if (setimes) {
1633 			setimes = 0;
1634 			if (utimes(np, tv) < 0)
1635 				error("rcp: can't set times on %s: %s\n",
1636 				    np, strerror(errno));
1637 		}
1638 		if (wrerr)
1639 			error("rcp: %s: %s\n", np, strerror(errno));
1640 		else
1641 			(void) desrcpwrite(rem, "", 1);
1642 	}
1643 screwup:
1644 	error("rcp: protocol screwup: %s\n", why);
1645 	exit(1);
1646 }
1647 
1648 #ifndef roundup
1649 #define	roundup(x, y)   ((((x)+((y)-1))/(y))*(y))
1650 #endif /* !roundup */
1651 
1652 static BUF *
1653 allocbuf(BUF *bp, int fd, int blksize)
1654 {
1655 	struct stat stb;
1656 	int size;
1657 
1658 	if (fstat(fd, &stb) < 0) {
1659 		error("rcp: fstat: %s\n", strerror(errno));
1660 		return (0);
1661 	}
1662 	size = roundup(stb.st_blksize, blksize);
1663 	if (size == 0)
1664 		size = blksize;
1665 	if (bp->cnt < size) {
1666 		if (bp->buf != 0)
1667 			free(bp->buf);
1668 		bp->buf = (char *)malloc((uint_t)size);
1669 		if (!bp->buf) {
1670 			error("rcp: malloc: out of memory\n");
1671 			return (0);
1672 		}
1673 	}
1674 	bp->cnt = size;
1675 	return (bp);
1676 }
1677 
1678 static void
1679 usage(void)
1680 {
1681 	(void) fprintf(stderr, "%s: \t%s\t%s", gettext("Usage"),
1682 		gettext("\trcp [-p] [-a] [-x] [-k realm] [-PN / -PO] "
1683 #ifdef DEBUG
1684 			"[-D port] "
1685 #endif /* DEBUG */
1686 			"f1 f2; or:\n"),
1687 		gettext("\trcp [-r] [-p] [-a] [-x] "
1688 #ifdef DEBUG
1689 			"[-D port] "
1690 #endif /* DEBUG */
1691 			"[-k realm] [-PN / -PO] f1...fn d2\n"));
1692 	exit(1);
1693 }
1694 
1695 
1696 /*
1697  * sparse file support
1698  */
1699 
1700 static off_t zbsize;
1701 static off_t zlastseek;
1702 
1703 /* is it ok to try to create holes? */
1704 static void
1705 zopen(int fd, int flag)
1706 {
1707 	struct stat st;
1708 
1709 	zbsize = 0;
1710 	zlastseek = 0;
1711 
1712 	if (flag &&
1713 		fstat(fd, &st) == 0 &&
1714 		(st.st_mode & S_IFMT) == S_IFREG)
1715 		zbsize = st.st_blksize;
1716 }
1717 
1718 /* write and/or seek */
1719 static int
1720 zwrite(int fd, char *buf, int nbytes)
1721 {
1722 	off_t block = zbsize ? zbsize : nbytes;
1723 
1724 	do {
1725 		if (block > nbytes)
1726 			block = nbytes;
1727 		nbytes -= block;
1728 
1729 		if (!zbsize || notzero(buf, block)) {
1730 			register int n, count = block;
1731 
1732 			do {
1733 				if ((n = write(fd, buf, count)) < 0)
1734 					return (-1);
1735 				buf += n;
1736 			} while ((count -= n) > 0);
1737 			zlastseek = 0;
1738 		} else {
1739 			if (lseek(fd, (off_t)block, SEEK_CUR) < 0)
1740 				return (-1);
1741 			buf += block;
1742 			zlastseek = 1;
1743 		}
1744 	} while (nbytes > 0);
1745 
1746 	return (0);
1747 }
1748 
1749 /* write last byte of file if necessary */
1750 static int
1751 zclose(int fd)
1752 {
1753 	zbsize = 0;
1754 
1755 	if (zlastseek && (lseek(fd, (off_t)-1, SEEK_CUR) < 0 ||
1756 		zwrite(fd, "", 1) < 0))
1757 		return (-1);
1758 	else
1759 		return (0);
1760 }
1761 
1762 /* return true if buffer is not all zeros */
1763 static int
1764 notzero(char *p, int n)
1765 {
1766 	register int result = 0;
1767 
1768 	while ((int)p & 3 && --n >= 0)
1769 		result |= *p++;
1770 
1771 	while ((n -= 4 * sizeof (int)) >= 0) {
1772 		/* LINTED */
1773 		result |= ((int *)p)[0];
1774 		/* LINTED */
1775 		result |= ((int *)p)[1];
1776 		/* LINTED */
1777 		result |= ((int *)p)[2];
1778 		/* LINTED */
1779 		result |= ((int *)p)[3];
1780 		if (result)
1781 			return (result);
1782 		p += 4 * sizeof (int);
1783 	}
1784 	n += 4 * sizeof (int);
1785 
1786 	while (--n >= 0)
1787 		result |= *p++;
1788 
1789 	return (result);
1790 }
1791 
1792 /*
1793  * New functions to support ACLs
1794  */
1795 
1796 /*
1797  * Get acl from f and send it over.
1798  * ACL record includes acl entry count, acl text length, and acl text.
1799  */
1800 static int
1801 sendacl(int f)
1802 {
1803 	int		aclcnt;
1804 	char		*acltext;
1805 	char		buf[BUFSIZ];
1806 	acl_t		*aclp;
1807 	char		acltype;
1808 	int		aclerror;
1809 	int		trivial;
1810 
1811 
1812 	aclerror = facl_get(f, ACL_NO_TRIVIAL, &aclp);
1813 	if (aclerror != 0) {
1814 		error("can't retrieve ACL: %s \n", acl_strerror(aclerror));
1815 		return (ACL_FAIL);
1816 	}
1817 
1818 	/*
1819 	 * if acl type is not ACLENT_T and were operating in acl_aclflag == 0
1820 	 * then don't do the malloc and facl(fd, getcntcmd,...);
1821 	 * since the remote side doesn't support alternate style ACL's.
1822 	 */
1823 
1824 	if (aclp && (acl_type(aclp) != ACLENT_T) && (acl_aclflag == 0)) {
1825 		aclcnt = MIN_ACL_ENTRIES;
1826 		acltype = 'A';
1827 		trivial = ACL_IS_TRIVIAL;
1828 	} else {
1829 
1830 		aclcnt = (aclp != NULL) ? acl_cnt(aclp) : 0;
1831 
1832 		if (aclp) {
1833 			acltype = (acl_type(aclp) != ACLENT_T) ? 'Z' : 'A';
1834 			aclcnt = acl_cnt(aclp);
1835 			trivial = (acl_flags(aclp) & ACL_IS_TRIVIAL);
1836 		} else {
1837 			acltype = 'A';
1838 			aclcnt = MIN_ACL_ENTRIES;
1839 			trivial = ACL_IS_TRIVIAL;
1840 		}
1841 
1842 	}
1843 
1844 	/* send the acl count over */
1845 	(void) snprintf(buf, sizeof (buf), "%c%d\n", acltype, aclcnt);
1846 	(void) desrcpwrite(rem, buf, strlen(buf));
1847 
1848 	/*
1849 	 * only send acl when we have an aclp, which would
1850 	 * imply its not trivial.
1851 	 */
1852 	if (aclp && (trivial != ACL_IS_TRIVIAL)) {
1853 		acltext = acl_totext(aclp, 0);
1854 		if (acltext == NULL) {
1855 			error("rcp: failed to convert to text\n");
1856 			acl_free(aclp);
1857 			return (ACL_FAIL);
1858 		}
1859 
1860 		/* send ACLs over: send the length first */
1861 		(void) snprintf(buf, sizeof (buf), "%c%d\n",
1862 		    acltype, strlen(acltext));
1863 
1864 		(void) desrcpwrite(rem, buf, strlen(buf));
1865 		(void) desrcpwrite(rem, acltext, strlen(acltext));
1866 		free(acltext);
1867 		if (response() < 0) {
1868 			acl_free(aclp);
1869 			return (ACL_FAIL);
1870 		}
1871 
1872 	}
1873 
1874 	if (aclp)
1875 		acl_free(aclp);
1876 	return (ACL_OK);
1877 }
1878 
1879 /*
1880  * Use this routine to get acl entry count and acl text size (in bytes)
1881  */
1882 static int
1883 getaclinfo(int *cnt, int *acltype)
1884 {
1885 	char		buf[BUFSIZ];
1886 	char		*cp;
1887 	char		ch;
1888 
1889 	/* get acl count */
1890 	cp = buf;
1891 	if (desrcpread(rem, cp, 1) <= 0)
1892 		return (ACL_FAIL);
1893 
1894 	switch (*cp++) {
1895 	case 'A':
1896 		*acltype = 0;
1897 		break;
1898 	case 'Z':
1899 		*acltype = 1;
1900 		break;
1901 	default:
1902 		error("rcp: expect an ACL record, but got %c\n", *cp);
1903 		return (ACL_FAIL);
1904 	}
1905 	do {
1906 		if (desrcpread(rem, &ch, sizeof (ch)) != sizeof (ch)) {
1907 			error("rcp: lost connection ..\n");
1908 			return (ACL_FAIL);
1909 		}
1910 		*cp++ = ch;
1911 	} while (cp < &buf[BUFSIZ - 1] && ch != '\n');
1912 	if (ch != '\n') {
1913 		error("rcp: ACL record corrupted \n");
1914 		return (ACL_FAIL);
1915 	}
1916 	cp = &buf[1];
1917 	*cnt = strtol(cp, &cp, 0);
1918 	if (*cp != '\n') {
1919 		error("rcp: ACL record corrupted \n");
1920 		return (ACL_FAIL);
1921 	}
1922 	return (ACL_OK);
1923 }
1924 
1925 
1926 /*
1927  * Receive acl from the pipe and set it to f
1928  */
1929 static int
1930 recvacl(int f, int exists, int preserve)
1931 {
1932 	int		aclcnt;		/* acl entry count */
1933 	int		aclsize;	/* acl text length */
1934 	int		j;
1935 	char		*tp;
1936 	char		*acltext;	/* external format */
1937 	acl_t		*aclp;
1938 	int		acltype;
1939 	int		min_entries;
1940 	int		aclerror;
1941 
1942 	/* get acl count */
1943 	if (getaclinfo(&aclcnt, &acltype) != ACL_OK)
1944 		return (ACL_FAIL);
1945 
1946 	if (acltype == 0) {
1947 		min_entries = MIN_ACL_ENTRIES;
1948 	} else {
1949 		min_entries = 1;
1950 	}
1951 
1952 	if (aclcnt > min_entries) {
1953 		/* get acl text size */
1954 		if (getaclinfo(&aclsize, &acltype) != ACL_OK)
1955 			return (ACL_FAIL);
1956 		if ((acltext = malloc(aclsize + 1)) == NULL) {
1957 			error("rcp: cant allocate memory: %d\n", aclsize);
1958 			return (ACL_FAIL);
1959 		}
1960 
1961 		tp = acltext;
1962 		do {
1963 			j = desrcpread(rem, tp, aclsize);
1964 			if (j <= 0) {
1965 				error("rcp: %s\n", j ? strerror(errno) :
1966 				    "dropped connection");
1967 				exit(1);
1968 			}
1969 			aclsize -= j;
1970 			tp += j;
1971 		} while (aclsize > 0);
1972 		*tp = '\0';
1973 
1974 		if (preserve || !exists) {
1975 			aclerror = acl_fromtext(acltext, &aclp);
1976 			if (aclerror != 0) {
1977 				error("rcp: failed to parse acl : %s\n",
1978 				    acl_strerror(aclerror));
1979 				free(acltext);
1980 				return (ACL_FAIL);
1981 			}
1982 
1983 			if (f != -1) {
1984 				if (facl_set(f, aclp) < 0) {
1985 					error("rcp: failed to set acl\n");
1986 					acl_free(aclp);
1987 					free(acltext);
1988 					return (ACL_FAIL);
1989 				}
1990 			}
1991 			/* -1 means that just consume the data in the pipe */
1992 			acl_free(aclp);
1993 		}
1994 		free(acltext);
1995 		(void) desrcpwrite(rem, "", 1);
1996 	}
1997 	return (ACL_OK);
1998 }
1999 
2000 
2001 static char *
2002 search_char(unsigned char *cp, unsigned char chr)
2003 {
2004 	int	len;
2005 
2006 	while (*cp) {
2007 		if (*cp == chr)
2008 			return ((char *)cp);
2009 		if ((len = mblen((char *)cp, MB_CUR_MAX)) <= 0)
2010 			len = 1;
2011 		cp += len;
2012 	}
2013 	return (0);
2014 }
2015 
2016 
2017 static int
2018 desrcpread(int fd, char *buf, int len)
2019 {
2020 	return ((int)desread(fd, buf, len, 0));
2021 }
2022 
2023 static int
2024 desrcpwrite(int fd, char *buf, int len)
2025 {
2026 	/*
2027 	 * Note that rcp depends on the same file descriptor being both
2028 	 * input and output to the remote side.  This is bogus, especially
2029 	 * when rcp is being run by a rsh that pipes. Fix it here because
2030 	 * it would require significantly more work in other places.
2031 	 * --hartmans 1/96
2032 	 */
2033 
2034 	if (fd == 0)
2035 		fd = 1;
2036 	return ((int)deswrite(fd, buf, len, 0));
2037 }
2038 
2039 static char **
2040 save_argv(int argc, char **argv)
2041 {
2042 	int i;
2043 
2044 	char **local_argv = (char **)calloc((unsigned)argc + 1,
2045 	    (unsigned)sizeof (char *));
2046 
2047 	/*
2048 	 * allocate an extra pointer, so that it is initialized to NULL and
2049 	 * execv() will work
2050 	 */
2051 	for (i = 0; i < argc; i++) {
2052 		local_argv[i] = strsave(argv[i]);
2053 	}
2054 
2055 	return (local_argv);
2056 }
2057 
2058 #define	SIZEOF_INADDR sizeof (struct in_addr)
2059 
2060 static void
2061 answer_auth(char *config_file, char *ccache_file)
2062 {
2063 	krb5_data pname_data, msg;
2064 	krb5_creds creds, *new_creds;
2065 	krb5_ccache cc;
2066 	krb5_auth_context auth_context = NULL;
2067 
2068 	if (config_file) {
2069 		const char *filenames[2];
2070 
2071 		filenames[1] = NULL;
2072 		filenames[0] = config_file;
2073 		if (krb5_set_config_files(bsd_context, filenames))
2074 			exit(1);
2075 	}
2076 	(void) memset((char *)&creds, 0, sizeof (creds));
2077 
2078 	if (krb5_read_message(bsd_context, (krb5_pointer) &rem, &pname_data))
2079 		exit(1);
2080 
2081 	if (krb5_read_message(bsd_context, (krb5_pointer) &rem,
2082 	    &creds.second_ticket))
2083 		exit(1);
2084 
2085 	if (ccache_file == NULL) {
2086 		if (krb5_cc_default(bsd_context, &cc))
2087 			exit(1);
2088 	} else {
2089 		if (krb5_cc_resolve(bsd_context, ccache_file, &cc))
2090 			exit(1);
2091 	}
2092 
2093 	if (krb5_cc_get_principal(bsd_context, cc, &creds.client))
2094 		exit(1);
2095 
2096 	if (krb5_parse_name(bsd_context, pname_data.data, &creds.server))
2097 		exit(1);
2098 
2099 	krb5_xfree(pname_data.data);
2100 	if (krb5_get_credentials(bsd_context, KRB5_GC_USER_USER, cc, &creds,
2101 	    &new_creds))
2102 		exit(1);
2103 
2104 	if (krb5_mk_req_extended(bsd_context, &auth_context,
2105 	    AP_OPTS_USE_SESSION_KEY, NULL, new_creds, &msg))
2106 		exit(1);
2107 
2108 	if (krb5_write_message(bsd_context, (krb5_pointer) & rem, &msg)) {
2109 		krb5_xfree(msg.data);
2110 		exit(1);
2111 	}
2112 	/* setup eblock for des_read and write */
2113 	krb5_copy_keyblock(bsd_context, &new_creds->keyblock, &session_key);
2114 
2115 	/* OK process key */
2116 	eblock.crypto_entry = session_key->enctype;
2117 	eblock.key = (krb5_keyblock *)session_key;
2118 
2119 	init_encrypt(encrypt_flag, bsd_context, KCMD_OLD_PROTOCOL,
2120 	    &desinbuf, &desoutbuf, CLIENT, &eblock);
2121 	/* cleanup */
2122 	krb5_free_cred_contents(bsd_context, &creds);
2123 	krb5_free_creds(bsd_context, new_creds);
2124 	krb5_xfree(msg.data);
2125 }
2126 
2127 
2128 static void
2129 try_normal_rcp(int cur_argc, char **cur_argv)
2130 {
2131 	char *target;
2132 
2133 	/*
2134 	 * Reset all KRB5 relevant flags and set the
2135 	 * cmd-buffer so that normal rcp works
2136 	 */
2137 	krb5auth_flag = encrypt_flag = encrypt_done = 0;
2138 	cmd = cmd_orig;
2139 	cmd_sunw = cmd_sunw_orig;
2140 
2141 	if (cur_argc < 2)
2142 		usage();
2143 
2144 	if (cur_argc > 2)
2145 		targetshouldbedirectory = 1;
2146 
2147 	rem = -1;
2148 
2149 	prev_argc = cur_argc;
2150 	prev_argv = save_argv(cur_argc, cur_argv);
2151 
2152 	(void) init_service(krb5auth_flag);
2153 
2154 	if (target = colon(cur_argv[cur_argc - 1])) {
2155 		toremote(target, cur_argc, cur_argv);
2156 	} else {
2157 		tolocal(cur_argc, cur_argv);
2158 		if (targetshouldbedirectory)
2159 			verifydir(cur_argv[cur_argc - 1]);
2160 	}
2161 	exit(errs);
2162 	/* NOTREACHED */
2163 }
2164 
2165 
2166 static int
2167 init_service(int krb5flag)
2168 {
2169 	struct servent *sp;
2170 	boolean_t success = B_FALSE;
2171 
2172 	if (krb5flag > 0) {
2173 		sp = getservbyname("kshell", "tcp");
2174 		if (sp == NULL) {
2175 			(void) fprintf(stderr,
2176 				gettext("rcp: kshell/tcp: unknown service.\n"
2177 				"trying normal shell/tcp service\n"));
2178 		} else {
2179 			portnumber = sp->s_port;
2180 			success = B_TRUE;
2181 		}
2182 	} else {
2183 		portnumber = htons(IPPORT_CMDSERVER);
2184 		success = B_TRUE;
2185 	}
2186 	return (success);
2187 }
2188 
2189 /*PRINTFLIKE1*/
2190 static void
2191 error(char *fmt, ...)
2192 {
2193 	va_list ap;
2194 	char buf[RCP_BUFSIZE];
2195 	char *cp = buf;
2196 
2197 	va_start(ap, fmt);
2198 	errs++;
2199 	*cp++ = 1;
2200 	(void) vsnprintf(cp, sizeof (buf) - 1, fmt, ap);
2201 	va_end(ap);
2202 
2203 	(void) desrcpwrite(rem, buf, strlen(buf));
2204 	if (iamremote == 0)
2205 		(void) write(2, buf + 1, strlen(buf + 1));
2206 }
2207