PageRenderTime 46ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/cmd_ui.c

https://gitlab.com/Maffblaster/steel
C | 967 lines | 662 code | 188 blank | 117 comment | 170 complexity | 0f23e159ee54429bf713d7665644fec4 MD5 | raw file
Possible License(s): GPL-3.0
  1. /*
  2. * Copyright (C) 2015 Niko Rosvall <niko@byteptr.com>
  3. *
  4. * This file is part of Steel.
  5. *
  6. * Steel is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * Steel is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with Steel. If not, see <http://www.gnu.org/licenses/>.
  18. *
  19. */
  20. #define _XOPEN_SOURCE 700
  21. #include <stdio.h>
  22. #include <ctype.h>
  23. #include <stdlib.h>
  24. #include <stdbool.h>
  25. #include <string.h>
  26. #include <termios.h>
  27. #include "database.h"
  28. #include "crypto.h"
  29. #include "cmd_ui.h"
  30. #include "status.h"
  31. #include "backup.h"
  32. /*cmd_ui.c implements simple interface for command line version
  33. *of Steel. All functions in here are only called from main()
  34. */
  35. /*Removes new line character from a string.*/
  36. static void
  37. strip_newline_str(char *str)
  38. {
  39. char *i = str;
  40. char *j = str;
  41. while (*j != '\0')
  42. {
  43. *i = *j++;
  44. if (*i != '\n')
  45. i++;
  46. }
  47. *i = '\0';
  48. }
  49. /*Function works like strstr, but ignores the case.
  50. *There's a function strcasestr, but it's nonstandard
  51. *GNU extension, so let's not use that.
  52. *Return value must be freed by the caller.
  53. */
  54. static char *
  55. my_strcasestr(const char *str, const char *str2)
  56. {
  57. char *tmp1 = NULL;
  58. char *tmp2 = NULL;
  59. char *tmp3 = NULL;
  60. char *retval = NULL;
  61. tmp1 = strdup(str);
  62. if (tmp1 == NULL)
  63. {
  64. fprintf(stderr, "Strdup failed\n");
  65. return NULL;
  66. }
  67. tmp2 = strdup(str2);
  68. if (tmp2 == NULL)
  69. {
  70. free(tmp1);
  71. fprintf(stderr, "Strdup failed\n");
  72. return NULL;
  73. }
  74. for (int i = 0; i < strlen(tmp1); i++)
  75. tmp1[i] = tolower(tmp1[i]);
  76. for (int i = 0; i < strlen(tmp2); i++)
  77. tmp2[i] = tolower(tmp2[i]);
  78. tmp3 = strstr(tmp1, tmp2);
  79. if (tmp3 != NULL)
  80. {
  81. retval = strdup(tmp3);
  82. /* Sanity check
  83. * Inform the user that something went wrong
  84. * even the search term was found. Probably never happens.
  85. */
  86. if (retval == NULL)
  87. fprintf(stderr,"Search term found, but strdup failed.\n");
  88. }
  89. free(tmp1);
  90. free(tmp2);
  91. return retval;
  92. }
  93. /*Simple helper function to check if there's an open database
  94. *available.
  95. */
  96. static bool
  97. open_db_exist(const char *message)
  98. {
  99. char *old = NULL;
  100. old = read_path_from_lockfile();
  101. if(old != NULL)
  102. {
  103. if(db_file_exists(old))
  104. {
  105. fprintf(stderr, "An open database exists. To improve security only one\n" \
  106. "passphrase database can be open at once.\n");
  107. fprintf(stderr, "Close %s first before %s another"\
  108. " database.\n", old, message);
  109. free(old);
  110. return true;
  111. }
  112. free(old);
  113. }
  114. return false;
  115. }
  116. /*Simple helper function to check if the steel_dbs file used for
  117. *tracking databases exists.
  118. */
  119. static bool
  120. steel_tracker_file_exists()
  121. {
  122. char *dbs = NULL;
  123. dbs = status_get_file_path();
  124. if(dbs == NULL)
  125. {
  126. fprintf(stderr, "Error getting status file path.\n");
  127. return false;
  128. }
  129. /*We can use db_file_exists function to check any file existence.
  130. *In the end, it's just simple check, not related to databases.
  131. */
  132. if(!db_file_exists(dbs))
  133. {
  134. fprintf(stdout, "No databases found.\n");
  135. free(dbs);
  136. return false;
  137. }
  138. free(dbs);
  139. return true;
  140. }
  141. /*Initialize new database and encrypt it.
  142. *Return false on failure, true on success.
  143. *Path must be a path to a file that does not exists.
  144. */
  145. bool
  146. init_database(const char *path)
  147. {
  148. if(open_db_exist("creating"))
  149. return false;
  150. if(!db_init(path))
  151. {
  152. fprintf(stderr, "Database initialization unsuccessful\n");
  153. return false;
  154. }
  155. status_set_tracking(path);
  156. return true;
  157. }
  158. /*Decrypt database the database pointed by path.
  159. *If decryption fails, function returns false.
  160. */
  161. bool
  162. open_database(const char *path)
  163. {
  164. if(!steel_tracker_file_exists())
  165. return false;
  166. /*Max passphrase length. Should be enough, really.*/
  167. size_t pwdlen = 1024;
  168. char passphrase[pwdlen];
  169. char *ptr = passphrase;
  170. if(open_db_exist("opening"))
  171. return false;
  172. my_getpass(MASTER_PWD_PROMPT, &ptr, &pwdlen, stdin);
  173. if(!db_open(path, passphrase))
  174. {
  175. fprintf(stderr, "Database opening unsuccessful.\n");
  176. return false;
  177. }
  178. return true;
  179. }
  180. /*Encrypt the database. We don't need the path of the database,
  181. *as it's read from the steel_open file. Only one database can be
  182. *open at once.
  183. */
  184. void
  185. close_database()
  186. {
  187. if(!steel_tracker_file_exists())
  188. return;
  189. size_t pwdlen = 1024;
  190. char passphrase[pwdlen];
  191. char *ptr = passphrase;
  192. char pass2[pwdlen];
  193. char *ptr2 = pass2;
  194. char *path = NULL;
  195. path = read_path_from_lockfile();
  196. if(path == NULL)
  197. {
  198. fprintf(stderr, "No open databases found.\n");
  199. return;
  200. }
  201. free(path);
  202. my_getpass(MASTER_PWD_PROMPT, &ptr, &pwdlen, stdin);
  203. my_getpass(MASTER_PWD_PROMPT_RETRY, &ptr2, &pwdlen, stdin);
  204. if(strcmp(passphrase, pass2) != 0)
  205. {
  206. fprintf(stderr, "Passphrases do not match.\n");
  207. return;
  208. }
  209. db_close(passphrase);
  210. }
  211. /*This is called from main(). Adds new entry to the database.*/
  212. void
  213. add_new_entry(char *title, char *user, char *url, char *note)
  214. {
  215. if(!steel_tracker_file_exists())
  216. return;
  217. int id;
  218. /*Should be enough...*/
  219. size_t pwdlen = 1024;
  220. char pass[pwdlen];
  221. char *ptr = pass;
  222. id = db_get_next_id();
  223. if(id == -1)
  224. {
  225. fprintf(stderr, "Failed to add a new entry.\n");
  226. return;
  227. }
  228. my_getpass(ENTRY_PWD_PROMPT, &ptr, &pwdlen, stdin);
  229. Entry_t *entry = list_create(title, user, pass, url, note, id, NULL);
  230. if(!db_add_entry(entry))
  231. {
  232. fprintf(stderr, "Failed to add a new entry.\n");
  233. return;
  234. }
  235. list_free(entry);
  236. }
  237. /*Add new entry interactively*/
  238. void
  239. add_new_entry_interactive()
  240. {
  241. if(!steel_tracker_file_exists())
  242. return;
  243. char title[1024] = {0};
  244. char user[1024] = {0};
  245. char url[1024] = {0};
  246. char notes[1024] = {0};
  247. size_t pwdlen = 1024;
  248. char pass[pwdlen];
  249. char *ptr = pass;
  250. int id;
  251. id = db_get_next_id();
  252. if(id == -1)
  253. {
  254. fprintf(stderr, "Failed to add a new entry.\n");
  255. return;
  256. }
  257. fprintf(stdout, "Title: ");
  258. fgets(title, 1024, stdin);
  259. fprintf(stdout, "Username: ");
  260. fgets(user, 1024, stdin);
  261. fprintf(stdout, "Address: ");
  262. fgets(url, 1024, stdin);
  263. fprintf(stdout, "Notes: ");
  264. fgets(notes, 1024, stdin);
  265. my_getpass(ENTRY_PWD_PROMPT, &ptr, &pwdlen, stdin);
  266. strip_newline_str(title);
  267. strip_newline_str(user);
  268. strip_newline_str(url);
  269. strip_newline_str(notes);
  270. Entry_t *entry = list_create(title, user, pass, url,
  271. notes, id, NULL);
  272. if(!db_add_entry(entry))
  273. {
  274. fprintf(stderr, "Failed to add a new entry.\n");
  275. return;
  276. }
  277. list_free(entry);
  278. }
  279. /*Print all available entries to stdin.
  280. *Database must not be encrypted.
  281. */
  282. void
  283. show_all_entries(int show_passphrase)
  284. {
  285. bool show_pass = false;
  286. if(show_passphrase)
  287. show_pass = true;
  288. if(!steel_tracker_file_exists())
  289. return;
  290. Entry_t *list = db_get_all_entries();
  291. if(list != NULL)
  292. {
  293. list_print(list, show_pass);
  294. list_free(list);
  295. }
  296. }
  297. /*Print one entry by id to stdin, if found.
  298. *Database must not be encrypted.
  299. */
  300. void
  301. show_one_entry(int id, int show_passphrase)
  302. {
  303. bool show_pass = false;
  304. if(show_passphrase)
  305. show_pass = true;
  306. if(!steel_tracker_file_exists())
  307. return;
  308. Entry_t *entry = db_get_entry_by_id(id);
  309. if(entry == NULL)
  310. {
  311. fprintf(stderr, "Cannot show entry with id %d.\n", id);
  312. return;
  313. }
  314. Entry_t *head = entry;
  315. Entry_t *next;
  316. if(head != NULL)
  317. {
  318. /*Skip the first one, it only has our initialization data.*/
  319. next = head->next;
  320. if(next != NULL)
  321. {
  322. list_print_one(next, show_pass);
  323. }
  324. else
  325. {
  326. printf("No entry found with id %d.\n", id);
  327. }
  328. }
  329. list_free(entry);
  330. }
  331. /*Delete entry by id from the database.
  332. *Database must not be encrypted.
  333. */
  334. void
  335. delete_entry(int id)
  336. {
  337. if(!steel_tracker_file_exists())
  338. return;
  339. bool success = false;
  340. char ch;
  341. fprintf(stdout, "Are you sure? (y/N) ");
  342. ch = getc(stdin);
  343. if(ch == 'y' || ch == 'Y')
  344. {
  345. if(!db_delete_entry_by_id(id, &success))
  346. {
  347. fprintf(stderr, "Entry deletion failed.\n");
  348. }
  349. else
  350. {
  351. if(!success)
  352. fprintf(stderr, "No entry found with id %d.\n", id);
  353. }
  354. }
  355. }
  356. /*Print all entries to stdin which has data matching with search.
  357. *Database must not be encrypted.
  358. */
  359. void
  360. find_entries(const char *search, int show_passphrase)
  361. {
  362. bool show_pass = false;
  363. if(show_passphrase)
  364. show_pass = true;
  365. if(!steel_tracker_file_exists())
  366. return;
  367. Entry_t *list = db_get_all_entries();
  368. char *title = NULL;
  369. char *user = NULL;
  370. char *url = NULL;
  371. char *notes = NULL;
  372. if(list == NULL)
  373. {
  374. fprintf(stderr, "Cannot perform the search operation.\n");
  375. return;
  376. }
  377. Entry_t *new_head = list->next;
  378. while(new_head != NULL)
  379. {
  380. /*Search for matching data*/
  381. title = my_strcasestr(new_head->title, search);
  382. user = my_strcasestr(new_head->user, search);
  383. url = my_strcasestr(new_head->url, search);
  384. notes = my_strcasestr(new_head->notes, search);
  385. /*Check if we found something*/
  386. if(title != NULL || user != NULL || url != NULL ||
  387. notes != NULL)
  388. {
  389. list_print_one(new_head, show_pass);
  390. }
  391. if(title != NULL)
  392. free(title);
  393. if(user != NULL)
  394. free(user);
  395. if(url != NULL)
  396. free(url);
  397. if(notes != NULL)
  398. free(notes);
  399. new_head = new_head->next;
  400. }
  401. list_free(list);
  402. }
  403. /*Turns echo of from the terminal and asks for a passphrase.
  404. *Usually stream is stdin. Returns length of the passphrase,
  405. *passphrase is stored to lineptr. Lineptr must be allocated beforehand.
  406. */
  407. size_t
  408. my_getpass(char *prompt, char **lineptr, size_t *n, FILE *stream)
  409. {
  410. struct termios old, new;
  411. int nread;
  412. /*Turn terminal echoing off.*/
  413. if(tcgetattr(fileno(stream), &old) != 0)
  414. return -1;
  415. new = old;
  416. new.c_lflag &= ~ECHO;
  417. if(tcsetattr(fileno(stream), TCSAFLUSH, &new) != 0)
  418. return -1;
  419. if(prompt)
  420. printf("%s", prompt);
  421. /*Read the password.*/
  422. nread = getline(lineptr, n, stream);
  423. if(nread >= 1 && (*lineptr)[nread - 1] == '\n')
  424. {
  425. (*lineptr)[nread - 1] = 0;
  426. nread--;
  427. }
  428. printf("\n");
  429. /*Restore terminal echo.*/
  430. tcsetattr(fileno(stream), TCSAFLUSH, &old);
  431. return nread;
  432. }
  433. /*Replace part of an entry pointed by id. "What" tells the function what to replace
  434. *with the new data. What can be "passphrase", "user", "title", "url" or "notes".
  435. *Database must not be encrypted.
  436. */
  437. void
  438. replace_part(int id, const char *what, const char *new_data)
  439. {
  440. if(!steel_tracker_file_exists())
  441. return;
  442. if(strcmp(what,"passphrase") != 0 && strcmp(what, "user") != 0
  443. && strcmp(what, "title") != 0 && strcmp(what, "url") != 0
  444. && strcmp(what, "notes") !=0)
  445. {
  446. fprintf(stderr, "Only title, user, passphrase, url or notes" \
  447. " can be replaced.\n");
  448. return;
  449. }
  450. Entry_t *entry = NULL;
  451. Entry_t *head = NULL;
  452. entry = db_get_entry_by_id(id);
  453. if(entry == NULL)
  454. {
  455. fprintf(stderr, "Cannot replace %s from entry %d.\n",
  456. what, id);
  457. return;
  458. }
  459. /*Skip the initialization data*/
  460. head = entry->next;
  461. if(head == NULL)
  462. {
  463. fprintf(stderr, "No entry found with id %d.\n", id);
  464. list_free(entry);
  465. return;
  466. }
  467. size_t pwdlen = 1024;
  468. char pass[pwdlen];
  469. char *ptr = pass;
  470. if(strcmp(what, "passphrase") == 0)
  471. {
  472. /*Ok, user want's to replace passphrase.
  473. */
  474. my_getpass(ENTRY_PWD_PROMPT, &ptr, &pwdlen, stdin);
  475. }
  476. if(strcmp(what, "title") == 0)
  477. {
  478. free(head->title);
  479. head->title = strdup(new_data);
  480. }
  481. if(strcmp(what, "user") == 0)
  482. {
  483. free(head->user);
  484. head->user = strdup(new_data);
  485. }
  486. if(strcmp(what, "passphrase") == 0)
  487. {
  488. free(head->pwd);
  489. head->pwd = strdup(pass);
  490. }
  491. if(strcmp(what, "url") == 0)
  492. {
  493. free(head->url);
  494. head->url = strdup(new_data);
  495. }
  496. if(strcmp(what, "notes") == 0)
  497. {
  498. free(head->notes);
  499. head->notes = strdup(new_data);
  500. }
  501. db_update_entry(id, head);
  502. list_free(entry);
  503. }
  504. /*Replace an entry data interactively
  505. *Interactive replace does not support replacing
  506. *password at all. Users should use steel -e <id> passphrase
  507. *instead.
  508. */
  509. void
  510. replace_interactively(int id)
  511. {
  512. if(!steel_tracker_file_exists())
  513. return;
  514. Entry_t *entry = NULL;
  515. Entry_t *head = NULL;
  516. char title[1024] = {0};
  517. char user[1024] = {0};
  518. char url[1024] = {0};
  519. char notes[1024] = {0};
  520. size_t pwdlen = 1024;
  521. char pass[pwdlen];
  522. char *ptr = pass;
  523. entry = db_get_entry_by_id(id);
  524. if(entry == NULL)
  525. {
  526. fprintf(stderr, "Cannot process entry %d.\n", id);
  527. return;
  528. }
  529. /*Skip initialization data*/
  530. head = entry->next;
  531. if(head == NULL)
  532. {
  533. fprintf(stderr, "No entry found with id %d.\n", id);
  534. list_free(entry);
  535. return;
  536. }
  537. fprintf(stdout, "Current title %s\n", head->title);
  538. fprintf(stdout, "New title: ");
  539. fgets(title, 1024, stdin);
  540. fprintf(stdout, "Current username %s\n", head->user);
  541. fprintf(stdout, "New username: ");
  542. fgets(user, 1024, stdin);
  543. fprintf(stdout, "Current address %s\n", head->url);
  544. fprintf(stdout, "Address: ");
  545. fgets(url, 1024, stdin);
  546. fprintf(stdout, "Current note %s\n", head->notes);
  547. fprintf(stdout, "New note: ");
  548. fgets(notes, 1024, stdin);
  549. fprintf(stdout, "Current passphrase %s\n", head->pwd);
  550. my_getpass(ENTRY_PWD_PROMPT, &ptr, &pwdlen, stdin);
  551. strip_newline_str(title);
  552. strip_newline_str(user);
  553. strip_newline_str(url);
  554. strip_newline_str(notes);
  555. if(title[0] != '\0')
  556. head->title = strdup(title);
  557. if(user[0] != '\0')
  558. head->user = strdup(user);
  559. if(url[0] != '\0')
  560. head->url = strdup(url);
  561. if(notes[0] != '\0')
  562. head->notes = strdup(notes);
  563. if(pass[0] != '\0')
  564. head->pwd = strdup(pass);
  565. db_update_entry(id, head);
  566. list_free(entry);
  567. }
  568. /*Function generates new password and prints it to stdout.
  569. *Does not use the database, so this function can be called
  570. *even if the database is encrypted.
  571. */
  572. void
  573. generate_password(int length, int count)
  574. {
  575. if(length < 6)
  576. {
  577. fprintf(stderr, "Minimum length is 6 characters.\n");
  578. return;
  579. }
  580. for(int i = 0; i < count; i++)
  581. {
  582. char *pass = generate_pass(length);
  583. if(pass == NULL)
  584. {
  585. fprintf(stderr, "Generating new password failed.\n");
  586. return;
  587. }
  588. printf("%s\n", pass);
  589. free(pass);
  590. }
  591. }
  592. /*Show all tracked databases, including their encryption status and last
  593. *modified date.
  594. */
  595. void
  596. show_database_statuses()
  597. {
  598. int count;
  599. FILE *fp = NULL;
  600. if(!steel_tracker_file_exists())
  601. return;
  602. fp = status_get_file_ptr("r");
  603. if(fp == NULL)
  604. return;
  605. count = status_count_file_lines(fp);
  606. if(count == -2)
  607. {
  608. fprintf(stdout, "No databases found.\n");
  609. fclose(fp);
  610. return;
  611. }
  612. rewind(fp);
  613. while(count >= 0)
  614. {
  615. char *line = NULL;
  616. line = status_read_file_line(fp);
  617. if(line == NULL)
  618. {
  619. fprintf(stderr, "Error reading line. Corrupted .steel_dbs file?\n");
  620. fclose(fp);
  621. return;
  622. }
  623. if(!db_file_exists(line))
  624. {
  625. fprintf(stderr, "Database file %s does not exist.\n",
  626. line);
  627. fprintf(stderr, "Will disable tracking for it.\n");
  628. /*Remove the entry from the steel_dbs*/
  629. status_del_tracking(line);
  630. count--;
  631. free(line);
  632. continue;
  633. }
  634. if(is_file_encrypted(line))
  635. fprintf(stdout, "%s\t%s\t%s\n", "[Encrypted]",
  636. db_last_modified(line), line);
  637. else
  638. fprintf(stdout, "%s\t%s\t%s\n", "[Decrypted]",
  639. db_last_modified(line), line);
  640. free(line);
  641. count--;
  642. }
  643. fclose(fp);
  644. }
  645. /*Shreds the database file pointed by path.
  646. *If the database is decrypted (currently the open one)
  647. *function will also remove .steel_open file.
  648. *Method will also remove entry from steel_dbs tracker file.
  649. */
  650. void
  651. remove_database(const char *path)
  652. {
  653. if(!steel_tracker_file_exists())
  654. return;
  655. bool encrypted = false;
  656. char ch;
  657. encrypted = is_file_encrypted(path);
  658. fprintf(stdout, "Are you sure? (y/N) ");
  659. ch = getc(stdin);
  660. if(ch == 'y' || ch == 'Y')
  661. {
  662. if(db_shred(path))
  663. {
  664. status_del_tracking(path);
  665. if(!encrypted)
  666. db_remove_lockfile();
  667. }
  668. else
  669. {
  670. fprintf(stderr, "Unable to shred the database.\n");
  671. }
  672. }
  673. else
  674. {
  675. fprintf(stdout, "Aborted.\n");
  676. }
  677. }
  678. /*Backup the database*/
  679. void
  680. backup_database(const char *source, const char *dest)
  681. {
  682. if(!steel_tracker_file_exists())
  683. return;
  684. if(!backup_export(source, dest))
  685. fprintf(stderr, "Unable to backup the database.\n");
  686. }
  687. /*Function does not check the existence of existing databases,
  688. *As we of course want to allow the first database to be imported one.
  689. *Function also sets tracking status for the imported database.
  690. */
  691. void
  692. backup_import_database(const char *source, const char *dest)
  693. {
  694. if(!backup_import(source, dest))
  695. {
  696. fprintf(stderr, "Unable to import the backup.\n");
  697. return;
  698. }
  699. }
  700. /*Print passphrase of an entry to stdout*/
  701. void
  702. show_passphrase_only(int id)
  703. {
  704. if(!steel_tracker_file_exists())
  705. return;
  706. Entry_t *entry = db_get_entry_by_id(id);
  707. if(entry == NULL)
  708. {
  709. fprintf(stderr, "Cannot process entry with id %d.\n", id);
  710. return;
  711. }
  712. /*Skip the first one, it's initialization data.*/
  713. Entry_t *next = entry->next;
  714. if(next != NULL)
  715. fprintf(stdout, "%s\n", next->pwd);
  716. else
  717. printf("No entry found with id %d.\n", id);
  718. list_free(entry);
  719. }
  720. /*Print username of an entry to stdout*/
  721. void
  722. show_username_only(int id)
  723. {
  724. if(!steel_tracker_file_exists())
  725. return;
  726. Entry_t *entry = db_get_entry_by_id(id);
  727. if(entry == NULL)
  728. {
  729. fprintf(stderr, "Cannot process entry with id %d.\n", id);
  730. return;
  731. }
  732. /*Skip the first one, it's initialization data.*/
  733. Entry_t *next = entry->next;
  734. if(next != NULL)
  735. fprintf(stdout, "%s\n", next->user);
  736. else
  737. printf("No entry found with id %d.\n", id);
  738. list_free(entry);
  739. }
  740. /*Print url of an entry to stdout*/
  741. void
  742. show_url_only(int id)
  743. {
  744. if(!steel_tracker_file_exists())
  745. return;
  746. Entry_t *entry = db_get_entry_by_id(id);
  747. if(entry == NULL)
  748. {
  749. fprintf(stderr, "Cannot process entry with id %d.\n", id);
  750. return;
  751. }
  752. /*Skip the first one, it's initialization data.*/
  753. Entry_t *next = entry->next;
  754. if(next != NULL)
  755. fprintf(stdout, "%s\n", next->url);
  756. else
  757. printf("No entry found with id %d.\n", id);
  758. list_free(entry);
  759. }
  760. /*Print notes of an entry to stdout*/
  761. void
  762. show_notes_only(int id)
  763. {
  764. if(!steel_tracker_file_exists())
  765. return;
  766. Entry_t *entry = db_get_entry_by_id(id);
  767. if(entry == NULL)
  768. {
  769. fprintf(stderr, "Cannot process entry with id %d.\n", id);
  770. return;
  771. }
  772. /*Skip the first one, it's initialization data.*/
  773. Entry_t *next = entry->next;
  774. if(next != NULL)
  775. fprintf(stdout, "%s\n", next->notes);
  776. else
  777. printf("No entry found with id %d.\n", id);
  778. list_free(entry);
  779. }