]> ruderich.org/simon Gitweb - nsscash/nsscash.git/blob - nss/gr.c
79ac54000a885ab6fece8a82986e7fbda781ea1b
[nsscash/nsscash.git] / nss / gr.c
1 /*
2  * Handle group entries via struct group
3  *
4  * Copyright (C) 2019  Simon Ruderich
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Affero General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (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 Affero General Public License for more details.
15  *
16  * You should have received a copy of the GNU Affero General Public License
17  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
18  */
19
20 #include <errno.h>
21 #include <stddef.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25
26 #include <pthread.h>
27
28 #include "cash_nss.h"
29 #include "file.h"
30 #include "search.h"
31
32
33 // NOTE: This file is very similar to pw.c, keep in sync!
34
35 // TODO: adapt offsets to 32 bit to fit more than 5000 users per group (for 9
36 // byte user names)
37 struct group_entry {
38     uint64_t gid;
39
40     //       off_name = 0, not stored on disk
41     uint16_t off_passwd;
42     uint16_t off_mem_off;
43
44     uint16_t mem_count; // group member count
45
46     /*
47      * Data contains all strings (name, passwd) concatenated, with their
48      * trailing NUL. The off_* variables point to beginning of each string.
49      *
50      * After that the offsets of the members of the group are stored as
51      * mem_count uint16_t values, followed by the member names concatenated as
52      * with the strings above.
53      *
54      * All offsets are relative to the beginning of data.
55      */
56     uint16_t data_size; // size of data in bytes
57     const char data[];
58 } __attribute__((packed));
59
60 static bool entry_to_group(const struct group_entry *e, struct group *g, char *tmp, size_t space) {
61     // Space required for the gr_mem array
62     const size_t mem_size = (size_t)(e->mem_count + 1) * sizeof(char *);
63
64     if (space < e->data_size + mem_size) {
65         return false;
66     }
67
68     char **groups = (char **)tmp;
69
70     const uint16_t *offs_mem = (const uint16_t *)(e->data + e->off_mem_off);
71     for (uint16_t i = 0; i < e->mem_count; i++) {
72         groups[i] = tmp + mem_size + offs_mem[i];
73     }
74     groups[e->mem_count] = NULL;
75
76     // This unnecessarily copies offs_mem[] as well but keeps the code simpler
77     // and the meaning of variables consistent with pw.c
78     memcpy(tmp + mem_size, e->data, e->data_size);
79
80     g->gr_gid = (gid_t)e->gid;
81     g->gr_name = tmp + mem_size + 0;
82     g->gr_passwd = tmp + mem_size + e->off_passwd;
83     g->gr_mem = groups;
84
85     return true;
86 }
87
88
89 static struct file static_file = {
90     .fd = -1,
91 };
92 static pthread_mutex_t static_file_lock = PTHREAD_MUTEX_INITIALIZER;
93
94 enum nss_status _nss_cash_setgrent(int x) {
95     (void)x;
96
97     pthread_mutex_lock(&static_file_lock);
98     // Unmap is necessary to detect changes when the file was replaced on
99     // disk
100     unmap_file(&static_file);
101     // getgrent_r will open the file if necessary when called
102     pthread_mutex_unlock(&static_file_lock);
103
104     return NSS_STATUS_SUCCESS;
105 }
106
107 enum nss_status _nss_cash_endgrent(void) {
108     pthread_mutex_lock(&static_file_lock);
109     unmap_file(&static_file);
110     pthread_mutex_unlock(&static_file_lock);
111
112     return NSS_STATUS_SUCCESS;
113 }
114
115 static enum nss_status internal_getgrent_r(struct group *result, char *buffer, size_t buflen) {
116     // First call to getgrent_r, load file from disk
117     if (static_file.header == NULL) {
118         if (!map_file(NSSCASH_GROUP_FILE, &static_file)) {
119             return NSS_STATUS_UNAVAIL;
120         }
121     }
122
123     const struct header *h = static_file.header;
124     // End of "file", stop
125     if (static_file.next_index >= h->count) {
126         errno = ENOENT;
127         return NSS_STATUS_NOTFOUND;
128     }
129
130     uint64_t *off_orig = (uint64_t *)(h->data + h->off_orig_index);
131     const char *e = h->data + h->off_data + off_orig[static_file.next_index];
132     if (!entry_to_group((struct group_entry *)e, result, buffer, buflen)) {
133         errno = ERANGE;
134         return NSS_STATUS_TRYAGAIN;
135     }
136     static_file.next_index++;
137
138     return NSS_STATUS_SUCCESS;
139 }
140 enum nss_status _nss_cash_getgrent_r(struct group *result, char *buffer, size_t buflen, int *errnop) {
141     pthread_mutex_lock(&static_file_lock);
142     enum nss_status s = internal_getgrent_r(result, buffer, buflen);
143     pthread_mutex_unlock(&static_file_lock);
144     if (s != NSS_STATUS_SUCCESS) {
145         *errnop = errno;
146     }
147     return s;
148 }
149
150
151 static enum nss_status internal_getgr(struct search_key *key, struct group *result, char *buffer, size_t buflen, int *errnop) {
152     struct file f;
153     if (!map_file(NSSCASH_GROUP_FILE, &f)) {
154         *errnop = errno;
155         return NSS_STATUS_UNAVAIL;
156     }
157     const struct header *h = f.header;
158
159     key->data = h->data + h->off_data;
160     uint64_t off_index = (key->name != NULL)
161                        ? h->off_name_index
162                        : h->off_id_index;
163     uint64_t *off = search(key, h->data + off_index, h->count);
164     if (off == NULL) {
165         unmap_file(&f);
166         errno = ENOENT;
167         *errnop = errno;
168         return NSS_STATUS_NOTFOUND;
169     }
170
171     const char *e = key->data + *off;
172     if (!entry_to_group((struct group_entry *)e, result, buffer, buflen)) {
173         unmap_file(&f);
174         errno = ERANGE;
175         *errnop = errno;
176         return NSS_STATUS_TRYAGAIN;
177     }
178
179     unmap_file(&f);
180     return NSS_STATUS_SUCCESS;
181 }
182
183 enum nss_status _nss_cash_getgrgid_r(gid_t gid, struct group *result, char *buffer, size_t buflen, int *errnop) {
184     uint64_t id = (uint64_t)gid;
185     struct search_key key = {
186         .id = &id,
187         .offset = offsetof(struct group_entry, gid),
188     };
189     return internal_getgr(&key, result, buffer, buflen, errnop);
190 }
191
192 enum nss_status _nss_cash_getgrnam_r(const char *name, struct group *result, char *buffer, size_t buflen, int *errnop) {
193     struct search_key key = {
194         .name = name,
195         .offset = sizeof(struct group_entry), // name is first value in data[]
196     };
197     return internal_getgr(&key, result, buffer, buflen, errnop);
198 }