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