s3:libsmb: allow store_cldap_reply() to work with a ipv6 response
[samba.git] / source3 / modules / vfs_fake_acls.c
1 /* 
2  * Fake ACLs VFS module.  Implements passthrough operation of all VFS
3  * calls to disk functions, except for file ownership and ACLs, which
4  * are stored in xattrs.
5  *
6  * Copyright (C) Tim Potter, 1999-2000
7  * Copyright (C) Alexander Bokovoy, 2002
8  * Copyright (C) Andrew Bartlett, 2002,2012
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 3 of the License, or
13  * (at your option) any later version.
14  *  
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *  
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, see <http://www.gnu.org/licenses/>.
22  */
23
24 #include "includes.h"
25 #include "smbd/smbd.h"
26 #include "system/filesys.h"
27 #include "auth.h"
28 #include "librpc/gen_ndr/ndr_smb_acl.h"
29
30 #undef DBGC_CLASS
31 #define DBGC_CLASS DBGC_VFS
32
33 #define FAKE_UID "system.fake_uid"
34 #define FAKE_GID "system.fake_gid"
35 #define FAKE_ACL_ACCESS_XATTR "system.fake_access_acl"
36 #define FAKE_ACL_DEFAULT_XATTR "system.fake_default_acl"
37
38 struct in_pathref_data {
39         bool calling_pathref_fsp;
40 };
41
42 static int fake_acls_fuid(vfs_handle_struct *handle,
43                            files_struct *fsp,
44                            uid_t *uid)
45 {
46         ssize_t size;
47         uint8_t uid_buf[4];
48
49         size = SMB_VFS_NEXT_FGETXATTR(handle, fsp, FAKE_UID, uid_buf, sizeof(uid_buf));
50         if (size == -1 && errno == ENOATTR) {
51                 return 0;
52         }
53         if (size != 4) {
54                 return -1;
55         }
56         *uid = IVAL(uid_buf, 0);
57         return 0;
58 }
59
60 static int fake_acls_fgid(vfs_handle_struct *handle,
61                            files_struct *fsp,
62                           uid_t *gid)
63 {
64         ssize_t size;
65         uint8_t gid_buf[4];
66
67         size = SMB_VFS_NEXT_FGETXATTR(handle, fsp, FAKE_GID, gid_buf, sizeof(gid_buf));
68         if (size == -1 && errno == ENOATTR) {
69                 return 0;
70         }
71         if (size != 4) {
72                 return -1;
73         }
74         *gid = IVAL(gid_buf, 0);
75         return 0;
76 }
77
78 static int fake_acls_stat(vfs_handle_struct *handle,
79                            struct smb_filename *smb_fname)
80 {
81         int ret = -1;
82         struct in_pathref_data *prd = NULL;
83         struct smb_filename *smb_fname_cp = NULL;
84         struct files_struct *fsp = NULL;
85
86         SMB_VFS_HANDLE_GET_DATA(handle,
87                                 prd,
88                                 struct in_pathref_data,
89                                 return -1);
90
91         ret = SMB_VFS_NEXT_STAT(handle, smb_fname);
92         if (ret != 0) {
93                 return ret;
94         }
95
96         if (smb_fname->fsp != NULL) {
97                 fsp = metadata_fsp(smb_fname->fsp);
98         } else {
99                 NTSTATUS status;
100
101                 /*
102                  * Ensure openat_pathref_fsp()
103                  * can't recurse into fake_acls_stat().
104                  * openat_pathref_fsp() doesn't care
105                  * about the uid/gid values, it only
106                  * wants a valid/invalid stat answer
107                  * and we know smb_fname exists as
108                  * the SMB_VFS_NEXT_STAT() returned
109                  * zero above.
110                  */
111                 if (prd->calling_pathref_fsp) {
112                         return 0;
113                 }
114
115                 /*
116                  * openat_pathref_fsp() expects a talloc'ed
117                  * smb_filename. stat can be passed a struct
118                  * from the stack. Make a talloc'ed copy
119                  * so openat_pathref_fsp() can add its
120                  * destructor.
121                  */
122                 smb_fname_cp = cp_smb_filename(talloc_tos(),
123                                                smb_fname);
124                 if (smb_fname_cp == NULL) {
125                         errno = ENOMEM;
126                         return -1;
127                 }
128
129                 /* Recursion guard. */
130                 prd->calling_pathref_fsp = true;
131                 status = openat_pathref_fsp(handle->conn->cwd_fsp,
132                                             smb_fname_cp);
133                 /* End recursion guard. */
134                 prd->calling_pathref_fsp = false;
135
136                 if (!NT_STATUS_IS_OK(status)) {
137                         /*
138                          * Ignore errors here. We know
139                          * the path exists (the SMB_VFS_NEXT_STAT()
140                          * above succeeded. So being unable to
141                          * open a pathref fsp can be due to a
142                          * range of errors (startup path beginning
143                          * with '/' for example, path = ".." when
144                          * enumerating a directory. Just treat this
145                          * the same way as the path not having the
146                          * FAKE_UID or FAKE_GID EA's present. For the
147                          * test purposes of this module (fake NT ACLs
148                          * from windows clients) this is close enough.
149                          * Just report for debugging purposes.
150                          */
151                         DBG_DEBUG("Unable to get pathref fsp on %s. "
152                                   "Error %s\n",
153                                   smb_fname_str_dbg(smb_fname_cp),
154                                   nt_errstr(status));
155                         TALLOC_FREE(smb_fname_cp);
156                         return 0;
157                 }
158                 fsp = smb_fname_cp->fsp;
159         }
160
161         ret = fake_acls_fuid(handle,
162                              fsp,
163                              &smb_fname->st.st_ex_uid);
164         if (ret != 0) {
165                 TALLOC_FREE(smb_fname_cp);
166                 return ret;
167         }
168         ret = fake_acls_fgid(handle,
169                              fsp,
170                              &smb_fname->st.st_ex_gid);
171         if (ret != 0) {
172                 TALLOC_FREE(smb_fname_cp);
173                 return ret;
174         }
175         TALLOC_FREE(smb_fname_cp);
176         return ret;
177 }
178
179 static int fake_acls_lstat(vfs_handle_struct *handle,
180                            struct smb_filename *smb_fname)
181 {
182         int ret = -1;
183         struct in_pathref_data *prd = NULL;
184
185         SMB_VFS_HANDLE_GET_DATA(handle,
186                                 prd,
187                                 struct in_pathref_data,
188                                 return -1);
189
190         ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname);
191         if (ret == 0) {
192                 struct smb_filename *smb_fname_base = NULL;
193                 SMB_STRUCT_STAT sbuf = { 0 };
194                 NTSTATUS status;
195
196                 /*
197                  * Ensure synthetic_pathref()
198                  * can't recurse into fake_acls_lstat().
199                  * synthetic_pathref() doesn't care
200                  * about the uid/gid values, it only
201                  * wants a valid/invalid stat answer
202                  * and we know smb_fname exists as
203                  * the SMB_VFS_NEXT_LSTAT() returned
204                  * zero above.
205                  */
206                 if (prd->calling_pathref_fsp) {
207                         return 0;
208                 }
209
210                 /* Recursion guard. */
211                 prd->calling_pathref_fsp = true;
212                 status = synthetic_pathref(talloc_tos(),
213                                            handle->conn->cwd_fsp,
214                                            smb_fname->base_name,
215                                            NULL,
216                                            &sbuf,
217                                            smb_fname->twrp,
218                                            0, /* we want stat, not lstat. */
219                                            &smb_fname_base);
220                 /* End recursion guard. */
221                 prd->calling_pathref_fsp = false;
222                 if (NT_STATUS_IS_OK(status)) {
223                         /*
224                          * This isn't quite right (calling fgetxattr not
225                          * lgetxattr), but for the test purposes of this
226                          * module (fake NT ACLs from windows clients), it is
227                          * close enough.  We removed the l*xattr functions
228                          * because linux doesn't support using them, but we
229                          * could fake them in xattr_tdb if we really wanted
230                          * to. We ignore errors because the link might not
231                          * point anywhere */
232                         fake_acls_fuid(handle,
233                                        smb_fname_base->fsp,
234                                        &smb_fname->st.st_ex_uid);
235                         fake_acls_fgid(handle,
236                                        smb_fname_base->fsp,
237                                        &smb_fname->st.st_ex_gid);
238                 }
239                 TALLOC_FREE(smb_fname_base);
240         }
241
242         return ret;
243 }
244
245 static int fake_acls_fstat(vfs_handle_struct *handle, files_struct *fsp, SMB_STRUCT_STAT *sbuf)
246 {
247         int ret = -1;
248
249         ret = SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf);
250         if (ret == 0) {
251                 ret = fake_acls_fuid(handle, fsp, &sbuf->st_ex_uid);
252                 if (ret != 0) {
253                         return ret;
254                 }
255                 ret = fake_acls_fgid(handle, fsp, &sbuf->st_ex_gid);
256                 if (ret != 0) {
257                         return ret;
258                 }
259         }
260         return ret;
261 }
262
263 static SMB_ACL_T fake_acls_blob2acl(DATA_BLOB *blob, TALLOC_CTX *mem_ctx)
264 {
265         enum ndr_err_code ndr_err;
266         struct smb_acl_t *acl = talloc(mem_ctx, struct smb_acl_t);
267         if (!acl) {
268                 errno = ENOMEM;
269                 return NULL;
270         }
271
272         ndr_err = ndr_pull_struct_blob(blob, acl, acl, 
273                 (ndr_pull_flags_fn_t)ndr_pull_smb_acl_t);
274
275         if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
276                 DEBUG(0, ("ndr_pull_acl_t failed: %s\n",
277                           ndr_errstr(ndr_err)));
278                 TALLOC_FREE(acl);
279                 return NULL;
280         }
281         return acl;
282 }
283
284 static DATA_BLOB fake_acls_acl2blob(TALLOC_CTX *mem_ctx, SMB_ACL_T acl)
285 {
286         enum ndr_err_code ndr_err;
287         DATA_BLOB blob;
288         ndr_err = ndr_push_struct_blob(&blob, mem_ctx, acl, 
289                 (ndr_push_flags_fn_t)ndr_push_smb_acl_t);
290
291         if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
292                 DEBUG(0, ("ndr_push_acl_t failed: %s\n",
293                           ndr_errstr(ndr_err)));
294                 return data_blob_null;
295         }
296         return blob;
297 }
298
299 static SMB_ACL_T fake_acls_sys_acl_get_fd(struct vfs_handle_struct *handle,
300                                           files_struct *fsp,
301                                           SMB_ACL_TYPE_T type,
302                                           TALLOC_CTX *mem_ctx)
303 {
304         DATA_BLOB blob = data_blob_null;
305         ssize_t length;
306         const char *name = NULL;
307         struct smb_acl_t *acl = NULL;
308         TALLOC_CTX *frame = talloc_stackframe();
309                 
310         switch (type) {
311         case SMB_ACL_TYPE_ACCESS:
312                 name = FAKE_ACL_ACCESS_XATTR;
313                 break;
314         case SMB_ACL_TYPE_DEFAULT:
315                 name = FAKE_ACL_DEFAULT_XATTR;
316                 break;
317         default:
318                 DBG_ERR("Illegal ACL type %d\n", (int)type);
319                 break;
320         }
321
322         if (name == NULL) {
323                 TALLOC_FREE(frame);
324                 return NULL;
325         }
326
327         do {
328                 blob.length += 1000;
329                 blob.data = talloc_realloc(frame, blob.data, uint8_t, blob.length);
330                 if (!blob.data) {
331                         errno = ENOMEM;
332                         TALLOC_FREE(frame);
333                         return NULL;
334                 }
335                 length = SMB_VFS_NEXT_FGETXATTR(handle, fsp, name, blob.data, blob.length);
336                 blob.length = length;
337         } while (length == -1 && errno == ERANGE);
338         if (length == -1 && errno == ENOATTR) {
339                 TALLOC_FREE(frame);
340                 return NULL;
341         }
342         if (length != -1) {
343                 acl = fake_acls_blob2acl(&blob, mem_ctx);
344         }
345         TALLOC_FREE(frame);
346         return acl;
347 }
348
349 static int fake_acls_sys_acl_set_fd(vfs_handle_struct *handle,
350                                     struct files_struct *fsp,
351                                     SMB_ACL_TYPE_T type,
352                                     SMB_ACL_T theacl)
353 {
354         int ret;
355         const char *name = NULL;
356         TALLOC_CTX *frame = talloc_stackframe();
357         DATA_BLOB blob = fake_acls_acl2blob(frame, theacl);
358         if (!blob.data) {
359                 DEBUG(0, ("Failed to convert ACL to linear blob for xattr storage\n"));
360                 TALLOC_FREE(frame);
361                 errno = EINVAL;
362                 return -1;
363         }
364
365         switch (type) {
366         case SMB_ACL_TYPE_ACCESS:
367                 name = FAKE_ACL_ACCESS_XATTR;
368                 break;
369         case SMB_ACL_TYPE_DEFAULT:
370                 name = FAKE_ACL_DEFAULT_XATTR;
371                 break;
372         default:
373                 errno = EINVAL;
374                 return -1;
375         }
376
377         ret = SMB_VFS_NEXT_FSETXATTR(handle, fsp, name, blob.data, blob.length, 0);
378         TALLOC_FREE(frame);
379         return ret;
380 }
381
382 static int fake_acls_sys_acl_delete_def_fd(vfs_handle_struct *handle,
383                         struct files_struct *fsp)
384 {
385         int ret;
386         const char *name = FAKE_ACL_DEFAULT_XATTR;
387
388         if (!fsp->fsp_flags.is_directory) {
389                 errno = EINVAL;
390                 return -1;
391         }
392
393         ret = SMB_VFS_NEXT_FREMOVEXATTR(handle, fsp, name);
394         if (ret == -1 && errno == ENOATTR) {
395                 ret = 0;
396                 errno = 0;
397         }
398
399         return ret;
400 }
401
402 static int fake_acls_lchown(vfs_handle_struct *handle,
403                         const struct smb_filename *smb_fname,
404                         uid_t uid,
405                         gid_t gid)
406 {
407         int ret;
408         uint8_t id_buf[4];
409         if (uid != -1) {
410                 uid_t current_uid = get_current_uid(handle->conn);
411
412                 if (current_uid != 0 && current_uid != uid) {
413                         return EACCES;
414                 }
415
416                 /* This isn't quite right (calling setxattr not
417                  * lsetxattr), but for the test purposes of this
418                  * module (fake NT ACLs from windows clients), it is
419                  * close enough.  We removed the l*xattr functions
420                  * because linux doesn't support using them, but we
421                  * could fake them in xattr_tdb if we really wanted
422                  * to.
423                  */
424                 SIVAL(id_buf, 0, uid);
425                 ret = SMB_VFS_NEXT_FSETXATTR(handle,
426                                 smb_fname->fsp,
427                                 FAKE_UID,
428                                 id_buf,
429                                 sizeof(id_buf),
430                                 0);
431                 if (ret != 0) {
432                         return ret;
433                 }
434         }
435         if (gid != -1) {
436                 SIVAL(id_buf, 0, gid);
437                 ret = SMB_VFS_NEXT_FSETXATTR(handle,
438                                 smb_fname->fsp,
439                                 FAKE_GID,
440                                 id_buf,
441                                 sizeof(id_buf),
442                                 0);
443                 if (ret != 0) {
444                         return ret;
445                 }
446         }
447         return 0;
448 }
449
450 static int fake_acls_fchown(vfs_handle_struct *handle, files_struct *fsp, uid_t uid, gid_t gid)
451 {
452         int ret;
453         uint8_t id_buf[4];
454         if (uid != -1) {
455                 uid_t current_uid = get_current_uid(handle->conn);
456
457                 if (current_uid != 0 && current_uid != uid) {
458                         return EACCES;
459                 }
460
461                 SIVAL(id_buf, 0, uid);
462                 ret = SMB_VFS_NEXT_FSETXATTR(handle, fsp, FAKE_UID, id_buf, sizeof(id_buf), 0);
463                 if (ret != 0) {
464                         return ret;
465                 }
466         }
467         if (gid != -1) {
468                 SIVAL(id_buf, 0, gid);
469                 ret = SMB_VFS_NEXT_FSETXATTR(handle, fsp, FAKE_GID, id_buf, sizeof(id_buf), 0);
470                 if (ret != 0) {
471                         return ret;
472                 }
473         }
474         return 0;
475 }
476
477 /*
478  * Implement the chmod uid/mask/other mode changes on a fake ACL.
479  */
480
481 static int fake_acl_process_chmod(SMB_ACL_T *pp_the_acl,
482                                 uid_t owner,
483                                 mode_t mode)
484 {
485         bool got_mask = false;
486         int entry_id = SMB_ACL_FIRST_ENTRY;
487         mode_t umode = 0;
488         mode_t mmode = 0;
489         mode_t omode = 0;
490         int ret = -1;
491         SMB_ACL_T the_acl = *pp_the_acl;
492
493         /* Split the mode into u/mask/other masks. */
494         umode = unix_perms_to_acl_perms(mode, S_IRUSR, S_IWUSR, S_IXUSR);
495         mmode = unix_perms_to_acl_perms(mode, S_IRGRP, S_IWGRP, S_IXGRP);
496         omode = unix_perms_to_acl_perms(mode, S_IROTH, S_IWOTH, S_IXOTH);
497
498         while (1) {
499                 SMB_ACL_ENTRY_T entry;
500                 SMB_ACL_TAG_T tagtype;
501                 SMB_ACL_PERMSET_T permset;
502                 uid_t *puid = NULL;
503
504                 ret = sys_acl_get_entry(the_acl,
505                                         entry_id,
506                                         &entry);
507                 if (ret == 0) {
508                         /* End of ACL */
509                         break;
510                 }
511                 if (ret == -1) {
512                         return -1;
513                 }
514
515                 ret = sys_acl_get_tag_type(entry, &tagtype);
516                 if (ret == -1) {
517                         return -1;
518                 }
519                 ret = sys_acl_get_permset(entry, &permset);
520                 if (ret == -1) {
521                         return -1;
522                 }
523                 switch (tagtype) {
524                         case SMB_ACL_USER_OBJ:
525                                 ret = map_acl_perms_to_permset(umode, &permset);
526                                 if (ret == -1) {
527                                         return -1;
528                                 }
529                                 break;
530                         case SMB_ACL_USER:
531                                 puid = (uid_t *)sys_acl_get_qualifier(entry);
532                                 if (puid == NULL) {
533                                         return -1;
534                                 }
535                                 if (owner != *puid) {
536                                         break;
537                                 }
538                                 ret = map_acl_perms_to_permset(umode, &permset);
539                                 if (ret == -1) {
540                                         return -1;
541                                 }
542                                 break;
543                         case SMB_ACL_GROUP_OBJ:
544                         case SMB_ACL_GROUP:
545                                 /* Ignore all group entries. */
546                                 break;
547                         case SMB_ACL_MASK:
548                                 ret = map_acl_perms_to_permset(mmode, &permset);
549                                 if (ret == -1) {
550                                         return -1;
551                                 }
552                                 got_mask = true;
553                                 break;
554                         case SMB_ACL_OTHER:
555                                 ret = map_acl_perms_to_permset(omode, &permset);
556                                 if (ret == -1) {
557                                         return -1;
558                                 }
559                                 break;
560                         default:
561                                 errno = EINVAL;
562                                 return -1;
563                 }
564                 ret = sys_acl_set_permset(entry, permset);
565                 if (ret == -1) {
566                         return -1;
567                 }
568                 /* Move to next entry. */
569                 entry_id = SMB_ACL_NEXT_ENTRY;
570         }
571
572         /*
573          * If we didn't see a mask entry, add one.
574          */
575
576         if (!got_mask) {
577                 SMB_ACL_ENTRY_T mask_entry;
578                 uint32_t mask_perm = 0;
579                 SMB_ACL_PERMSET_T mask_permset = &mask_perm;
580                 ret = sys_acl_create_entry(&the_acl, &mask_entry);
581                 if (ret == -1) {
582                         return -1;
583                 }
584                 ret = map_acl_perms_to_permset(mmode, &mask_permset);
585                 if (ret == -1) {
586                         return -1;
587                 }
588                 ret = sys_acl_set_permset(mask_entry, mask_permset);
589                 if (ret == -1) {
590                         return -1;
591                 }
592                 ret = sys_acl_set_tag_type(mask_entry, SMB_ACL_MASK);
593                 if (ret == -1) {
594                         return -1;
595                 }
596                 /* In case we were realloced and moved. */
597                 *pp_the_acl = the_acl;
598         }
599
600         return 0;
601 }
602
603 static int fake_acls_fchmod(vfs_handle_struct *handle,
604                         files_struct *fsp,
605                         mode_t mode)
606 {
607         TALLOC_CTX *frame = talloc_stackframe();
608         int ret = -1;
609         SMB_ACL_T the_acl = NULL;
610
611         /*
612          * Passthrough first to preserve the
613          * S_ISUID | S_ISGID | S_ISVTX
614          * bits.
615          */
616
617         ret = SMB_VFS_NEXT_FCHMOD(handle,
618                                 fsp,
619                                 mode);
620         if (ret == -1) {
621                 TALLOC_FREE(frame);
622                 return -1;
623         }
624
625         the_acl = fake_acls_sys_acl_get_fd(handle,
626                                 fsp,
627                                 SMB_ACL_TYPE_ACCESS,
628                                 talloc_tos());
629         if (the_acl == NULL) {
630                 TALLOC_FREE(frame);
631                 if (errno == ENOATTR) {
632                         /* No ACL on this file. Just passthrough. */
633                         return 0;
634                 }
635                 return -1;
636         }
637         ret = fake_acl_process_chmod(&the_acl,
638                         fsp->fsp_name->st.st_ex_uid,
639                         mode);
640         if (ret == -1) {
641                 TALLOC_FREE(frame);
642                 return -1;
643         }
644         ret = fake_acls_sys_acl_set_fd(handle,
645                                 fsp,
646                                 SMB_ACL_TYPE_ACCESS,
647                                 the_acl);
648         TALLOC_FREE(frame);
649         return ret;
650 }
651
652 static int fake_acls_connect(struct vfs_handle_struct *handle,
653                              const char *service,
654                              const char *user)
655 {
656         struct in_pathref_data *prd = NULL;
657         int ret;
658
659         ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
660         if (ret < 0) {
661                 return ret;
662         }
663         /*
664          * Create a struct can tell us if we're recursing
665          * into openat_pathref_fsp() in this module. This will
666          * go away once we have SMB_VFS_STATX() and we will
667          * have a way for a caller to as for specific stat
668          * fields in a granular way. Then we will know exactly
669          * what fields the caller wants, so we won't have to
670          * fill in everything.
671          */
672         prd = talloc_zero(handle->conn, struct in_pathref_data);
673         if (prd == NULL) {
674                 return -1;
675         }
676         SMB_VFS_HANDLE_SET_DATA(handle,
677                                 prd,
678                                 NULL,
679                                 struct in_pathref_data,
680                                 return -1);
681         return 0;
682 }
683
684 static struct vfs_fn_pointers vfs_fake_acls_fns = {
685         .connect_fn = fake_acls_connect,
686         .stat_fn = fake_acls_stat,
687         .lstat_fn = fake_acls_lstat,
688         .fstat_fn = fake_acls_fstat,
689         .fchmod_fn = fake_acls_fchmod,
690         .sys_acl_get_fd_fn = fake_acls_sys_acl_get_fd,
691         .sys_acl_blob_get_fd_fn = posix_sys_acl_blob_get_fd,
692         .sys_acl_set_fd_fn = fake_acls_sys_acl_set_fd,
693         .sys_acl_delete_def_fd_fn = fake_acls_sys_acl_delete_def_fd,
694         .lchown_fn = fake_acls_lchown,
695         .fchown_fn = fake_acls_fchown,
696         
697 };
698
699 static_decl_vfs;
700 NTSTATUS vfs_fake_acls_init(TALLOC_CTX *ctx)
701 {
702         return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "fake_acls",
703                                 &vfs_fake_acls_fns);
704 }