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