1999-03-14 Roland McGrath <roland@baalperazim.frob.com>
[kopensolaris-gnu/glibc.git] / hurd / hurdlookup.c
1 /* Copyright (C) 1992, 93, 94, 95, 96, 97, 99 Free Software Foundation, Inc.
2    This file is part of the GNU C Library.
3
4    The GNU C Library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Library General Public License as
6    published by the Free Software Foundation; either version 2 of the
7    License, or (at your option) any later version.
8
9    The GNU C Library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Library General Public License for more details.
13
14    You should have received a copy of the GNU Library General Public
15    License along with the GNU C Library; see the file COPYING.LIB.  If not,
16    write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17    Boston, MA 02111-1307, USA.  */
18
19 #include <hurd.h>
20 #include <hurd/lookup.h>
21 #include <string.h>
22 #include <limits.h>
23 #include <fcntl.h>
24 #include "stdio-common/_itoa.h"
25 #include <hurd/term.h>
26 #include <hurd/paths.h>
27
28
29 /* Translate the error from dir_lookup into the error the user sees.  */
30 static inline error_t
31 lookup_error (error_t error)
32 {
33   switch (error)
34     {
35     case EOPNOTSUPP:
36     case MIG_BAD_ID:
37       /* These indicate that the server does not understand dir_lookup
38          at all.  If it were a directory, it would, by definition.  */
39       return ENOTDIR;
40     default:
41       return error;
42     }
43 }
44
45 error_t
46 __hurd_file_name_lookup (error_t (*use_init_port)
47                            (int which, error_t (*operate) (file_t)),
48                          file_t (*get_dtable_port) (int fd),
49                          error_t (*lookup)
50                            (file_t dir, char *name, int flags, mode_t mode,
51                             retry_type *do_retry, string_t retry_name,
52                             mach_port_t *result),
53                          const char *file_name, int flags, mode_t mode,
54                          file_t *result)
55 {
56   error_t err;
57   enum retry_type doretry;
58   char retryname[1024];         /* XXX string_t LOSES! */
59   int startport;
60
61   error_t lookup_op (mach_port_t startdir)
62     {
63       return lookup_error ((*lookup) (startdir, file_name, flags, mode,
64                                       &doretry, retryname, result));
65     }
66
67   if (! lookup)
68     lookup = __dir_lookup;
69
70   startport = (file_name[0] == '/') ? INIT_PORT_CRDIR : INIT_PORT_CWDIR;
71   while (file_name[0] == '/')
72     file_name++;
73
74   if (flags & O_NOFOLLOW)       /* See comments below about O_NOFOLLOW.  */
75     flags |= O_NOTRANS;
76
77   if (flags & O_DIRECTORY)
78     {
79       /* The caller wants to require that the file we look up is a directory.
80          We can do this without an extra RPC by appending a trailing slash
81          to the file name we look up.  */
82       size_t len = strlen (file_name);
83       if (len == 0)
84         file_name = "/";
85       else if (file_name[len - 1] != '/')
86         {
87           char *n = alloca (len + 2);
88           memcpy (n, file_name, len);
89           n[len] = '/';
90           n[len + 1] = '\0';
91           file_name = n;
92         }
93     }
94
95   err = (*use_init_port) (startport, &lookup_op);
96   if (! err)
97     err = __hurd_file_name_lookup_retry (use_init_port, get_dtable_port,
98                                          lookup, doretry, retryname,
99                                          flags, mode, result);
100
101   return err;
102 }
103 weak_alias (__hurd_file_name_lookup, hurd_file_name_lookup)
104
105 error_t
106 __hurd_file_name_lookup_retry (error_t (*use_init_port)
107                                  (int which, error_t (*operate) (file_t)),
108                                file_t (*get_dtable_port) (int fd),
109                                error_t (*lookup)
110                                  (file_t dir, char *name,
111                                   int flags, mode_t mode,
112                                   retry_type *do_retry, string_t retry_name,
113                                   mach_port_t *result),
114                                enum retry_type doretry,
115                                char retryname[1024],
116                                int flags, mode_t mode,
117                                file_t *result)
118 {
119   error_t err;
120   char *file_name;
121   int nloops;
122
123   error_t lookup_op (file_t startdir)
124     {
125       while (file_name[0] == '/')
126         file_name++;
127
128       return lookup_error ((*lookup) (startdir, file_name, flags, mode,
129                                       &doretry, retryname, result));
130     }
131   error_t reauthenticate (file_t unauth)
132     {
133       error_t err;
134       mach_port_t ref = __mach_reply_port ();
135       error_t reauth (auth_t auth)
136         {
137           return __auth_user_authenticate (auth, ref,
138                                            MACH_MSG_TYPE_MAKE_SEND,
139                                            result);
140         }
141       err = __io_reauthenticate (unauth, ref, MACH_MSG_TYPE_MAKE_SEND);
142       if (! err)
143         err = (*use_init_port) (INIT_PORT_AUTH, &reauth);
144       __mach_port_destroy (__mach_task_self (), ref);
145       __mach_port_deallocate (__mach_task_self (), unauth);
146       return err;
147     }
148
149   if (! lookup)
150     lookup = __dir_lookup;
151
152   nloops = 0;
153   err = 0;
154   do
155     {
156       file_t startdir = MACH_PORT_NULL;
157       int dirport = INIT_PORT_CWDIR;
158
159       switch (doretry)
160         {
161         case FS_RETRY_REAUTH:
162           if (err = reauthenticate (*result))
163             return err;
164           /* Fall through.  */
165
166         case FS_RETRY_NORMAL:
167 #ifdef SYMLOOP_MAX
168           if (nloops++ >= SYMLOOP_MAX)
169             return ELOOP;
170 #endif
171
172           /* An empty RETRYNAME indicates we have the final port.  */
173           if (retryname[0] == '\0' &&
174               /* If reauth'd, we must do one more retry on "" to give the new
175                  translator a chance to make a new port for us.  */
176               doretry == FS_RETRY_NORMAL)
177             {
178               if (flags & O_NOFOLLOW)
179                 {
180                   /* In Linux, O_NOFOLLOW means to reject symlinks.  If we
181                      did an O_NOLINK lookup above and io_stat here to check
182                      for S_IFLNK, a translator like firmlink could easily
183                      spoof this check by not showing S_IFLNK, but in fact
184                      redirecting the lookup to some other name
185                      (i.e. opening the very same holes a symlink would).
186
187                      Instead we do an O_NOTRANS lookup above, and stat the
188                      underlying node: if it has a translator set, and its
189                      owner is not root (st_uid 0) then we reject it.
190                      Since the motivation for this feature is security, and
191                      that security presumes we trust the containing
192                      directory, this check approximates the security of
193                      refusing symlinks while accepting mount points.
194                      Note that we actually permit something Linux doesn't:
195                      we follow root-owned symlinks; if that is deemed
196                      undesireable, we can add a final check for that
197                      one exception to our general translator-based rule.  */
198                   struct stat st;
199                   err = __io_stat (*result, &st);
200                   if (!err
201                       && (st.st_mode & (S_IPTRANS|S_IATRANS)))
202                     {
203                       if (st.st_uid != 0)
204                         err = ENOENT;
205                       else if (st.st_mode & S_IPTRANS)
206                         {
207                           char buf[1024];
208                           char *trans = buf;
209                           size_t translen = sizeof buf;
210                           err = __file_get_translator (*result,
211                                                        &trans, &translen);
212                           if (!err
213                               && translen > sizeof _HURD_SYMLINK
214                               && !memcmp (trans,
215                                           _HURD_SYMLINK, sizeof _HURD_SYMLINK))
216                             err = ENOENT;
217                         }
218                     }
219                 }
220
221               /* We got a successful translation.  Now apply any open-time
222                  action flags we were passed.  */
223
224               if (!err && (flags & O_TRUNC)) /* Asked to truncate the file.  */
225                 err = __file_set_size (*result, 0);
226
227               if (err)
228                 __mach_port_deallocate (__mach_task_self (), *result);
229               return err;
230             }
231
232           startdir = *result;
233           file_name = retryname;
234           break;
235
236         case FS_RETRY_MAGICAL:
237           switch (retryname[0])
238             {
239             case '/':
240               dirport = INIT_PORT_CRDIR;
241               if (*result != MACH_PORT_NULL)
242                 __mach_port_deallocate (__mach_task_self (), *result);
243               file_name = &retryname[1];
244               break;
245
246             case 'f':
247               if (retryname[1] == 'd' && retryname[2] == '/')
248                 {
249                   int fd;
250                   char *end;
251                   int save = errno;
252                   errno = 0;
253                   fd = (int) strtol (&retryname[3], &end, 10);
254                   if (end == NULL || errno || /* Malformed number.  */
255                       /* Check for excess text after the number.  A slash
256                          is valid; it ends the component.  Anything else
257                          does not name a numeric file descriptor.  */
258                       (*end != '/' && *end != '\0'))
259                     {
260                       errno = save;
261                       return ENOENT;
262                     }
263                   if (! get_dtable_port)
264                     err = EGRATUITOUS;
265                   else
266                     {
267                       *result = (*get_dtable_port) (fd);
268                       if (*result == MACH_PORT_NULL)
269                         {
270                           /* If the name was a proper number, but the file
271                              descriptor does not exist, we return EBADF instead
272                              of ENOENT.  */
273                           err = errno;
274                           errno = save;
275                         }
276                     }
277                   errno = save;
278                   if (err)
279                     return err;
280                   if (*end == '\0')
281                     return 0;
282                   else
283                     {
284                       /* Do a normal retry on the remaining components.  */
285                       startdir = *result;
286                       file_name = end + 1; /* Skip the slash.  */
287                       break;
288                     }
289                 }
290               else
291                 goto bad_magic;
292               break;
293
294             case 'm':
295               if (retryname[1] == 'a' && retryname[2] == 'c' &&
296                   retryname[3] == 'h' && retryname[4] == 't' &&
297                   retryname[5] == 'y' && retryname[6] == 'p' &&
298                   retryname[7] == 'e')
299                 {
300                   error_t err;
301                   struct host_basic_info hostinfo;
302                   mach_msg_type_number_t hostinfocnt = HOST_BASIC_INFO_COUNT;
303                   char *p;
304                   /* XXX want client's host */
305                   if (err = __host_info (__mach_host_self (), HOST_BASIC_INFO,
306                                          (natural_t *) &hostinfo,
307                                          &hostinfocnt))
308                     return err;
309                   if (hostinfocnt != HOST_BASIC_INFO_COUNT)
310                     return EGRATUITOUS;
311                   p = _itoa (hostinfo.cpu_subtype, &retryname[8], 10, 0);
312                   *--p = '/';
313                   p = _itoa (hostinfo.cpu_type, &retryname[8], 10, 0);
314                   if (p < retryname)
315                     abort ();   /* XXX write this right if this ever happens */
316                   if (p > retryname)
317                     strcpy (retryname, p);
318                   startdir = *result;
319                 }
320               else
321                 goto bad_magic;
322               break;
323
324             case 't':
325               if (retryname[1] == 't' && retryname[2] == 'y')
326                 switch (retryname[3])
327                   {
328                     error_t opentty (file_t *result)
329                       {
330                         error_t err;
331                         error_t ctty_open (file_t port)
332                           {
333                             if (port == MACH_PORT_NULL)
334                               return ENXIO; /* No controlling terminal.  */
335                             return __termctty_open_terminal (port,
336                                                              flags,
337                                                              result);
338                           }
339                         err = (*use_init_port) (INIT_PORT_CTTYID, &ctty_open);
340                         if (! err)
341                           err = reauthenticate (*result);
342                         return err;
343                       }
344
345                   case '\0':
346                     return opentty (result);
347                   case '/':
348                     if (err = opentty (&startdir))
349                       return err;
350                     strcpy (retryname, &retryname[4]);
351                     break;
352                   default:
353                     goto bad_magic;
354                   }
355               else
356                 goto bad_magic;
357               break;
358
359             default:
360             bad_magic:
361               return EGRATUITOUS;
362             }
363           break;
364
365         default:
366           return EGRATUITOUS;
367         }
368
369       if (startdir != MACH_PORT_NULL)
370         {
371           err = lookup_op (startdir);
372           __mach_port_deallocate (__mach_task_self (), startdir);
373           startdir = MACH_PORT_NULL;
374         }
375       else
376         err = (*use_init_port) (dirport, &lookup_op);
377     } while (! err);
378
379   return err;
380 }
381 weak_alias (__hurd_file_name_lookup_retry, hurd_file_name_lookup_retry)
382
383 error_t
384 __hurd_file_name_split (error_t (*use_init_port)
385                           (int which, error_t (*operate) (file_t)),
386                         file_t (*get_dtable_port) (int fd),
387                         error_t (*lookup)
388                           (file_t dir, char *name, int flags, mode_t mode,
389                            retry_type *do_retry, string_t retry_name,
390                            mach_port_t *result),
391                         const char *file_name,
392                         file_t *dir, char **name)
393 {
394   error_t addref (file_t crdir)
395     {
396       *dir = crdir;
397       return __mach_port_mod_refs (__mach_task_self (),
398                                    crdir, MACH_PORT_RIGHT_SEND, +1);
399     }
400
401   const char *lastslash = strrchr (file_name, '/');
402
403   if (lastslash != NULL)
404     {
405       if (lastslash == file_name)
406         {
407           /* "/foobar" => crdir + "foobar".  */
408           *name = (char *) file_name + 1;
409           return (*use_init_port) (INIT_PORT_CRDIR, &addref);
410         }
411       else
412         {
413           /* "/dir1/dir2/.../file".  */
414           char dirname[lastslash - file_name + 1];
415           memcpy (dirname, file_name, lastslash - file_name);
416           dirname[lastslash - file_name] = '\0';
417           *name = (char *) lastslash + 1;
418           return
419             __hurd_file_name_lookup (use_init_port, get_dtable_port, lookup,
420                                      dirname, 0, 0, dir);
421         }
422     }
423   else
424     {
425       /* "foobar" => cwdir + "foobar".  */
426       *name = (char *) file_name;
427       return (*use_init_port) (INIT_PORT_CWDIR, &addref);
428     }
429 }
430 weak_alias (__hurd_file_name_split, hurd_file_name_split)
431
432 \f
433 file_t
434 __file_name_lookup (const char *file_name, int flags, mode_t mode)
435 {
436   error_t err;
437   file_t result;
438
439   err = __hurd_file_name_lookup (&_hurd_ports_use, &__getdport, 0,
440                                  file_name, flags, mode & ~_hurd_umask,
441                                  &result);
442
443   return err ? (__hurd_fail (err), MACH_PORT_NULL) : result;
444 }
445 weak_alias (__file_name_lookup, file_name_lookup)
446
447
448 file_t
449 __file_name_split (const char *file_name, char **name)
450 {
451   error_t err;
452   file_t result;
453
454   err = __hurd_file_name_split (&_hurd_ports_use, &__getdport, 0,
455                                 file_name, &result, name);
456
457   return err ? (__hurd_fail (err), MACH_PORT_NULL) : result;
458 }
459 weak_alias (__file_name_split, file_name_split)
460
461
462 file_t
463 __file_name_lookup_under (file_t startdir,
464                           const char *file_name, int flags, mode_t mode)
465 {
466   error_t err;
467   file_t result;
468
469   error_t use_init_port (int which, error_t (*operate) (mach_port_t))
470     {
471       return (which == INIT_PORT_CWDIR ? (*operate) (startdir) :
472               _hurd_ports_use (which, operate));
473     }
474
475   err = __hurd_file_name_lookup (&use_init_port, &__getdport, 0,
476                                  file_name, flags, mode & ~_hurd_umask,
477                                  &result);
478
479   return err ? (__hurd_fail (err), MACH_PORT_NULL) : result;
480 }
481 weak_alias (__file_name_lookup_under, file_name_lookup_under)