rpmdb_pubkey: more bounds checking
[platform/upstream/libsolv.git] / ext / repo_rpmdb_pubkey.c
1 /*
2  * Copyright (c) 2007-2013, Novell Inc.
3  *
4  * This program is licensed under the BSD license, read LICENSE.BSD
5  * for further information
6  */
7
8 /*
9  * repo_rpmdb_pubkey
10  *
11  * support for pubkeys stored in the rpmdb database
12  *
13  */
14
15 #include <sys/types.h>
16 #include <sys/stat.h>
17 #include <limits.h>
18 #include <fcntl.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <unistd.h>
23 #include <assert.h>
24 #include <stdint.h>
25 #include <errno.h>
26
27 #include <rpm/rpmio.h>
28 #include <rpm/rpmpgp.h>
29 #ifndef RPM5
30 #include <rpm/header.h>
31 #endif
32 #include <rpm/rpmdb.h>
33
34 #include "pool.h"
35 #include "repo.h"
36 #include "hash.h"
37 #include "util.h"
38 #include "queue.h"
39 #include "chksum.h"
40 #include "repo_rpmdb.h"
41
42 /* FIXME: dedup with repo_rpmdb.c */
43 static void
44 setutf8string(Repodata *repodata, Id handle, Id tag, const char *str)
45 {
46   const unsigned char *cp;
47   int state = 0;
48   int c;
49   unsigned char *buf = 0, *bp;
50
51   /* check if it's already utf8, code taken from screen ;-) */
52   cp = (const unsigned char *)str;
53   while ((c = *cp++) != 0)
54     {
55       if (state)
56         {
57           if ((c & 0xc0) != 0x80)
58             break; /* encoding error */
59           c = (c & 0x3f) | (state << 6);
60           if (!(state & 0x40000000))
61             {
62               /* check for overlong sequences */
63               if ((c & 0x820823e0) == 0x80000000)
64                 break;
65               else if ((c & 0x020821f0) == 0x02000000)
66                 break;
67               else if ((c & 0x000820f8) == 0x00080000)
68                 break;
69               else if ((c & 0x0000207c) == 0x00002000)
70                 break;
71             }
72         }
73       else
74         {
75           /* new sequence */
76           if (c >= 0xfe)
77             break;
78           else if (c >= 0xfc)
79             c = (c & 0x01) | 0xbffffffc;    /* 5 bytes to follow */
80           else if (c >= 0xf8)
81             c = (c & 0x03) | 0xbfffff00;    /* 4 */
82           else if (c >= 0xf0)
83             c = (c & 0x07) | 0xbfffc000;    /* 3 */
84           else if (c >= 0xe0)
85             c = (c & 0x0f) | 0xbff00000;    /* 2 */
86           else if (c >= 0xc2)
87             c = (c & 0x1f) | 0xfc000000;    /* 1 */
88           else if (c >= 0x80)
89             break;
90         }
91       state = (c & 0x80000000) ? c : 0;
92     }
93   if (c)
94     {
95       /* not utf8, assume latin1 */
96       buf = solv_malloc(2 * strlen(str) + 1);
97       cp = (const unsigned char *)str;
98       str = (char *)buf;
99       bp = buf;
100       while ((c = *cp++) != 0)
101         {
102           if (c >= 0xc0)
103             {
104               *bp++ = 0xc3;
105               c ^= 0x80;
106             }
107           else if (c >= 0x80)
108             *bp++ = 0xc2;
109           *bp++ = c;
110         }
111       *bp++ = 0;
112     }
113   repodata_set_str(repodata, handle, tag, str);
114   if (buf)
115     solv_free(buf);
116 }
117
118 static char *
119 r64dec1(char *p, unsigned int *vp, int *eofp)
120 {
121   int i, x;
122   unsigned int v = 0;
123
124   for (i = 0; i < 4; )
125     {
126       x = *p++;
127       if (!x)
128         return 0;
129       if (x >= 'A' && x <= 'Z')
130         x -= 'A';
131       else if (x >= 'a' && x <= 'z')
132         x -= 'a' - 26;
133       else if (x >= '0' && x <= '9')
134         x -= '0' - 52;
135       else if (x == '+')
136         x = 62;
137       else if (x == '/')
138         x = 63;
139       else if (x == '=')
140         {
141           x = 0;
142           if (i == 0)
143             {
144               *eofp = 3;
145               *vp = 0;
146               return p - 1;
147             }
148           *eofp += 1;
149         }
150       else
151         continue;
152       v = v << 6 | x;
153       i++;
154     }
155   *vp = v;
156   return p;
157 }
158
159 static unsigned int
160 crc24(unsigned char *p, int len)
161 {
162   unsigned int crc = 0xb704ceL;
163   int i;
164
165   while (len--)
166     {
167       crc ^= (*p++) << 16;
168       for (i = 0; i < 8; i++)
169         if ((crc <<= 1) & 0x1000000)
170           crc ^= 0x1864cfbL;
171     }
172   return crc & 0xffffffL;
173 }
174
175 static unsigned char *
176 unarmor(char *pubkey, int *pktlp)
177 {
178   char *p;
179   int l, eof;
180   unsigned char *buf, *bp;
181   unsigned int v;
182
183   *pktlp = 0;
184   while (strncmp(pubkey, "-----BEGIN PGP PUBLIC KEY BLOCK-----", 36) != 0)
185     {
186       pubkey = strchr(pubkey, '\n');
187       if (!pubkey)
188         return 0;
189       pubkey++;
190     }
191   pubkey = strchr(pubkey, '\n');
192   if (!pubkey++)
193     return 0;
194   /* skip header lines */
195   for (;;)
196     {
197       while (*pubkey == ' ' || *pubkey == '\t')
198         pubkey++;
199       if (*pubkey == '\n')
200         break;
201       pubkey = strchr(pubkey, '\n');
202       if (!pubkey++)
203         return 0;
204     }
205   pubkey++;
206   p = strchr(pubkey, '=');
207   if (!p)
208     return 0;
209   l = p - pubkey;
210   bp = buf = solv_malloc(l * 3 / 4 + 4);
211   eof = 0;
212   while (!eof)
213     {
214       pubkey = r64dec1(pubkey, &v, &eof);
215       if (!pubkey)
216         {
217           solv_free(buf);
218           return 0;
219         }
220       *bp++ = v >> 16;
221       *bp++ = v >> 8;
222       *bp++ = v;
223     }
224   while (*pubkey == ' ' || *pubkey == '\t' || *pubkey == '\n' || *pubkey == '\r')
225     pubkey++;
226   bp -= eof;
227   if (*pubkey != '=' || (pubkey = r64dec1(pubkey + 1, &v, &eof)) == 0)
228     {
229       solv_free(buf);
230       return 0;
231     }
232   if (v != crc24(buf, bp - buf))
233     {
234       solv_free(buf);
235       return 0;
236     }
237   while (*pubkey == ' ' || *pubkey == '\t' || *pubkey == '\n' || *pubkey == '\r')
238     pubkey++;
239   if (strncmp(pubkey, "-----END PGP PUBLIC KEY BLOCK-----", 34) != 0)
240     {
241       solv_free(buf);
242       return 0;
243     }
244   *pktlp = bp - buf;
245   return buf;
246 }
247
248 static void
249 parsekeydata(Solvable *s, Repodata *data, unsigned char *p, int pl)
250 {
251   int x, tag, l;
252   unsigned char keyid[8];
253   unsigned int kcr = 0, maxex = 0;
254 #if 0
255   unsigned char *pubkey = 0;
256   unsigned char *userid = 0;
257   int pubkeyl = 0;
258   int useridl = 0;
259 #endif
260
261   for (; pl; p += l, pl -= l)
262     {
263       x = *p++;
264       pl--;
265       if (!(x & 128) || pl <= 0)
266         return;
267       if ((x & 64) == 0)
268         {
269           /* old format */
270           tag = (x & 0x3c) >> 2;
271           x &= 3;
272           if (x == 3)
273             return;
274           l = 1 << x;
275           if (pl < l)
276             return;
277           x = 0;
278           while (l--)
279             {
280               x = x << 8 | *p++;
281               pl--;
282             }
283           l = x;
284         }
285       else
286         {
287           tag = (x & 0x3f);
288           x = *p++;
289           pl--;
290           if (x < 192)
291             l = x;
292           else if (x >= 192 && x < 224)
293             {
294               if (pl <= 0)
295                 return;
296               l = ((x - 192) << 8) + *p++ + 192;
297               pl--;
298             }
299           else if (x == 255)
300             {
301               /* sanity: p[0] must be zero */
302               if (pl <= 4 || p[0] != 0)
303                 return;
304               l = p[1] << 16 | p[2] << 8 | p[3];
305               p += 4;
306               pl -= 4;
307             }
308           else
309             return;
310         }
311       if (pl < l)
312         return;
313       if (tag == 6)
314         {
315 #if 0
316           pubkey = solv_realloc(pubkey, l);
317           if (l)
318             memcpy(pubkey, p, l);
319           pubkeyl = l;
320 #endif
321           kcr = 0;
322           if (p[0] == 3 && l >= 10)
323             {
324               unsigned int ex;
325               void *h;
326               kcr = p[1] << 24 | p[2] << 16 | p[3] << 8 | p[4];
327               ex = 0;
328               if (p[5] || p[6])
329                 {
330                   ex = kcr + 24*3600 * (p[5] << 8 | p[6]);
331                   if (ex > maxex)
332                     maxex = ex;
333                 }
334               memset(keyid, 0, 8);
335               if (p[7] == 1)    /* RSA */
336                 {
337                   int i, ql, ql2;
338                   unsigned char fp[16];
339                   char fpx[32 + 1];
340                   unsigned char *q;
341
342                   ql = ((p[8] << 8 | p[9]) + 7) / 8;    /* length of public modulus */
343                   if (ql >= 8 && 10 + ql + 2 <= l)
344                     {
345                       memcpy(keyid, p + 10 + ql - 8, 8);        /* keyid is last 64 bits of public modulus */
346                       q = p + 10 + ql;
347                       ql2 = ((q[0] << 8 | q[1]) + 7) / 8;       /* length of encryption exponent */
348                       if (10 + ql + 2 + ql2 <= l)
349                         {
350                           /* fingerprint is the md5 over the two MPI bodies */
351                           h = solv_chksum_create(REPOKEY_TYPE_MD5);
352                           solv_chksum_add(h, p + 10, ql);
353                           solv_chksum_add(h, q + 2, ql2);
354                           solv_chksum_free(h, fp);
355                           for (i = 0; i < 16; i++)
356                             sprintf(fpx + i * 2, "%02x", fp[i]);
357                           repodata_set_str(data, s - s->repo->pool->solvables, PUBKEY_FINGERPRINT, fpx);
358                         }
359                     }
360                 }
361             }
362           else if (p[0] == 4 && l >= 6)
363             {
364               int i;
365               void *h;
366               unsigned char hdr[3];
367               unsigned char fp[20];
368               char fpx[40 + 1];
369
370               kcr = p[1] << 24 | p[2] << 16 | p[3] << 8 | p[4];
371               hdr[0] = 0x99;
372               hdr[1] = l >> 8;
373               hdr[2] = l;
374               /* fingerprint is the sha1 over the packet */
375               h = solv_chksum_create(REPOKEY_TYPE_SHA1);
376               solv_chksum_add(h, hdr, 3);
377               solv_chksum_add(h, p, l);
378               solv_chksum_free(h, fp);
379               for (i = 0; i < 20; i++)
380                 sprintf(fpx + i * 2, "%02x", fp[i]);
381               repodata_set_str(data, s - s->repo->pool->solvables, PUBKEY_FINGERPRINT, fpx);
382               memcpy(keyid, fp + 12, 8);        /* keyid is last 64 bits of fingerprint */
383             }
384         }
385       if (tag == 2)
386         {
387           if (p[0] == 3 && p[1] == 5)
388             {
389 #if 0
390               Id htype = 0;
391 #endif
392               /* printf("V3 signature packet\n"); */
393               if (l < 17)
394                 continue;
395               if (p[2] != 0x10 && p[2] != 0x11 && p[2] != 0x12 && p[2] != 0x13 && p[2] != 0x1f)
396                 continue;
397               if (!memcmp(keyid, p + 6, 8))
398                 {
399                   /* printf("SELF SIG\n"); */
400                 }
401               else
402                 {
403                   /* printf("OTHER SIG\n"); */
404                 }
405 #if 0
406               if (p[16] == 1)
407                 htype = REPOKEY_TYPE_MD5;
408               else if (p[16] == 2)
409                 htype = REPOKEY_TYPE_SHA1;
410               else if (p[16] == 8)
411                 htype = REPOKEY_TYPE_SHA256;
412               if (htype)
413                 {
414                   void *h = solv_chksum_create(htype);
415                   unsigned char b[3], *cs;
416
417                   b[0] = 0x99;
418                   b[1] = pubkeyl >> 8;
419                   b[2] = pubkeyl;
420                   solv_chksum_add(h, b, 3);
421                   solv_chksum_add(h, pubkey, pubkeyl);
422                   if (p[2] >= 0x10 && p[2] <= 0x13)
423                     solv_chksum_add(h, userid, useridl);
424                   solv_chksum_add(h, p + 2, 5);
425                   cs = solv_chksum_get(h, 0);
426                   solv_chksum_free(h, 0);
427                 }
428 #endif
429             }
430           if (p[0] == 4)
431             {
432               int j, ql, haveissuer;
433               unsigned char *q;
434               unsigned int ex = 0;
435 #if 0
436               unsigned int scr = 0;
437 #endif
438               unsigned char issuer[8];
439
440               /* printf("V4 signature packet\n"); */
441               if (l < 6)
442                 continue;
443               if (p[1] != 0x10 && p[1] != 0x11 && p[1] != 0x12 && p[1] != 0x13 && p[1] != 0x1f)
444                 continue;
445               haveissuer = 0;
446               ex = 0;
447               q = p + 4;
448               for (j = 0; q && j < 2; j++)
449                 {
450                   if (q + 2 > p + l)
451                     {
452                       q = 0;
453                       break;
454                     }
455                   ql = q[0] << 8 | q[1];
456                   q += 2;
457                   if (q + ql > p + l)
458                     {
459                       q = 0;
460                       break;
461                     }
462                   while (ql)
463                     {
464                       int sl;
465                       /* decode sub-packet length */
466                       x = *q++;
467                       ql--;
468                       if (x < 192)
469                         sl = x;
470                       else if (x == 255)
471                         {
472                           if (ql < 4 || q[0] != 0)
473                             {
474                               q = 0;
475                               break;
476                             }
477                           sl = q[1] << 16 | q[2] << 8 | q[3];
478                           q += 4;
479                           ql -= 4;
480                         }
481                       else
482                         {
483                           if (ql < 1)
484                             {
485                               q = 0;
486                               break;
487                             }
488                           sl = ((x - 192) << 8) + *q++ + 192;
489                           ql--;
490                         }
491                       if (ql < sl)
492                         {
493                           q = 0;
494                           break;
495                         }
496                       x = q[0] & 127;
497                       /* printf("%d SIGSUB %d %d\n", j, x, sl); */
498                       if (x == 16 && sl == 9 && !haveissuer)
499                         {
500                           memcpy(issuer, q + 1, 8);
501                           haveissuer = 1;
502                         }
503 #if 0
504                       if (x == 2 && j == 0)
505                         scr = q[1] << 24 | q[2] << 16 | q[3] << 8 | q[4];
506 #endif
507                       if (x == 9 && j == 0)
508                         ex = q[1] << 24 | q[2] << 16 | q[3] << 8 | q[4];
509                       q += sl;
510                       ql -= sl;
511                     }
512                 }
513               if (ex)
514                 ex += kcr;
515               if (haveissuer)
516                 {
517 #if 0
518                   Id htype = 0;
519                   if (p[3] == 1)
520                     htype = REPOKEY_TYPE_MD5;
521                   else if (p[3] == 2)
522                     htype = REPOKEY_TYPE_SHA1;
523                   else if (p[3] == 8)
524                     htype = REPOKEY_TYPE_SHA256;
525                   if (htype && pubkeyl)
526                     {
527                       void *h = solv_chksum_create(htype);
528                       unsigned char b[6], *cs;
529                       unsigned int hl;
530
531                       b[0] = 0x99;
532                       b[1] = pubkeyl >> 8;
533                       b[2] = pubkeyl;
534                       solv_chksum_add(h, b, 3);
535                       solv_chksum_add(h, pubkey, pubkeyl);
536                       if (p[1] >= 0x10 && p[1] <= 0x13)
537                         {
538                           b[0] = 0xb4;
539                           b[1] = useridl >> 24;
540                           b[2] = useridl >> 16;
541                           b[3] = useridl >> 8;
542                           b[4] = useridl;
543                           solv_chksum_add(h, b, 5);
544                           solv_chksum_add(h, userid, useridl);
545                         }
546                       hl = 6 + (p[4] << 8 | p[5]);
547                       solv_chksum_add(h, p, hl);
548                       b[0] = 4;
549                       b[1] = 0xff;
550                       b[2] = hl >> 24;
551                       b[3] = hl >> 16;
552                       b[4] = hl >> 8;
553                       b[5] = hl;
554                       solv_chksum_add(h, b, 6);
555                       cs = solv_chksum_get(h, 0);
556                       solv_chksum_free(h, 0);
557                     }
558 #endif
559                   if (!memcmp(keyid, issuer, 8))
560                     {
561                       /* printf("SELF SIG cr %d ex %d\n", cr, ex); */
562                       if (ex > maxex)
563                         maxex = ex;
564                     }
565                   else
566                     {
567                       /* printf("OTHER SIG cr %d ex %d\n", cr, ex); */
568                     }
569                 }
570             }
571         }
572 #if 0
573       if (tag == 13)
574         {
575           userid = solv_realloc(userid, l);
576           if (l)
577             memcpy(userid, p, l);
578           useridl = l;
579         }
580 #endif
581     }
582   if (maxex)
583     repodata_set_num(data, s - s->repo->pool->solvables, PUBKEY_EXPIRES, maxex);
584 #if 0
585   solv_free(pubkey);
586   solv_free(userid);
587 #endif
588 }
589
590 /* this is private to rpm, but rpm lacks an interface to retrieve
591  * the values. Sigh. */
592 struct pgpDigParams_s {
593     const char * userid;
594     const unsigned char * hash;
595 #ifndef HAVE_PGPDIGGETPARAMS
596     const char * params[4];
597 #endif
598     unsigned char tag;
599     unsigned char version;               /*!< version number. */
600     unsigned char time[4];               /*!< time that the key was created. */
601     unsigned char pubkey_algo;           /*!< public key algorithm. */
602     unsigned char hash_algo;
603     unsigned char sigtype;
604     unsigned char hashlen;
605     unsigned char signhash16[2];
606     unsigned char signid[8];
607     unsigned char saved;
608 };
609
610 #ifndef HAVE_PGPDIGGETPARAMS
611 struct pgpDig_s {
612     struct pgpDigParams_s signature;
613     struct pgpDigParams_s pubkey;
614 };
615 #endif
616
617 static int
618 pubkey2solvable(Solvable *s, Repodata *data, char *pubkey)
619 {
620   Pool *pool = s->repo->pool;
621   unsigned char *pkts;
622   unsigned int btime;
623   int pktsl, i;
624   pgpDig dig = 0;
625   char keyid[16 + 1];
626   char evrbuf[8 + 1 + 8 + 1];
627   struct pgpDigParams_s *digpubkey;
628
629   pkts = unarmor(pubkey, &pktsl);
630   if (!pkts)
631     return 0;
632   setutf8string(data, s - s->repo->pool->solvables, SOLVABLE_DESCRIPTION, pubkey);
633   parsekeydata(s, data, pkts, pktsl);
634   /* only rpm knows how to do the release calculation, we don't dare
635    * to recreate all the bugs */
636 #ifndef RPM5
637   dig = pgpNewDig();
638 #else
639   dig = pgpDigNew(RPMVSF_DEFAULT, 0);
640 #endif
641   (void) pgpPrtPkts(pkts, pktsl, dig, 0);
642
643 #ifdef HAVE_PGPDIGGETPARAMS
644   digpubkey = pgpDigGetParams(dig, PGPTAG_PUBLIC_KEY);
645 #else
646   digpubkey = &dig->pubkey;
647 #endif
648   btime = digpubkey->time[0] << 24 | digpubkey->time[1] << 16 | digpubkey->time[2] << 8 | digpubkey->signid[3];
649   sprintf(evrbuf, "%02x%02x%02x%02x-%02x%02x%02x%02x", digpubkey->signid[4], digpubkey->signid[5], digpubkey->signid[6], digpubkey->signid[7], digpubkey->time[0], digpubkey->time[1], digpubkey->time[2], digpubkey->time[3]);
650
651   repodata_set_num(data, s - s->repo->pool->solvables, SOLVABLE_BUILDTIME, btime);
652
653   s->name = pool_str2id(pool, "gpg-pubkey", 1);
654   s->evr = pool_str2id(pool, evrbuf, 1);
655   s->arch = 1;
656   for (i = 0; i < 8; i++)
657     sprintf(keyid + 2 * i, "%02x", digpubkey->signid[i]);
658   repodata_set_str(data, s - s->repo->pool->solvables, PUBKEY_KEYID, keyid);
659   if (digpubkey->userid)
660     setutf8string(data, s - s->repo->pool->solvables, SOLVABLE_SUMMARY, digpubkey->userid);
661 #ifndef RPM5
662   (void)pgpFreeDig(dig);
663 #else
664   (void)pgpDigFree(dig);
665 #endif
666   solv_free((void *)pkts);
667   return 1;
668 }
669
670 int
671 repo_add_rpmdb_pubkeys(Repo *repo, int flags)
672 {
673   Pool *pool = repo->pool;
674   Queue q;
675   int i;
676   char *str;
677   Repodata *data;
678   Solvable *s;
679   const char *rootdir = 0;
680   void *state;
681
682   data = repo_add_repodata(repo, flags);
683   if (flags & REPO_USE_ROOTDIR)
684     rootdir = pool_get_rootdir(pool);
685   state = rpm_state_create(repo->pool, rootdir);
686   queue_init(&q);
687   rpm_installedrpmdbids(state, "Name", "gpg-pubkey", &q);
688   for (i = 0; i < q.count; i++)
689     {
690       void *handle;
691       unsigned long long itime;
692
693       handle = rpm_byrpmdbid(state, q.elements[i]);
694       if (!handle)
695         continue;
696       str = rpm_query(handle, SOLVABLE_DESCRIPTION);
697       if (!str)
698         continue;
699       s = pool_id2solvable(pool, repo_add_solvable(repo));
700       pubkey2solvable(s, data, str);
701       solv_free(str);
702       itime = rpm_query_num(handle, SOLVABLE_INSTALLTIME, 0);
703       if (itime)
704         repodata_set_num(data, s - pool->solvables, SOLVABLE_INSTALLTIME, itime);
705       if (!repo->rpmdbid)
706         repo->rpmdbid = repo_sidedata_create(repo, sizeof(Id));
707       repo->rpmdbid[s - pool->solvables - repo->start] = q.elements[i];
708     }
709   queue_free(&q);
710   rpm_state_free(state);
711   if (!(flags & REPO_NO_INTERNALIZE))
712     repodata_internalize(data);
713   return 0;
714 }
715
716 Id
717 repo_add_pubkey(Repo *repo, const char *key, int flags)
718 {
719   Pool *pool = repo->pool;
720   Repodata *data;
721   Solvable *s;
722   char *buf;
723   int bufl, l, ll;
724   FILE *fp;
725
726   data = repo_add_repodata(repo, flags);
727   buf = 0;
728   bufl = 0;
729   if ((fp = fopen(flags & REPO_USE_ROOTDIR ? pool_prepend_rootdir_tmp(pool, key) : key, "r")) == 0)
730     {
731       pool_error(pool, -1, "%s: %s", key, strerror(errno));
732       return 0;
733     }
734   for (l = 0; ;)
735     {
736       if (bufl - l < 4096)
737         {
738           bufl += 4096;
739           buf = solv_realloc(buf, bufl);
740         }
741       ll = fread(buf, 1, bufl - l, fp);
742       if (ll < 0)
743         {
744           fclose(fp);
745           pool_error(pool, -1, "%s: %s", key, strerror(errno));
746           return 0;
747         }
748       if (ll == 0)
749         break;
750       l += ll;
751     }
752   buf[l] = 0;
753   fclose(fp);
754   s = pool_id2solvable(pool, repo_add_solvable(repo));
755   if (!pubkey2solvable(s, data, buf))
756     {
757       repo_free_solvable(repo, s - pool->solvables, 1);
758       solv_free(buf);
759       return 0;
760     }
761   solv_free(buf);
762   if (!(flags & REPO_NO_INTERNALIZE))
763     repodata_internalize(data);
764   return s - pool->solvables;
765 }
766