1 /* Mail alias file parser in nss_files module.
2 Copyright (C) 1996-2022 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
4
5 The GNU C Library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 The GNU C Library 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 GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with the GNU C Library; if not, see
17 <https://www.gnu.org/licenses/>. */
18
19 #include <aliases.h>
20 #include <ctype.h>
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <libc-lock.h>
24 #include <stdlib.h>
25 #include <stdio.h>
26 #include <string.h>
27
28 #include <kernel-features.h>
29
30 #include "nsswitch.h"
31 #include <nss_files.h>
32
33
34 /* Maintenance of the stream open on the database file. For getXXent
35 operations the stream needs to be held open across calls, the other
36 getXXbyYY operations all use their own stream. */
37
38 static enum nss_status
internal_setent(FILE ** stream)39 internal_setent (FILE **stream)
40 {
41 enum nss_status status = NSS_STATUS_SUCCESS;
42
43 if (*stream == NULL)
44 {
45 *stream = __nss_files_fopen ("/etc/aliases");
46
47 if (*stream == NULL)
48 status = errno == EAGAIN ? NSS_STATUS_TRYAGAIN : NSS_STATUS_UNAVAIL;
49 }
50 else
51 rewind (*stream);
52
53 return status;
54 }
55
56
57 /* Thread-safe, exported version of that. */
58 enum nss_status
_nss_files_setaliasent(void)59 _nss_files_setaliasent (void)
60 {
61 return __nss_files_data_setent (nss_file_aliasent, "/etc/aliases");
62 }
libc_hidden_def(_nss_files_setaliasent)63 libc_hidden_def (_nss_files_setaliasent)
64
65 enum nss_status
66 _nss_files_endaliasent (void)
67 {
68 return __nss_files_data_endent (nss_file_aliasent);
69 }
libc_hidden_def(_nss_files_endaliasent)70 libc_hidden_def (_nss_files_endaliasent)
71
72 /* Parsing the database file into `struct aliasent' data structures. */
73 static enum nss_status
74 get_next_alias (FILE *stream, const char *match, struct aliasent *result,
75 char *buffer, size_t buflen, int *errnop)
76 {
77 enum nss_status status = NSS_STATUS_NOTFOUND;
78 int ignore = 0;
79
80 result->alias_members_len = 0;
81
82 while (1)
83 {
84 /* Now we are ready to process the input. We have to read a
85 line and all its continuations and construct the array of
86 string pointers. This pointers and the names itself have to
87 be placed in BUFFER. */
88 char *first_unused = buffer;
89 size_t room_left = buflen - (buflen % __alignof__ (char *));
90 char *line;
91
92 /* Check whether the buffer is large enough for even trying to
93 read something. */
94 if (room_left < 2)
95 goto no_more_room;
96
97 /* Read the first line. It must contain the alias name and
98 possibly some alias names. */
99 first_unused[room_left - 1] = '\xff';
100 line = __fgets_unlocked (first_unused, room_left, stream);
101 if (line == NULL)
102 /* Nothing to read. */
103 break;
104 else if (first_unused[room_left - 1] != '\xff')
105 {
106 /* The line is too long for our buffer. */
107 no_more_room:
108 *errnop = ERANGE;
109 status = NSS_STATUS_TRYAGAIN;
110 break;
111 }
112 else
113 {
114 char *cp;
115
116 /* If we are in IGNORE mode and the first character in the
117 line is a white space we ignore the line and start
118 reading the next. */
119 if (ignore && isspace (*first_unused))
120 continue;
121
122 /* Terminate the line for any case. */
123 cp = strpbrk (first_unused, "#\n");
124 if (cp != NULL)
125 *cp = '\0';
126
127 /* Skip leading blanks. */
128 while (isspace (*line))
129 ++line;
130
131 result->alias_name = first_unused;
132 while (*line != '\0' && *line != ':')
133 *first_unused++ = *line++;
134 if (*line == '\0' || result->alias_name == first_unused)
135 /* No valid name. Ignore the line. */
136 continue;
137
138 *first_unused++ = '\0';
139 if (room_left < (size_t) (first_unused - result->alias_name))
140 goto no_more_room;
141 room_left -= first_unused - result->alias_name;
142 ++line;
143
144 /* When we search for a specific alias we can avoid all the
145 difficult parts and compare now with the name we are
146 looking for. If it does not match we simply ignore all
147 lines until the next line containing the start of a new
148 alias is found. */
149 ignore = (match != NULL
150 && __strcasecmp (result->alias_name, match) != 0);
151
152 while (! ignore)
153 {
154 while (isspace (*line))
155 ++line;
156
157 cp = first_unused;
158 while (*line != '\0' && *line != ',')
159 *first_unused++ = *line++;
160
161 if (first_unused != cp)
162 {
163 /* OK, we can have a regular entry or an include
164 request. */
165 if (*line != '\0')
166 ++line;
167 *first_unused++ = '\0';
168
169 if (strncmp (cp, ":include:", 9) != 0)
170 {
171 if (room_left < (first_unused - cp) + sizeof (char *))
172 goto no_more_room;
173 room_left -= (first_unused - cp) + sizeof (char *);
174
175 ++result->alias_members_len;
176 }
177 else
178 {
179 /* Oh well, we have to read the addressed file. */
180 FILE *listfile;
181 char *old_line = NULL;
182
183 first_unused = cp;
184
185 listfile = __nss_files_fopen (&cp[9]);
186 /* If the file does not exist we simply ignore
187 the statement. */
188 if (listfile != NULL
189 && (old_line = __strdup (line)) != NULL)
190 {
191 while (! __feof_unlocked (listfile))
192 {
193 if (room_left < 2)
194 {
195 free (old_line);
196 fclose (listfile);
197 goto no_more_room;
198 }
199
200 first_unused[room_left - 1] = '\xff';
201 line = __fgets_unlocked (first_unused, room_left,
202 listfile);
203 if (line == NULL)
204 break;
205 if (first_unused[room_left - 1] != '\xff')
206 {
207 free (old_line);
208 fclose (listfile);
209 goto no_more_room;
210 }
211
212 /* Parse the line. */
213 cp = strpbrk (line, "#\n");
214 if (cp != NULL)
215 *cp = '\0';
216
217 do
218 {
219 while (isspace (*line))
220 ++line;
221
222 cp = first_unused;
223 while (*line != '\0' && *line != ',')
224 *first_unused++ = *line++;
225
226 if (*line != '\0')
227 ++line;
228
229 if (first_unused != cp)
230 {
231 *first_unused++ = '\0';
232 if (room_left < ((first_unused - cp)
233 + __alignof__ (char *)))
234 {
235 free (old_line);
236 fclose (listfile);
237 goto no_more_room;
238 }
239 room_left -= ((first_unused - cp)
240 + __alignof__ (char *));
241 ++result->alias_members_len;
242 }
243 }
244 while (*line != '\0');
245 }
246 fclose (listfile);
247
248 first_unused[room_left - 1] = '\0';
249 strncpy (first_unused, old_line, room_left);
250
251 free (old_line);
252 line = first_unused;
253
254 if (first_unused[room_left - 1] != '\0')
255 goto no_more_room;
256 }
257 }
258 }
259
260 if (*line == '\0')
261 {
262 /* Get the next line. But we must be careful. We
263 must not read the whole line at once since it
264 might belong to the current alias. Simply read
265 the first character. If it is a white space we
266 have a continuation line. Otherwise it is the
267 beginning of a new alias and we can push back the
268 just read character. */
269 int ch;
270
271 ch = __getc_unlocked (stream);
272 if (ch == EOF || ch == '\n' || !isspace (ch))
273 {
274 size_t cnt;
275
276 /* Now prepare the return. Provide string
277 pointers for the currently selected aliases. */
278 if (ch != EOF)
279 ungetc (ch, stream);
280
281 /* Adjust the pointer so it is aligned for
282 storing pointers. */
283 first_unused += __alignof__ (char *) - 1;
284 first_unused -= ((first_unused - (char *) 0)
285 % __alignof__ (char *));
286 result->alias_members = (char **) first_unused;
287
288 /* Compute addresses of alias entry strings. */
289 cp = result->alias_name;
290 for (cnt = 0; cnt < result->alias_members_len; ++cnt)
291 {
292 cp = strchr (cp, '\0') + 1;
293 result->alias_members[cnt] = cp;
294 }
295
296 status = (result->alias_members_len == 0
297 ? NSS_STATUS_RETURN : NSS_STATUS_SUCCESS);
298 break;
299 }
300
301 /* The just read character is a white space and so
302 can be ignored. */
303 first_unused[room_left - 1] = '\xff';
304 line = __fgets_unlocked (first_unused, room_left, stream);
305 if (line == NULL)
306 {
307 /* Continuation line without any data and
308 without a newline at the end. Treat it as an
309 empty line and retry, reaching EOF once
310 more. */
311 line = first_unused;
312 *line = '\0';
313 continue;
314 }
315 if (first_unused[room_left - 1] != '\xff')
316 goto no_more_room;
317 cp = strpbrk (line, "#\n");
318 if (cp != NULL)
319 *cp = '\0';
320 }
321 }
322 }
323
324 if (status != NSS_STATUS_NOTFOUND)
325 /* We read something. In any case break here. */
326 break;
327 }
328
329 return status;
330 }
331
332
333 enum nss_status
_nss_files_getaliasent_r(struct aliasent * result,char * buffer,size_t buflen,int * errnop)334 _nss_files_getaliasent_r (struct aliasent *result, char *buffer, size_t buflen,
335 int *errnop)
336 {
337 /* Return next entry in host file. */
338
339 struct nss_files_per_file_data *data;
340 enum nss_status status = __nss_files_data_open (&data, nss_file_aliasent,
341 "/etc/aliases", errnop, NULL);
342 if (status != NSS_STATUS_SUCCESS)
343 return status;
344
345 result->alias_local = 1;
346
347 /* Read lines until we get a definite result. */
348 do
349 status = get_next_alias (data->stream, NULL, result, buffer, buflen,
350 errnop);
351 while (status == NSS_STATUS_RETURN);
352
353 __nss_files_data_put (data);
354 return status;
355 }
libc_hidden_def(_nss_files_getaliasent_r)356 libc_hidden_def (_nss_files_getaliasent_r)
357
358 enum nss_status
359 _nss_files_getaliasbyname_r (const char *name, struct aliasent *result,
360 char *buffer, size_t buflen, int *errnop)
361 {
362 /* Return next entry in host file. */
363 enum nss_status status = NSS_STATUS_SUCCESS;
364 FILE *stream = NULL;
365
366 if (name == NULL)
367 {
368 __set_errno (EINVAL);
369 return NSS_STATUS_UNAVAIL;
370 }
371
372 /* Open the stream. */
373 status = internal_setent (&stream);
374
375 if (status == NSS_STATUS_SUCCESS)
376 {
377 result->alias_local = 1;
378
379 /* Read lines until we get a definite result. */
380 do
381 status = get_next_alias (stream, name, result, buffer, buflen, errnop);
382 while (status == NSS_STATUS_RETURN);
383
384 fclose (stream);
385 }
386
387 return status;
388 }
389 libc_hidden_def (_nss_files_getaliasbyname_r)
390