1 /* Copyright (C) 1998-2022 Free Software Foundation, Inc.
2    This file is part of the GNU C Library.
3 
4    The GNU C Library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Lesser General Public
6    License as published by the Free Software Foundation; either
7    version 2.1 of the License, or (at your option) any later version.
8 
9    The GNU C Library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Lesser General Public License for more details.
13 
14    You should have received a copy of the GNU Lesser General Public
15    License along with the GNU C Library; if not, see
16    <https://www.gnu.org/licenses/>.  */
17 
18 #include <assert.h>
19 #include <errno.h>
20 #include <pwd.h>
21 #include <stdint.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <unistd.h>
26 #include <sys/mman.h>
27 #include <sys/socket.h>
28 #include <sys/uio.h>
29 #include <sys/un.h>
30 #include <not-cancel.h>
31 #include <_itoa.h>
32 
33 #include "nscd-client.h"
34 #include "nscd_proto.h"
35 
36 int __nss_not_use_nscd_passwd;
37 
38 static int nscd_getpw_r (const char *key, size_t keylen, request_type type,
39 			 struct passwd *resultbuf, char *buffer,
40 			 size_t buflen, struct passwd **result);
41 
42 int
__nscd_getpwnam_r(const char * name,struct passwd * resultbuf,char * buffer,size_t buflen,struct passwd ** result)43 __nscd_getpwnam_r (const char *name, struct passwd *resultbuf, char *buffer,
44 		   size_t buflen, struct passwd **result)
45 {
46   if (name == NULL)
47     return -1;
48 
49   return nscd_getpw_r (name, strlen (name) + 1, GETPWBYNAME, resultbuf,
50 		       buffer, buflen, result);
51 }
52 
53 int
__nscd_getpwuid_r(uid_t uid,struct passwd * resultbuf,char * buffer,size_t buflen,struct passwd ** result)54 __nscd_getpwuid_r (uid_t uid, struct passwd *resultbuf, char *buffer,
55 		   size_t buflen, struct passwd **result)
56 {
57   char buf[3 * sizeof (uid_t)];
58   buf[sizeof (buf) - 1] = '\0';
59   char *cp = _itoa_word (uid, buf + sizeof (buf) - 1, 10, 0);
60 
61   return nscd_getpw_r (cp, buf + sizeof (buf) - cp, GETPWBYUID, resultbuf,
62 		       buffer, buflen, result);
63 }
64 
65 
66 libc_locked_map_ptr (static, map_handle);
67 /* Note that we only free the structure if necessary.  The memory
68    mapping is not removed since it is not visible to the malloc
69    handling.  */
libc_freeres_fn(pw_map_free)70 libc_freeres_fn (pw_map_free)
71 {
72   if (map_handle.mapped != NO_MAPPING)
73     {
74       void *p = map_handle.mapped;
75       map_handle.mapped = NO_MAPPING;
76       free (p);
77     }
78 }
79 
80 
81 static int
nscd_getpw_r(const char * key,size_t keylen,request_type type,struct passwd * resultbuf,char * buffer,size_t buflen,struct passwd ** result)82 nscd_getpw_r (const char *key, size_t keylen, request_type type,
83 	      struct passwd *resultbuf, char *buffer, size_t buflen,
84 	      struct passwd **result)
85 {
86   int gc_cycle;
87   int nretries = 0;
88 
89   /* If the mapping is available, try to search there instead of
90      communicating with the nscd.  */
91   struct mapped_database *mapped;
92   mapped = __nscd_get_map_ref (GETFDPW, "passwd", &map_handle, &gc_cycle);
93 
94  retry:;
95   const char *pw_name = NULL;
96   int retval = -1;
97   const char *recend = (const char *) ~UINTMAX_C (0);
98   pw_response_header pw_resp;
99 
100   if (mapped != NO_MAPPING)
101     {
102       struct datahead *found = __nscd_cache_search (type, key, keylen, mapped,
103 						    sizeof pw_resp);
104       if (found != NULL)
105 	{
106 	  pw_name = (const char *) (&found->data[0].pwdata + 1);
107 	  pw_resp = found->data[0].pwdata;
108 	  recend = (const char *) found->data + found->recsize;
109 	  /* Now check if we can trust pw_resp fields.  If GC is
110 	     in progress, it can contain anything.  */
111 	  if (mapped->head->gc_cycle != gc_cycle)
112 	    {
113 	      retval = -2;
114 	      goto out;
115 	    }
116 	}
117     }
118 
119   int sock = -1;
120   if (pw_name == NULL)
121     {
122       sock = __nscd_open_socket (key, keylen, type, &pw_resp,
123 				 sizeof (pw_resp));
124       if (sock == -1)
125 	{
126 	  __nss_not_use_nscd_passwd = 1;
127 	  goto out;
128 	}
129     }
130 
131   /* No value found so far.  */
132   *result = NULL;
133 
134   if (__glibc_unlikely (pw_resp.found == -1))
135     {
136       /* The daemon does not cache this database.  */
137       __nss_not_use_nscd_passwd = 1;
138       goto out_close;
139     }
140 
141   if (pw_resp.found == 1)
142     {
143       /* Set the information we already have.  */
144       resultbuf->pw_uid = pw_resp.pw_uid;
145       resultbuf->pw_gid = pw_resp.pw_gid;
146 
147       char *p = buffer;
148       /* get pw_name */
149       resultbuf->pw_name = p;
150       p += pw_resp.pw_name_len;
151       /* get pw_passwd */
152       resultbuf->pw_passwd = p;
153       p += pw_resp.pw_passwd_len;
154       /* get pw_gecos */
155       resultbuf->pw_gecos = p;
156       p += pw_resp.pw_gecos_len;
157       /* get pw_dir */
158       resultbuf->pw_dir = p;
159       p += pw_resp.pw_dir_len;
160       /* get pw_pshell */
161       resultbuf->pw_shell = p;
162       p += pw_resp.pw_shell_len;
163 
164       ssize_t total = p - buffer;
165       if (__glibc_unlikely (pw_name + total > recend))
166 	goto out_close;
167       if (__glibc_unlikely (buflen < total))
168 	{
169 	  __set_errno (ERANGE);
170 	  retval = ERANGE;
171 	  goto out_close;
172 	}
173 
174       retval = 0;
175       if (pw_name == NULL)
176 	{
177 	  ssize_t nbytes = __readall (sock, buffer, total);
178 
179 	  if (__glibc_unlikely (nbytes != total))
180 	    {
181 	      /* The `errno' to some value != ERANGE.  */
182 	      __set_errno (ENOENT);
183 	      retval = ENOENT;
184 	    }
185 	  else
186 	    *result = resultbuf;
187 	}
188       else
189 	{
190 	  /* Copy the various strings.  */
191 	  memcpy (resultbuf->pw_name, pw_name, total);
192 
193 	  /* Try to detect corrupt databases.  */
194 	  if (resultbuf->pw_name[pw_resp.pw_name_len - 1] != '\0'
195 	      || resultbuf->pw_passwd[pw_resp.pw_passwd_len - 1] != '\0'
196 	      || resultbuf->pw_gecos[pw_resp.pw_gecos_len - 1] != '\0'
197 	      || resultbuf->pw_dir[pw_resp.pw_dir_len - 1] != '\0'
198 	      || resultbuf->pw_shell[pw_resp.pw_shell_len - 1] != '\0')
199 	    {
200 	      /* We cannot use the database.  */
201 	      retval = mapped->head->gc_cycle != gc_cycle ? -2 : -1;
202 	      goto out_close;
203 	    }
204 
205 	  *result = resultbuf;
206 	}
207     }
208   else
209     {
210       /* Set errno to 0 to indicate no error, just no found record.  */
211       __set_errno (0);
212       /* Even though we have not found anything, the result is zero.  */
213       retval = 0;
214     }
215 
216  out_close:
217   if (sock != -1)
218     __close_nocancel_nostatus (sock);
219  out:
220   if (__nscd_drop_map_ref (mapped, &gc_cycle) != 0)
221     {
222       /* When we come here this means there has been a GC cycle while we
223 	 were looking for the data.  This means the data might have been
224 	 inconsistent.  Retry if possible.  */
225       if ((gc_cycle & 1) != 0 || ++nretries == 5 || retval == -1)
226 	{
227 	  /* nscd is just running gc now.  Disable using the mapping.  */
228 	  if (atomic_decrement_val (&mapped->counter) == 0)
229 	    __nscd_unmap (mapped);
230 	  mapped = NO_MAPPING;
231 	}
232 
233       if (retval != -1)
234 	goto retry;
235     }
236 
237   return retval;
238 }
239