r5501: check the return of talloc with the NT_STATUS_HAVE_NO_MEMORY()
[ira/wip.git] / source4 / ntvfs / ipc / vfs_ipc.c
1 /* 
2    Unix SMB/CIFS implementation.
3    default IPC$ NTVFS backend
4
5    Copyright (C) Andrew Tridgell 2003
6    Copyright (C) Stefan (metze) Metzmacher 2004-2005
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 2 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, write to the Free Software
20    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 */
22 /*
23   this implements the IPC$ backend, called by the NTVFS subsystem to
24   handle requests on IPC$ shares
25 */
26
27
28 #include "includes.h"
29 #include "system/filesys.h"
30 #include "dlinklist.h"
31 #include "smb_server/smb_server.h"
32
33 #define IPC_BASE_FNUM 0x400
34
35 /* this is the private structure used to keep the state of an open
36    ipc$ connection. It needs to keep information about all open
37    pipes */
38 struct ipc_private {
39         struct idr_context *idtree_fnum;
40
41         struct dcesrv_context *dcesrv;
42
43         /* a list of open pipes */
44         struct pipe_state {
45                 struct pipe_state *next, *prev;
46                 struct ipc_private *private;
47                 const char *pipe_name;
48                 uint16_t fnum;
49                 struct dcesrv_connection *dce_conn;
50                 uint16_t ipc_state;
51                 /* we need to remember the session it was opened on,
52                    as it is illegal to operate on someone elses fnum */
53                 struct smbsrv_session *session;
54
55                 /* we need to remember the client pid that 
56                    opened the file so SMBexit works */
57                 uint16_t smbpid;
58         } *pipe_list;
59
60 };
61
62
63 /*
64   find a open pipe give a file descriptor
65 */
66 static struct pipe_state *pipe_state_find(struct ipc_private *private, uint16_t fnum)
67 {
68         return idr_find(private->idtree_fnum, fnum);
69 }
70
71
72 /*
73   connect to a share - always works 
74 */
75 static NTSTATUS ipc_connect(struct ntvfs_module_context *ntvfs,
76                             struct smbsrv_request *req, const char *sharename)
77 {
78         NTSTATUS status;
79         struct smbsrv_tcon *tcon = req->tcon;
80         struct ipc_private *private;
81
82         tcon->fs_type = talloc_strdup(tcon, "IPC");
83         NT_STATUS_HAVE_NO_MEMORY(tcon->fs_type);
84
85         tcon->dev_type = talloc_strdup(tcon, "IPC");
86         NT_STATUS_HAVE_NO_MEMORY(tcon->dev_type);
87
88         /* prepare the private state for this connection */
89         private = talloc(tcon, struct ipc_private);
90         NT_STATUS_HAVE_NO_MEMORY(private);
91
92         ntvfs->private_data = private;
93
94         private->pipe_list = NULL;
95
96         private->idtree_fnum = idr_init(private);
97         NT_STATUS_HAVE_NO_MEMORY(private->idtree_fnum);
98
99         /* setup the DCERPC server subsystem */
100         status = dcesrv_init_ipc_context(private, &private->dcesrv);
101         NT_STATUS_NOT_OK_RETURN(status);
102
103         return NT_STATUS_OK;
104 }
105
106 /*
107   disconnect from a share
108 */
109 static NTSTATUS ipc_disconnect(struct ntvfs_module_context *ntvfs,
110                                struct smbsrv_tcon *tcon)
111 {
112         return NT_STATUS_OK;
113 }
114
115 /*
116   delete a file
117 */
118 static NTSTATUS ipc_unlink(struct ntvfs_module_context *ntvfs,
119                            struct smbsrv_request *req, struct smb_unlink *unl)
120 {
121         return NT_STATUS_ACCESS_DENIED;
122 }
123
124
125 /*
126   ioctl interface - we don't do any
127 */
128 static NTSTATUS ipc_ioctl(struct ntvfs_module_context *ntvfs,
129                           struct smbsrv_request *req, union smb_ioctl *io)
130 {
131         return NT_STATUS_ACCESS_DENIED;
132 }
133
134 /*
135   check if a directory exists
136 */
137 static NTSTATUS ipc_chkpath(struct ntvfs_module_context *ntvfs,
138                             struct smbsrv_request *req, struct smb_chkpath *cp)
139 {
140         return NT_STATUS_ACCESS_DENIED;
141 }
142
143 /*
144   return info on a pathname
145 */
146 static NTSTATUS ipc_qpathinfo(struct ntvfs_module_context *ntvfs,
147                               struct smbsrv_request *req, union smb_fileinfo *info)
148 {
149         return NT_STATUS_ACCESS_DENIED;
150 }
151
152 /*
153   set info on a pathname
154 */
155 static NTSTATUS ipc_setpathinfo(struct ntvfs_module_context *ntvfs,
156                                 struct smbsrv_request *req, union smb_setfileinfo *st)
157 {
158         return NT_STATUS_ACCESS_DENIED;
159 }
160
161
162 /*
163   destroy a open pipe structure
164 */
165 static int ipc_fd_destructor(void *ptr)
166 {
167         struct pipe_state *p = ptr;
168         idr_remove(p->private->idtree_fnum, p->fnum);
169         DLIST_REMOVE(p->private->pipe_list, p);
170         return 0;
171 }
172
173
174 /*
175   open a file backend - used for MSRPC pipes
176 */
177 static NTSTATUS ipc_open_generic(struct ntvfs_module_context *ntvfs,
178                                  struct smbsrv_request *req, const char *fname, 
179                                  struct pipe_state **ps)
180 {
181         struct pipe_state *p;
182         NTSTATUS status;
183         struct dcerpc_binding ep_description;
184         struct ipc_private *private = ntvfs->private_data;
185         int fnum;
186         struct stream_connection *srv_conn = req->smb_conn->connection;
187
188         if (!req->session || !req->session->session_info) {
189                 return NT_STATUS_ACCESS_DENIED;
190         }
191
192         p = talloc(req, struct pipe_state);
193         NT_STATUS_HAVE_NO_MEMORY(p);
194
195         while (fname[0] == '\\') fname++;
196
197         p->pipe_name = talloc_asprintf(p, "\\pipe\\%s", fname);
198         NT_STATUS_HAVE_NO_MEMORY(p->pipe_name);
199
200         fnum = idr_get_new_above(private->idtree_fnum, p, IPC_BASE_FNUM, UINT16_MAX);
201         if (fnum == -1) {
202                 return NT_STATUS_TOO_MANY_OPENED_FILES;
203         }
204
205         p->fnum = fnum;
206         p->ipc_state = 0x5ff;
207
208         /*
209           we're all set, now ask the dcerpc server subsystem to open the 
210           endpoint. At this stage the pipe isn't bound, so we don't
211           know what interface the user actually wants, just that they want
212           one of the interfaces attached to this pipe endpoint.
213         */
214         ep_description.transport = NCACN_NP;
215         ep_description.endpoint = p->pipe_name;
216
217         /* The session info is refcount-increased in the 
218          * dcesrv_endpoint_search_connect() function
219          */
220         status = dcesrv_endpoint_search_connect(private->dcesrv,
221                                                 p,
222                                                 &ep_description, 
223                                                 req->session->session_info,
224                                                 srv_conn,
225                                                 &p->dce_conn);
226         if (!NT_STATUS_IS_OK(status)) {
227                 idr_remove(private->idtree_fnum, p->fnum);
228                 return status;
229         }
230
231         DLIST_ADD(private->pipe_list, p);
232
233         p->smbpid = req->smbpid;
234         p->session = req->session;
235         p->private = private;
236
237         *ps = p;
238
239         talloc_steal(private, p);
240
241         talloc_set_destructor(p, ipc_fd_destructor);
242
243         return NT_STATUS_OK;
244 }
245
246 /*
247   open a file with ntcreatex - used for MSRPC pipes
248 */
249 static NTSTATUS ipc_open_ntcreatex(struct ntvfs_module_context *ntvfs,
250                                    struct smbsrv_request *req, union smb_open *oi)
251 {
252         struct pipe_state *p;
253         NTSTATUS status;
254
255         status = ipc_open_generic(ntvfs, req, oi->ntcreatex.in.fname, &p);
256         if (!NT_STATUS_IS_OK(status)) {
257                 return status;
258         }
259
260         ZERO_STRUCT(oi->ntcreatex.out);
261         oi->ntcreatex.out.fnum = p->fnum;
262         oi->ntcreatex.out.ipc_state = p->ipc_state;
263         oi->ntcreatex.out.file_type = FILE_TYPE_MESSAGE_MODE_PIPE;
264
265         return status;
266 }
267
268 /*
269   open a file with openx - used for MSRPC pipes
270 */
271 static NTSTATUS ipc_open_openx(struct ntvfs_module_context *ntvfs,
272                                struct smbsrv_request *req, union smb_open *oi)
273 {
274         struct pipe_state *p;
275         NTSTATUS status;
276         const char *fname = oi->openx.in.fname;
277
278         status = ipc_open_generic(ntvfs, req, fname, &p);
279         if (!NT_STATUS_IS_OK(status)) {
280                 return status;
281         }
282
283         ZERO_STRUCT(oi->openx.out);
284         oi->openx.out.fnum = p->fnum;
285         oi->openx.out.ftype = 2;
286         oi->openx.out.devstate = p->ipc_state;
287         
288         return status;
289 }
290
291 /*
292   open a file - used for MSRPC pipes
293 */
294 static NTSTATUS ipc_open(struct ntvfs_module_context *ntvfs,
295                                 struct smbsrv_request *req, union smb_open *oi)
296 {
297         NTSTATUS status;
298
299         switch (oi->generic.level) {
300         case RAW_OPEN_NTCREATEX:
301                 status = ipc_open_ntcreatex(ntvfs, req, oi);
302                 break;
303         case RAW_OPEN_OPENX:
304                 status = ipc_open_openx(ntvfs, req, oi);
305                 break;
306         default:
307                 status = NT_STATUS_NOT_SUPPORTED;
308                 break;
309         }
310
311         return status;
312 }
313
314 /*
315   create a directory
316 */
317 static NTSTATUS ipc_mkdir(struct ntvfs_module_context *ntvfs,
318                           struct smbsrv_request *req, union smb_mkdir *md)
319 {
320         return NT_STATUS_ACCESS_DENIED;
321 }
322
323 /*
324   remove a directory
325 */
326 static NTSTATUS ipc_rmdir(struct ntvfs_module_context *ntvfs,
327                           struct smbsrv_request *req, struct smb_rmdir *rd)
328 {
329         return NT_STATUS_ACCESS_DENIED;
330 }
331
332 /*
333   rename a set of files
334 */
335 static NTSTATUS ipc_rename(struct ntvfs_module_context *ntvfs,
336                            struct smbsrv_request *req, union smb_rename *ren)
337 {
338         return NT_STATUS_ACCESS_DENIED;
339 }
340
341 /*
342   copy a set of files
343 */
344 static NTSTATUS ipc_copy(struct ntvfs_module_context *ntvfs,
345                          struct smbsrv_request *req, struct smb_copy *cp)
346 {
347         return NT_STATUS_ACCESS_DENIED;
348 }
349
350 /*
351   read from a file
352 */
353 static NTSTATUS ipc_read(struct ntvfs_module_context *ntvfs,
354                          struct smbsrv_request *req, union smb_read *rd)
355 {
356         struct ipc_private *private = ntvfs->private_data;
357         DATA_BLOB data;
358         uint16_t fnum;
359         struct pipe_state *p;
360         NTSTATUS status;
361
362         if (rd->generic.level != RAW_READ_GENERIC) {
363                 return ntvfs_map_read(req, rd, ntvfs);
364         }
365
366         fnum = rd->readx.in.fnum;
367
368         p = pipe_state_find(private, fnum);
369         if (!p) {
370                 return NT_STATUS_INVALID_HANDLE;
371         }
372
373         data.length = rd->readx.in.maxcnt;
374         data.data = rd->readx.out.data;
375         if (data.length > UINT16_MAX) {
376                 data.length = 0;
377         }
378
379         if (data.length != 0) {
380                 status = dcesrv_output_blob(p->dce_conn, &data);
381                 if (NT_STATUS_IS_ERR(status)) {
382                         return status;
383                 }
384         }
385
386         rd->readx.out.remaining = 0;
387         rd->readx.out.compaction_mode = 0;
388         rd->readx.out.nread = data.length;
389
390         return status;
391 }
392
393 /*
394   write to a file
395 */
396 static NTSTATUS ipc_write(struct ntvfs_module_context *ntvfs,
397                           struct smbsrv_request *req, union smb_write *wr)
398 {
399         struct ipc_private *private = ntvfs->private_data;
400         DATA_BLOB data;
401         uint16_t fnum;
402         struct pipe_state *p;
403         NTSTATUS status;
404
405         if (wr->generic.level != RAW_WRITE_GENERIC) {
406                 return ntvfs_map_write(req, wr, ntvfs);
407         }
408
409         fnum = wr->writex.in.fnum;
410         data.data = discard_const_p(void, wr->writex.in.data);
411         data.length = wr->writex.in.count;
412
413         p = pipe_state_find(private, fnum);
414         if (!p) {
415                 return NT_STATUS_INVALID_HANDLE;
416         }
417
418         status = dcesrv_input(p->dce_conn, &data);
419         if (!NT_STATUS_IS_OK(status)) {
420                 return status;
421         }
422
423         wr->writex.out.nwritten = data.length;
424         wr->writex.out.remaining = 0;
425
426         return NT_STATUS_OK;
427 }
428
429 /*
430   seek in a file
431 */
432 static NTSTATUS ipc_seek(struct ntvfs_module_context *ntvfs,
433                          struct smbsrv_request *req, struct smb_seek *io)
434 {
435         return NT_STATUS_ACCESS_DENIED;
436 }
437
438 /*
439   flush a file
440 */
441 static NTSTATUS ipc_flush(struct ntvfs_module_context *ntvfs,
442                           struct smbsrv_request *req, struct smb_flush *io)
443 {
444         return NT_STATUS_ACCESS_DENIED;
445 }
446
447 /*
448   close a file
449 */
450 static NTSTATUS ipc_close(struct ntvfs_module_context *ntvfs,
451                           struct smbsrv_request *req, union smb_close *io)
452 {
453         struct ipc_private *private = ntvfs->private_data;
454         struct pipe_state *p;
455
456         if (io->generic.level != RAW_CLOSE_CLOSE) {
457                 return ntvfs_map_close(req, io, ntvfs);
458         }
459
460         p = pipe_state_find(private, io->close.in.fnum);
461         if (!p) {
462                 return NT_STATUS_INVALID_HANDLE;
463         }
464
465         talloc_free(p);
466
467         return NT_STATUS_OK;
468 }
469
470 /*
471   exit - closing files
472 */
473 static NTSTATUS ipc_exit(struct ntvfs_module_context *ntvfs,
474                          struct smbsrv_request *req)
475 {
476         struct ipc_private *private = ntvfs->private_data;
477         struct pipe_state *p, *next;
478         
479         for (p=private->pipe_list; p; p=next) {
480                 next = p->next;
481                 if (p->smbpid == req->smbpid) {
482                         talloc_free(p);
483                 }
484         }
485
486         return NT_STATUS_OK;
487 }
488
489 /*
490   logoff - closing files open by the user
491 */
492 static NTSTATUS ipc_logoff(struct ntvfs_module_context *ntvfs,
493                            struct smbsrv_request *req)
494 {
495         struct ipc_private *private = ntvfs->private_data;
496         struct pipe_state *p, *next;
497         
498         for (p=private->pipe_list; p; p=next) {
499                 next = p->next;
500                 if (p->session == req->session) {
501                         talloc_free(p);
502                 }
503         }
504
505         return NT_STATUS_OK;
506 }
507
508 /*
509   setup for an async call
510 */
511 static NTSTATUS ipc_async_setup(struct ntvfs_module_context *ntvfs,
512                                 struct smbsrv_request *req,
513                                 void *private)
514 {
515         return NT_STATUS_OK;
516 }
517
518 /*
519   cancel an async call
520 */
521 static NTSTATUS ipc_cancel(struct ntvfs_module_context *ntvfs,
522                            struct smbsrv_request *req)
523 {
524         return NT_STATUS_UNSUCCESSFUL;
525 }
526
527 /*
528   lock a byte range
529 */
530 static NTSTATUS ipc_lock(struct ntvfs_module_context *ntvfs,
531                          struct smbsrv_request *req, union smb_lock *lck)
532 {
533         return NT_STATUS_ACCESS_DENIED;
534 }
535
536 /*
537   set info on a open file
538 */
539 static NTSTATUS ipc_setfileinfo(struct ntvfs_module_context *ntvfs,
540                                 struct smbsrv_request *req, union smb_setfileinfo *info)
541 {
542         return NT_STATUS_ACCESS_DENIED;
543 }
544
545 /*
546   query info on a open file
547 */
548 static NTSTATUS ipc_qfileinfo(struct ntvfs_module_context *ntvfs,
549                               struct smbsrv_request *req, union smb_fileinfo *info)
550 {
551         return NT_STATUS_ACCESS_DENIED;
552 }
553
554
555 /*
556   return filesystem info
557 */
558 static NTSTATUS ipc_fsinfo(struct ntvfs_module_context *ntvfs,
559                            struct smbsrv_request *req, union smb_fsinfo *fs)
560 {
561         return NT_STATUS_ACCESS_DENIED;
562 }
563
564 /*
565   return print queue info
566 */
567 static NTSTATUS ipc_lpq(struct ntvfs_module_context *ntvfs,
568                         struct smbsrv_request *req, union smb_lpq *lpq)
569 {
570         return NT_STATUS_ACCESS_DENIED;
571 }
572
573 /* 
574    list files in a directory matching a wildcard pattern
575 */
576 static NTSTATUS ipc_search_first(struct ntvfs_module_context *ntvfs,
577                           struct smbsrv_request *req, union smb_search_first *io,
578                           void *search_private, 
579                           BOOL (*callback)(void *, union smb_search_data *))
580 {
581         return NT_STATUS_ACCESS_DENIED;
582 }
583
584 /* 
585    continue listing files in a directory 
586 */
587 static NTSTATUS ipc_search_next(struct ntvfs_module_context *ntvfs,
588                          struct smbsrv_request *req, union smb_search_next *io,
589                          void *search_private, 
590                          BOOL (*callback)(void *, union smb_search_data *))
591 {
592         return NT_STATUS_ACCESS_DENIED;
593 }
594
595 /* 
596    end listing files in a directory 
597 */
598 static NTSTATUS ipc_search_close(struct ntvfs_module_context *ntvfs,
599                           struct smbsrv_request *req, union smb_search_close *io)
600 {
601         return NT_STATUS_ACCESS_DENIED;
602 }
603
604
605 /* SMBtrans - handle a DCERPC command */
606 static NTSTATUS ipc_dcerpc_cmd(struct ntvfs_module_context *ntvfs,
607                                struct smbsrv_request *req, struct smb_trans2 *trans)
608 {
609         struct pipe_state *p;
610         struct ipc_private *private = ntvfs->private_data;
611         NTSTATUS status;
612
613         /* the fnum is in setup[1] */
614         p = pipe_state_find(private, trans->in.setup[1]);
615         if (!p) {
616                 return NT_STATUS_INVALID_HANDLE;
617         }
618
619         trans->out.data = data_blob_talloc(req, NULL, trans->in.max_data);
620         if (!trans->out.data.data) {
621                 return NT_STATUS_NO_MEMORY;
622         }
623
624         /* pass the data to the dcerpc server. Note that we don't
625            expect this to fail, and things like NDR faults are not
626            reported at this stage. Those sorts of errors happen in the
627            dcesrv_output stage */
628         status = dcesrv_input(p->dce_conn, &trans->in.data);
629         if (!NT_STATUS_IS_OK(status)) {
630                 return status;
631         }
632
633         /*
634           now ask the dcerpc system for some output. This doesn't yet handle
635           async calls. Again, we only expect NT_STATUS_OK. If the call fails then
636           the error is encoded at the dcerpc level
637         */
638         status = dcesrv_output_blob(p->dce_conn, &trans->out.data);
639         if (NT_STATUS_IS_ERR(status)) {
640                 return status;
641         }
642
643         trans->out.setup_count = 0;
644         trans->out.setup = NULL;
645         trans->out.params = data_blob(NULL, 0);
646
647         return status;
648 }
649
650
651 /* SMBtrans - set named pipe state */
652 static NTSTATUS ipc_set_nm_pipe_state(struct ntvfs_module_context *ntvfs,
653                                 struct smbsrv_request *req, struct smb_trans2 *trans)
654 {
655         struct ipc_private *private = ntvfs->private_data;
656         struct pipe_state *p;
657
658         /* the fnum is in setup[1] */
659         p = pipe_state_find(private, trans->in.setup[1]);
660         if (!p) {
661                 return NT_STATUS_INVALID_HANDLE;
662         }
663
664         if (trans->in.params.length != 2) {
665                 return NT_STATUS_INVALID_PARAMETER;
666         }
667         p->ipc_state = SVAL(trans->in.params.data, 0);
668
669         trans->out.setup_count = 0;
670         trans->out.setup = NULL;
671         trans->out.params = data_blob(NULL, 0);
672         trans->out.data = data_blob(NULL, 0);
673
674         return NT_STATUS_OK;
675 }
676
677
678 /* SMBtrans - used to provide access to SMB pipes */
679 static NTSTATUS ipc_trans(struct ntvfs_module_context *ntvfs,
680                                 struct smbsrv_request *req, struct smb_trans2 *trans)
681 {
682         NTSTATUS status;
683
684         if (strequal(trans->in.trans_name, "\\PIPE\\LANMAN"))
685                 return ipc_rap_call(req, trans);
686
687         if (trans->in.setup_count != 2) {
688                 return NT_STATUS_INVALID_PARAMETER;
689         }
690
691         switch (trans->in.setup[0]) {
692         case TRANSACT_SETNAMEDPIPEHANDLESTATE:
693                 status = ipc_set_nm_pipe_state(ntvfs, req, trans);
694                 break;
695         case TRANSACT_DCERPCCMD:
696                 status = ipc_dcerpc_cmd(ntvfs, req, trans);
697                 break;
698         default:
699                 status = NT_STATUS_INVALID_PARAMETER;
700                 break;
701         }
702
703         return status;
704 }
705
706
707
708 /*
709   initialialise the IPC backend, registering ourselves with the ntvfs subsystem
710  */
711 NTSTATUS ntvfs_ipc_init(void)
712 {
713         NTSTATUS ret;
714         struct ntvfs_ops ops;
715
716         ZERO_STRUCT(ops);
717         
718         /* fill in the name and type */
719         ops.name = "default";
720         ops.type = NTVFS_IPC;
721
722         /* fill in all the operations */
723         ops.connect = ipc_connect;
724         ops.disconnect = ipc_disconnect;
725         ops.unlink = ipc_unlink;
726         ops.chkpath = ipc_chkpath;
727         ops.qpathinfo = ipc_qpathinfo;
728         ops.setpathinfo = ipc_setpathinfo;
729         ops.openfile = ipc_open;
730         ops.mkdir = ipc_mkdir;
731         ops.rmdir = ipc_rmdir;
732         ops.rename = ipc_rename;
733         ops.copy = ipc_copy;
734         ops.ioctl = ipc_ioctl;
735         ops.read = ipc_read;
736         ops.write = ipc_write;
737         ops.seek = ipc_seek;
738         ops.flush = ipc_flush;  
739         ops.close = ipc_close;
740         ops.exit = ipc_exit;
741         ops.lock = ipc_lock;
742         ops.setfileinfo = ipc_setfileinfo;
743         ops.qfileinfo = ipc_qfileinfo;
744         ops.fsinfo = ipc_fsinfo;
745         ops.lpq = ipc_lpq;
746         ops.search_first = ipc_search_first;
747         ops.search_next = ipc_search_next;
748         ops.search_close = ipc_search_close;
749         ops.trans = ipc_trans;
750         ops.logoff = ipc_logoff;
751         ops.async_setup = ipc_async_setup;
752         ops.cancel = ipc_cancel;
753
754         /* register ourselves with the NTVFS subsystem. */
755         ret = ntvfs_register(&ops);
756
757         if (!NT_STATUS_IS_OK(ret)) {
758                 DEBUG(0,("Failed to register IPC backend!\n"));
759                 return ret;
760         }
761
762         return ret;
763 }