s3:libsmb: allow store_cldap_reply() to work with a ipv6 response
[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 existence 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         bool is_dfs_share;
110         char *cwd;
111 };
112
113 static int widelinks_connect(struct vfs_handle_struct *handle,
114                         const char *service,
115                         const char *user)
116 {
117         struct widelinks_config *config;
118         int ret;
119
120         ret = SMB_VFS_NEXT_CONNECT(handle,
121                                 service,
122                                 user);
123         if (ret != 0) {
124                 return ret;
125         }
126
127         config = talloc_zero(handle->conn,
128                                 struct widelinks_config);
129         if (!config) {
130                 SMB_VFS_NEXT_DISCONNECT(handle);
131                 return -1;
132         }
133         config->active = lp_widelinks(SNUM(handle->conn));
134         if (!config->active) {
135                 DBG_ERR("vfs_widelinks module loaded with "
136                         "widelinks = no\n");
137         }
138         config->is_dfs_share =
139                 (lp_host_msdfs() && lp_msdfs_root(SNUM(handle->conn)));
140         SMB_VFS_HANDLE_SET_DATA(handle,
141                                 config,
142                                 NULL, /* free_fn */
143                                 struct widelinks_config,
144                                 return -1);
145         return 0;
146 }
147
148 static int widelinks_chdir(struct vfs_handle_struct *handle,
149                                 const struct smb_filename *smb_fname)
150 {
151         int ret = -1;
152         struct widelinks_config *config = NULL;
153         char *new_cwd = NULL;
154
155         SMB_VFS_HANDLE_GET_DATA(handle,
156                                 config,
157                                 struct widelinks_config,
158                                 return -1);
159
160         if (!config->active) {
161                 /* Module not active. */
162                 return SMB_VFS_NEXT_CHDIR(handle, smb_fname);
163         }
164
165         /*
166          * We know we never get a path containing
167          * DOT or DOTDOT.
168          */
169
170         if (smb_fname->base_name[0] == '/') {
171                 /* Absolute path - replace. */
172                 new_cwd = talloc_strdup(config,
173                                 smb_fname->base_name);
174         } else {
175                 if (config->cwd == NULL) {
176                         /*
177                          * Relative chdir before absolute one -
178                          * see note 1b above.
179                          */
180                         struct smb_filename *current_dir_fname =
181                                         SMB_VFS_NEXT_GETWD(handle,
182                                                         config);
183                         if (current_dir_fname == NULL) {
184                                 return -1;
185                         }
186                         /* Paranoia.. */
187                         if (current_dir_fname->base_name[0] != '/') {
188                                 DBG_ERR("SMB_VFS_NEXT_GETWD returned "
189                                         "non-absolute path |%s|\n",
190                                         current_dir_fname->base_name);
191                                 TALLOC_FREE(current_dir_fname);
192                                 return -1;
193                         }
194                         config->cwd = talloc_strdup(config,
195                                         current_dir_fname->base_name);
196                         TALLOC_FREE(current_dir_fname);
197                         if (config->cwd == NULL) {
198                                 return -1;
199                         }
200                 }
201                 new_cwd = talloc_asprintf(config,
202                                 "%s/%s",
203                                 config->cwd,
204                                 smb_fname->base_name);
205         }
206         if (new_cwd == NULL) {
207                 return -1;
208         }
209         ret = SMB_VFS_NEXT_CHDIR(handle, smb_fname);
210         if (ret == -1) {
211                 TALLOC_FREE(new_cwd);
212                 return ret;
213         }
214         /* Replace the cache we use for realpath/getwd. */
215         TALLOC_FREE(config->cwd);
216         config->cwd = new_cwd;
217         DBG_DEBUG("config->cwd now |%s|\n", config->cwd);
218         return 0;
219 }
220
221 static struct smb_filename *widelinks_getwd(vfs_handle_struct *handle,
222                                 TALLOC_CTX *ctx)
223 {
224         struct widelinks_config *config = NULL;
225
226         SMB_VFS_HANDLE_GET_DATA(handle,
227                                 config,
228                                 struct widelinks_config,
229                                 return NULL);
230
231         if (!config->active) {
232                 /* Module not active. */
233                 return SMB_VFS_NEXT_GETWD(handle, ctx);
234         }
235         if (config->cwd == NULL) {
236                 /* getwd before chdir. See note 1b above. */
237                 return SMB_VFS_NEXT_GETWD(handle, ctx);
238         }
239         return synthetic_smb_fname(ctx,
240                                 config->cwd,
241                                 NULL,
242                                 NULL,
243                                 0,
244                                 0);
245 }
246
247 static struct smb_filename *widelinks_realpath(vfs_handle_struct *handle,
248                         TALLOC_CTX *ctx,
249                         const struct smb_filename *smb_fname_in)
250 {
251         struct widelinks_config *config = NULL;
252         char *pathname = NULL;
253         char *resolved_pathname = NULL;
254         struct smb_filename *smb_fname;
255
256         SMB_VFS_HANDLE_GET_DATA(handle,
257                                 config,
258                                 struct widelinks_config,
259                                 return NULL);
260
261         if (!config->active) {
262                 /* Module not active. */
263                 return SMB_VFS_NEXT_REALPATH(handle,
264                                 ctx,
265                                 smb_fname_in);
266         }
267
268         if (config->cwd == NULL) {
269                 /* realpath before chdir. See note 1b above. */
270                 return SMB_VFS_NEXT_REALPATH(handle,
271                                 ctx,
272                                 smb_fname_in);
273         }
274
275         if (smb_fname_in->base_name[0] == '/') {
276                 /* Absolute path - process as-is. */
277                 pathname = talloc_strdup(config,
278                                         smb_fname_in->base_name);
279         } else {
280                 /* Relative path - most commonly "." */
281                 pathname = talloc_asprintf(config,
282                                 "%s/%s",
283                                 config->cwd,
284                                 smb_fname_in->base_name);
285         }
286
287         SMB_ASSERT(pathname[0] == '/');
288
289         resolved_pathname = canonicalize_absolute_path(config, pathname);
290         if (resolved_pathname == NULL) {
291                 TALLOC_FREE(pathname);
292                 return NULL;
293         }
294
295         DBG_DEBUG("realpath |%s| -> |%s| -> |%s|\n",
296                         smb_fname_in->base_name,
297                         pathname,
298                         resolved_pathname);
299
300         smb_fname = synthetic_smb_fname(ctx,
301                                 resolved_pathname,
302                                 NULL,
303                                 NULL,
304                                 0,
305                                 0);
306         TALLOC_FREE(pathname);
307         TALLOC_FREE(resolved_pathname);
308         return smb_fname;
309 }
310
311 static int widelinks_lstat(vfs_handle_struct *handle,
312                         struct smb_filename *smb_fname)
313 {
314         struct widelinks_config *config = NULL;
315
316         SMB_VFS_HANDLE_GET_DATA(handle,
317                                 config,
318                                 struct widelinks_config,
319                                 return -1);
320
321         if (!config->active) {
322                 /* Module not active. */
323                 return SMB_VFS_NEXT_LSTAT(handle,
324                                 smb_fname);
325         }
326
327         if (config->cwd == NULL) {
328                 /* lstat before chdir. See note 1b above. */
329                 return SMB_VFS_NEXT_LSTAT(handle,
330                                 smb_fname);
331         }
332
333         if (smb_fname->flags & SMB_FILENAME_POSIX_PATH) {
334                 /* POSIX sees symlinks. */
335                 return SMB_VFS_NEXT_LSTAT(handle,
336                                 smb_fname);
337         }
338
339         /* Replace with STAT. */
340         return SMB_VFS_NEXT_STAT(handle, smb_fname);
341 }
342
343 static int widelinks_openat(vfs_handle_struct *handle,
344                             const struct files_struct *dirfsp,
345                             const struct smb_filename *smb_fname,
346                             files_struct *fsp,
347                             const struct vfs_open_how *_how)
348 {
349         struct vfs_open_how how = *_how;
350         struct widelinks_config *config = NULL;
351         int ret;
352         SMB_VFS_HANDLE_GET_DATA(handle,
353                                 config,
354                                 struct widelinks_config,
355                                 return -1);
356
357         if (config->active &&
358             (config->cwd != NULL) &&
359             !(smb_fname->flags & SMB_FILENAME_POSIX_PATH))
360         {
361                 /*
362                  * Module active, openat after chdir (see note 1b above) and not
363                  * a POSIX open (POSIX sees symlinks), so remove O_NOFOLLOW.
364                  */
365                 how.flags = (how.flags & ~O_NOFOLLOW);
366         }
367
368         ret = SMB_VFS_NEXT_OPENAT(handle,
369                                    dirfsp,
370                                    smb_fname,
371                                    fsp,
372                                    &how);
373         if (config->is_dfs_share && ret == -1 && errno == ENOENT) {
374                 struct smb_filename *full_fname = NULL;
375                 int lstat_ret;
376
377                 full_fname = full_path_from_dirfsp_atname(talloc_tos(),
378                                 dirfsp,
379                                 smb_fname);
380                 if (full_fname == NULL) {
381                         errno = ENOMEM;
382                         return -1;
383                 }
384                 lstat_ret = SMB_VFS_NEXT_LSTAT(handle,
385                                 full_fname);
386                 if (lstat_ret != -1 &&
387                     VALID_STAT(full_fname->st) &&
388                     S_ISLNK(full_fname->st.st_ex_mode)) {
389                         fsp->fsp_name->st = full_fname->st;
390                 }
391                 TALLOC_FREE(full_fname);
392                 errno = ELOOP;
393         }
394         return ret;
395 }
396
397 static struct vfs_fn_pointers vfs_widelinks_fns = {
398         .connect_fn = widelinks_connect,
399
400         .openat_fn = widelinks_openat,
401         .lstat_fn = widelinks_lstat,
402         /*
403          * NB. We don't need an lchown function as this
404          * is only called (a) on directory create and
405          * (b) on POSIX extensions names.
406          */
407         .chdir_fn = widelinks_chdir,
408         .getwd_fn = widelinks_getwd,
409         .realpath_fn = widelinks_realpath,
410 };
411
412 static_decl_vfs;
413 NTSTATUS vfs_widelinks_init(TALLOC_CTX *ctx)
414 {
415         return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
416                                 "widelinks",
417                                 &vfs_widelinks_fns);
418 }