1 /* Cache handling for services lookup.
2    Copyright (C) 2007-2022 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4 
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published
7    by the Free Software Foundation; version 2 of the License, or
8    (at your option) any later version.
9 
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, see <https://www.gnu.org/licenses/>.  */
17 
18 #include <assert.h>
19 #include <errno.h>
20 #include <libintl.h>
21 #include <netdb.h>
22 #include <unistd.h>
23 #include <stdint.h>
24 #include <sys/mman.h>
25 #include <kernel-features.h>
26 #include <scratch_buffer.h>
27 
28 #include "nscd.h"
29 #include "dbg_log.h"
30 
31 
32 /* This is the standard reply in case the service is disabled.  */
33 static const serv_response_header disabled =
34 {
35   .version = NSCD_VERSION,
36   .found = -1,
37   .s_name_len = 0,
38   .s_proto_len = 0,
39   .s_aliases_cnt = 0,
40   .s_port = -1
41 };
42 
43 /* This is the struct describing how to write this record.  */
44 const struct iovec serv_iov_disabled =
45 {
46   .iov_base = (void *) &disabled,
47   .iov_len = sizeof (disabled)
48 };
49 
50 
51 /* This is the standard reply in case we haven't found the dataset.  */
52 static const serv_response_header notfound =
53 {
54   .version = NSCD_VERSION,
55   .found = 0,
56   .s_name_len = 0,
57   .s_proto_len = 0,
58   .s_aliases_cnt = 0,
59   .s_port = -1
60 };
61 
62 
63 static time_t
cache_addserv(struct database_dyn * db,int fd,request_header * req,const void * key,struct servent * serv,uid_t owner,struct hashentry * const he,struct datahead * dh,int errval)64 cache_addserv (struct database_dyn *db, int fd, request_header *req,
65 	       const void *key, struct servent *serv, uid_t owner,
66 	       struct hashentry *const he, struct datahead *dh, int errval)
67 {
68   bool all_written = true;
69   ssize_t total;
70   time_t t = time (NULL);
71 
72   /* We allocate all data in one memory block: the iov vector,
73      the response header and the dataset itself.  */
74   struct dataset
75   {
76     struct datahead head;
77     serv_response_header resp;
78     char strdata[0];
79   } *dataset;
80 
81   assert (offsetof (struct dataset, resp) == offsetof (struct datahead, data));
82 
83   time_t timeout = MAX_TIMEOUT_VALUE;
84   if (serv == NULL)
85     {
86       if (he != NULL && errval == EAGAIN)
87 	{
88 	  /* If we have an old record available but cannot find one
89 	     now because the service is not available we keep the old
90 	     record and make sure it does not get removed.  */
91 	  if (reload_count != UINT_MAX)
92 	    /* Do not reset the value if we never not reload the record.  */
93 	    dh->nreloads = reload_count - 1;
94 
95 	  /* Reload with the same time-to-live value.  */
96 	  timeout = dh->timeout = t + db->postimeout;
97 
98 	  total = 0;
99 	}
100       else
101 	{
102 	  /* We have no data.  This means we send the standard reply for this
103 	     case.  */
104 	  total = sizeof (notfound);
105 
106 	  if (fd != -1
107 	      && TEMP_FAILURE_RETRY (send (fd, &notfound, total,
108 					   MSG_NOSIGNAL)) != total)
109 	    all_written = false;
110 
111 	  /* If we have a transient error or cannot permanently store
112 	     the result, so be it.  */
113 	  if (errval == EAGAIN || __builtin_expect (db->negtimeout == 0, 0))
114 	    {
115 	      /* Mark the old entry as obsolete.  */
116 	      if (dh != NULL)
117 		dh->usable = false;
118 	    }
119 	  else if ((dataset = mempool_alloc (db, (sizeof (struct dataset)
120 						  + req->key_len), 1)) != NULL)
121 	    {
122 	      timeout = datahead_init_neg (&dataset->head,
123 					   (sizeof (struct dataset)
124 					    + req->key_len), total,
125 					   db->negtimeout);
126 
127 	      /* This is the reply.  */
128 	      memcpy (&dataset->resp, &notfound, total);
129 
130 	      /* Copy the key data.  */
131 	      memcpy (dataset->strdata, key, req->key_len);
132 
133 	      /* If necessary, we also propagate the data to disk.  */
134 	      if (db->persistent)
135 		{
136 		  // XXX async OK?
137 		  uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
138 		  msync ((void *) pval,
139 			 ((uintptr_t) dataset & pagesize_m1)
140 			 + sizeof (struct dataset) + req->key_len, MS_ASYNC);
141 		}
142 
143 	      (void) cache_add (req->type, &dataset->strdata, req->key_len,
144 				&dataset->head, true, db, owner, he == NULL);
145 
146 	      pthread_rwlock_unlock (&db->lock);
147 
148 	      /* Mark the old entry as obsolete.  */
149 	      if (dh != NULL)
150 		dh->usable = false;
151 	    }
152 	}
153     }
154   else
155     {
156       /* Determine the I/O structure.  */
157       size_t s_name_len = strlen (serv->s_name) + 1;
158       size_t s_proto_len = strlen (serv->s_proto) + 1;
159       uint32_t *s_aliases_len;
160       size_t s_aliases_cnt;
161       char *aliases;
162       char *cp;
163       size_t cnt;
164 
165       /* Determine the number of aliases.  */
166       s_aliases_cnt = 0;
167       for (cnt = 0; serv->s_aliases[cnt] != NULL; ++cnt)
168 	++s_aliases_cnt;
169       /* Determine the length of all aliases.  */
170       s_aliases_len = (uint32_t *) alloca (s_aliases_cnt * sizeof (uint32_t));
171       total = 0;
172       for (cnt = 0; cnt < s_aliases_cnt; ++cnt)
173 	{
174 	  s_aliases_len[cnt] = strlen (serv->s_aliases[cnt]) + 1;
175 	  total += s_aliases_len[cnt];
176 	}
177 
178       total += (offsetof (struct dataset, strdata)
179 		+ s_name_len
180 		+ s_proto_len
181 		+ s_aliases_cnt * sizeof (uint32_t));
182 
183       /* If we refill the cache, first assume the reconrd did not
184 	 change.  Allocate memory on the cache since it is likely
185 	 discarded anyway.  If it turns out to be necessary to have a
186 	 new record we can still allocate real memory.  */
187       bool alloca_used = false;
188       dataset = NULL;
189 
190       if (he == NULL)
191 	dataset = (struct dataset *) mempool_alloc (db, total + req->key_len,
192 						    1);
193 
194       if (dataset == NULL)
195 	{
196 	  /* We cannot permanently add the result in the moment.  But
197 	     we can provide the result as is.  Store the data in some
198 	     temporary memory.  */
199 	  dataset = (struct dataset *) alloca (total + req->key_len);
200 
201 	  /* We cannot add this record to the permanent database.  */
202 	  alloca_used = true;
203 	}
204 
205       timeout = datahead_init_pos (&dataset->head, total + req->key_len,
206 				   total - offsetof (struct dataset, resp),
207 				   he == NULL ? 0 : dh->nreloads + 1,
208 				   db->postimeout);
209 
210       dataset->resp.version = NSCD_VERSION;
211       dataset->resp.found = 1;
212       dataset->resp.s_name_len = s_name_len;
213       dataset->resp.s_proto_len = s_proto_len;
214       dataset->resp.s_port = serv->s_port;
215       dataset->resp.s_aliases_cnt = s_aliases_cnt;
216 
217       cp = dataset->strdata;
218 
219       cp = mempcpy (cp, serv->s_name, s_name_len);
220       cp = mempcpy (cp, serv->s_proto, s_proto_len);
221       cp = mempcpy (cp, s_aliases_len, s_aliases_cnt * sizeof (uint32_t));
222 
223       /* Then the aliases.  */
224       aliases = cp;
225       for (cnt = 0; cnt < s_aliases_cnt; ++cnt)
226 	cp = mempcpy (cp, serv->s_aliases[cnt], s_aliases_len[cnt]);
227 
228       assert (cp
229 	      == dataset->strdata + total - offsetof (struct dataset,
230 						      strdata));
231 
232       char *key_copy = memcpy (cp, key, req->key_len);
233 
234       /* Now we can determine whether on refill we have to create a new
235 	 record or not.  */
236       if (he != NULL)
237 	{
238 	  assert (fd == -1);
239 
240 	  if (total + req->key_len == dh->allocsize
241 	      && total - offsetof (struct dataset, resp) == dh->recsize
242 	      && memcmp (&dataset->resp, dh->data,
243 			 dh->allocsize - offsetof (struct dataset, resp)) == 0)
244 	    {
245 	      /* The data has not changed.  We will just bump the
246 		 timeout value.  Note that the new record has been
247 		 allocated on the stack and need not be freed.  */
248 	      dh->timeout = dataset->head.timeout;
249 	      ++dh->nreloads;
250 	    }
251 	  else
252 	    {
253 	      /* We have to create a new record.  Just allocate
254 		 appropriate memory and copy it.  */
255 	      struct dataset *newp
256 		= (struct dataset *) mempool_alloc (db, total + req->key_len,
257 						    1);
258 	      if (newp != NULL)
259 		{
260 		  /* Adjust pointers into the memory block.  */
261 		  aliases = (char *) newp + (aliases - (char *) dataset);
262 		  assert (key_copy != NULL);
263 		  key_copy = (char *) newp + (key_copy - (char *) dataset);
264 
265 		  dataset = memcpy (newp, dataset, total + req->key_len);
266 		  alloca_used = false;
267 		}
268 
269 	      /* Mark the old record as obsolete.  */
270 	      dh->usable = false;
271 	    }
272 	}
273       else
274 	{
275 	  /* We write the dataset before inserting it to the database
276 	     since while inserting this thread might block and so would
277 	     unnecessarily keep the receiver waiting.  */
278 	  assert (fd != -1);
279 
280 	  if (writeall (fd, &dataset->resp, dataset->head.recsize)
281 	      != dataset->head.recsize)
282 	    all_written = false;
283 	}
284 
285       /* Add the record to the database.  But only if it has not been
286 	 stored on the stack.  */
287       if (! alloca_used)
288 	{
289 	  /* If necessary, we also propagate the data to disk.  */
290 	  if (db->persistent)
291 	    {
292 	      // XXX async OK?
293 	      uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
294 	      msync ((void *) pval,
295 		     ((uintptr_t) dataset & pagesize_m1)
296 		     + total + req->key_len, MS_ASYNC);
297 	    }
298 
299 	  (void) cache_add (req->type, key_copy, req->key_len,
300 			    &dataset->head, true, db, owner, he == NULL);
301 
302 	  pthread_rwlock_unlock (&db->lock);
303 	}
304     }
305 
306   if (__builtin_expect (!all_written, 0) && debug_level > 0)
307     {
308       char buf[256];
309       dbg_log (_("short write in %s: %s"),  __FUNCTION__,
310 	       strerror_r (errno, buf, sizeof (buf)));
311     }
312 
313   return timeout;
314 }
315 
316 
317 static int
lookup(int type,char * key,struct servent * resultbufp,char * buffer,size_t buflen,struct servent ** serv)318 lookup (int type, char *key, struct servent *resultbufp, char *buffer,
319 	size_t buflen, struct servent **serv)
320 {
321   char *proto = strrchr (key, '/');
322   if (proto != NULL && proto != key)
323     {
324       key = strndupa (key, proto - key);
325       if (proto[1] == '\0')
326 	proto = NULL;
327       else
328 	++proto;
329     }
330 
331   if (type == GETSERVBYNAME)
332     return __getservbyname_r (key, proto, resultbufp, buffer, buflen, serv);
333 
334   assert (type == GETSERVBYPORT);
335   return __getservbyport_r (atol (key), proto, resultbufp, buffer, buflen,
336 			    serv);
337 }
338 
339 
340 static time_t
addservbyX(struct database_dyn * db,int fd,request_header * req,char * key,uid_t uid,struct hashentry * he,struct datahead * dh)341 addservbyX (struct database_dyn *db, int fd, request_header *req,
342 	    char *key, uid_t uid, struct hashentry *he, struct datahead *dh)
343 {
344   /* Search for the entry matching the key.  Please note that we don't
345      look again in the table whether the dataset is now available.  We
346      simply insert it.  It does not matter if it is in there twice.  The
347      pruning function only will look at the timestamp.  */
348   struct servent resultbuf;
349   struct servent *serv;
350   int errval = 0;
351   struct scratch_buffer tmpbuf;
352   scratch_buffer_init (&tmpbuf);
353 
354   if (__glibc_unlikely (debug_level > 0))
355     {
356       if (he == NULL)
357 	dbg_log (_("Haven't found \"%s\" in services cache!"), key);
358       else
359 	dbg_log (_("Reloading \"%s\" in services cache!"), key);
360     }
361 
362   while (lookup (req->type, key, &resultbuf,
363 		 tmpbuf.data, tmpbuf.length, &serv) != 0
364 	 && (errval = errno) == ERANGE)
365     if (!scratch_buffer_grow (&tmpbuf))
366       {
367 	/* We ran out of memory.  We cannot do anything but sending a
368 	   negative response.  In reality this should never
369 	   happen.  */
370 	serv = NULL;
371 	/* We set the error to indicate this is (possibly) a temporary
372 	   error and that it does not mean the entry is not available
373 	   at all.  */
374 	errval = EAGAIN;
375 	break;
376       }
377 
378   time_t timeout = cache_addserv (db, fd, req, key, serv, uid, he, dh, errval);
379   scratch_buffer_free (&tmpbuf);
380   return timeout;
381 }
382 
383 
384 void
addservbyname(struct database_dyn * db,int fd,request_header * req,void * key,uid_t uid)385 addservbyname (struct database_dyn *db, int fd, request_header *req,
386 	       void *key, uid_t uid)
387 {
388   addservbyX (db, fd, req, key, uid, NULL, NULL);
389 }
390 
391 
392 time_t
readdservbyname(struct database_dyn * db,struct hashentry * he,struct datahead * dh)393 readdservbyname (struct database_dyn *db, struct hashentry *he,
394 		 struct datahead *dh)
395 {
396   request_header req =
397     {
398       .type = GETSERVBYNAME,
399       .key_len = he->len
400     };
401 
402   return addservbyX (db, -1, &req, db->data + he->key, he->owner, he, dh);
403 }
404 
405 
406 void
addservbyport(struct database_dyn * db,int fd,request_header * req,void * key,uid_t uid)407 addservbyport (struct database_dyn *db, int fd, request_header *req,
408 	       void *key, uid_t uid)
409 {
410   addservbyX (db, fd, req, key, uid, NULL, NULL);
411 }
412 
413 
414 time_t
readdservbyport(struct database_dyn * db,struct hashentry * he,struct datahead * dh)415 readdservbyport (struct database_dyn *db, struct hashentry *he,
416 		 struct datahead *dh)
417 {
418   request_header req =
419     {
420       .type = GETSERVBYPORT,
421       .key_len = he->len
422     };
423 
424   return addservbyX (db, -1, &req, db->data + he->key, he->owner, he, dh);
425 }
426