1 /*
2 * Copyright (c) 2000, Boris Popov
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 * must display the following acknowledgement:
15 * This product includes software developed by Boris Popov.
16 * 4. Neither the name of the author nor the names of any co-contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 *
32 * $Id: rcfile.c,v 1.1.1.2 2001/07/06 22:38:43 conrad Exp $
33 */
34 /*
35 * Copyright 2018 Nexenta Systems, Inc. All rights reserved.
36 */
37
38 #include <fcntl.h>
39 #include <sys/types.h>
40 #include <sys/queue.h>
41 #include <sys/stat.h>
42
43 #include <ctype.h>
44 #include <errno.h>
45 #include <stdio.h>
46 #include <string.h>
47 #include <strings.h>
48 #include <stdlib.h>
49 #include <synch.h>
50 #include <unistd.h>
51 #include <pwd.h>
52 #include <libintl.h>
53
54 #include <cflib.h>
55 #include "rcfile_priv.h"
56
57 #include <assert.h>
58
59 #if 0 /* before SMF */
60 #define SMB_CFG_FILE "/etc/nsmb.conf"
61 #define OLD_SMB_CFG_FILE "/usr/local/etc/nsmb.conf"
62 #endif
63
64 extern int smb_debug;
65
66 static struct rcfile *rc_cachelookup(const char *filename);
67 static struct rcsection *rc_findsect(struct rcfile *rcp, const char *sectname);
68 static struct rcsection *rc_addsect(struct rcfile *rcp, const char *sectname);
69 static int rc_freesect(struct rcfile *rcp, struct rcsection *rsp);
70 static struct rckey *rc_sect_findkey(struct rcsection *rsp, const char *key);
71 static struct rckey *rc_sect_addkey(struct rcsection *rsp, const char *name,
72 const char *value);
73 static void rc_key_free(struct rckey *p);
74 static void rc_parse(struct rcfile *rcp);
75
76 /* lock for the variables below */
77 mutex_t rcfile_mutex = DEFAULTMUTEX;
78
79 SLIST_HEAD(rcfile_head, rcfile);
80 static struct rcfile_head pf_head = {NULL};
81 struct rcfile *smb_rc;
82 int home_nsmbrc;
83 int insecure_nsmbrc;
84
85 /*
86 * open rcfile and load its content, if already open - return previous handle
87 */
88 static int
rc_open(const char * filename,const char * mode,struct rcfile ** rcfile)89 rc_open(const char *filename, const char *mode, struct rcfile **rcfile)
90 {
91 struct stat statbuf;
92 struct rcfile *rcp;
93 FILE *f;
94
95 assert(MUTEX_HELD(&rcfile_mutex));
96
97 rcp = rc_cachelookup(filename);
98 if (rcp) {
99 *rcfile = rcp;
100 return (0);
101 }
102 f = fopen(filename, mode);
103 if (f == NULL)
104 return (errno);
105 insecure_nsmbrc = 0;
106 if (fstat(fileno(f), &statbuf) >= 0 &&
107 (statbuf.st_mode & 077) != 0)
108 insecure_nsmbrc = 1;
109 rcp = malloc(sizeof (struct rcfile));
110 if (rcp == NULL) {
111 fclose(f);
112 return (ENOMEM);
113 }
114 bzero(rcp, sizeof (struct rcfile));
115 rcp->rf_name = strdup(filename);
116 rcp->rf_f = f;
117 SLIST_INSERT_HEAD(&pf_head, rcp, rf_next);
118 rc_parse(rcp);
119 *rcfile = rcp;
120 return (0);
121 }
122
123 static int
rc_merge(const char * filename,struct rcfile ** rcfile)124 rc_merge(const char *filename, struct rcfile **rcfile)
125 {
126 struct stat statbuf;
127 struct rcfile *rcp = *rcfile;
128 FILE *f, *t;
129
130 assert(MUTEX_HELD(&rcfile_mutex));
131
132 insecure_nsmbrc = 0;
133 if (rcp == NULL) {
134 return (rc_open(filename, "r", rcfile));
135 }
136 f = fopen(filename, "r");
137 if (f == NULL)
138 return (errno);
139 insecure_nsmbrc = 0;
140 if (fstat(fileno(f), &statbuf) >= 0 &&
141 (statbuf.st_mode & 077) != 0)
142 insecure_nsmbrc = 1;
143 t = rcp->rf_f;
144 rcp->rf_f = f;
145 rc_parse(rcp);
146 rcp->rf_f = t;
147 fclose(f);
148 return (0);
149 }
150
151 /*
152 * Like rc_open, but creates a temporary file and
153 * reads the sharectl settings into it.
154 * The file is deleted when we close it.
155 */
156 static int
rc_open_sharectl(struct rcfile ** rcfile)157 rc_open_sharectl(struct rcfile **rcfile)
158 {
159 static char template[24] = "/tmp/smbfsXXXXXX";
160 struct rcfile *rcp = NULL;
161 FILE *fp = NULL;
162 int err;
163 int fd = -1;
164
165 assert(MUTEX_HELD(&rcfile_mutex));
166
167 fd = mkstemp(template);
168 if (fd < 0) {
169 err = errno;
170 goto errout;
171 }
172
173 fp = fdopen(fd, "w+");
174 if (fp == NULL) {
175 err = errno;
176 close(fd);
177 goto errout;
178 }
179 fd = -1; /* The fp owns this fd now. */
180
181 /*
182 * Get smbfs sharectl settings into the file.
183 */
184 if ((err = rc_scf_get_sharectl(fp)) != 0)
185 goto errout;
186
187 rcp = malloc(sizeof (struct rcfile));
188 if (rcp == NULL) {
189 err = ENOMEM;
190 goto errout;
191 }
192 bzero(rcp, sizeof (struct rcfile));
193
194 rcp->rf_name = strdup(template);
195 if (rcp->rf_name == NULL) {
196 err = ENOMEM;
197 goto errout;
198 }
199 rcp->rf_f = fp;
200 rcp->rf_flags = RCFILE_DELETE_ON_CLOSE;
201
202 SLIST_INSERT_HEAD(&pf_head, rcp, rf_next);
203 insecure_nsmbrc = 0;
204 rc_parse(rcp);
205 *rcfile = rcp;
206 /* fclose(f) in rc_close */
207 return (0);
208
209 errout:
210 if (rcp != NULL)
211 free(rcp);
212 if (fp != NULL) {
213 fclose(fp);
214 fd = -1;
215 }
216 if (fd != -1)
217 close(fd);
218
219 return (err);
220 }
221
222
223 static int
rc_close(struct rcfile * rcp)224 rc_close(struct rcfile *rcp)
225 {
226 struct rcsection *p, *n;
227
228 mutex_lock(&rcfile_mutex);
229
230 fclose(rcp->rf_f);
231 if (rcp->rf_flags & RCFILE_DELETE_ON_CLOSE)
232 (void) unlink(rcp->rf_name);
233
234 for (p = SLIST_FIRST(&rcp->rf_sect); p; ) {
235 n = p;
236 p = SLIST_NEXT(p, rs_next);
237 rc_freesect(rcp, n);
238 }
239 free(rcp->rf_name);
240 SLIST_REMOVE(&pf_head, rcp, rcfile, rf_next);
241 free(rcp);
242
243 mutex_unlock(&rcfile_mutex);
244 return (0);
245 }
246
247 static struct rcfile *
rc_cachelookup(const char * filename)248 rc_cachelookup(const char *filename)
249 {
250 struct rcfile *p;
251
252 assert(MUTEX_HELD(&rcfile_mutex));
253
254 SLIST_FOREACH(p, &pf_head, rf_next)
255 if (strcmp(filename, p->rf_name) == 0)
256 return (p);
257 return (0);
258 }
259
260 static struct rcsection *
rc_findsect(struct rcfile * rcp,const char * sectname)261 rc_findsect(struct rcfile *rcp, const char *sectname)
262 {
263 struct rcsection *p;
264
265 assert(MUTEX_HELD(&rcfile_mutex));
266
267 SLIST_FOREACH(p, &rcp->rf_sect, rs_next)
268 if (strcasecmp(p->rs_name, sectname) == 0)
269 return (p);
270 return (NULL);
271 }
272
273 static struct rcsection *
rc_addsect(struct rcfile * rcp,const char * sectname)274 rc_addsect(struct rcfile *rcp, const char *sectname)
275 {
276 struct rcsection *p;
277
278 assert(MUTEX_HELD(&rcfile_mutex));
279
280 p = rc_findsect(rcp, sectname);
281 if (p)
282 return (p);
283 p = malloc(sizeof (*p));
284 if (!p)
285 return (NULL);
286 p->rs_name = strdup(sectname);
287 SLIST_INIT(&p->rs_keys);
288 SLIST_INSERT_HEAD(&rcp->rf_sect, p, rs_next);
289 return (p);
290 }
291
292 static int
rc_freesect(struct rcfile * rcp,struct rcsection * rsp)293 rc_freesect(struct rcfile *rcp, struct rcsection *rsp)
294 {
295 struct rckey *p, *n;
296
297 assert(MUTEX_HELD(&rcfile_mutex));
298
299 SLIST_REMOVE(&rcp->rf_sect, rsp, rcsection, rs_next);
300 for (p = SLIST_FIRST(&rsp->rs_keys); p; ) {
301 n = p;
302 p = SLIST_NEXT(p, rk_next);
303 rc_key_free(n);
304 }
305 free(rsp->rs_name);
306 free(rsp);
307 return (0);
308 }
309
310 static struct rckey *
rc_sect_findkey(struct rcsection * rsp,const char * keyname)311 rc_sect_findkey(struct rcsection *rsp, const char *keyname)
312 {
313 struct rckey *p;
314
315 assert(MUTEX_HELD(&rcfile_mutex));
316
317 SLIST_FOREACH(p, &rsp->rs_keys, rk_next)
318 if (strcmp(p->rk_name, keyname) == 0)
319 return (p);
320 return (NULL);
321 }
322
323 static struct rckey *
rc_sect_addkey(struct rcsection * rsp,const char * name,const char * value)324 rc_sect_addkey(struct rcsection *rsp, const char *name, const char *value)
325 {
326 struct rckey *p;
327
328 assert(MUTEX_HELD(&rcfile_mutex));
329
330 p = rc_sect_findkey(rsp, name);
331 if (!p) {
332 p = malloc(sizeof (*p));
333 if (!p)
334 return (NULL);
335 SLIST_INSERT_HEAD(&rsp->rs_keys, p, rk_next);
336 p->rk_name = strdup(name);
337 p->rk_value = value ? strdup(value) : strdup("");
338 }
339 return (p);
340 }
341
342 #if 0
343 void
344 rc_sect_delkey(struct rcsection *rsp, struct rckey *p)
345 {
346
347 SLIST_REMOVE(&rsp->rs_keys, p, rckey, rk_next);
348 rc_key_free(p);
349 }
350 #endif
351
352 static void
rc_key_free(struct rckey * p)353 rc_key_free(struct rckey *p)
354 {
355 free(p->rk_value);
356 free(p->rk_name);
357 free(p);
358 }
359
360
361 static char *minauth_values[] = {
362 "none",
363 "lm",
364 "ntlm",
365 "ntlmv2",
366 "kerberos",
367 NULL
368 };
369
370 static int
eval_minauth(char * auth)371 eval_minauth(char *auth)
372 {
373 int i;
374
375 for (i = 0; minauth_values[i]; i++)
376 if (strcmp(auth, minauth_values[i]) == 0)
377 return (i);
378 return (-1);
379 }
380
381 /*
382 * Ensure that "minauth" is set to the highest level
383 */
384 /*ARGSUSED*/
385 static void
set_value(struct rcfile * rcp,struct rcsection * rsp,struct rckey * rkp,char * ptr)386 set_value(struct rcfile *rcp, struct rcsection *rsp, struct rckey *rkp,
387 char *ptr)
388 {
389 int now, new;
390 #ifdef DEBUG
391 char *from = "SMF";
392
393 if (home_nsmbrc != 0)
394 from = "user file";
395 #endif
396
397 if (strcmp(rkp->rk_name, "minauth") == 0) {
398 now = eval_minauth(rkp->rk_value);
399 new = eval_minauth(ptr);
400 if (new <= now) {
401 #ifdef DEBUG
402 if (smb_debug)
403 fprintf(stderr,
404 "set_value: rejecting %s=%s"
405 " in %s from %s\n",
406 rkp->rk_name, ptr,
407 rsp->rs_name, from);
408 #endif
409 return;
410 }
411 }
412 #ifdef DEBUG
413 if (smb_debug)
414 fprintf(stderr,
415 "set_value: applying %s=%s in %s from %s\n",
416 rkp->rk_name, ptr, rsp->rs_name, from);
417 #endif
418 rkp->rk_value = strdup(ptr);
419 }
420
421
422 /* states in rc_parse */
423 enum { stNewLine, stHeader, stSkipToEOL, stGetKey, stGetValue};
424
425 static void
rc_parse(struct rcfile * rcp)426 rc_parse(struct rcfile *rcp)
427 {
428 FILE *f = rcp->rf_f;
429 int state = stNewLine, c;
430 struct rcsection *rsp = NULL;
431 struct rckey *rkp = NULL;
432 char buf[2048];
433 char *next = buf, *last = &buf[sizeof (buf)-1];
434
435 assert(MUTEX_HELD(&rcfile_mutex));
436
437 while ((c = getc(f)) != EOF) {
438 if (c == '\r')
439 continue;
440 if (state == stNewLine) {
441 next = buf;
442 if (isspace(c))
443 continue; /* skip leading junk */
444 if (c == '[') {
445 state = stHeader;
446 rsp = NULL;
447 continue;
448 }
449 if (c == '#' || c == ';') {
450 state = stSkipToEOL;
451 } else { /* something meaningfull */
452 state = stGetKey;
453 }
454 }
455 /* ignore long lines */
456 if (state == stSkipToEOL || next == last) {
457 if (c == '\n') {
458 state = stNewLine;
459 next = buf;
460 }
461 continue;
462 }
463 if (state == stHeader) {
464 if (c == ']') {
465 *next = 0;
466 next = buf;
467 rsp = rc_addsect(rcp, buf);
468 state = stSkipToEOL;
469 } else
470 *next++ = c;
471 continue;
472 }
473 if (state == stGetKey) {
474 /* side effect: 'key name=' */
475 if (c == ' ' || c == '\t')
476 continue; /* become 'keyname=' */
477 if (c == '\n') { /* silently ignore ... */
478 state = stNewLine;
479 continue;
480 }
481 if (c != '=') {
482 *next++ = c;
483 continue;
484 }
485 *next = 0;
486 if (rsp == NULL) {
487 fprintf(stderr, dgettext(TEXT_DOMAIN,
488 "Key '%s' defined before section\n"), buf);
489 state = stSkipToEOL;
490 continue;
491 }
492 if (home_nsmbrc != 0 && (
493 strcmp(buf, "nbns") == 0 ||
494 strcmp(buf, "nbns_enable") == 0 ||
495 strcmp(buf, "nbns_broadcast") == 0)) {
496 fprintf(stderr, dgettext(TEXT_DOMAIN,
497 "option %s may not be set "
498 "in user .nsmbrc file\n"), buf);
499 next = buf;
500 state = stNewLine;
501 continue;
502 }
503 if (insecure_nsmbrc != 0 &&
504 strcmp(buf, "password") == 0) {
505 fprintf(stderr, dgettext(TEXT_DOMAIN,
506 "Warning: .nsmbrc file not secure, "
507 "ignoring passwords\n"));
508 next = buf;
509 state = stNewLine;
510 continue;
511 }
512 rkp = rc_sect_addkey(rsp, buf, NULL);
513 next = buf;
514 state = stGetValue;
515 continue;
516 }
517 /* only stGetValue left */
518 if (state != stGetValue) {
519 fprintf(stderr, dgettext(TEXT_DOMAIN,
520 "Well, I can't parse file '%s'\n"), rcp->rf_name);
521 state = stSkipToEOL;
522 }
523 if (c != '\n') {
524 *next++ = c;
525 continue;
526 }
527 *next = 0;
528 set_value(rcp, rsp, rkp, buf);
529 state = stNewLine;
530 rkp = NULL;
531 } /* while */
532 if (c == EOF && state == stGetValue) {
533 *next = 0;
534 set_value(rcp, rsp, rkp, buf);
535 }
536 }
537
538 int
rc_getstringptr(struct rcfile * rcp,const char * section,const char * key,char ** dest)539 rc_getstringptr(struct rcfile *rcp, const char *section, const char *key,
540 char **dest)
541 {
542 struct rcsection *rsp;
543 struct rckey *rkp;
544 int err;
545
546 mutex_lock(&rcfile_mutex);
547
548 *dest = NULL;
549 rsp = rc_findsect(rcp, section);
550 if (!rsp) {
551 err = ENOENT;
552 goto out;
553 }
554 rkp = rc_sect_findkey(rsp, key);
555 if (!rkp) {
556 err = ENOENT;
557 goto out;
558 }
559 *dest = rkp->rk_value;
560 err = 0;
561
562 out:
563 mutex_unlock(&rcfile_mutex);
564 return (err);
565 }
566
567 int
rc_getstring(struct rcfile * rcp,const char * section,const char * key,size_t maxlen,char * dest)568 rc_getstring(struct rcfile *rcp, const char *section, const char *key,
569 size_t maxlen, char *dest)
570 {
571 char *value;
572 int error;
573
574 error = rc_getstringptr(rcp, section, key, &value);
575 if (error)
576 return (error);
577 if (strlen(value) >= maxlen) {
578 fprintf(stderr, dgettext(TEXT_DOMAIN,
579 "line too long for key '%s' in section '%s', max = %d\n"),
580 key, section, maxlen);
581 return (EINVAL);
582 }
583 strcpy(dest, value);
584 return (0);
585 }
586
587 int
rc_getint(struct rcfile * rcp,const char * section,const char * key,int * value)588 rc_getint(struct rcfile *rcp, const char *section, const char *key, int *value)
589 {
590 struct rcsection *rsp;
591 struct rckey *rkp;
592 int err;
593
594 mutex_lock(&rcfile_mutex);
595
596 rsp = rc_findsect(rcp, section);
597 if (!rsp) {
598 err = ENOENT;
599 goto out;
600 }
601 rkp = rc_sect_findkey(rsp, key);
602 if (!rkp) {
603 err = ENOENT;
604 goto out;
605 }
606 errno = 0;
607 *value = strtol(rkp->rk_value, NULL, 0);
608 if ((err = errno) != 0) {
609 fprintf(stderr, dgettext(TEXT_DOMAIN,
610 "invalid int value '%s' for key '%s' in section '%s'\n"),
611 rkp->rk_value, key, section);
612 }
613
614 out:
615 mutex_unlock(&rcfile_mutex);
616 return (err);
617 }
618
619 /*
620 * 1,yes,true
621 * 0,no,false
622 */
623 int
rc_getbool(struct rcfile * rcp,const char * section,const char * key,int * value)624 rc_getbool(struct rcfile *rcp, const char *section, const char *key, int *value)
625 {
626 struct rcsection *rsp;
627 struct rckey *rkp;
628 char *p;
629 int err;
630
631 mutex_lock(&rcfile_mutex);
632
633 rsp = rc_findsect(rcp, section);
634 if (!rsp) {
635 err = ENOENT;
636 goto out;
637 }
638 rkp = rc_sect_findkey(rsp, key);
639 if (!rkp) {
640 err = ENOENT;
641 goto out;
642 }
643 p = rkp->rk_value;
644 while (*p && isspace(*p)) p++;
645 if (*p == '0' ||
646 strcasecmp(p, "no") == 0 ||
647 strcasecmp(p, "false") == 0) {
648 *value = 0;
649 err = 0;
650 goto out;
651 }
652 if (*p == '1' ||
653 strcasecmp(p, "yes") == 0 ||
654 strcasecmp(p, "true") == 0) {
655 *value = 1;
656 err = 0;
657 goto out;
658 }
659 fprintf(stderr, dgettext(TEXT_DOMAIN,
660 "invalid boolean value '%s' for key '%s' in section '%s' \n"),
661 p, key, section);
662 err = EINVAL;
663
664 out:
665 mutex_unlock(&rcfile_mutex);
666 return (err);
667 }
668
669 #ifdef DEBUG
670 void
dump_props(char * where)671 dump_props(char *where)
672 {
673 struct rcsection *rsp = NULL;
674 struct rckey *rkp = NULL;
675
676 fprintf(stderr, "Settings %s\n", where);
677 SLIST_FOREACH(rsp, &smb_rc->rf_sect, rs_next) {
678 fprintf(stderr, "section=%s\n", rsp->rs_name);
679 fflush(stderr);
680
681 SLIST_FOREACH(rkp, &rsp->rs_keys, rk_next) {
682 fprintf(stderr, " key=%s, value=%s\n",
683 rkp->rk_name, rkp->rk_value);
684 fflush(stderr);
685 }
686 }
687 }
688 #endif
689
690 /*
691 * first parse "sharectl get smbfs, then $HOME/.nsmbrc
692 * This is called by library consumers (commands)
693 */
694 int
smb_open_rcfile(char * home)695 smb_open_rcfile(char *home)
696 {
697 char *fn;
698 int len, error = 0;
699
700 mutex_lock(&rcfile_mutex);
701
702 smb_rc = NULL;
703 #if 0 /* before SMF */
704 fn = SMB_CFG_FILE;
705 error = rc_open(fn, &smb_rc);
706 #else
707 fn = "(sharectl get smbfs)";
708 error = rc_open_sharectl(&smb_rc);
709 #endif
710 if (error != 0 && error != ENOENT) {
711 /* Error from fopen. strerror is OK. */
712 fprintf(stderr, dgettext(TEXT_DOMAIN,
713 "Can't open %s: %s\n"), fn, strerror(errno));
714 }
715 #ifdef DEBUG
716 if (smb_debug)
717 dump_props(fn);
718 #endif
719
720 if (home) {
721 len = strlen(home) + 20;
722 fn = malloc(len);
723 snprintf(fn, len, "%s/.nsmbrc", home);
724 home_nsmbrc = 1;
725 error = rc_merge(fn, &smb_rc);
726 if (error != 0 && error != ENOENT) {
727 fprintf(stderr, dgettext(TEXT_DOMAIN,
728 "Can't open %s: %s\n"), fn, strerror(errno));
729 }
730 home_nsmbrc = 0;
731 #ifdef DEBUG
732 if (smb_debug)
733 dump_props(fn);
734 #endif
735 free(fn);
736 }
737
738 /* Mostly ignore error returns above. */
739 if (smb_rc == NULL)
740 error = ENOENT;
741 else
742 error = 0;
743
744 mutex_unlock(&rcfile_mutex);
745
746 return (error);
747 }
748
749 /*
750 * This is called by library consumers (commands)
751 */
752 void
smb_close_rcfile(void)753 smb_close_rcfile(void)
754 {
755 struct rcfile *rcp;
756
757 if ((rcp = smb_rc) != NULL) {
758 smb_rc = NULL;
759 rc_close(rcp);
760 }
761 }
762