(cache_addpw): Optimize case of unsuccessful lookup a bit.
[kopensolaris-gnu/glibc.git] / nscd / pwdcache.c
1 /* Cache handling for passwd lookup.
2    Copyright (C) 1998-2002, 2003, 2004 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4    Contributed by Ulrich Drepper <drepper@cygnus.com>, 1998.
5
6    The GNU C Library is free software; you can redistribute it and/or
7    modify it under the terms of the GNU Lesser General Public
8    License as published by the Free Software Foundation; either
9    version 2.1 of the License, or (at your option) any later version.
10
11    The GNU C Library 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 GNU
14    Lesser General Public License for more details.
15
16    You should have received a copy of the GNU Lesser General Public
17    License along with the GNU C Library; if not, write to the Free
18    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19    02111-1307 USA.  */
20
21 #include <alloca.h>
22 #include <errno.h>
23 #include <error.h>
24 #include <pwd.h>
25 #include <stdbool.h>
26 #include <stddef.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <time.h>
31 #include <unistd.h>
32 #include <libintl.h>
33 #include <stackinfo.h>
34
35 #include "nscd.h"
36 #include "dbg_log.h"
37
38 /* This is the standard reply in case the service is disabled.  */
39 static const pw_response_header disabled =
40 {
41   .version = NSCD_VERSION,
42   .found = -1,
43   .pw_name_len = 0,
44   .pw_passwd_len = 0,
45   .pw_uid = -1,
46   .pw_gid = -1,
47   .pw_gecos_len = 0,
48   .pw_dir_len = 0,
49   .pw_shell_len = 0
50 };
51
52 /* This is the struct describing how to write this record.  */
53 const struct iovec pwd_iov_disabled =
54 {
55   .iov_base = (void *) &disabled,
56   .iov_len = sizeof (disabled)
57 };
58
59
60 /* This is the standard reply in case we haven't found the dataset.  */
61 static const pw_response_header notfound =
62 {
63   .version = NSCD_VERSION,
64   .found = 0,
65   .pw_name_len = 0,
66   .pw_passwd_len = 0,
67   .pw_uid = -1,
68   .pw_gid = -1,
69   .pw_gecos_len = 0,
70   .pw_dir_len = 0,
71   .pw_shell_len = 0
72 };
73
74
75 struct passwddata
76 {
77   pw_response_header resp;
78   char strdata[0];
79 };
80
81
82 static void
83 cache_addpw (struct database *db, int fd, request_header *req, void *key,
84              struct passwd *pwd, uid_t owner)
85 {
86   ssize_t total;
87   ssize_t written;
88   time_t t = time (NULL);
89
90   if (pwd == NULL)
91     {
92       /* We have no data.  This means we send the standard reply for this
93          case.  */
94       total = sizeof (notfound);
95
96       written = TEMP_FAILURE_RETRY (write (fd, &notfound, total));
97
98       void *copy = malloc (req->key_len);
99       /* If we cannot allocate memory simply do not cache the information.  */
100       if (copy != NULL)
101         {
102           memcpy (copy, key, req->key_len);
103
104           /* Compute the timeout time.  */
105           t += db->negtimeout;
106
107           /* Now get the lock to safely insert the records.  */
108           pthread_rwlock_rdlock (&db->lock);
109
110           cache_add (req->type, copy, req->key_len, &notfound,
111                      sizeof (notfound), (void *) -1, 0, t, db, owner);
112
113           pthread_rwlock_unlock (&db->lock);
114         }
115     }
116   else
117     {
118       /* Determine the I/O structure.  */
119       struct passwddata *data;
120       size_t pw_name_len = strlen (pwd->pw_name) + 1;
121       size_t pw_passwd_len = strlen (pwd->pw_passwd) + 1;
122       size_t pw_gecos_len = strlen (pwd->pw_gecos) + 1;
123       size_t pw_dir_len = strlen (pwd->pw_dir) + 1;
124       size_t pw_shell_len = strlen (pwd->pw_shell) + 1;
125       char *cp;
126       char buf[12];
127       ssize_t n;
128
129       /* We need this to insert the `byuid' entry.  */
130       n = snprintf (buf, sizeof (buf), "%d", pwd->pw_uid) + 1;
131
132       /* We allocate all data in one memory block: the iov vector,
133          the response header and the dataset itself.  */
134       total = (sizeof (struct passwddata) + pw_name_len + pw_passwd_len
135                + pw_gecos_len + pw_dir_len + pw_shell_len);
136       data = (struct passwddata *) malloc (total + n);
137       if (data == NULL)
138         /* There is no reason to go on.  */
139         error (EXIT_FAILURE, errno, _("while allocating cache entry"));
140
141       data->resp.version = NSCD_VERSION;
142       data->resp.found = 1;
143       data->resp.pw_name_len = pw_name_len;
144       data->resp.pw_passwd_len = pw_passwd_len;
145       data->resp.pw_uid = pwd->pw_uid;
146       data->resp.pw_gid = pwd->pw_gid;
147       data->resp.pw_gecos_len = pw_gecos_len;
148       data->resp.pw_dir_len = pw_dir_len;
149       data->resp.pw_shell_len = pw_shell_len;
150
151       cp = data->strdata;
152
153       /* Copy the strings over into the buffer.  */
154       cp = mempcpy (cp, pwd->pw_name, pw_name_len);
155       cp = mempcpy (cp, pwd->pw_passwd, pw_passwd_len);
156       cp = mempcpy (cp, pwd->pw_gecos, pw_gecos_len);
157       cp = mempcpy (cp, pwd->pw_dir, pw_dir_len);
158       cp = mempcpy (cp, pwd->pw_shell, pw_shell_len);
159
160       /* Finally the stringified UID value.  */
161       memcpy (cp, buf, n);
162
163       /* We write the dataset before inserting it to the database
164          since while inserting this thread might block and so would
165          unnecessarily let the receiver wait.  */
166       written = TEMP_FAILURE_RETRY (write (fd, &data->resp, total));
167
168       /* Compute the timeout time.  */
169       t += db->postimeout;
170
171       /* Now get the lock to safely insert the records.  */
172       pthread_rwlock_rdlock (&db->lock);
173
174       /* We have to add the value for both, byname and byuid.  */
175       cache_add (GETPWBYNAME, data->strdata, pw_name_len, data,
176                  total, data, 0, t, db, owner);
177
178       cache_add (GETPWBYUID, cp, n, data, total, data, 1, t, db, owner);
179
180       pthread_rwlock_unlock (&db->lock);
181     }
182
183   if (__builtin_expect (written != total, 0) && debug_level > 0)
184     {
185       char buf[256];
186       dbg_log (_("short write in %s: %s"),  __FUNCTION__,
187                strerror_r (errno, buf, sizeof (buf)));
188     }
189 }
190
191
192 void
193 addpwbyname (struct database *db, int fd, request_header *req,
194              void *key, uid_t c_uid)
195 {
196   /* Search for the entry matching the key.  Please note that we don't
197      look again in the table whether the dataset is now available.  We
198      simply insert it.  It does not matter if it is in there twice.  The
199      pruning function only will look at the timestamp.  */
200   int buflen = 1024;
201   char *buffer = (char *) alloca (buflen);
202   struct passwd resultbuf;
203   struct passwd *pwd;
204   uid_t oldeuid = 0;
205   bool use_malloc = false;
206
207   if (__builtin_expect (debug_level > 0, 0))
208     dbg_log (_("Haven't found \"%s\" in password cache!"), (char *) key);
209
210   if (secure[pwddb])
211     {
212       oldeuid = geteuid ();
213       seteuid (c_uid);
214     }
215
216   while (__getpwnam_r (key, &resultbuf, buffer, buflen, &pwd) != 0
217          && errno == ERANGE)
218     {
219       char *old_buffer = buffer;
220       errno = 0;
221 #define INCR 1024
222
223       if (__builtin_expect (buflen > 32768, 0))
224         {
225           buflen += INCR;
226           buffer = (char *) realloc (use_malloc ? buffer : NULL, buflen);
227           if (buffer == NULL)
228             {
229               /* We ran out of memory.  We cannot do anything but
230                  sending a negative response.  In reality this should
231                  never happen.  */
232               pwd = NULL;
233               buffer = old_buffer;
234               break;
235             }
236           use_malloc = true;
237         }
238       else
239         /* Allocate a new buffer on the stack.  If possible combine it
240            with the previously allocated buffer.  */
241         buffer = (char *) extend_alloca (buffer, buflen, buflen + INCR);
242     }
243
244   if (secure[pwddb])
245     seteuid (oldeuid);
246
247   cache_addpw (db, fd, req, key, pwd, c_uid);
248
249   if (use_malloc)
250     free (buffer);
251 }
252
253
254 void
255 addpwbyuid (struct database *db, int fd, request_header *req,
256             void *key, uid_t c_uid)
257 {
258   /* Search for the entry matching the key.  Please note that we don't
259      look again in the table whether the dataset is now available.  We
260      simply insert it.  It does not matter if it is in there twice.  The
261      pruning function only will look at the timestamp.  */
262   int buflen = 256;
263   char *buffer = (char *) alloca (buflen);
264   struct passwd resultbuf;
265   struct passwd *pwd;
266   uid_t oldeuid = 0;
267   char *ep;
268   uid_t uid = strtoul ((char *) key, &ep, 10);
269   bool use_malloc = false;
270
271   if (*(char *) key == '\0' || *ep != '\0')  /* invalid numeric uid */
272     {
273       if (debug_level > 0)
274         dbg_log (_("Invalid numeric uid \"%s\"!"), (char *) key);
275
276       errno = EINVAL;
277       return;
278     }
279
280   if (__builtin_expect (debug_level > 0, 0))
281     dbg_log (_("Haven't found \"%d\" in password cache!"), uid);
282
283   if (secure[pwddb])
284     {
285       oldeuid = geteuid ();
286       seteuid (c_uid);
287     }
288
289   while (__getpwuid_r (uid, &resultbuf, buffer, buflen, &pwd) != 0
290          && errno == ERANGE)
291     {
292       char *old_buffer = buffer;
293       errno = 0;
294
295       if (__builtin_expect (buflen > 32768, 0))
296         {
297           buflen += 1024;
298           buffer = (char *) realloc (use_malloc ? buffer : NULL, buflen);
299           if (buffer == NULL)
300             {
301               /* We ran out of memory.  We cannot do anything but
302                  sending a negative response.  In reality this should
303                  never happen.  */
304               pwd = NULL;
305               buffer = old_buffer;
306               break;
307             }
308           use_malloc = true;
309         }
310       else
311         /* Allocate a new buffer on the stack.  If possible combine it
312            with the previously allocated buffer.  */
313         buffer = (char *) extend_alloca (buffer, buflen, buflen + INCR);
314     }
315
316   if (secure[pwddb])
317     seteuid (oldeuid);
318
319   cache_addpw (db, fd, req, key, pwd, c_uid);
320
321   if (use_malloc)
322     free (buffer);
323 }