xref: /freebsd/usr.bin/write/write.c (revision 0b8224d1cc9dc6c9778ba04a75b2c8d47e5d7481)
18a16b7a1SPedro F. Giffuni /*-
28a16b7a1SPedro F. Giffuni  * SPDX-License-Identifier: BSD-3-Clause
38a16b7a1SPedro F. Giffuni  *
49b50d902SRodney W. Grimes  * Copyright (c) 1989, 1993
59b50d902SRodney W. Grimes  *	The Regents of the University of California.  All rights reserved.
69b50d902SRodney W. Grimes  *
79b50d902SRodney W. Grimes  * This code is derived from software contributed to Berkeley by
89b50d902SRodney W. Grimes  * Jef Poskanzer and Craig Leres of the Lawrence Berkeley Laboratory.
99b50d902SRodney W. Grimes  *
109b50d902SRodney W. Grimes  * Redistribution and use in source and binary forms, with or without
119b50d902SRodney W. Grimes  * modification, are permitted provided that the following conditions
129b50d902SRodney W. Grimes  * are met:
139b50d902SRodney W. Grimes  * 1. Redistributions of source code must retain the above copyright
149b50d902SRodney W. Grimes  *    notice, this list of conditions and the following disclaimer.
159b50d902SRodney W. Grimes  * 2. Redistributions in binary form must reproduce the above copyright
169b50d902SRodney W. Grimes  *    notice, this list of conditions and the following disclaimer in the
179b50d902SRodney W. Grimes  *    documentation and/or other materials provided with the distribution.
18fbbd9655SWarner Losh  * 3. Neither the name of the University nor the names of its contributors
199b50d902SRodney W. Grimes  *    may be used to endorse or promote products derived from this software
209b50d902SRodney W. Grimes  *    without specific prior written permission.
219b50d902SRodney W. Grimes  *
229b50d902SRodney W. Grimes  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
239b50d902SRodney W. Grimes  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
249b50d902SRodney W. Grimes  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
259b50d902SRodney W. Grimes  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
269b50d902SRodney W. Grimes  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
279b50d902SRodney W. Grimes  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
289b50d902SRodney W. Grimes  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
299b50d902SRodney W. Grimes  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
309b50d902SRodney W. Grimes  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
319b50d902SRodney W. Grimes  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
329b50d902SRodney W. Grimes  * SUCH DAMAGE.
339b50d902SRodney W. Grimes  */
349b50d902SRodney W. Grimes 
359b50d902SRodney W. Grimes #include <sys/param.h>
3638adbfe6SConrad Meyer #include <sys/capsicum.h>
3738adbfe6SConrad Meyer #include <sys/filio.h>
389b50d902SRodney W. Grimes #include <sys/signal.h>
399b50d902SRodney W. Grimes #include <sys/stat.h>
409b50d902SRodney W. Grimes #include <sys/time.h>
4138adbfe6SConrad Meyer 
4238adbfe6SConrad Meyer #include <capsicum_helpers.h>
439b50d902SRodney W. Grimes #include <ctype.h>
4429909ffeSPhilippe Charnier #include <err.h>
4538adbfe6SConrad Meyer #include <errno.h>
4677caf211SAndrey A. Chernov #include <locale.h>
472d754ac8SAlexander Langer #include <paths.h>
489b50d902SRodney W. Grimes #include <pwd.h>
499b50d902SRodney W. Grimes #include <stdio.h>
50421dfbcfSDavid Malone #include <stdlib.h>
519b50d902SRodney W. Grimes #include <string.h>
5229909ffeSPhilippe Charnier #include <unistd.h>
53ab90a4d1SEd Schouten #include <utmpx.h>
540de6400bSGleb Smirnoff #include <wchar.h>
550de6400bSGleb Smirnoff #include <wctype.h>
569b50d902SRodney W. Grimes 
573f330d7dSWarner Losh void done(int);
5838adbfe6SConrad Meyer void do_write(int, char *, char *, const char *);
59*cccdaf50SAlfonso Gregory static void usage(void) __dead2;
6038adbfe6SConrad Meyer int term_chk(int, char *, int *, time_t *, int);
610de6400bSGleb Smirnoff void wr_fputs(wchar_t *s);
6238adbfe6SConrad Meyer void search_utmp(int, char *, char *, char *, uid_t);
633f330d7dSWarner Losh int utmp_chk(char *, char *);
649b50d902SRodney W. Grimes 
6529909ffeSPhilippe Charnier int
main(int argc,char ** argv)66f4ac32deSDavid Malone main(int argc, char **argv)
679b50d902SRodney W. Grimes {
6838adbfe6SConrad Meyer 	unsigned long cmds[] = { TIOCGETA, TIOCGWINSZ, FIODGNAME };
6938adbfe6SConrad Meyer 	cap_rights_t rights;
7038adbfe6SConrad Meyer 	struct passwd *pwd;
719b50d902SRodney W. Grimes 	time_t atime;
729b50d902SRodney W. Grimes 	uid_t myuid;
739b50d902SRodney W. Grimes 	int msgsok, myttyfd;
7429909ffeSPhilippe Charnier 	char tty[MAXPATHLEN], *mytty;
7538adbfe6SConrad Meyer 	const char *login;
7638adbfe6SConrad Meyer 	int devfd;
779b50d902SRodney W. Grimes 
7877caf211SAndrey A. Chernov 	(void)setlocale(LC_CTYPE, "");
7977caf211SAndrey A. Chernov 
8038adbfe6SConrad Meyer 	devfd = open(_PATH_DEV, O_RDONLY);
8138adbfe6SConrad Meyer 	if (devfd < 0)
8238adbfe6SConrad Meyer 		err(1, "open(/dev)");
8338adbfe6SConrad Meyer 	cap_rights_init(&rights, CAP_FCNTL, CAP_FSTAT, CAP_IOCTL, CAP_LOOKUP,
8438adbfe6SConrad Meyer 	    CAP_PWRITE);
85377421dfSMariusz Zaborski 	if (caph_rights_limit(devfd, &rights) < 0)
8638adbfe6SConrad Meyer 		err(1, "can't limit devfd rights");
8738adbfe6SConrad Meyer 
8838adbfe6SConrad Meyer 	/*
8938adbfe6SConrad Meyer 	 * Can't use capsicum helpers here because we need the additional
9038adbfe6SConrad Meyer 	 * FIODGNAME ioctl.
9138adbfe6SConrad Meyer 	 */
9238adbfe6SConrad Meyer 	cap_rights_init(&rights, CAP_FCNTL, CAP_FSTAT, CAP_IOCTL, CAP_READ,
9338adbfe6SConrad Meyer 	    CAP_WRITE);
94377421dfSMariusz Zaborski 	if (caph_rights_limit(STDIN_FILENO, &rights) < 0 ||
95377421dfSMariusz Zaborski 	    caph_rights_limit(STDOUT_FILENO, &rights) < 0 ||
96377421dfSMariusz Zaborski 	    caph_rights_limit(STDERR_FILENO, &rights) < 0 ||
97377421dfSMariusz Zaborski 	    caph_ioctls_limit(STDIN_FILENO, cmds, nitems(cmds)) < 0 ||
98377421dfSMariusz Zaborski 	    caph_ioctls_limit(STDOUT_FILENO, cmds, nitems(cmds)) < 0 ||
99377421dfSMariusz Zaborski 	    caph_ioctls_limit(STDERR_FILENO, cmds, nitems(cmds)) < 0 ||
100377421dfSMariusz Zaborski 	    caph_fcntls_limit(STDIN_FILENO, CAP_FCNTL_GETFL) < 0 ||
101377421dfSMariusz Zaborski 	    caph_fcntls_limit(STDOUT_FILENO, CAP_FCNTL_GETFL) < 0 ||
102377421dfSMariusz Zaborski 	    caph_fcntls_limit(STDERR_FILENO, CAP_FCNTL_GETFL) < 0)
10338adbfe6SConrad Meyer 		err(1, "can't limit stdio rights");
10438adbfe6SConrad Meyer 
10538adbfe6SConrad Meyer 	caph_cache_catpages();
10638adbfe6SConrad Meyer 	caph_cache_tzdata();
10738adbfe6SConrad Meyer 
10838adbfe6SConrad Meyer 	/*
10938adbfe6SConrad Meyer 	 * Cache UTX database fds.
11038adbfe6SConrad Meyer 	 */
11138adbfe6SConrad Meyer 	setutxent();
11238adbfe6SConrad Meyer 
11338adbfe6SConrad Meyer 	/*
11438adbfe6SConrad Meyer 	 * Determine our login name before we reopen() stdout
11538adbfe6SConrad Meyer 	 * and before entering capability sandbox.
11638adbfe6SConrad Meyer 	 */
11738adbfe6SConrad Meyer 	myuid = getuid();
11838adbfe6SConrad Meyer 	if ((login = getlogin()) == NULL) {
11938adbfe6SConrad Meyer 		if ((pwd = getpwuid(myuid)))
12038adbfe6SConrad Meyer 			login = pwd->pw_name;
12138adbfe6SConrad Meyer 		else
12238adbfe6SConrad Meyer 			login = "???";
12338adbfe6SConrad Meyer 	}
12438adbfe6SConrad Meyer 
1257672a014SMariusz Zaborski 	if (caph_enter() < 0)
12638adbfe6SConrad Meyer 		err(1, "cap_enter");
12738adbfe6SConrad Meyer 
128759484baSTim J. Robbins 	while (getopt(argc, argv, "") != -1)
129759484baSTim J. Robbins 		usage();
130759484baSTim J. Robbins 	argc -= optind;
131759484baSTim J. Robbins 	argv += optind;
132759484baSTim J. Robbins 
1339b50d902SRodney W. Grimes 	/* check that sender has write enabled */
1349b50d902SRodney W. Grimes 	if (isatty(fileno(stdin)))
1359b50d902SRodney W. Grimes 		myttyfd = fileno(stdin);
1369b50d902SRodney W. Grimes 	else if (isatty(fileno(stdout)))
1379b50d902SRodney W. Grimes 		myttyfd = fileno(stdout);
1389b50d902SRodney W. Grimes 	else if (isatty(fileno(stderr)))
1399b50d902SRodney W. Grimes 		myttyfd = fileno(stderr);
14029909ffeSPhilippe Charnier 	else
14129909ffeSPhilippe Charnier 		errx(1, "can't find your tty");
14229909ffeSPhilippe Charnier 	if (!(mytty = ttyname(myttyfd)))
14329909ffeSPhilippe Charnier 		errx(1, "can't find your tty's name");
144a4cc298aSJohn Baldwin 	if (!strncmp(mytty, _PATH_DEV, strlen(_PATH_DEV)))
145a4cc298aSJohn Baldwin 		mytty += strlen(_PATH_DEV);
14638adbfe6SConrad Meyer 	if (term_chk(devfd, mytty, &msgsok, &atime, 1))
1479b50d902SRodney W. Grimes 		exit(1);
14829909ffeSPhilippe Charnier 	if (!msgsok)
14929909ffeSPhilippe Charnier 		errx(1, "you have write permission turned off");
1509b50d902SRodney W. Grimes 
1519b50d902SRodney W. Grimes 	/* check args */
1529b50d902SRodney W. Grimes 	switch (argc) {
153759484baSTim J. Robbins 	case 1:
15438adbfe6SConrad Meyer 		search_utmp(devfd, argv[0], tty, mytty, myuid);
15538adbfe6SConrad Meyer 		do_write(devfd, tty, mytty, login);
1569b50d902SRodney W. Grimes 		break;
157759484baSTim J. Robbins 	case 2:
158759484baSTim J. Robbins 		if (!strncmp(argv[1], _PATH_DEV, strlen(_PATH_DEV)))
159759484baSTim J. Robbins 			argv[1] += strlen(_PATH_DEV);
160759484baSTim J. Robbins 		if (utmp_chk(argv[0], argv[1]))
161759484baSTim J. Robbins 			errx(1, "%s is not logged in on %s", argv[0], argv[1]);
16238adbfe6SConrad Meyer 		if (term_chk(devfd, argv[1], &msgsok, &atime, 1))
1639b50d902SRodney W. Grimes 			exit(1);
16429909ffeSPhilippe Charnier 		if (myuid && !msgsok)
165759484baSTim J. Robbins 			errx(1, "%s has messages disabled on %s", argv[0], argv[1]);
16638adbfe6SConrad Meyer 		do_write(devfd, argv[1], mytty, login);
1679b50d902SRodney W. Grimes 		break;
1689b50d902SRodney W. Grimes 	default:
16929909ffeSPhilippe Charnier 		usage();
1709b50d902SRodney W. Grimes 	}
17177caf211SAndrey A. Chernov 	done(0);
17229909ffeSPhilippe Charnier 	return (0);
17329909ffeSPhilippe Charnier }
17429909ffeSPhilippe Charnier 
17529909ffeSPhilippe Charnier static void
usage(void)176f4ac32deSDavid Malone usage(void)
17729909ffeSPhilippe Charnier {
17829909ffeSPhilippe Charnier 	(void)fprintf(stderr, "usage: write user [tty]\n");
17929909ffeSPhilippe Charnier 	exit(1);
1809b50d902SRodney W. Grimes }
1819b50d902SRodney W. Grimes 
1829b50d902SRodney W. Grimes /*
1839b50d902SRodney W. Grimes  * utmp_chk - checks that the given user is actually logged in on
1849b50d902SRodney W. Grimes  *     the given tty
1859b50d902SRodney W. Grimes  */
18629909ffeSPhilippe Charnier int
utmp_chk(char * user,char * tty)187f4ac32deSDavid Malone utmp_chk(char *user, char *tty)
1889b50d902SRodney W. Grimes {
18950950530SEd Schouten 	struct utmpx lu, *u;
1909b50d902SRodney W. Grimes 
19150950530SEd Schouten 	strncpy(lu.ut_line, tty, sizeof lu.ut_line);
19250950530SEd Schouten 	while ((u = getutxline(&lu)) != NULL)
19350950530SEd Schouten 		if (u->ut_type == USER_PROCESS &&
19450950530SEd Schouten 		    strcmp(user, u->ut_user) == 0) {
19550950530SEd Schouten 			endutxent();
1969b50d902SRodney W. Grimes 			return(0);
1979b50d902SRodney W. Grimes 		}
19850950530SEd Schouten 	endutxent();
1999b50d902SRodney W. Grimes 	return(1);
2009b50d902SRodney W. Grimes }
2019b50d902SRodney W. Grimes 
2029b50d902SRodney W. Grimes /*
2039b50d902SRodney W. Grimes  * search_utmp - search utmp for the "best" terminal to write to
2049b50d902SRodney W. Grimes  *
2059b50d902SRodney W. Grimes  * Ignores terminals with messages disabled, and of the rest, returns
2069b50d902SRodney W. Grimes  * the one with the most recent access time.  Returns as value the number
2079b50d902SRodney W. Grimes  * of the user's terminals with messages enabled, or -1 if the user is
2089b50d902SRodney W. Grimes  * not logged in at all.
2099b50d902SRodney W. Grimes  *
2109b50d902SRodney W. Grimes  * Special case for writing to yourself - ignore the terminal you're
2119b50d902SRodney W. Grimes  * writing from, unless that's the only terminal with messages enabled.
2129b50d902SRodney W. Grimes  */
21329909ffeSPhilippe Charnier void
search_utmp(int devfd,char * user,char * tty,char * mytty,uid_t myuid)21438adbfe6SConrad Meyer search_utmp(int devfd, char *user, char *tty, char *mytty, uid_t myuid)
2159b50d902SRodney W. Grimes {
21650950530SEd Schouten 	struct utmpx *u;
2179b50d902SRodney W. Grimes 	time_t bestatime, atime;
21850950530SEd Schouten 	int nloggedttys, nttys, msgsok, user_is_me;
2199b50d902SRodney W. Grimes 
2209b50d902SRodney W. Grimes 	nloggedttys = nttys = 0;
2219b50d902SRodney W. Grimes 	bestatime = 0;
2229b50d902SRodney W. Grimes 	user_is_me = 0;
22350950530SEd Schouten 
22450950530SEd Schouten 	while ((u = getutxent()) != NULL)
22550950530SEd Schouten 		if (u->ut_type == USER_PROCESS &&
22650950530SEd Schouten 		    strcmp(user, u->ut_user) == 0) {
2279b50d902SRodney W. Grimes 			++nloggedttys;
22838adbfe6SConrad Meyer 			if (term_chk(devfd, u->ut_line, &msgsok, &atime, 0))
2299b50d902SRodney W. Grimes 				continue;	/* bad term? skip */
2309b50d902SRodney W. Grimes 			if (myuid && !msgsok)
2319b50d902SRodney W. Grimes 				continue;	/* skip ttys with msgs off */
23250950530SEd Schouten 			if (strcmp(u->ut_line, mytty) == 0) {
2339b50d902SRodney W. Grimes 				user_is_me = 1;
2349b50d902SRodney W. Grimes 				continue;	/* don't write to yourself */
2359b50d902SRodney W. Grimes 			}
2369b50d902SRodney W. Grimes 			++nttys;
2379b50d902SRodney W. Grimes 			if (atime > bestatime) {
2389b50d902SRodney W. Grimes 				bestatime = atime;
23950950530SEd Schouten 				(void)strlcpy(tty, u->ut_line, MAXPATHLEN);
2409b50d902SRodney W. Grimes 			}
2419b50d902SRodney W. Grimes 		}
24250950530SEd Schouten 	endutxent();
2439b50d902SRodney W. Grimes 
24429909ffeSPhilippe Charnier 	if (nloggedttys == 0)
24529909ffeSPhilippe Charnier 		errx(1, "%s is not logged in", user);
2469b50d902SRodney W. Grimes 	if (nttys == 0) {
2479b50d902SRodney W. Grimes 		if (user_is_me) {		/* ok, so write to yourself! */
24850950530SEd Schouten 			(void)strlcpy(tty, mytty, MAXPATHLEN);
2499b50d902SRodney W. Grimes 			return;
2509b50d902SRodney W. Grimes 		}
25129909ffeSPhilippe Charnier 		errx(1, "%s has messages disabled", user);
2529b50d902SRodney W. Grimes 	} else if (nttys > 1) {
25329909ffeSPhilippe Charnier 		warnx("%s is logged in more than once; writing to %s", user, tty);
2549b50d902SRodney W. Grimes 	}
2559b50d902SRodney W. Grimes }
2569b50d902SRodney W. Grimes 
2579b50d902SRodney W. Grimes /*
2589b50d902SRodney W. Grimes  * term_chk - check that a terminal exists, and get the message bit
2599b50d902SRodney W. Grimes  *     and the access time
2609b50d902SRodney W. Grimes  */
26129909ffeSPhilippe Charnier int
term_chk(int devfd,char * tty,int * msgsokP,time_t * atimeP,int showerror)26238adbfe6SConrad Meyer term_chk(int devfd, char *tty, int *msgsokP, time_t *atimeP, int showerror)
2639b50d902SRodney W. Grimes {
2649b50d902SRodney W. Grimes 	struct stat s;
2659b50d902SRodney W. Grimes 
26638adbfe6SConrad Meyer 	if (fstatat(devfd, tty, &s, 0) < 0) {
2679b50d902SRodney W. Grimes 		if (showerror)
26838adbfe6SConrad Meyer 			warn("%s%s", _PATH_DEV, tty);
2699b50d902SRodney W. Grimes 		return(1);
2709b50d902SRodney W. Grimes 	}
2719b50d902SRodney W. Grimes 	*msgsokP = (s.st_mode & (S_IWRITE >> 3)) != 0;	/* group write bit */
2729b50d902SRodney W. Grimes 	*atimeP = s.st_atime;
2739b50d902SRodney W. Grimes 	return(0);
2749b50d902SRodney W. Grimes }
2759b50d902SRodney W. Grimes 
2769b50d902SRodney W. Grimes /*
2779b50d902SRodney W. Grimes  * do_write - actually make the connection
2789b50d902SRodney W. Grimes  */
27929909ffeSPhilippe Charnier void
do_write(int devfd,char * tty,char * mytty,const char * login)28038adbfe6SConrad Meyer do_write(int devfd, char *tty, char *mytty, const char *login)
2819b50d902SRodney W. Grimes {
2829ba3a235SMark Murray 	char *nows;
28329909ffeSPhilippe Charnier 	time_t now;
28438adbfe6SConrad Meyer 	char host[MAXHOSTNAMELEN];
2850de6400bSGleb Smirnoff 	wchar_t line[512];
28638adbfe6SConrad Meyer 	int fd;
2879b50d902SRodney W. Grimes 
28838adbfe6SConrad Meyer 	fd = openat(devfd, tty, O_WRONLY);
28938adbfe6SConrad Meyer 	if (fd < 0)
29038adbfe6SConrad Meyer 		err(1, "openat(%s%s)", _PATH_DEV, tty);
29138adbfe6SConrad Meyer 	fclose(stdout);
29238adbfe6SConrad Meyer 	stdout = fdopen(fd, "w");
29338adbfe6SConrad Meyer 	if (stdout == NULL)
29438adbfe6SConrad Meyer 		err(1, "%s%s", _PATH_DEV, tty);
2959b50d902SRodney W. Grimes 
2969b50d902SRodney W. Grimes 	(void)signal(SIGINT, done);
2979b50d902SRodney W. Grimes 	(void)signal(SIGHUP, done);
2989b50d902SRodney W. Grimes 
2999b50d902SRodney W. Grimes 	/* print greeting */
3009b50d902SRodney W. Grimes 	if (gethostname(host, sizeof(host)) < 0)
3019b50d902SRodney W. Grimes 		(void)strcpy(host, "???");
3029b50d902SRodney W. Grimes 	now = time((time_t *)NULL);
3039b50d902SRodney W. Grimes 	nows = ctime(&now);
3049b50d902SRodney W. Grimes 	nows[16] = '\0';
3059b50d902SRodney W. Grimes 	(void)printf("\r\n\007\007\007Message from %s@%s on %s at %s ...\r\n",
3069b50d902SRodney W. Grimes 	    login, host, mytty, nows + 11);
3079b50d902SRodney W. Grimes 
3080de6400bSGleb Smirnoff 	while (fgetws(line, sizeof(line)/sizeof(wchar_t), stdin) != NULL)
3099b50d902SRodney W. Grimes 		wr_fputs(line);
3109b50d902SRodney W. Grimes }
3119b50d902SRodney W. Grimes 
3129b50d902SRodney W. Grimes /*
3139b50d902SRodney W. Grimes  * done - cleanup and exit
3149b50d902SRodney W. Grimes  */
3159b50d902SRodney W. Grimes void
done(int n __unused)316f4ac32deSDavid Malone done(int n __unused)
3179b50d902SRodney W. Grimes {
3189b50d902SRodney W. Grimes 	(void)printf("EOF\r\n");
3199b50d902SRodney W. Grimes 	exit(0);
3209b50d902SRodney W. Grimes }
3219b50d902SRodney W. Grimes 
3229b50d902SRodney W. Grimes /*
3239b50d902SRodney W. Grimes  * wr_fputs - like fputs(), but makes control characters visible and
3249b50d902SRodney W. Grimes  *     turns \n into \r\n
3259b50d902SRodney W. Grimes  */
32629909ffeSPhilippe Charnier void
wr_fputs(wchar_t * s)3270de6400bSGleb Smirnoff wr_fputs(wchar_t *s)
3289b50d902SRodney W. Grimes {
3299b50d902SRodney W. Grimes 
3300de6400bSGleb Smirnoff #define	PUTC(c)	if (putwchar(c) == WEOF) err(1, NULL);
3319b50d902SRodney W. Grimes 
3320de6400bSGleb Smirnoff 	for (; *s != L'\0'; ++s) {
3330de6400bSGleb Smirnoff 		if (*s == L'\n') {
3340de6400bSGleb Smirnoff 			PUTC(L'\r');
3350de6400bSGleb Smirnoff 			PUTC(L'\n');
3360de6400bSGleb Smirnoff 		} else if (iswprint(*s) || iswspace(*s)) {
337b6c671c7SAndrey A. Chernov 			PUTC(*s);
3380de6400bSGleb Smirnoff 		} else {
3390de6400bSGleb Smirnoff 			wprintf(L"<0x%X>", *s);
3400de6400bSGleb Smirnoff 		}
3419b50d902SRodney W. Grimes 	}
3429b50d902SRodney W. Grimes 	return;
3439b50d902SRodney W. Grimes #undef PUTC
3449b50d902SRodney W. Grimes }
345