Differences From
Artifact [f865f78c63]:
13 13 * systems, but should run fine on Linux as well
14 14 * as BSDs with getrandom() support.
15 15 * ! for getrandom() to work with the version of
16 16 * libc on my android phone, the getrandom() call
17 17 * had to be converted to use the syscall()
18 18 * interface. this is unlikely to cause problems,
19 19 * but should be kept in mind.
20 + *
21 + * TODO prevent pw reads from going off the edge of
22 + * the screen and fucking up all the shit
20 23 */
21 24
22 25 #include <unistd.h>
23 26 #include <sys/random.h>
24 27 #include <sys/syscall.h>
25 28 #include <stddef.h>
26 29 #include <stdint.h>
................................................................................
29 32 #include <fcntl.h>
30 33 #include <sodium.h>
31 34 #include <termios.h>
32 35
33 36
34 37 #define sz(a) ( sizeof (a) / sizeof (a) [0] )
35 38 #define say(x) (write(2, (x), sizeof (x)))
39 +#define _str(s) (s),sizeof(s)
36 40
37 41 #ifdef _CLIPBOARD
38 42 # include <sys/types.h>
39 43 # include <pwd.h>
40 44 # include <stdlib.h>
41 45 #else
42 46 # define copy(str,len)
................................................................................
48 52
49 53 #include "err.inc"
50 54
51 55 enum /* db format constants */ {
52 56 db_pubkey_len = crypto_box_PUBLICKEYBYTES,
53 57 db_privkey_len = crypto_box_SECRETKEYBYTES,
54 58 kpw_db_pw_max = 64,
59 + default_pw_len = 32,
55 60 };
56 61
57 62 typedef _Bool bool;
58 63 typedef unsigned long long iaia_word_type;
59 64 typedef bad iaia_error_type;
60 65 enum /* iaia errors */ {
61 66 iaia_e_ok = ok,
................................................................................
82 87
83 88 #define str_ucase "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
84 89 #define str_lcase "abcdefghijklmnopqrstuvwxyz"
85 90 #define str_num "0123456789"
86 91 const char* reftbl = str_num str_ucase str_lcase;
87 92 const char* reftbl_lcase = str_num str_lcase;
88 93
94 +const char* _g_binary_name;
89 95 #ifdef _CLIPBOARD
90 96 char* const*
91 97 cbd_cmds[] = {
92 98 /* NOTE: these commands must be specified in order of
93 99 * most- to least-specific. more than one utility may
94 100 * be present on a given system, so we need to make sure
95 101 * the right one is called. */
96 102 (char* const[]){"termux-clipboard-set", null},
97 103 (char* const[]){"xsel", "-bi", null},
98 104 /* TODO: allow command to be specified by env var */
99 105 null
100 106 };
101 107
102 -bad
108 +enum { plain_term, ansi_term, color_term } _g_term_type[3];
109 +enum alert {a_notice, a_warn, a_debug, a_fatal = 1 << 8};
110 +pstr alert_msg[] = {
111 + _p("(kpw notice)"), _p("(kpw warn)"),
112 + _p("(kpw debug)"), _p("(kpw fatal)")
113 +};
114 +
115 +bool _g_alert_quiet = false,
116 + _g_debug_msgs = false;
117 +void alert(uint16_t kind, const char* msg) {
118 + if (!((kind >= a_fatal) ||
119 + (_g_debug_msgs && kind == a_debug) ||
120 + (!_g_alert_quiet && kind != a_debug))) return;
121 +
122 + uint8_t idx;
123 + if (kind & a_fatal) idx = a_debug + 1;
124 + else idx = kind;
125 +
126 +
127 + if (_g_term_type[2] == color_term) {
128 + char msgcode[] = "\x1b[90m";
129 + char* color = msgcode+3;
130 + *color = '0' + (4 - idx);
131 + write(2,msgcode, sz(msgcode));
132 + } else if (_g_term_type[2] == ansi_term) {
133 + write(2,"\x1b[1m",4);
134 + }
135 +
136 + write(2,alert_msg[idx].ptr,
137 + alert_msg[idx].len);
138 +
139 + if (_g_term_type[2] != plain_term) write(2,"\x1b[m ",4);
140 + else write(2," ",1);
141 + write(2,msg,strlen(msg));
142 + write(2,"\n",1);
143 +
144 + if (kind & a_fatal) exit(kind & (~a_fatal));
145 +}
146 +
147 +enum bad
103 148 copy(const char* str, size_t len) {
149 + alert(a_debug, "copying password to clipboard");
150 + if (geteuid() == 0) {
151 + /* on a sane system, what we'd do is hike up the process
152 + * tree til we found a non-root user. alas, this is UNIX. */
153 + const char* realuser = getenv("SUDO_USER");
154 + if (realuser == null) realuser = "nobody";
155 +
156 + alert(a_warn, "running as root! dropping privileges to prevent malicious use of copy functionality");
157 + setenv("USER", realuser, true);
158 +
159 + struct passwd* nobody = getpwnam(realuser);
160 + if (nobody == null) {
161 + alert(a_fatal | bad_user, "could not get UID to drop privileges; bailing");
162 + return bad_user;
163 + } else {
164 + setenv("HOME", nobody -> pw_dir, true);
165 + setenv("SHELL", "/dev/null", true);
166 + setuid(nobody -> pw_uid);
167 + if (geteuid() == 0)
168 + alert(a_fatal | bad_user, "i don't fucking think so, you sneaky bastard");
169 + }
170 +
171 + }
104 172 char* const clipboard_env = getenv("mkpw_clipboard_setter");
105 173 char* const clipboard_env_arg = getenv("mkpw_clipboard_setter_arg");
106 174 // FIXME: allow multiple args
107 175 int fds[2];
108 - if (pipe(fds) != 0) return 63;
176 + if (pipe(fds) != 0) return bad_pipe;
109 177 if (!fork()) {
110 178 close(fds[1]);
111 179 dup2(fds[0], 0);
112 180 if (clipboard_env != null) {
113 181 execvp(clipboard_env, (char* const[]){
114 182 clipboard_env, clipboard_env_arg, null});
115 183 return bad_copy;
................................................................................
125 193 return ok;
126 194 }
127 195 }
128 196 #endif
129 197
130 198 enum genmode { upper, mix, lower, stupid };
131 199
132 -bad
200 +enum bad
133 201 mkpw(enum genmode mode, char* buf, size_t const len) {
134 202 const unsigned char chars = (sizeof str_num - 1) +
135 203 ((mode == upper) ? (sizeof str_ucase - 1) :
136 204 ((mode == lower) ? (sizeof str_lcase - 1) :
137 205 ((sizeof str_ucase - 1) + (sizeof str_lcase - 1))));
138 206 const char* tbl = (mode == upper) ? reftbl :
139 207 ((mode == lower) ? reftbl_lcase : reftbl);
................................................................................
164 232 pstr account;
165 233 pstr pw;
166 234 };
167 235
168 236 enum term_clear {term_clear_line,
169 237 term_clear_screen};
170 238
171 -enum alert {a_notice, a_warn, a_debug, a_fatal = 1 << 8};
172 -pstr alert_msg[] = {
173 - _p("(notice)"), _p("(warn)"),
174 - _p("(debug)"), _p("(fatal)")
175 -};
176 -
177 -bool _g_alert_quiet = false,
178 - _g_debug_msgs = false;
179 -void alert(uint16_t kind, const char* msg) {
180 - if (!((kind >= a_fatal) ||
181 - (_g_debug_msgs && kind == a_debug) ||
182 - (!_g_alert_quiet && kind != a_debug))) return;
183 -
184 - uint8_t idx;
185 - if (kind & a_fatal) idx = a_debug + 1;
186 - else idx = kind;
187 -
188 -
189 - if (isatty(2)) {
190 - char msgcode[] = "\x1b[1;30m";
191 - char* color = msgcode+5;
192 - *color = '0' + (4 - idx);
193 - write(2,msgcode, sz(msgcode));
194 - }
195 -
196 - write(2,alert_msg[idx].ptr,alert_msg[idx].len);
197 - if (isatty(2)) write(2,"\x1b[m ",4);
198 - else write(2," ",1);
199 - write(2,msg,strlen(msg));
200 - write(2,"\n",1);
201 -
202 - if (kind & a_fatal) exit(kind & (~a_fatal));
203 -}
204 -
205 239 void term_clear(enum term_clear behavior) {
206 240 switch(behavior) {
207 241 case term_clear_line: write(1,"\r\x1b[2K",5); break;
208 242 case term_clear_screen: write(1,"\r\x1b[3J",5); break;
209 243 }
210 244 }
211 245 void term_bell() {
212 246 write(1,"\a",1);
213 247 }
214 248
215 249 typedef char password[kpw_db_pw_max + 1];
216 -bad pwread(char* dest, size_t* out_len, const char* prompt, const size_t plen) {
250 +bad pwread(bool obscure, char* dest, size_t* out_len, const char* prompt, const size_t plen) {
217 251 if (isatty(0)) {
218 252 struct termios initial; {
219 253 /* in order to take PW input, we need to shut
220 254 * off echo and canonical mode. now we're in
221 255 * charge of reading each keypress. */
222 256 tcgetattr(1, &initial);
223 257 struct termios nt = initial;
................................................................................
226 260 }
227 261
228 262 *dest = 0;
229 263 char* p = dest;
230 264
231 265 do {
232 266 term_clear(term_clear_line);
233 - write(1, "\x1b[1m", 4);
267 + if (_g_term_type[1] >= ansi_term) write(1, "\x1b[1m", 4);
234 268 write(1, prompt, plen);
235 - write(1, "\x1b[21m", 5);
269 + if (_g_term_type[1] >= ansi_term) write(1, "\x1b[21m", 5);
236 270
237 - for(size_t i = 0; i < p - dest; ++i)
271 + if (obscure) for(size_t i = 0; i < p - dest; ++i)
238 272 write(1, "*", 1);
273 + else write(1, dest, p-dest);
239 274
240 275 char c;
241 276 if (read(0, &c, 1) == 1) {
242 277 switch (c) {
243 278 case '\n': case '\r':
244 279 /* accept pw */
245 280 if (p > dest) goto end_read_loop;
................................................................................
307 342 }
308 343
309 344 return db;
310 345 }
311 346
312 347 int
313 348 kpw(int argc, const char** argv) {
314 - if (argc == 0) return -1;
315 - if (argc == 1) {
316 - size_t namelen = strlen(argv[0]);
317 - say("\x1b[1musage:\x1b[m ");
318 - write(2, argv[0], namelen);
319 - write(2, kpw_optstr, sz(kpw_optstr));
320 - write(2, kpw_usage, sz(kpw_usage));
321 - return bad_usage;
322 - }
349 + if (argc == 0) return bad_insane;
350 + _g_binary_name = argv[0];
323 351
324 352 enum genmode
325 353 mode = lower;
326 - enum {usage,getpw,addpw,delpw,genpw,
354 + enum {usage,getpw,addpw,delpw,lspw,genpw,
327 355 chpw,keyin,logout,createdb,rekeydb}
328 356 op = getpw;
329 357
330 358 const char* params[3];
331 359 uint8_t param = 0;
332 360
333 361 bool print = false,
334 - clobber = false;
362 + clobber = false,
363 + no_more_opts = false;
335 364 # ifdef _CLIPBOARD
336 365 bool copy_pw = true;
337 366 # endif
338 367 for (const char** arg = argv + 1; *arg != null; ++arg) {
339 - if ((*arg)[0] == '-') {
368 + if (!no_more_opts && (*arg)[0] == '-') {
340 369 if ((*arg)[1] == '-') { /* long opt */
370 + if((*arg)[2] == 0) {
371 + no_more_opts = true;
372 + continue;
373 + }
341 374 unsigned char a;
342 375 if (tblget(sz(argtbl), argtbl, *arg + 2, &a) == ok) switch (a) {
343 376 kpw_emit_long_option_switch
344 377 } else {
345 378 return bad_option;
346 379 }
347 380 } else { /* short opt */
................................................................................
353 386 }
354 387 }
355 388 } else {
356 389 if (param > sz(params)) return bad_syntax;
357 390 params[param++] = *arg;
358 391 }
359 392 }
393 +
394 + if (op == getpw && param == 0) {
395 + size_t namelen = strlen(argv[0]);
396 + say("\x1b[1musage:\x1b[m ");
397 + write(2, argv[0], namelen);
398 + write(2, kpw_optstr, sz(kpw_optstr));
399 + write(2, kpw_usage, sz(kpw_usage));
400 + return bad_usage;
401 + }
360 402
361 403 if (sodium_init() < 0)
362 404 return bad_lib_sodium_init;
363 405
364 406 switch(op) {
365 407 case getpw:{ /* kpw <acct> */
366 408 break;
367 409 }
368 410
369 - case addpw:{ /* kpw -a <acct> [<pw>] */
411 + case genpw: /* kpw -g[lmu] <acct> [<len>] */
412 + case addpw: { /* kpw -a <acct> [<pw>] */
413 + if (param > 2 || param < 1) return bad_syntax;
414 + const char* acct = params[0],
415 + * prm = (param == 2 ? params[1] : NULL);
416 +
417 + alert(a_debug, "opening database");
418 + int db = dbopen(O_RDWR);
419 + if (db == -1) return bad_db_load;
420 + alert(a_debug, "reading in public key");
421 + uint8_t key [db_pubkey_len];
422 + ssize_t e = read(db, key, sz(key));
423 + if(e < sz(key)) return bad_db_corrupt;
424 +
425 + lseek(db, 0, SEEK_END);
426 + bool tty_in = isatty(0),
427 + tty_out = isatty(1);
428 +
429 + password pw; size_t pwlen;
430 + const char* acct_pw;
431 + if (op == addpw) {
432 + if (prm == NULL) {
433 + pstr prompt_l[] = { _p("- new password for "),
434 + {0, acct}, _p(": "), };
435 + char prompt[pstrsum(prompt_l, sz(prompt_l)) + 1];
436 + if (tty_in) pstrcoll(prompt_l, sz(prompt_l), prompt);
437 +
438 + bad e = pwread(!print, pw, &pwlen,
439 + prompt, sz(prompt) - 1);
440 + if (e != ok) return e;
441 + if (tty_in && !print) {
442 + password pw_conf;
443 + e = pwread(true, pw_conf, NULL, _str("- confirm: "));
444 + if (e != ok) return e;
445 + if (strcmp(pw,pw_conf) != 0)
446 + return bad_pw_match;
447 + }
448 + acct_pw = pw;
449 + } else acct_pw = prm;
450 +
451 + } else if (op == genpw) {
452 + unsigned long long len;
453 + if (prm != NULL) {
454 + alert(a_debug, "converting length parameter to integer");
455 + bad e = katoi(10, prm, &len);
456 + if (e != ok) return bad_num;
457 + } else alert(a_debug, "using default password length"),
458 + len = default_pw_len;
459 +
460 + alert(a_debug, "generating new password");
461 + mkpw(mode, pw, len);
462 + if (print || !tty_out) {
463 + write(1, pw, len);
464 + if(tty_out) write(1, "\n", 1);
465 + }
466 + pwlen = len;
467 + }
468 + if (copy_pw) copy(pw, pwlen);
469 + alert(a_debug, "enciphering password");
470 +
370 471 break;
371 472 }
372 473
373 474 case delpw:{ /* kpw -d <acct> */
374 475 break;
375 476 }
376 477
377 - case genpw:{ /* kpw -g[lmu] <acct> [<len>] */
478 + case lspw: { /* kpw -t[p] [<prefix>] */
378 479 break;
379 480 }
380 481
381 482 case createdb: { /* kpw -C [<db>] */
382 483 alert(a_debug, "creating new database");
383 484 if (clobber)
384 485 alert(a_warn, "will clobber any existing database");
385 486 /* before we open our new file, we need to generate
386 487 * our keypairs. the private key will be encrypted
387 488 * with a blake2 hash of a user-supplied passphrase
388 489 * and stored in the database after the unencrypted
389 490 * public key.
390 491 */
391 - uint8_t pub [crypto_box_PUBLICKEYBYTES],
392 - priv[crypto_box_SECRETKEYBYTES];
492 + uint8_t pub [db_pubkey_len],
493 + priv[db_privkey_len];
393 494 uint8_t priv_enc[sz(priv)];
394 495
395 496 alert(a_debug, "generating keypair");
396 497 crypto_box_keypair(pub, priv);
397 498
398 499 /* kpw works very differently compared to
399 500 * most password managers. it uses public-key
................................................................................
400 501 * encryption so that new passwords can be saved
401 502 * to the db without bothering the user for the
402 503 * password. now we have our keypair, we can
403 504 * prompt the user for a secret passphrase with
404 505 * which to encrypt the private key with.
405 506 */
406 507
407 -
408 508 alert(a_notice, "database keypair generated, encrypting");
409 509 password dbpw;
410 510 size_t pwlen;
411 -# define _str(s) (s),sizeof(s)
412 - bad e = pwread(dbpw, &pwlen, _str("database passphrase: "));
511 + bad e = pwread(!print, dbpw, &pwlen, _str("- database passphrase: "));
413 512 if (e != ok) return e;
414 - if (isatty(0)) {
513 + if (!print && isatty(0)) {
415 514 password dbpw_conf;
416 - e = pwread(dbpw_conf, NULL, _str("confirm: "));
417 -# undef _str
515 + e = pwread(!print, dbpw_conf, NULL, _str("- confirm: "));
418 516 if (e != ok) return e;
419 517
420 518 if(strcmp(dbpw,dbpw_conf) != 0)
421 519 return bad_pw_match;
422 520 }
423 521
424 522 uint8_t salt[crypto_pwhash_SALTBYTES],
................................................................................
442 540 crypto_box_seal(salt_enc, salt, sz(salt), priv);
443 541
444 542 /* we have everything we need. now we create the
445 543 * file, failing if it already exists so as not
446 544 * to clobber anyone's passwords.
447 545 */
448 546 int db;
449 - const int flags = O_CREAT | (clobber ? O_TRUNC : O_EXCL);
547 + const int flags = O_CREAT | O_WRONLY |
548 + (clobber ? O_TRUNC : O_EXCL);
450 549 if(param == 0)
451 550 db = dbopen(flags);
452 551 else if (param == 1)
453 552 db = open(params[0], flags, 0600);
454 553 else return bad_syntax;
455 554
456 555 if (db == -1) return bad_db_create;
457 556
557 + /* the file is safely created; all that's left to
558 + * do is write out the header and then we can call
559 + * it a day.
560 + */
458 561 write(db, pub, sz(pub));
459 562 write(db, salt, sz(salt));
460 563 write(db, priv_enc, sz(priv_enc));
461 564 write(db, salt_enc, sz(salt_enc));
462 565 close(db);
463 566
464 - for (int i = 0; i < sz(key); ++i) {
465 - printf("%2x ", key[i]);
466 - if (!((i+1)%8)) printf("\n");
467 - }
567 + alert(a_debug, "database created");
468 568 break;
469 569 }
470 570 }
471 571
472 -# ifdef _CLIPBOARD
473 - if (geteuid() == 0 && copy_pw) {
474 - /* on a sane system, what we'd do is hike up the process
475 - * tree til we found a non-root user. alas, this is UNIX. */
476 - const char* realuser = getenv("SUDO_USER");
477 - if (realuser == null) realuser = "nobody";
478 -
479 - say("\x1b[1;33mwarning:\x1b[m you are running \x1b[4m");
480 - size_t namelen = strlen(argv[0]);
481 - write(2,argv[0],namelen);
482 - say("\x1b[24m as \x1b[1mroot\x1b[m! dropping to \x1b[1m");
483 - write(2,realuser,strlen(realuser));
484 - setenv("USER", realuser, true);
485 - say("\x1b[m to prevent malicious behavior\n");
486 -
487 - struct passwd* nobody = getpwnam(realuser);
488 - if (nobody == null) {
489 - say("\x1b[1;31mfatal:\x1b[m could not get UID to drop privileges; bailing");
490 - return bad_user;
491 - } else {
492 - setenv("HOME", nobody -> pw_dir, true);
493 - setenv("SHELL", "/dev/null", true);
494 - setuid(nobody -> pw_uid);
495 - }
496 -
497 - }
498 -# endif
499 572
500 573
501 574 /* char buf[len+1]; */
502 575 /* *buf = 0; */
503 576 /* mkpw(mode,buf,len); */
504 577 /* write(1, buf, len + 1); */
505 578
506 579 # ifdef _CLIPBOARD
507 580 if (copy_pw) {
508 - copy(buf,len);
581 + /* copy(buf,len); */
509 582 }
510 583 # endif
511 584
512 585 return ok;
513 586 }
514 587
515 588 int
516 589 main (int argc, const char** argv) {
590 + const char* colorterm = getenv("COLORTERM");
591 + const char* term = getenv("TERM");
592 + bool color, ansi;
593 +
594 + if (colorterm != NULL)
595 + color = true;
596 + else if (term == NULL)
597 + ansi = false, color = false;
598 + else if (strstr(term, "color") == NULL)
599 + ansi = true, color = false;
600 + else color = true;
601 +
602 + for (uint8_t i = 0; i < 3; ++i) {
603 + if(isatty(i)) {
604 + _g_term_type[i] = (color ? color_term :
605 + ansi ? ansi_term : plain_term);
606 + } else _g_term_type[i] = plain_term;
607 + }
608 +
609 +
517 610 int e = 0;
518 611 const char* msg;
519 612 uint16_t level;
520 613 uint8_t rv;
521 614 switch (e = kpw(argc, argv)) {
522 615 kpw_emit_error_switch
523 616 }
524 617
525 618 alert(level, msg);
526 619 return rv;
527 620 }