@@ -16,8 +16,11 @@ * libc on my android phone, the getrandom() call * had to be converted to use the syscall() * interface. this is unlikely to cause problems, * but should be kept in mind. + * + * TODO prevent pw reads from going off the edge of + * the screen and fucking up all the shit */ #include #include @@ -32,8 +35,9 @@ #define sz(a) ( sizeof (a) / sizeof (a) [0] ) #define say(x) (write(2, (x), sizeof (x))) +#define _str(s) (s),sizeof(s) #ifdef _CLIPBOARD # include # include @@ -51,8 +55,9 @@ enum /* db format constants */ { db_pubkey_len = crypto_box_PUBLICKEYBYTES, db_privkey_len = crypto_box_SECRETKEYBYTES, kpw_db_pw_max = 64, + default_pw_len = 32, }; typedef _Bool bool; typedef unsigned long long iaia_word_type; @@ -85,8 +90,9 @@ #define str_num "0123456789" const char* reftbl = str_num str_ucase str_lcase; const char* reftbl_lcase = str_num str_lcase; +const char* _g_binary_name; #ifdef _CLIPBOARD char* const* cbd_cmds[] = { /* NOTE: these commands must be specified in order of @@ -98,15 +104,77 @@ /* TODO: allow command to be specified by env var */ null }; -bad +enum { plain_term, ansi_term, color_term } _g_term_type[3]; +enum alert {a_notice, a_warn, a_debug, a_fatal = 1 << 8}; +pstr alert_msg[] = { + _p("(kpw notice)"), _p("(kpw warn)"), + _p("(kpw debug)"), _p("(kpw fatal)") +}; + +bool _g_alert_quiet = false, + _g_debug_msgs = false; +void alert(uint16_t kind, const char* msg) { + if (!((kind >= a_fatal) || + (_g_debug_msgs && kind == a_debug) || + (!_g_alert_quiet && kind != a_debug))) return; + + uint8_t idx; + if (kind & a_fatal) idx = a_debug + 1; + else idx = kind; + + + if (_g_term_type[2] == color_term) { + char msgcode[] = "\x1b[90m"; + char* color = msgcode+3; + *color = '0' + (4 - idx); + write(2,msgcode, sz(msgcode)); + } else if (_g_term_type[2] == ansi_term) { + write(2,"\x1b[1m",4); + } + + write(2,alert_msg[idx].ptr, + alert_msg[idx].len); + + if (_g_term_type[2] != plain_term) write(2,"\x1b[m ",4); + else write(2," ",1); + write(2,msg,strlen(msg)); + write(2,"\n",1); + + if (kind & a_fatal) exit(kind & (~a_fatal)); +} + +enum bad copy(const char* str, size_t len) { + alert(a_debug, "copying password to clipboard"); + if (geteuid() == 0) { + /* on a sane system, what we'd do is hike up the process + * tree til we found a non-root user. alas, this is UNIX. */ + const char* realuser = getenv("SUDO_USER"); + if (realuser == null) realuser = "nobody"; + + alert(a_warn, "running as root! dropping privileges to prevent malicious use of copy functionality"); + setenv("USER", realuser, true); + + struct passwd* nobody = getpwnam(realuser); + if (nobody == null) { + alert(a_fatal | bad_user, "could not get UID to drop privileges; bailing"); + return bad_user; + } else { + setenv("HOME", nobody -> pw_dir, true); + setenv("SHELL", "/dev/null", true); + setuid(nobody -> pw_uid); + if (geteuid() == 0) + alert(a_fatal | bad_user, "i don't fucking think so, you sneaky bastard"); + } + + } char* const clipboard_env = getenv("mkpw_clipboard_setter"); char* const clipboard_env_arg = getenv("mkpw_clipboard_setter_arg"); // FIXME: allow multiple args int fds[2]; - if (pipe(fds) != 0) return 63; + if (pipe(fds) != 0) return bad_pipe; if (!fork()) { close(fds[1]); dup2(fds[0], 0); if (clipboard_env != null) { @@ -128,9 +196,9 @@ #endif enum genmode { upper, mix, lower, stupid }; -bad +enum bad mkpw(enum genmode mode, char* buf, size_t const len) { const unsigned char chars = (sizeof str_num - 1) + ((mode == upper) ? (sizeof str_ucase - 1) : ((mode == lower) ? (sizeof str_lcase - 1) : @@ -167,42 +235,8 @@ enum term_clear {term_clear_line, term_clear_screen}; -enum alert {a_notice, a_warn, a_debug, a_fatal = 1 << 8}; -pstr alert_msg[] = { - _p("(notice)"), _p("(warn)"), - _p("(debug)"), _p("(fatal)") -}; - -bool _g_alert_quiet = false, - _g_debug_msgs = false; -void alert(uint16_t kind, const char* msg) { - if (!((kind >= a_fatal) || - (_g_debug_msgs && kind == a_debug) || - (!_g_alert_quiet && kind != a_debug))) return; - - uint8_t idx; - if (kind & a_fatal) idx = a_debug + 1; - else idx = kind; - - - if (isatty(2)) { - char msgcode[] = "\x1b[1;30m"; - char* color = msgcode+5; - *color = '0' + (4 - idx); - write(2,msgcode, sz(msgcode)); - } - - write(2,alert_msg[idx].ptr,alert_msg[idx].len); - if (isatty(2)) write(2,"\x1b[m ",4); - else write(2," ",1); - write(2,msg,strlen(msg)); - write(2,"\n",1); - - if (kind & a_fatal) exit(kind & (~a_fatal)); -} - void term_clear(enum term_clear behavior) { switch(behavior) { case term_clear_line: write(1,"\r\x1b[2K",5); break; case term_clear_screen: write(1,"\r\x1b[3J",5); break; @@ -212,9 +246,9 @@ write(1,"\a",1); } typedef char password[kpw_db_pw_max + 1]; -bad pwread(char* dest, size_t* out_len, const char* prompt, const size_t plen) { +bad pwread(bool obscure, char* dest, size_t* out_len, const char* prompt, const size_t plen) { if (isatty(0)) { struct termios initial; { /* in order to take PW input, we need to shut * off echo and canonical mode. now we're in @@ -229,14 +263,15 @@ char* p = dest; do { term_clear(term_clear_line); - write(1, "\x1b[1m", 4); + if (_g_term_type[1] >= ansi_term) write(1, "\x1b[1m", 4); write(1, prompt, plen); - write(1, "\x1b[21m", 5); + if (_g_term_type[1] >= ansi_term) write(1, "\x1b[21m", 5); - for(size_t i = 0; i < p - dest; ++i) + if (obscure) for(size_t i = 0; i < p - dest; ++i) write(1, "*", 1); + else write(1, dest, p-dest); char c; if (read(0, &c, 1) == 1) { switch (c) { @@ -310,35 +345,33 @@ } int kpw(int argc, const char** argv) { - if (argc == 0) return -1; - if (argc == 1) { - size_t namelen = strlen(argv[0]); - say("\x1b[1musage:\x1b[m "); - write(2, argv[0], namelen); - write(2, kpw_optstr, sz(kpw_optstr)); - write(2, kpw_usage, sz(kpw_usage)); - return bad_usage; - } + if (argc == 0) return bad_insane; + _g_binary_name = argv[0]; enum genmode mode = lower; - enum {usage,getpw,addpw,delpw,genpw, + enum {usage,getpw,addpw,delpw,lspw,genpw, chpw,keyin,logout,createdb,rekeydb} op = getpw; const char* params[3]; uint8_t param = 0; bool print = false, - clobber = false; + clobber = false, + no_more_opts = false; # ifdef _CLIPBOARD bool copy_pw = true; # endif for (const char** arg = argv + 1; *arg != null; ++arg) { - if ((*arg)[0] == '-') { + if (!no_more_opts && (*arg)[0] == '-') { if ((*arg)[1] == '-') { /* long opt */ + if((*arg)[2] == 0) { + no_more_opts = true; + continue; + } unsigned char a; if (tblget(sz(argtbl), argtbl, *arg + 2, &a) == ok) switch (a) { kpw_emit_long_option_switch } else { @@ -356,8 +389,17 @@ if (param > sz(params)) return bad_syntax; params[param++] = *arg; } } + + if (op == getpw && param == 0) { + size_t namelen = strlen(argv[0]); + say("\x1b[1musage:\x1b[m "); + write(2, argv[0], namelen); + write(2, kpw_optstr, sz(kpw_optstr)); + write(2, kpw_usage, sz(kpw_usage)); + return bad_usage; + } if (sodium_init() < 0) return bad_lib_sodium_init; @@ -365,17 +407,76 @@ case getpw:{ /* kpw */ break; } - case addpw:{ /* kpw -a [] */ + case genpw: /* kpw -g[lmu] [] */ + case addpw: { /* kpw -a [] */ + if (param > 2 || param < 1) return bad_syntax; + const char* acct = params[0], + * prm = (param == 2 ? params[1] : NULL); + + alert(a_debug, "opening database"); + int db = dbopen(O_RDWR); + if (db == -1) return bad_db_load; + alert(a_debug, "reading in public key"); + uint8_t key [db_pubkey_len]; + ssize_t e = read(db, key, sz(key)); + if(e < sz(key)) return bad_db_corrupt; + + lseek(db, 0, SEEK_END); + bool tty_in = isatty(0), + tty_out = isatty(1); + + password pw; size_t pwlen; + const char* acct_pw; + if (op == addpw) { + if (prm == NULL) { + pstr prompt_l[] = { _p("- new password for "), + {0, acct}, _p(": "), }; + char prompt[pstrsum(prompt_l, sz(prompt_l)) + 1]; + if (tty_in) pstrcoll(prompt_l, sz(prompt_l), prompt); + + bad e = pwread(!print, pw, &pwlen, + prompt, sz(prompt) - 1); + if (e != ok) return e; + if (tty_in && !print) { + password pw_conf; + e = pwread(true, pw_conf, NULL, _str("- confirm: ")); + if (e != ok) return e; + if (strcmp(pw,pw_conf) != 0) + return bad_pw_match; + } + acct_pw = pw; + } else acct_pw = prm; + + } else if (op == genpw) { + unsigned long long len; + if (prm != NULL) { + alert(a_debug, "converting length parameter to integer"); + bad e = katoi(10, prm, &len); + if (e != ok) return bad_num; + } else alert(a_debug, "using default password length"), + len = default_pw_len; + + alert(a_debug, "generating new password"); + mkpw(mode, pw, len); + if (print || !tty_out) { + write(1, pw, len); + if(tty_out) write(1, "\n", 1); + } + pwlen = len; + } + if (copy_pw) copy(pw, pwlen); + alert(a_debug, "enciphering password"); + break; } case delpw:{ /* kpw -d */ break; } - case genpw:{ /* kpw -g[lmu] [] */ + case lspw: { /* kpw -t[p] [] */ break; } case createdb: { /* kpw -C [] */ @@ -387,10 +488,10 @@ * with a blake2 hash of a user-supplied passphrase * and stored in the database after the unencrypted * public key. */ - uint8_t pub [crypto_box_PUBLICKEYBYTES], - priv[crypto_box_SECRETKEYBYTES]; + uint8_t pub [db_pubkey_len], + priv[db_privkey_len]; uint8_t priv_enc[sz(priv)]; alert(a_debug, "generating keypair"); crypto_box_keypair(pub, priv); @@ -403,19 +504,16 @@ * prompt the user for a secret passphrase with * which to encrypt the private key with. */ - alert(a_notice, "database keypair generated, encrypting"); password dbpw; size_t pwlen; -# define _str(s) (s),sizeof(s) - bad e = pwread(dbpw, &pwlen, _str("database passphrase: ")); + bad e = pwread(!print, dbpw, &pwlen, _str("- database passphrase: ")); if (e != ok) return e; - if (isatty(0)) { + if (!print && isatty(0)) { password dbpw_conf; - e = pwread(dbpw_conf, NULL, _str("confirm: ")); -# undef _str + e = pwread(!print, dbpw_conf, NULL, _str("- confirm: ")); if (e != ok) return e; if(strcmp(dbpw,dbpw_conf) != 0) return bad_pw_match; @@ -445,9 +543,10 @@ * file, failing if it already exists so as not * to clobber anyone's passwords. */ int db; - const int flags = O_CREAT | (clobber ? O_TRUNC : O_EXCL); + const int flags = O_CREAT | O_WRONLY | + (clobber ? O_TRUNC : O_EXCL); if(param == 0) db = dbopen(flags); else if (param == 1) db = open(params[0], flags, 0600); @@ -454,49 +553,23 @@ else return bad_syntax; if (db == -1) return bad_db_create; + /* the file is safely created; all that's left to + * do is write out the header and then we can call + * it a day. + */ write(db, pub, sz(pub)); write(db, salt, sz(salt)); write(db, priv_enc, sz(priv_enc)); write(db, salt_enc, sz(salt_enc)); close(db); - for (int i = 0; i < sz(key); ++i) { - printf("%2x ", key[i]); - if (!((i+1)%8)) printf("\n"); - } + alert(a_debug, "database created"); break; } } -# ifdef _CLIPBOARD - if (geteuid() == 0 && copy_pw) { - /* on a sane system, what we'd do is hike up the process - * tree til we found a non-root user. alas, this is UNIX. */ - const char* realuser = getenv("SUDO_USER"); - if (realuser == null) realuser = "nobody"; - - say("\x1b[1;33mwarning:\x1b[m you are running \x1b[4m"); - size_t namelen = strlen(argv[0]); - write(2,argv[0],namelen); - say("\x1b[24m as \x1b[1mroot\x1b[m! dropping to \x1b[1m"); - write(2,realuser,strlen(realuser)); - setenv("USER", realuser, true); - say("\x1b[m to prevent malicious behavior\n"); - - struct passwd* nobody = getpwnam(realuser); - if (nobody == null) { - say("\x1b[1;31mfatal:\x1b[m could not get UID to drop privileges; bailing"); - return bad_user; - } else { - setenv("HOME", nobody -> pw_dir, true); - setenv("SHELL", "/dev/null", true); - setuid(nobody -> pw_uid); - } - - } -# endif /* char buf[len+1]; */ /* *buf = 0; */ @@ -504,9 +577,9 @@ /* write(1, buf, len + 1); */ # ifdef _CLIPBOARD if (copy_pw) { - copy(buf,len); + /* copy(buf,len); */ } # endif return ok; @@ -513,8 +586,28 @@ } int main (int argc, const char** argv) { + const char* colorterm = getenv("COLORTERM"); + const char* term = getenv("TERM"); + bool color, ansi; + + if (colorterm != NULL) + color = true; + else if (term == NULL) + ansi = false, color = false; + else if (strstr(term, "color") == NULL) + ansi = true, color = false; + else color = true; + + for (uint8_t i = 0; i < 3; ++i) { + if(isatty(i)) { + _g_term_type[i] = (color ? color_term : + ansi ? ansi_term : plain_term); + } else _g_term_type[i] = plain_term; + } + + int e = 0; const char* msg; uint16_t level; uint8_t rv;