1*8a16b7a1SPedro F. Giffuni /*- 2*8a16b7a1SPedro F. Giffuni * SPDX-License-Identifier: BSD-3-Clause 3*8a16b7a1SPedro 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 #ifndef lint 3629909ffeSPhilippe Charnier static const char copyright[] = 379b50d902SRodney W. Grimes "@(#) Copyright (c) 1989, 1993\n\ 389b50d902SRodney W. Grimes The Regents of the University of California. All rights reserved.\n"; 399ba3a235SMark Murray #endif 409b50d902SRodney W. Grimes 41421dfbcfSDavid Malone #if 0 429b50d902SRodney W. Grimes #ifndef lint 43421dfbcfSDavid Malone static char sccsid[] = "@(#)write.c 8.1 (Berkeley) 6/6/93"; 4429909ffeSPhilippe Charnier #endif 45421dfbcfSDavid Malone #endif 46421dfbcfSDavid Malone 47421dfbcfSDavid Malone #include <sys/cdefs.h> 48421dfbcfSDavid Malone __FBSDID("$FreeBSD$"); 499b50d902SRodney W. Grimes 509b50d902SRodney W. Grimes #include <sys/param.h> 5138adbfe6SConrad Meyer #include <sys/capsicum.h> 5238adbfe6SConrad Meyer #include <sys/filio.h> 539b50d902SRodney W. Grimes #include <sys/signal.h> 549b50d902SRodney W. Grimes #include <sys/stat.h> 559b50d902SRodney W. Grimes #include <sys/time.h> 5638adbfe6SConrad Meyer 5738adbfe6SConrad Meyer #include <capsicum_helpers.h> 589b50d902SRodney W. Grimes #include <ctype.h> 5929909ffeSPhilippe Charnier #include <err.h> 6038adbfe6SConrad Meyer #include <errno.h> 6177caf211SAndrey A. Chernov #include <locale.h> 622d754ac8SAlexander Langer #include <paths.h> 639b50d902SRodney W. Grimes #include <pwd.h> 649b50d902SRodney W. Grimes #include <stdio.h> 65421dfbcfSDavid Malone #include <stdlib.h> 669b50d902SRodney W. Grimes #include <string.h> 6729909ffeSPhilippe Charnier #include <unistd.h> 68ab90a4d1SEd Schouten #include <utmpx.h> 690de6400bSGleb Smirnoff #include <wchar.h> 700de6400bSGleb Smirnoff #include <wctype.h> 719b50d902SRodney W. Grimes 723f330d7dSWarner Losh void done(int); 7338adbfe6SConrad Meyer void do_write(int, char *, char *, const char *); 743f330d7dSWarner Losh static void usage(void); 7538adbfe6SConrad Meyer int term_chk(int, char *, int *, time_t *, int); 760de6400bSGleb Smirnoff void wr_fputs(wchar_t *s); 7738adbfe6SConrad Meyer void search_utmp(int, char *, char *, char *, uid_t); 783f330d7dSWarner Losh int utmp_chk(char *, char *); 799b50d902SRodney W. Grimes 8029909ffeSPhilippe Charnier int 81f4ac32deSDavid Malone main(int argc, char **argv) 829b50d902SRodney W. Grimes { 8338adbfe6SConrad Meyer unsigned long cmds[] = { TIOCGETA, TIOCGWINSZ, FIODGNAME }; 8438adbfe6SConrad Meyer cap_rights_t rights; 8538adbfe6SConrad Meyer struct passwd *pwd; 869b50d902SRodney W. Grimes time_t atime; 879b50d902SRodney W. Grimes uid_t myuid; 889b50d902SRodney W. Grimes int msgsok, myttyfd; 8929909ffeSPhilippe Charnier char tty[MAXPATHLEN], *mytty; 9038adbfe6SConrad Meyer const char *login; 9138adbfe6SConrad Meyer int devfd; 929b50d902SRodney W. Grimes 9377caf211SAndrey A. Chernov (void)setlocale(LC_CTYPE, ""); 9477caf211SAndrey A. Chernov 9538adbfe6SConrad Meyer devfd = open(_PATH_DEV, O_RDONLY); 9638adbfe6SConrad Meyer if (devfd < 0) 9738adbfe6SConrad Meyer err(1, "open(/dev)"); 9838adbfe6SConrad Meyer cap_rights_init(&rights, CAP_FCNTL, CAP_FSTAT, CAP_IOCTL, CAP_LOOKUP, 9938adbfe6SConrad Meyer CAP_PWRITE); 10038adbfe6SConrad Meyer if (cap_rights_limit(devfd, &rights) < 0 && errno != ENOSYS) 10138adbfe6SConrad Meyer err(1, "can't limit devfd rights"); 10238adbfe6SConrad Meyer 10338adbfe6SConrad Meyer /* 10438adbfe6SConrad Meyer * Can't use capsicum helpers here because we need the additional 10538adbfe6SConrad Meyer * FIODGNAME ioctl. 10638adbfe6SConrad Meyer */ 10738adbfe6SConrad Meyer cap_rights_init(&rights, CAP_FCNTL, CAP_FSTAT, CAP_IOCTL, CAP_READ, 10838adbfe6SConrad Meyer CAP_WRITE); 10938adbfe6SConrad Meyer if ((cap_rights_limit(STDIN_FILENO, &rights) < 0 && errno != ENOSYS) || 11038adbfe6SConrad Meyer (cap_rights_limit(STDOUT_FILENO, &rights) < 0 && errno != ENOSYS) || 11138adbfe6SConrad Meyer (cap_rights_limit(STDERR_FILENO, &rights) < 0 && errno != ENOSYS) || 11238adbfe6SConrad Meyer (cap_ioctls_limit(STDIN_FILENO, cmds, nitems(cmds)) < 0 && errno != ENOSYS) || 11338adbfe6SConrad Meyer (cap_ioctls_limit(STDOUT_FILENO, cmds, nitems(cmds)) < 0 && errno != ENOSYS) || 11438adbfe6SConrad Meyer (cap_ioctls_limit(STDERR_FILENO, cmds, nitems(cmds)) < 0 && errno != ENOSYS) || 11538adbfe6SConrad Meyer (cap_fcntls_limit(STDIN_FILENO, CAP_FCNTL_GETFL) < 0 && errno != ENOSYS) || 11638adbfe6SConrad Meyer (cap_fcntls_limit(STDOUT_FILENO, CAP_FCNTL_GETFL) < 0 && errno != ENOSYS) || 11738adbfe6SConrad Meyer (cap_fcntls_limit(STDERR_FILENO, CAP_FCNTL_GETFL) < 0 && errno != ENOSYS)) 11838adbfe6SConrad Meyer err(1, "can't limit stdio rights"); 11938adbfe6SConrad Meyer 12038adbfe6SConrad Meyer caph_cache_catpages(); 12138adbfe6SConrad Meyer caph_cache_tzdata(); 12238adbfe6SConrad Meyer 12338adbfe6SConrad Meyer /* 12438adbfe6SConrad Meyer * Cache UTX database fds. 12538adbfe6SConrad Meyer */ 12638adbfe6SConrad Meyer setutxent(); 12738adbfe6SConrad Meyer 12838adbfe6SConrad Meyer /* 12938adbfe6SConrad Meyer * Determine our login name before we reopen() stdout 13038adbfe6SConrad Meyer * and before entering capability sandbox. 13138adbfe6SConrad Meyer */ 13238adbfe6SConrad Meyer myuid = getuid(); 13338adbfe6SConrad Meyer if ((login = getlogin()) == NULL) { 13438adbfe6SConrad Meyer if ((pwd = getpwuid(myuid))) 13538adbfe6SConrad Meyer login = pwd->pw_name; 13638adbfe6SConrad Meyer else 13738adbfe6SConrad Meyer login = "???"; 13838adbfe6SConrad Meyer } 13938adbfe6SConrad Meyer 14038adbfe6SConrad Meyer if (cap_enter() < 0 && errno != ENOSYS) 14138adbfe6SConrad Meyer err(1, "cap_enter"); 14238adbfe6SConrad Meyer 143759484baSTim J. Robbins while (getopt(argc, argv, "") != -1) 144759484baSTim J. Robbins usage(); 145759484baSTim J. Robbins argc -= optind; 146759484baSTim J. Robbins argv += optind; 147759484baSTim J. Robbins 1489b50d902SRodney W. Grimes /* check that sender has write enabled */ 1499b50d902SRodney W. Grimes if (isatty(fileno(stdin))) 1509b50d902SRodney W. Grimes myttyfd = fileno(stdin); 1519b50d902SRodney W. Grimes else if (isatty(fileno(stdout))) 1529b50d902SRodney W. Grimes myttyfd = fileno(stdout); 1539b50d902SRodney W. Grimes else if (isatty(fileno(stderr))) 1549b50d902SRodney W. Grimes myttyfd = fileno(stderr); 15529909ffeSPhilippe Charnier else 15629909ffeSPhilippe Charnier errx(1, "can't find your tty"); 15729909ffeSPhilippe Charnier if (!(mytty = ttyname(myttyfd))) 15829909ffeSPhilippe Charnier errx(1, "can't find your tty's name"); 159a4cc298aSJohn Baldwin if (!strncmp(mytty, _PATH_DEV, strlen(_PATH_DEV))) 160a4cc298aSJohn Baldwin mytty += strlen(_PATH_DEV); 16138adbfe6SConrad Meyer if (term_chk(devfd, mytty, &msgsok, &atime, 1)) 1629b50d902SRodney W. Grimes exit(1); 16329909ffeSPhilippe Charnier if (!msgsok) 16429909ffeSPhilippe Charnier errx(1, "you have write permission turned off"); 1659b50d902SRodney W. Grimes 1669b50d902SRodney W. Grimes /* check args */ 1679b50d902SRodney W. Grimes switch (argc) { 168759484baSTim J. Robbins case 1: 16938adbfe6SConrad Meyer search_utmp(devfd, argv[0], tty, mytty, myuid); 17038adbfe6SConrad Meyer do_write(devfd, tty, mytty, login); 1719b50d902SRodney W. Grimes break; 172759484baSTim J. Robbins case 2: 173759484baSTim J. Robbins if (!strncmp(argv[1], _PATH_DEV, strlen(_PATH_DEV))) 174759484baSTim J. Robbins argv[1] += strlen(_PATH_DEV); 175759484baSTim J. Robbins if (utmp_chk(argv[0], argv[1])) 176759484baSTim J. Robbins errx(1, "%s is not logged in on %s", argv[0], argv[1]); 17738adbfe6SConrad Meyer if (term_chk(devfd, argv[1], &msgsok, &atime, 1)) 1789b50d902SRodney W. Grimes exit(1); 17929909ffeSPhilippe Charnier if (myuid && !msgsok) 180759484baSTim J. Robbins errx(1, "%s has messages disabled on %s", argv[0], argv[1]); 18138adbfe6SConrad Meyer do_write(devfd, argv[1], mytty, login); 1829b50d902SRodney W. Grimes break; 1839b50d902SRodney W. Grimes default: 18429909ffeSPhilippe Charnier usage(); 1859b50d902SRodney W. Grimes } 18677caf211SAndrey A. Chernov done(0); 18729909ffeSPhilippe Charnier return (0); 18829909ffeSPhilippe Charnier } 18929909ffeSPhilippe Charnier 19029909ffeSPhilippe Charnier static void 191f4ac32deSDavid Malone usage(void) 19229909ffeSPhilippe Charnier { 19329909ffeSPhilippe Charnier (void)fprintf(stderr, "usage: write user [tty]\n"); 19429909ffeSPhilippe Charnier exit(1); 1959b50d902SRodney W. Grimes } 1969b50d902SRodney W. Grimes 1979b50d902SRodney W. Grimes /* 1989b50d902SRodney W. Grimes * utmp_chk - checks that the given user is actually logged in on 1999b50d902SRodney W. Grimes * the given tty 2009b50d902SRodney W. Grimes */ 20129909ffeSPhilippe Charnier int 202f4ac32deSDavid Malone utmp_chk(char *user, char *tty) 2039b50d902SRodney W. Grimes { 20450950530SEd Schouten struct utmpx lu, *u; 2059b50d902SRodney W. Grimes 20650950530SEd Schouten strncpy(lu.ut_line, tty, sizeof lu.ut_line); 20750950530SEd Schouten setutxent(); 20850950530SEd Schouten while ((u = getutxline(&lu)) != NULL) 20950950530SEd Schouten if (u->ut_type == USER_PROCESS && 21050950530SEd Schouten strcmp(user, u->ut_user) == 0) { 21150950530SEd Schouten endutxent(); 2129b50d902SRodney W. Grimes return(0); 2139b50d902SRodney W. Grimes } 21450950530SEd Schouten endutxent(); 2159b50d902SRodney W. Grimes return(1); 2169b50d902SRodney W. Grimes } 2179b50d902SRodney W. Grimes 2189b50d902SRodney W. Grimes /* 2199b50d902SRodney W. Grimes * search_utmp - search utmp for the "best" terminal to write to 2209b50d902SRodney W. Grimes * 2219b50d902SRodney W. Grimes * Ignores terminals with messages disabled, and of the rest, returns 2229b50d902SRodney W. Grimes * the one with the most recent access time. Returns as value the number 2239b50d902SRodney W. Grimes * of the user's terminals with messages enabled, or -1 if the user is 2249b50d902SRodney W. Grimes * not logged in at all. 2259b50d902SRodney W. Grimes * 2269b50d902SRodney W. Grimes * Special case for writing to yourself - ignore the terminal you're 2279b50d902SRodney W. Grimes * writing from, unless that's the only terminal with messages enabled. 2289b50d902SRodney W. Grimes */ 22929909ffeSPhilippe Charnier void 23038adbfe6SConrad Meyer search_utmp(int devfd, char *user, char *tty, char *mytty, uid_t myuid) 2319b50d902SRodney W. Grimes { 23250950530SEd Schouten struct utmpx *u; 2339b50d902SRodney W. Grimes time_t bestatime, atime; 23450950530SEd Schouten int nloggedttys, nttys, msgsok, user_is_me; 2359b50d902SRodney W. Grimes 2369b50d902SRodney W. Grimes nloggedttys = nttys = 0; 2379b50d902SRodney W. Grimes bestatime = 0; 2389b50d902SRodney W. Grimes user_is_me = 0; 23950950530SEd Schouten 24050950530SEd Schouten setutxent(); 24150950530SEd Schouten while ((u = getutxent()) != NULL) 24250950530SEd Schouten if (u->ut_type == USER_PROCESS && 24350950530SEd Schouten strcmp(user, u->ut_user) == 0) { 2449b50d902SRodney W. Grimes ++nloggedttys; 24538adbfe6SConrad Meyer if (term_chk(devfd, u->ut_line, &msgsok, &atime, 0)) 2469b50d902SRodney W. Grimes continue; /* bad term? skip */ 2479b50d902SRodney W. Grimes if (myuid && !msgsok) 2489b50d902SRodney W. Grimes continue; /* skip ttys with msgs off */ 24950950530SEd Schouten if (strcmp(u->ut_line, mytty) == 0) { 2509b50d902SRodney W. Grimes user_is_me = 1; 2519b50d902SRodney W. Grimes continue; /* don't write to yourself */ 2529b50d902SRodney W. Grimes } 2539b50d902SRodney W. Grimes ++nttys; 2549b50d902SRodney W. Grimes if (atime > bestatime) { 2559b50d902SRodney W. Grimes bestatime = atime; 25650950530SEd Schouten (void)strlcpy(tty, u->ut_line, MAXPATHLEN); 2579b50d902SRodney W. Grimes } 2589b50d902SRodney W. Grimes } 25950950530SEd Schouten endutxent(); 2609b50d902SRodney W. Grimes 26129909ffeSPhilippe Charnier if (nloggedttys == 0) 26229909ffeSPhilippe Charnier errx(1, "%s is not logged in", user); 2639b50d902SRodney W. Grimes if (nttys == 0) { 2649b50d902SRodney W. Grimes if (user_is_me) { /* ok, so write to yourself! */ 26550950530SEd Schouten (void)strlcpy(tty, mytty, MAXPATHLEN); 2669b50d902SRodney W. Grimes return; 2679b50d902SRodney W. Grimes } 26829909ffeSPhilippe Charnier errx(1, "%s has messages disabled", user); 2699b50d902SRodney W. Grimes } else if (nttys > 1) { 27029909ffeSPhilippe Charnier warnx("%s is logged in more than once; writing to %s", user, tty); 2719b50d902SRodney W. Grimes } 2729b50d902SRodney W. Grimes } 2739b50d902SRodney W. Grimes 2749b50d902SRodney W. Grimes /* 2759b50d902SRodney W. Grimes * term_chk - check that a terminal exists, and get the message bit 2769b50d902SRodney W. Grimes * and the access time 2779b50d902SRodney W. Grimes */ 27829909ffeSPhilippe Charnier int 27938adbfe6SConrad Meyer term_chk(int devfd, char *tty, int *msgsokP, time_t *atimeP, int showerror) 2809b50d902SRodney W. Grimes { 2819b50d902SRodney W. Grimes struct stat s; 2829b50d902SRodney W. Grimes 28338adbfe6SConrad Meyer if (fstatat(devfd, tty, &s, 0) < 0) { 2849b50d902SRodney W. Grimes if (showerror) 28538adbfe6SConrad Meyer warn("%s%s", _PATH_DEV, tty); 2869b50d902SRodney W. Grimes return(1); 2879b50d902SRodney W. Grimes } 2889b50d902SRodney W. Grimes *msgsokP = (s.st_mode & (S_IWRITE >> 3)) != 0; /* group write bit */ 2899b50d902SRodney W. Grimes *atimeP = s.st_atime; 2909b50d902SRodney W. Grimes return(0); 2919b50d902SRodney W. Grimes } 2929b50d902SRodney W. Grimes 2939b50d902SRodney W. Grimes /* 2949b50d902SRodney W. Grimes * do_write - actually make the connection 2959b50d902SRodney W. Grimes */ 29629909ffeSPhilippe Charnier void 29738adbfe6SConrad Meyer do_write(int devfd, char *tty, char *mytty, const char *login) 2989b50d902SRodney W. Grimes { 2999ba3a235SMark Murray char *nows; 30029909ffeSPhilippe Charnier time_t now; 30138adbfe6SConrad Meyer char host[MAXHOSTNAMELEN]; 3020de6400bSGleb Smirnoff wchar_t line[512]; 30338adbfe6SConrad Meyer int fd; 3049b50d902SRodney W. Grimes 30538adbfe6SConrad Meyer fd = openat(devfd, tty, O_WRONLY); 30638adbfe6SConrad Meyer if (fd < 0) 30738adbfe6SConrad Meyer err(1, "openat(%s%s)", _PATH_DEV, tty); 30838adbfe6SConrad Meyer fclose(stdout); 30938adbfe6SConrad Meyer stdout = fdopen(fd, "w"); 31038adbfe6SConrad Meyer if (stdout == NULL) 31138adbfe6SConrad Meyer err(1, "%s%s", _PATH_DEV, tty); 3129b50d902SRodney W. Grimes 3139b50d902SRodney W. Grimes (void)signal(SIGINT, done); 3149b50d902SRodney W. Grimes (void)signal(SIGHUP, done); 3159b50d902SRodney W. Grimes 3169b50d902SRodney W. Grimes /* print greeting */ 3179b50d902SRodney W. Grimes if (gethostname(host, sizeof(host)) < 0) 3189b50d902SRodney W. Grimes (void)strcpy(host, "???"); 3199b50d902SRodney W. Grimes now = time((time_t *)NULL); 3209b50d902SRodney W. Grimes nows = ctime(&now); 3219b50d902SRodney W. Grimes nows[16] = '\0'; 3229b50d902SRodney W. Grimes (void)printf("\r\n\007\007\007Message from %s@%s on %s at %s ...\r\n", 3239b50d902SRodney W. Grimes login, host, mytty, nows + 11); 3249b50d902SRodney W. Grimes 3250de6400bSGleb Smirnoff while (fgetws(line, sizeof(line)/sizeof(wchar_t), stdin) != NULL) 3269b50d902SRodney W. Grimes wr_fputs(line); 3279b50d902SRodney W. Grimes } 3289b50d902SRodney W. Grimes 3299b50d902SRodney W. Grimes /* 3309b50d902SRodney W. Grimes * done - cleanup and exit 3319b50d902SRodney W. Grimes */ 3329b50d902SRodney W. Grimes void 333f4ac32deSDavid Malone done(int n __unused) 3349b50d902SRodney W. Grimes { 3359b50d902SRodney W. Grimes (void)printf("EOF\r\n"); 3369b50d902SRodney W. Grimes exit(0); 3379b50d902SRodney W. Grimes } 3389b50d902SRodney W. Grimes 3399b50d902SRodney W. Grimes /* 3409b50d902SRodney W. Grimes * wr_fputs - like fputs(), but makes control characters visible and 3419b50d902SRodney W. Grimes * turns \n into \r\n 3429b50d902SRodney W. Grimes */ 34329909ffeSPhilippe Charnier void 3440de6400bSGleb Smirnoff wr_fputs(wchar_t *s) 3459b50d902SRodney W. Grimes { 3469b50d902SRodney W. Grimes 3470de6400bSGleb Smirnoff #define PUTC(c) if (putwchar(c) == WEOF) err(1, NULL); 3489b50d902SRodney W. Grimes 3490de6400bSGleb Smirnoff for (; *s != L'\0'; ++s) { 3500de6400bSGleb Smirnoff if (*s == L'\n') { 3510de6400bSGleb Smirnoff PUTC(L'\r'); 3520de6400bSGleb Smirnoff PUTC(L'\n'); 3530de6400bSGleb Smirnoff } else if (iswprint(*s) || iswspace(*s)) { 354b6c671c7SAndrey A. Chernov PUTC(*s); 3550de6400bSGleb Smirnoff } else { 3560de6400bSGleb Smirnoff wprintf(L"<0x%X>", *s); 3570de6400bSGleb Smirnoff } 3589b50d902SRodney W. Grimes } 3599b50d902SRodney W. Grimes return; 3609b50d902SRodney W. Grimes #undef PUTC 3619b50d902SRodney W. Grimes } 362