2001-06-18 Roland McGrath <roland@frob.com>
[kopensolaris-gnu/glibc.git] / hurd / lookup-retry.c
1 /* hairy bits of Hurd file name lookup
2    Copyright (C) 1992,93,94,95,96,97,99,2001 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 Library General Public License as
7    published by the Free Software Foundation; either version 2 of the
8    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    Library General Public License for more details.
14
15    You should have received a copy of the GNU Library General Public
16    License along with the GNU C Library; see the file COPYING.LIB.  If not,
17    write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18    Boston, MA 02111-1307, USA.  */
19
20 #include <hurd.h>
21 #include <hurd/lookup.h>
22 #include <hurd/term.h>
23 #include <hurd/paths.h>
24 #include <limits.h>
25 #include <fcntl.h>
26 #include <string.h>
27 #include "stdio-common/_itoa.h"
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_retry (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,
51                                   int flags, mode_t mode,
52                                   retry_type *do_retry, string_t retry_name,
53                                   mach_port_t *result),
54                                enum retry_type doretry,
55                                char retryname[1024],
56                                int flags, mode_t mode,
57                                file_t *result)
58 {
59   error_t err;
60   char *file_name;
61   int nloops;
62
63   error_t lookup_op (file_t startdir)
64     {
65       while (file_name[0] == '/')
66         file_name++;
67
68       return lookup_error ((*lookup) (startdir, file_name, flags, mode,
69                                       &doretry, retryname, result));
70     }
71   error_t reauthenticate (file_t unauth)
72     {
73       error_t err;
74       mach_port_t ref = __mach_reply_port ();
75       error_t reauth (auth_t auth)
76         {
77           return __auth_user_authenticate (auth, ref,
78                                            MACH_MSG_TYPE_MAKE_SEND,
79                                            result);
80         }
81       err = __io_reauthenticate (unauth, ref, MACH_MSG_TYPE_MAKE_SEND);
82       if (! err)
83         err = (*use_init_port) (INIT_PORT_AUTH, &reauth);
84       __mach_port_destroy (__mach_task_self (), ref);
85       __mach_port_deallocate (__mach_task_self (), unauth);
86       return err;
87     }
88
89   if (! lookup)
90     lookup = __dir_lookup;
91
92   nloops = 0;
93   err = 0;
94   do
95     {
96       file_t startdir = MACH_PORT_NULL;
97       int dirport = INIT_PORT_CWDIR;
98
99       switch (doretry)
100         {
101         case FS_RETRY_REAUTH:
102           if (err = reauthenticate (*result))
103             return err;
104           /* Fall through.  */
105
106         case FS_RETRY_NORMAL:
107           if (nloops++ >= SYMLOOP_MAX)
108             {
109               __mach_port_deallocate (__mach_task_self (), *result);
110               return ELOOP;
111             }
112
113           /* An empty RETRYNAME indicates we have the final port.  */
114           if (retryname[0] == '\0' &&
115               /* If reauth'd, we must do one more retry on "" to give the new
116                  translator a chance to make a new port for us.  */
117               doretry == FS_RETRY_NORMAL)
118             {
119               if (flags & O_NOFOLLOW)
120                 {
121                   /* In Linux, O_NOFOLLOW means to reject symlinks.  If we
122                      did an O_NOLINK lookup above and io_stat here to check
123                      for S_IFLNK, a translator like firmlink could easily
124                      spoof this check by not showing S_IFLNK, but in fact
125                      redirecting the lookup to some other name
126                      (i.e. opening the very same holes a symlink would).
127
128                      Instead we do an O_NOTRANS lookup above, and stat the
129                      underlying node: if it has a translator set, and its
130                      owner is not root (st_uid 0) then we reject it.
131                      Since the motivation for this feature is security, and
132                      that security presumes we trust the containing
133                      directory, this check approximates the security of
134                      refusing symlinks while accepting mount points.
135                      Note that we actually permit something Linux doesn't:
136                      we follow root-owned symlinks; if that is deemed
137                      undesireable, we can add a final check for that
138                      one exception to our general translator-based rule.  */
139                   struct stat st;
140                   err = __io_stat (*result, &st);
141                   if (!err
142                       && (st.st_mode & (S_IPTRANS|S_IATRANS)))
143                     {
144                       if (st.st_uid != 0)
145                         err = ENOENT;
146                       else if (st.st_mode & S_IPTRANS)
147                         {
148                           char buf[1024];
149                           char *trans = buf;
150                           size_t translen = sizeof buf;
151                           err = __file_get_translator (*result,
152                                                        &trans, &translen);
153                           if (!err
154                               && translen > sizeof _HURD_SYMLINK
155                               && !memcmp (trans,
156                                           _HURD_SYMLINK, sizeof _HURD_SYMLINK))
157                             err = ENOENT;
158                         }
159                     }
160                 }
161
162               /* We got a successful translation.  Now apply any open-time
163                  action flags we were passed.  */
164
165               if (!err && (flags & O_TRUNC)) /* Asked to truncate the file.  */
166                 err = __file_set_size (*result, 0);
167
168               if (err)
169                 __mach_port_deallocate (__mach_task_self (), *result);
170               return err;
171             }
172
173           startdir = *result;
174           file_name = retryname;
175           break;
176
177         case FS_RETRY_MAGICAL:
178           switch (retryname[0])
179             {
180             case '/':
181               dirport = INIT_PORT_CRDIR;
182               if (*result != MACH_PORT_NULL)
183                 __mach_port_deallocate (__mach_task_self (), *result);
184               if (nloops++ >= SYMLOOP_MAX)
185                 return ELOOP;
186               file_name = &retryname[1];
187               break;
188
189             case 'f':
190               if (retryname[1] == 'd' && retryname[2] == '/')
191                 {
192                   int fd;
193                   char *end;
194                   int save = errno;
195                   errno = 0;
196                   fd = (int) strtol (&retryname[3], &end, 10);
197                   if (end == NULL || errno || /* Malformed number.  */
198                       /* Check for excess text after the number.  A slash
199                          is valid; it ends the component.  Anything else
200                          does not name a numeric file descriptor.  */
201                       (*end != '/' && *end != '\0'))
202                     {
203                       errno = save;
204                       return ENOENT;
205                     }
206                   if (! get_dtable_port)
207                     err = EGRATUITOUS;
208                   else
209                     {
210                       *result = (*get_dtable_port) (fd);
211                       if (*result == MACH_PORT_NULL)
212                         {
213                           /* If the name was a proper number, but the file
214                              descriptor does not exist, we return EBADF instead
215                              of ENOENT.  */
216                           err = errno;
217                           errno = save;
218                         }
219                     }
220                   errno = save;
221                   if (err)
222                     return err;
223                   if (*end == '\0')
224                     return 0;
225                   else
226                     {
227                       /* Do a normal retry on the remaining components.  */
228                       startdir = *result;
229                       file_name = end + 1; /* Skip the slash.  */
230                       break;
231                     }
232                 }
233               else
234                 goto bad_magic;
235               break;
236
237             case 'm':
238               if (retryname[1] == 'a' && retryname[2] == 'c' &&
239                   retryname[3] == 'h' && retryname[4] == 't' &&
240                   retryname[5] == 'y' && retryname[6] == 'p' &&
241                   retryname[7] == 'e')
242                 {
243                   error_t err;
244                   struct host_basic_info hostinfo;
245                   mach_msg_type_number_t hostinfocnt = HOST_BASIC_INFO_COUNT;
246                   char *p;
247                   /* XXX want client's host */
248                   if (err = __host_info (__mach_host_self (), HOST_BASIC_INFO,
249                                          (natural_t *) &hostinfo,
250                                          &hostinfocnt))
251                     return err;
252                   if (hostinfocnt != HOST_BASIC_INFO_COUNT)
253                     return EGRATUITOUS;
254                   p = _itoa (hostinfo.cpu_subtype, &retryname[8], 10, 0);
255                   *--p = '/';
256                   p = _itoa (hostinfo.cpu_type, &retryname[8], 10, 0);
257                   if (p < retryname)
258                     abort ();   /* XXX write this right if this ever happens */
259                   if (p > retryname)
260                     strcpy (retryname, p);
261                   startdir = *result;
262                 }
263               else
264                 goto bad_magic;
265               break;
266
267             case 't':
268               if (retryname[1] == 't' && retryname[2] == 'y')
269                 switch (retryname[3])
270                   {
271                     error_t opentty (file_t *result)
272                       {
273                         error_t err;
274                         error_t ctty_open (file_t port)
275                           {
276                             if (port == MACH_PORT_NULL)
277                               return ENXIO; /* No controlling terminal.  */
278                             return __termctty_open_terminal (port,
279                                                              flags,
280                                                              result);
281                           }
282                         err = (*use_init_port) (INIT_PORT_CTTYID, &ctty_open);
283                         if (! err)
284                           err = reauthenticate (*result);
285                         return err;
286                       }
287
288                   case '\0':
289                     return opentty (result);
290                   case '/':
291                     if (err = opentty (&startdir))
292                       return err;
293                     strcpy (retryname, &retryname[4]);
294                     break;
295                   default:
296                     goto bad_magic;
297                   }
298               else
299                 goto bad_magic;
300               break;
301
302             default:
303             bad_magic:
304               return EGRATUITOUS;
305             }
306           break;
307
308         default:
309           return EGRATUITOUS;
310         }
311
312       if (startdir != MACH_PORT_NULL)
313         {
314           err = lookup_op (startdir);
315           __mach_port_deallocate (__mach_task_self (), startdir);
316           startdir = MACH_PORT_NULL;
317         }
318       else
319         err = (*use_init_port) (dirport, &lookup_op);
320     } while (! err);
321
322   return err;
323 }
324 weak_alias (__hurd_file_name_lookup_retry, hurd_file_name_lookup_retry)