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