diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 12:46:01 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 12:46:01 +0000 |
commit | 23c86b12fa7151e0942d1c0d12e926816e25cd72 (patch) | |
tree | 37c8fa89d7982fa5c44fb83e5275791a93b9141e /utmp.c | |
parent | Initial commit. (diff) | |
download | screen-23c86b12fa7151e0942d1c0d12e926816e25cd72.tar.xz screen-23c86b12fa7151e0942d1c0d12e926816e25cd72.zip |
Adding upstream version 4.8.0.upstream/4.8.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'utmp.c')
-rw-r--r-- | utmp.c | 893 |
1 files changed, 893 insertions, 0 deletions
@@ -0,0 +1,893 @@ +/* Copyright (c) 2008, 2009 + * Juergen Weigert (jnweiger@immd4.informatik.uni-erlangen.de) + * Michael Schroeder (mlschroe@immd4.informatik.uni-erlangen.de) + * Micah Cowan (micah@cowan.name) + * Sadrul Habib Chowdhury (sadrul@users.sourceforge.net) + * Copyright (c) 1993-2002, 2003, 2005, 2006, 2007 + * Juergen Weigert (jnweiger@immd4.informatik.uni-erlangen.de) + * Michael Schroeder (mlschroe@immd4.informatik.uni-erlangen.de) + * Copyright (c) 1987 Oliver Laumann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (see the file COPYING); if not, see + * https://www.gnu.org/licenses/, or contact Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + * + **************************************************************** + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "config.h" +#include "screen.h" +#include "extern.h" + +#ifdef HAVE_UTEMPTER +#include <utempter.h> +#endif + + +extern struct display *display; +#ifdef CAREFULUTMP +extern struct win *windows; +#endif +extern struct win *fore; +extern char *LoginName; +extern int real_uid, eff_uid; + + +/* + * UTNOKEEP: A (ugly) hack for apollo that does two things: + * 1) Always close and reopen the utmp file descriptor. (I don't know + * for what reason this is done...) + * 2) Implement an unsorted utmp file much like GETUTENT. + * (split into UT_CLOSE and UT_UNSORTED) + */ + + +#ifdef UTNOKEEP +# define UT_CLOSE +# define UT_UNSORTED +#endif + +#ifdef UT_CLOSE +# undef UT_CLOSE +# define UT_CLOSE endutent() +#else +# define UT_CLOSE +#endif + + +/* + * we have a suid-root helper app that changes the utmp for us + * (won't work for login-slots) + */ +#if (defined(sun) && defined(SVR4) && defined(GETUTENT)) || defined(HAVE_UTEMPTER) +# define UTMP_HELPER +#endif + + + +#ifdef UTMPOK + + +static slot_t TtyNameSlot __P((char *)); +static void makeuser __P((struct utmp *, char *, char *, int)); +static void makedead __P((struct utmp *)); +static int pututslot __P((slot_t, struct utmp *, char *, struct win *)); +static struct utmp *getutslot __P((slot_t)); +#ifndef GETUTENT +static struct utmp *getutent __P((void)); +static void endutent __P((void)); +static int initutmp __P((void)); +static void setutent __P((void)); +#endif +#if defined(linux) && defined(GETUTENT) +static struct utmp *xpututline __P((struct utmp *utmp)); +# define pututline xpututline +#endif + + +static int utmpok; +static char UtmpName[] = UTMPFILE; +#ifndef UTMP_HELPER +static int utmpfd = -1; +#endif + + +# if defined(GETUTENT) && (!defined(SVR4) || defined(__hpux)) && ! defined(__CYGWIN__) +# if defined(hpux) /* cruel hpux release 8.0 */ +# define pututline _pututline +# endif /* hpux */ +extern struct utmp *getutline(), *pututline(); +# if defined(_SEQUENT_) +extern struct utmp *ut_add_user(), *ut_delete_user(); +extern char *ut_find_host(); +# ifndef UTHOST +# define UTHOST /* _SEQUENT_ has ut_find_host() */ +# endif +# endif /* _SEQUENT_ */ +# endif /* GETUTENT && !SVR4 */ + +# if !defined(GETUTENT) && !defined(UT_UNSORTED) +# ifdef GETTTYENT +# include <ttyent.h> +# else +struct ttyent { char *ty_name; }; +static void setttyent __P((void)); +static struct ttyent *getttyent __P((void)); +# endif +# endif /* !GETUTENT && !UT_UNSORTED */ + +#ifndef _SEQUENT_ +# undef D_loginhost +# define D_loginhost D_utmp_logintty.ut_host +#endif +#ifndef UTHOST +# undef D_loginhost +# define D_loginhost ((char *)0) +#endif + + +#endif /* UTMPOK */ + + +/* + * SlotToggle - modify the utmp slot of the fore window. + * + * how > 0 do try to set a utmp slot. + * how = 0 try to withdraw a utmp slot. + * + * w_slot = -1 window not logged in. + * w_slot = 0 window not logged in, but should be logged in. + * (unable to write utmp, or detached). + */ + +#ifndef UTMPOK +void +SlotToggle(how) +int how; +{ + debug1("SlotToggle (!UTMPOK) %d\n", how); +# ifdef UTMPFILE + Msg(0, "Unable to modify %s.\n", UTMPFILE); +# else + Msg(0, "Unable to modify utmp-database.\n"); +# endif +} +#endif + + + +#ifdef UTMPOK + +void +SlotToggle(how) +int how; +{ + debug1("SlotToggle %d\n", how); + if (fore->w_type != W_TYPE_PTY) + { + Msg(0, "Can only work with normal windows.\n"); + return; + } + if (how) + { + debug(" try to log in\n"); + if ((fore->w_slot == (slot_t) -1) || (fore->w_slot == (slot_t) 0)) + { +#ifdef USRLIMIT + if (CountUsers() >= USRLIMIT) + { + Msg(0, "User limit reached."); + return; + } +#endif + if (SetUtmp(fore) == 0) + Msg(0, "This window is now logged in."); + else + Msg(0, "This window should now be logged in."); + WindowChanged(fore, 'f'); + } + else + Msg(0, "This window is already logged in."); + } + else + { + debug(" try to log out\n"); + if (fore->w_slot == (slot_t) -1) + Msg(0, "This window is already logged out\n"); + else if (fore->w_slot == (slot_t) 0) + { + debug("What a relief! In fact, it was not logged in\n"); + Msg(0, "This window is not logged in."); + fore->w_slot = (slot_t) -1; + } + else + { + RemoveUtmp(fore); + if (fore->w_slot != (slot_t) -1) + Msg(0, "What? Cannot remove Utmp slot?"); + else + Msg(0, "This window is no longer logged in."); +#ifdef CAREFULUTMP + CarefulUtmp(); +#endif + WindowChanged(fore, 'f'); + } + } +} + + +#ifdef CAREFULUTMP + +/* CAREFULUTMP: goodie for paranoid sysadmins: always leave one + * window logged in + */ +void +CarefulUtmp() +{ + struct win *p; + + if (!windows) /* hopeless */ + return; + debug("CarefulUtmp counting slots\n"); + for (p = windows; p; p = p->w_next) + if (p->w_ptyfd >= 0 && p->w_slot != (slot_t)-1) + return; /* found one, nothing to do */ + + debug("CarefulUtmp: no slots, log one in again.\n"); + for (p = windows; p; p = p->w_next) + if (p->w_ptyfd >= 0) /* no zombies please */ + break; + if (!p) + return; /* really hopeless */ + SetUtmp(p); + Msg(0, "Window %d is now logged in.\n", p->w_number); +} +#endif /* CAREFULUTMP */ + + +void +InitUtmp() +{ + debug1("InitUtmp testing '%s'...\n", UtmpName); +#ifndef UTMP_HELPER + if ((utmpfd = open(UtmpName, O_RDWR)) == -1) + { + if (errno != EACCES) + Msg(errno, "%s", UtmpName); + debug("InitUtmp failed.\n"); + utmpok = 0; + return; + } +# ifdef GETUTENT + close(utmpfd); /* it was just a test */ + utmpfd = -1; +# endif /* GETUTENT */ +#endif /* UTMP_HELPER */ + utmpok = 1; +} + + +#ifdef USRLIMIT +int +CountUsers() +{ + struct utmp *ut; + int UserCount; + + debug1("CountUsers() - utmpok=%d\n", utmpok); + if (!utmpok) + return 0; + UserCount = 0; + setutent(); + while (ut = getutent()) + if (SLOT_USED(ut)) + UserCount++; + UT_CLOSE; + return UserCount; +} +#endif /* USRLIMIT */ + + + +/* + * the utmp entry for tty is located and removed. + * it is stored in D_utmp_logintty. + */ +void +RemoveLoginSlot() +{ + struct utmp u, *uu; + + ASSERT(display); + debug("RemoveLoginSlot: removing your logintty\n"); + D_loginslot = TtyNameSlot(D_usertty); + if (D_loginslot == (slot_t)0 || D_loginslot == (slot_t)-1) + return; +#ifdef UTMP_HELPER + if (eff_uid) /* helpers can't do login slots. sigh. */ +#else + if (!utmpok) +#endif + { + D_loginslot = 0; + debug("RemoveLoginSlot: utmpok == 0\n"); + } + else + { +#ifdef _SEQUENT_ + { + char *p; + if ((p = ut_find_host(D_loginslot)) != 0) + strncpy(D_loginhost, p, sizeof(D_loginhost) - 1); + D_loginhost[sizeof(D_loginhost) - 1] = 0; + } +#endif /* _SEQUENT_ */ + + if ((uu = getutslot(D_loginslot)) == 0) + { + debug("Utmp slot not found -> not removed"); + D_loginslot = 0; + } + else + { + D_utmp_logintty = *uu; + u = *uu; + makedead(&u); + if (pututslot(D_loginslot, &u, (char *)0, (struct win *)0) == 0) + D_loginslot = 0; + } + UT_CLOSE; + } + debug1(" slot %d zapped\n", (int)D_loginslot); + if (D_loginslot == (slot_t)0) + { + /* couldn't remove slot, do a 'mesg n' at least. */ + struct stat stb; + char *tty; + debug("couln't zap slot -> do mesg n\n"); + D_loginttymode = 0; + if ((tty = GetPtsPathOrSymlink(D_userfd)) && stat(tty, &stb) == 0 && (int)stb.st_uid == real_uid && !CheckTtyname(tty) && ((int)stb.st_mode & 0777) != 0666) + { + D_loginttymode = (int)stb.st_mode & 0777; + chmod(D_usertty, stb.st_mode & 0600); + } + } +} + +/* + * D_utmp_logintty is reinserted into utmp + */ +void +RestoreLoginSlot() +{ + char *tty; + + debug("RestoreLoginSlot()\n"); + ASSERT(display); + if (utmpok && D_loginslot != (slot_t)0 && D_loginslot != (slot_t)-1) + { + debug1(" logging you in again (slot %#x)\n", (int)D_loginslot); + if (pututslot(D_loginslot, &D_utmp_logintty, D_loginhost, (struct win *)0) == 0) + Msg(errno,"Could not write %s", UtmpName); + } + UT_CLOSE; + D_loginslot = (slot_t)0; + if (D_loginttymode && (tty = GetPtsPathOrSymlink(D_userfd)) && !CheckTtyname(tty)) + chmod(tty, D_loginttymode); +} + + + +/* + * Construct a utmp entry for window wi. + * the hostname field reflects what we know about the user (display) + * location. If d_loginhost is not set, then he is local and we write + * down the name of his terminal line; else he is remote and we keep + * the hostname here. The letter S and the window id will be appended. + * A saved utmp entry in wi->w_savut serves as a template, usually. + */ + +int +SetUtmp(wi) +struct win *wi; +{ + register slot_t slot; + struct utmp u; + int saved_ut; +#ifdef UTHOST + char *p; + char host[sizeof(D_loginhost) + 15]; +#else + char *host = 0; +#endif /* UTHOST */ + + wi->w_slot = (slot_t)0; + if (!utmpok || wi->w_type != W_TYPE_PTY) + return -1; + if ((slot = TtyNameSlot(wi->w_tty)) == (slot_t)0) + { + debug1("SetUtmp failed (tty %s).\n",wi->w_tty); + return -1; + } + debug2("SetUtmp %d will get slot %d...\n", wi->w_number, (int)slot); + + bzero((char *)&u, sizeof(u)); + if ((saved_ut = bcmp((char *) &wi->w_savut, (char *)&u, sizeof(u)))) + /* restore original, of which we will adopt all fields but ut_host */ + bcopy((char *)&wi->w_savut, (char *) &u, sizeof(u)); + + if (!saved_ut) + makeuser(&u, stripdev(wi->w_tty), LoginName, wi->w_pid); + +#ifdef UTHOST + host[sizeof(host) - 15] = '\0'; + if (display) + { + strncpy(host, D_loginhost, sizeof(host) - 15); + if (D_loginslot != (slot_t)0 && D_loginslot != (slot_t)-1 && host[0] != '\0') + { + /* + * we want to set our ut_host field to something like + * ":ttyhf:s.0" or + * "faui45:s.0" or + * "132.199.81.4:s.0" (even this may hurt..), but not + * "faui45.informati"......:s.0 + * HPUX uses host:0.0, so chop at "." and ":" (Eric Backus) + */ + for (p = host; *p; p++) + if ((*p < '0' || *p > '9') && (*p != '.')) + break; + if (*p) + { + for (p = host; *p; p++) + if (*p == '.' || (*p == ':' && p != host)) + { + *p = '\0'; + break; + } + } + } + else + { + strncpy(host + 1, stripdev(D_usertty), sizeof(host) - 15 - 1); + host[0] = ':'; + } + } + else + strncpy(host, "local", sizeof(host) - 15); + + sprintf(host + strlen(host), ":S.%d", wi->w_number); + debug1("rlogin hostname: '%s'\n", host); + +# if !defined(_SEQUENT_) && !defined(sequent) + strncpy(u.ut_host, host, sizeof(u.ut_host)); +# endif +#endif /* UTHOST */ + + if (pututslot(slot, &u, host, wi) == 0) + { + Msg(errno,"Could not write %s", UtmpName); + UT_CLOSE; + return -1; + } + debug("SetUtmp successful\n"); + wi->w_slot = slot; + bcopy((char *)&u, (char *)&wi->w_savut, sizeof(u)); + UT_CLOSE; + return 0; +} + +/* + * if slot could be removed or was 0, wi->w_slot = -1; + * else not changed. + */ + +int +RemoveUtmp(wi) +struct win *wi; +{ + struct utmp u, *uu; + slot_t slot; + + slot = wi->w_slot; + debug1("RemoveUtmp slot=%#x\n", slot); + if (!utmpok) + return -1; + if (slot == (slot_t)0 || slot == (slot_t)-1) + { + wi->w_slot = (slot_t)-1; + return 0; + } + bzero((char *) &u, sizeof(u)); +#ifdef sgi + bcopy((char *)&wi->w_savut, (char *)&u, sizeof(u)); + uu = &u; +#else + if ((uu = getutslot(slot)) == 0) + { + Msg(0, "Utmp slot not found -> not removed"); + return -1; + } + bcopy((char *)uu, (char *)&wi->w_savut, sizeof(wi->w_savut)); +#endif + u = *uu; + makedead(&u); + if (pututslot(slot, &u, (char *)0, wi) == 0) + { + Msg(errno,"Could not write %s", UtmpName); + UT_CLOSE; + return -1; + } + debug("RemoveUtmp successfull\n"); + wi->w_slot = (slot_t)-1; + UT_CLOSE; + return 0; +} + + + +/********************************************************************* + * + * routines using the getut* api + */ + +#ifdef GETUTENT + +#define SLOT_USED(u) (u->ut_type == USER_PROCESS) + +static struct utmp * +getutslot(slot) +slot_t slot; +{ + struct utmp u; + bzero((char *)&u, sizeof(u)); + strncpy(u.ut_line, slot, sizeof(u.ut_line)); + setutent(); + return getutline(&u); +} + +static int +pututslot(slot, u, host, wi) +slot_t slot; +struct utmp *u; +char *host; +struct win *wi; +{ +#ifdef _SEQUENT_ + if (SLOT_USED(u) && host && *host) + return ut_add_user(u.ut_name, slot, u.ut_pid, host) != 0; + if (!SLOT_USED(u)) + return ut_delete_user(slot, u.ut_pid, 0, 0) != 0; +#endif +#ifdef HAVE_UTEMPTER + if (eff_uid && wi && wi->w_ptyfd != -1) + { + /* sigh, linux hackers made the helper functions void */ + if (SLOT_USED(u)) + addToUtmp(wi->w_tty, host, wi->w_ptyfd); + else + removeLineFromUtmp(wi->w_tty, wi->w_ptyfd); + return 1; /* pray for success */ + } +#endif + setutent(); +#ifndef __CYGWIN__ + return pututline(u) != 0; +#else + return 1; +#endif +} + +static void +makedead(u) +struct utmp *u; +{ + u->ut_type = DEAD_PROCESS; +#if (!defined(linux) || defined(EMPTY)) && !defined(__CYGWIN__) + u->ut_exit.e_termination = 0; + u->ut_exit.e_exit = 0; +#endif +#if !defined(sun) || !defined(SVR4) + u->ut_user[0] = 0; /* for Digital UNIX, kilbi@rad.rwth-aachen.de */ +#endif +} + +static void +makeuser(u, line, user, pid) +struct utmp *u; +char *line, *user; +int pid; +{ + time_t now; + u->ut_type = USER_PROCESS; + strncpy(u->ut_user, user, sizeof(u->ut_user)); + /* Now the tricky part... guess ut_id */ +#if defined(sgi) || defined(linux) + strncpy(u->ut_id, line + 3, sizeof(u->ut_id)); +#else /* sgi */ +# ifdef _IBMR2 + strncpy(u->ut_id, line, sizeof(u->ut_id)); +# else + strncpy(u->ut_id, line + strlen(line) - 2, sizeof(u->ut_id)); +# endif +#endif /* sgi */ + strncpy(u->ut_line, line, sizeof(u->ut_line)); + u->ut_pid = pid; + /* must use temp variable because of NetBSD/sparc64, where + * ut_xtime is long(64) but time_t is int(32) */ + (void)time(&now); + u->ut_time = now; +} + +static slot_t +TtyNameSlot(nam) +char *nam; +{ + return stripdev(nam); +} + + +#else /* GETUTENT */ + +/********************************************************************* + * + * getut emulation for systems lacking the api + */ + +static struct utmp uent; + +#define SLOT_USED(u) (u.ut_name[0] != 0) + +static int +initutmp() +{ + if (utmpfd >= 0) + return 1; + return (utmpfd = open(UtmpName, O_RDWR)) >= 0; +} + +static void +setutent() +{ + if (utmpfd >= 0) + (void)lseek(utmpfd, (off_t)0, 0); +} + +static void +endutent() +{ + if (utmpfd >= 0) + close(utmpfd); + utmpfd = -1; +} + +static struct utmp * +getutent() +{ + if (utmpfd < 0 && !initutmp()) + return 0; + if (read(utmpfd, &uent, sizeof(uent)) != sizeof(uent)) + return 0; + return &uent; +} + +static struct utmp * +getutslot(slot) +slot_t slot; +{ + if (utmpfd < 0 && !initutmp()) + return 0; + lseek(utmpfd, (off_t)(slot * sizeof(struct utmp)), 0); + if (read(utmpfd, &uent, sizeof(uent)) != sizeof(uent)) + return 0; + return &uent; +} + +static int +pututslot(slot, u, host, wi) +slot_t slot; +struct utmp *u; +char *host; +struct win *wi; +{ +#ifdef sequent + if (SLOT_USED(u)) + return add_utmp(slot, u) != -1; +#endif + if (utmpfd < 0 && !initutmp()) + return 0; + lseek(utmpfd, (off_t)(slot * sizeof(*u)), 0); + if (write(utmpfd, u, sizeof(*u)) != sizeof(*u)) + return 0; + return 1; +} + + +static void +makedead(u) +struct utmp *u; +{ +#ifdef UT_UNSORTED + bzero(u->ut_name, sizeof(u->ut_name)); +# ifdef UTHOST + bzero(u->ut_host, sizeof(u->ut_host)); +# endif +#else + bzero((char *)u, sizeof(*u)); +#endif +} + + +static void +makeuser(u, line, user, pid) +struct utmp *u; +char *line, *user; +int pid; +{ + time_t now; + strncpy(u->ut_line, line, sizeof(u->ut_line)); + strncpy(u->ut_name, user, sizeof(u->ut_name)); + (void)time(&now); + u->ut_time = now; +} + +static slot_t +TtyNameSlot(nam) +char *nam; +{ + slot_t slot; + char *line; +#ifndef UT_UNSORTED + struct ttyent *tp; +#endif + + line = stripdev(nam); +#ifdef UT_UNSORTED + setutent(); + if (utmpfd < 0) + return -1; + for (slot = 0; getutent(); slot++) + if (strcmp(uent.ut_line, line) == 0) + break; + UT_CLOSE; +#else + slot = 1; + setttyent(); + while ((tp = getttyent()) != 0 && strcmp(line, tp->ty_name) != 0) + slot++; +#endif + return slot; +} + +#endif /* GETUTENT */ + + + +/********************************************************************* + * + * Cheap plastic imitation of ttyent routines. + */ + +#if !defined(GETTTYENT) && !defined(GETUTENT) && !defined(UT_UNSORTED) + + +static char *tt, *ttnext; +static char ttys[] = "/etc/ttys"; + +static void +setttyent() +{ + if (ttnext == 0) + { + struct stat s; + register int f; + register char *p, *ep; + + if ((f = open(ttys, O_RDONLY)) == -1 || fstat(f, &s) == -1) + Panic(errno, ttys); + if ((tt = malloc((unsigned) s.st_size + 1)) == 0) + Panic(0, strnomem); + if (read(f, tt, s.st_size) != s.st_size) + Panic(errno, ttys); + close(f); + for (p = tt, ep = p + s.st_size; p < ep; p++) + if (*p == '\n') + *p = '\0'; + *p = '\0'; + } + ttnext = tt; +} + +static struct ttyent * +getttyent() +{ + static struct ttyent t; + + if (*ttnext == '\0') + return NULL; + t.ty_name = ttnext + 2; + ttnext += strlen(ttnext) + 1; + return &t; +} + +#endif /* !GETTTYENT && !GETUTENT && !UT_UNSORTED*/ + + + +#endif /* UTMPOK */ + + + + +/********************************************************************* + * + * getlogin() replacement (for SVR4 machines) + */ + +# if defined(BUGGYGETLOGIN) && defined(UTMP_FILE) +char * +getlogin() +{ + char *tty = NULL; +#ifdef utmp +# undef utmp +#endif + struct utmp u; + static char retbuf[sizeof(u.ut_user)+1]; + int fd; + + for (fd = 0; fd <= 2 && (tty = GetPtsPathOrSymlink(fd)) == NULL; fd++) + ; + if ((tty == NULL) || CheckTtyname(tty) || ((fd = open(UTMP_FILE, O_RDONLY)) < 0)) + return NULL; + tty = stripdev(tty); + retbuf[0] = '\0'; + while (read(fd, (char *)&u, sizeof(struct utmp)) == sizeof(struct utmp)) + { + if (!strncmp(tty, u.ut_line, sizeof(u.ut_line))) + { + strncpy(retbuf, u.ut_user, sizeof(u.ut_user)); + retbuf[sizeof(u.ut_user)] = '\0'; + if (u.ut_type == USER_PROCESS) + break; + } + } + close(fd); + + return *retbuf ? retbuf : NULL; +} +# endif /* BUGGYGETLOGIN */ + +#if defined(linux) && defined(GETUTENT) +# undef pututline + +/* aargh, linux' pututline returns void! */ +struct utmp * +xpututline(u) +struct utmp *u; +{ + struct utmp *u2; + pututline(u); + setutent(); + u2 = getutline(u); + if (u2 == 0) + return u->ut_type == DEAD_PROCESS ? u : 0; + return u->ut_type == u2->ut_type ? u : 0; +} +#endif + |