1/* idcache.c -- map user and group IDs, cached for speed
2
3   Copyright (C) 1985, 1988, 1989, 1990, 1997, 1998, 2003, 2005-2007
4   Free Software Foundation, Inc.
5
6   This program is free software: you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation; either version 3 of the License, or
9   (at your option) any later version.
10
11   This program is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   GNU General Public License for more details.
15
16   You should have received a copy of the GNU General Public License
17   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
18
19#include <config.h>
20
21#include "idcache.h"
22#include <stddef.h>
23#include <stdio.h>
24#include <string.h>
25#include <pwd.h>
26#include <grp.h>
27
28#include <unistd.h>
29
30#include "xalloc.h"
31
32#ifdef __DJGPP__
33static char digits[] = "0123456789";
34#endif
35
36struct userid
37{
38  union
39    {
40      uid_t u;
41      gid_t g;
42    } id;
43  struct userid *next;
44  char name[FLEXIBLE_ARRAY_MEMBER];
45};
46
47/* FIXME: provide a function to free any malloc'd storage and reset lists,
48   so that an application can use code like this just before exiting:
49   #ifdef lint
50     idcache_clear ();
51   #endif
52*/
53
54static struct userid *user_alist;
55
56/* Each entry on list is a user name for which the first lookup failed.  */
57static struct userid *nouser_alist;
58
59/* Use the same struct as for userids.  */
60static struct userid *group_alist;
61
62/* Each entry on list is a group name for which the first lookup failed.  */
63static struct userid *nogroup_alist;
64
65/* Translate UID to a login name, with cache, or NULL if unresolved.  */
66
67char *
68getuser (uid_t uid)
69{
70  struct userid *tail;
71  struct userid *match = NULL;
72
73  for (tail = user_alist; tail; tail = tail->next)
74    {
75      if (tail->id.u == uid)
76	{
77	  match = tail;
78	  break;
79	}
80    }
81
82  if (match == NULL)
83    {
84      struct passwd *pwent = getpwuid (uid);
85      char const *name = pwent ? pwent->pw_name : "";
86      match = xmalloc (offsetof (struct userid, name) + strlen (name) + 1);
87      match->id.u = uid;
88      strcpy (match->name, name);
89
90      /* Add to the head of the list, so most recently used is first.  */
91      match->next = user_alist;
92      user_alist = match;
93    }
94
95  return match->name[0] ? match->name : NULL;
96}
97
98/* Translate USER to a UID, with cache.
99   Return NULL if there is no such user.
100   (We also cache which user names have no passwd entry,
101   so we don't keep looking them up.)  */
102
103uid_t *
104getuidbyname (const char *user)
105{
106  struct userid *tail;
107  struct passwd *pwent;
108
109  for (tail = user_alist; tail; tail = tail->next)
110    /* Avoid a function call for the most common case.  */
111    if (*tail->name == *user && !strcmp (tail->name, user))
112      return &tail->id.u;
113
114  for (tail = nouser_alist; tail; tail = tail->next)
115    /* Avoid a function call for the most common case.  */
116    if (*tail->name == *user && !strcmp (tail->name, user))
117      return NULL;
118
119  pwent = getpwnam (user);
120#ifdef __DJGPP__
121  /* We need to pretend to be the user USER, to make
122     pwd functions know about an arbitrary user name.  */
123  if (!pwent && strspn (user, digits) < strlen (user))
124    {
125      setenv ("USER", user, 1);
126      pwent = getpwnam (user);	/* now it will succeed */
127    }
128#endif
129
130  tail = xmalloc (offsetof (struct userid, name) + strlen (user) + 1);
131  strcpy (tail->name, user);
132
133  /* Add to the head of the list, so most recently used is first.  */
134  if (pwent)
135    {
136      tail->id.u = pwent->pw_uid;
137      tail->next = user_alist;
138      user_alist = tail;
139      return &tail->id.u;
140    }
141
142  tail->next = nouser_alist;
143  nouser_alist = tail;
144  return NULL;
145}
146
147/* Translate GID to a group name, with cache, or NULL if unresolved.  */
148
149char *
150getgroup (gid_t gid)
151{
152  struct userid *tail;
153  struct userid *match = NULL;
154
155  for (tail = group_alist; tail; tail = tail->next)
156    {
157      if (tail->id.g == gid)
158	{
159	  match = tail;
160	  break;
161	}
162    }
163
164  if (match == NULL)
165    {
166      struct group *grent = getgrgid (gid);
167      char const *name = grent ? grent->gr_name : "";
168      match = xmalloc (offsetof (struct userid, name) + strlen (name) + 1);
169      match->id.g = gid;
170      strcpy (match->name, name);
171
172      /* Add to the head of the list, so most recently used is first.  */
173      match->next = group_alist;
174      group_alist = match;
175    }
176
177  return match->name[0] ? match->name : NULL;
178}
179
180/* Translate GROUP to a GID, with cache.
181   Return NULL if there is no such group.
182   (We also cache which group names have no group entry,
183   so we don't keep looking them up.)  */
184
185gid_t *
186getgidbyname (const char *group)
187{
188  struct userid *tail;
189  struct group *grent;
190
191  for (tail = group_alist; tail; tail = tail->next)
192    /* Avoid a function call for the most common case.  */
193    if (*tail->name == *group && !strcmp (tail->name, group))
194      return &tail->id.g;
195
196  for (tail = nogroup_alist; tail; tail = tail->next)
197    /* Avoid a function call for the most common case.  */
198    if (*tail->name == *group && !strcmp (tail->name, group))
199      return NULL;
200
201  grent = getgrnam (group);
202#ifdef __DJGPP__
203  /* We need to pretend to belong to group GROUP, to make
204     grp functions know about an arbitrary group name.  */
205  if (!grent && strspn (group, digits) < strlen (group))
206    {
207      setenv ("GROUP", group, 1);
208      grent = getgrnam (group);	/* now it will succeed */
209    }
210#endif
211
212  tail = xmalloc (offsetof (struct userid, name) + strlen (group) + 1);
213  strcpy (tail->name, group);
214
215  /* Add to the head of the list, so most recently used is first.  */
216  if (grent)
217    {
218      tail->id.g = grent->gr_gid;
219      tail->next = group_alist;
220      group_alist = tail;
221      return &tail->id.g;
222    }
223
224  tail->next = nogroup_alist;
225  nogroup_alist = tail;
226  return NULL;
227}
228