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