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