util  Diff

Differences From Artifact [f865f78c63]:

To Artifact [f4297483ea]:


    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   }