r2794: a very simple version of the unixuid NTVFS pass-thru module. In
[gd/samba-autobuild/.git] / source4 / ntvfs / unixuid / vfs_unixuid.c
1 /* 
2    Unix SMB/CIFS implementation.
3
4    a pass-thru NTVFS module to setup a security context using unix
5    uid/gid
6
7    Copyright (C) Andrew Tridgell 2004
8
9    This program is free software; you can redistribute it and/or modify
10    it under the terms of the GNU General Public License as published by
11    the Free Software Foundation; either version 2 of the License, or
12    (at your option) any later version.
13    
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License for more details.
18    
19    You should have received a copy of the GNU General Public License
20    along with this program; if not, write to the Free Software
21    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 */
23
24 #include "includes.h"
25
26 struct unixuid_private {
27         void *samctx;
28 };
29
30
31 /*
32   map a sid to a unix uid
33 */
34 static NTSTATUS sid_to_unixuid(struct ntvfs_module_context *ntvfs,
35                                struct smbsrv_request *req, struct dom_sid *sid, uid_t *uid)
36 {
37         struct unixuid_private *private = ntvfs->private_data;
38         const char *attrs[] = { "sAMAccountName", "UnixID", "UnixName", "sAMAccountType", NULL };
39         int ret;
40         const char *s;
41         void *ctx;
42         struct ldb_message **res;
43         const char *sidstr;
44
45         ctx = talloc(req, 0);
46         sidstr = dom_sid_string(ctx, sid);
47
48         ret = samdb_search(private->samctx, ctx, NULL, &res, attrs, "objectSid=%s", sidstr);
49         if (ret != 1) {
50                 DEBUG(2,("Unable to map sid %s to unix uid\n", sidstr));
51                 talloc_free(ctx);
52                 return NT_STATUS_ACCESS_DENIED;
53         }
54
55         /* make sure its a user, not a group */
56         if (samdb_result_uint(res[0], "sAMAccountType", 0) != ATYPE_NORMAL_ACCOUNT) {
57                 DEBUG(0,("sid_to_unixuid: sid %s is not ATYPE_NORMAL_ACCOUNT\n", sidstr));
58                 talloc_free(ctx);
59                 return NT_STATUS_ACCESS_DENIED;
60         }
61
62         /* first try to get the uid directly */
63         s = samdb_result_string(res[0], "UnixID", NULL);
64         if (s != NULL) {
65                 *uid = strtoul(s, NULL, 0);
66                 talloc_free(ctx);
67                 return NT_STATUS_OK;
68         }
69
70         /* next try via the UnixName attribute */
71         s = samdb_result_string(res[0], "UnixName", NULL);
72         if (s != NULL) {
73                 struct passwd *pwd = getpwnam(s);
74                 if (!pwd) {
75                         DEBUG(0,("UnixName %s for sid %s does not exist as a local user\n", s, sidstr));
76                         talloc_free(ctx);
77                         return NT_STATUS_ACCESS_DENIED;
78                 }
79                 *uid = pwd->pw_uid;
80                 talloc_free(ctx);
81                 return NT_STATUS_OK;
82         }
83
84         /* finally try via the sAMAccountName attribute */
85         s = samdb_result_string(res[0], "sAMAccountName", NULL);
86         if (s != NULL) {
87                 struct passwd *pwd = getpwnam(s);
88                 if (!pwd) {
89                         DEBUG(0,("sAMAccountName '%s' for sid %s does not exist as a local user\n", s, sidstr));
90                         talloc_free(ctx);
91                         return NT_STATUS_ACCESS_DENIED;
92                 }
93                 *uid = pwd->pw_uid;
94                 talloc_free(ctx);
95                 return NT_STATUS_OK;
96         }
97
98         DEBUG(0,("No sAMAccountName for sid %s!?\n", sidstr));
99
100         talloc_free(ctx);
101         return NT_STATUS_ACCESS_DENIED;
102 }
103
104
105 /*
106   map a sid to a unix gid
107 */
108 static NTSTATUS sid_to_unixgid(struct ntvfs_module_context *ntvfs,
109                                struct smbsrv_request *req, struct dom_sid *sid, gid_t *gid)
110 {
111         struct unixuid_private *private = ntvfs->private_data;
112         const char *attrs[] = { "sAMAccountName", "UnixID", "UnixName", "sAMAccountType", NULL };
113         int ret;
114         const char *s;
115         void *ctx;
116         struct ldb_message **res;
117         const char *sidstr;
118
119         ctx = talloc(req, 0);
120         sidstr = dom_sid_string(ctx, sid);
121
122         ret = samdb_search(private->samctx, ctx, NULL, &res, attrs, "objectSid=%s", sidstr);
123         if (ret != 1) {
124                 DEBUG(2,("Unable to map sid %s to unix gid\n", sidstr));
125                 talloc_free(ctx);
126                 return NT_STATUS_ACCESS_DENIED;
127         }
128
129         /* make sure its not a user */
130         if (samdb_result_uint(res[0], "sAMAccountType", 0) == ATYPE_NORMAL_ACCOUNT) {
131                 DEBUG(0,("sid_to_unixgid: sid %s is a ATYPE_NORMAL_ACCOUNT\n", sidstr));
132                 talloc_free(ctx);
133                 return NT_STATUS_ACCESS_DENIED;
134         }
135
136         /* first try to get the gid directly */
137         s = samdb_result_string(res[0], "UnixID", NULL);
138         if (s != NULL) {
139                 *gid = strtoul(s, NULL, 0);
140                 talloc_free(ctx);
141                 return NT_STATUS_OK;
142         }
143
144         /* next try via the UnixName attribute */
145         s = samdb_result_string(res[0], "UnixName", NULL);
146         if (s != NULL) {
147                 struct group *grp = getgrnam(s);
148                 if (!grp) {
149                         DEBUG(0,("UnixName '%s' for sid %s does not exist as a local group\n", s, sidstr));
150                         talloc_free(ctx);
151                         return NT_STATUS_ACCESS_DENIED;
152                 }
153                 *gid = grp->gr_gid;
154                 talloc_free(ctx);
155                 return NT_STATUS_OK;
156         }
157
158         /* finally try via the sAMAccountName attribute */
159         s = samdb_result_string(res[0], "sAMAccountName", NULL);
160         if (s != NULL) {
161                 struct group *grp = getgrnam(s);
162                 if (!grp) {
163                         DEBUG(0,("sAMAccountName '%s' for sid %s does not exist as a local group\n", s, sidstr));
164                         talloc_free(ctx);
165                         return NT_STATUS_ACCESS_DENIED;
166                 }
167                 *gid = grp->gr_gid;
168                 talloc_free(ctx);
169                 return NT_STATUS_OK;
170         }
171
172         DEBUG(0,("No sAMAccountName for sid %s!?\n", sidstr));
173
174         talloc_free(ctx);
175         return NT_STATUS_ACCESS_DENIED;
176 }
177
178 struct unix_sec_ctx {
179         uid_t uid;
180         gid_t gid;
181         uint_t ngroups;
182         gid_t *groups;
183 };
184
185 /*
186   pull the current security context into a unix_sec_ctx
187 */
188 static struct unix_sec_ctx *save_unix_security(TALLOC_CTX *mem_ctx)
189 {
190         struct unix_sec_ctx *sec = talloc_p(mem_ctx, struct unix_sec_ctx);
191         if (sec == NULL) {
192                 return NULL;
193         }
194         sec->uid = geteuid();
195         sec->gid = getegid();
196         sec->ngroups = getgroups(0, NULL);
197         if (sec->ngroups == -1) {
198                 talloc_free(sec);
199                 return NULL;
200         }
201         sec->groups = talloc_array_p(sec, gid_t, sec->ngroups);
202         if (sec->groups == NULL) {
203                 talloc_free(sec);
204                 return NULL;
205         }
206
207         if (getgroups(sec->ngroups, sec->groups) != sec->ngroups) {
208                 talloc_free(sec);
209                 return NULL;
210         }
211
212         return sec;
213 }
214
215 /*
216   set the current security context from a unix_sec_ctx
217 */
218 static NTSTATUS set_unix_security(struct unix_sec_ctx *sec)
219 {
220         seteuid(0);
221
222         if (setgroups(sec->ngroups, sec->groups) != 0) {
223                 return NT_STATUS_ACCESS_DENIED;
224         }
225         if (setegid(sec->gid) != 0) {
226                 return NT_STATUS_ACCESS_DENIED;
227         }
228         if (seteuid(sec->uid) != 0) {
229                 return NT_STATUS_ACCESS_DENIED;
230         }
231         return NT_STATUS_OK;
232 }
233
234 /*
235   form a unix_sec_ctx from the current session info
236 */
237 static NTSTATUS authinfo_to_unix_security(struct ntvfs_module_context *ntvfs,
238                                           struct smbsrv_request *req,
239                                           struct auth_serversupplied_info *info,
240                                           struct unix_sec_ctx **sec)
241 {
242         int i;
243         NTSTATUS status;
244         *sec = talloc_p(req, struct unix_sec_ctx);
245
246         status = sid_to_unixuid(ntvfs, req, info->user_sid, &(*sec)->uid);
247         if (!NT_STATUS_IS_OK(status)) {
248                 return status;
249         }
250
251         status = sid_to_unixgid(ntvfs, req, info->primary_group_sid, &(*sec)->gid);
252         if (!NT_STATUS_IS_OK(status)) {
253                 return status;
254         }
255
256         (*sec)->ngroups = info->n_domain_groups;
257         (*sec)->groups = talloc_array_p(*sec, gid_t, (*sec)->ngroups);
258         if ((*sec)->groups == NULL) {
259                 return NT_STATUS_NO_MEMORY;
260         }
261
262         for (i=0;i<(*sec)->ngroups;i++) {
263                 status = sid_to_unixgid(ntvfs, req, info->domain_groups[i], &(*sec)->groups[i]);
264                 if (!NT_STATUS_IS_OK(status)) {
265                         return status;
266                 }
267         }
268
269         return NT_STATUS_OK;
270 }
271
272 /*
273   setup our unix security context according to the session authentication info
274 */
275 static NTSTATUS unixuid_setup_security(struct ntvfs_module_context *ntvfs,
276                                        struct smbsrv_request *req, struct unix_sec_ctx **sec)
277 {
278         struct auth_serversupplied_info *info = req->session->session_info->server_info;
279         void *ctx = talloc(req, 0);
280         struct unix_sec_ctx *newsec;
281         NTSTATUS status;
282
283         *sec = save_unix_security(req);
284         if (*sec == NULL) {
285                 return NT_STATUS_NO_MEMORY;
286         }
287
288         status = authinfo_to_unix_security(ntvfs, req, info, &newsec);
289         if (!NT_STATUS_IS_OK(status)) {
290                 talloc_free(ctx);
291                 return status;
292         }
293
294         status = set_unix_security(newsec);
295         if (!NT_STATUS_IS_OK(status)) {
296                 talloc_free(ctx);
297                 return status;
298         }
299
300         talloc_free(ctx);
301
302         return NT_STATUS_OK;
303 }
304
305 /*
306   this pass through macro operates on request contexts
307 */
308 #define PASS_THRU_REQ(ntvfs, req, op, args) do { \
309         NTSTATUS status2; \
310         struct unix_sec_ctx *sec; \
311         status = unixuid_setup_security(ntvfs, req, &sec); \
312         if (NT_STATUS_IS_OK(status)) status = ntvfs_next_##op args; \
313         status2 = set_unix_security(sec); \
314         if (!NT_STATUS_IS_OK(status2)) smb_panic("Unable to reset security context"); \
315 } while (0)
316
317
318
319 /*
320   connect to a share - used when a tree_connect operation comes in.
321 */
322 static NTSTATUS unixuid_connect(struct ntvfs_module_context *ntvfs,
323                                 struct smbsrv_request *req, const char *sharename)
324 {
325         struct unixuid_private *private;
326         NTSTATUS status;
327
328         private = talloc_p(req->tcon, struct unixuid_private);
329         if (!private) {
330                 return NT_STATUS_NO_MEMORY;
331         }
332
333         private->samctx = samdb_connect(private);
334         if (private->samctx == NULL) {
335                 return NT_STATUS_INTERNAL_DB_CORRUPTION;
336         }
337
338         ntvfs->private_data = private;
339
340         PASS_THRU_REQ(ntvfs, req, connect, (ntvfs, req, sharename));
341
342         return status;
343 }
344
345 /*
346   disconnect from a share
347 */
348 static NTSTATUS unixuid_disconnect(struct ntvfs_module_context *ntvfs,
349                                    struct smbsrv_tcon *tcon)
350 {
351         struct unixuid_private *private = ntvfs->private_data;
352         NTSTATUS status;
353
354         talloc_free(private);
355
356         status = ntvfs_next_disconnect(ntvfs, tcon);
357  
358         return status;
359 }
360
361
362 /*
363   delete a file
364 */
365 static NTSTATUS unixuid_unlink(struct ntvfs_module_context *ntvfs,
366                               struct smbsrv_request *req, struct smb_unlink *unl)
367 {
368         NTSTATUS status;
369
370         PASS_THRU_REQ(ntvfs, req, unlink, (ntvfs, req, unl));
371
372         return status;
373 }
374
375 /*
376   ioctl interface
377 */
378 static NTSTATUS unixuid_ioctl(struct ntvfs_module_context *ntvfs,
379                              struct smbsrv_request *req, union smb_ioctl *io)
380 {
381         NTSTATUS status;
382
383         PASS_THRU_REQ(ntvfs, req, ioctl, (ntvfs, req, io));
384
385         return status;
386 }
387
388 /*
389   check if a directory exists
390 */
391 static NTSTATUS unixuid_chkpath(struct ntvfs_module_context *ntvfs,
392                                struct smbsrv_request *req, struct smb_chkpath *cp)
393 {
394         NTSTATUS status;
395
396         PASS_THRU_REQ(ntvfs, req, chkpath, (ntvfs, req, cp));
397
398         return status;
399 }
400
401 /*
402   return info on a pathname
403 */
404 static NTSTATUS unixuid_qpathinfo(struct ntvfs_module_context *ntvfs,
405                                  struct smbsrv_request *req, union smb_fileinfo *info)
406 {
407         NTSTATUS status;
408
409         PASS_THRU_REQ(ntvfs, req, qpathinfo, (ntvfs, req, info));
410
411         return status;
412 }
413
414 /*
415   query info on a open file
416 */
417 static NTSTATUS unixuid_qfileinfo(struct ntvfs_module_context *ntvfs,
418                                  struct smbsrv_request *req, union smb_fileinfo *info)
419 {
420         NTSTATUS status;
421
422         PASS_THRU_REQ(ntvfs, req, qfileinfo, (ntvfs, req, info));
423
424         return status;
425 }
426
427
428 /*
429   set info on a pathname
430 */
431 static NTSTATUS unixuid_setpathinfo(struct ntvfs_module_context *ntvfs,
432                                    struct smbsrv_request *req, union smb_setfileinfo *st)
433 {
434         NTSTATUS status;
435
436         PASS_THRU_REQ(ntvfs, req, setpathinfo, (ntvfs, req, st));
437
438         return status;
439 }
440
441 /*
442   open a file
443 */
444 static NTSTATUS unixuid_open(struct ntvfs_module_context *ntvfs,
445                             struct smbsrv_request *req, union smb_open *io)
446 {
447         NTSTATUS status;
448
449         PASS_THRU_REQ(ntvfs, req, open, (ntvfs, req, io));
450
451         return status;
452 }
453
454 /*
455   create a directory
456 */
457 static NTSTATUS unixuid_mkdir(struct ntvfs_module_context *ntvfs,
458                              struct smbsrv_request *req, union smb_mkdir *md)
459 {
460         NTSTATUS status;
461
462         PASS_THRU_REQ(ntvfs, req, mkdir, (ntvfs, req, md));
463
464         return status;
465 }
466
467 /*
468   remove a directory
469 */
470 static NTSTATUS unixuid_rmdir(struct ntvfs_module_context *ntvfs,
471                              struct smbsrv_request *req, struct smb_rmdir *rd)
472 {
473         NTSTATUS status;
474
475         PASS_THRU_REQ(ntvfs, req, rmdir, (ntvfs, req, rd));
476
477         return status;
478 }
479
480 /*
481   rename a set of files
482 */
483 static NTSTATUS unixuid_rename(struct ntvfs_module_context *ntvfs,
484                               struct smbsrv_request *req, union smb_rename *ren)
485 {
486         NTSTATUS status;
487
488         PASS_THRU_REQ(ntvfs, req, rename, (ntvfs, req, ren));
489
490         return status;
491 }
492
493 /*
494   copy a set of files
495 */
496 static NTSTATUS unixuid_copy(struct ntvfs_module_context *ntvfs,
497                             struct smbsrv_request *req, struct smb_copy *cp)
498 {
499         NTSTATUS status;
500
501         PASS_THRU_REQ(ntvfs, req, copy, (ntvfs, req, cp));
502
503         return status;
504 }
505
506 /*
507   read from a file
508 */
509 static NTSTATUS unixuid_read(struct ntvfs_module_context *ntvfs,
510                             struct smbsrv_request *req, union smb_read *rd)
511 {
512         NTSTATUS status;
513
514         PASS_THRU_REQ(ntvfs, req, read, (ntvfs, req, rd));
515
516         return status;
517 }
518
519 /*
520   write to a file
521 */
522 static NTSTATUS unixuid_write(struct ntvfs_module_context *ntvfs,
523                              struct smbsrv_request *req, union smb_write *wr)
524 {
525         NTSTATUS status;
526
527         PASS_THRU_REQ(ntvfs, req, write, (ntvfs, req, wr));
528
529         return status;
530 }
531
532 /*
533   seek in a file
534 */
535 static NTSTATUS unixuid_seek(struct ntvfs_module_context *ntvfs,
536                             struct smbsrv_request *req, struct smb_seek *io)
537 {
538         NTSTATUS status;
539
540         PASS_THRU_REQ(ntvfs, req, seek, (ntvfs, req, io));
541
542         return status;
543 }
544
545 /*
546   flush a file
547 */
548 static NTSTATUS unixuid_flush(struct ntvfs_module_context *ntvfs,
549                              struct smbsrv_request *req, struct smb_flush *io)
550 {
551         NTSTATUS status;
552
553         PASS_THRU_REQ(ntvfs, req, flush, (ntvfs, req, io));
554
555         return status;
556 }
557
558 /*
559   close a file
560 */
561 static NTSTATUS unixuid_close(struct ntvfs_module_context *ntvfs,
562                              struct smbsrv_request *req, union smb_close *io)
563 {
564         NTSTATUS status;
565
566         PASS_THRU_REQ(ntvfs, req, close, (ntvfs, req, io));
567
568         return status;
569 }
570
571 /*
572   exit - closing files
573 */
574 static NTSTATUS unixuid_exit(struct ntvfs_module_context *ntvfs,
575                             struct smbsrv_request *req)
576 {
577         NTSTATUS status;
578
579         PASS_THRU_REQ(ntvfs, req, exit, (ntvfs, req));
580
581         return status;
582 }
583
584 /*
585   logoff - closing files
586 */
587 static NTSTATUS unixuid_logoff(struct ntvfs_module_context *ntvfs,
588                               struct smbsrv_request *req)
589 {
590         NTSTATUS status;
591
592         PASS_THRU_REQ(ntvfs, req, logoff, (ntvfs, req));
593
594         return status;
595 }
596
597 /*
598   lock a byte range
599 */
600 static NTSTATUS unixuid_lock(struct ntvfs_module_context *ntvfs,
601                             struct smbsrv_request *req, union smb_lock *lck)
602 {
603         NTSTATUS status;
604
605         PASS_THRU_REQ(ntvfs, req, lock, (ntvfs, req, lck));
606
607         return status;
608 }
609
610 /*
611   set info on a open file
612 */
613 static NTSTATUS unixuid_setfileinfo(struct ntvfs_module_context *ntvfs,
614                                    struct smbsrv_request *req, 
615                                    union smb_setfileinfo *info)
616 {
617         NTSTATUS status;
618
619         PASS_THRU_REQ(ntvfs, req, setfileinfo, (ntvfs, req, info));
620
621         return status;
622 }
623
624
625 /*
626   return filesystem space info
627 */
628 static NTSTATUS unixuid_fsinfo(struct ntvfs_module_context *ntvfs,
629                               struct smbsrv_request *req, union smb_fsinfo *fs)
630 {
631         NTSTATUS status;
632
633         PASS_THRU_REQ(ntvfs, req, fsinfo, (ntvfs, req, fs));
634
635         return status;
636 }
637
638 /*
639   return print queue info
640 */
641 static NTSTATUS unixuid_lpq(struct ntvfs_module_context *ntvfs,
642                            struct smbsrv_request *req, union smb_lpq *lpq)
643 {
644         NTSTATUS status;
645
646         PASS_THRU_REQ(ntvfs, req, lpq, (ntvfs, req, lpq));
647
648         return status;
649 }
650
651 /* 
652    list files in a directory matching a wildcard pattern
653 */
654 static NTSTATUS unixuid_search_first(struct ntvfs_module_context *ntvfs,
655                                     struct smbsrv_request *req, union smb_search_first *io, 
656                                     void *search_private, 
657                                     BOOL (*callback)(void *, union smb_search_data *))
658 {
659         NTSTATUS status;
660
661         PASS_THRU_REQ(ntvfs, req, search_first, (ntvfs, req, io, search_private, callback));
662
663         return status;
664 }
665
666 /* continue a search */
667 static NTSTATUS unixuid_search_next(struct ntvfs_module_context *ntvfs,
668                                    struct smbsrv_request *req, union smb_search_next *io, 
669                                    void *search_private, 
670                                    BOOL (*callback)(void *, union smb_search_data *))
671 {
672         NTSTATUS status;
673
674         PASS_THRU_REQ(ntvfs, req, search_next, (ntvfs, req, io, search_private, callback));
675
676         return status;
677 }
678
679 /* close a search */
680 static NTSTATUS unixuid_search_close(struct ntvfs_module_context *ntvfs,
681                                     struct smbsrv_request *req, union smb_search_close *io)
682 {
683         NTSTATUS status;
684
685         PASS_THRU_REQ(ntvfs, req, search_close, (ntvfs, req, io));
686
687         return status;
688 }
689
690 /* SMBtrans - not used on file shares */
691 static NTSTATUS unixuid_trans(struct ntvfs_module_context *ntvfs,
692                              struct smbsrv_request *req, struct smb_trans2 *trans2)
693 {
694         NTSTATUS status;
695
696         PASS_THRU_REQ(ntvfs, req, trans, (ntvfs, req, trans2));
697
698         return status;
699 }
700
701 /*
702   initialise the unixuid backend, registering ourselves with the ntvfs subsystem
703  */
704 NTSTATUS ntvfs_unixuid_init(void)
705 {
706         NTSTATUS ret;
707         struct ntvfs_ops ops;
708
709         ZERO_STRUCT(ops);
710
711         /* fill in the name and type */
712         ops.name = "unixuid";
713         ops.type = NTVFS_DISK;
714         
715         /* fill in all the operations */
716         ops.connect = unixuid_connect;
717         ops.disconnect = unixuid_disconnect;
718         ops.unlink = unixuid_unlink;
719         ops.chkpath = unixuid_chkpath;
720         ops.qpathinfo = unixuid_qpathinfo;
721         ops.setpathinfo = unixuid_setpathinfo;
722         ops.open = unixuid_open;
723         ops.mkdir = unixuid_mkdir;
724         ops.rmdir = unixuid_rmdir;
725         ops.rename = unixuid_rename;
726         ops.copy = unixuid_copy;
727         ops.ioctl = unixuid_ioctl;
728         ops.read = unixuid_read;
729         ops.write = unixuid_write;
730         ops.seek = unixuid_seek;
731         ops.flush = unixuid_flush;      
732         ops.close = unixuid_close;
733         ops.exit = unixuid_exit;
734         ops.lock = unixuid_lock;
735         ops.setfileinfo = unixuid_setfileinfo;
736         ops.qfileinfo = unixuid_qfileinfo;
737         ops.fsinfo = unixuid_fsinfo;
738         ops.lpq = unixuid_lpq;
739         ops.search_first = unixuid_search_first;
740         ops.search_next = unixuid_search_next;
741         ops.search_close = unixuid_search_close;
742         ops.trans = unixuid_trans;
743         ops.logoff = unixuid_logoff;
744
745         /* register ourselves with the NTVFS subsystem. */
746         ret = register_backend("ntvfs", &ops);
747
748         if (!NT_STATUS_IS_OK(ret)) {
749                 DEBUG(0,("Failed to register unixuid backend!\n"));
750         }
751         
752         return ret;
753 }