xref: /freebsd/contrib/tcsh/tc.who.c (revision 59c8e88e72633afbc47a4ace0d2170d00d51f7dc)
1 /*
2  * tc.who.c: Watch logins and logouts...
3  */
4 /*-
5  * Copyright (c) 1980, 1991 The Regents of the University of California.
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. Neither the name of the University nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32 #include "sh.h"
33 #include "tc.h"
34 
35 #ifndef HAVENOUTMP
36 /*
37  * kfk 26 Jan 1984 - for login watch functions.
38  */
39 #include <ctype.h>
40 
41 #ifdef HAVE_UTMPX_H
42 # include <utmpx.h>
43 # define UTNAMLEN	sizeof(((struct utmpx *) 0)->ut_name)
44 # define UTLINLEN	sizeof(((struct utmpx *) 0)->ut_line)
45 # ifdef HAVE_STRUCT_UTMPX_UT_HOST
46 #  define UTHOSTLEN	sizeof(((struct utmpx *) 0)->ut_host)
47 # endif
48 /* I just redefine a few words here.  Changing every occurrence below
49  * seems like too much of work.  All UTMP functions have equivalent
50  * UTMPX counterparts, so they can be added all here when needed.
51  * Kimmo Suominen, Oct 14 1991
52  */
53 # if defined(__UTMPX_FILE) && !defined(UTMPX_FILE)
54 #  define TCSH_PATH_UTMP __UTMPX_FILE
55 # elif defined(_PATH_UTMPX)
56 #  define TCSH_PATH_UTMP _PATH_UTMPX
57 # elif defined(UTMPX_FILE)
58 #  define TCSH_PATH_UTMP UTMPX_FILE
59 # elif __FreeBSD_version >= 900000
60 #  /* Why isn't this defined somewhere? */
61 #  define TCSH_PATH_UTMP "/var/run/utx.active"
62 # elif defined(__hpux)
63 #  define TCSH_PATH_UTMP "/etc/utmpx"
64 # elif defined(IBMAIX) && defined(UTMP_FILE)
65 #  define TCSH_PATH_UTMP UTMP_FILE
66 # endif
67 # if defined(TCSH_PATH_UTMP) || !defined(HAVE_UTMP_H)
68 #  define utmp utmpx
69 #  define TCSH_USE_UTMPX
70 #  if defined(HAVE_GETUTENT) || defined(HAVE_GETUTXENT)
71 #   define getutent getutxent
72 #   define setutent setutxent
73 #   define endutent endutxent
74 #  endif /* HAVE_GETUTENT || HAVE_GETUTXENT */
75 #  if defined(HAVE_STRUCT_UTMPX_UT_TV)
76 #   define ut_time ut_tv.tv_sec
77 #  elif defined(HAVE_STRUCT_UTMPX_UT_XTIME)
78 #   define ut_time ut_xtime
79 #  endif
80 #  if defined(HAVE_STRUCT_UTMPX_UT_USER)
81 #   define ut_name ut_user
82 #  endif
83 # endif /* TCSH_PATH_UTMP || !HAVE_UTMP_H */
84 #endif /* HAVE_UTMPX_H */
85 
86 #if !defined(TCSH_USE_UTMPX) && defined(HAVE_UTMP_H)
87 # include <utmp.h>
88 # if defined(HAVE_STRUCT_UTMP_UT_TV)
89 #  define ut_time ut_tv.tv_sec
90 # elif defined(HAVE_STRUCT_UTMP_UT_XTIME)
91 #  define ut_time ut_xtime
92 # endif
93 # if defined(HAVE_STRUCT_UTMP_UT_USER)
94 #  define ut_name ut_user
95 # endif
96 # ifndef BROKEN_CC
97 #  define UTNAMLEN	sizeof(((struct utmp *) 0)->ut_name)
98 #  define UTLINLEN	sizeof(((struct utmp *) 0)->ut_line)
99 #  ifdef HAVE_STRUCT_UTMP_UT_HOST
100 #   ifdef _SEQUENT_
101 #    define UTHOSTLEN	100
102 #   else
103 #    define UTHOSTLEN	sizeof(((struct utmp *) 0)->ut_host)
104 #   endif
105 #  endif	/* HAVE_STRUCT_UTMP_UT_HOST */
106 # else
107 /* give poor cc a little help if it needs it */
108 struct utmp __ut;
109 #  define UTNAMLEN	sizeof(__ut.ut_name)
110 #  define UTLINLEN	sizeof(__ut.ut_line)
111 #  ifdef HAVE_STRUCT_UTMP_UT_HOST
112 #   ifdef _SEQUENT_
113 #    define UTHOSTLEN	100
114 #   else
115 #    define UTHOSTLEN	sizeof(__ut.ut_host)
116 #   endif
117 #  endif /* HAVE_STRUCT_UTMP_UT_HOST */
118 # endif /* BROKEN_CC */
119 # ifndef TCSH_PATH_UTMP
120 #  ifdef UTMP_FILE
121 #   define TCSH_PATH_UTMP UTMP_FILE
122 #  elif defined(_PATH_UTMP)
123 #   define TCSH_PATH_UTMP _PATH_UTMP
124 #  else
125 #   define TCSH_PATH_UTMP "/etc/utmp"
126 #  endif /* UTMP_FILE */
127 # endif /* TCSH_PATH_UTMP */
128 #endif /* !TCSH_USE_UTMPX && HAVE_UTMP_H */
129 
130 #ifndef UTNAMLEN
131 #define UTNAMLEN 64
132 #endif
133 #ifndef UTLINLEN
134 #define UTLINLEN 64
135 #endif
136 
137 struct who {
138     struct who *who_next;
139     struct who *who_prev;
140     char    who_name[UTNAMLEN + 1];
141     char    who_new[UTNAMLEN + 1];
142     char    who_tty[UTLINLEN + 1];
143 #ifdef UTHOSTLEN
144     char    who_host[UTHOSTLEN + 1];
145 #endif /* UTHOSTLEN */
146     time_t  who_time;
147     int     who_status;
148 };
149 
150 static struct who whohead, whotail;
151 static time_t watch_period = 0;
152 static time_t stlast = 0;
153 #ifdef WHODEBUG
154 static	void	debugwholist	(struct who *, struct who *);
155 #endif
156 static	void	print_who	(struct who *);
157 
158 
159 #define ONLINE		01
160 #define OFFLINE		02
161 #define CHANGED		04
162 #define STMASK		07
163 #define ANNOUNCE	010
164 #define CLEARED		020
165 
166 /*
167  * Karl Kleinpaste, 26 Jan 1984.
168  * Initialize the dummy tty list for login watch.
169  * This dummy list eliminates boundary conditions
170  * when doing pointer-chase searches.
171  */
172 void
173 initwatch(void)
174 {
175     whohead.who_next = &whotail;
176     whotail.who_prev = &whohead;
177     stlast = 1;
178 #ifdef WHODEBUG
179     debugwholist(NULL, NULL);
180 #endif /* WHODEBUG */
181 }
182 
183 void
184 resetwatch(void)
185 {
186     watch_period = 0;
187     stlast = 0;
188 }
189 
190 /*
191  * Karl Kleinpaste, 26 Jan 1984.
192  * Watch /etc/utmp for login/logout changes.
193  */
194 void
195 watch_login(int force)
196 {
197     int     comp = -1, alldone;
198     int	    firsttime = stlast == 1;
199 #if defined(HAVE_GETUTENT) || defined(HAVE_GETUTXENT)
200     struct utmp *uptr;
201 #else
202     int utmpfd;
203 #endif
204     struct utmp utmp;
205     struct who *wp, *wpnew;
206     struct varent *v;
207     Char  **vp = NULL;
208     time_t  t, interval = MAILINTVL;
209     struct stat sta;
210 #if defined(HAVE_STRUCT_UTMP_UT_HOST) && defined(_SEQUENT_)
211     char   *host, *ut_find_host();
212 #endif
213 #ifdef WINNT_NATIVE
214     USE(utmp);
215     USE(utmpfd);
216     USE(sta);
217     USE(wpnew);
218 #endif /* WINNT_NATIVE */
219 
220     /* stop SIGINT, lest our login list get trashed. */
221     pintr_disabled++;
222     cleanup_push(&pintr_disabled, disabled_cleanup);
223 
224     v = adrof(STRwatch);
225     if ((v == NULL || v->vec == NULL) && !force) {
226 	cleanup_until(&pintr_disabled);
227 	return;			/* no names to watch */
228     }
229     if (!force) {
230 	trim(vp = v->vec);
231 	if (blklen(vp) % 2)		/* odd # args: 1st == # minutes. */
232 	    interval = (number(*vp)) ? (getn(*vp++) * 60) : MAILINTVL;
233     }
234     else
235 	interval = 0;
236 
237     (void) time(&t);
238     if (t - watch_period < interval) {
239 	cleanup_until(&pintr_disabled);
240 	return;			/* not long enough yet... */
241     }
242     watch_period = t;
243 #ifndef WINNT_NATIVE
244     /*
245      * From: Michael Schroeder <mlschroe@immd4.informatik.uni-erlangen.de>
246      * Don't open utmp all the time, stat it first...
247      */
248     if (stat(TCSH_PATH_UTMP, &sta)) {
249 	if (!force)
250 	    xprintf(CGETS(26, 1,
251 			  "cannot stat %s.  Please \"unset watch\".\n"),
252 		    TCSH_PATH_UTMP);
253 	cleanup_until(&pintr_disabled);
254 	return;
255     }
256     if (stlast == sta.st_mtime) {
257 	cleanup_until(&pintr_disabled);
258 	return;
259     }
260     stlast = sta.st_mtime;
261 #if defined(HAVE_GETUTENT) || defined(HAVE_GETUTXENT)
262     setutent();
263 #else
264     if ((utmpfd = xopen(TCSH_PATH_UTMP, O_RDONLY|O_LARGEFILE)) < 0) {
265 	if (!force)
266 	    xprintf(CGETS(26, 2,
267 			  "%s cannot be opened.  Please \"unset watch\".\n"),
268 		    TCSH_PATH_UTMP);
269 	cleanup_until(&pintr_disabled);
270 	return;
271     }
272     cleanup_push(&utmpfd, open_cleanup);
273 #endif
274 
275     /*
276      * xterm clears the entire utmp entry - mark everyone on the status list
277      * OFFLINE or we won't notice X "logouts"
278      */
279     for (wp = whohead.who_next; wp->who_next != NULL; wp = wp->who_next)
280 	wp->who_status = OFFLINE | CLEARED;
281 
282     /*
283      * Read in the utmp file, sort the entries, and update existing entries or
284      * add new entries to the status list.
285      */
286 #if defined(HAVE_GETUTENT) || defined(HAVE_GETUTXENT)
287     while ((uptr = getutent()) != NULL) {
288         memcpy(&utmp, uptr, sizeof (utmp));
289 #else
290     while (xread(utmpfd, &utmp, sizeof utmp) == sizeof utmp) {
291 #endif
292 
293 # ifdef DEAD_PROCESS
294 #  ifndef IRIS4D
295 	if (utmp.ut_type != USER_PROCESS)
296 	    continue;
297 #  else
298 	/* Why is that? Cause the utmp file is always corrupted??? */
299 	if (utmp.ut_type != USER_PROCESS && utmp.ut_type != DEAD_PROCESS)
300 	    continue;
301 #  endif /* IRIS4D */
302 # endif /* DEAD_PROCESS */
303 
304 	if (utmp.ut_name[0] == '\0' && utmp.ut_line[0] == '\0')
305 	    continue;	/* completely void entry */
306 # ifdef DEAD_PROCESS
307 	if (utmp.ut_type == DEAD_PROCESS && utmp.ut_line[0] == '\0')
308 	    continue;
309 # endif /* DEAD_PROCESS */
310 	wp = whohead.who_next;
311 	while (wp->who_next && (comp = strncmp(wp->who_tty, utmp.ut_line, UTLINLEN)) < 0)
312 	    wp = wp->who_next;/* find that tty! */
313 
314 	if (wp->who_next && comp == 0) {	/* found the tty... */
315 	    if (utmp.ut_time < wp->who_time)
316 	        continue;
317 # ifdef DEAD_PROCESS
318 	    if (utmp.ut_type == DEAD_PROCESS) {
319 		wp->who_time = utmp.ut_time;
320 		wp->who_status = OFFLINE;
321 	    }
322 	    else
323 # endif /* DEAD_PROCESS */
324 	    if (utmp.ut_name[0] == '\0') {
325 		wp->who_time = utmp.ut_time;
326 		wp->who_status = OFFLINE;
327 	    }
328 	    else if (strncmp(utmp.ut_name, wp->who_name, UTNAMLEN) == 0) {
329 		/* someone is logged in */
330 		wp->who_time = utmp.ut_time;
331 		wp->who_status = ONLINE | ANNOUNCE;	/* same guy */
332 	    }
333 	    else {
334 		(void) strncpy(wp->who_new, utmp.ut_name, UTNAMLEN);
335 # ifdef UTHOSTLEN
336 #  ifdef _SEQUENT_
337 		host = ut_find_host(wp->who_tty);
338 		if (host)
339 		    (void) strncpy(wp->who_host, host, UTHOSTLEN);
340 		else
341 		    wp->who_host[0] = 0;
342 #  else
343 		(void) strncpy(wp->who_host, utmp.ut_host, UTHOSTLEN);
344 #  endif
345 # endif /* UTHOSTLEN */
346 		wp->who_time = utmp.ut_time;
347 		if (wp->who_name[0] == '\0')
348 		    wp->who_status = ONLINE;
349 		else
350 		    wp->who_status = CHANGED;
351 	    }
352 	}
353 	else {		/* new tty in utmp */
354 	    wpnew = xcalloc(1, sizeof *wpnew);
355 	    (void) strncpy(wpnew->who_tty, utmp.ut_line, UTLINLEN);
356 # ifdef UTHOSTLEN
357 #  ifdef _SEQUENT_
358 	    host = ut_find_host(wpnew->who_tty);
359 	    if (host)
360 		(void) strncpy(wpnew->who_host, host, UTHOSTLEN);
361 	    else
362 		wpnew->who_host[0] = 0;
363 #  else
364 	    (void) strncpy(wpnew->who_host, utmp.ut_host, UTHOSTLEN);
365 #  endif
366 # endif /* UTHOSTLEN */
367 	    wpnew->who_time = utmp.ut_time;
368 # ifdef DEAD_PROCESS
369 	    if (utmp.ut_type == DEAD_PROCESS)
370 		wpnew->who_status = OFFLINE;
371 	    else
372 # endif /* DEAD_PROCESS */
373 	    if (utmp.ut_name[0] == '\0')
374 		wpnew->who_status = OFFLINE;
375 	    else {
376 		(void) strncpy(wpnew->who_new, utmp.ut_name, UTNAMLEN);
377 		wpnew->who_status = ONLINE;
378 	    }
379 # ifdef WHODEBUG
380 	    debugwholist(wpnew, wp);
381 # endif /* WHODEBUG */
382 
383 	    wpnew->who_next = wp;	/* link in a new 'who' */
384 	    wpnew->who_prev = wp->who_prev;
385 	    wpnew->who_prev->who_next = wpnew;
386 	    wp->who_prev = wpnew;	/* linked in now */
387 	}
388     }
389 #if defined(HAVE_GETUTENT) || defined(HAVE_GETUTXENT)
390     endutent();
391 #else
392     cleanup_until(&utmpfd);
393 #endif
394 #endif /* !WINNT_NATIVE */
395 
396     if (force || vp == NULL) {
397 	cleanup_until(&pintr_disabled);
398 	return;
399     }
400 
401     /*
402      * The state of all logins is now known, so we can search the user's list
403      * of watchables to print the interesting ones.
404      */
405     for (alldone = 0; !alldone && *vp != NULL && **vp != '\0' &&
406 	 *(vp + 1) != NULL && **(vp + 1) != '\0';
407 	 vp += 2) {		/* args used in pairs... */
408 
409 	if (eq(*vp, STRany) && eq(*(vp + 1), STRany))
410 	    alldone = 1;
411 
412 	for (wp = whohead.who_next; wp->who_next != NULL; wp = wp->who_next) {
413 	    if (wp->who_status & ANNOUNCE ||
414 		(!eq(STRany, vp[0]) &&
415 		 !Gmatch(str2short(wp->who_name), vp[0]) &&
416 		 !Gmatch(str2short(wp->who_new),  vp[0])) ||
417 		(!Gmatch(str2short(wp->who_tty),  vp[1]) &&
418 		 !eq(STRany, vp[1])))
419 		continue;	/* entry doesn't qualify */
420 	    /* already printed or not right one to print */
421 
422 
423 	    if (wp->who_status & CLEARED) {/* utmp entry was cleared */
424 		wp->who_time = watch_period;
425 		wp->who_status &= ~CLEARED;
426 	    }
427 
428 	    if ((wp->who_status & OFFLINE) &&
429 		(wp->who_name[0] != '\0')) {
430 		if (!firsttime)
431 		    print_who(wp);
432 		wp->who_name[0] = '\0';
433 		wp->who_status |= ANNOUNCE;
434 		continue;
435 	    }
436 	    if (wp->who_status & ONLINE) {
437 		if (!firsttime)
438 		    print_who(wp);
439 		(void) strcpy(wp->who_name, wp->who_new);
440 		wp->who_status |= ANNOUNCE;
441 		continue;
442 	    }
443 	    if (wp->who_status & CHANGED) {
444 		if (!firsttime)
445 		    print_who(wp);
446 		(void) strcpy(wp->who_name, wp->who_new);
447 		wp->who_status |= ANNOUNCE;
448 		continue;
449 	    }
450 	}
451     }
452     cleanup_until(&pintr_disabled);
453 }
454 
455 #ifdef WHODEBUG
456 static void
457 debugwholist(struct who *new, struct who *wp)
458 {
459     struct who *a;
460 
461     a = whohead.who_next;
462     while (a->who_next != NULL) {
463 	xprintf("%s/%s -> ", a->who_name, a->who_tty);
464 	a = a->who_next;
465     }
466     xprintf("TAIL\n");
467     if (a != &whotail) {
468 	xprintf(CGETS(26, 3, "BUG! last element is not whotail!\n"));
469 	abort();
470     }
471     a = whotail.who_prev;
472     xprintf(CGETS(26, 4, "backward: "));
473     while (a->who_prev != NULL) {
474 	xprintf("%s/%s -> ", a->who_name, a->who_tty);
475 	a = a->who_prev;
476     }
477     xprintf("HEAD\n");
478     if (a != &whohead) {
479 	xprintf(CGETS(26, 5, "BUG! first element is not whohead!\n"));
480 	abort();
481     }
482     if (new)
483 	xprintf(CGETS(26, 6, "new: %s/%s\n"), new->who_name, new->who_tty);
484     if (wp)
485 	xprintf("wp: %s/%s\n", wp->who_name, wp->who_tty);
486 }
487 #endif /* WHODEBUG */
488 
489 
490 static void
491 print_who(struct who *wp)
492 {
493 #ifdef UTHOSTLEN
494     Char   *cp = str2short(CGETS(26, 7, "%n has %a %l from %m."));
495 #else
496     Char   *cp = str2short(CGETS(26, 8, "%n has %a %l."));
497 #endif /* UTHOSTLEN */
498     struct varent *vp = adrof(STRwho);
499     Char *str;
500 
501     if (vp && vp->vec && vp->vec[0])
502 	cp = vp->vec[0];
503 
504     str = tprintf(FMT_WHO, cp, NULL, wp->who_time, wp);
505     cleanup_push(str, xfree);
506     for (cp = str; *cp;)
507 	xputwchar(*cp++);
508     cleanup_until(str);
509     xputchar('\n');
510 } /* end print_who */
511 
512 
513 char *
514 who_info(ptr_t ptr, int c)
515 {
516     struct who *wp = ptr;
517     char *wbuf;
518 #ifdef UTHOSTLEN
519     char *wb;
520     int flg;
521     char *pb;
522 #endif /* UTHOSTLEN */
523 
524     switch (c) {
525     case 'n':		/* user name */
526 	switch (wp->who_status & STMASK) {
527 	case ONLINE:
528 	case CHANGED:
529 	    return strsave(wp->who_new);
530 	case OFFLINE:
531 	    return strsave(wp->who_name);
532 	default:
533 	    break;
534 	}
535 	break;
536 
537     case 'a':
538 	switch (wp->who_status & STMASK) {
539 	case ONLINE:
540 	    return strsave(CGETS(26, 9, "logged on"));
541 	case OFFLINE:
542 	    return strsave(CGETS(26, 10, "logged off"));
543 	case CHANGED:
544 	    return xasprintf(CGETS(26, 11, "replaced %s on"), wp->who_name);
545 	default:
546 	    break;
547 	}
548 	break;
549 
550 #ifdef UTHOSTLEN
551     case 'm':
552 	if (wp->who_host[0] == '\0')
553 	    return strsave(CGETS(26, 12, "local"));
554 	else {
555 	    pb = wp->who_host;
556 	    wbuf = xmalloc(strlen(pb) + 1);
557 	    wb = wbuf;
558 	    /* the ':' stuff is for <host>:<display>.<screen> */
559 	    for (flg = isdigit((unsigned char)*pb) ? '\0' : '.';
560 		 *pb != '\0' && (*pb != flg || ((pb = strchr(pb, ':')) != 0));
561 		 pb++) {
562 		if (*pb == ':')
563 		    flg = '\0';
564 		*wb++ = isupper((unsigned char)*pb) ?
565 		    tolower((unsigned char)*pb) : *pb;
566 	    }
567 	    *wb = '\0';
568 	    return wbuf;
569 	}
570 
571     case 'M':
572 	if (wp->who_host[0] == '\0')
573 	    return strsave(CGETS(26, 12, "local"));
574 	else {
575 	    pb = wp->who_host;
576 	    wbuf = xmalloc(strlen(pb) + 1);
577 	    wb = wbuf;
578 	    for (; *pb != '\0'; pb++)
579 		*wb++ = isupper((unsigned char)*pb) ?
580 		    tolower((unsigned char)*pb) : *pb;
581 	    *wb = '\0';
582 	    return wbuf;
583 	}
584 #endif /* UTHOSTLEN */
585 
586     case 'l':
587 	return strsave(wp->who_tty);
588 
589     default:
590 	wbuf = xmalloc(3);
591 	wbuf[0] = '%';
592 	wbuf[1] = (char) c;
593 	wbuf[2] = '\0';
594 	return wbuf;
595     }
596     return NULL;
597 }
598 
599 void
600 /*ARGSUSED*/
601 dolog(Char **v, struct command *c)
602 {
603     struct who *wp;
604     struct varent *vp;
605 
606     USE(v);
607     USE(c);
608     vp = adrof(STRwatch);	/* lint insists vp isn't used unless we */
609     if (vp == NULL)		/* unless we assign it outside the if */
610 	stderror(ERR_NOWATCH);
611     resetwatch();
612     wp = whohead.who_next;
613     while (wp->who_next != NULL) {
614 	wp->who_name[0] = '\0';
615 	wp = wp->who_next;
616     }
617 }
618 
619 # ifdef UTHOSTLEN
620 size_t
621 utmphostsize(void)
622 {
623     return UTHOSTLEN;
624 }
625 
626 char *
627 utmphost(void)
628 {
629     char *tty = short2str(varval(STRtty));
630     struct who *wp;
631     char *host = NULL;
632 
633     watch_login(1);
634 
635     for (wp = whohead.who_next; wp->who_next != NULL; wp = wp->who_next) {
636 	if (strcmp(tty, wp->who_tty) == 0)
637 	    host = wp->who_host;
638 	wp->who_name[0] = '\0';
639     }
640     resetwatch();
641     return host;
642 }
643 # endif /* UTHOSTLEN */
644 
645 #endif /* HAVENOUTMP */
646