1/* $OpenBSD: keynote.y,v 1.19 2022/12/27 17:10:06 jmc Exp $ */ 2/* 3 * The author of this code is Angelos D. Keromytis (angelos@dsl.cis.upenn.edu) 4 * 5 * This code was written by Angelos D. Keromytis in Philadelphia, PA, USA, 6 * in April-May 1998 7 * 8 * Copyright (C) 1998, 1999 by Angelos D. Keromytis. 9 * 10 * Permission to use, copy, and modify this software with or without fee 11 * is hereby granted, provided that this entire notice is included in 12 * all copies of any software which is or includes a copy or 13 * modification of this software. 14 * 15 * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR 16 * IMPLIED WARRANTY. IN PARTICULAR, THE AUTHORS MAKES NO 17 * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE 18 * MERCHANTABILITY OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR 19 * PURPOSE. 20 */ 21%union { 22 char *string; 23 double doubval; 24 int intval; 25 int bool; 26}; 27%type <bool> stringexp numexp expr floatexp 28%type <intval> NUM KOF numex afterhint notemptyprog 29%type <intval> notemptykeypredicate prog key keyexp keylist 30%type <doubval> FLOAT floatex 31%type <string> STRING VARIABLE str strnotconcat 32%token TRUE FALSE NUM FLOAT STRING VARIABLE 33%token OPENPAREN CLOSEPAREN EQQ COMMA ACTSTR LOCINI KOF KEYPRE KNVERSION 34%token DOTT SIGNERKEY HINT OPENBLOCK CLOSEBLOCK SIGNATUREENTRY PRIVATEKEY 35%token SEMICOLON TRUE FALSE 36%nonassoc EQ NE LT GT LE GE REGEXP 37%left OR 38%left AND 39%right NOT 40%left PLUS MINUS DOTT 41%left MULT DIV MOD 42%left EXP 43%nonassoc UNARYMINUS DEREF OPENNUM OPENFLT 44%start grammarswitch 45%{ 46#include <sys/types.h> 47 48#include <ctype.h> 49#include <math.h> 50#include <regex.h> 51#include <stdio.h> 52#include <stdlib.h> 53#include <string.h> 54 55#include "keynote.h" 56#include "assertion.h" 57 58static int *keynote_kth_array = NULL; 59static int keylistcount = 0; 60 61static int resolve_assertion(char *); 62static int keynote_init_kth(void); 63static int isfloatstring(char *); 64static int checkexception(int); 65static char *my_lookup(char *); 66static int intpow(int, int); 67static int get_kth(int); 68%} 69%% 70 71grammarswitch: LOCINI { keynote_exceptionflag = keynote_donteval = 0; } 72 localinit 73 | ACTSTR { keynote_exceptionflag = keynote_donteval = 0; } program 74 | KEYPRE { keynote_exceptionflag = keynote_donteval = 0; } 75 keypredicate 76 | SIGNERKEY { keynote_exceptionflag = keynote_donteval = 0; } key 77 | SIGNATUREENTRY { keynote_exceptionflag = keynote_donteval = 0; } 78 key 79 | KNVERSION { keynote_exceptionflag = keynote_donteval = 0; } 80 STRING { keynote_lex_remove($3); 81 if (strcmp($3, KEYNOTE_VERSION_STRING)) 82 keynote_errno = ERROR_SYNTAX; 83 free($3); 84 } 85 | PRIVATEKEY { keynote_exceptionflag = keynote_donteval = 0; } 86 STRING { keynote_lex_remove($3); 87 keynote_privkey = $3; 88 } 89 90keypredicate: /* Nothing */ { keynote_returnvalue = 0; 91 return 0; 92 } 93 | notemptykeypredicate { keynote_returnvalue = $1; 94 return 0; 95 } 96 97notemptykeypredicate: key { $$ = $1; } 98 | keyexp { $$ = $1; } 99 100keyexp: notemptykeypredicate AND { if (($1 == 0) && !keynote_justrecord) 101 keynote_donteval = 1; 102 } notemptykeypredicate 103 { if ($1 > $4) 104 $$ = $4; 105 else 106 $$ = $1; 107 keynote_donteval = 0; 108 } /* Min */ 109 | notemptykeypredicate OR { if (($1 == (keynote_current_session->ks_values_num - 1)) && !keynote_justrecord) 110 keynote_donteval = 1; 111 } notemptykeypredicate 112 { if ($1 >= $4) 113 $$ = $1; 114 else 115 $$ = $4; 116 keynote_donteval = 0; 117 } /* Max */ 118 | OPENPAREN keyexp CLOSEPAREN { $$ = $2; } 119 | KOF { keylistcount = 0; } OPENPAREN { 120 if (!keynote_justrecord && !keynote_donteval) 121 if (keynote_init_kth() == -1) 122 return -1; 123 } keylist CLOSEPAREN 124 { 125 if (keylistcount < $1) 126 { 127 keynote_errno = ERROR_SYNTAX; 128 return -1; 129 } 130 131 if (!keynote_justrecord && !keynote_donteval) 132 $$ = get_kth($1); 133 else 134 $$ = 0; 135 } /* K-th */ 136 137keylist: key 138 { /* Don't do anything if we're just recording */ 139 if (!keynote_justrecord && !keynote_donteval) 140 if (($1 < keynote_current_session->ks_values_num) && ($1 >= 0)) 141 keynote_kth_array[$1]++; 142 143 keylistcount++; 144 } 145 | key COMMA keylist 146 { /* Don't do anything if we're just recording */ 147 if (!keynote_justrecord && !keynote_donteval) 148 if (($1 < keynote_current_session->ks_values_num) && ($1 >= 0)) 149 keynote_kth_array[$1]++; 150 151 keylistcount++; 152 } 153 154key: str { 155 if (keynote_donteval) 156 $$ = 0; 157 else 158 { 159 keynote_lex_remove($1); 160 if (keynote_justrecord) 161 { 162 if (keynote_keylist_add(&keynote_keypred_keylist, 163 $1) == -1) 164 { 165 free($1); 166 return -1; 167 } 168 169 $$ = 0; 170 } 171 else 172 switch (keynote_in_action_authorizers($1, KEYNOTE_ALGORITHM_UNSPEC)) 173 { 174 case -1: 175 free($1); 176 return -1; 177 178 case RESULT_TRUE: 179 free($1); 180 $$ = keynote_current_session->ks_values_num - 181 1; 182 break; 183 184 default: 185 $$ = resolve_assertion($1); 186 free($1); 187 break; 188 } 189 } 190 } 191 192localinit: /* Nothing */ 193 | localconstants 194 195localconstants: VARIABLE EQQ STRING 196 { 197 int i; 198 199 keynote_lex_remove($1); 200 keynote_lex_remove($3); 201 202 /* 203 * Variable names starting with underscores are illegal here. 204 */ 205 if ($1[0] == '_') 206 { 207 free($1); 208 free($3); 209 keynote_errno = ERROR_SYNTAX; 210 return -1; 211 } 212 213 /* If the identifier already exists, report error. */ 214 if (keynote_env_lookup($1, &keynote_init_list, 1) != NULL) 215 { 216 free($1); 217 free($3); 218 keynote_errno = ERROR_SYNTAX; 219 return -1; 220 } 221 222 i = keynote_env_add($1, $3, &keynote_init_list, 1, 0); 223 free($1); 224 free($3); 225 226 if (i != RESULT_TRUE) 227 return -1; 228 } 229 | VARIABLE EQQ STRING 230 { 231 int i; 232 233 keynote_lex_remove($1); 234 keynote_lex_remove($3); 235 236 /* 237 * Variable names starting with underscores are illegal here. 238 */ 239 if ($1[0] == '_') 240 { 241 free($1); 242 free($3); 243 keynote_errno = ERROR_SYNTAX; 244 return -1; 245 } 246 247 /* If the identifier already exists, report error. */ 248 if (keynote_env_lookup($1, &keynote_init_list, 1) != NULL) 249 { 250 free($1); 251 free($3); 252 keynote_errno = ERROR_SYNTAX; 253 return -1; 254 } 255 256 i = keynote_env_add($1, $3, &keynote_init_list, 1, 0); 257 free($1); 258 free($3); 259 260 if (i != RESULT_TRUE) 261 return -1; 262 } localconstants 263 264program: prog { 265 keynote_returnvalue = $1; 266 return 0; 267 } 268 269prog: /* Nada */ { $$ = 0; } 270 | notemptyprog { 271 /* 272 * Cleanup envlist of additions such as 273 * regexp results 274 */ 275 keynote_env_cleanup(&keynote_temp_list, 1); 276 } SEMICOLON prog 277 { 278 if ($1 > $4) 279 $$ = $1; 280 else 281 $$ = $4; 282 } 283 284notemptyprog: expr HINT afterhint 285 { 286 if (checkexception($1)) 287 $$ = $3; 288 else 289 $$ = 0; 290 } 291 | expr 292 { 293 if (checkexception($1)) 294 $$ = keynote_current_session->ks_values_num - 1; 295 else 296 $$ = 0; 297 } 298 299afterhint: str { if (keynote_exceptionflag || keynote_donteval) 300 $$ = 0; 301 else 302 { 303 keynote_lex_remove($1); 304 305 $$ = keynote_retindex($1); 306 if ($$ == -1) /* Invalid return value */ 307 $$ = 0; 308 309 free($1); 310 } 311 } 312 | OPENBLOCK prog CLOSEBLOCK { $$ = $2; } 313 314 315expr: OPENPAREN expr CLOSEPAREN { $$ = $2; } 316 | expr AND { if ($1 == 0) 317 keynote_donteval = 1; 318 } expr { $$ = ($1 && $4); 319 keynote_donteval = 0; 320 } 321 | expr OR { if ($1) 322 keynote_donteval = 1; 323 } expr { $$ = ($1 || $4); 324 keynote_donteval = 0; 325 } 326 | NOT expr { $$ = !($2); } 327 | numexp { $$ = $1; } 328 | floatexp { $$ = $1; } 329 | stringexp { $$ = $1; } 330 | TRUE { $$ = 1; } 331 | FALSE { $$ = 0; } 332 333numexp: numex LT numex { $$ = $1 < $3; } 334 | numex GT numex { $$ = $1 > $3; } 335 | numex EQ numex { $$ = $1 == $3; } 336 | numex LE numex { $$ = $1 <= $3; } 337 | numex GE numex { $$ = $1 >= $3; } 338 | numex NE numex { $$ = $1 != $3; } 339 340floatexp: floatex LT floatex { $$ = $1 < $3; } 341 | floatex GT floatex { $$ = $1 > $3; } 342 | floatex LE floatex { $$ = $1 <= $3; } 343 | floatex GE floatex { $$ = $1 >= $3; } 344 345numex: numex PLUS numex { $$ = $1 + $3; } 346 | numex MINUS numex { $$ = $1 - $3; } 347 | numex MULT numex { $$ = $1 * $3; } 348 | numex DIV numex { if ($3 == 0) 349 { 350 if (!keynote_donteval) 351 keynote_exceptionflag = 1; 352 } 353 else 354 $$ = ($1 / $3); 355 } 356 | numex MOD numex { if ($3 == 0) 357 { 358 if (!keynote_donteval) 359 keynote_exceptionflag = 1; 360 } 361 else 362 $$ = $1 % $3; 363 } 364 | numex EXP numex { $$ = intpow($1, $3); } 365 | MINUS numex %prec UNARYMINUS { $$ = -($2); } 366 | OPENPAREN numex CLOSEPAREN { $$ = $2; } 367 | NUM { $$ = $1; } 368 | OPENNUM strnotconcat { if (keynote_exceptionflag || 369 keynote_donteval) 370 $$ = 0; 371 else 372 { 373 keynote_lex_remove($2); 374 375 if (!isfloatstring($2)) 376 $$ = 0; 377 else 378 $$ = (int) floor(atof($2)); 379 free($2); 380 } 381 } 382 383floatex: floatex PLUS floatex { $$ = ($1 + $3); } 384 | floatex MINUS floatex { $$ = ($1 - $3); } 385 | floatex MULT floatex { $$ = ($1 * $3); } 386 | floatex DIV floatex { if ($3 == 0) 387 { 388 if (!keynote_donteval) 389 keynote_exceptionflag = 1; 390 } 391 else 392 $$ = ($1 / $3); 393 } 394 | floatex EXP floatex { if (!keynote_exceptionflag && 395 !keynote_donteval) 396 $$ = pow($1, $3); 397 } 398 | MINUS floatex %prec UNARYMINUS { $$ = -($2); } 399 | OPENPAREN floatex CLOSEPAREN { $$ = $2; } 400 | FLOAT { $$ = $1; } 401 | OPENFLT strnotconcat { 402 if (keynote_exceptionflag || 403 keynote_donteval) 404 $$ = 0.0; 405 else 406 { 407 keynote_lex_remove($2); 408 409 if (!isfloatstring($2)) 410 $$ = 0.0; 411 else 412 $$ = atof($2); 413 free($2); 414 } 415 } 416 417stringexp: str EQ str { 418 if (keynote_exceptionflag || keynote_donteval) 419 $$ = 0; 420 else 421 { 422 $$ = strcmp($1, $3) == 0 ? 1 : 0; 423 keynote_lex_remove($1); 424 keynote_lex_remove($3); 425 free($1); 426 free($3); 427 } 428 } 429 | str NE str { 430 if (keynote_exceptionflag || keynote_donteval) 431 $$ = 0; 432 else 433 { 434 $$ = strcmp($1, $3) != 0 ? 1 : 0; 435 keynote_lex_remove($1); 436 keynote_lex_remove($3); 437 free($1); 438 free($3); 439 } 440 } 441 | str LT str { 442 if (keynote_exceptionflag || keynote_donteval) 443 $$ = 0; 444 else 445 { 446 $$ = strcmp($1, $3) < 0 ? 1 : 0; 447 keynote_lex_remove($1); 448 keynote_lex_remove($3); 449 free($1); 450 free($3); 451 } 452 } 453 | str GT str { 454 if (keynote_exceptionflag || keynote_donteval) 455 $$ = 0; 456 else 457 { 458 $$ = strcmp($1, $3) > 0 ? 1 : 0; 459 keynote_lex_remove($1); 460 keynote_lex_remove($3); 461 free($1); 462 free($3); 463 } 464 } 465 | str LE str { 466 if (keynote_exceptionflag || keynote_donteval) 467 $$ = 0; 468 else 469 { 470 $$ = strcmp($1, $3) <= 0 ? 1 : 0; 471 keynote_lex_remove($1); 472 keynote_lex_remove($3); 473 free($1); 474 free($3); 475 } 476 } 477 | str GE str { 478 if (keynote_exceptionflag || keynote_donteval) 479 $$ = 0; 480 else 481 { 482 $$ = strcmp($1, $3) >= 0 ? 1 : 0; 483 keynote_lex_remove($1); 484 keynote_lex_remove($3); 485 free($1); 486 free($3); 487 } 488 } 489 | str REGEXP str 490 { 491 regmatch_t pmatch[32]; 492 char grp[10], *gr; 493 regex_t preg; 494 int i; 495 496 if (keynote_exceptionflag || keynote_donteval) 497 $$ = 0; 498 else 499 { 500 keynote_lex_remove($1); 501 keynote_lex_remove($3); 502 503 memset(pmatch, 0, sizeof(pmatch)); 504 memset(grp, 0, sizeof(grp)); 505 506 if (regcomp(&preg, $3, REG_EXTENDED)) 507 { 508 free($1); 509 free($3); 510 keynote_exceptionflag = 1; 511 } 512 else 513 { 514 /* Clean-up residuals from previous regexps */ 515 keynote_env_cleanup(&keynote_temp_list, 1); 516 517 free($3); 518 i = regexec(&preg, $1, 32, pmatch, 0); 519 $$ = (i == 0 ? 1 : 0); 520 if (i == 0) 521 { 522 snprintf(grp, sizeof grp, "%lu", 523 (unsigned long)preg.re_nsub); 524 if (keynote_env_add("_0", grp, &keynote_temp_list, 525 1, 0) != RESULT_TRUE) 526 { 527 free($1); 528 regfree(&preg); 529 return -1; 530 } 531 532 for (i = 1; i < 32 && pmatch[i].rm_so != -1; i++) 533 { 534 gr = calloc(pmatch[i].rm_eo - pmatch[i].rm_so + 535 1, sizeof(char)); 536 if (gr == NULL) 537 { 538 free($1); 539 regfree(&preg); 540 keynote_errno = ERROR_MEMORY; 541 return -1; 542 } 543 544 strncpy(gr, $1 + pmatch[i].rm_so, 545 pmatch[i].rm_eo - pmatch[i].rm_so); 546 gr[pmatch[i].rm_eo - pmatch[i].rm_so] = '\0'; 547 snprintf(grp, sizeof grp, "_%d", i); 548 if (keynote_env_add(grp, gr, &keynote_temp_list, 549 1, 0) == -1) 550 { 551 free($1); 552 regfree(&preg); 553 free(gr); 554 return -1; 555 } 556 else 557 free(gr); 558 } 559 } 560 561 regfree(&preg); 562 free($1); 563 } 564 } 565 } 566 567str: str DOTT str { if (keynote_exceptionflag || keynote_donteval) 568 $$ = NULL; 569 else 570 { 571 int len = strlen($1) + strlen($3) + 1; 572 $$ = calloc(len, sizeof(char)); 573 keynote_lex_remove($1); 574 keynote_lex_remove($3); 575 if ($$ == NULL) 576 { 577 free($1); 578 free($3); 579 keynote_errno = ERROR_MEMORY; 580 return -1; 581 } 582 snprintf($$, len, "%s%s", $1, $3); 583 free($1); 584 free($3); 585 if (keynote_lex_add($$, LEXTYPE_CHAR) == -1) 586 return -1; 587 } 588 } 589 | strnotconcat { $$ = $1; } 590 591strnotconcat: STRING { $$ = $1; } 592 | OPENPAREN str CLOSEPAREN { $$ = $2; } 593 | VARIABLE { if (keynote_exceptionflag || keynote_donteval) 594 $$ = NULL; 595 else 596 { 597 $$ = my_lookup($1); 598 keynote_lex_remove($1); 599 free($1); 600 if ($$ == NULL) 601 { 602 if (keynote_errno) 603 return -1; 604 $$ = strdup(""); 605 } 606 else 607 $$ = strdup($$); 608 609 if ($$ == NULL) 610 { 611 keynote_errno = ERROR_MEMORY; 612 return -1; 613 } 614 615 if (keynote_lex_add($$, LEXTYPE_CHAR) == -1) 616 return -1; 617 } 618 } 619 | DEREF str { if (keynote_exceptionflag || keynote_donteval) 620 $$ = NULL; 621 else 622 { 623 $$ = my_lookup($2); 624 keynote_lex_remove($2); 625 free($2); 626 if ($$ == NULL) 627 { 628 if (keynote_errno) 629 return -1; 630 $$ = strdup(""); 631 } 632 else 633 $$ = strdup($$); 634 635 if ($$ == NULL) 636 { 637 keynote_errno = ERROR_MEMORY; 638 return -1; 639 } 640 641 if (keynote_lex_add($$, LEXTYPE_CHAR) == -1) 642 return -1; 643 } 644 } 645%% 646 647/* 648 * Find all assertions signed by s and give us the one with the highest 649 * return value. 650 */ 651static int 652resolve_assertion(char *s) 653{ 654 int i, alg = KEYNOTE_ALGORITHM_NONE, p = 0; 655 void *key = (void *) s; 656 struct assertion *as; 657 struct keylist *kl; 658 659 kl = keynote_keylist_find(keynote_current_assertion->as_keylist, s); 660 if (kl != NULL) 661 { 662 alg = kl->key_alg; 663 key = kl->key_key; 664 } 665 666 for (i = 0;; i++) 667 { 668 as = keynote_find_assertion(key, i, alg); 669 if (as == NULL) /* Gone through all of them */ 670 return p; 671 672 if (as->as_kresult == KRESULT_DONE) 673 if (p < as->as_result) 674 p = as->as_result; 675 676 /* Short circuit if we find an assertion with maximum return value */ 677 if (p == (keynote_current_session->ks_values_num - 1)) 678 return p; 679 } 680 681 return 0; 682} 683 684/* 685 * Environment variable lookup. 686 */ 687static char * 688my_lookup(char *s) 689{ 690 struct keynote_session *ks = keynote_current_session; 691 char *ret; 692 693 if (!strcmp(s, "_MIN_TRUST")) 694 { 695 keynote_used_variable = 1; 696 return ks->ks_values[0]; 697 } 698 else 699 { 700 if (!strcmp(s, "_MAX_TRUST")) 701 { 702 keynote_used_variable = 1; 703 return ks->ks_values[ks->ks_values_num - 1]; 704 } 705 else 706 { 707 if (!strcmp(s, "_VALUES")) 708 { 709 keynote_used_variable = 1; 710 return keynote_env_lookup("_VALUES", ks->ks_env_table, 711 HASHTABLESIZE); 712 } 713 else 714 { 715 if (!strcmp(s, "_ACTION_AUTHORIZERS")) 716 { 717 keynote_used_variable = 1; 718 return keynote_env_lookup("_ACTION_AUTHORIZERS", 719 ks->ks_env_table, HASHTABLESIZE); 720 } 721 } 722 } 723 } 724 725 /* Temporary list (regexp results) */ 726 if (keynote_temp_list != NULL) 727 { 728 ret = keynote_env_lookup(s, &keynote_temp_list, 1); 729 if (ret != NULL) 730 return ret; 731 else 732 if (keynote_errno != 0) 733 return NULL; 734 } 735 736 /* Local-Constants */ 737 if (keynote_init_list != NULL) 738 { 739 ret = keynote_env_lookup(s, &keynote_init_list, 1); 740 if (ret != NULL) 741 return ret; 742 else 743 if (keynote_errno != 0) 744 return NULL; 745 } 746 747 if (ks != NULL) 748 { 749 /* Action environment */ 750 ret = keynote_env_lookup(s, ks->ks_env_table, HASHTABLESIZE); 751 if (ret != NULL) 752 { 753 keynote_used_variable = 1; 754 return ret; 755 } 756 else 757 if (keynote_errno != 0) 758 return NULL; 759 } 760 761 /* Regex table */ 762 if ((ks != NULL) && (ks->ks_env_regex != NULL)) 763 { 764 ret = keynote_env_lookup(s, &(ks->ks_env_regex), 1); 765 if (ret != NULL) 766 { 767 keynote_used_variable = 1; 768 return ret; 769 } 770 771 return NULL; 772 } 773 774 return NULL; 775} 776 777/* 778 * If we had an exception, the boolean expression should return false. 779 * Otherwise, return the result of the expression (the argument). 780 */ 781static int 782checkexception(int i) 783{ 784 if (keynote_exceptionflag) 785 { 786 keynote_exceptionflag = 0; 787 return 0; 788 } 789 else 790 return i; 791} 792 793 794/* 795 * Integer exponentiation -- copied from Schneier's AC2, page 244. 796 */ 797static int 798intpow(int x, int y) 799{ 800 int s = 1; 801 802 /* 803 * x^y with y < 0 is equivalent to 1/(x^y), which for 804 * integer arithmetic is 0. 805 */ 806 if (y < 0) 807 return 0; 808 809 while (y) 810 { 811 if (y & 1) 812 s *= x; 813 814 y >>= 1; 815 x *= x; 816 } 817 818 return s; 819} 820 821/* 822 * Check whether the string is a floating point number. 823 */ 824static int 825isfloatstring(char *s) 826{ 827 int i, point = 0; 828 829 for (i = strlen(s) - 1; i >= 0; i--) 830 if (!isdigit((unsigned char)s[i])) 831 { 832 if (s[i] == '.') 833 { 834 if (point == 1) 835 return 0; 836 else 837 point = 1; 838 } 839 else 840 return 0; 841 } 842 843 return 1; 844} 845 846/* 847 * Initialize array for threshold search. 848 */ 849static int 850keynote_init_kth(void) 851{ 852 int i = keynote_current_session->ks_values_num; 853 854 if (i == -1) 855 return -1; 856 857 keynote_kth_array = calloc(i, sizeof(int)); 858 if (keynote_kth_array == NULL) 859 { 860 keynote_errno = ERROR_MEMORY; 861 return -1; 862 } 863 864 return RESULT_TRUE; 865} 866 867/* 868 * Get the k-th best return value. 869 */ 870static int 871get_kth(int k) 872{ 873 int i; 874 875 for (i = keynote_current_session->ks_values_num - 1; i >= 0; i--) 876 { 877 k -= keynote_kth_array[i]; 878 879 if (k <= 0) 880 return i; 881 } 882 883 return 0; 884} 885 886/* 887 * Cleanup array. 888 */ 889void 890keynote_cleanup_kth(void) 891{ 892 if (keynote_kth_array != NULL) 893 { 894 free(keynote_kth_array); 895 keynote_kth_array = NULL; 896 } 897} 898 899void 900knerror(char *s) 901{} 902