1 /* keybox-search.c - Search operations
2 * Copyright (C) 2001, 2002, 2003, 2004 Free Software Foundation, Inc.
4 * This file is part of GnuPG.
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.
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.
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/>.
27 #include "../jnlib/stringhelp.h" /* ascii_xxxx() */
29 #include "keybox-defs.h"
33 #define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \
34 *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10))
35 #define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1))
46 get32 (const byte
*buffer
)
57 get16 (const byte
*buffer
)
68 blob_get_type (KEYBOXBLOB blob
)
70 const unsigned char *buffer
;
73 buffer
= _keybox_get_blob_image (blob
, &length
);
75 return -1; /* blob too short */
80 static inline unsigned int
81 blob_get_blob_flags (KEYBOXBLOB blob
)
83 const unsigned char *buffer
;
86 buffer
= _keybox_get_blob_image (blob
, &length
);
90 return get16 (buffer
+ 6);
94 /* Return information on the flag WHAT within the blob BUFFER,LENGTH.
95 Return the offset and the length (in bytes) of the flag in
98 _keybox_get_flag_location (const unsigned char *buffer
, size_t length
,
99 int what
, size_t *flag_off
, size_t *flag_size
)
102 size_t nkeys
, keyinfolen
;
103 size_t nuids
, uidinfolen
;
105 size_t nsigs
, siginfolen
;
109 case KEYBOX_FLAG_BLOB
:
111 return GPG_ERR_INV_OBJ
;
116 case KEYBOX_FLAG_OWNERTRUST
:
117 case KEYBOX_FLAG_VALIDITY
:
118 case KEYBOX_FLAG_CREATED_AT
:
120 return GPG_ERR_INV_OBJ
;
122 nkeys
= get16 (buffer
+ 16);
123 keyinfolen
= get16 (buffer
+ 18 );
125 return GPG_ERR_INV_OBJ
;
126 pos
= 20 + keyinfolen
*nkeys
;
128 return GPG_ERR_INV_OBJ
; /* Out of bounds. */
130 nserial
= get16 (buffer
+pos
);
133 return GPG_ERR_INV_OBJ
; /* Out of bounds. */
135 nuids
= get16 (buffer
+ pos
); pos
+= 2;
136 uidinfolen
= get16 (buffer
+ pos
); pos
+= 2;
137 if (uidinfolen
< 12 )
138 return GPG_ERR_INV_OBJ
;
139 pos
+= uidinfolen
*nuids
;
141 return GPG_ERR_INV_OBJ
; /* Out of bounds. */
142 /* Signature info. */
143 nsigs
= get16 (buffer
+ pos
); pos
+= 2;
144 siginfolen
= get16 (buffer
+ pos
); pos
+= 2;
146 return GPG_ERR_INV_OBJ
;
147 pos
+= siginfolen
*nsigs
;
148 if (pos
+1+1+2+4+4+4+4 > length
)
149 return GPG_ERR_INV_OBJ
; /* Out of bounds. */
154 case KEYBOX_FLAG_VALIDITY
:
157 case KEYBOX_FLAG_CREATED_AT
:
159 *flag_off
+= 1+2+4+4+4;
167 return GPG_ERR_INV_FLAG
;
174 /* Return one of the flags WHAT in VALUE from teh blob BUFFER of
175 LENGTH bytes. Return 0 on success or an raw error code. */
176 static gpg_err_code_t
177 get_flag_from_image (const unsigned char *buffer
, size_t length
,
178 int what
, unsigned int *value
)
184 ec
= _keybox_get_flag_location (buffer
, length
, what
, &pos
, &size
);
188 case 1: *value
= buffer
[pos
]; break;
189 case 2: *value
= get16 (buffer
+ pos
); break;
190 case 4: *value
= get32 (buffer
+ pos
); break;
191 default: ec
= GPG_ERR_BUG
; break;
199 blob_cmp_sn (KEYBOXBLOB blob
, const unsigned char *sn
, int snlen
)
201 const unsigned char *buffer
;
204 size_t nkeys
, keyinfolen
;
207 buffer
= _keybox_get_blob_image (blob
, &length
);
209 return 0; /* blob too short */
212 nkeys
= get16 (buffer
+ 16);
213 keyinfolen
= get16 (buffer
+ 18 );
215 return 0; /* invalid blob */
216 pos
= 20 + keyinfolen
*nkeys
;
218 return 0; /* out of bounds */
221 nserial
= get16 (buffer
+pos
);
223 if (off
+nserial
> length
)
224 return 0; /* out of bounds */
226 return nserial
== snlen
&& !memcmp (buffer
+off
, sn
, snlen
);
231 blob_cmp_fpr (KEYBOXBLOB blob
, const unsigned char *fpr
)
233 const unsigned char *buffer
;
236 size_t nkeys
, keyinfolen
;
239 buffer
= _keybox_get_blob_image (blob
, &length
);
241 return 0; /* blob too short */
244 nkeys
= get16 (buffer
+ 16);
245 keyinfolen
= get16 (buffer
+ 18 );
247 return 0; /* invalid blob */
249 if (pos
+ keyinfolen
*nkeys
> length
)
250 return 0; /* out of bounds */
252 for (idx
=0; idx
< nkeys
; idx
++)
254 off
= pos
+ idx
*keyinfolen
;
255 if (!memcmp (buffer
+ off
, fpr
, 20))
256 return 1; /* found */
258 return 0; /* not found */
262 blob_cmp_fpr_part (KEYBOXBLOB blob
, const unsigned char *fpr
,
263 int fproff
, int fprlen
)
265 const unsigned char *buffer
;
268 size_t nkeys
, keyinfolen
;
271 buffer
= _keybox_get_blob_image (blob
, &length
);
273 return 0; /* blob too short */
276 nkeys
= get16 (buffer
+ 16);
277 keyinfolen
= get16 (buffer
+ 18 );
279 return 0; /* invalid blob */
281 if (pos
+ keyinfolen
*nkeys
> length
)
282 return 0; /* out of bounds */
284 for (idx
=0; idx
< nkeys
; idx
++)
286 off
= pos
+ idx
*keyinfolen
;
287 if (!memcmp (buffer
+ off
+ fproff
, fpr
, fprlen
))
288 return 1; /* found */
290 return 0; /* not found */
295 blob_cmp_name (KEYBOXBLOB blob
, int idx
,
296 const char *name
, size_t namelen
, int substr
)
298 const unsigned char *buffer
;
300 size_t pos
, off
, len
;
301 size_t nkeys
, keyinfolen
;
302 size_t nuids
, uidinfolen
;
305 buffer
= _keybox_get_blob_image (blob
, &length
);
307 return 0; /* blob too short */
310 nkeys
= get16 (buffer
+ 16);
311 keyinfolen
= get16 (buffer
+ 18 );
313 return 0; /* invalid blob */
314 pos
= 20 + keyinfolen
*nkeys
;
316 return 0; /* out of bounds */
319 nserial
= get16 (buffer
+pos
);
322 return 0; /* out of bounds */
325 nuids
= get16 (buffer
+ pos
); pos
+= 2;
326 uidinfolen
= get16 (buffer
+ pos
); pos
+= 2;
327 if (uidinfolen
< 12 /* should add a: || nuidinfolen > MAX_UIDINFOLEN */)
328 return 0; /* invalid blob */
329 if (pos
+ uidinfolen
*nuids
> length
)
330 return 0; /* out of bounds */
333 { /* compare all names starting with that (negated) index */
336 for ( ;idx
< nuids
; idx
++)
340 mypos
+= idx
*uidinfolen
;
341 off
= get32 (buffer
+mypos
);
342 len
= get32 (buffer
+mypos
+4);
343 if (off
+len
> length
)
344 return 0; /* error: better stop here out of bounds */
346 continue; /* empty name */
349 if (ascii_memcasemem (buffer
+off
, len
, name
, namelen
))
350 return 1; /* found */
354 if (len
== namelen
&& !memcmp (buffer
+off
, name
, len
))
355 return 1; /* found */
358 return 0; /* not found */
363 return 0; /* no user ID with that idx */
364 pos
+= idx
*uidinfolen
;
365 off
= get32 (buffer
+pos
);
366 len
= get32 (buffer
+pos
+4);
367 if (off
+len
> length
)
368 return 0; /* out of bounds */
370 return 0; /* empty name */
374 return !!ascii_memcasemem (buffer
+off
, len
, name
, namelen
);
378 return len
== namelen
&& !memcmp (buffer
+off
, name
, len
);
384 /* compare all email addresses of the subject. With SUBSTR given as
385 True a substring search is done in the mail address */
387 blob_cmp_mail (KEYBOXBLOB blob
, const char *name
, size_t namelen
, int substr
)
389 const unsigned char *buffer
;
391 size_t pos
, off
, len
;
392 size_t nkeys
, keyinfolen
;
393 size_t nuids
, uidinfolen
;
397 /* fixme: this code is common to blob_cmp_mail */
398 buffer
= _keybox_get_blob_image (blob
, &length
);
400 return 0; /* blob too short */
403 nkeys
= get16 (buffer
+ 16);
404 keyinfolen
= get16 (buffer
+ 18 );
406 return 0; /* invalid blob */
407 pos
= 20 + keyinfolen
*nkeys
;
409 return 0; /* out of bounds */
412 nserial
= get16 (buffer
+pos
);
415 return 0; /* out of bounds */
418 nuids
= get16 (buffer
+ pos
); pos
+= 2;
419 uidinfolen
= get16 (buffer
+ pos
); pos
+= 2;
420 if (uidinfolen
< 12 /* should add a: || nuidinfolen > MAX_UIDINFOLEN */)
421 return 0; /* invalid blob */
422 if (pos
+ uidinfolen
*nuids
> length
)
423 return 0; /* out of bounds */
428 for (idx
=1 ;idx
< nuids
; idx
++)
432 mypos
+= idx
*uidinfolen
;
433 off
= get32 (buffer
+mypos
);
434 len
= get32 (buffer
+mypos
+4);
435 if (off
+len
> length
)
436 return 0; /* error: better stop here out of bounds */
437 if (len
< 2 || buffer
[off
] != '<')
438 continue; /* empty name or trailing 0 not stored */
439 len
--; /* one back */
440 if ( len
< 3 || buffer
[off
+len
] != '>')
441 continue; /* not a proper email address */
445 if (ascii_memcasemem (buffer
+off
+1, len
, name
, namelen
))
446 return 1; /* found */
450 if (len
== namelen
&& !ascii_memcasecmp (buffer
+off
+1, name
, len
))
451 return 1; /* found */
454 return 0; /* not found */
458 #ifdef KEYBOX_WITH_X509
459 /* Return true if the key in BLOB matches the 20 bytes keygrip GRIP.
460 We don't have the keygrips as meta data, thus wen need to parse the
461 certificate. Fixme: We might want to return proper error codes
462 instead of failing a search for invalid certificates etc. */
464 blob_x509_has_grip (KEYBOXBLOB blob
, const unsigned char *grip
)
467 const unsigned char *buffer
;
469 size_t cert_off
, cert_len
;
470 ksba_reader_t reader
= NULL
;
471 ksba_cert_t cert
= NULL
;
472 ksba_sexp_t p
= NULL
;
474 unsigned char array
[20];
478 buffer
= _keybox_get_blob_image (blob
, &length
);
480 return 0; /* Too short. */
481 cert_off
= get32 (buffer
+8);
482 cert_len
= get32 (buffer
+12);
483 if (cert_off
+cert_len
> length
)
484 return 0; /* Too short. */
486 rc
= ksba_reader_new (&reader
);
488 return 0; /* Problem with ksba. */
489 rc
= ksba_reader_set_mem (reader
, buffer
+cert_off
, cert_len
);
492 rc
= ksba_cert_new (&cert
);
495 rc
= ksba_cert_read_der (cert
, reader
);
498 p
= ksba_cert_get_public_key (cert
);
501 n
= gcry_sexp_canon_len (p
, 0, NULL
, NULL
);
504 rc
= gcry_sexp_sscan (&s_pkey
, NULL
, (char*)p
, n
);
507 gcry_sexp_release (s_pkey
);
510 rcp
= gcry_pk_get_keygrip (s_pkey
, array
);
511 gcry_sexp_release (s_pkey
);
513 goto failed
; /* Can't calculate keygrip. */
516 ksba_cert_release (cert
);
517 ksba_reader_release (reader
);
518 return !memcmp (array
, grip
, 20);
521 ksba_cert_release (cert
);
522 ksba_reader_release (reader
);
525 #endif /*KEYBOX_WITH_X509*/
530 The has_foo functions are used as helpers for search
533 has_short_kid (KEYBOXBLOB blob
, const unsigned char *kid
)
535 return blob_cmp_fpr_part (blob
, kid
+4, 16, 4);
539 has_long_kid (KEYBOXBLOB blob
, const unsigned char *kid
)
541 return blob_cmp_fpr_part (blob
, kid
, 12, 8);
545 has_fingerprint (KEYBOXBLOB blob
, const unsigned char *fpr
)
547 return blob_cmp_fpr (blob
, fpr
);
551 has_keygrip (KEYBOXBLOB blob
, const unsigned char *grip
)
553 #ifdef KEYBOX_WITH_X509
554 if (blob_get_type (blob
) == BLOBTYPE_X509
)
555 return blob_x509_has_grip (blob
, grip
);
562 has_issuer (KEYBOXBLOB blob
, const char *name
)
566 return_val_if_fail (name
, 0);
568 if (blob_get_type (blob
) != BLOBTYPE_X509
)
571 namelen
= strlen (name
);
572 return blob_cmp_name (blob
, 0 /* issuer */, name
, namelen
, 0);
576 has_issuer_sn (KEYBOXBLOB blob
, const char *name
,
577 const unsigned char *sn
, int snlen
)
581 return_val_if_fail (name
, 0);
582 return_val_if_fail (sn
, 0);
584 if (blob_get_type (blob
) != BLOBTYPE_X509
)
587 namelen
= strlen (name
);
589 return (blob_cmp_sn (blob
, sn
, snlen
)
590 && blob_cmp_name (blob
, 0 /* issuer */, name
, namelen
, 0));
594 has_sn (KEYBOXBLOB blob
, const unsigned char *sn
, int snlen
)
596 return_val_if_fail (sn
, 0);
598 if (blob_get_type (blob
) != BLOBTYPE_X509
)
600 return blob_cmp_sn (blob
, sn
, snlen
);
604 has_subject (KEYBOXBLOB blob
, const char *name
)
608 return_val_if_fail (name
, 0);
610 if (blob_get_type (blob
) != BLOBTYPE_X509
)
613 namelen
= strlen (name
);
614 return blob_cmp_name (blob
, 1 /* subject */, name
, namelen
, 0);
618 has_subject_or_alt (KEYBOXBLOB blob
, const char *name
, int substr
)
622 return_val_if_fail (name
, 0);
624 if (blob_get_type (blob
) != BLOBTYPE_X509
)
627 namelen
= strlen (name
);
628 return blob_cmp_name (blob
, -1 /* all subject names*/, name
,
634 has_mail (KEYBOXBLOB blob
, const char *name
, int substr
)
638 return_val_if_fail (name
, 0);
640 if (blob_get_type (blob
) != BLOBTYPE_X509
)
643 namelen
= strlen (name
);
644 if (namelen
&& name
[namelen
-1] == '>')
646 return blob_cmp_mail (blob
, name
, namelen
, substr
);
651 release_sn_array (struct sn_array_s
*array
, size_t size
)
655 for (n
=0; n
< size
; n
++)
668 keybox_search_reset (KEYBOX_HANDLE hd
)
671 return gpg_error (GPG_ERR_INV_VALUE
);
675 _keybox_release_blob (hd
->found
.blob
);
676 hd
->found
.blob
= NULL
;
690 /* Note: When in ephemeral mode the search function does visit all
691 blobs but in standard mode, blobs flagged as ephemeral are ignored. */
693 keybox_search (KEYBOX_HANDLE hd
, KEYBOX_SEARCH_DESC
*desc
, size_t ndesc
)
697 int need_words
, any_skip
;
698 KEYBOXBLOB blob
= NULL
;
699 struct sn_array_s
*sn_array
= NULL
;
702 return gpg_error (GPG_ERR_INV_VALUE
);
704 /* clear last found result */
707 _keybox_release_blob (hd
->found
.blob
);
708 hd
->found
.blob
= NULL
;
712 return hd
->error
; /* still in error state */
714 return -1; /* still EOF */
716 /* figure out what information we need */
717 need_words
= any_skip
= 0;
718 for (n
=0; n
< ndesc
; n
++)
720 switch (desc
[n
].mode
)
722 case KEYDB_SEARCH_MODE_WORDS
:
725 case KEYDB_SEARCH_MODE_FIRST
:
726 /* always restart the search in this mode */
727 keybox_search_reset (hd
);
734 if (desc
[n
].snlen
== -1 && !sn_array
)
736 sn_array
= xtrycalloc (ndesc
, sizeof *sn_array
);
738 return (hd
->error
= gpg_error_from_syserror ());
744 hd
->fp
= fopen (hd
->kb
->fname
, "rb");
747 hd
->error
= gpg_error_from_syserror ();
753 /* Kludge: We need to convert an SN given as hexstring to its binary
754 representation - in some cases we are not able to store it in the
755 search descriptor, because due to the way we use it, it is not
756 possible to free allocated memory. */
759 const unsigned char *s
;
763 for (n
=0; n
< ndesc
; n
++)
767 else if (desc
[n
].snlen
== -1)
772 for (i
=0; *s
&& *s
!= '/'; s
++, i
++)
776 sn_array
[n
].sn
= xtrymalloc (snlen
);
779 hd
->error
= gpg_error_from_syserror ();
780 release_sn_array (sn_array
, n
);
783 sn_array
[n
].snlen
= snlen
;
791 for (; *s
&& *s
!= '/'; s
+= 2)
796 const unsigned char *sn
;
799 snlen
= desc
[n
].snlen
;
800 sn_array
[n
].sn
= xtrymalloc (snlen
);
803 hd
->error
= gpg_error_from_syserror ();
804 release_sn_array (sn_array
, n
);
807 sn_array
[n
].snlen
= snlen
;
808 memcpy (sn_array
[n
].sn
, sn
, snlen
);
816 unsigned int blobflags
;
818 _keybox_release_blob (blob
); blob
= NULL
;
819 rc
= _keybox_read_blob (&blob
, hd
->fp
);
823 if (blob_get_type (blob
) == BLOBTYPE_HEADER
)
827 blobflags
= blob_get_blob_flags (blob
);
828 if (!hd
->ephemeral
&& (blobflags
& 2))
829 continue; /* Not in ephemeral mode but blob is flagged ephemeral. */
831 for (n
=0; n
< ndesc
; n
++)
833 switch (desc
[n
].mode
)
835 case KEYDB_SEARCH_MODE_NONE
:
838 case KEYDB_SEARCH_MODE_EXACT
:
839 if (has_subject_or_alt (blob
, desc
[n
].u
.name
, 0))
842 case KEYDB_SEARCH_MODE_MAIL
:
843 if (has_mail (blob
, desc
[n
].u
.name
, 0))
846 case KEYDB_SEARCH_MODE_MAILSUB
:
847 if (has_mail (blob
, desc
[n
].u
.name
, 1))
850 case KEYDB_SEARCH_MODE_SUBSTR
:
851 if (has_subject_or_alt (blob
, desc
[n
].u
.name
, 1))
854 case KEYDB_SEARCH_MODE_MAILEND
:
855 case KEYDB_SEARCH_MODE_WORDS
:
856 never_reached (); /* not yet implemented */
858 case KEYDB_SEARCH_MODE_ISSUER
:
859 if (has_issuer (blob
, desc
[n
].u
.name
))
862 case KEYDB_SEARCH_MODE_ISSUER_SN
:
863 if (has_issuer_sn (blob
, desc
[n
].u
.name
,
864 sn_array
? sn_array
[n
].sn
: desc
[n
].sn
,
865 sn_array
? sn_array
[n
].snlen
: desc
[n
].snlen
))
868 case KEYDB_SEARCH_MODE_SN
:
869 if (has_sn (blob
, sn_array
? sn_array
[n
].sn
: desc
[n
].sn
,
870 sn_array
? sn_array
[n
].snlen
: desc
[n
].snlen
))
873 case KEYDB_SEARCH_MODE_SUBJECT
:
874 if (has_subject (blob
, desc
[n
].u
.name
))
877 case KEYDB_SEARCH_MODE_SHORT_KID
:
878 if (has_short_kid (blob
, desc
[n
].u
.kid
))
881 case KEYDB_SEARCH_MODE_LONG_KID
:
882 if (has_long_kid (blob
, desc
[n
].u
.kid
))
885 case KEYDB_SEARCH_MODE_FPR
:
886 case KEYDB_SEARCH_MODE_FPR20
:
887 if (has_fingerprint (blob
, desc
[n
].u
.fpr
))
890 case KEYDB_SEARCH_MODE_KEYGRIP
:
891 if (has_keygrip (blob
, desc
[n
].u
.grip
))
894 case KEYDB_SEARCH_MODE_FIRST
:
897 case KEYDB_SEARCH_MODE_NEXT
:
901 rc
= gpg_error (GPG_ERR_INV_VALUE
);
907 for (n
=any_skip
?0:ndesc
; n
< ndesc
; n
++)
909 /* if (desc[n].skipfnc */
910 /* && desc[n].skipfnc (desc[n].skipfncvalue, aki)) */
919 hd
->found
.blob
= blob
;
923 _keybox_release_blob (blob
);
928 _keybox_release_blob (blob
);
933 release_sn_array (sn_array
, ndesc
);
942 Functions to return a certificate or a keyblock. To be used after
943 a successful search operation.
945 #ifdef KEYBOX_WITH_X509
947 Return the last found cert. Caller must free it.
950 keybox_get_cert (KEYBOX_HANDLE hd
, ksba_cert_t
*r_cert
)
952 const unsigned char *buffer
;
954 size_t cert_off
, cert_len
;
955 ksba_reader_t reader
= NULL
;
956 ksba_cert_t cert
= NULL
;
960 return gpg_error (GPG_ERR_INV_VALUE
);
962 return gpg_error (GPG_ERR_NOTHING_FOUND
);
964 if (blob_get_type (hd
->found
.blob
) != BLOBTYPE_X509
)
965 return gpg_error (GPG_ERR_WRONG_BLOB_TYPE
);
967 buffer
= _keybox_get_blob_image (hd
->found
.blob
, &length
);
969 return gpg_error (GPG_ERR_TOO_SHORT
);
970 cert_off
= get32 (buffer
+8);
971 cert_len
= get32 (buffer
+12);
972 if (cert_off
+cert_len
> length
)
973 return gpg_error (GPG_ERR_TOO_SHORT
);
975 rc
= ksba_reader_new (&reader
);
978 rc
= ksba_reader_set_mem (reader
, buffer
+cert_off
, cert_len
);
981 ksba_reader_release (reader
);
982 /* fixme: need to map the error codes */
983 return gpg_error (GPG_ERR_GENERAL
);
986 rc
= ksba_cert_new (&cert
);
989 ksba_reader_release (reader
);
993 rc
= ksba_cert_read_der (cert
, reader
);
996 ksba_cert_release (cert
);
997 ksba_reader_release (reader
);
998 /* fixme: need to map the error codes */
999 return gpg_error (GPG_ERR_GENERAL
);
1003 ksba_reader_release (reader
);
1007 #endif /*KEYBOX_WITH_X509*/
1009 /* Return the flags named WHAT at the address of VALUE. IDX is used
1010 only for certain flags and should be 0 if not required. */
1012 keybox_get_flags (KEYBOX_HANDLE hd
, int what
, int idx
, unsigned int *value
)
1014 const unsigned char *buffer
;
1018 (void)idx
; /* Not yet used. */
1021 return gpg_error (GPG_ERR_INV_VALUE
);
1022 if (!hd
->found
.blob
)
1023 return gpg_error (GPG_ERR_NOTHING_FOUND
);
1025 buffer
= _keybox_get_blob_image (hd
->found
.blob
, &length
);
1026 ec
= get_flag_from_image (buffer
, length
, what
, value
);
1027 return ec
? gpg_error (ec
):0;