xref: /freebsd/contrib/tcsh/tc.who.c (revision ae83180158c4c937f170e31eff311b18c0286a93)
1 /* $Header: /src/pub/tcsh/tc.who.c,v 3.35 2002/07/01 21:12:04 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 
35 RCSID("$Id: tc.who.c,v 3.35 2002/07/01 21:12:04 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 HAVEUTMPX
46 # include <utmpx.h>
47 /* I just redefine a few words here.  Changing every occurrence below
48  * seems like too much of work.  All UTMP functions have equivalent
49  * UTMPX counterparts, so they can be added all here when needed.
50  * Kimmo Suominen, Oct 14 1991
51  */
52 # ifndef _PATH_UTMP
53 #  if defined(__UTMPX_FILE) && !defined(UTMPX_FILE)
54 #   define _PATH_UTMP __UTMPX_FILE
55 #  else
56 #   define _PATH_UTMP UTMPX_FILE
57 #  endif /* __UTMPX_FILE && !UTMPX_FILE */
58 # endif /* _PATH_UTMP */
59 # define utmp utmpx
60 # ifdef __MVS__
61 #  define ut_time ut_tv.tv_sec
62 #  define ut_name ut_user
63 # else
64 #  define ut_time ut_xtime
65 # endif /* __MVS__ */
66 #else /* !HAVEUTMPX */
67 # ifndef WINNT_NATIVE
68 #  include <utmp.h>
69 # endif /* WINNT_NATIVE */
70 #endif /* HAVEUTMPX */
71 
72 #ifndef BROKEN_CC
73 # define UTNAMLEN	sizeof(((struct utmp *) 0)->ut_name)
74 # define UTLINLEN	sizeof(((struct utmp *) 0)->ut_line)
75 # ifdef UTHOST
76 #  ifdef _SEQUENT_
77 #   define UTHOSTLEN	100
78 #  else
79 #   define UTHOSTLEN	sizeof(((struct utmp *) 0)->ut_host)
80 #  endif
81 # endif	/* UTHOST */
82 #else
83 /* give poor cc a little help if it needs it */
84 struct utmp __ut;
85 
86 # define UTNAMLEN	sizeof(__ut.ut_name)
87 # define UTLINLEN	sizeof(__ut.ut_line)
88 # ifdef UTHOST
89 #  ifdef _SEQUENT_
90 #   define UTHOSTLEN	100
91 #  else
92 #   define UTHOSTLEN	sizeof(__ut.ut_host)
93 #  endif
94 # endif /* UTHOST */
95 #endif /* BROKEN_CC */
96 
97 #ifndef _PATH_UTMP
98 # ifdef	UTMP_FILE
99 #  define _PATH_UTMP UTMP_FILE
100 # else
101 #  define _PATH_UTMP "/etc/utmp"
102 # endif /* UTMP_FILE */
103 #endif /* _PATH_UTMP */
104 
105 
106 struct who {
107     struct who *who_next;
108     struct who *who_prev;
109     char    who_name[UTNAMLEN + 1];
110     char    who_new[UTNAMLEN + 1];
111     char    who_tty[UTLINLEN + 1];
112 #ifdef UTHOST
113     char    who_host[UTHOSTLEN + 1];
114 #endif /* UTHOST */
115     time_t  who_time;
116     int     who_status;
117 };
118 
119 static struct who whohead, whotail;
120 static time_t watch_period = 0;
121 static time_t stlast = 0;
122 #ifdef WHODEBUG
123 static	void	debugwholist	__P((struct who *, struct who *));
124 #endif
125 static	void	print_who	__P((struct who *));
126 
127 
128 #define ONLINE		01
129 #define OFFLINE		02
130 #define CHANGED		04
131 #define STMASK		07
132 #define ANNOUNCE	010
133 
134 /*
135  * Karl Kleinpaste, 26 Jan 1984.
136  * Initialize the dummy tty list for login watch.
137  * This dummy list eliminates boundary conditions
138  * when doing pointer-chase searches.
139  */
140 void
141 initwatch()
142 {
143     whohead.who_next = &whotail;
144     whotail.who_prev = &whohead;
145     stlast = 1;
146 #ifdef WHODEBUG
147     debugwholist(NULL, NULL);
148 #endif /* WHODEBUG */
149 }
150 
151 void
152 resetwatch()
153 {
154     watch_period = 0;
155     stlast = 0;
156 }
157 
158 /*
159  * Karl Kleinpaste, 26 Jan 1984.
160  * Watch /etc/utmp for login/logout changes.
161  */
162 void
163 watch_login(force)
164     int force;
165 {
166     int     utmpfd, comp = -1, alldone;
167     int	    firsttime = stlast == 1;
168 #ifdef BSDSIGS
169     sigmask_t omask;
170 #endif				/* BSDSIGS */
171     struct utmp utmp;
172     struct who *wp, *wpnew;
173     struct varent *v;
174     Char  **vp = NULL;
175     time_t  t, interval = MAILINTVL;
176     struct stat sta;
177 #if defined(UTHOST) && defined(_SEQUENT_)
178     char   *host, *ut_find_host();
179 #endif
180 #ifdef WINNT_NATIVE
181     static int ncbs_posted = 0;
182     USE(utmp);
183     USE(utmpfd);
184     USE(sta);
185     USE(wpnew);
186 #endif /* WINNT_NATIVE */
187 
188     /* stop SIGINT, lest our login list get trashed. */
189 #ifdef BSDSIGS
190     omask = sigblock(sigmask(SIGINT));
191 #else
192     (void) sighold(SIGINT);
193 #endif
194 
195     v = adrof(STRwatch);
196     if ((v == NULL || v->vec == NULL) && !force) {
197 #ifdef BSDSIGS
198 	(void) sigsetmask(omask);
199 #else
200 	(void) sigrelse(SIGINT);
201 #endif
202 	return;			/* no names to watch */
203     }
204     if (!force) {
205 	trim(vp = v->vec);
206 	if (blklen(vp) % 2)		/* odd # args: 1st == # minutes. */
207 	    interval = (number(*vp)) ? (getn(*vp++) * 60) : MAILINTVL;
208     }
209     else
210 	interval = 0;
211 
212     (void) time(&t);
213 #ifdef WINNT_NATIVE
214 	/*
215 	 * Since NCB_ASTATs take time, start em async at least 90 secs
216 	 * before we are due -amol 6/5/97
217 	 */
218 	if (!ncbs_posted) {
219 	    unsigned long tdiff = t - watch_period;
220 	    if (!watch_period || ((tdiff  > 0) && (tdiff > (interval - 90)))) {
221 		start_ncbs(vp);
222  		ncbs_posted = 1;
223 	    }
224 	}
225 #endif /* WINNT_NATIVE */
226     if (t - watch_period < interval) {
227 #ifdef BSDSIGS
228 	(void) sigsetmask(omask);
229 #else
230 	(void) sigrelse(SIGINT);
231 #endif
232 	return;			/* not long enough yet... */
233     }
234     watch_period = t;
235 #ifdef WINNT_NATIVE
236     ncbs_posted = 0;
237 #else /* !WINNT_NATIVE */
238 
239     /*
240      * From: Michael Schroeder <mlschroe@immd4.informatik.uni-erlangen.de>
241      * Don't open utmp all the time, stat it first...
242      */
243     if (stat(_PATH_UTMP, &sta)) {
244 	if (!force)
245 	    xprintf(CGETS(26, 1,
246 			  "cannot stat %s.  Please \"unset watch\".\n"),
247 		    _PATH_UTMP);
248 # ifdef BSDSIGS
249 	(void) sigsetmask(omask);
250 # else
251 	(void) sigrelse(SIGINT);
252 # endif
253 	return;
254     }
255     if (stlast == sta.st_mtime) {
256 # ifdef BSDSIGS
257 	(void) sigsetmask(omask);
258 # else
259 	(void) sigrelse(SIGINT);
260 # endif
261 	return;
262     }
263     stlast = sta.st_mtime;
264     if ((utmpfd = open(_PATH_UTMP, O_RDONLY)) < 0) {
265 	if (!force)
266 	    xprintf(CGETS(26, 2,
267 			  "%s cannot be opened.  Please \"unset watch\".\n"),
268 		    _PATH_UTMP);
269 # ifdef BSDSIGS
270 	(void) sigsetmask(omask);
271 # else
272 	(void) sigrelse(SIGINT);
273 # endif
274 	return;
275     }
276 
277     /*
278      * xterm clears the entire utmp entry - mark everyone on the status list
279      * OFFLINE or we won't notice X "logouts"
280      */
281     for (wp = whohead.who_next; wp->who_next != NULL; wp = wp->who_next) {
282 	wp->who_status = OFFLINE;
283 	wp->who_time = 0;
284     }
285 
286     /*
287      * Read in the utmp file, sort the entries, and update existing entries or
288      * add new entries to the status list.
289      */
290     while (read(utmpfd, (char *) &utmp, sizeof utmp) == sizeof utmp) {
291 
292 # ifdef DEAD_PROCESS
293 #  ifndef IRIS4D
294 	if (utmp.ut_type != USER_PROCESS)
295 	    continue;
296 #  else
297 	/* Why is that? Cause the utmp file is always corrupted??? */
298 	if (utmp.ut_type != USER_PROCESS && utmp.ut_type != DEAD_PROCESS)
299 	    continue;
300 #  endif /* IRIS4D */
301 # endif /* DEAD_PROCESS */
302 
303 	if (utmp.ut_name[0] == '\0' && utmp.ut_line[0] == '\0')
304 	    continue;	/* completely void entry */
305 # ifdef DEAD_PROCESS
306 	if (utmp.ut_type == DEAD_PROCESS && utmp.ut_line[0] == '\0')
307 	    continue;
308 # endif /* DEAD_PROCESS */
309 	wp = whohead.who_next;
310 	while (wp->who_next && (comp = strncmp(wp->who_tty, utmp.ut_line, UTLINLEN)) < 0)
311 	    wp = wp->who_next;/* find that tty! */
312 
313 	if (wp->who_next && comp == 0) {	/* found the tty... */
314 # ifdef DEAD_PROCESS
315 	    if (utmp.ut_type == DEAD_PROCESS) {
316 		wp->who_time = utmp.ut_time;
317 		wp->who_status = OFFLINE;
318 	    }
319 	    else
320 # endif /* DEAD_PROCESS */
321 	    if (utmp.ut_name[0] == '\0') {
322 		wp->who_time = utmp.ut_time;
323 		wp->who_status = OFFLINE;
324 	    }
325 	    else if (strncmp(utmp.ut_name, wp->who_name, UTNAMLEN) == 0) {
326 		/* someone is logged in */
327 		wp->who_time = utmp.ut_time;
328 		wp->who_status = 0;	/* same guy */
329 	    }
330 	    else {
331 		(void) strncpy(wp->who_new, utmp.ut_name, UTNAMLEN);
332 # ifdef UTHOST
333 #  ifdef _SEQUENT_
334 		host = ut_find_host(wp->who_tty);
335 		if (host)
336 		    (void) strncpy(wp->who_host, host, UTHOSTLEN);
337 		else
338 		    wp->who_host[0] = 0;
339 #  else
340 		(void) strncpy(wp->who_host, utmp.ut_host, UTHOSTLEN);
341 #  endif
342 # endif /* UTHOST */
343 		wp->who_time = utmp.ut_time;
344 		if (wp->who_name[0] == '\0')
345 		    wp->who_status = ONLINE;
346 		else
347 		    wp->who_status = CHANGED;
348 	    }
349 	}
350 	else {		/* new tty in utmp */
351 	    wpnew = (struct who *) xcalloc(1, sizeof *wpnew);
352 	    (void) strncpy(wpnew->who_tty, utmp.ut_line, UTLINLEN);
353 # ifdef UTHOST
354 #  ifdef _SEQUENT_
355 	    host = ut_find_host(wpnew->who_tty);
356 	    if (host)
357 		(void) strncpy(wpnew->who_host, host, UTHOSTLEN);
358 	    else
359 		wpnew->who_host[0] = 0;
360 #  else
361 	    (void) strncpy(wpnew->who_host, utmp.ut_host, UTHOSTLEN);
362 #  endif
363 # endif /* UTHOST */
364 	    wpnew->who_time = utmp.ut_time;
365 # ifdef DEAD_PROCESS
366 	    if (utmp.ut_type == DEAD_PROCESS)
367 		wpnew->who_status = OFFLINE;
368 	    else
369 # endif /* DEAD_PROCESS */
370 	    if (utmp.ut_name[0] == '\0')
371 		wpnew->who_status = OFFLINE;
372 	    else {
373 		(void) strncpy(wpnew->who_new, utmp.ut_name, UTNAMLEN);
374 		wpnew->who_status = ONLINE;
375 	    }
376 # ifdef WHODEBUG
377 	    debugwholist(wpnew, wp);
378 # endif /* WHODEBUG */
379 
380 	    wpnew->who_next = wp;	/* link in a new 'who' */
381 	    wpnew->who_prev = wp->who_prev;
382 	    wpnew->who_prev->who_next = wpnew;
383 	    wp->who_prev = wpnew;	/* linked in now */
384 	}
385     }
386     (void) close(utmpfd);
387 # if defined(UTHOST) && defined(_SEQUENT_)
388     endutent();
389 # endif
390 #endif /* !WINNT_NATIVE */
391 
392     if (force || vp == NULL)
393 	return;
394 
395     /*
396      * The state of all logins is now known, so we can search the user's list
397      * of watchables to print the interesting ones.
398      */
399     for (alldone = 0; !alldone && *vp != NULL && **vp != '\0' &&
400 	 *(vp + 1) != NULL && **(vp + 1) != '\0';
401 	 vp += 2) {		/* args used in pairs... */
402 
403 	if (eq(*vp, STRany) && eq(*(vp + 1), STRany))
404 	    alldone = 1;
405 
406 	for (wp = whohead.who_next; wp->who_next != NULL; wp = wp->who_next) {
407 	    if (wp->who_status & ANNOUNCE ||
408 		(!eq(STRany, vp[0]) &&
409 		 !Gmatch(str2short(wp->who_name), vp[0]) &&
410 		 !Gmatch(str2short(wp->who_new),  vp[0])) ||
411 		(!Gmatch(str2short(wp->who_tty),  vp[1]) &&
412 		 !eq(STRany, vp[1])))
413 		continue;	/* entry doesn't qualify */
414 	    /* already printed or not right one to print */
415 
416 
417 	    if (wp->who_time == 0)/* utmp entry was cleared */
418 		wp->who_time = watch_period;
419 
420 	    if ((wp->who_status & OFFLINE) &&
421 		(wp->who_name[0] != '\0')) {
422 		if (!firsttime)
423 		    print_who(wp);
424 		wp->who_name[0] = '\0';
425 		wp->who_status |= ANNOUNCE;
426 		continue;
427 	    }
428 	    if (wp->who_status & ONLINE) {
429 		if (!firsttime)
430 		    print_who(wp);
431 		(void) strcpy(wp->who_name, wp->who_new);
432 		wp->who_status |= ANNOUNCE;
433 		continue;
434 	    }
435 	    if (wp->who_status & CHANGED) {
436 		if (!firsttime)
437 		    print_who(wp);
438 		(void) strcpy(wp->who_name, wp->who_new);
439 		wp->who_status |= ANNOUNCE;
440 		continue;
441 	    }
442 	}
443     }
444 #ifdef BSDSIGS
445     (void) sigsetmask(omask);
446 #else
447     (void) sigrelse(SIGINT);
448 #endif
449 }
450 
451 #ifdef WHODEBUG
452 static void
453 debugwholist(new, wp)
454     register struct who *new, *wp;
455 {
456     register struct who *a;
457 
458     a = whohead.who_next;
459     while (a->who_next != NULL) {
460 	xprintf("%s/%s -> ", a->who_name, a->who_tty);
461 	a = a->who_next;
462     }
463     xprintf("TAIL\n");
464     if (a != &whotail) {
465 	xprintf(CGETS(26, 3, "BUG! last element is not whotail!\n"));
466 	abort();
467     }
468     a = whotail.who_prev;
469     xprintf(CGETS(26, 4, "backward: "));
470     while (a->who_prev != NULL) {
471 	xprintf("%s/%s -> ", a->who_name, a->who_tty);
472 	a = a->who_prev;
473     }
474     xprintf("HEAD\n");
475     if (a != &whohead) {
476 	xprintf(CGETS(26, 5, "BUG! first element is not whohead!\n"));
477 	abort();
478     }
479     if (new)
480 	xprintf(CGETS(26, 6, "new: %s/%s\n"), new->who_name, new->who_tty);
481     if (wp)
482 	xprintf("wp: %s/%s\n", wp->who_name, wp->who_tty);
483 }
484 #endif /* WHODEBUG */
485 
486 
487 static void
488 print_who(wp)
489     struct who *wp;
490 {
491 #ifdef UTHOST
492     Char   *cp = str2short(CGETS(26, 7, "%n has %a %l from %m."));
493 #else
494     Char   *cp = str2short(CGETS(26, 8, "%n has %a %l."));
495 #endif /* UTHOST */
496     struct varent *vp = adrof(STRwho);
497     Char buf[BUFSIZE];
498 
499     if (vp && vp->vec && vp->vec[0])
500 	cp = vp->vec[0];
501 
502     tprintf(FMT_WHO, buf, cp, BUFSIZE, NULL, wp->who_time, (ptr_t) wp);
503     for (cp = buf; *cp;)
504 	xputchar(*cp++);
505     xputchar('\n');
506 } /* end print_who */
507 
508 
509 const char *
510 who_info(ptr, c, wbuf, wbufsiz)
511     ptr_t ptr;
512     int c;
513     char *wbuf;
514     size_t wbufsiz;
515 {
516     struct who *wp = (struct who *) ptr;
517 #ifdef UTHOST
518     char *wb = wbuf;
519     int flg;
520     char *pb;
521 #endif /* UTHOST */
522 
523     switch (c) {
524     case 'n':		/* user name */
525 	switch (wp->who_status & STMASK) {
526 	case ONLINE:
527 	case CHANGED:
528 	    return wp->who_new;
529 	case OFFLINE:
530 	    return wp->who_name;
531 	default:
532 	    break;
533 	}
534 	break;
535 
536     case 'a':
537 	switch (wp->who_status & STMASK) {
538 	case ONLINE:
539 	    return CGETS(26, 9, "logged on");
540 	case OFFLINE:
541 	    return CGETS(26, 10, "logged off");
542 	case CHANGED:
543 	    xsnprintf(wbuf, wbufsiz, CGETS(26, 11, "replaced %s on"),
544 		      wp->who_name);
545 	    return wbuf;
546 	default:
547 	    break;
548 	}
549 	break;
550 
551 #ifdef UTHOST
552     case 'm':
553 	if (wp->who_host[0] == '\0')
554 	    return CGETS(26, 12, "local");
555 	else {
556 	    /* the ':' stuff is for <host>:<display>.<screen> */
557 	    for (pb = wp->who_host, flg = Isdigit(*pb) ? '\0' : '.';
558 		 *pb != '\0' &&
559 		 (*pb != flg || ((pb = strchr(pb, ':')) != 0));
560 		 pb++) {
561 		if (*pb == ':')
562 		    flg = '\0';
563 		*wb++ = Isupper(*pb) ? Tolower(*pb) : *pb;
564 	    }
565 	    *wb = '\0';
566 	    return wbuf;
567 	}
568 
569     case 'M':
570 	if (wp->who_host[0] == '\0')
571 	    return CGETS(26, 12, "local");
572 	else {
573 	    for (pb = wp->who_host; *pb != '\0'; pb++)
574 		*wb++ = Isupper(*pb) ? Tolower(*pb) : *pb;
575 	    *wb = '\0';
576 	    return wbuf;
577 	}
578 #endif /* UTHOST */
579 
580     case 'l':
581 	return wp->who_tty;
582 
583     default:
584 	wbuf[0] = '%';
585 	wbuf[1] = (char) c;
586 	wbuf[2] = '\0';
587 	return wbuf;
588     }
589     return NULL;
590 }
591 
592 void
593 /*ARGSUSED*/
594 dolog(v, c)
595 Char **v;
596 struct command *c;
597 {
598     struct who *wp;
599     struct varent *vp;
600 
601     USE(v);
602     USE(c);
603     vp = adrof(STRwatch);	/* lint insists vp isn't used unless we */
604     if (vp == NULL)		/* unless we assign it outside the if */
605 	stderror(ERR_NOWATCH);
606     resetwatch();
607     wp = whohead.who_next;
608     while (wp->who_next != NULL) {
609 	wp->who_name[0] = '\0';
610 	wp = wp->who_next;
611     }
612 }
613 
614 # ifdef UTHOST
615 size_t
616 utmphostsize()
617 {
618     return UTHOSTLEN;
619 }
620 
621 char *
622 utmphost()
623 {
624     char *tty = short2str(varval(STRtty));
625     struct who *wp;
626     char *host = NULL;
627 
628     watch_login(1);
629 
630     for (wp = whohead.who_next; wp->who_next != NULL; wp = wp->who_next) {
631 	if (strcmp(tty, wp->who_tty) == 0)
632 	    host = wp->who_host;
633 	wp->who_name[0] = '\0';
634     }
635     resetwatch();
636     return host;
637 }
638 # endif /* UTHOST */
639 
640 #ifdef WINNT_NATIVE
641 void add_to_who_list(name, mach_nm)
642     char *name;
643     char *mach_nm;
644 {
645 
646     struct who *wp, *wpnew;
647     int comp = -1;
648 
649     wp = whohead.who_next;
650     while (wp->who_next && (comp = strncmp(wp->who_tty,mach_nm,UTLINLEN)) < 0)
651 	wp = wp->who_next;/* find that tty! */
652 
653     if (wp->who_next && comp == 0) {	/* found the tty... */
654 
655 	if (*name == '\0') {
656 	    wp->who_time = 0;
657 	    wp->who_status = OFFLINE;
658 	}
659 	else if (strncmp(name, wp->who_name, UTNAMLEN) == 0) {
660 	    /* someone is logged in */
661 	    wp->who_time = 0;
662 	    wp->who_status = 0;	/* same guy */
663 	}
664 	else {
665 	    (void) strncpy(wp->who_new, name, UTNAMLEN);
666 	    wp->who_time = 0;
667 	    if (wp->who_name[0] == '\0')
668 		wp->who_status = ONLINE;
669 	    else
670 		wp->who_status = CHANGED;
671 	}
672     }
673     else {
674 	wpnew = (struct who *) xcalloc(1, sizeof *wpnew);
675 	(void) strncpy(wpnew->who_tty, mach_nm, UTLINLEN);
676 	wpnew->who_time = 0;
677 	if (*name == '\0')
678 	    wpnew->who_status = OFFLINE;
679 	else {
680 	    (void) strncpy(wpnew->who_new, name, UTNAMLEN);
681 	    wpnew->who_status = ONLINE;
682 	}
683 #ifdef WHODEBUG
684 	debugwholist(wpnew, wp);
685 #endif /* WHODEBUG */
686 
687 	wpnew->who_next = wp;	/* link in a new 'who' */
688 	wpnew->who_prev = wp->who_prev;
689 	wpnew->who_prev->who_next = wpnew;
690 	wp->who_prev = wpnew;	/* linked in now */
691     }
692 }
693 #endif /* WINNT_NATIVE */
694 #endif /* HAVENOUTMP */
695