1 /*
2  * ckpass.c
3  *
4  * Main program and support functions.
5  *
6  * This file is part of the ckpass project.
7  *
8  * Copyright (C) 2009  Heath N. Caldwell <hncaldwell@gmail.com>
9  *
10  * This program is free software: you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation, either version 3 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
22  *
23  */
24 
25 #include <ncurses.h>
26 #include <locale.h>
27 
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <string.h>
31 #include <sys/mman.h>
32 #include <sys/stat.h>
33 #include <unistd.h>
34 #include <fcntl.h>
35 #include <errno.h>
36 
37 #include <kpass.h>
38 
39 #include "ckpass.h"
40 #include "bindings.h"
41 #include "forms.h"
42 
main(int argc,char ** argv)43 int main(int argc, char **argv)
44 {
45 	kpass_db *db;
46 	kpass_db *new_db;
47 	int press;
48 	int active_win;
49 	int first_group = 0;
50 	int highlighted_group = 0;
51 	int selected_group = 0;
52 	int entries_in_selected_group = 0;
53 	int first_entry = 0;
54 	int highlighted_entry = 0;
55 	int max_x, max_y;
56 	bool reveal = FALSE;
57 	char db_filename[MAX_FILENAME_LENGTH];
58 	char new_db_filename[MAX_FILENAME_LENGTH];
59 	struct binding *b;
60 	struct kpass_entry *entry;
61 	bool file_from_arg = FALSE; /* True if using database filename from command line argument. */
62 	int result;
63 
64 	setlocale(LC_ALL, "C");
65 
66 	initscr();
67 	start_color();
68 	cbreak();
69 	noecho();
70 	//nonl();
71 	//intrflush(stdscr, FALSE);
72 	curs_set(0);
73 	keypad(stdscr, TRUE);
74 
75 	active_win = WIN_GROUPS;
76 	init_bindings();
77 
78 	db = malloc(sizeof(kpass_db));
79 
80 	if(argc > 1) {
81 		strncpy(db_filename, argv[1], MAX_FILENAME_LENGTH);
82 		file_from_arg = TRUE;
83 	}
84 
85 	do {
86 		/* If it was set by command line argument, try it the first time.  If
87 		 * the user cancels password prompt, read a new filename with form. */
88 		if(!file_from_arg) {
89 			if(open_database_form(db_filename, MAX_FILENAME_LENGTH)) goto quit;
90 		}
91 
92 		file_from_arg = FALSE;
93 
94 		result = open_db(db, db_filename);
95 		if(result < 0) kpass_free_db(db); /* It was initialized, but password cancelled. */
96 	} while(result);
97 
98 
99 
100 	init_windows(db);
101 	draw_groups(db, first_group, highlighted_group, TRUE);
102 	draw_entries(db, highlighted_group, first_entry, highlighted_entry, FALSE, FALSE);
103 	draw_top_bar(db_filename);
104 	draw_bottom_bar(active_win);
105 
106 	while(1) {
107 		press = wgetch(active_win == WIN_GROUPS ? _groups_win : _entries_win);
108 
109 		if(press == KEY_RESIZE) {
110 			init_windows(db);
111 			draw_groups(db, first_group, highlighted_group, active_win == WIN_GROUPS ? TRUE : FALSE);
112 			draw_entries(db, highlighted_group, first_entry, highlighted_entry, active_win == WIN_ENTRIES ? TRUE : FALSE, FALSE);
113 			draw_bottom_bar(active_win);
114 			continue;
115 		}
116 
117 		if(active_win == WIN_GROUPS) {
118 			for(b = _groups_bindings; b->key && b->key != press; b++);
119 			if(!b->key) continue;
120 
121 			if(b->command == C_PREV) {
122 				if(highlighted_group > 0) {
123 					if(highlighted_group == first_group) first_group--;
124 					highlighted_group--;
125 				}
126 				draw_groups(db, first_group, highlighted_group, TRUE);
127 				draw_entries(db, highlighted_group, first_entry, highlighted_entry, FALSE, FALSE);
128 			} else if(b->command == C_NEXT) {
129 				if(highlighted_group < db->groups_len - 1) {
130 					getmaxyx(_groups_win, max_y, max_x);
131 					if(highlighted_group == first_group + max_y - 1) first_group++;
132 
133 					highlighted_group++;
134 				}
135 				draw_groups(db, first_group, highlighted_group, TRUE);
136 				draw_entries(db, highlighted_group, first_entry, highlighted_entry, FALSE, FALSE);
137 			} else if(b->command == C_SELECT) {
138 				selected_group = highlighted_group;
139 				first_entry = 0;
140 				highlighted_entry = 0;
141 				entries_in_selected_group = entries_in_group(db, selected_group);
142 				active_win = WIN_ENTRIES;
143 				draw_groups(db, first_group, highlighted_group, FALSE);
144 				draw_entries(db, selected_group, first_entry, highlighted_entry, TRUE, FALSE);
145 				draw_bottom_bar(active_win);
146 			} else if(b->command == C_OPEN) {
147 				if(!open_database_form(new_db_filename, MAX_FILENAME_LENGTH)) {
148 					new_db = malloc(sizeof(kpass_db));
149 
150 					result = open_db(new_db, new_db_filename);
151 					if(!result) {
152 						kpass_free_db(db);
153 						free(db);
154 						db = new_db;
155 						strncpy(db_filename, new_db_filename, MAX_FILENAME_LENGTH);
156 
157 						first_group = 0;
158 						selected_group = 0;
159 						highlighted_group = 0;
160 						first_entry = 0;
161 						highlighted_entry = 0;
162 					} else if(result < 0) {
163 						kpass_free_db(new_db);
164 						free(new_db);
165 					} else {
166 						free(new_db);
167 					}
168 				}
169 
170 				init_windows(db);
171 				draw_groups(db, first_group, highlighted_group, TRUE);
172 				draw_entries(db, highlighted_group, first_entry, highlighted_entry, FALSE, FALSE);
173 				draw_top_bar(db_filename);
174 				draw_bottom_bar(active_win);
175 			} else if(b->command == C_QUIT) {
176 				goto quit;
177 			}
178 		} else if(active_win == WIN_ENTRIES) {
179 			for(b = _entries_bindings; b->key && b->key != press; b++);
180 			if(!b->key) continue;
181 
182 			if(b->command == C_REVEAL) {
183 				reveal = !reveal;
184 				draw_entries(db, selected_group, first_entry, highlighted_entry, TRUE, reveal);
185 				continue;
186 			} else {
187 				reveal = FALSE;
188 			}
189 
190 			if(b->command == C_PREV) {
191 				if(highlighted_entry > 0) {
192 					if(highlighted_entry == first_entry) first_entry--;
193 					highlighted_entry--;
194 				}
195 				draw_entries(db, selected_group, first_entry, highlighted_entry, TRUE, FALSE);
196 				reveal = FALSE;
197 			} else if(b->command == C_NEXT) {
198 				if(highlighted_entry < entries_in_selected_group - 1) {
199 					getmaxyx(_entries_win, max_y, max_x);
200 					if(highlighted_entry == first_entry + max_y - 1) first_entry++;
201 
202 					highlighted_entry++;
203 				}
204 				draw_entries(db, selected_group, first_entry, highlighted_entry, TRUE, FALSE);
205 				reveal = FALSE;
206 			} else if(b->command == C_GROUPS) {
207 				active_win = WIN_GROUPS;
208 				first_entry = 0;
209 				highlighted_entry = 0;
210 				draw_entries(db, selected_group, first_entry, highlighted_entry, FALSE, FALSE);
211 				draw_groups(db, first_group, highlighted_group, TRUE);
212 				draw_bottom_bar(active_win);
213 			} else if(b->command == C_EDIT) {
214 				entry_form(db, nth_entry_in_group(db, selected_group, highlighted_entry));
215 
216 				/* This redraw needs to be revisited. */
217 				draw_entries(db, selected_group, first_entry, highlighted_entry, TRUE, FALSE);
218 				draw_groups(db, first_group, highlighted_group, FALSE);
219 			} else if(b->command == C_XCLIP) {
220 				entry = nth_entry_in_group(db, selected_group, highlighted_entry);
221 				pipeout("/usr/bin/xclip", entry->password);
222 			} else if(b->command == C_OPEN) {
223 				if(!open_database_form(new_db_filename, MAX_FILENAME_LENGTH)) {
224 					new_db = malloc(sizeof(kpass_db));
225 
226 					result = open_db(new_db, new_db_filename);
227 					if(!result) {
228 						kpass_free_db(db);
229 						free(db);
230 						db = new_db;
231 						strncpy(db_filename, new_db_filename, MAX_FILENAME_LENGTH);
232 
233 						active_win = WIN_GROUPS;
234 						first_group = 0;
235 						selected_group = 0;
236 						highlighted_group = 0;
237 						first_entry = 0;
238 						highlighted_entry = 0;
239 					} else if(result < 0) {
240 						kpass_free_db(new_db);
241 						free(new_db);
242 					} else {
243 						free(new_db);
244 					}
245 				}
246 
247 				init_windows(db);
248 
249 				if(new_db == db) {
250 					draw_entries(db, selected_group, first_entry, highlighted_entry, FALSE, FALSE);
251 					draw_groups(db, first_group, highlighted_group, TRUE);
252 				} else {
253 					draw_entries(db, selected_group, first_entry, highlighted_entry, TRUE, FALSE);
254 					draw_groups(db, first_group, highlighted_group, FALSE);
255 				}
256 
257 				draw_top_bar(db_filename);
258 				draw_bottom_bar(active_win);
259 			} else if(b->command == C_QUIT) {
260 				goto quit;
261 			}
262 		}
263 	}
264 
265 	quit:
266 
267 	endwin();
268 	free(_groups_bindings);
269 	free(_entries_bindings);
270 
271 	kpass_free_db(db);
272 	free(db);
273 	return 0;
274 }
275 
draw_top_bar(const char * s)276 void draw_top_bar(const char *s)
277 {
278 	int max_x, max_y;
279 
280 	wmove(_top_bar, 0, 0);
281 	waddstr(_top_bar, s);
282 
283 	getmaxyx(_top_bar, max_y, max_x);
284 	whline(_top_bar, ' ', max_x);
285 	wrefresh(_top_bar);
286 }
287 
draw_bottom_bar(int mode)288 void draw_bottom_bar(int mode)
289 {
290 	int max_x, max_y;
291 	struct binding *b;
292 
293 	wmove(_bottom_bar, 0, 0);
294 
295 	if(mode == WIN_GROUPS || mode == WIN_ENTRIES) {
296 		b = mode == WIN_GROUPS ? _groups_bindings : _entries_bindings;
297 
298 		for(; b->key; b++) {
299 			switch(b->key) {
300 				case '\t': waddstr(_bottom_bar, "[tab]"); break;
301 				case '\n': waddstr(_bottom_bar, "[return]"); break;
302 				case ' ': waddstr(_bottom_bar, "[space]"); break;
303 				case KEY_UP: continue;
304 				case KEY_DOWN: continue;
305 				default: waddch(_bottom_bar, b->key); break;
306 			}
307 
308 			if((b+1)->key && (b+1)->command == b->command) {
309 				waddch(_bottom_bar, ',');
310 				continue;
311 			}
312 
313 			waddch(_bottom_bar, ':');
314 			waddstr(_bottom_bar, b->command);
315 			waddstr(_bottom_bar, "  ");
316 		}
317 	}
318 
319 	getmaxyx(_bottom_bar, max_y, max_x);
320 	whline(_bottom_bar, ' ', max_x);
321 	wrefresh(_bottom_bar);
322 }
323 
fatal(const char * message)324 void fatal(const char *message)
325 {
326 	endwin();
327 
328 	fprintf(stderr, "%s\n", message);
329 	exit(1);
330 }
331 
open_db(kpass_db * db,const char * filename)332 int open_db(kpass_db *db, const char *filename)
333 {
334 	char password[MAX_PASSWORD_LENGTH];
335 	bool decrypt_success = FALSE;
336 	int retval = 0;
337 
338 	clear();
339 	refresh();
340 
341 	if(read_db(db, filename)) {
342 		error_dialog("Couldn't read.");
343 		clear();
344 		refresh();
345 		return 1;
346 	}
347 
348 	while(!decrypt_success) {
349 		if(password_form(password, MAX_PASSWORD_LENGTH)) {
350 			retval = -1;
351 			clear();
352 			refresh();
353 			break;
354 		}
355 
356 		if(decrypt_db(db, password)) {
357 			error_dialog("Password does not match.");
358 		} else {
359 			decrypt_success = TRUE;
360 		}
361 
362 		clear();
363 		refresh();
364 	}
365 
366 	// Clear password since we don't need it anymore.
367 	memset(password, 0, MAX_PASSWORD_LENGTH);
368 
369 	return retval;
370 }
371 
read_db(kpass_db * db,const char * filename)372 int read_db(kpass_db *db, const char *filename)
373 {
374 	uint8_t *data;
375 	kpass_retval retval;
376 	struct stat sb;
377 	int fd;
378 
379 	fd = open(filename, O_RDONLY);
380 	if(fd < 0) return errno;
381 
382 	if(fstat(fd, &sb) < 0) return errno;
383 
384 	data = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
385 	if(data == MAP_FAILED) return errno;
386 
387 	retval = kpass_init_db(db, data, sb.st_size);
388 	if(retval) return -1;
389 	munmap(data, sb.st_size);
390 
391 	return 0;
392 }
393 
decrypt_db(kpass_db * db,const char * password)394 int decrypt_db(kpass_db *db, const char *password)
395 {
396 	kpass_retval retval;
397 	unsigned char pw_hash[32];
398 
399 	kpass_hash_pw(password, pw_hash);
400 
401 	retval = kpass_decrypt_db(db, pw_hash);
402 	if(retval) return -1;
403 
404 	return 0;
405 }
406 
draw_groups(const kpass_db * db,int first,int highlighted,bool foreground)407 void draw_groups(const kpass_db *db, int first, int highlighted, bool foreground)
408 {
409 	int max_x, max_y;
410 	int i, j;
411 	int y;
412 	int highlight_attrib;
413 
414 	getmaxyx(_groups_win, max_y, max_x);
415 	highlight_attrib = foreground ? A_REVERSE : A_NORMAL;
416 
417 	for(i = first, y = 0; i < db->groups_len; i++, y++) {
418 		wattrset(_groups_win, i == highlighted ? highlight_attrib : A_NORMAL);
419 
420 		if(db->groups[i]->level > 0) {
421 			if(y < max_y) {
422 				wmove(_groups_win, y, 0);
423 				whline(_groups_win, ' ', 3*(db->groups[i]->level - 1));
424 				wmove(_groups_win, y, 3*(db->groups[i]->level - 1));
425 				waddch(_groups_win, ACS_LLCORNER);
426 				waddch(_groups_win, '-');
427 				waddch(_groups_win, '>');
428 			}
429 
430 			for(j = i-1; j >= 0 && j >= first && db->groups[j]->level >= db->groups[i]->level; j--) {
431 				if(y - (i-j) >= max_y) continue;
432 
433 				wmove(_groups_win, y - (i-j), 3*(db->groups[i]->level - 1));
434 
435 				wattrset(_groups_win, j == highlighted ? highlight_attrib : A_NORMAL);
436 
437 				if(db->groups[j]->level == db->groups[i]->level) {
438 					waddch(_groups_win, ACS_LTEE);
439 				} else {
440 					waddch(_groups_win, ACS_VLINE);
441 				}
442 			}
443 
444 			if(y < max_y) {
445 				wattrset(_groups_win, i == highlighted ? highlight_attrib : A_NORMAL);
446 				wmove(_groups_win, y, 3*(db->groups[i]->level));
447 			}
448 		} else {
449 			if(y < max_y) wmove(_groups_win, y, 0);
450 		}
451 
452 		if(y < max_y) {
453 			waddstr(_groups_win, db->groups[i]->name);
454 
455 			whline(_groups_win, ' ', max_x);
456 			if(i == highlighted) mvwaddch(_groups_win, y, max_x - 2, '*');
457 		}
458 	}
459 
460 	wattrset(_groups_win, A_NORMAL);
461 
462 	/* Blank the rest of the window. */
463 	for(; y < max_y; y++) {
464 		mvwhline(_groups_win, y, 0, ' ', max_x);
465 	}
466 
467 	wrefresh(_groups_win);
468 }
469 
draw_entries(const kpass_db * db,int group,int first,int highlighted,bool foreground,bool reveal)470 void draw_entries(const kpass_db *db, int group, int first, int highlighted, bool foreground, bool reveal)
471 {
472 	int max_x;
473 	int max_y;
474 	int i;
475 	int c; /* Accumulator for count of entries in group. */
476 	int x, y;
477 
478 	getmaxyx(_entries_win, max_y, max_x);
479 
480 	for(i = 0, y = 0, c = 0; i < db->entries_len && y < max_y; i++) {
481 		if(db->entries[i]->group_id != db->groups[group]->id) continue;
482 		if(c < first) {
483 			/* Increase count since we did encounter one, we just aren't showing it. */
484 			c++;
485 			continue;
486 		}
487 
488 		if(foreground) wattrset(_entries_win, c == highlighted ? A_REVERSE : A_NORMAL);
489 
490 		mvwhline(_entries_win, y, 0, ' ', max_x); /* clear line first */
491 
492 		x = 0;
493 		mvwaddnstr(_entries_win, y, x, db->entries[i]->title, _entry_widths[0] - 1);
494 		if(strlen(db->entries[i]->title) > _entry_widths[0] - 1 ||
495 				x + MIN(strlen(db->entries[i]->title), _entry_widths[0]) >= max_x)
496 			mvwaddch(_entries_win,
497 				y, MIN(x + _entry_widths[0] - 2, max_x - 1),
498 				'+' | A_REVERSE);
499 
500 		x += _entry_widths[0];
501 		mvwaddnstr(_entries_win, y, x, db->entries[i]->username, _entry_widths[1] - 1);
502 		if(strlen(db->entries[i]->username) > _entry_widths[1] - 1 ||
503 				x + MIN(strlen(db->entries[i]->username), _entry_widths[1]) >= max_x)
504 			mvwaddch(_entries_win,
505 				y, MIN(x + _entry_widths[1] - 2, max_x - 1),
506 				'+' | A_REVERSE);
507 
508 		x += _entry_widths[1];
509 		if(reveal && c == highlighted) {
510 			mvwaddnstr(_entries_win, y, x, db->entries[i]->password, _entry_widths[2] - 1);
511 			if(strlen(db->entries[i]->password) > _entry_widths[2] - 1 ||
512 					x + MIN(strlen(db->entries[i]->password), _entry_widths[2]) >= max_x)
513 				mvwaddch(_entries_win,
514 					y, MIN(x + _entry_widths[2] - 2, max_x - 1),
515 					'+' | A_REVERSE);
516 		} else {
517 			mvwaddstr(_entries_win, y, x, "********");
518 		}
519 
520 		x += _entry_widths[2];
521 		mvwaddnstr(_entries_win, y, x, db->entries[i]->url, _entry_widths[3]);
522 		if(strlen(db->entries[i]->url) > _entry_widths[3] ||
523 				x + MIN(strlen(db->entries[i]->url), _entry_widths[3]) >= max_x)
524 			mvwaddch(_entries_win,
525 				y, MIN(x + _entry_widths[3] - 1, max_x - 1),
526 				'+' | A_REVERSE);
527 
528 		y++;
529 		c++;
530 	}
531 
532 	wattrset(_entries_win, A_NORMAL);
533 
534 	/* Blank the rest of the window. */
535 	for(; y < max_y; y++) {
536 		mvwhline(_entries_win, y, 0, ' ', max_x);
537 	}
538 
539 	wrefresh(_entries_win);
540 }
541 
entries_in_group(const kpass_db * db,int group)542 int entries_in_group(const kpass_db *db, int group)
543 {
544 	int i;
545 	int n = 0;
546 
547 	for(i=0; i < db->entries_len; i++) {
548 		if(db->entries[i]->group_id == db->groups[group]->id) n++;
549 	}
550 
551 	return n;
552 }
553 
nth_entry_in_group(const kpass_db * db,int group,int n)554 struct kpass_entry *nth_entry_in_group(const kpass_db *db, int group, int n)
555 {
556 	int i;
557 
558 	/* Add 1 since n is expected to start at 0
559 	 * (first listed entry for the group is called 0). */
560 	n++;
561 
562 	for(i=0; i < db->entries_len; i++) {
563 		if(db->entries[i]->group_id == db->groups[group]->id) n--;
564 		if(!n) return db->entries[i];
565 	}
566 
567 	return 0;
568 }
569 
find_groups_win_width(const kpass_db * db)570 int find_groups_win_width(const kpass_db *db)
571 {
572 	int i;
573 	int n = 0;
574 
575 	for(i=0; i < db->groups_len; i++) {
576 		if(strlen(db->groups[i]->name) + 3*(db->groups[i]->level) > n)
577 			n = strlen(db->groups[i]->name) + 3*(db->groups[i]->level);
578 	}
579 
580 	/* Longest line plus borders plus " * ". */
581 	return n + 5;
582 }
583 
init_windows(const kpass_db * db)584 void init_windows(const kpass_db *db)
585 {
586 	int max_y, max_x;
587 	int x;
588 	int groups_win_width;
589 
590 	if(_groups_super_win) delwin(_groups_super_win);
591 	if(_groups_win) delwin(_groups_super_win);
592 	if(_entries_super_win) delwin(_entries_super_win);
593 	if(_entries_win) delwin(_entries_super_win);
594 	if(_top_bar) delwin(_top_bar);
595 	if(_bottom_bar) delwin(_bottom_bar);
596 
597 	getmaxyx(stdscr, max_y, max_x);
598 	groups_win_width = find_groups_win_width(db);
599 
600 	_groups_super_win = newwin(max_y - 2, groups_win_width, 1, 0);
601 	_groups_win = derwin(_groups_super_win, max_y - 6, groups_win_width - 2, 3, 1);
602 	_entries_super_win = newwin(max_y - 2, max_x - groups_win_width, 1, groups_win_width);
603 	_entries_win = derwin(_entries_super_win, max_y - 6, max_x - groups_win_width - 2, 3, 1);
604 	_top_bar = newwin(1, max_x, 0, 0);
605 	_bottom_bar = newwin(1, max_x, max_y - 1, 0);
606 
607 	keypad(_groups_win, TRUE);
608 	box(_groups_super_win, 0, 0);
609 	mvwaddch(_groups_super_win, 2, 0, ACS_LTEE);
610 	mvwhline(_groups_super_win, 2, 1, ACS_HLINE, groups_win_width - 2);
611 	mvwaddch(_groups_super_win, 2, groups_win_width - 1, ACS_RTEE);
612 	mvwaddstr(_groups_super_win, 1, 1, "Groups");
613 
614 	keypad(_entries_win, TRUE);
615 	box(_entries_super_win, 0, 0);
616 	mvwaddch(_entries_super_win, 2, 0, ACS_LTEE);
617 	mvwhline(_entries_super_win, 2, 1, ACS_HLINE, max_x - groups_win_width - 2);
618 	mvwaddch(_entries_super_win, 2, max_x - groups_win_width - 1, ACS_RTEE);
619 
620 	x = 1;
621 	mvwaddstr(_entries_super_win, 1, 1, "Title");
622 	x += _entry_widths[0];
623 	mvwaddstr(_entries_super_win, 1, x, "Username");
624 	x += _entry_widths[1];
625 	mvwaddstr(_entries_super_win, 1, x, "Password");
626 	x += _entry_widths[2];
627 	mvwaddstr(_entries_super_win, 1, x, "URL");
628 
629 	wrefresh(_groups_super_win);
630 	wrefresh(_entries_super_win);
631 }
632 
error_dialog(char * s)633 void error_dialog(char *s) {
634 	WINDOW *win;
635 	int press;
636 	int max_x, max_y;
637 	int rows, cols;
638 
639 	getmaxyx(stdscr, max_y, max_x);
640 
641 	rows = 7;
642 	cols = strlen(s) + 4;
643 	win = newwin(rows, cols, (max_y - rows) / 2, (max_x - cols) / 2);
644 	keypad(win, TRUE);
645 	box(win, 0, 0);
646 
647 	mvwaddstr(win, 2, 2, s);
648 	wattrset(win, A_REVERSE);
649 	mvwaddstr(win, 4, cols / 2 - 2, "[OK]");
650 	wattrset(win, A_NORMAL);
651 
652 	wrefresh(win);
653 
654 	while(wgetch(win) != '\n');
655 
656 	delwin(win);
657 }
658 
pipeout(const char * command,const char * s)659 int pipeout(const char *command, const char *s)
660 {
661 	int pipefd[2];
662 	pid_t pid;
663 
664 	if(pipe(pipefd) < 0) return -1;
665 
666 	pid = fork();
667 	if(pid < 0) return -1;
668 
669 	if(pid) {
670 		/* parent */
671 		close(pipefd[0]); /* Close read end. */
672 		write(pipefd[1], s, strlen(s));
673 		close(pipefd[1]);
674 		wait(0); /* Wait for child. */
675 	} else {
676 		/* child */
677 		close(pipefd[1]); /* Close write end. */
678 		dup2(pipefd[0], 0); /* Dup read end to stdin. */
679 
680 		execl(command, command, 0);
681 
682 		/* I don't really think these are necessary, but just in case. */
683 		close(pipefd[0]);
684 		exit(0);
685 	}
686 
687 	return 0;
688 }
689 
init_bindings()690 void init_bindings()
691 {
692 	_groups_bindings = new_binding_set();
693 	add_binding(&_groups_bindings, 'o', C_OPEN);
694 	add_binding(&_groups_bindings, 's', C_SAVE);
695 	add_binding(&_groups_bindings, 'a', C_ADD_GROUP);
696 	add_binding(&_groups_bindings, KEY_UP, C_PREV);
697 	add_binding(&_groups_bindings, KEY_DOWN, C_NEXT);
698 	add_binding(&_groups_bindings, '\t', C_SELECT);
699 	add_binding(&_groups_bindings, '\n', C_SELECT);
700 	add_binding(&_groups_bindings, 'e', C_EDIT);
701 	add_binding(&_groups_bindings, 'q', C_QUIT);
702 
703 	_entries_bindings = new_binding_set();
704 	add_binding(&_entries_bindings, 'o', C_OPEN);
705 	add_binding(&_entries_bindings, 's', C_SAVE);
706 	add_binding(&_entries_bindings, 'a', C_ADD_ENTRY);
707 	add_binding(&_entries_bindings, '\t', C_GROUPS);
708 	add_binding(&_entries_bindings, KEY_UP, C_PREV);
709 	add_binding(&_entries_bindings, KEY_DOWN, C_NEXT);
710 	add_binding(&_entries_bindings, ' ', C_REVEAL);
711 	add_binding(&_entries_bindings, 'r', C_REVEAL);
712 	add_binding(&_entries_bindings, 'e', C_EDIT);
713 	add_binding(&_entries_bindings, '\n', C_EDIT);
714 	add_binding(&_entries_bindings, 'q', C_QUIT);
715 
716 	/* This should be optional. */
717 	add_binding(&_entries_bindings, 'x', C_XCLIP);
718 }
719 
720