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