vfs_fileid: add fileid:algorithm = hostname
[samba.git] / source3 / modules / vfs_fileid.c
1 /*
2  * VFS module to alter the algorithm to calculate
3  * the struct file_id used as key for the share mode
4  * and byte range locking db's.
5  *
6  * Copyright (C) 2007, Stefan Metzmacher
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, see <http://www.gnu.org/licenses/>.
20  */
21
22 #include "includes.h"
23 #include "smbd/smbd.h"
24 #include "system/filesys.h"
25
26 static int vfs_fileid_debug_level = DBGC_VFS;
27
28 #undef DBGC_CLASS
29 #define DBGC_CLASS vfs_fileid_debug_level
30
31 struct fileid_mount_entry {
32         SMB_DEV_T device;
33         const char *mnt_fsname;
34         fsid_t fsid;
35         uint64_t devid;
36 };
37
38 struct fileid_handle_data {
39         uint64_t (*device_mapping_fn)(struct fileid_handle_data *data,
40                                       const SMB_STRUCT_STAT *sbuf);
41         char **fstype_deny_list;
42         char **fstype_allow_list;
43         char **mntdir_deny_list;
44         char **mntdir_allow_list;
45         unsigned num_mount_entries;
46         struct fileid_mount_entry *mount_entries;
47 };
48
49 /* check if a mount entry is allowed based on fstype and mount directory */
50 static bool fileid_mount_entry_allowed(struct fileid_handle_data *data,
51                                        struct mntent *m)
52 {
53         int i;
54         char **fstype_deny = data->fstype_deny_list;
55         char **fstype_allow = data->fstype_allow_list;
56         char **mntdir_deny = data->mntdir_deny_list;
57         char **mntdir_allow = data->mntdir_allow_list;
58
59         if (fstype_deny != NULL) {
60                 for (i = 0; fstype_deny[i] != NULL; i++) {
61                         if (strcmp(m->mnt_type, fstype_deny[i]) == 0) {
62                                 return false;
63                         }
64                 }
65         }
66         if (fstype_allow != NULL) {
67                 for (i = 0; fstype_allow[i] != NULL; i++) {
68                         if (strcmp(m->mnt_type, fstype_allow[i]) == 0) {
69                                 break;
70                         }
71                 }
72                 if (fstype_allow[i] == NULL) {
73                         return false;
74                 }
75         }
76         if (mntdir_deny != NULL) {
77                 for (i=0; mntdir_deny[i] != NULL; i++) {
78                         if (strcmp(m->mnt_dir, mntdir_deny[i]) == 0) {
79                                 return false;
80                         }
81                 }
82         }
83         if (mntdir_allow != NULL) {
84                 for (i=0; mntdir_allow[i] != NULL; i++) {
85                         if (strcmp(m->mnt_dir, mntdir_allow[i]) == 0) {
86                                 break;
87                         }
88                 }
89                 if (mntdir_allow[i] == NULL) {
90                         return false;
91                 }
92         }
93         return true;
94 }
95
96
97 /* load all the mount entries from the mtab */
98 static void fileid_load_mount_entries(struct fileid_handle_data *data)
99 {
100         FILE *f;
101         struct mntent *m;
102
103         data->num_mount_entries = 0;
104         TALLOC_FREE(data->mount_entries);
105
106         f = setmntent("/etc/mtab", "r");
107         if (!f) return;
108
109         while ((m = getmntent(f))) {
110                 struct stat st;
111                 struct statfs sfs;
112                 struct fileid_mount_entry *cur;
113                 bool allowed;
114
115                 allowed = fileid_mount_entry_allowed(data, m);
116                 if (!allowed) {
117                         DBG_DEBUG("skipping mount entry %s\n", m->mnt_dir);
118                         continue;
119                 }
120                 if (stat(m->mnt_dir, &st) != 0) continue;
121                 if (statfs(m->mnt_dir, &sfs) != 0) continue;
122
123                 if (strncmp(m->mnt_fsname, "/dev/", 5) == 0) {
124                         m->mnt_fsname += 5;
125                 }
126
127                 data->mount_entries = talloc_realloc(data,
128                                                            data->mount_entries,
129                                                            struct fileid_mount_entry,
130                                                            data->num_mount_entries+1);
131                 if (data->mount_entries == NULL) {
132                         goto nomem;
133                 }
134
135                 cur = &data->mount_entries[data->num_mount_entries];
136                 cur->device     = st.st_dev;
137                 cur->mnt_fsname = talloc_strdup(data->mount_entries,
138                                                 m->mnt_fsname);
139                 if (!cur->mnt_fsname) goto nomem;
140                 cur->fsid       = sfs.f_fsid;
141                 cur->devid      = (uint64_t)-1;
142
143                 data->num_mount_entries++;
144         }
145         endmntent(f);
146         return;
147         
148 nomem:
149         if (f) endmntent(f);
150
151         data->num_mount_entries = 0;
152         TALLOC_FREE(data->mount_entries);
153
154         return;
155 }
156
157 /* find a mount entry given a dev_t */
158 static struct fileid_mount_entry *fileid_find_mount_entry(struct fileid_handle_data *data,
159                                                           SMB_DEV_T dev)
160 {
161         unsigned i;
162
163         if (data->num_mount_entries == 0) {
164                 fileid_load_mount_entries(data);
165         }
166         for (i=0;i<data->num_mount_entries;i++) {
167                 if (data->mount_entries[i].device == dev) {
168                         return &data->mount_entries[i];
169                 }
170         }
171         /* 2nd pass after reloading */
172         fileid_load_mount_entries(data);
173         for (i=0;i<data->num_mount_entries;i++) {
174                 if (data->mount_entries[i].device == dev) {
175                         return &data->mount_entries[i];
176                 }
177         }       
178         return NULL;
179 }
180
181
182 /* a 64 bit hash, based on the one in tdb */
183 static uint64_t fileid_uint64_hash(const uint8_t *s, size_t len)
184 {
185         uint64_t value; /* Used to compute the hash value.  */
186         uint32_t i;     /* Used to cycle through random values. */
187
188         /* Set the initial value from the key size. */
189         for (value = 0x238F13AFLL * len, i=0; i < len; i++)
190                 value = (value + (((uint64_t)s[i]) << (i*5 % 24)));
191
192         return (1103515243LL * value + 12345LL);
193 }
194
195 /* a device mapping using a fsname */
196 static uint64_t fileid_device_mapping_fsname(struct fileid_handle_data *data,
197                                              const SMB_STRUCT_STAT *sbuf)
198 {
199         struct fileid_mount_entry *m;
200
201         m = fileid_find_mount_entry(data, sbuf->st_ex_dev);
202         if (!m) return sbuf->st_ex_dev;
203
204         if (m->devid == (uint64_t)-1) {
205                 m->devid = fileid_uint64_hash((const uint8_t *)m->mnt_fsname,
206                                               strlen(m->mnt_fsname));
207         }
208
209         return m->devid;
210 }
211
212 /* a device mapping using a hostname */
213 static uint64_t fileid_device_mapping_hostname(struct fileid_handle_data *data,
214                                                const SMB_STRUCT_STAT *sbuf)
215 {
216         char hostname[HOST_NAME_MAX+1];
217         char *devname = NULL;
218         uint64_t id;
219         size_t devname_len;
220         int rc;
221
222         rc = gethostname(hostname, HOST_NAME_MAX+1);
223         if (rc != 0) {
224                 DBG_ERR("gethostname failed\n");
225                 return UINT64_MAX;
226         }
227
228         devname = talloc_asprintf(talloc_tos(), "%s%lu",
229                                   hostname, sbuf->st_ex_dev);
230         if (devname == NULL) {
231                 DBG_ERR("talloc_asprintf failed\n");
232                 return UINT64_MAX;
233         }
234         devname_len = talloc_array_length(devname) - 1;
235         TALLOC_FREE(devname);
236
237         id = fileid_uint64_hash((uint8_t *)devname, devname_len);
238         return id;
239 }
240
241 /* device mapping functions using a fsid */
242 static uint64_t fileid_device_mapping_fsid(struct fileid_handle_data *data,
243                                            const SMB_STRUCT_STAT *sbuf)
244 {
245         struct fileid_mount_entry *m;
246
247         m = fileid_find_mount_entry(data, sbuf->st_ex_dev);
248         if (!m) return sbuf->st_ex_dev;
249
250         if (m->devid == (uint64_t)-1) {
251                 if (sizeof(fsid_t) > sizeof(uint64_t)) {
252                         m->devid = fileid_uint64_hash((uint8_t *)&m->fsid,
253                                                       sizeof(m->fsid));
254                 } else {
255                         union {
256                                 uint64_t ret;
257                                 fsid_t fsid;
258                         } u;
259                         ZERO_STRUCT(u);
260                         u.fsid = m->fsid;
261                         m->devid = u.ret;
262                 }
263         }
264
265         return m->devid;
266 }
267
268 static int fileid_connect(struct vfs_handle_struct *handle,
269                           const char *service, const char *user)
270 {
271         struct fileid_handle_data *data;
272         const char *algorithm;
273         const char **fstype_deny_list = NULL;
274         const char **fstype_allow_list = NULL;
275         const char **mntdir_deny_list = NULL;
276         const char **mntdir_allow_list = NULL;
277         int saved_errno;
278         int ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
279
280         if (ret < 0) {
281                 return ret;
282         }
283
284         data = talloc_zero(handle->conn, struct fileid_handle_data);
285         if (!data) {
286                 saved_errno = errno;
287                 SMB_VFS_NEXT_DISCONNECT(handle);
288                 DEBUG(0, ("talloc_zero() failed\n"));
289                 errno = saved_errno;
290                 return -1;
291         }
292
293         /*
294          * "fileid:mapping" is only here as fallback for old setups
295          * "fileid:algorithm" is the option new setups should use
296          */
297         algorithm = lp_parm_const_string(SNUM(handle->conn),
298                                          "fileid", "mapping",
299                                          "fsname");
300         algorithm = lp_parm_const_string(SNUM(handle->conn),
301                                          "fileid", "algorithm",
302                                          algorithm);
303         if (strcmp("fsname", algorithm) == 0) {
304                 data->device_mapping_fn = fileid_device_mapping_fsname;
305         } else if (strcmp("fsid", algorithm) == 0) {
306                 data->device_mapping_fn = fileid_device_mapping_fsid;
307         } else if (strcmp("hostname", algorithm) == 0) {
308                 data->device_mapping_fn = fileid_device_mapping_hostname;
309         } else {
310                 SMB_VFS_NEXT_DISCONNECT(handle);
311                 DEBUG(0,("fileid_connect(): unknown algorithm[%s]\n", algorithm));
312                 return -1;
313         }
314
315         fstype_deny_list = lp_parm_string_list(SNUM(handle->conn), "fileid",
316                                                "fstype deny", NULL);
317         if (fstype_deny_list != NULL) {
318                 data->fstype_deny_list = str_list_copy(data, fstype_deny_list);
319                 if (data->fstype_deny_list == NULL) {
320                         saved_errno = errno;
321                         DBG_ERR("str_list_copy failed\n");
322                         SMB_VFS_NEXT_DISCONNECT(handle);
323                         errno = saved_errno;
324                         return -1;
325                 }
326         }
327
328         fstype_allow_list = lp_parm_string_list(SNUM(handle->conn), "fileid",
329                                                 "fstype allow", NULL);
330         if (fstype_allow_list != NULL) {
331                 data->fstype_allow_list = str_list_copy(data, fstype_allow_list);
332                 if (data->fstype_allow_list == NULL) {
333                         saved_errno = errno;
334                         DBG_ERR("str_list_copy failed\n");
335                         SMB_VFS_NEXT_DISCONNECT(handle);
336                         errno = saved_errno;
337                         return -1;
338                 }
339         }
340
341         mntdir_deny_list = lp_parm_string_list(SNUM(handle->conn), "fileid",
342                                                "mntdir deny", NULL);
343         if (mntdir_deny_list != NULL) {
344                 data->mntdir_deny_list = str_list_copy(data, mntdir_deny_list);
345                 if (data->mntdir_deny_list == NULL) {
346                         saved_errno = errno;
347                         DBG_ERR("str_list_copy failed\n");
348                         SMB_VFS_NEXT_DISCONNECT(handle);
349                         errno = saved_errno;
350                         return -1;
351                 }
352         }
353
354         mntdir_allow_list = lp_parm_string_list(SNUM(handle->conn), "fileid",
355                                                 "mntdir allow", NULL);
356         if (mntdir_allow_list != NULL) {
357                 data->mntdir_allow_list = str_list_copy(data, mntdir_allow_list);
358                 if (data->mntdir_allow_list == NULL) {
359                         saved_errno = errno;
360                         DBG_ERR("str_list_copy failed\n");
361                         SMB_VFS_NEXT_DISCONNECT(handle);
362                         errno = saved_errno;
363                         return -1;
364                 }
365         }
366
367         SMB_VFS_HANDLE_SET_DATA(handle, data, NULL,
368                                 struct fileid_handle_data,
369                                 return -1);
370
371         DEBUG(10, ("fileid_connect(): connect to service[%s] with algorithm[%s]\n",
372                 service, algorithm));
373
374         return 0;
375 }
376
377 static void fileid_disconnect(struct vfs_handle_struct *handle)
378 {
379         DEBUG(10,("fileid_disconnect() connect to service[%s].\n",
380                   lp_servicename(talloc_tos(), SNUM(handle->conn))));
381
382         SMB_VFS_NEXT_DISCONNECT(handle);
383 }
384
385 static struct file_id fileid_file_id_create(struct vfs_handle_struct *handle,
386                                             const SMB_STRUCT_STAT *sbuf)
387 {
388         struct fileid_handle_data *data;
389         struct file_id id;
390
391         ZERO_STRUCT(id);
392
393         SMB_VFS_HANDLE_GET_DATA(handle, data,
394                                 struct fileid_handle_data,
395                                 return id);
396
397         id.devid        = data->device_mapping_fn(data, sbuf);
398         id.inode        = sbuf->st_ex_ino;
399
400         DBG_DEBUG("Returning dev [%jx] inode [%jx]\n",
401                   (uintmax_t)id.devid, (uintmax_t)id.inode);
402
403         return id;
404 }
405
406 static struct vfs_fn_pointers vfs_fileid_fns = {
407         .connect_fn = fileid_connect,
408         .disconnect_fn = fileid_disconnect,
409         .file_id_create_fn = fileid_file_id_create
410 };
411
412 static_decl_vfs;
413 NTSTATUS vfs_fileid_init(TALLOC_CTX *ctx)
414 {
415         NTSTATUS ret;
416
417         ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "fileid",
418                                &vfs_fileid_fns);
419         if (!NT_STATUS_IS_OK(ret)) {
420                 return ret;
421         }
422
423         vfs_fileid_debug_level = debug_add_class("fileid");
424         if (vfs_fileid_debug_level == -1) {
425                 vfs_fileid_debug_level = DBGC_VFS;
426                 DEBUG(0, ("vfs_fileid: Couldn't register custom debugging class!\n"));
427         } else {
428                 DEBUG(10, ("vfs_fileid: Debug class number of 'fileid': %d\n", vfs_fileid_debug_level));
429         }
430
431         return ret;
432 }