1 /* GNU Mailutils -- a suite of utilities for electronic mail
2    Copyright (C) 2009-2021 Free Software Foundation, Inc.
3 
4    This library is free software; you can redistribute it and/or modify
5    it under the terms of the GNU Lesser General Public License as published by
6    the Free Software Foundation; either version 3, or (at your option)
7    any later version.
8 
9    This library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU Lesser General Public License for more details.
13 
14    You should have received a copy of the GNU Lesser General Public License
15    along with GNU Mailutils.  If not, see <http://www.gnu.org/licenses/>. */
16 
17 #ifdef HAVE_CONFIG_H
18 # include <config.h>
19 #endif
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <sys/wait.h>
23 #include <string.h>
24 #include <stdlib.h>
25 #include <unistd.h>
26 #include <fcntl.h>
27 #include <syslog.h>
28 
29 #include <mailutils/types.h>
30 #include <mailutils/alloc.h>
31 #include <mailutils/argcv.h>
32 #include <mailutils/wordsplit.h>
33 #include <mailutils/error.h>
34 #include <mailutils/errno.h>
35 #include <mailutils/nls.h>
36 #include <mailutils/stream.h>
37 #include <mailutils/sys/stream.h>
38 #include <mailutils/sys/prog_stream.h>
39 #include <mailutils/util.h>
40 
41 static mu_list_t prog_stream_list;
42 
43 static int
_prog_stream_register(struct _mu_prog_stream * stream)44 _prog_stream_register (struct _mu_prog_stream *stream)
45 {
46   if (!prog_stream_list)
47     {
48       int rc = mu_list_create (&prog_stream_list);
49       if (rc)
50 	return rc;
51     }
52   return mu_list_append (prog_stream_list, stream);
53 }
54 
55 static void
_prog_stream_unregister(struct _mu_prog_stream * stream)56 _prog_stream_unregister (struct _mu_prog_stream *stream)
57 {
58   mu_list_remove (prog_stream_list, stream);
59 }
60 
61 
62 
63 #define REDIRECT_STDIN_P(f) ((f) & MU_STREAM_WRITE)
64 #define REDIRECT_STDOUT_P(f) ((f) & MU_STREAM_READ)
65 
66 #ifdef RLIMIT_AS
67 # define _MU_RLIMIT_AS_FLAG MU_PROG_HINT_LIMIT(MU_PROG_LIMIT_AS)
68 #else
69 # define _MU_RLIMIT_AS_FLAG 0
70 # define RLIMIT_AS 0
71 #endif
72 
73 #ifdef RLIMIT_CPU
74 # define _MU_RLIMIT_CPU_FLAG MU_PROG_HINT_LIMIT(MU_PROG_LIMIT_CPU)
75 #else
76 # define _MU_RLIMIT_CPU_FLAG 0
77 # define RLIMIT_CPU 0
78 #endif
79 
80 #ifdef RLIMIT_DATA
81 # define _MU_RLIMIT_DATA_FLAG MU_PROG_HINT_LIMIT(MU_PROG_LIMIT_DATA)
82 #else
83 # define _MU_RLIMIT_DATA_FLAG 0
84 # define RLIMIT_DATA 0
85 #endif
86 
87 #ifdef RLIMIT_FSIZE
88 # define _MU_RLIMIT_FSIZE_FLAG MU_PROG_HINT_LIMIT(MU_PROG_LIMIT_FSIZE)
89 #else
90 # define _MU_RLIMIT_FSIZE_FLAG 0
91 # define RLIMIT_FSIZE 0
92 #endif
93 
94 #ifdef RLIMIT_NPROC
95 # define _MU_RLIMIT_NPROC_FLAG MU_PROG_HINT_LIMIT(MU_PROG_LIMIT_NPROC)
96 #else
97 # define _MU_RLIMIT_NPROC_FLAG 0
98 # define RLIMIT_NPROC 0
99 #endif
100 
101 #ifdef RLIMIT_CORE
102 # define _MU_RLIMIT_CORE_FLAG MU_PROG_HINT_LIMIT(MU_PROG_LIMIT_CORE)
103 #else
104 # define _MU_RLIMIT_CORE_FLAG 0
105 # define RLIMIT_CORE 0
106 #endif
107 
108 #ifdef RLIMIT_MEMLOCK
109 # define _MU_RLIMIT_MEMLOCK_FLAG MU_PROG_HINT_LIMIT(MU_PROG_LIMIT_MEMLOCK)
110 #else
111 # define _MU_RLIMIT_MEMLOCK_FLAG 0
112 # define RLIMIT_MEMLOCK 0
113 #endif
114 
115 #ifdef RLIMIT_NOFILE
116 # define _MU_RLIMIT_NOFILE_FLAG MU_PROG_HINT_LIMIT(MU_PROG_LIMIT_NOFILE)
117 #else
118 # define _MU_RLIMIT_NOFILE_FLAG 0
119 # define RLIMIT_NOFILE 0
120 #endif
121 
122 #ifdef RLIMIT_RSS
123 # define _MU_RLIMIT_RSS_FLAG MU_PROG_HINT_LIMIT(MU_PROG_LIMIT_RSS)
124 #else
125 # define _MU_RLIMIT_RSS_FLAG 0
126 # define RLIMIT_RSS 0
127 #endif
128 
129 #ifdef RLIMIT_STACK
130 # define _MU_RLIMIT_STACK MU_PROG_HINT_LIMIT(MU_PROG_LIMIT_STACK)
131 #else
132 # define _MU_RLIMIT_STACK 0
133 # define RLIMIT_STACK 0
134 #endif
135 
136 #define _MU_PROG_AVAILABLE_LIMITS \
137   (_MU_RLIMIT_AS_FLAG |		  \
138    _MU_RLIMIT_CPU_FLAG |	  \
139    _MU_RLIMIT_DATA_FLAG |	  \
140    _MU_RLIMIT_FSIZE_FLAG |	  \
141    _MU_RLIMIT_NPROC_FLAG |	  \
142    _MU_RLIMIT_CORE_FLAG |	  \
143    _MU_RLIMIT_MEMLOCK_FLAG |	  \
144    _MU_RLIMIT_NOFILE_FLAG |	  \
145    _MU_RLIMIT_RSS_FLAG |	  \
146    _MU_RLIMIT_STACK)
147 
148 int _mu_prog_limit_flags = _MU_PROG_AVAILABLE_LIMITS;
149 
150 int _mu_prog_limit_codes[_MU_PROG_LIMIT_MAX] = {
151   RLIMIT_AS,
152   RLIMIT_CPU,
153   RLIMIT_DATA,
154   RLIMIT_FSIZE,
155   RLIMIT_NPROC,
156   RLIMIT_CORE,
157   RLIMIT_MEMLOCK,
158   RLIMIT_NOFILE,
159   RLIMIT_RSS,
160   RLIMIT_STACK
161 };
162 
163 static int
start_program_filter(int * p,struct _mu_prog_stream * fs,int flags)164 start_program_filter (int *p, struct _mu_prog_stream *fs, int flags)
165 {
166   int rightp[2], leftp[2];
167   int i;
168   int rc = 0;
169 
170   if (REDIRECT_STDIN_P (flags))
171     pipe (leftp);
172   if (REDIRECT_STDOUT_P (flags))
173     pipe (rightp);
174 
175   switch (fs->pid = fork ())
176     {
177       /* The child branch.  */
178     case 0:
179       /* attach the pipes */
180 
181       /* Right-end */
182       if (REDIRECT_STDOUT_P (flags))
183 	{
184 	  if (rightp[1] != 1)
185 	    {
186 	      close (1);
187 	      dup2 (rightp[1], 1);
188 	    }
189 	  close (rightp[0]);
190 	}
191 
192       /* Left-end */
193       if (REDIRECT_STDIN_P (flags))
194 	{
195 	  if (leftp[0] != 0)
196 	    {
197 	      close (0);
198 	      dup2 (leftp[0], 0);
199 	    }
200 	  close (leftp[1]);
201 	}
202 
203       if (fs->hint_flags & MU_PROG_HINT_ERRTOOUT)
204 	dup2 (1, 2);
205 
206       if (fs->hint_flags & MU_PROG_HINT_WORKDIR)
207 	{
208 	  if (chdir (fs->hints.mu_prog_workdir))
209 	    {
210 	      mu_error (_("cannot change to %s: %s"),
211 			fs->hints.mu_prog_workdir, mu_strerror (errno));
212 	      if (!(fs->hint_flags & MU_PROG_HINT_IGNOREFAIL))
213 		_exit (127);
214 	    }
215 	}
216 
217       if (fs->hint_flags & MU_PROG_HINT_UID)
218 	{
219 	  if (mu_set_user_privileges (fs->hints.mu_prog_uid,
220 				      fs->hints.mu_prog_gidv,
221 				      fs->hints.mu_prog_gidc)
222 	      && !(fs->hint_flags & MU_PROG_HINT_IGNOREFAIL))
223 	    _exit (127);
224 	}
225 
226       for (i = 0; i < _MU_PROG_LIMIT_MAX; i++)
227 	{
228 	  if (MU_PROG_HINT_LIMIT(i) & fs->hint_flags)
229 	    {
230 	      struct rlimit rlim;
231 
232 	      rlim.rlim_cur = rlim.rlim_max = fs->hints.mu_prog_limit[i];
233 	      if (setrlimit (_mu_prog_limit_codes[i], &rlim))
234 		{
235 		  mu_error (_("error setting limit %d to %lu: %s"),
236 			    i, (unsigned long) rlim.rlim_cur,
237 			    mu_strerror (errno));
238 		  if (!(fs->hint_flags & MU_PROG_HINT_IGNOREFAIL))
239 		    _exit (127);
240 		}
241 	    }
242 	}
243       if (MU_PROG_HINT_PRIO & fs->hint_flags)
244 	{
245 	  if (setpriority (PRIO_PROCESS, 0, fs->hints.mu_prog_prio))
246 	    {
247 	      mu_error (_("error setting priority: %s"),
248 			mu_strerror (errno));
249 	      if (!(fs->hint_flags & MU_PROG_HINT_IGNOREFAIL))
250 		_exit (127);
251 	    }
252 	}
253 
254       /* Close unneded descripitors */
255       mu_close_fds (3);
256 
257       /*FIXME: Switch to other uid/gid if desired */
258       execvp (fs->progname, fs->argv);
259 
260       /* Report error via syslog */
261       syslog (LOG_ERR|LOG_USER, "can't run %s (ruid=%d, euid=%d): %m",
262 	      fs->progname, getuid (), geteuid ());
263       _exit (127);
264       /********************/
265 
266       /* Parent branches: */
267     case -1:
268       /* Fork has failed */
269       /* Restore things */
270       rc = errno;
271       if (REDIRECT_STDOUT_P (flags))
272 	{
273 	  close (rightp[0]);
274 	  close (rightp[1]);
275 	}
276       if (REDIRECT_STDIN_P (flags))
277 	{
278 	  close (leftp[0]);
279 	  close (leftp[1]);
280 	}
281       break;
282 
283     default:
284       if (REDIRECT_STDOUT_P (flags))
285 	{
286 	  p[0] = rightp[0];
287 	  close (rightp[1]);
288 	}
289       else
290 	p[0] = -1;
291 
292       if (REDIRECT_STDIN_P (flags))
293 	{
294 	  p[1] = leftp[1];
295 	  close (leftp[0]);
296 	}
297       else
298 	p[1] = -1;
299     }
300   return rc;
301 }
302 
303 static void
_prog_wait(pid_t pid,int * pstatus)304 _prog_wait (pid_t pid, int *pstatus)
305 {
306   if (pid > 0)
307     {
308       pid_t t;
309       do
310 	t = waitpid (pid, pstatus, 0);
311       while (t == -1 && errno == EINTR);
312     }
313 }
314 
315 static void
_prog_done(mu_stream_t stream)316 _prog_done (mu_stream_t stream)
317 {
318   int status;
319   struct _mu_prog_stream *fs = (struct _mu_prog_stream *) stream;
320 
321   mu_argcv_free (fs->argc, fs->argv);
322   free (fs->progname);
323   if (fs->hint_flags & MU_PROG_HINT_WORKDIR)
324     free (fs->hints.mu_prog_workdir);
325   if (fs->hint_flags & MU_PROG_HINT_INPUT)
326     mu_stream_unref (fs->hints.mu_prog_input);
327 
328   if (fs->in)
329     mu_stream_destroy (&fs->in);
330   if (fs->out)
331     mu_stream_destroy (&fs->out);
332 
333   _prog_wait (fs->pid, &fs->status);
334   fs->pid = -1;
335   _prog_wait (fs->writer_pid, &status);
336   fs->writer_pid = -1;
337 
338   _prog_stream_unregister (fs);
339 }
340 
341 static int
_prog_close(mu_stream_t stream)342 _prog_close (mu_stream_t stream)
343 {
344   struct _mu_prog_stream *fs = (struct _mu_prog_stream *) stream;
345   int status;
346 
347   if (!stream)
348     return EINVAL;
349 
350   if (fs->pid <= 0)
351     return 0;
352 
353   mu_stream_close (fs->out);
354   mu_stream_destroy (&fs->out);
355 
356   _prog_wait (fs->pid, &fs->status);
357   fs->pid = -1;
358   _prog_wait (fs->writer_pid, &status);
359   fs->writer_pid = -1;
360 
361   mu_stream_close (fs->in);
362   mu_stream_destroy (&fs->in);
363 
364   if (WIFEXITED (fs->status))
365     {
366       if (WEXITSTATUS (fs->status) == 0)
367 	return 0;
368       else if (WEXITSTATUS (fs->status) == 127)
369 	return MU_ERR_PROCESS_NOEXEC;
370       else
371 	return MU_ERR_PROCESS_EXITED;
372     }
373   else if (WIFSIGNALED (fs->status))
374     return MU_ERR_PROCESS_SIGNALED;
375   return MU_ERR_PROCESS_UNKNOWN_FAILURE;
376 }
377 
378 static int
feed_input(struct _mu_prog_stream * fs)379 feed_input (struct _mu_prog_stream *fs)
380 {
381   pid_t pid;
382   int rc = 0;
383 
384   pid = fork ();
385   switch (pid)
386     {
387     default:
388       /* Master */
389       fs->writer_pid = pid;
390       mu_stream_close (fs->out);
391       mu_stream_destroy (&fs->out);
392       break;
393 
394     case 0:
395       /* Child */
396       mu_stream_copy (fs->out, fs->hints.mu_prog_input, 0, NULL);
397       mu_stream_close (fs->out);
398       exit (0);
399 
400     case -1:
401       rc = errno;
402     }
403 
404   return rc;
405 }
406 
407 static int
_prog_open(mu_stream_t stream)408 _prog_open (mu_stream_t stream)
409 {
410   struct _mu_prog_stream *fs = (struct _mu_prog_stream *) stream;
411   int rc;
412   int pfd[2];
413   int flags;
414   int seekable_flag;
415 
416   if (!fs || fs->argc == 0)
417     return EINVAL;
418 
419   if (fs->pid)
420     {
421       _prog_close (stream);
422     }
423 
424   mu_stream_get_flags (stream, &flags);
425   seekable_flag = (flags & MU_STREAM_SEEK);
426 
427   rc = start_program_filter (pfd, fs, flags);
428   if (rc)
429     return rc;
430 
431   if (REDIRECT_STDOUT_P (flags))
432     {
433       rc = mu_stdio_stream_create (&fs->in, pfd[0],
434 				   MU_STREAM_READ|seekable_flag);
435       if (rc)
436 	{
437 	  _prog_close (stream);
438 	  return rc;
439 	}
440     }
441 
442   if (REDIRECT_STDIN_P (flags))
443     {
444       rc = mu_stdio_stream_create (&fs->out, pfd[1],
445 				   MU_STREAM_WRITE|seekable_flag);
446       if (rc)
447 	{
448 	  _prog_close (stream);
449 	  return rc;
450 	}
451     }
452 
453   _prog_stream_register (fs);
454   if (fs->hint_flags & MU_PROG_HINT_INPUT)
455     return feed_input (fs);
456   return 0;
457 }
458 
459 static int
_prog_read(mu_stream_t stream,char * optr,size_t osize,size_t * pnbytes)460 _prog_read (mu_stream_t stream, char *optr, size_t osize, size_t *pnbytes)
461 {
462   struct _mu_prog_stream *fs = (struct _mu_prog_stream *) stream;
463   return mu_stream_read (fs->in, optr, osize, pnbytes);
464 }
465 
466 static int
_prog_write(mu_stream_t stream,const char * iptr,size_t isize,size_t * pnbytes)467 _prog_write (mu_stream_t stream, const char *iptr, size_t isize,
468 	     size_t *pnbytes)
469 {
470   struct _mu_prog_stream *fs = (struct _mu_prog_stream *) stream;
471   return mu_stream_write (fs->out, iptr, isize, pnbytes);
472 }
473 
474 static int
_prog_flush(mu_stream_t stream)475 _prog_flush (mu_stream_t stream)
476 {
477   struct _mu_prog_stream *fs = (struct _mu_prog_stream *) stream;
478   mu_stream_flush (fs->in);
479   mu_stream_flush (fs->out);
480   return 0;
481 }
482 
483 static int
_prog_ioctl(struct _mu_stream * str,int code,int opcode,void * ptr)484 _prog_ioctl (struct _mu_stream *str, int code, int opcode, void *ptr)
485 {
486   struct _mu_prog_stream *fstr = (struct _mu_prog_stream *) str;
487 
488   switch (code)
489     {
490     case MU_IOCTL_TRANSPORT:
491       if (!ptr)
492       	return EINVAL;
493       else
494 	{
495 	  mu_transport_t *ptrans = ptr;
496 	  mu_transport_t t[2];
497 
498 	  switch (opcode)
499 	    {
500 	    case MU_IOCTL_OP_GET:
501 	      mu_stream_ioctl (fstr->in, MU_IOCTL_TRANSPORT,
502 			       MU_IOCTL_OP_GET, t);
503 	      ptrans[0] = t[0];
504 	      mu_stream_ioctl (fstr->out, MU_IOCTL_TRANSPORT,
505 			       MU_IOCTL_OP_GET, t);
506 	      ptrans[1] = t[1];
507 	      break;
508 	    case MU_IOCTL_OP_SET:
509 	      return ENOSYS;
510 	    default:
511 	      return EINVAL;
512 	    }
513 	}
514       break;
515 
516     case MU_IOCTL_PROGSTREAM:
517       if (!ptr)
518       	return EINVAL;
519       switch (opcode)
520 	{
521 	case MU_IOCTL_PROG_STATUS:
522 	  *(int*)ptr = fstr->status;
523 	  break;
524 
525 	case MU_IOCTL_PROG_PID:
526 	  *(pid_t*)ptr = fstr->pid;
527 	  break;
528 
529 	default:
530 	  return EINVAL;
531 	}
532       break;
533 
534     default:
535       return ENOSYS;
536     }
537   return 0;
538 }
539 
540 /* NOTE: Steals argv */
541 static struct _mu_prog_stream *
_prog_stream_create(const char * progname,size_t argc,char ** argv,int hint_flags,struct mu_prog_hints * hints,int flags)542 _prog_stream_create (const char *progname, size_t argc, char **argv,
543 		     int hint_flags, struct mu_prog_hints *hints, int flags)
544 {
545   struct _mu_prog_stream *fs;
546 
547   fs = (struct _mu_prog_stream *) _mu_stream_create (sizeof (*fs), flags);
548   if (!fs)
549     return NULL;
550 
551   fs->progname = strdup (progname);
552   if (!fs->progname)
553     {
554       free (fs);
555       return NULL;
556     }
557   fs->argc = argc;
558   fs->argv = argv;
559   fs->stream.read = _prog_read;
560   fs->stream.write = _prog_write;
561   fs->stream.open = _prog_open;
562   fs->stream.close = _prog_close;
563   fs->stream.ctl = _prog_ioctl;
564   fs->stream.flush = _prog_flush;
565   fs->stream.done = _prog_done;
566 
567   if (!hints)
568     fs->hint_flags = 0;
569   else
570     {
571       fs->hint_flags = (hint_flags & _MU_PROG_HINT_MASK) |
572 	                (hint_flags & _MU_PROG_AVAILABLE_LIMITS);
573       if (fs->hint_flags & MU_PROG_HINT_WORKDIR)
574 	{
575 	  fs->hints.mu_prog_workdir = strdup (hints->mu_prog_workdir);
576 	  if (!fs->hints.mu_prog_workdir)
577 	    {
578 	      free (fs);
579 	      return NULL;
580 	    }
581 	}
582       memcpy (fs->hints.mu_prog_limit, hints->mu_prog_limit,
583 	      sizeof (fs->hints.mu_prog_limit));
584       fs->hints.mu_prog_prio = hints->mu_prog_prio;
585       if (fs->hint_flags & MU_PROG_HINT_INPUT)
586 	{
587 	  fs->hints.mu_prog_input = hints->mu_prog_input;
588 	  mu_stream_ref (fs->hints.mu_prog_input);
589 	}
590       if (fs->hint_flags & MU_PROG_HINT_UID)
591 	{
592 	  fs->hints.mu_prog_uid = hints->mu_prog_uid;
593 	  if (fs->hint_flags & MU_PROG_HINT_GID)
594 	    {
595 	      fs->hints.mu_prog_gidv = calloc (hints->mu_prog_gidc,
596 					       sizeof (fs->hints.mu_prog_gidv[0]));
597 	      if (!fs->hints.mu_prog_gidv)
598 		{
599 		  mu_stream_unref ((mu_stream_t) fs);
600 		  return NULL;
601 		}
602 	      memcpy (fs->hints.mu_prog_gidv, hints->mu_prog_gidv,
603 		      hints->mu_prog_gidc * fs->hints.mu_prog_gidv[0]);
604 	      fs->hints.mu_prog_gidc = hints->mu_prog_gidc;
605 	    }
606 	  else
607 	    {
608 	      fs->hints.mu_prog_gidc = 0;
609 	      fs->hints.mu_prog_gidv = NULL;
610 	    }
611 	}
612     }
613 
614   return fs;
615 }
616 
617 int
mu_prog_stream_create(mu_stream_t * pstream,const char * progname,size_t argc,char ** argv,int hint_flags,struct mu_prog_hints * hints,int flags)618 mu_prog_stream_create (mu_stream_t *pstream,
619 		       const char *progname, size_t argc, char **argv,
620 		       int hint_flags,
621 		       struct mu_prog_hints *hints,
622 		       int flags)
623 {
624   int rc;
625   mu_stream_t stream;
626   char **xargv;
627   size_t i;
628 
629   if (pstream == NULL)
630     return MU_ERR_OUT_PTR_NULL;
631 
632   if (progname == NULL)
633     return EINVAL;
634 
635   xargv = calloc (argc + 1, sizeof (xargv[0]));
636   if (!xargv)
637     return ENOMEM;
638 
639   for (i = 0; i < argc; i++)
640     {
641       xargv[i] = strdup (argv[i]);
642       if (!xargv[i])
643 	{
644 	  mu_argcv_free (i, argv);
645 	  return ENOMEM;
646 	}
647     }
648   stream = (mu_stream_t) _prog_stream_create (progname, argc, xargv,
649 					      hint_flags, hints, flags);
650   if (!stream)
651     {
652       mu_argcv_free (argc, xargv);
653       return ENOMEM;
654     }
655 
656   rc = mu_stream_open (stream);
657   if (rc)
658     mu_stream_destroy (&stream);
659   else
660     *pstream = stream;
661   return rc;
662 }
663 
664 int
mu_command_stream_create(mu_stream_t * pstream,const char * command,int flags)665 mu_command_stream_create (mu_stream_t *pstream, const char *command,
666 			  int flags)
667 {
668   int rc;
669   mu_stream_t stream;
670   struct mu_wordsplit ws;
671 
672   if (pstream == NULL)
673     return MU_ERR_OUT_PTR_NULL;
674 
675   if (command == NULL)
676     return EINVAL;
677 
678   ws.ws_comment = "#";
679   if (mu_wordsplit (command, &ws, MU_WRDSF_DEFFLAGS|MU_WRDSF_COMMENT))
680     {
681       mu_error (_("cannot split line `%s': %s"), command,
682 		mu_wordsplit_strerror (&ws));
683       return errno;
684     }
685 
686   rc = mu_prog_stream_create (&stream,
687 			      ws.ws_wordv[0],
688 			      ws.ws_wordc, ws.ws_wordv,
689 			      0, NULL, flags);
690   if (rc == 0)
691     {
692       ws.ws_wordc = 0;
693       ws.ws_wordv = NULL;
694       *pstream = stream;
695     }
696   mu_wordsplit_free (&ws);
697 
698   return rc;
699 }
700