1 /* $OpenBSD: username.c,v 1.14 2004/05/09 22:14:15 deraadt Exp $	 */
2 
3 /*
4  *  Top users/processes display for Unix
5  *  Version 3
6  *
7  * Copyright (c) 1984, 1989, William LeFebvre, Rice University
8  * Copyright (c) 1989, 1990, 1992, William LeFebvre, Northwestern University
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22  * IN NO EVENT SHALL THE AUTHOR OR HIS EMPLOYER BE LIABLE FOR ANY DIRECT, INDIRECT,
23  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 /*
32  *  Username translation code for top.
33  *
34  *  These routines handle uid to username mapping.
35  *  They use a hashing table scheme to reduce reading overhead.
36  *  For the time being, these are very straightforward hashing routines.
37  *  Maybe someday I'll put in something better.  But with the advent of
38  *  "random access" password files, it might not be worth the effort.
39  *
40  *  Changes to these have been provided by John Gilmore (gnu@toad.com).
41  *
42  *  The hash has been simplified in this release, to avoid the
43  *  table overflow problems of previous releases.  If the value
44  *  at the initial hash location is not right, it is replaced
45  *  by the right value.  Collisions will cause us to call getpw*
46  *  but hey, this is a cache, not the Library of Congress.
47  *  This makes the table size independent of the passwd file size.
48  */
49 
50 #include <sys/types.h>
51 #include <stdio.h>
52 #include <string.h>
53 #include <pwd.h>
54 
55 #include "top.local.h"
56 #include "top.h"
57 #include "utils.h"
58 
59 struct hash_el {
60 	uid_t	uid;
61 	char	name[_PW_NAME_LEN + 1];
62 };
63 
64 static int	enter_user(uid_t, char *, int);
65 static int	get_user(uid_t);
66 
67 #define	is_empty_hash(x)	(hash_table[x].name[0] == 0)
68 
69 /*
70  * Simple minded hashing function, assumes i is unsigned.
71  */
72 #define	hashit(i)	(i % Table_size)
73 
74 struct hash_el  hash_table[Table_size];
75 
76 char *
username(uid_t uid)77 username(uid_t uid)
78 {
79 	int hashindex;
80 
81 	hashindex = hashit(uid);
82 	if (is_empty_hash(hashindex) || (hash_table[hashindex].uid != uid)) {
83 		/* not here or not right -- get it out of passwd */
84 		hashindex = get_user(uid);
85 	}
86 	return (hash_table[hashindex].name);
87 }
88 
89 uid_t
userid(char * username)90 userid(char *username)
91 {
92 	struct passwd *pwd;
93 
94 	/*
95 	 * Eventually we want this to enter everything in the hash table, but
96 	 * for now we just do it simply and remember just the result.
97 	 */
98 	if ((pwd = getpwnam(username)) == NULL)
99 		return ((uid_t)-1);
100 
101 	/* enter the result in the hash table */
102 	enter_user(pwd->pw_uid, username, 1);
103 
104 	/* return our result */
105 	return (pwd->pw_uid);
106 }
107 
108 /*
109  * wecare: 1 = enter it always, 0 = nice to have
110  */
111 static int
enter_user(uid_t uid,char * name,int wecare)112 enter_user(uid_t uid, char *name, int wecare)
113 {
114 	int hashindex;
115 
116 #ifdef DEBUG
117 	fprintf(stderr, "enter_hash(%u, %s, %d)\n", uid, name, wecare);
118 #endif
119 
120 	hashindex = hashit(uid);
121 
122 	if (!is_empty_hash(hashindex)) {
123 		if (!wecare)
124 			return 0;	/* Don't clobber a slot for trash */
125 		if (hash_table[hashindex].uid == uid)
126 			return (hashindex);	/* Fortuitous find */
127 	}
128 	/* empty or wrong slot -- fill it with new value */
129 	hash_table[hashindex].uid = uid;
130 	(void) strlcpy(hash_table[hashindex].name, name,
131 	    sizeof(hash_table[hashindex].name));
132 	return (hashindex);
133 }
134 
135 /*
136  * Get a userid->name mapping from the system.
137  * If the passwd database is hashed (#define RANDOM_PW), we
138  * just handle this uid.  Otherwise we scan the passwd file
139  * and cache any entries we pass over while looking.
140  */
141 static int
get_user(uid_t uid)142 get_user(uid_t uid)
143 {
144 	struct passwd *pwd;
145 
146 #ifdef RANDOM_PW
147 	/* no performance penalty for using getpwuid makes it easy */
148 	if ((pwd = getpwuid(uid)) != NULL)
149 		return (enter_user(pwd->pw_uid, pwd->pw_name, 1));
150 #else
151 
152 	int from_start = 0;
153 
154 	/*
155 	 *  If we just called getpwuid each time, things would be very slow
156 	 *  since that just iterates through the passwd file each time.  So,
157 	 *  we walk through the file instead (using getpwent) and cache each
158 	 *  entry as we go.  Once the right record is found, we cache it and
159 	 *  return immediately.  The next time we come in, getpwent will get
160 	 *  the next record.  In theory, we never have to read the passwd file
161 	 *  a second time (because we cache everything we read).  But in
162 	 *  practice, the cache may not be large enough, so if we don't find
163 	 *  it the first time we have to scan the file a second time.  This
164 	 *  is not very efficient, but it will do for now.
165 	 */
166 	while (from_start++ < 2) {
167 		while ((pwd = getpwent()) != NULL) {
168 			if (pwd->pw_uid == uid)
169 				return (enter_user(pwd->pw_uid, pwd->pw_name, 1));
170 			(void) enter_user(pwd->pw_uid, pwd->pw_name, 0);
171 		}
172 		/* try again */
173 		setpwent();
174 	}
175 #endif
176 	/* if we can't find the name at all, then use the uid as the name */
177 	return (enter_user(uid, format_uid(uid), 1));
178 }
179