Minimal plaintext password store https://fdpl.io/sicuit/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

1343 lines
45 KiB

  1. /* Required by GPGME to determine large file system (LFS) support. */
  2. #ifdef HAVE_CONFIG_H
  3. #include <config.h>
  4. #endif
  5. #include <dirent.h>
  6. #include <errno.h>
  7. #include <gpgme.h>
  8. #include <locale.h>
  9. #include <ncurses.h>
  10. #include <pwd.h>
  11. #include <stdio.h>
  12. #include <stdlib.h>
  13. #include <string.h>
  14. #include <sys/stat.h>
  15. #include <unicode/ucol.h>
  16. #include <unistd.h>
  17. #include "eno.h"
  18. #define CYAN_ON_BLACK 1
  19. #define MAGENTA_ON_BLACK 2
  20. #define RED_ON_BLACK 3
  21. #define YELLOW_ON_BLACK 4
  22. #define ENTRIES_INITIAL_SIZE 128
  23. #define GPG_KEY_FINGERPRINT_BUFFER_SIZE 41
  24. #define PRINT_BUFFER_SIZE 1024
  25. #define QUERY_STRING_MAX_SIZE 256
  26. const char DEFAULT_STORE[] =
  27. "> This is your sicuit password store\n"
  28. "> For usage instructions go to https://codeberg.org/simonrepp/sicuit\n"
  29. "\n"
  30. "# Example Category\n"
  31. "\n"
  32. "alice@example.com: password_of_alice\n"
  33. "\n"
  34. "## Example Subcategory\n"
  35. "\n"
  36. "bob@example.com:\n"
  37. "Password = password_of_bob\n"
  38. "Server = imap.example.com\n"
  39. "User = bob\n"
  40. ;
  41. struct StoreEntry {
  42. char *key;
  43. unsigned int key_size;
  44. int match_offset;
  45. int special_role; /* 0 (none), -1 (comment), 1..n (list item number) */
  46. char *value;
  47. unsigned int value_size;
  48. };
  49. struct QueryContext {
  50. unsigned int available_columns;
  51. unsigned int available_lines;
  52. struct StoreEntry *entries;
  53. unsigned int entries_count;
  54. unsigned int entries_size;
  55. unsigned int displayed;
  56. unsigned int num_found;
  57. unsigned int hide_keys;
  58. char query_string[QUERY_STRING_MAX_SIZE];
  59. unsigned int query_string_size;
  60. int selected_index;
  61. UCollator *unicode_collator;
  62. UErrorCode unicode_status;
  63. };
  64. void build_entries(struct QueryContext *context, ENOIterator *iterator, char *key_hierarchy_buffer, size_t key_hierarchy_buffer_size);
  65. unsigned int decrypt_file_to_file(FILE *store_file, FILE *temp_file);
  66. unsigned int decrypt_file_to_memory(FILE *store_file, char **store_buffer, size_t *store_size);
  67. int edit_store(char **argv);
  68. unsigned int encrypt_file_to_file(FILE *temp_file, FILE *store_file, char *gpg_key_fingerprint);
  69. unsigned int encrypt_memory_to_file(const char *buffer, size_t size, FILE *store_file, char *gpg_key_fingerprint);
  70. void filter_results(struct QueryContext *context);
  71. char *get_editor();
  72. char *get_home_dir();
  73. char *get_temp_dir();
  74. int init_store();
  75. unsigned int load_config(char *home_dir, char *gpg_key_fingerprint, unsigned int *hide_keys);
  76. void move_selection_cursor(struct QueryContext *context, unsigned int offset);
  77. void print_selection_cursor(struct QueryContext *context, int previously_selected_index);
  78. void print_query_string(struct QueryContext *context);
  79. void print_results(struct QueryContext *context);
  80. int query_store();
  81. void reposition_query_cursor(struct QueryContext *context);
  82. int main(int argc, char **argv)
  83. {
  84. if (argc == 1)
  85. return query_store();
  86. if (strcmp(argv[1], "-e") == 0 || strcmp(argv[1], "--edit") == 0)
  87. return edit_store(argv);
  88. if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0) {
  89. printf("Usage: %s [OPTIONS]\n\n"
  90. "Omit OPTIONS to query the store.\n\n"
  91. "-e, --edit: Edit the store in your configured texteditor.\n"
  92. "-i, --init: Initialize the store at ~/.sicuit/.\n"
  93. "-h, --help: Print help text, you're looking at it right now.\n"
  94. "-v, --version: Print version.\n",
  95. argv[0]);
  96. return EXIT_SUCCESS;
  97. }
  98. if (strcmp(argv[1], "-v") == 0 || strcmp(argv[1], "--version") == 0) {
  99. printf("sicuit beta " VERSION "\n");
  100. return EXIT_SUCCESS;
  101. }
  102. if (strcmp(argv[1], "-i") == 0 || strcmp(argv[1], "--init") == 0)
  103. return init_store();
  104. fprintf(stderr, "Unrecognized argument '%s', use --help for usage instructions.\n", argv[1]);
  105. return EXIT_FAILURE;
  106. }
  107. void ensure_entries_capacity(struct QueryContext *context)
  108. {
  109. if (context->entries_count == context->entries_size) {
  110. struct StoreEntry *entries_memo = context->entries;
  111. context->entries_size *= 2;
  112. context->entries = realloc(context->entries, context->entries_size * sizeof(*context->entries));
  113. if (context->entries == NULL) {
  114. /* TODO: Gracefully exit program */
  115. free(entries_memo);
  116. }
  117. }
  118. }
  119. void build_entries(struct QueryContext *context, ENOIterator *iterator, char *key_hierarchy_buffer, size_t key_hierarchy_buffer_size)
  120. {
  121. ENOIterator children_iterator;
  122. size_t size;
  123. char *string;
  124. while (eno_iterate_next(iterator)) {
  125. if (eno_has_value(iterator)) {
  126. ensure_entries_capacity(context);
  127. if (eno_is_item(iterator)) {
  128. context->entries[context->entries_count].key = malloc(key_hierarchy_buffer_size);
  129. memcpy(context->entries[context->entries_count].key, key_hierarchy_buffer, key_hierarchy_buffer_size);
  130. context->entries[context->entries_count].key_size = key_hierarchy_buffer_size;
  131. if (context->entries_count == 0 || context->entries[context->entries_count - 1].special_role < 1) {
  132. context->entries[context->entries_count].special_role = 1;
  133. } else {
  134. context->entries[context->entries_count].special_role = context->entries[context->entries_count - 1].special_role + 1;
  135. }
  136. } else {
  137. eno_get_key(iterator, &string, &size);
  138. if (key_hierarchy_buffer_size > 0) {
  139. context->entries[context->entries_count].key_size = key_hierarchy_buffer_size + 3 + size;
  140. } else {
  141. context->entries[context->entries_count].key_size = size;
  142. }
  143. context->entries[context->entries_count].key = malloc(context->entries[context->entries_count].key_size);
  144. if (key_hierarchy_buffer_size > 0) {
  145. memcpy(context->entries[context->entries_count].key, key_hierarchy_buffer, key_hierarchy_buffer_size);
  146. memcpy(&context->entries[context->entries_count].key[key_hierarchy_buffer_size], " > ", 3);
  147. memcpy(&context->entries[context->entries_count].key[key_hierarchy_buffer_size + 3], string, size);
  148. } else {
  149. memcpy(context->entries[context->entries_count].key, string, size);
  150. }
  151. context->entries[context->entries_count].special_role = 0;
  152. }
  153. eno_get_value(iterator, &string, &size);
  154. context->entries[context->entries_count].value = string;
  155. context->entries[context->entries_count].value_size = size;
  156. context->entries_count++;
  157. if (eno_has_comment(iterator)) {
  158. ensure_entries_capacity(context);
  159. eno_get_comment(iterator, &string, &size);
  160. context->entries[context->entries_count].key = malloc(context->entries[context->entries_count - 1].key_size);
  161. memcpy(context->entries[context->entries_count].key, context->entries[context->entries_count - 1].key, context->entries[context->entries_count - 1].key_size);
  162. context->entries[context->entries_count].key_size = context->entries[context->entries_count - 1].key_size;
  163. context->entries[context->entries_count].value = string;
  164. context->entries[context->entries_count].value_size = size;
  165. context->entries[context->entries_count].special_role = -1;
  166. context->entries_count++;
  167. }
  168. } else if (eno_has_children(iterator)) {
  169. unsigned int key_size;
  170. eno_get_key(iterator, &string, &size);
  171. eno_iterate_children(iterator, &children_iterator);
  172. if (key_hierarchy_buffer_size > 0) {
  173. memcpy(&key_hierarchy_buffer[key_hierarchy_buffer_size], " > ", 3);
  174. key_size = key_hierarchy_buffer_size + 3;
  175. } else {
  176. key_size = key_hierarchy_buffer_size;
  177. }
  178. memcpy(&key_hierarchy_buffer[key_size], string, size);
  179. key_size += size;
  180. if (eno_has_comment(iterator)) {
  181. ensure_entries_capacity(context);
  182. eno_get_comment(iterator, &string, &size);
  183. context->entries[context->entries_count].key = malloc(key_size);
  184. memcpy(context->entries[context->entries_count].key, key_hierarchy_buffer, key_size);
  185. context->entries[context->entries_count].key_size = key_size;
  186. context->entries[context->entries_count].value = string;
  187. context->entries[context->entries_count].value_size = size;
  188. context->entries[context->entries_count].special_role = -1;
  189. context->entries_count++;
  190. }
  191. build_entries(context, &children_iterator, key_hierarchy_buffer, key_size);
  192. }
  193. }
  194. }
  195. unsigned int decrypt_file_to_file(FILE *store_file, FILE *temp_file)
  196. {
  197. gpgme_ctx_t gpgme_ctx;
  198. gpgme_data_t gpgme_data_store;
  199. gpgme_data_t gpgme_data_temp_file;
  200. gpgme_error_t gpgme_err;
  201. setlocale(LC_ALL, "");
  202. gpgme_check_version(NULL);
  203. gpgme_set_locale(NULL, LC_CTYPE, setlocale(LC_CTYPE, NULL));
  204. gpgme_set_locale(NULL, LC_MESSAGES, setlocale(LC_MESSAGES, NULL));
  205. if ((gpgme_err = gpgme_engine_check_version(GPGME_PROTOCOL_OPENPGP))) {
  206. fprintf(stderr,
  207. "The OpenPGP engine is not available (or of an incompatible version) in your environment.\n\n"
  208. "%s: %s\n",
  209. gpgme_strsource(gpgme_err),
  210. gpgme_strerror(gpgme_err));
  211. return 0;
  212. }
  213. if ((gpgme_err = gpgme_new(&gpgme_ctx))) {
  214. fprintf(stderr,
  215. "GPGME context could not be initialized.\n\n"
  216. "%s: %s\n",
  217. gpgme_strsource(gpgme_err),
  218. gpgme_strerror(gpgme_err));
  219. return 0;
  220. }
  221. if ((gpgme_err = gpgme_data_new_from_stream(&gpgme_data_store, store_file))) {
  222. fprintf(stderr,
  223. "GPGME encrypted data buffer could not be initialized.\n\n"
  224. "%s: %s\n",
  225. gpgme_strsource(gpgme_err),
  226. gpgme_strerror(gpgme_err));
  227. return 0;
  228. }
  229. if ((gpgme_err = gpgme_data_new_from_stream(&gpgme_data_temp_file, temp_file))) {
  230. fprintf(stderr,
  231. "GPGME decrypted data buffer could not be initialized.\n\n"
  232. "%s: %s\n",
  233. gpgme_strsource(gpgme_err),
  234. gpgme_strerror(gpgme_err));
  235. return 0;
  236. }
  237. if ((gpgme_err = gpgme_op_decrypt(gpgme_ctx, gpgme_data_store, gpgme_data_temp_file))) {
  238. fprintf(stderr,
  239. "GPGME decryption failed.\n\n"
  240. "%s: %s\n",
  241. gpgme_strsource(gpgme_err),
  242. gpgme_strerror(gpgme_err));
  243. return 0;
  244. }
  245. if (gpgme_op_decrypt_result(gpgme_ctx)->unsupported_algorithm) {
  246. fprintf(stderr, "GPGME decryption failed - unsupported algorithm.\n");
  247. return 0;
  248. }
  249. gpgme_data_release(gpgme_data_temp_file);
  250. gpgme_data_release(gpgme_data_store);
  251. gpgme_release(gpgme_ctx);
  252. return 1;
  253. }
  254. unsigned int decrypt_file_to_memory(FILE *store_file, char **store_buffer, size_t *store_size)
  255. {
  256. gpgme_ctx_t gpgme_ctx;
  257. gpgme_data_t gpgme_data_store;
  258. gpgme_data_t gpgme_data_memory;
  259. gpgme_error_t gpgme_err;
  260. setlocale(LC_ALL, "");
  261. gpgme_check_version(NULL);
  262. gpgme_set_locale(NULL, LC_CTYPE, setlocale(LC_CTYPE, NULL));
  263. gpgme_set_locale(NULL, LC_MESSAGES, setlocale(LC_MESSAGES, NULL));
  264. if ((gpgme_err = gpgme_engine_check_version(GPGME_PROTOCOL_OPENPGP))) {
  265. fprintf(stderr,
  266. "The OpenPGP engine is not available (or of an incompatible version) in your environment.\n\n"
  267. "%s: %s\n",
  268. gpgme_strsource(gpgme_err),
  269. gpgme_strerror(gpgme_err));
  270. return 0;
  271. }
  272. if ((gpgme_err = gpgme_new(&gpgme_ctx))) {
  273. fprintf(stderr,
  274. "GPGME context could not be initialized.\n\n"
  275. "%s: %s\n",
  276. gpgme_strsource(gpgme_err),
  277. gpgme_strerror(gpgme_err));
  278. return 0;
  279. }
  280. if ((gpgme_err = gpgme_data_new_from_stream(&gpgme_data_store, store_file))) {
  281. fprintf(stderr,
  282. "GPGME encrypted data buffer could not be initialized.\n\n"
  283. "%s: %s\n",
  284. gpgme_strsource(gpgme_err),
  285. gpgme_strerror(gpgme_err));
  286. return 0;
  287. }
  288. if ((gpgme_err = gpgme_data_new(&gpgme_data_memory))) {
  289. fprintf(stderr,
  290. "GPGME decrypted data buffer could not be initialized.\n\n"
  291. "%s: %s\n",
  292. gpgme_strsource(gpgme_err),
  293. gpgme_strerror(gpgme_err));
  294. return 0;
  295. }
  296. if ((gpgme_err = gpgme_op_decrypt(gpgme_ctx, gpgme_data_store, gpgme_data_memory))) {
  297. fprintf(stderr,
  298. "GPGME decryption failed.\n\n"
  299. "%s: %s\n",
  300. gpgme_strsource(gpgme_err),
  301. gpgme_strerror(gpgme_err));
  302. return 0;
  303. }
  304. if (gpgme_op_decrypt_result(gpgme_ctx)->unsupported_algorithm) {
  305. fprintf(stderr, "GPGME decryption failed - unsupported algorithm.\n");
  306. return 0;
  307. }
  308. *store_buffer = gpgme_data_release_and_get_mem(gpgme_data_memory, store_size);
  309. gpgme_data_release(gpgme_data_store);
  310. gpgme_release(gpgme_ctx);
  311. return 1;
  312. }
  313. int edit_store(char **argv)
  314. {
  315. char print_buffer[PRINT_BUFFER_SIZE];
  316. char *editor = get_editor();
  317. char gpg_key_fingerprint[GPG_KEY_FINGERPRINT_BUFFER_SIZE];
  318. char *home_dir = get_home_dir();
  319. FILE *store_file;
  320. char *temp_dir = get_temp_dir();
  321. FILE *temp_file;
  322. if (!load_config(home_dir, gpg_key_fingerprint, NULL))
  323. return EXIT_FAILURE;
  324. if (editor == NULL) {
  325. fprintf(stderr,
  326. "Your preferred editor could not be determined:\n"
  327. "- The environment variables EDITOR, VISUAL, SELECTED_EDITOR are empty\n"
  328. "- Neither of nano, nano-tiny or vi exist in any of the directories in PATH\n\n"
  329. "You can run 'EDITOR=insert_your_editor %s %s' to temporarily specify an editor.\n",
  330. argv[0],
  331. argv[1]);
  332. return EXIT_FAILURE;
  333. }
  334. sprintf(print_buffer, "%s/.sicuit/store.eno.gpg", home_dir);
  335. store_file = fopen(print_buffer, "rb");
  336. if (store_file == NULL) {
  337. fprintf(stderr,
  338. "Could not open the store at '%s'.\n"
  339. "Make sure it exists and has appropriate permissions set.\n", /* TODO: Mention the critically required permissions */
  340. print_buffer);
  341. return EXIT_FAILURE;
  342. }
  343. sprintf(print_buffer, "%s/sicuit_store.eno", temp_dir);
  344. temp_file = fopen(print_buffer, "wb");
  345. if (temp_file == NULL) {
  346. fprintf(stderr,
  347. "Could not create the temporary file for editing the store at '%s'.\n",
  348. print_buffer);
  349. return EXIT_FAILURE;
  350. }
  351. if (!decrypt_file_to_file(store_file, temp_file))
  352. return EXIT_FAILURE;
  353. fclose(temp_file);
  354. fclose(store_file);
  355. sprintf(print_buffer, "%s %s/sicuit_store.eno", editor, temp_dir);
  356. system(print_buffer);
  357. sprintf(print_buffer, "%s/sicuit_store.eno", temp_dir);
  358. temp_file = fopen(print_buffer, "rb");
  359. if (temp_file == NULL) {
  360. fprintf(stderr,
  361. "Could not open the temporary file at '%s'.\n",
  362. print_buffer);
  363. return EXIT_FAILURE;
  364. }
  365. sprintf(print_buffer, "%s/.sicuit/store.eno.gpg", home_dir);
  366. store_file = fopen(print_buffer, "wb");
  367. if (store_file == NULL) {
  368. fprintf(stderr,
  369. "Could not open the store at '%s' for writing.\n",
  370. print_buffer);
  371. return EXIT_FAILURE;
  372. }
  373. if (!encrypt_file_to_file(temp_file, store_file, gpg_key_fingerprint))
  374. return EXIT_FAILURE;
  375. fclose(store_file);
  376. fclose(temp_file);
  377. sprintf(print_buffer, "%s/sicuit_store.eno", temp_dir);
  378. remove(print_buffer);
  379. printf("The store has been updated.\n");
  380. return EXIT_SUCCESS;
  381. }
  382. unsigned int encrypt_file_to_file(FILE *temp_file, FILE *store_file, char *gpg_key_fingerprint)
  383. {
  384. gpgme_ctx_t gpgme_ctx;
  385. gpgme_data_t gpgme_data_store;
  386. gpgme_data_t gpgme_data_temp_file;
  387. gpgme_error_t gpgme_err;
  388. gpgme_key_t gpgme_keys[2]; /* we only have one key but gpgme_op_encrypt takes a null-terminated array */
  389. setlocale(LC_ALL, "");
  390. gpgme_check_version(NULL);
  391. gpgme_set_locale(NULL, LC_CTYPE, setlocale(LC_CTYPE, NULL));
  392. gpgme_set_locale(NULL, LC_MESSAGES, setlocale(LC_MESSAGES, NULL));
  393. if ((gpgme_err = gpgme_engine_check_version(GPGME_PROTOCOL_OPENPGP))) {
  394. fprintf(stderr,
  395. "The OpenPGP engine is not available (or of an incompatible version) in your environment.\n\n"
  396. "%s: %s\n",
  397. gpgme_strsource(gpgme_err),
  398. gpgme_strerror(gpgme_err));
  399. return 0;
  400. }
  401. if ((gpgme_err = gpgme_new(&gpgme_ctx))) {
  402. fprintf(stderr,
  403. "GPGME context could not be initialized.\n\n"
  404. "%s: %s\n",
  405. gpgme_strsource(gpgme_err),
  406. gpgme_strerror(gpgme_err));
  407. return 0;
  408. }
  409. if ((gpgme_err = gpgme_get_key(gpgme_ctx, gpg_key_fingerprint, &gpgme_keys[0], 0))) {
  410. fprintf(stderr,
  411. "GPG key with ID '%s' could not be obtained from the GPGME backend.\n\n"
  412. "%s: %s\n",
  413. gpg_key_fingerprint,
  414. gpgme_strsource(gpgme_err),
  415. gpgme_strerror(gpgme_err));
  416. return 0;
  417. }
  418. if ((gpgme_err = gpgme_data_new_from_stream(&gpgme_data_temp_file, temp_file))) {
  419. fprintf(stderr,
  420. "GPGME decrypted data buffer could not be initialized.\n\n"
  421. "%s: %s\n",
  422. gpgme_strsource(gpgme_err),
  423. gpgme_strerror(gpgme_err));
  424. return 0;
  425. }
  426. if ((gpgme_err = gpgme_data_new_from_stream(&gpgme_data_store, store_file))) {
  427. fprintf(stderr,
  428. "GPGME encrypted data buffer could not be initialized.\n\n"
  429. "%s: %s\n",
  430. gpgme_strsource(gpgme_err),
  431. gpgme_strerror(gpgme_err));
  432. return 0;
  433. }
  434. gpgme_keys[1] = NULL;
  435. if ((gpgme_err = gpgme_op_encrypt(gpgme_ctx, gpgme_keys, GPGME_ENCRYPT_ALWAYS_TRUST, gpgme_data_temp_file, gpgme_data_store))) {
  436. fprintf(stderr,
  437. "GPGME encryption failed.\n\n"
  438. "%s: %s\n",
  439. gpgme_strsource(gpgme_err),
  440. gpgme_strerror(gpgme_err));
  441. return 0;
  442. }
  443. if (gpgme_op_encrypt_result(gpgme_ctx)->invalid_recipients) {
  444. fprintf(stderr, "GPGME encryption failed - invalid recipients.\n");
  445. return 0;
  446. }
  447. gpgme_key_unref(gpgme_keys[0]);
  448. gpgme_data_release(gpgme_data_store);
  449. gpgme_data_release(gpgme_data_temp_file);
  450. gpgme_release(gpgme_ctx);
  451. return 1;
  452. }
  453. unsigned int encrypt_memory_to_file(const char *buffer, size_t size, FILE *store_file, char *gpg_key_fingerprint)
  454. {
  455. gpgme_ctx_t gpgme_ctx;
  456. gpgme_data_t gpgme_data_store;
  457. gpgme_data_t gpgme_data_memory;
  458. gpgme_error_t gpgme_err;
  459. gpgme_key_t gpgme_keys[2]; /* we only have one key but gpgme_op_encrypt takes a null-terminated array */
  460. setlocale(LC_ALL, "");
  461. gpgme_check_version(NULL);
  462. gpgme_set_locale(NULL, LC_CTYPE, setlocale(LC_CTYPE, NULL));
  463. gpgme_set_locale(NULL, LC_MESSAGES, setlocale(LC_MESSAGES, NULL));
  464. if ((gpgme_err = gpgme_engine_check_version(GPGME_PROTOCOL_OPENPGP))) {
  465. fprintf(stderr,
  466. "The OpenPGP engine is not available (or of an incompatible version) in your environment.\n\n"
  467. "%s: %s\n",
  468. gpgme_strsource(gpgme_err),
  469. gpgme_strerror(gpgme_err));
  470. return 0;
  471. }
  472. if ((gpgme_err = gpgme_new(&gpgme_ctx))) {
  473. fprintf(stderr,
  474. "GPGME context could not be initialized.\n\n"
  475. "%s: %s\n",
  476. gpgme_strsource(gpgme_err),
  477. gpgme_strerror(gpgme_err));
  478. return 0;
  479. }
  480. if ((gpgme_err = gpgme_get_key(gpgme_ctx, gpg_key_fingerprint, &gpgme_keys[0], 0))) {
  481. fprintf(stderr,
  482. "GPG key with ID '%s' could not be obtained from the GPGME backend.\n\n"
  483. "%s: %s\n",
  484. gpg_key_fingerprint,
  485. gpgme_strsource(gpgme_err),
  486. gpgme_strerror(gpgme_err));
  487. return 0;
  488. }
  489. if ((gpgme_err = gpgme_data_new_from_mem(&gpgme_data_memory, buffer, size, 0))) {
  490. fprintf(stderr,
  491. "GPGME unencrypted memory data buffer could not be initialized.\n\n"
  492. "%s: %s\n",
  493. gpgme_strsource(gpgme_err),
  494. gpgme_strerror(gpgme_err));
  495. return 0;
  496. }
  497. if ((gpgme_err = gpgme_data_new_from_stream(&gpgme_data_store, store_file))) {
  498. fprintf(stderr,
  499. "GPGME encrypted data buffer could not be initialized.\n\n"
  500. "%s: %s\n",
  501. gpgme_strsource(gpgme_err),
  502. gpgme_strerror(gpgme_err));
  503. return 0;
  504. }
  505. gpgme_keys[1] = NULL;
  506. if ((gpgme_err = gpgme_op_encrypt(gpgme_ctx, gpgme_keys, GPGME_ENCRYPT_ALWAYS_TRUST, gpgme_data_memory, gpgme_data_store))) {
  507. fprintf(stderr,
  508. "GPGME encryption failed.\n\n"
  509. "%s: %s\n",
  510. gpgme_strsource(gpgme_err),
  511. gpgme_strerror(gpgme_err));
  512. return 0;
  513. }
  514. if (gpgme_op_encrypt_result(gpgme_ctx)->invalid_recipients) {
  515. fprintf(stderr, "GPGME encryption failed - invalid recipients.\n");
  516. return 0;
  517. }
  518. gpgme_key_unref(gpgme_keys[0]);
  519. gpgme_data_release(gpgme_data_store);
  520. gpgme_data_release(gpgme_data_memory);
  521. gpgme_release(gpgme_ctx);
  522. return 1;
  523. }
  524. void filter_results(struct QueryContext *context)
  525. {
  526. unsigned int entry_index;
  527. int offset;
  528. UCollationResult result;
  529. context->num_found = 0;
  530. for (entry_index = 0; entry_index < context->entries_count; entry_index++) {
  531. offset = 0;
  532. context->entries[entry_index].match_offset = -1;
  533. while (offset + context->query_string_size <= context->entries[entry_index].key_size) {
  534. result = ucol_strcollUTF8(context->unicode_collator,
  535. context->entries[entry_index].key + offset,
  536. context->query_string_size,
  537. context->query_string,
  538. context->query_string_size,
  539. &context->unicode_status);
  540. if (result == UCOL_EQUAL) {
  541. context->entries[entry_index].match_offset = offset;
  542. context->num_found++;
  543. break;
  544. }
  545. offset++;
  546. }
  547. }
  548. }
  549. char *get_editor()
  550. {
  551. char *editor;
  552. if ((editor = getenv("EDITOR")) != NULL)
  553. return editor;
  554. if ((editor = getenv("VISUAL")) != NULL)
  555. return editor;
  556. if ((editor = getenv("SELECTED_EDITOR")) != NULL)
  557. return editor;
  558. /* TODO: Manually inspect PATH instead of delegating to 'which' below?
  559. * see e.g. https://stackoverflow.com/questions/41230547/check-if-program-is-installed-in-c */
  560. if (system("which nano &> /dev/null") == EXIT_SUCCESS)
  561. return "nano";
  562. if (system("which nano-tiny &> /dev/null") == EXIT_SUCCESS)
  563. return "nano-tiny";
  564. if (system("which vi &> /dev/null") == EXIT_SUCCESS)
  565. return "vi";
  566. return NULL;
  567. }
  568. char *get_home_dir()
  569. {
  570. char *home_dir = getenv("$XDG_CONFIG_HOME");
  571. if (home_dir != NULL) return home_dir;
  572. home_dir = getenv("HOME");
  573. if (home_dir != NULL) return home_dir;
  574. return getpwuid(getuid())->pw_dir;
  575. }
  576. /**
  577. * Probes and returns one of the following locations, based on availability:
  578. * - "/dev/shm" (first priority - non-persistent by technical nature [tmpfs])
  579. * - "/run" (second priority - non-persistent by specification [tmpfs or removal/truncation at next boot])
  580. * - "/tmp" (always available - non-persistent by chance [completely unspecified])
  581. */
  582. char *get_temp_dir()
  583. {
  584. DIR* dir_handle;
  585. if ((dir_handle = opendir("/dev/shm")) != NULL) {
  586. closedir(dir_handle);
  587. return "/dev/shm";
  588. }
  589. if ((dir_handle = opendir("/run")) != NULL) {
  590. closedir(dir_handle);
  591. return "/run";
  592. }
  593. return "/tmp";
  594. }
  595. int init_store()
  596. {
  597. FILE *config_file;
  598. char gpg_key_fingerprint[GPG_KEY_FINGERPRINT_BUFFER_SIZE];
  599. gpgme_ctx_t gpgme_ctx;
  600. gpgme_error_t gpgme_err;
  601. gpgme_key_t gpgme_key;
  602. char *home_dir = get_home_dir();
  603. char print_buffer[PRINT_BUFFER_SIZE];
  604. DIR *sicuit_dir;
  605. FILE *store_file;
  606. sprintf(print_buffer, "%s/.sicuit", home_dir);
  607. sicuit_dir = opendir(print_buffer);
  608. if (sicuit_dir != NULL || errno != ENOENT) {
  609. closedir(sicuit_dir);
  610. fprintf(stderr,
  611. "A previously initialized store directory at '%s' already exists.\n"
  612. "If you really want to replace it, remove it manually, then re-run 'sicuit --init'.\n",
  613. print_buffer);
  614. return EXIT_FAILURE;
  615. }
  616. setlocale(LC_ALL, "");
  617. gpgme_check_version(NULL);
  618. gpgme_set_locale(NULL, LC_CTYPE, setlocale(LC_CTYPE, NULL));
  619. gpgme_set_locale(NULL, LC_MESSAGES, setlocale(LC_MESSAGES, NULL));
  620. if ((gpgme_err = gpgme_engine_check_version(GPGME_PROTOCOL_OPENPGP))) {
  621. fprintf(stderr,
  622. "The OpenPGP engine is not available (or of an incompatible version) in your environment.\n\n%s: %s\n",
  623. gpgme_strsource(gpgme_err),
  624. gpgme_strerror(gpgme_err));
  625. return EXIT_FAILURE;
  626. }
  627. if ((gpgme_err = gpgme_new(&gpgme_ctx))) {
  628. fprintf(stderr,
  629. "GPGME context could not be initialized.\n\n%s: %s\n",
  630. gpgme_strsource(gpgme_err),
  631. gpgme_strerror(gpgme_err));
  632. return EXIT_FAILURE;
  633. }
  634. if ((gpgme_err = gpgme_op_keylist_start(gpgme_ctx, NULL, 1))) {
  635. fprintf(stderr,
  636. "GPG key listing could not be started.\n\n%s: %s\n",
  637. gpgme_strsource(gpgme_err),
  638. gpgme_strerror(gpgme_err));
  639. return EXIT_FAILURE;
  640. }
  641. initscr();
  642. start_color();
  643. noecho();
  644. cbreak();
  645. init_pair(MAGENTA_ON_BLACK, COLOR_MAGENTA, COLOR_BLACK);
  646. while (1) {
  647. gpgme_err = gpgme_op_keylist_next(gpgme_ctx, &gpgme_key);
  648. if (gpgme_err) {
  649. /* TODO: Investigate why this returns 117456895 on clean end of iteration (?) instead of 16383 (GPG_ERR_EOF) */
  650. /* if (gpgme_err == GPG_ERR_EOF) */
  651. break;
  652. fprintf(stderr,
  653. "GPG key listing failed in between.\n\n%s: %s\n",
  654. gpgme_strsource(gpgme_err),
  655. gpgme_strerror(gpgme_err));
  656. return EXIT_FAILURE;
  657. }
  658. sprintf(gpg_key_fingerprint, "%s", gpgme_key->fpr);
  659. attron(A_BOLD);
  660. mvprintw(1, 2, "### sicuit store initialization");
  661. attroff(A_BOLD);
  662. mvprintw(3, 2, "Use this key for encrypting the store?");
  663. attron(A_BOLD | COLOR_PAIR(MAGENTA_ON_BLACK));
  664. mvprintw(5, 2, "%s", gpgme_key->subkeys->keyid);
  665. if (gpgme_key->uids && gpgme_key->uids->name)
  666. printw(": %s", gpgme_key->uids->name);
  667. if (gpgme_key->uids && gpgme_key->uids->email)
  668. printw(" <%s>", gpgme_key->uids->email);
  669. attroff(A_BOLD | COLOR_PAIR(MAGENTA_ON_BLACK));
  670. mvprintw(7, 2, "ENTER to confirm, ESCAPE for next key.");
  671. mvprintw(8, 2, "----- ------");
  672. refresh();
  673. gpgme_key_release(gpgme_key);
  674. switch (getch()) {
  675. case 10: /* ENTER */
  676. goto key_confirmed;
  677. case 27: /* ESC */
  678. break;
  679. }
  680. }
  681. endwin();
  682. printf("No GPG key was found or chosen, hence no store was initialized.\n");
  683. return EXIT_SUCCESS;
  684. key_confirmed:
  685. endwin();
  686. gpgme_release(gpgme_ctx);
  687. if (mkdir(print_buffer, 0755)) {
  688. fprintf(stderr,
  689. "The store directory at '%s' can not be created.\n"
  690. "Make sure that your current user has adequate permissions, then re-run 'sicuit --init'.\n",
  691. print_buffer);
  692. return EXIT_FAILURE;
  693. }
  694. sprintf(print_buffer, "%s/.sicuit/config.eno", home_dir);
  695. config_file = fopen(print_buffer, "wb");
  696. if (config_file == NULL) {
  697. fprintf(stderr,
  698. "The config file at '%s' cannot be written to.\n"
  699. "Make sure that your current user has adequate permissions, then re-run 'sicuit --init'.\n",
  700. print_buffer);
  701. return EXIT_FAILURE;
  702. } else {
  703. sprintf(print_buffer,
  704. "gpg_key_fingerprint: %s\n"
  705. "hide_keys: auto\n",
  706. gpg_key_fingerprint);
  707. fwrite(print_buffer, strlen(print_buffer), 1, config_file);
  708. }
  709. fclose(config_file);
  710. sprintf(print_buffer, "%s/.sicuit/store.eno.gpg", home_dir);
  711. store_file = fopen(print_buffer, "wb");
  712. if (store_file == NULL) {
  713. fprintf(stderr,
  714. "The store file at '%s' cannot be written to.\n"
  715. "Make sure that your current user has adequate permissions, then re-run 'sicuit --init'.\n",
  716. print_buffer);
  717. return EXIT_FAILURE;
  718. }
  719. encrypt_memory_to_file(DEFAULT_STORE, strlen(DEFAULT_STORE), store_file, gpg_key_fingerprint);
  720. fclose(store_file);
  721. printf("The store has been initialized at '%s/.sicuit/'.\n", home_dir);
  722. return EXIT_SUCCESS;
  723. }
  724. unsigned int load_config(char *home_dir, char *gpg_key_fingerprint, unsigned int *hide_keys)
  725. {
  726. FILE *config_file;
  727. ENOIterator config_iterator;
  728. size_t token_size;
  729. char *token;
  730. char print_buffer[PRINT_BUFFER_SIZE];
  731. sprintf(print_buffer, "%s/.sicuit/config.eno", home_dir);
  732. config_file = fopen(print_buffer, "rb");
  733. if (config_file == NULL) {
  734. fprintf(stderr,
  735. "Could not open config file at '%s'.\nIf you are using sicuit for the first time, please run 'sicuit --init' first.\n",
  736. print_buffer);
  737. return 0;
  738. }
  739. if (!eno_parse_stream(&config_iterator, config_file)) {
  740. fprintf(stderr,
  741. "The config file at '%s' contains errors.\n",
  742. print_buffer);
  743. eno_report_error(&config_iterator);
  744. eno_free_document(&config_iterator);
  745. return 0;
  746. }
  747. gpg_key_fingerprint[0] = '\0';
  748. while (eno_iterate_next(&config_iterator)) {
  749. if (eno_compare_key(&config_iterator, "gpg_key_fingerprint")) {
  750. eno_get_value(&config_iterator, &token, &token_size);
  751. if (token == NULL) {
  752. fprintf(stderr,
  753. "The config file at '%s' specifies an empty gpg_key_fingerprint.\n",
  754. print_buffer);
  755. return 0;
  756. }
  757. sprintf(gpg_key_fingerprint, "%.*s", (unsigned int)token_size, token);
  758. } else if (eno_compare_key(&config_iterator, "hide_keys")) {
  759. eno_get_value(&config_iterator, &token, &token_size);
  760. if (token_size == 4 && strncmp(token, "auto", 4) == 0) {
  761. if (hide_keys != NULL)
  762. *hide_keys = 1;
  763. } else if (token_size == 6 && strncmp(token, "manual", 6) == 0) {
  764. if (hide_keys != NULL)
  765. *hide_keys = 0;
  766. } else {
  767. fprintf(stderr,
  768. "The config file at '%s' assigns the unrecognized value '%.*s' to the 'hide_keys' setting.\n"
  769. "Only 'auto' and 'manual' are supported.\n",
  770. print_buffer,
  771. (unsigned int)token_size,
  772. token);
  773. return 0;
  774. }
  775. } else {
  776. eno_get_key(&config_iterator, &token, &token_size);
  777. fprintf(stderr,
  778. "The config file at '%s' contains the unsupported setting '%.*s'.\n\n"
  779. "Available settings are:\n"
  780. "gpg_key_fingerprint: [YOUR GPG KEY FINGERPRINT (20 BYTES)]\n"
  781. "hide_keys: auto|manual\n",
  782. print_buffer,
  783. (unsigned int)token_size,
  784. token);
  785. return 0;
  786. }
  787. }
  788. eno_free_document(&config_iterator);
  789. if (strlen(gpg_key_fingerprint) == 0) {
  790. fprintf(stderr,
  791. "The config file at '%s' does not specify the gpg_key_fingerprint setting.\n"
  792. "The required entry for this is 'gpg_key_fingerprint: [YOUR GPG KEY FINGERPRINT (20 BYTES)]'\n",
  793. print_buffer);
  794. return 0;
  795. }
  796. return 1;
  797. }
  798. void move_selection_cursor(struct QueryContext *context, unsigned int offset)
  799. {
  800. if (context->num_found > 0) {
  801. int previously_selected_index = context->selected_index;
  802. context->selected_index += offset;
  803. if (context->selected_index < 0) {
  804. context->selected_index = 0;
  805. } else if ((unsigned int)context->selected_index >= context->num_found) {
  806. context->selected_index = context->num_found - 1;
  807. }
  808. if ((unsigned int)context->selected_index + 1 > context->available_lines - 2 ||
  809. (unsigned int)previously_selected_index + 1 > context->available_lines - 2)
  810. print_results(context);
  811. print_selection_cursor(context, previously_selected_index);
  812. reposition_query_cursor(context);
  813. refresh();
  814. }
  815. }
  816. void print_selection_cursor(struct QueryContext *context, int previously_selected_index)
  817. {
  818. if (previously_selected_index != -1) {
  819. if ((unsigned int)previously_selected_index + 1 > context->available_lines - 2)
  820. mvprintw(context->available_lines - 1, 0, " ");
  821. else
  822. mvprintw(previously_selected_index + 2, 0, " ");
  823. }
  824. if (context->selected_index != -1) {
  825. attron(A_BOLD | COLOR_PAIR(RED_ON_BLACK));
  826. if ((unsigned int)context->selected_index + 1 > context->available_lines - 2)
  827. mvprintw(context->available_lines - 1, 0, ">");
  828. else
  829. mvprintw(context->selected_index + 2, 0, ">");
  830. attroff(A_BOLD | COLOR_PAIR(RED_ON_BLACK));
  831. }
  832. }
  833. void print_query_string(struct QueryContext *context)
  834. {
  835. unsigned int char_index;
  836. attron(A_BOLD);
  837. if (context->hide_keys) {
  838. move(0, 2);
  839. for (char_index = 0; char_index < context->query_string_size; char_index++) {
  840. addch('#');
  841. }
  842. } else {
  843. mvprintw(0, 2, "%.*s", context->query_string_size, context->query_string);
  844. }
  845. clrtoeol();
  846. attroff(A_BOLD);
  847. }
  848. void print_results(struct QueryContext *context)
  849. {
  850. unsigned int char_index;
  851. unsigned int display_offset;
  852. unsigned int displayed = 0;
  853. unsigned int entry_index;
  854. if (context->selected_index + 1 > (int)context->available_lines - 2) {
  855. display_offset = context->selected_index + 1 - (context->available_lines - 2);
  856. } else {
  857. display_offset = 0;
  858. }
  859. for (entry_index = 0; entry_index < context->entries_count && displayed < context->available_lines - 2; entry_index++) {
  860. struct StoreEntry *entry = &context->entries[entry_index];
  861. if (context->query_string_size == 0) {
  862. if (display_offset > 0) {
  863. display_offset--;
  864. continue;
  865. }
  866. displayed++;
  867. if (context->hide_keys) {
  868. move(displayed + 1, 2);
  869. for (char_index = 0; char_index < entry->key_size; char_index++) {
  870. addch('#'); /*addch("!@#$%&*+=/\\|~"[rand() % 13]);*/
  871. }
  872. } else {
  873. mvprintw(displayed + 1,
  874. 2,
  875. "%.*s",
  876. entry->key_size,
  877. entry->key);
  878. }
  879. if (entry->special_role == -1) {
  880. attron(A_BOLD | COLOR_PAIR(MAGENTA_ON_BLACK));
  881. printw(" (notes)");
  882. attroff(A_BOLD | COLOR_PAIR(MAGENTA_ON_BLACK));
  883. } else if (entry->special_role >= 1) {
  884. attron(A_BOLD | COLOR_PAIR(YELLOW_ON_BLACK));
  885. printw(" (%d)", entry->special_role);
  886. attroff(A_BOLD | COLOR_PAIR(YELLOW_ON_BLACK));
  887. }
  888. clrtoeol();
  889. } else if (entry->match_offset != -1) {
  890. if (display_offset > 0) {
  891. display_offset--;
  892. continue;
  893. }
  894. displayed++;
  895. if (context->hide_keys) {
  896. move(displayed + 1, 2);
  897. for (char_index = 0; (int)char_index < entry->match_offset; char_index++) {
  898. addch('#'); /*addch("!@#$%&*+=/\\|~"[rand() % 13]);*/
  899. }
  900. attron(A_BOLD | COLOR_PAIR(RED_ON_BLACK));
  901. for (char_index = 0; char_index < context->query_string_size; char_index++) {
  902. addch('#');
  903. }
  904. attroff(A_BOLD | COLOR_PAIR(RED_ON_BLACK));
  905. for (char_index = 0; char_index < entry->key_size - entry->match_offset - context->query_string_size; char_index++) {
  906. addch('#'); /*addch("!@#$%&*+=/\\|~"[rand() % 13]);*/
  907. }
  908. } else {
  909. mvprintw(displayed + 1,
  910. 2,
  911. "%.*s",
  912. entry->match_offset,
  913. entry->key);
  914. attron(A_BOLD | COLOR_PAIR(RED_ON_BLACK));
  915. printw("%.*s",
  916. context->query_string_size,
  917. entry->key + entry->match_offset);
  918. attroff(A_BOLD | COLOR_PAIR(RED_ON_BLACK));
  919. printw("%.*s",
  920. entry->key_size - entry->match_offset - context->query_string_size,
  921. entry->key + entry->match_offset + context->query_string_size);
  922. }
  923. if (entry->special_role == -1) {
  924. attron(A_BOLD | COLOR_PAIR(MAGENTA_ON_BLACK));
  925. printw(" (notes)");
  926. attroff(A_BOLD | COLOR_PAIR(MAGENTA_ON_BLACK));
  927. } else if (entry->special_role >= 1) {
  928. attron(A_BOLD | COLOR_PAIR(YELLOW_ON_BLACK));
  929. printw(" (%d)", entry->special_role);
  930. attroff(A_BOLD | COLOR_PAIR(YELLOW_ON_BLACK));
  931. }
  932. clrtoeol();
  933. }
  934. }
  935. if (context->displayed > displayed) {
  936. do {
  937. move(1 + context->displayed--, 2);
  938. clrtoeol();
  939. } while (context->displayed > displayed);
  940. } else {
  941. context->displayed = displayed;
  942. }
  943. }
  944. int query_store()
  945. {
  946. char key_hierarchy_buffer[2048];
  947. int ch;
  948. struct QueryContext context;
  949. unsigned int entry_index;
  950. char gpg_key_fingerprint[GPG_KEY_FINGERPRINT_BUFFER_SIZE];
  951. char *home_dir = get_home_dir();
  952. char print_buffer[PRINT_BUFFER_SIZE];
  953. char *store_buffer;
  954. FILE *store_file;
  955. ENOIterator store_iterator;
  956. size_t store_size;
  957. if (!load_config(home_dir, gpg_key_fingerprint, &context.hide_keys))
  958. return EXIT_FAILURE;
  959. sprintf(print_buffer, "%s/.sicuit/store.eno.gpg", home_dir);
  960. store_file = fopen(print_buffer, "rb");
  961. if (store_file == NULL) {
  962. fprintf(stderr,
  963. "Could not open the store at '%s'.\n"
  964. "Make sure it exists and has appropriate permissions set.\n", /* TODO: Mention the critically required permissions */
  965. print_buffer);
  966. return EXIT_FAILURE;
  967. }
  968. if (!decrypt_file_to_memory(store_file, &store_buffer, &store_size))
  969. return EXIT_FAILURE;
  970. fclose(store_file);
  971. if (!eno_parse_memory(&store_iterator, store_buffer, store_size)) {
  972. eno_report_error(&store_iterator);
  973. eno_free_document(&store_iterator);
  974. gpgme_free(store_buffer);
  975. return EXIT_FAILURE;
  976. }
  977. if (!eno_has_children(&store_iterator)) {
  978. printf("The password store has no entries.\n");
  979. eno_free_document(&store_iterator);
  980. gpgme_free(store_buffer);
  981. return EXIT_SUCCESS;
  982. }
  983. context.entries_count = 0;
  984. context.entries_size = ENTRIES_INITIAL_SIZE;
  985. context.entries = malloc(context.entries_size * sizeof(*context.entries));
  986. context.query_string_size = 0;
  987. context.selected_index = 0;
  988. context.unicode_status = U_ZERO_ERROR;
  989. context.unicode_collator = ucol_open(NULL, &context.unicode_status);
  990. build_entries(&context, &store_iterator, key_hierarchy_buffer, 0);
  991. context.displayed = 0;
  992. context.num_found = context.entries_count;
  993. initscr();
  994. start_color();
  995. noecho();
  996. cbreak();
  997. keypad(stdscr, TRUE);
  998. set_escdelay(0);
  999. init_pair(CYAN_ON_BLACK, COLOR_CYAN, COLOR_BLACK);
  1000. init_pair(MAGENTA_ON_BLACK, COLOR_MAGENTA, COLOR_BLACK);
  1001. init_pair(RED_ON_BLACK, COLOR_RED, COLOR_BLACK);
  1002. init_pair(YELLOW_ON_BLACK, COLOR_YELLOW, COLOR_BLACK);
  1003. attron(A_DIM | COLOR_PAIR(CYAN_ON_BLACK));
  1004. mvprintw(0, 0, "> ");
  1005. attroff(A_DIM | COLOR_PAIR(CYAN_ON_BLACK));
  1006. getmaxyx(stdscr, context.available_lines, context.available_columns);
  1007. print_query_string(&context);
  1008. print_selection_cursor(&context, -1);
  1009. print_results(&context);
  1010. reposition_query_cursor(&context);
  1011. refresh();
  1012. while (1) {
  1013. ch = getch();
  1014. getmaxyx(stdscr, context.available_lines, context.available_columns);
  1015. switch (ch) {
  1016. case KEY_DOWN:
  1017. move_selection_cursor(&context, 1);
  1018. break;
  1019. case KEY_NPAGE:
  1020. move_selection_cursor(&context, context.available_lines - 2);
  1021. break;
  1022. case KEY_PPAGE:
  1023. move_selection_cursor(&context, -(context.available_lines - 2));
  1024. break;
  1025. case KEY_UP:
  1026. move_selection_cursor(&context, -1);
  1027. break;
  1028. case KEY_BACKSPACE: /* fall-through */
  1029. case 127: /* fall-through */
  1030. case '\b':
  1031. if (context.query_string_size > 0) {
  1032. /*
  1033. * TODO: Use ICU to pop off the adequate number of bytes of the last grapheme.
  1034. * Or better yet use the 0/1 codeunit flag trick to pop off until we
  1035. * arrive at the next codeunit, respecitvely do exactly that through ICU?
  1036. */
  1037. context.query_string_size--;
  1038. if (context.query_string_size == 0)
  1039. context.num_found = context.entries_count;
  1040. else
  1041. filter_results(&context);
  1042. if (context.selected_index == -1 && context.num_found > 0) {
  1043. context.selected_index = 0;
  1044. print_selection_cursor(&context, -1);
  1045. }
  1046. print_query_string(&context);
  1047. print_results(&context);
  1048. reposition_query_cursor(&context);
  1049. refresh();
  1050. }
  1051. break;
  1052. case 9: /* TAB */
  1053. context.hide_keys = !context.hide_keys;
  1054. print_query_string(&context);
  1055. print_results(&context);
  1056. reposition_query_cursor(&context);
  1057. refresh();
  1058. break;
  1059. case 10: /* ENTER */
  1060. endwin();
  1061. if (context.selected_index != -1) {
  1062. for (entry_index = 0; entry_index < context.entries_count; entry_index++) {
  1063. if (context.query_string_size == 0 || context.entries[entry_index].match_offset != -1) {
  1064. if (context.selected_index == 0) {
  1065. printf("%.*s",
  1066. context.entries[entry_index].value_size,
  1067. context.entries[entry_index].value);
  1068. break;
  1069. } else {
  1070. context.selected_index--;
  1071. }
  1072. }
  1073. }
  1074. }
  1075. goto end;
  1076. case 27: /* ESC */
  1077. endwin();
  1078. goto end;
  1079. case KEY_LEFT: /* fall-through */
  1080. case KEY_RIGHT:
  1081. break;
  1082. default:
  1083. nodelay(stdscr, TRUE);
  1084. do {
  1085. context.query_string[context.query_string_size++] = ch;
  1086. ch = getch();
  1087. } while (ch != ERR);
  1088. nodelay(stdscr, FALSE);
  1089. filter_results(&context);
  1090. if (context.selected_index >= (int)context.num_found) {
  1091. int previously_selected_index = context.selected_index;
  1092. context.selected_index = context.num_found - 1;
  1093. print_selection_cursor(&context, previously_selected_index);
  1094. } else if (context.selected_index == -1 && context.num_found > 0) {
  1095. context.selected_index = 0;
  1096. print_selection_cursor(&context, -1);
  1097. }
  1098. print_query_string(&context);
  1099. print_results(&context);
  1100. reposition_query_cursor(&context);
  1101. refresh();
  1102. }
  1103. }
  1104. end:
  1105. for (entry_index = 0; entry_index < context.entries_count; entry_index++)
  1106. free(context.entries[entry_index].key);
  1107. free(context.entries);
  1108. ucol_close(context.unicode_collator);
  1109. eno_free_document(&store_iterator);
  1110. gpgme_free(store_buffer);
  1111. return EXIT_SUCCESS;
  1112. }
  1113. void reposition_query_cursor(struct QueryContext *context)
  1114. {
  1115. move(0, context->query_string_size + 2);
  1116. }