Started to implement the audit log feature.
[gnupg.git] / sm / call-agent.c
1 /* call-agent.c - divert operations to the agent
2  *      Copyright (C) 2001, 2002, 2003, 2005 Free Software Foundation, Inc.
3  *
4  * This file is part of GnuPG.
5  *
6  * GnuPG is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * GnuPG is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <config.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <errno.h>
25 #include <unistd.h> 
26 #include <time.h>
27 #include <assert.h>
28 #ifdef HAVE_LOCALE_H
29 #include <locale.h>
30 #endif
31
32 #include "gpgsm.h"
33 #include <gcrypt.h>
34 #include <assuan.h>
35 #include "i18n.h"
36 #include "asshelp.h"
37 #include "keydb.h" /* fixme: Move this to import.c */
38 #include "membuf.h"
39
40
41 static assuan_context_t agent_ctx = NULL;
42
43
44 struct cipher_parm_s
45 {
46   assuan_context_t ctx;
47   const unsigned char *ciphertext;
48   size_t ciphertextlen;
49 };
50
51 struct genkey_parm_s
52 {
53   assuan_context_t ctx;
54   const unsigned char *sexp;
55   size_t sexplen;
56 };
57
58 struct learn_parm_s
59 {
60   int error;
61   assuan_context_t ctx;
62   membuf_t *data;
63 };
64
65
66 \f
67 /* Try to connect to the agent via socket or fork it off and work by
68    pipes.  Handle the server's initial greeting */
69 static int
70 start_agent (ctrl_t ctrl)
71 {
72   if (agent_ctx)
73     return 0; /* fixme: We need a context for each thread or serialize
74                  the access to the agent (which is suitable given that
75                  the agent is not MT. */
76
77
78   return start_new_gpg_agent (&agent_ctx,
79                               GPG_ERR_SOURCE_DEFAULT,
80                               opt.homedir,
81                               opt.agent_program,
82                               opt.display, opt.ttyname, opt.ttytype,
83                               opt.lc_ctype, opt.lc_messages,
84                               opt.xauthority, opt.pinentry_user_data,
85                               opt.verbose, DBG_ASSUAN,
86                               gpgsm_status2, ctrl);
87
88 }
89
90
91
92 static int
93 membuf_data_cb (void *opaque, const void *buffer, size_t length)
94 {
95   membuf_t *data = opaque;
96
97   if (buffer)
98     put_membuf (data, buffer, length);
99   return 0;
100 }
101   
102
103
104 \f
105 /* Call the agent to do a sign operation using the key identified by
106    the hex string KEYGRIP. */
107 int
108 gpgsm_agent_pksign (ctrl_t ctrl, const char *keygrip, const char *desc,
109                     unsigned char *digest, size_t digestlen, int digestalgo,
110                     unsigned char **r_buf, size_t *r_buflen )
111 {
112   int rc, i;
113   char *p, line[ASSUAN_LINELENGTH];
114   membuf_t data;
115   size_t len;
116
117   *r_buf = NULL;
118   rc = start_agent (ctrl);
119   if (rc)
120     return rc;
121
122   if (digestlen*2 + 50 > DIM(line))
123     return gpg_error (GPG_ERR_GENERAL);
124
125   rc = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL);
126   if (rc)
127     return rc;
128
129   snprintf (line, DIM(line)-1, "SIGKEY %s", keygrip);
130   line[DIM(line)-1] = 0;
131   rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
132   if (rc)
133     return rc;
134
135   if (desc)
136     {
137       snprintf (line, DIM(line)-1, "SETKEYDESC %s", desc);
138       line[DIM(line)-1] = 0;
139       rc = assuan_transact (agent_ctx, line,
140                             NULL, NULL, NULL, NULL, NULL, NULL);
141       if (rc)
142         return rc;
143     }
144
145   sprintf (line, "SETHASH %d ", digestalgo);
146   p = line + strlen (line);
147   for (i=0; i < digestlen ; i++, p += 2 )
148     sprintf (p, "%02X", digest[i]);
149   rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
150   if (rc)
151     return rc;
152
153   init_membuf (&data, 1024);
154   rc = assuan_transact (agent_ctx, "PKSIGN",
155                         membuf_data_cb, &data, NULL, NULL, NULL, NULL);
156   if (rc)
157     {
158       xfree (get_membuf (&data, &len));
159       return rc;
160     }
161   *r_buf = get_membuf (&data, r_buflen);
162
163   if (!gcry_sexp_canon_len (*r_buf, *r_buflen, NULL, NULL))
164     {
165       xfree (*r_buf); *r_buf = NULL;
166       return gpg_error (GPG_ERR_INV_VALUE);
167     }
168
169   return *r_buf? 0 : out_of_core ();
170 }
171
172
173 /* Call the scdaemon to do a sign operation using the key identified by
174    the hex string KEYID. */
175 int
176 gpgsm_scd_pksign (ctrl_t ctrl, const char *keyid, const char *desc,
177                   unsigned char *digest, size_t digestlen, int digestalgo,
178                   unsigned char **r_buf, size_t *r_buflen )
179 {
180   int rc, i;
181   char *p, line[ASSUAN_LINELENGTH];
182   membuf_t data;
183   size_t len;
184   const char *hashopt;
185   unsigned char *sigbuf;
186   size_t sigbuflen;
187
188   *r_buf = NULL;
189
190   switch(digestalgo)
191     {
192     case GCRY_MD_SHA1:  hashopt = "--hash=sha1"; break;
193     case GCRY_MD_RMD160:hashopt = "--hash=rmd160"; break;
194     case GCRY_MD_MD5:   hashopt = "--hash=md5"; break;
195     case GCRY_MD_SHA256:hashopt = "--hash=sha256"; break;
196     default: 
197       return gpg_error (GPG_ERR_DIGEST_ALGO);
198     }
199
200   rc = start_agent (ctrl);
201   if (rc)
202     return rc;
203
204   if (digestlen*2 + 50 > DIM(line))
205     return gpg_error (GPG_ERR_GENERAL);
206
207   p = stpcpy (line, "SCD SETDATA " );
208   for (i=0; i < digestlen ; i++, p += 2 )
209     sprintf (p, "%02X", digest[i]);
210   rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
211   if (rc)
212     return rc;
213
214   init_membuf (&data, 1024);
215
216   snprintf (line, DIM(line)-1, "SCD PKSIGN %s %s", hashopt, keyid);
217   line[DIM(line)-1] = 0;
218   rc = assuan_transact (agent_ctx, line,
219                         membuf_data_cb, &data, NULL, NULL, NULL, NULL);
220   if (rc)
221     {
222       xfree (get_membuf (&data, &len));
223       return rc;
224     }
225   sigbuf = get_membuf (&data, &sigbuflen);
226
227   /* Create an S-expression from it which is formatted like this:
228      "(7:sig-val(3:rsa(1:sSIGBUFLEN:SIGBUF)))" Fixme: If a card ever
229      creates non-RSA keys we need to change things. */
230   *r_buflen = 21 + 11 + sigbuflen + 4;
231   p = xtrymalloc (*r_buflen);
232   *r_buf = (unsigned char*)p;
233   if (!p)
234     {
235       xfree (sigbuf);
236       return 0;
237     }
238   p = stpcpy (p, "(7:sig-val(3:rsa(1:s" );
239   sprintf (p, "%u:", (unsigned int)sigbuflen);
240   p += strlen (p);
241   memcpy (p, sigbuf, sigbuflen);
242   p += sigbuflen;
243   strcpy (p, ")))");
244   xfree (sigbuf);
245
246   assert (gcry_sexp_canon_len (*r_buf, *r_buflen, NULL, NULL));
247   return  0;
248 }
249
250
251
252 \f
253 /* Handle a CIPHERTEXT inquiry.  Note, we only send the data,
254    assuan_transact talkes care of flushing and writing the end */
255 static int
256 inq_ciphertext_cb (void *opaque, const char *keyword)
257 {
258   struct cipher_parm_s *parm = opaque; 
259   int rc;
260
261   assuan_begin_confidential (parm->ctx);
262   rc = assuan_send_data (parm->ctx, parm->ciphertext, parm->ciphertextlen);
263   assuan_end_confidential (parm->ctx);
264   return rc; 
265 }
266
267
268 /* Call the agent to do a decrypt operation using the key identified by
269    the hex string KEYGRIP. */
270 int
271 gpgsm_agent_pkdecrypt (ctrl_t ctrl, const char *keygrip, const char *desc,
272                        ksba_const_sexp_t ciphertext, 
273                        char **r_buf, size_t *r_buflen )
274 {
275   int rc;
276   char line[ASSUAN_LINELENGTH];
277   membuf_t data;
278   struct cipher_parm_s cipher_parm;
279   size_t n, len;
280   char *p, *buf, *endp;
281   size_t ciphertextlen;
282   
283   if (!keygrip || strlen(keygrip) != 40 || !ciphertext || !r_buf || !r_buflen)
284     return gpg_error (GPG_ERR_INV_VALUE);
285   *r_buf = NULL;
286
287   ciphertextlen = gcry_sexp_canon_len (ciphertext, 0, NULL, NULL);
288   if (!ciphertextlen)
289     return gpg_error (GPG_ERR_INV_VALUE);
290
291   rc = start_agent (ctrl);
292   if (rc)
293     return rc;
294
295   rc = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL);
296   if (rc)
297     return rc;
298
299   assert ( DIM(line) >= 50 );
300   snprintf (line, DIM(line)-1, "SETKEY %s", keygrip);
301   line[DIM(line)-1] = 0;
302   rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
303   if (rc)
304     return rc;
305
306   if (desc)
307     {
308       snprintf (line, DIM(line)-1, "SETKEYDESC %s", desc);
309       line[DIM(line)-1] = 0;
310       rc = assuan_transact (agent_ctx, line,
311                             NULL, NULL, NULL, NULL, NULL, NULL);
312       if (rc)
313         return rc;
314     }
315
316   init_membuf (&data, 1024);
317   cipher_parm.ctx = agent_ctx;
318   cipher_parm.ciphertext = ciphertext;
319   cipher_parm.ciphertextlen = ciphertextlen;
320   rc = assuan_transact (agent_ctx, "PKDECRYPT",
321                         membuf_data_cb, &data,
322                         inq_ciphertext_cb, &cipher_parm, NULL, NULL);
323   if (rc)
324     {
325       xfree (get_membuf (&data, &len));
326       return rc;
327     }
328
329   put_membuf (&data, "", 1); /* Make sure it is 0 terminated. */
330   buf = get_membuf (&data, &len);
331   if (!buf)
332     return gpg_error (GPG_ERR_ENOMEM);
333   assert (len); /* (we forced Nul termination.)  */
334
335   if (*buf == '(')
336     {
337       if (len < 13 || memcmp (buf, "(5:value", 8) ) /* "(5:valueN:D)\0" */
338         return gpg_error (GPG_ERR_INV_SEXP);
339       len -= 11;   /* Count only the data of the second part. */
340       p = buf + 8; /* Skip leading parenthesis and the value tag. */
341     }
342   else
343     {
344       /* For compatibility with older gpg-agents handle the old style
345          incomplete S-exps. */
346       len--;      /* Do not count the Nul. */
347       p = buf;
348     }
349
350   n = strtoul (p, &endp, 10);
351   if (!n || *endp != ':')
352     return gpg_error (GPG_ERR_INV_SEXP);
353   endp++;
354   if (endp-p+n > len)
355     return gpg_error (GPG_ERR_INV_SEXP); /* Oops: Inconsistent S-Exp. */
356   
357   memmove (buf, endp, n);
358
359   *r_buflen = n;
360   *r_buf = buf;
361   return 0;
362 }
363
364
365
366
367 \f
368 /* Handle a KEYPARMS inquiry.  Note, we only send the data,
369    assuan_transact takes care of flushing and writing the end */
370 static int
371 inq_genkey_parms (void *opaque, const char *keyword)
372 {
373   struct genkey_parm_s *parm = opaque; 
374   int rc;
375
376   rc = assuan_send_data (parm->ctx, parm->sexp, parm->sexplen);
377   return rc; 
378 }
379
380
381 \f
382 /* Call the agent to generate a newkey */
383 int
384 gpgsm_agent_genkey (ctrl_t ctrl,
385                     ksba_const_sexp_t keyparms, ksba_sexp_t *r_pubkey)
386 {
387   int rc;
388   struct genkey_parm_s gk_parm;
389   membuf_t data;
390   size_t len;
391   unsigned char *buf;
392
393   *r_pubkey = NULL;
394   rc = start_agent (ctrl);
395   if (rc)
396     return rc;
397
398   rc = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL);
399   if (rc)
400     return rc;
401
402   init_membuf (&data, 1024);
403   gk_parm.ctx = agent_ctx;
404   gk_parm.sexp = keyparms;
405   gk_parm.sexplen = gcry_sexp_canon_len (keyparms, 0, NULL, NULL);
406   if (!gk_parm.sexplen)
407     return gpg_error (GPG_ERR_INV_VALUE);
408   rc = assuan_transact (agent_ctx, "GENKEY",
409                         membuf_data_cb, &data, 
410                         inq_genkey_parms, &gk_parm, NULL, NULL);
411   if (rc)
412     {
413       xfree (get_membuf (&data, &len));
414       return rc;
415     }
416   buf = get_membuf (&data, &len);
417   if (!buf)
418     return gpg_error (GPG_ERR_ENOMEM);
419   if (!gcry_sexp_canon_len (buf, len, NULL, NULL))
420     {
421       xfree (buf);
422       return gpg_error (GPG_ERR_INV_SEXP);
423     }
424   *r_pubkey = buf;
425   return 0;
426 }
427
428 \f
429 /* Call the agent to read the public key part for a given keygrip.  If
430    FROMCARD is true, the key is directly read from the current
431    smartcard. In this case HEXKEYGRIP should be the keyID
432    (e.g. OPENPGP.3). */
433 int
434 gpgsm_agent_readkey (ctrl_t ctrl, int fromcard, const char *hexkeygrip,
435                      ksba_sexp_t *r_pubkey)
436 {
437   int rc;
438   membuf_t data;
439   size_t len;
440   unsigned char *buf;
441   char line[ASSUAN_LINELENGTH];
442
443   *r_pubkey = NULL;
444   rc = start_agent (ctrl);
445   if (rc)
446     return rc;
447
448   rc = assuan_transact (agent_ctx, "RESET",NULL, NULL, NULL, NULL, NULL, NULL);
449   if (rc)
450     return rc;
451
452   snprintf (line, DIM(line)-1, "%sREADKEY %s",
453             fromcard? "SCD ":"", hexkeygrip);
454   line[DIM(line)-1] = 0;
455
456   init_membuf (&data, 1024);
457   rc = assuan_transact (agent_ctx, line,
458                         membuf_data_cb, &data, 
459                         NULL, NULL, NULL, NULL);
460   if (rc)
461     {
462       xfree (get_membuf (&data, &len));
463       return rc;
464     }
465   buf = get_membuf (&data, &len);
466   if (!buf)
467     return gpg_error (GPG_ERR_ENOMEM);
468   if (!gcry_sexp_canon_len (buf, len, NULL, NULL))
469     {
470       xfree (buf);
471       return gpg_error (GPG_ERR_INV_SEXP);
472     }
473   *r_pubkey = buf;
474   return 0;
475 }
476
477 \f
478
479 static int
480 istrusted_status_cb (void *opaque, const char *line)
481 {
482   struct rootca_flags_s *flags = opaque;
483
484   if (!strncmp (line, "TRUSTLISTFLAG", 13) && (line[13]==' ' || !line[13]))
485     {
486       for (line += 13; *line == ' '; line++)
487         ;
488       if (!strncmp (line, "relax", 5) && (line[5] == ' ' || !line[5]))
489         flags->relax = 1;
490       else if (!strncmp (line, "cm", 2) && (line[2] == ' ' || !line[2]))
491         flags->chain_model = 1;
492     }
493   return 0;
494 }
495
496
497
498 /* Ask the agent whether the certificate is in the list of trusted
499    keys.  ROOTCA_FLAGS is guaranteed to be cleared on error. */
500 int
501 gpgsm_agent_istrusted (ctrl_t ctrl, ksba_cert_t cert,
502                        struct rootca_flags_s *rootca_flags)
503 {
504   int rc;
505   char *fpr;
506   char line[ASSUAN_LINELENGTH];
507
508   memset (rootca_flags, 0, sizeof *rootca_flags);
509
510   rc = start_agent (ctrl);
511   if (rc)
512     return rc;
513
514   fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1);
515   if (!fpr)
516     {
517       log_error ("error getting the fingerprint\n");
518       return gpg_error (GPG_ERR_GENERAL);
519     }
520
521   snprintf (line, DIM(line)-1, "ISTRUSTED %s", fpr);
522   line[DIM(line)-1] = 0;
523   xfree (fpr);
524
525   rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL,
526                         istrusted_status_cb, rootca_flags);
527   if (!rc)
528     rootca_flags->valid = 1;
529   return rc;
530 }
531
532 /* Ask the agent to mark CERT as a trusted Root-CA one */
533 int
534 gpgsm_agent_marktrusted (ctrl_t ctrl, ksba_cert_t cert)
535 {
536   int rc;
537   char *fpr, *dn;
538   char line[ASSUAN_LINELENGTH];
539
540   rc = start_agent (ctrl);
541   if (rc)
542     return rc;
543
544   fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1);
545   if (!fpr)
546     {
547       log_error ("error getting the fingerprint\n");
548       return gpg_error (GPG_ERR_GENERAL);
549     }
550
551   dn = ksba_cert_get_issuer (cert, 0);
552   if (!dn)
553     {
554       xfree (fpr);
555       return gpg_error (GPG_ERR_GENERAL);
556     }
557   snprintf (line, DIM(line)-1, "MARKTRUSTED %s S %s", fpr, dn);
558   line[DIM(line)-1] = 0;
559   ksba_free (dn);
560   xfree (fpr);
561
562   rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
563   return rc;
564 }
565
566
567 \f
568 /* Ask the agent whether the a corresponding secret key is available
569    for the given keygrip */
570 int
571 gpgsm_agent_havekey (ctrl_t ctrl, const char *hexkeygrip)
572 {
573   int rc;
574   char line[ASSUAN_LINELENGTH];
575
576   rc = start_agent (ctrl);
577   if (rc)
578     return rc;
579
580   if (!hexkeygrip || strlen (hexkeygrip) != 40)
581     return gpg_error (GPG_ERR_INV_VALUE);
582
583   snprintf (line, DIM(line)-1, "HAVEKEY %s", hexkeygrip);
584   line[DIM(line)-1] = 0;
585
586   rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
587   return rc;
588 }
589
590 \f
591 static int
592 learn_cb (void *opaque, const void *buffer, size_t length)
593 {
594   struct learn_parm_s *parm = opaque;
595   size_t len;
596   char *buf;
597   ksba_cert_t cert;
598   int rc;
599
600   if (parm->error)
601     return 0;
602
603   if (buffer)
604     {
605       put_membuf (parm->data, buffer, length);
606       return 0;
607     }
608   /* END encountered - process what we have */
609   buf = get_membuf (parm->data, &len);
610   if (!buf)
611     {
612       parm->error = gpg_error (GPG_ERR_ENOMEM);
613       return 0;
614     }
615
616
617   /* FIXME: this should go into import.c */
618   rc = ksba_cert_new (&cert);
619   if (rc)
620     {
621       parm->error = rc;
622       return 0;
623     }
624   rc = ksba_cert_init_from_mem (cert, buf, len);
625   if (rc)
626     {
627       log_error ("failed to parse a certificate: %s\n", gpg_strerror (rc));
628       ksba_cert_release (cert);
629       parm->error = rc;
630       return 0;
631     }
632
633   rc = gpgsm_basic_cert_check (cert);
634   if (gpg_err_code (rc) == GPG_ERR_MISSING_CERT)
635     { /* For later use we store it in the ephemeral database. */
636       log_info ("issuer certificate missing - storing as ephemeral\n");
637       keydb_store_cert (cert, 1, NULL);
638     }
639   else if (rc)
640     log_error ("invalid certificate: %s\n", gpg_strerror (rc));
641   else
642     {
643       int existed;
644
645       if (!keydb_store_cert (cert, 0, &existed))
646         {
647           if (opt.verbose > 1 && existed)
648             log_info ("certificate already in DB\n");
649           else if (opt.verbose && !existed)
650             log_info ("certificate imported\n");
651         }
652     }
653
654   ksba_cert_release (cert);
655   init_membuf (parm->data, 4096);
656   return 0;
657 }
658   
659 /* Call the agent to learn about a smartcard */
660 int
661 gpgsm_agent_learn (ctrl_t ctrl)
662 {
663   int rc;
664   struct learn_parm_s learn_parm;
665   membuf_t data;
666   size_t len;
667
668   rc = start_agent (ctrl);
669   if (rc)
670     return rc;
671
672   init_membuf (&data, 4096);
673   learn_parm.error = 0;
674   learn_parm.ctx = agent_ctx;
675   learn_parm.data = &data;
676   rc = assuan_transact (agent_ctx, "LEARN --send",
677                         learn_cb, &learn_parm, 
678                         NULL, NULL, NULL, NULL);
679   xfree (get_membuf (&data, &len));
680   if (rc)
681     return rc;
682   return learn_parm.error;
683 }
684
685 \f
686 /* Ask the agent to change the passphrase of the key identified by
687    HEXKEYGRIP. If DESC is not NULL, display instead of the default
688    description message. */
689 int
690 gpgsm_agent_passwd (ctrl_t ctrl, const char *hexkeygrip, const char *desc)
691 {
692   int rc;
693   char line[ASSUAN_LINELENGTH];
694
695   rc = start_agent (ctrl);
696   if (rc)
697     return rc;
698
699   if (!hexkeygrip || strlen (hexkeygrip) != 40)
700     return gpg_error (GPG_ERR_INV_VALUE);
701
702   if (desc)
703     {
704       snprintf (line, DIM(line)-1, "SETKEYDESC %s", desc);
705       line[DIM(line)-1] = 0;
706       rc = assuan_transact (agent_ctx, line,
707                             NULL, NULL, NULL, NULL, NULL, NULL);
708       if (rc)
709         return rc;
710     }
711
712   snprintf (line, DIM(line)-1, "PASSWD %s", hexkeygrip);
713   line[DIM(line)-1] = 0;
714
715   rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
716   return rc;
717 }
718
719
720 \f
721 /* Ask the agent to pop up a confirmation dialog with the text DESC
722    and an okay and cancel button.  */
723 gpg_error_t
724 gpgsm_agent_get_confirmation (ctrl_t ctrl, const char *desc)
725 {
726   int rc;
727   char line[ASSUAN_LINELENGTH];
728
729   rc = start_agent (ctrl);
730   if (rc)
731     return rc;
732
733   snprintf (line, DIM(line)-1, "GET_CONFIRMATION %s", desc);
734   line[DIM(line)-1] = 0;
735
736   rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
737   return rc;
738 }