Line data Source code
1 : /*
2 : * System Security Services Daemon. NSS client interface
3 : *
4 : * Copyright (C) Simo Sorce 2011
5 : *
6 : * This program is free software; you can redistribute it and/or modify
7 : * it under the terms of the GNU Lesser General Public License as
8 : * published by the Free Software Foundation; either version 2.1 of the
9 : * License, or (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 Lesser General Public License for more details.
15 : *
16 : * You should have received a copy of the GNU Lesser General Public License
17 : * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 : */
19 :
20 : /* NSS interfaces to mmap cache */
21 :
22 : #include "config.h"
23 :
24 : #include <stdio.h>
25 : #include <errno.h>
26 : #include <sys/types.h>
27 : #include <sys/stat.h>
28 : #include <fcntl.h>
29 : #include <unistd.h>
30 : #include <sys/mman.h>
31 : #include <string.h>
32 : #include <stdlib.h>
33 : #include "nss_mc.h"
34 : #include "sss_cli.h"
35 : #include "util/io.h"
36 :
37 : /* FIXME: hook up to library destructor to avoid leaks */
38 : /* FIXME: temporarily open passwd file on our own, later we will probably
39 : * use socket passing from the main process */
40 : /* FIXME: handle name upper/lower casing ? Maybe a flag passed down by
41 : * sssd or a flag in sss_mc_header ? per domain ? */
42 :
43 : #define MEMCPY_WITH_BARRIERS(res, dest, src, len) \
44 : do { \
45 : uint32_t _b1; \
46 : res = false; \
47 : _b1 = (src)->b1; \
48 : if (MC_VALID_BARRIER(_b1)) { \
49 : __sync_synchronize(); \
50 : memcpy(dest, src, len); \
51 : __sync_synchronize(); \
52 : if ((src)->b2 == _b1) { \
53 : res = true; \
54 : } \
55 : } \
56 : } while(0)
57 :
58 0 : errno_t sss_nss_check_header(struct sss_cli_mc_ctx *ctx)
59 : {
60 : struct sss_mc_header h;
61 : bool copy_ok;
62 : int count;
63 : int ret;
64 : struct stat fdstat;
65 :
66 : /* retry barrier protected reading max 5 times then give up */
67 0 : for (count = 5; count > 0; count--) {
68 0 : MEMCPY_WITH_BARRIERS(copy_ok, &h,
69 : (struct sss_mc_header *)ctx->mmap_base,
70 : sizeof(struct sss_mc_header));
71 0 : if (copy_ok) {
72 : /* record is consistent so we can proceed */
73 0 : break;
74 : }
75 : }
76 0 : if (count == 0) {
77 : /* couldn't successfully read header we have to give up */
78 0 : return EIO;
79 : }
80 :
81 0 : if (h.major_vno != SSS_MC_MAJOR_VNO ||
82 0 : h.minor_vno != SSS_MC_MINOR_VNO ||
83 0 : h.status == SSS_MC_HEADER_RECYCLED) {
84 0 : return EINVAL;
85 : }
86 :
87 : /* first time we check the header, let's fill our own struct */
88 0 : if (ctx->data_table == NULL) {
89 0 : ctx->seed = h.seed;
90 0 : ctx->data_table = MC_PTR_ADD(ctx->mmap_base, h.data_table);
91 0 : ctx->hash_table = MC_PTR_ADD(ctx->mmap_base, h.hash_table);
92 0 : ctx->dt_size = h.dt_size;
93 0 : ctx->ht_size = h.ht_size;
94 : } else {
95 0 : if (ctx->seed != h.seed ||
96 0 : ctx->data_table != MC_PTR_ADD(ctx->mmap_base, h.data_table) ||
97 0 : ctx->hash_table != MC_PTR_ADD(ctx->mmap_base, h.hash_table) ||
98 0 : ctx->dt_size != h.dt_size ||
99 0 : ctx->ht_size != h.ht_size) {
100 0 : return EINVAL;
101 : }
102 : }
103 :
104 0 : ret = fstat(ctx->fd, &fdstat);
105 0 : if (ret == -1) {
106 0 : return EIO;
107 : }
108 :
109 0 : if (fdstat.st_nlink == 0) {
110 : /* memory cache was removed; we need to reinitialize it. */
111 0 : return EINVAL;
112 : }
113 :
114 0 : return 0;
115 : }
116 :
117 130 : static void sss_nss_mc_destroy_ctx(struct sss_cli_mc_ctx *ctx)
118 : {
119 130 : uint32_t active_threads = ctx->active_threads;
120 :
121 130 : if ((ctx->mmap_base != NULL) && (ctx->mmap_size != 0)) {
122 0 : munmap(ctx->mmap_base, ctx->mmap_size);
123 : }
124 130 : if (ctx->fd != -1) {
125 0 : close(ctx->fd);
126 : }
127 130 : memset(ctx, 0, sizeof(struct sss_cli_mc_ctx));
128 130 : ctx->fd = -1;
129 :
130 : /* restore count of active threads */
131 130 : ctx->active_threads = active_threads;
132 130 : }
133 :
134 130 : static errno_t sss_nss_mc_init_ctx(const char *name,
135 : struct sss_cli_mc_ctx *ctx)
136 : {
137 : struct stat fdstat;
138 130 : char *file = NULL;
139 : int ret;
140 :
141 130 : sss_nss_mc_lock();
142 : /* check if ctx is initialised by previous thread. */
143 130 : if (ctx->initialized != UNINITIALIZED) {
144 0 : ret = sss_nss_check_header(ctx);
145 0 : goto done;
146 : }
147 :
148 130 : ret = asprintf(&file, "%s/%s", SSS_NSS_MCACHE_DIR, name);
149 130 : if (ret == -1) {
150 0 : ret = ENOMEM;
151 0 : goto done;
152 : }
153 :
154 130 : ctx->fd = sss_open_cloexec(file, O_RDONLY, &ret);
155 130 : if (ctx->fd == -1) {
156 130 : goto done;
157 : }
158 :
159 0 : ret = fstat(ctx->fd, &fdstat);
160 0 : if (ret == -1) {
161 0 : ret = EIO;
162 0 : goto done;
163 : }
164 :
165 0 : if (fdstat.st_size < MC_HEADER_SIZE) {
166 0 : ret = ENOMEM;
167 0 : goto done;
168 : }
169 0 : ctx->mmap_size = fdstat.st_size;
170 :
171 0 : ctx->mmap_base = mmap(NULL, ctx->mmap_size,
172 : PROT_READ, MAP_SHARED, ctx->fd, 0);
173 0 : if (ctx->mmap_base == MAP_FAILED) {
174 0 : ret = ENOMEM;
175 0 : goto done;
176 : }
177 :
178 0 : ret = sss_nss_check_header(ctx);
179 0 : if (ret != 0) {
180 0 : goto done;
181 : }
182 :
183 0 : ctx->initialized = INITIALIZED;
184 :
185 0 : ret = 0;
186 :
187 : done:
188 130 : if (ret) {
189 130 : sss_nss_mc_destroy_ctx(ctx);
190 : }
191 130 : free(file);
192 130 : sss_nss_mc_unlock();
193 :
194 130 : return ret;
195 : }
196 :
197 130 : errno_t sss_nss_mc_get_ctx(const char *name, struct sss_cli_mc_ctx *ctx)
198 : {
199 : char *envval;
200 : int ret;
201 130 : bool need_decrement = false;
202 :
203 130 : envval = getenv("SSS_NSS_USE_MEMCACHE");
204 130 : if (envval && strcasecmp(envval, "NO") == 0) {
205 0 : return EPERM;
206 : }
207 :
208 130 : switch (ctx->initialized) {
209 : case UNINITIALIZED:
210 130 : __sync_add_and_fetch(&ctx->active_threads, 1);
211 130 : ret = sss_nss_mc_init_ctx(name, ctx);
212 130 : if (ret) {
213 130 : need_decrement = true;
214 : }
215 130 : break;
216 : case INITIALIZED:
217 0 : __sync_add_and_fetch(&ctx->active_threads, 1);
218 0 : ret = sss_nss_check_header(ctx);
219 0 : if (ret) {
220 0 : need_decrement = true;
221 : }
222 0 : break;
223 : case RECYCLED:
224 : /* we need to safely destroy memory cache */
225 0 : ret = EAGAIN;
226 0 : break;
227 : default:
228 0 : ret = EFAULT;
229 : }
230 :
231 130 : if (ret) {
232 130 : if (ctx->initialized == INITIALIZED) {
233 0 : ctx->initialized = RECYCLED;
234 : }
235 130 : if (ctx->initialized == RECYCLED && ctx->active_threads == 0) {
236 : /* just one thread should call munmap */
237 0 : sss_nss_mc_lock();
238 0 : if (ctx->initialized == RECYCLED) {
239 0 : sss_nss_mc_destroy_ctx(ctx);
240 : }
241 0 : sss_nss_mc_unlock();
242 : }
243 130 : if (need_decrement) {
244 : /* In case of error, we will not touch mmapped area => decrement */
245 130 : __sync_sub_and_fetch(&ctx->active_threads, 1);
246 : }
247 : }
248 130 : return ret;
249 : }
250 :
251 0 : uint32_t sss_nss_mc_hash(struct sss_cli_mc_ctx *ctx,
252 : const char *key, size_t len)
253 : {
254 0 : return murmurhash3(key, len, ctx->seed) % MC_HT_ELEMS(ctx->ht_size);
255 : }
256 :
257 0 : errno_t sss_nss_mc_get_record(struct sss_cli_mc_ctx *ctx,
258 : uint32_t slot, struct sss_mc_rec **_rec)
259 : {
260 : struct sss_mc_rec *rec;
261 0 : struct sss_mc_rec *copy_rec = NULL;
262 0 : size_t buf_size = 0;
263 : size_t rec_len;
264 : uint32_t b1;
265 : uint32_t b2;
266 : bool copy_ok;
267 : int count;
268 : int ret;
269 :
270 : /* try max 5 times */
271 0 : for (count = 5; count > 0; count--) {
272 0 : rec = MC_SLOT_TO_PTR(ctx->data_table, slot, struct sss_mc_rec);
273 :
274 : /* fetch record length */
275 0 : b1 = rec->b1;
276 0 : __sync_synchronize();
277 0 : rec_len = rec->len;
278 0 : __sync_synchronize();
279 0 : b2 = rec->b2;
280 0 : if (!MC_VALID_BARRIER(b1) || b1 != b2) {
281 : /* record is inconsistent, retry */
282 0 : continue;
283 : }
284 :
285 0 : if (!MC_CHECK_RECORD_LENGTH(ctx, rec)) {
286 : /* record has invalid length */
287 0 : free(copy_rec);
288 0 : return EINVAL;
289 : }
290 :
291 0 : if (rec_len > buf_size) {
292 0 : free(copy_rec);
293 0 : copy_rec = malloc(rec_len);
294 0 : if (!copy_rec) {
295 0 : ret = ENOMEM;
296 0 : goto done;
297 : }
298 0 : buf_size = rec_len;
299 : }
300 : /* we cannot access data directly, we must copy data and then
301 : * access the copy */
302 0 : MEMCPY_WITH_BARRIERS(copy_ok, copy_rec, rec, rec_len);
303 :
304 : /* we must check data is consistent again after the copy */
305 0 : if (copy_ok && b1 == copy_rec->b2) {
306 : /* record is consistent, use it */
307 0 : break;
308 : }
309 : }
310 0 : if (count == 0) {
311 : /* couldn't successfully read header we have to give up */
312 0 : ret = EIO;
313 0 : goto done;
314 : }
315 :
316 0 : *_rec = copy_rec;
317 0 : ret = 0;
318 :
319 : done:
320 0 : if (ret) {
321 0 : free(copy_rec);
322 0 : *_rec = NULL;
323 : }
324 0 : return ret;
325 : }
326 :
327 : /*
328 : * returns strings froma a buffer.
329 : *
330 : * Call first time with *cookie set to null, then call again
331 : * with the returned cookie.
332 : * On the last string the cookie will be reset to null and
333 : * all strings will have been returned.
334 : * In case the last string is not zero terminated EINVAL is returned.
335 : */
336 0 : errno_t sss_nss_str_ptr_from_buffer(char **str, void **cookie,
337 : char *buf, size_t len)
338 : {
339 0 : char *max = buf + len;
340 : char *ret;
341 : char *p;
342 :
343 0 : if (*cookie == NULL) {
344 0 : p = buf;
345 : } else {
346 0 : p = *((char **)cookie);
347 : }
348 :
349 0 : ret = p;
350 :
351 0 : while (p < max) {
352 0 : if (*p == '\0') {
353 0 : break;
354 : }
355 0 : p++;
356 : }
357 0 : if (p >= max) {
358 0 : return EINVAL;
359 : }
360 0 : p++;
361 0 : if (p == max) {
362 0 : *cookie = NULL;
363 : } else {
364 0 : *cookie = p;
365 : }
366 :
367 0 : *str = ret;
368 0 : return 0;
369 : }
370 :
371 0 : uint32_t sss_nss_mc_next_slot_with_hash(struct sss_mc_rec *rec,
372 : uint32_t hash)
373 : {
374 0 : if (rec->hash1 == hash) {
375 0 : return rec->next1;
376 0 : } else if (rec->hash2 == hash) {
377 0 : return rec->next2;
378 : } else {
379 : /* it should never happen. */
380 0 : return MC_INVALID_VAL;
381 : }
382 :
383 : }
|