vfs: Add dirfsp arg to SMB_VFS_READDIR()
[samba.git] / source3 / modules / vfs_widelinks.c
1 /*
2  * Widelinks VFS module. Causes smbd not to see symlinks.
3  *
4  * Copyright (C) Jeremy Allison, 2020
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program 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
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see <http://www.gnu.org/licenses/>.
18  */
19
20 /*
21  What does this module do ? It implements the explicitly insecure
22  "widelinks = yes" functionality that used to be in the core smbd
23  code.
24
25  Now this is implemented here, the insecure share-escape code that
26  explicitly allows escape from an exported share path can be removed
27  from smbd, leaving it a cleaner and more maintainable code base.
28
29  The smbd code can now always return ACCESS_DENIED if a path
30  leads outside a share.
31
32  How does it do that ? There are 2 features.
33
34  1). When the upper layer code does a chdir() call to a pathname,
35  this module stores the requested pathname inside config->cwd.
36
37  When the upper layer code does a getwd() or realpath(), we return
38  the absolute path of the value stored in config->cwd, *not* the
39  position on the underlying filesystem.
40
41  This hides symlinks as if the chdir pathname contains a symlink,
42  normally doing a realpath call on it would return the real
43  position on the filesystem. For widelinks = yes, this isn't what
44  you want. You want the position you think is underneath the share
45  definition - the symlink path you used to go outside the share,
46  not the contents of the symlink itself.
47
48  That way, the upper layer smbd code can strictly enforce paths
49  being underneath a share definition without the knowledge that
50  "widelinks = yes" has moved us outside the share definition.
51
52  1a). Note that when setting up a share, smbd may make calls such
53  as realpath and stat/lstat in order to set up the share definition.
54  These calls are made *before* smbd calls chdir() to move the working
55  directory below the exported share definition. In order to allow
56  this, all the vfs_widelinks functions are coded to just pass through
57  the vfs call to the next module in the chain if (a). The widelinks
58  module was loaded in error by an administrator and widelinks is
59  set to "no". This is the:
60
61         if (!config->active) {
62                 Module not active.
63                 SMB_VFS_NEXT_XXXXX(...)
64         }
65
66  idiom in the vfs functions.
67
68  1b). If the module was correctly active, but smbd has yet
69  to call chdir(), then config->cwd == NULL. In that case
70  the correct action (to match the previous widelinks behavior
71  in the code inside smbd) is to pass through the vfs call to
72  the next module in the chain. That way, any symlinks in the
73  pathname are still exposed to smbd, which will restrict them to
74  be under the exported share definition. This allows the module
75  to "fail safe" for any vfs call made when setting up the share
76  structure definition, rather than fail unsafe by hiding symlinks
77  before chdir is called. This is the:
78
79         if (config->cwd == NULL) {
80                 XXXXX syscall before chdir - see note 1b above.
81                 return SMB_VFS_NEXT_XXXXX()
82         }
83
84  idiom in the vfs functions.
85
86  2). The module hides the existance of symlinks by inside
87  lstat(), open(), and readdir() so long as it's not a POSIX
88  pathname request (those requests *must* be aware of symlinks
89  and the POSIX client has to follow them, it's expected that
90  a server will always fail to follow symlinks).
91
92  It does this by:
93
94  2a). lstat -> stat
95  2b). open removes any O_NOFOLLOW from flags.
96  2c). The optimization in readdir that returns a stat
97  struct is removed as this could return a symlink mode
98  bit, causing smbd to always call stat/lstat itself on
99  a pathname (which we'll then use to hide symlinks).
100
101 */
102
103 #include "includes.h"
104 #include "smbd/smbd.h"
105 #include "lib/util_path.h"
106
107 struct widelinks_config {
108         bool active;
109         char *cwd;
110 };
111
112 static int widelinks_connect(struct vfs_handle_struct *handle,
113                         const char *service,
114                         const char *user)
115 {
116         struct widelinks_config *config;
117         int ret;
118
119         ret = SMB_VFS_NEXT_CONNECT(handle,
120                                 service,
121                                 user);
122         if (ret != 0) {
123                 return ret;
124         }
125
126         config = talloc_zero(handle->conn,
127                                 struct widelinks_config);
128         if (!config) {
129                 SMB_VFS_NEXT_DISCONNECT(handle);
130                 return -1;
131         }
132         config->active = lp_widelinks(SNUM(handle->conn));
133         if (!config->active) {
134                 DBG_ERR("vfs_widelinks module loaded with "
135                         "widelinks = no\n");
136         }
137
138         SMB_VFS_HANDLE_SET_DATA(handle,
139                                 config,
140                                 NULL, /* free_fn */
141                                 struct widelinks_config,
142                                 return -1);
143         return 0;
144 }
145
146 static int widelinks_chdir(struct vfs_handle_struct *handle,
147                                 const struct smb_filename *smb_fname)
148 {
149         int ret = -1;
150         struct widelinks_config *config = NULL;
151         char *new_cwd = NULL;
152
153         SMB_VFS_HANDLE_GET_DATA(handle,
154                                 config,
155                                 struct widelinks_config,
156                                 return -1);
157
158         if (!config->active) {
159                 /* Module not active. */
160                 return SMB_VFS_NEXT_CHDIR(handle, smb_fname);
161         }
162
163         /*
164          * We know we never get a path containing
165          * DOT or DOTDOT.
166          */
167
168         if (smb_fname->base_name[0] == '/') {
169                 /* Absolute path - replace. */
170                 new_cwd = talloc_strdup(config,
171                                 smb_fname->base_name);
172         } else {
173                 if (config->cwd == NULL) {
174                         /*
175                          * Relative chdir before absolute one -
176                          * see note 1b above.
177                          */
178                         struct smb_filename *current_dir_fname =
179                                         SMB_VFS_NEXT_GETWD(handle,
180                                                         config);
181                         if (current_dir_fname == NULL) {
182                                 return -1;
183                         }
184                         /* Paranoia.. */
185                         if (current_dir_fname->base_name[0] != '/') {
186                                 DBG_ERR("SMB_VFS_NEXT_GETWD returned "
187                                         "non-absolute path |%s|\n",
188                                         current_dir_fname->base_name);
189                                 TALLOC_FREE(current_dir_fname);
190                                 return -1;
191                         }
192                         config->cwd = talloc_strdup(config,
193                                         current_dir_fname->base_name);
194                         TALLOC_FREE(current_dir_fname);
195                         if (config->cwd == NULL) {
196                                 return -1;
197                         }
198                 }
199                 new_cwd = talloc_asprintf(config,
200                                 "%s/%s",
201                                 config->cwd,
202                                 smb_fname->base_name);
203         }
204         if (new_cwd == NULL) {
205                 return -1;
206         }
207         ret = SMB_VFS_NEXT_CHDIR(handle, smb_fname);
208         if (ret == -1) {
209                 TALLOC_FREE(new_cwd);
210                 return ret;
211         }
212         /* Replace the cache we use for realpath/getwd. */
213         TALLOC_FREE(config->cwd);
214         config->cwd = new_cwd;
215         DBG_DEBUG("config->cwd now |%s|\n", config->cwd);
216         return 0;
217 }
218
219 static struct smb_filename *widelinks_getwd(vfs_handle_struct *handle,
220                                 TALLOC_CTX *ctx)
221 {
222         struct widelinks_config *config = NULL;
223
224         SMB_VFS_HANDLE_GET_DATA(handle,
225                                 config,
226                                 struct widelinks_config,
227                                 return NULL);
228
229         if (!config->active) {
230                 /* Module not active. */
231                 return SMB_VFS_NEXT_GETWD(handle, ctx);
232         }
233         if (config->cwd == NULL) {
234                 /* getwd before chdir. See note 1b above. */
235                 return SMB_VFS_NEXT_GETWD(handle, ctx);
236         }
237         return synthetic_smb_fname(ctx,
238                                 config->cwd,
239                                 NULL,
240                                 NULL,
241                                 0,
242                                 0);
243 }
244
245 static struct smb_filename *widelinks_realpath(vfs_handle_struct *handle,
246                         TALLOC_CTX *ctx,
247                         const struct smb_filename *smb_fname_in)
248 {
249         struct widelinks_config *config = NULL;
250         char *pathname = NULL;
251         char *resolved_pathname = NULL;
252         struct smb_filename *smb_fname;
253
254         SMB_VFS_HANDLE_GET_DATA(handle,
255                                 config,
256                                 struct widelinks_config,
257                                 return NULL);
258
259         if (!config->active) {
260                 /* Module not active. */
261                 return SMB_VFS_NEXT_REALPATH(handle,
262                                 ctx,
263                                 smb_fname_in);
264         }
265
266         if (config->cwd == NULL) {
267                 /* realpath before chdir. See note 1b above. */
268                 return SMB_VFS_NEXT_REALPATH(handle,
269                                 ctx,
270                                 smb_fname_in);
271         }
272
273         if (smb_fname_in->base_name[0] == '/') {
274                 /* Absolute path - process as-is. */
275                 pathname = talloc_strdup(config,
276                                         smb_fname_in->base_name);
277         } else {
278                 /* Relative path - most commonly "." */
279                 pathname = talloc_asprintf(config,
280                                 "%s/%s",
281                                 config->cwd,
282                                 smb_fname_in->base_name);
283         }
284
285         SMB_ASSERT(pathname[0] == '/');
286
287         resolved_pathname = canonicalize_absolute_path(config, pathname);
288         if (resolved_pathname == NULL) {
289                 TALLOC_FREE(pathname);
290                 return NULL;
291         }
292
293         DBG_DEBUG("realpath |%s| -> |%s| -> |%s|\n",
294                         smb_fname_in->base_name,
295                         pathname,
296                         resolved_pathname);
297
298         smb_fname = synthetic_smb_fname(ctx,
299                                 resolved_pathname,
300                                 NULL,
301                                 NULL,
302                                 0,
303                                 0);
304         TALLOC_FREE(pathname);
305         TALLOC_FREE(resolved_pathname);
306         return smb_fname;
307 }
308
309 static int widelinks_lstat(vfs_handle_struct *handle,
310                         struct smb_filename *smb_fname)
311 {
312         struct widelinks_config *config = NULL;
313
314         SMB_VFS_HANDLE_GET_DATA(handle,
315                                 config,
316                                 struct widelinks_config,
317                                 return -1);
318
319         if (!config->active) {
320                 /* Module not active. */
321                 return SMB_VFS_NEXT_LSTAT(handle,
322                                 smb_fname);
323         }
324
325         if (config->cwd == NULL) {
326                 /* lstat before chdir. See note 1b above. */
327                 return SMB_VFS_NEXT_LSTAT(handle,
328                                 smb_fname);
329         }
330
331         if (smb_fname->flags & SMB_FILENAME_POSIX_PATH) {
332                 /* POSIX sees symlinks. */
333                 return SMB_VFS_NEXT_LSTAT(handle,
334                                 smb_fname);
335         }
336
337         /* Replace with STAT. */
338         return SMB_VFS_NEXT_STAT(handle, smb_fname);
339 }
340
341 static int widelinks_openat(vfs_handle_struct *handle,
342                             const struct files_struct *dirfsp,
343                             const struct smb_filename *smb_fname,
344                             files_struct *fsp,
345                             int flags,
346                             mode_t mode)
347 {
348         struct widelinks_config *config = NULL;
349
350         SMB_VFS_HANDLE_GET_DATA(handle,
351                                 config,
352                                 struct widelinks_config,
353                                 return -1);
354
355         if (config->active &&
356             (config->cwd != NULL) &&
357             !(smb_fname->flags & SMB_FILENAME_POSIX_PATH))
358         {
359                 /*
360                  * Module active, openat after chdir (see note 1b above) and not
361                  * a POSIX open (POSIX sees symlinks), so remove O_NOFOLLOW.
362                  */
363                 flags = (flags & ~O_NOFOLLOW);
364         }
365
366         return SMB_VFS_NEXT_OPENAT(handle,
367                                    dirfsp,
368                                    smb_fname,
369                                    fsp,
370                                    flags,
371                                    mode);
372 }
373
374 static struct dirent *widelinks_readdir(vfs_handle_struct *handle,
375                                         struct files_struct *dirfsp,
376                                         DIR *dirp,
377                                         SMB_STRUCT_STAT *sbuf)
378 {
379         struct widelinks_config *config = NULL;
380         struct dirent *result;
381
382         SMB_VFS_HANDLE_GET_DATA(handle,
383                                 config,
384                                 struct widelinks_config,
385                                 return NULL);
386
387         result = SMB_VFS_NEXT_READDIR(handle,
388                                       dirfsp,
389                                       dirp,
390                                       sbuf);
391
392         if (!config->active) {
393                 /* Module not active. */
394                 return result;
395         }
396
397         /*
398          * Prevent optimization of returning
399          * the stat info. Force caller to go
400          * through our LSTAT that hides symlinks.
401          */
402
403         if (sbuf) {
404                 SET_STAT_INVALID(*sbuf);
405         }
406         return result;
407 }
408
409 static struct vfs_fn_pointers vfs_widelinks_fns = {
410         .connect_fn = widelinks_connect,
411
412         .openat_fn = widelinks_openat,
413         .lstat_fn = widelinks_lstat,
414         /*
415          * NB. We don't need an lchown function as this
416          * is only called (a) on directory create and
417          * (b) on POSIX extensions names.
418          */
419         .chdir_fn = widelinks_chdir,
420         .getwd_fn = widelinks_getwd,
421         .realpath_fn = widelinks_realpath,
422         .readdir_fn = widelinks_readdir
423 };
424
425 static_decl_vfs;
426 NTSTATUS vfs_widelinks_init(TALLOC_CTX *ctx)
427 {
428         return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
429                                 "widelinks",
430                                 &vfs_widelinks_fns);
431 }