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