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 2 : 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 :
64 : /* retry barrier protected reading max 5 times then give up */
65 2 : for (count = 5; count > 0; count--) {
66 2 : MEMCPY_WITH_BARRIERS(copy_ok, &h,
67 : (struct sss_mc_header *)ctx->mmap_base,
68 : sizeof(struct sss_mc_header));
69 2 : if (copy_ok) {
70 : /* record is consistent so we can proceed */
71 2 : break;
72 : }
73 : }
74 2 : if (count == 0) {
75 : /* couldn't successfully read header we have to give up */
76 0 : return EIO;
77 : }
78 :
79 4 : if (h.major_vno != SSS_MC_MAJOR_VNO ||
80 4 : h.minor_vno != SSS_MC_MINOR_VNO ||
81 2 : h.status == SSS_MC_HEADER_RECYCLED) {
82 0 : return EINVAL;
83 : }
84 :
85 : /* first time we check the header, let's fill our own struct */
86 2 : if (ctx->data_table == NULL) {
87 1 : ctx->seed = h.seed;
88 1 : ctx->data_table = MC_PTR_ADD(ctx->mmap_base, h.data_table);
89 1 : ctx->hash_table = MC_PTR_ADD(ctx->mmap_base, h.hash_table);
90 1 : ctx->dt_size = h.dt_size;
91 1 : ctx->ht_size = h.ht_size;
92 : } else {
93 2 : if (ctx->seed != h.seed ||
94 2 : ctx->data_table != MC_PTR_ADD(ctx->mmap_base, h.data_table) ||
95 2 : ctx->hash_table != MC_PTR_ADD(ctx->mmap_base, h.hash_table) ||
96 2 : ctx->dt_size != h.dt_size ||
97 1 : ctx->ht_size != h.ht_size) {
98 0 : return EINVAL;
99 : }
100 : }
101 :
102 2 : return 0;
103 : }
104 :
105 0 : static void sss_nss_mc_destroy_ctx(struct sss_cli_mc_ctx *ctx)
106 : {
107 0 : if ((ctx->mmap_base != NULL) && (ctx->mmap_size != 0)) {
108 0 : munmap(ctx->mmap_base, ctx->mmap_size);
109 : }
110 0 : if (ctx->fd != -1) {
111 0 : close(ctx->fd);
112 : }
113 0 : memset(ctx, 0, sizeof(struct sss_cli_mc_ctx));
114 0 : ctx->fd = -1;
115 0 : }
116 :
117 1 : static errno_t sss_nss_mc_init_ctx(const char *name,
118 : struct sss_cli_mc_ctx *ctx)
119 : {
120 : struct stat fdstat;
121 1 : char *file = NULL;
122 : int ret;
123 :
124 1 : sss_nss_mc_lock();
125 : /* check if ctx is initialised by previous thread. */
126 1 : if (ctx->initialized != UNINITIALIZED) {
127 0 : ret = sss_nss_check_header(ctx);
128 0 : goto done;
129 : }
130 :
131 1 : ret = asprintf(&file, "%s/%s", SSS_NSS_MCACHE_DIR, name);
132 1 : if (ret == -1) {
133 0 : ret = ENOMEM;
134 0 : goto done;
135 : }
136 :
137 1 : ctx->fd = sss_open_cloexec(file, O_RDONLY, &ret);
138 1 : if (ctx->fd == -1) {
139 0 : goto done;
140 : }
141 :
142 1 : ret = fstat(ctx->fd, &fdstat);
143 1 : if (ret == -1) {
144 0 : ret = EIO;
145 0 : goto done;
146 : }
147 :
148 1 : if (fdstat.st_size < MC_HEADER_SIZE) {
149 0 : ret = ENOMEM;
150 0 : goto done;
151 : }
152 1 : ctx->mmap_size = fdstat.st_size;
153 :
154 1 : ctx->mmap_base = mmap(NULL, ctx->mmap_size,
155 : PROT_READ, MAP_SHARED, ctx->fd, 0);
156 1 : if (ctx->mmap_base == MAP_FAILED) {
157 0 : ret = ENOMEM;
158 0 : goto done;
159 : }
160 :
161 1 : ret = sss_nss_check_header(ctx);
162 1 : if (ret != 0) {
163 0 : goto done;
164 : }
165 :
166 1 : ctx->initialized = INITIALIZED;
167 :
168 1 : ret = 0;
169 :
170 : done:
171 1 : if (ret) {
172 0 : sss_nss_mc_destroy_ctx(ctx);
173 : }
174 1 : free(file);
175 1 : sss_nss_mc_unlock();
176 :
177 1 : return ret;
178 : }
179 :
180 2 : errno_t sss_nss_mc_get_ctx(const char *name, struct sss_cli_mc_ctx *ctx)
181 : {
182 : char *envval;
183 : int ret;
184 2 : bool need_decrement = false;
185 :
186 2 : envval = getenv("SSS_NSS_USE_MEMCACHE");
187 2 : if (envval && strcasecmp(envval, "NO") == 0) {
188 0 : return EPERM;
189 : }
190 :
191 2 : switch (ctx->initialized) {
192 : case UNINITIALIZED:
193 1 : __sync_add_and_fetch(&ctx->active_threads, 1);
194 1 : ret = sss_nss_mc_init_ctx(name, ctx);
195 1 : if (ret) {
196 0 : need_decrement = true;
197 : }
198 1 : break;
199 : case INITIALIZED:
200 1 : __sync_add_and_fetch(&ctx->active_threads, 1);
201 1 : ret = sss_nss_check_header(ctx);
202 1 : if (ret) {
203 0 : need_decrement = true;
204 : }
205 1 : break;
206 : case RECYCLED:
207 : /* we need to safely destroy memory cache */
208 0 : ret = EAGAIN;
209 0 : break;
210 : default:
211 0 : ret = EFAULT;
212 : }
213 :
214 2 : if (ret) {
215 0 : if (ctx->initialized == INITIALIZED) {
216 0 : ctx->initialized = RECYCLED;
217 : }
218 0 : if (ctx->initialized == RECYCLED && ctx->active_threads == 0) {
219 : /* just one thread should call munmap */
220 0 : sss_nss_mc_lock();
221 0 : if (ctx->initialized == RECYCLED) {
222 0 : sss_nss_mc_destroy_ctx(ctx);
223 : }
224 0 : sss_nss_mc_unlock();
225 : }
226 0 : if (need_decrement) {
227 : /* In case of error, we will not touch mmapped area => decrement */
228 0 : __sync_sub_and_fetch(&ctx->active_threads, 1);
229 : }
230 : }
231 2 : return ret;
232 : }
233 :
234 2 : uint32_t sss_nss_mc_hash(struct sss_cli_mc_ctx *ctx,
235 : const char *key, size_t len)
236 : {
237 2 : return murmurhash3(key, len, ctx->seed) % MC_HT_ELEMS(ctx->ht_size);
238 : }
239 :
240 0 : errno_t sss_nss_mc_get_record(struct sss_cli_mc_ctx *ctx,
241 : uint32_t slot, struct sss_mc_rec **_rec)
242 : {
243 : struct sss_mc_rec *rec;
244 0 : struct sss_mc_rec *copy_rec = NULL;
245 0 : size_t buf_size = 0;
246 : size_t rec_len;
247 : uint32_t b1;
248 : uint32_t b2;
249 : bool copy_ok;
250 : int count;
251 : int ret;
252 :
253 : /* try max 5 times */
254 0 : for (count = 5; count > 0; count--) {
255 0 : rec = MC_SLOT_TO_PTR(ctx->data_table, slot, struct sss_mc_rec);
256 :
257 : /* fetch record length */
258 0 : b1 = rec->b1;
259 0 : __sync_synchronize();
260 0 : rec_len = rec->len;
261 0 : __sync_synchronize();
262 0 : b2 = rec->b2;
263 0 : if (!MC_VALID_BARRIER(b1) || b1 != b2) {
264 : /* record is inconsistent, retry */
265 0 : continue;
266 : }
267 :
268 0 : if (!MC_CHECK_RECORD_LENGTH(ctx, rec)) {
269 : /* record has invalid length */
270 0 : free(copy_rec);
271 0 : return EINVAL;
272 : }
273 :
274 0 : if (rec_len > buf_size) {
275 0 : free(copy_rec);
276 0 : copy_rec = malloc(rec_len);
277 0 : if (!copy_rec) {
278 0 : ret = ENOMEM;
279 0 : goto done;
280 : }
281 0 : buf_size = rec_len;
282 : }
283 : /* we cannot access data directly, we must copy data and then
284 : * access the copy */
285 0 : MEMCPY_WITH_BARRIERS(copy_ok, copy_rec, rec, rec_len);
286 :
287 : /* we must check data is consistent again after the copy */
288 0 : if (copy_ok && b1 == copy_rec->b2) {
289 : /* record is consistent, use it */
290 0 : break;
291 : }
292 : }
293 0 : if (count == 0) {
294 : /* couldn't successfully read header we have to give up */
295 0 : ret = EIO;
296 0 : goto done;
297 : }
298 :
299 0 : *_rec = copy_rec;
300 0 : ret = 0;
301 :
302 : done:
303 0 : if (ret) {
304 0 : free(copy_rec);
305 0 : *_rec = NULL;
306 : }
307 0 : return ret;
308 : }
309 :
310 : /*
311 : * returns strings froma a buffer.
312 : *
313 : * Call first time with *cookie set to null, then call again
314 : * with the returned cookie.
315 : * On the last string the cookie will be reset to null and
316 : * all strings will have been returned.
317 : * In case the last string is not zero terminated EINVAL is returned.
318 : */
319 0 : errno_t sss_nss_str_ptr_from_buffer(char **str, void **cookie,
320 : char *buf, size_t len)
321 : {
322 0 : char *max = buf + len;
323 : char *ret;
324 : char *p;
325 :
326 0 : if (*cookie == NULL) {
327 0 : p = buf;
328 : } else {
329 0 : p = *((char **)cookie);
330 : }
331 :
332 0 : ret = p;
333 :
334 0 : while (p < max) {
335 0 : if (*p == '\0') {
336 0 : break;
337 : }
338 0 : p++;
339 : }
340 0 : if (p >= max) {
341 0 : return EINVAL;
342 : }
343 0 : p++;
344 0 : if (p == max) {
345 0 : *cookie = NULL;
346 : } else {
347 0 : *cookie = p;
348 : }
349 :
350 0 : *str = ret;
351 0 : return 0;
352 : }
353 :
354 0 : uint32_t sss_nss_mc_next_slot_with_hash(struct sss_mc_rec *rec,
355 : uint32_t hash)
356 : {
357 0 : if (rec->hash1 == hash) {
358 0 : return rec->next1;
359 0 : } else if (rec->hash2 == hash) {
360 0 : return rec->next2;
361 : } else {
362 : /* it should never happen. */
363 0 : return MC_INVALID_VAL;
364 : }
365 :
366 : }
|