/* [ʞ] mkpw.c - make password
* ~ lexi hale <lexi@hale.su>
* © AGPLv3
* $ cc -O4 mkpw.c -omkpw [-D_CLIPBOARD]
* - D_CLIPBOARD enables mkpw to automatically
* copy new passwords to the clipboard. it
* does this by attempting to execute a sequence
* of binaries, and then writing the password
* to STDIN of the binary that succeeds.
* ? generates passwords
* → mkpw is unlikely to be portable to non-POSIX
* systems, but should run fine on Linux as well
* as BSDs with getrandom() support.
*/
#include <unistd.h>
#include <sys/random.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#define sz(a) ( sizeof (a) / sizeof (a) [0] )
#define say(x) (write(2, (x), sizeof (x)))
#ifdef _CLIPBOARD
# include <sys/types.h>
# include <pwd.h>
# include <stdlib.h>
# define _cl_opt o('n',nocopy,copy = false)
#else
# define _cl_opt
#endif
#define options \
o('l',lower,mode = lower) \
o('m',mix,mode = mix) \
o('u',upper,mode = upper)\
_cl_opt
enum /* constants */ {
null = 0, true = 1, false = 0
};
typedef enum bad {
ok = 0,
fail = 1,
bad_user,
bad_option,
bad_syntax,
bad_usage = 64, /* fleabsd idiom */
} bad;
typedef _Bool bool;
typedef unsigned long long iaia_word_type;
typedef bad iaia_error_type;
enum /* iaia errors */ {
iaia_e_ok = ok,
iaia_e_base = fail,
iaia_e_domain = fail,
iaia_e_overflow = fail,
};
#define _IAIA_FN_ATOI katoi
#include "clib/iaia.c"
typedef enum {
tbl_ok = ok, tbl_error = fail
} tbl_error_type;
typedef unsigned char tbl_word_type;
#include "clib/tbl.c"
enum args {
#define o(short, long, code) arg_##long,
options
#undef o
};
struct tblrow argtbl[] = {
#define o(short, long, code) {arg_##long, #long},
options
#undef o
};
#define str_ucase "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
#define str_lcase "abcdefghijklmnopqrstuvwxyz"
#define str_num "0123456789"
const char* reftbl = str_num str_ucase str_lcase;
const char* reftbl_lcase = str_num str_lcase;
#ifdef _CLIPBOARD
char* const* cbd_cmds[] = {
/* NOTE: these commands must be specified in order of
* most- to least-specific. more than one utility may
* be present on a given system, so we need to make sure
* the right one is called. */
(char* const[]){"termux-clipboard-set", null},
(char* const[]){"xsel", "-bi", null},
/* TODO: allow command to be specified by env var */
null
};
#endif
int main(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);
say(" [-lmu"
# ifdef _CLIPBOARD
"n"
# endif
"] [--lower] [--mix] [--upper]"
# ifdef _CLIPBOARD
" [--nocopy]"
# endif
" <length> [<quantity>]");
return bad_usage;
}
enum { upper, mix, lower } mode = lower;
unsigned long long len, q = 1;
bool gotlen = false;
bool gotq = false;
# ifdef _CLIPBOARD
bool copy = true;
# endif
for (const char** arg = argv; *arg != null; ++arg) {
if ((*arg)[0] == '-') {
if ((*arg)[1] == '-') { /* long opt */
unsigned char a;
if (tblget(sz(argtbl), argtbl, *arg + 2, &a) == ok) switch (a) {
# define o(short, long, code) case arg_##long:code;break;
options
# undef o
} else {
return bad_option;
}
} else { /* short opt */
for(const char* ptr = (*arg) + 1; *ptr != 0; ++ptr) {
switch(*ptr) {
# define o(short, long, code) case short:code;break;
options
# undef o
default: return bad_option;
}
}
}
} else {
if (gotq) return bad_syntax;
else if (gotlen) {
if (katoi(10, *arg, &q) == ok) {
gotq = true;
}
} else if(katoi(10, *arg, &len) == ok) {
gotlen = true;
}
}
}
if (!gotlen) return bad_syntax;
# ifdef _CLIPBOARD
if (geteuid() == 0 && copy) {
/* 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
const unsigned char chars = (sizeof str_num - 1) +
((mode == upper) ? (sizeof str_ucase - 1) :
((mode == lower) ? (sizeof str_lcase - 1) :
((sizeof str_ucase - 1) + (sizeof str_lcase - 1))));
const char* tbl = (mode == upper) ? reftbl :
((mode == lower) ? reftbl_lcase : reftbl);
char buf[len+1];
/* *buf = 0; */
unsigned char noise[len];
for (size_t i = 0; i<q; ++i) {
unsigned char* cur = noise;
getrandom(noise, len, 0);
for(char* ptr = buf; ptr < buf + len; ++ptr, ++cur) {
*ptr = tbl[*cur % chars]; /* such a waste of entropy… :( */
}
buf[len] = '\n';
write(1, buf, len + 1);
}
# ifdef _CLIPBOARD
if (copy) {
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 (!fork()) {
close(fds[1]);
dup2(fds[0], 0);
if (clipboard_env != null) {
execvp(clipboard_env, (char* const[]){
clipboard_env, clipboard_env_arg, null});
return -1;
} else for(char* const** cmd = cbd_cmds; *cmd != null; ++cmd) {
execvp((*cmd)[0], *cmd);
}
return -1; /* exec failed */
} else {
close(fds[0]);
write(fds[1], buf, len);
write(fds[1], "\n", 1);
close(fds[1]);
}
}
# endif
return ok;
}