Index: clib/iaia.c ================================================================== --- clib/iaia.c +++ clib/iaia.c @@ -29,10 +29,13 @@ * representation or to store it in 8-bit space. * 1 = 7-bit (compressed) * 0 = 8-bit (uncompressed) */ # define _IAIA_EXP_ASCFORM (0) #endif +#ifndef NULL +# define NULL ((void*)0) +#endif enum /* constants */ { base7bit = 128, /* ascii address space */ @@ -54,14 +57,15 @@ typedef unsigned long long iaia_word_type; #endif /* -- string to integer converters -- */ -iaia_error_type _IAIA_FN_ASCTOI(const char* s, iaia_word_type* ret) { +iaia_error_type +_IAIA_FN_ASCTOI(const char* s, iaia_word_type* ret) { iaia_word_type val = 0; - for (;*s!=null;++s) { + for (;*s!=0;++s) { uint8_t v = *s; if (v > base7bit) return iaia_e_domain; if (_IAIA_EXP_ASCFORM) val *= base7bit; @@ -69,15 +73,16 @@ val += v; } *ret = val; - return ok; + return iaia_e_ok; } -iaia_error_type _IAIA_FN_ATOI(iaia_word_type base, const char* s, iaia_word_type* ret) { - /* s must be a null-terminated ASCII numeral string */ +iaia_error_type +_IAIA_FN_ATOI(iaia_word_type base, const char* s, iaia_word_type* ret) { + /* s must be a NUL-terminated ASCII numeral string */ if (base > maxbase) return iaia_e_base; /* override the default base if it's a basèd literal */ if (s[0] == '@' || base == 0) return _IAIA_FN_ASCTOI(s + (s[0]=='@'),ret); else if (s[0] == '0' && s[1] == 'x') base = 16, s += 2; @@ -87,11 +92,11 @@ else if (s[0] == '0') base = 8, s += 1; bool insens = (base <= imaxbase); iaia_word_type val = 0; - for (;*s!=null;++s) { + for (;*s!=0;++s) { uint8_t v = *s; if(v >= 0x30 && v <= 0x39) v -= 0x30; else { if(v >= 0x61 && v <= 0x7a) { if (insens) v -= 0x20; else { v = numspace + alphaspace + (v - 0x61); @@ -114,19 +119,20 @@ /* -- integer to string converters -- */ /* needed for efficiency's sake, but really sucky - * this table needs to be kept in sync with the - * itoa algorithm by hand. unfortunately, given C's + * itoa algorithm manually. unfortunately, given C's * abject lack of metaprogramming, we have to do this * by hand. */ const char iaia_ref_table[] = /* numerals[10] */ "0123456789" /* bigalpha[26] */ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" /* smallalpha[26] */ "abcdefghijklmnopqrstuvwxyz"; _Static_assert (sizeof iaia_ref_table - 1 == maxbase, "tables out of sync"); -iaia_error_type _IAIA_FN_ITOASC(iaia_word_type val, const char* buf_start, char* buf_end, char** newbuf) { +iaia_error_type +_IAIA_FN_ITOASC(iaia_word_type val, const char* buf_start, char* buf_end, char** newbuf) { char* ptr = buf_end; *ptr-- = 0; while(val > 0) { if (ptr < buf_start) return iaia_e_overflow; @@ -135,15 +141,16 @@ val /= base7bit; else val >>= 8; *ptr-- = (char)rem; } - if (newbuf != null) *newbuf = ptr + 1; - return ok; + if (newbuf != NULL) *newbuf = ptr + 1; + return iaia_e_ok; } -iaia_error_type _IAIA_FN_ITOA(iaia_word_type base, iaia_word_type val, const char* buf_start, +iaia_error_type +_IAIA_FN_ITOA(iaia_word_type base, iaia_word_type val, const char* buf_start, char* buf_end, char** newbuf, bool lowercase) { char* ptr = buf_end; if (base > maxbase) return iaia_e_base; @@ -160,9 +167,9 @@ if (out >= 'A' && out <= 'Z') out += ('a' - 'A'); *ptr-- = out; } - if (newbuf != null) *newbuf = ptr + 1; + if (newbuf != NULL) *newbuf = ptr + 1; return iaia_e_ok; } Index: makefile ================================================================== --- makefile +++ makefile @@ -5,14 +5,21 @@ # to build project contained in their own directory, run # $ make (dir).proj all: ctl conv xutil gen +clean: + rm xpriv safekill mkpw kpw.bin rosshil ord nkvd.so bgrd +install: + mkdir -p $(prefix)/bin $(prefix)/lib + cp xpriv safekill mkpw rosshil ord bgrd ${out}/bin/ + cp kpw.bin ${out}/bin/kpw + cp nkvd.so $(prefix)/lib xutil: xpriv safekill # X11 tools -gen: mkpw kpw rosshil +gen: mkpw kpw.bin rosshil # procedural generators conv: ord # converters ctl: nkvd.so bgrd # manipulating disobedient software Index: makerules ================================================================== --- makerules +++ makerules @@ -16,10 +16,12 @@ post = $(if $(debug),, && strip $@) cc-post = $(post) sc-post = $(post) mc-post = $(post) + +prefix = $(if ${out},${out},/usr) os = $(shell uname -o) %: %.c $(cc) $< -o$@ $(cc-post) Index: ord.c ================================================================== --- ord.c +++ ord.c @@ -3,11 +3,11 @@ * © AGPLv3 * * ord has no dependencies except for libc. * ? ord converts integers to ascii characters * and back. written because the only fucking * way to do this in shell is FUCKING PRINTF. - * $ cc ord.c -o ord [-D_IO=(LIBC|POSIX)] + * $ cc ord.c -o ord [-D_(POSIX|LIBC)_IO] * - the flag D_IO will instruct ord.c whether * to use POSIX io primitives (write and read) * instead of libc primitives (printf). if * you're on a UNIX system, POSIX primitives * will be used by default, but you can block @@ -18,11 +18,11 @@ TODO: take full advantage of write(2) by storing output in single string & making single write call */ -#if (defined(__unix__) && _IO != LIBC) || (_IO == POSIX) +#if (defined(__unix__) && !defined(_POSIX_IO)) && !defined(_LIBC_IO) # define _POSIX_IO #endif #ifdef _POSIX_IO # include @@ -31,11 +31,11 @@ # define forposix(x) x # define forlibc(x) #else # include # define say(x) (fprintf(stderr, (x))) -# define print(x) (printf("%s",(x))) +# define print(sz,x) (printf("%s",(x))) # define forposix(x) # define forlibc(x) x #endif #include #include Index: safekill.c ================================================================== --- safekill.c +++ safekill.c @@ -178,12 +178,12 @@ #define info "\x1b[34m-- " #define param "\x1b[32m" #define eol "\x1b[m\n" #define bold "\x1b[1m" #define nl " " - fprintf(stderr, bold "usage:\x1b[0m %s " " " info "kill active window if non-vital" eol + fprintf(stderr, bold "usage:\x1b[0m %1$s " " " info "kill active window if non-vital" eol nl "%1$s -v " param "[id] " info "make [id] or active window vital" eol nl "%1$s -c " param "[id] " info "clear vital flag on [id] or active window" eol nl "%1$s -q " param "[id] " info "'emergency' kill w/o reference to vital flag" eol, argv[0]); return 1; } Index: smake.c ================================================================== --- smake.c +++ smake.c @@ -19,10 +19,11 @@ #include #include #include #include #include +#include enum e { ok, badname, nofile }; enum kind { css, sass, scss, bad }; typedef enum { false, true } bool; @@ -49,11 +50,11 @@ bufptr = extbuf + rsz + sz; return bufptr - sz; } } } - // this point should never be reached + assert(false); /* this point should never be reached */ mkptr: { void* ret = bufptr; bufptr += sz; return ret; } @@ -125,16 +126,16 @@ namecur = namebuf; } ++cur; goto read_start; read_string: - if (*cur == 0) goto read_done; //unterminated string!? + if (*cur == 0) goto read_done; /* unterminated string!? */ if (*cur++ == strqt) goto read_start; goto read_string; read_ml_comment: - if (*cur == 0) goto read_done; //unterminated comment! + if (*cur == 0) goto read_done; /* unterminated comment! */ if (*cur == '*' && cur[1] == '/') { cur += 2; goto read_start; } ++cur; goto read_ml_comment; ADDED soda.c Index: soda.c ================================================================== --- soda.c +++ soda.c @@ -0,0 +1,1167 @@ +/* [ʞ] soda - libsodium front end + * ~ lexi hale + * © AGPLv3 + * @ vim: ft=c + */ + +#define _POSIX_C_SOURCE 200809L +#include +#include +#include /* boo hiss */ +#include +#include +#include +#include +#include +#include +#define sz(x) (sizeof(x)/sizeof((x)[0])) + +#define _IAIA_FN_ITOA str_of_int +#define _IAIA_FN_ATOI int_of_str +#include "clib/iaia.c" + +#ifdef _WIN32 +# define open _open +# define close _close +# define read _read +# define write _write +#endif + +#define _self_name "soda" + +typedef unsigned long long word; + +typedef enum atom { + /* commands */ + atom_none, + atom_key, + atom_gen, + atom_pub, + atom_psk, + atom_pair, + atom_cert, + atom_sign, + atom_verify, + atom_enc, + atom_dec, + atom_wrap, + atom_unwrap, + atom_help, + _atom_cmd_n, + + /* flags */ + atom_verbose, + atom_quiet, + atom_noise, + atom_ascii_armor, + atom_color_on, + atom_color_off, + atom_out, atom_outfd, + atom_in, atom_infd, + atom_pw, atom_pwfd, atom_pwtty, + atom_sig, atom_sigfd, + atom_symmetric, + atom_raw, + atom_fd, + atom_fmtv, + _atom_flag_n, + + /* debug values */ + atom_debug, + atom_info, + atom_warn, + atom_fatal, + atom_silent, + + _atom_n, + atom_capture = _atom_n +} atom; + +enum noise { + noise_debug, + noise_info, + noise_warn, + noise_fatal, + noise_silent, + + _noise_n +}; +struct { const char* label; unsigned char color; } +noise_level_text[] = { + [noise_debug] = {"(debug",5}, + [noise_info ] = {" (info", 4}, + [noise_warn ] = {" (warn", 3}, + [noise_fatal] = {"(fatal",1}, +}; + +struct cmd_spec { const char* desc; const char** params; struct cmd_spec* sub; } +cmd_specs[_atom_cmd_n] = { {NULL,NULL,NULL}, + [atom_key] = { "create and manipulate keypairs for public-key crypto", NULL, (struct cmd_spec[_atom_cmd_n]){ + [atom_gen] = { "generate new keypair", NULL, NULL }, + [atom_pub] = { "extract the public key portion from a keypair", NULL, NULL}, + [atom_wrap] = { "convert a binary key to its ascii-armor representation", NULL, NULL}, + }}, + [atom_psk] = { "create and manipulate keys for secret-key crypto", NULL, (struct cmd_spec[_atom_cmd_n]){ + [atom_gen] = { "generate new PSK", NULL, NULL }, + }}, + [atom_help] = { "display usage listings for commands", + (const char*[]){"commands…",NULL}, NULL }, + [atom_sign] = { "sign data using public-key crypto", + (const char*[]){"keyfile",NULL}, NULL }, + [atom_verify] = { "verify signature on data", + (const char*[]){"pubfile",NULL}, NULL }, + [atom_enc] = { "encrypt data", NULL, (struct cmd_spec[_atom_cmd_n]){ + [atom_pub] = { "encrypt against public key", (const char*[]){ + "pubfile", NULL + }, NULL}, + [atom_psk] = { "encrypt against symmetric, pre-shared key", (const char*[]){ + "pskfile", NULL + }, NULL}, + }}, + [atom_wrap] = {"wrap binary input with ascii armor so it can survive transmission mechanisms that are not 8-bit clean", NULL,NULL}, + [atom_unwrap] = {"translate ascii-armored input back to the original binary", NULL,NULL}, +}; + +enum say { say_stop, say_string, say_string_list, say_char, say_uint, say_sint, say_hex }; +struct sayp { + enum say kind; union { + const char* str; + const char** strlist; + char ch; + long long unsigned int uint; + long long signed int sint; + }; +}; + +enum feature { + feature_off = 0, + feature_on = 1, + feature_auto = 2, +}; + +struct ctx { + size_t argc; const char** argv; + size_t cmdc; atom* cmdv; + const char* bin; + struct { + enum noise noise; + enum feature + ascii_armor, + color; + bool fd; + bool pw_force_tty; + const char* infile,* outfile,* pwfile,* sigfile; + word infd, outfd, pwfd, sigfd; + bool symcrypt; + word fmtv; + } flags; +}; + +bool +isws(const char c) { + /* TODO: maybe ignore all characters outside the ascii armory? */ + return (c == ' ' || c == '\t' || c == '\n'); +} + +enum { _buffer_max = 512 }; +struct buffer { + int fd; /* where to flush */ + size_t sz; + char bytes[_buffer_max]; +}; +void buffer_flush(struct buffer* b) { write(b->fd, b->bytes, b->sz); b->sz = 0; } +#define _buffer_write(s,b) buffer_write(s, b, sizeof(b)) +void buffer_write(struct buffer* b, const char* str, size_t sz) { + if (sz == 0) { + size_t i; + for (i = 0; str[i] != 0; ++i) { + const size_t ofs = (b->sz + i) % _buffer_max; + b->bytes[ofs] = str[i]; + if (ofs == 0 && i != 0) buffer_flush(b); + } + b -> sz = (b->sz + i) % _buffer_max; + } else if (sz > _buffer_max) { + buffer_flush(b); + write(b->fd, str, sz); + b->sz = sz; + } else if (_buffer_max - b->sz < sz) { + buffer_flush(b); + memcpy(b->bytes, str, sz); + b->sz = sz; + } else { + memcpy(b->bytes + b->sz, str, sz); + b->sz += sz; + } +} +void buffer_push(struct buffer* b, const char c) { buffer_write(b, &c, 1); } + +void say(struct ctx c, enum noise kind, struct sayp* s) { + struct buffer o = {2,0}; + if (c.flags.noise > kind) return; + if (noise_level_text -> label != NULL) + if (c.flags.color) { + buffer_write(&o,"\x1b[1;3",0); + buffer_push(&o, 0x30 + noise_level_text[kind].color); + _buffer_write(&o,"m"); + buffer_write(&o, noise_level_text[kind].label, 0); + _buffer_write(&o,")\x1b[m"); + } else { + buffer_write(&o, noise_level_text[kind].label, 0); + buffer_push(&o,')'); + } + + bool bright = false; + while(s -> kind != say_stop) { + if (c.flags.color) { + if (bright) _buffer_write(&o," \x1b[1m"); + else _buffer_write(&o," \x1b[m"); + } else { + buffer_push(&o,' '); + } + char intbuf[128]; + char* const intbuf_end = intbuf + sizeof intbuf; + char* start; + + explain: switch(s -> kind) { + case say_string: buffer_write(&o, s -> str, 0); break; + case say_char: buffer_push(&o, s -> ch); break; + case say_uint: + str_of_int(10, s -> uint, intbuf, intbuf_end - 1, &start, true); + buffer_write(&o, start, intbuf_end - start); + break; + case say_sint: + /* TODO oops! iaia doesn't have signed int support yet 🙃 */ + /* str_of_int(10, s -> uint, intbuf, intbuf + sizeof intbuf, &end, true) */ + /* buffer_write(&o, intbuf, end - intbuf); */ + break; + case say_hex: + str_of_int(16, s -> uint, intbuf, intbuf_end - 1, &start, true); + _buffer_write(&o, "0x"); + buffer_write(&o, start, intbuf_end - start); + break; + case say_string_list: + buffer_push(&o,'['); + for(size_t i = 0; s -> strlist[i] != NULL; ++i) { + if (i != 0) dprintf(2,", "); + _buffer_write(&o,"“"); + buffer_write(&o,s -> strlist[i],0); + _buffer_write(&o,"”"); + } + buffer_push(&o,']'); + break; + } + + ++s; bright = !bright; + } + + if (c.flags.color) _buffer_write(&o, "\x1b[m\n"); + else buffer_push(&o, '\n'); + + buffer_flush(&o); +} + +enum { _atom_max_str_len = 12 }; +struct { atom val; const char* str;} +/* first name listed for an atom is treated as the canonical name + * by reconstitute() and displayed in usage listings */ +atom_strings[] = { + /* commands */ + {atom_key, "key"}, {atom_key, "privkey"}, {atom_key, "priv"}, + {atom_pub, "pub"}, {atom_pub, "pubkey"}, + {atom_psk, "psk"}, + {atom_cert, "cert"}, {atom_cert, "certificate"}, + {atom_sign, "sign"}, + {atom_verify, "verify"}, {atom_verify, "vfy"}, {atom_verify, "v"}, + {atom_enc, "enc"}, {atom_enc, "encipher"}, {atom_enc, "encrypt"}, + {atom_dec, "dec"}, {atom_dec, "decipher"}, {atom_dec, "decrypt"}, + {atom_wrap, "wrap"}, {atom_wrap, "armor"}, + {atom_unwrap, "unwrap"}, {atom_unwrap, "dearmor"}, {atom_unwrap, "disarmor"}, + {atom_gen, "gen"}, {atom_gen, "generate"}, {atom_gen, "create"}, {atom_gen, "new"}, + {atom_help, "help"}, + + /* flags */ + {atom_quiet, "quiet"}, + {atom_ascii_armor, "ascii-armor"}, + {atom_raw, "raw"}, + {atom_sig, "sig"}, {atom_sig, "sigfile"}, + {atom_sigfd, "sig-fd"}, {atom_sig, "sigfd"}, + {atom_verbose, "verbose"}, + {atom_noise, "noise"}, + {atom_color_on, "color"}, + {atom_color_off, "no-color"}, + {atom_out, "out"}, {atom_out, "output"}, {atom_out, "outfile"}, + {atom_outfd, "out-fd"}, {atom_outfd, "outfd"}, + {atom_in, "in"}, {atom_out, "input"}, {atom_out, "infile"}, + {atom_infd, "in-fd"}, {atom_infd, "infd"}, + {atom_pw, "pw"}, {atom_pw, "passphrase"}, {atom_pw, "password"}, + {atom_pwfd, "pw-fd"}, {atom_pwfd, "pwfd"}, + {atom_pwtty, "pw-tty"}, {atom_pwtty, "pwtty"}, + {atom_fd, "fd"}, + {atom_symmetric, "symmetric"}, {atom_symmetric, "sym"}, {atom_symmetric, "symcrypt"}, + {atom_fmtv, "format"}, {atom_fmtv, "fmt"}, {atom_fmtv, "format-version"}, + + /* debug levels */ + {atom_silent, "silent"}, + {atom_debug, "debug"}, {atom_debug, "dbg"}, + {atom_info, "info"}, + {atom_warn, "warn"}, {atom_warn, "warnings"}, + {atom_fatal, "fatal"}, {atom_fatal, "error"}, +}; + +atom +atomize(const char* str) { + for(size_t i = 0; i < sz(atom_strings); ++i) { + if (strncmp(atom_strings[i].str, str, _atom_max_str_len) == 0) { + return atom_strings[i].val; + } + } + return atom_none; +} + +const char* +reconstitute(atom a) { + for(size_t i = 0; i < sz(atom_strings); ++i) { + if (atom_strings[i].val == a) return atom_strings[i].str; + } + return NULL; +} + +struct flag { + enum flag_kind { + flag_none, + _flag_noparam, + flag_on, flag_off, flag_switch, + flag_inc, flag_dec, flag_sdec, + _flag_param, + flag_atom, flag_str, + flag_uint, flag_sint, + flag_raw_atom, + flag_value + } kind; + char shortname; + const char* desc; + struct { atom first; atom last; } opts; + size_t offset; +} + +#define _declflag_raw(a,k,s,o,rs,re,d) [atom_##a] = { \ + .kind = flag_##k, \ + .shortname = s, \ + .desc = d, \ + .opts = { rs,re }, \ + .offset = (size_t)&(((struct ctx*)0) -> flags.o) \ +}, +#define _declflag(a,k,s,o,d) _declflag_raw(a,k,s,o,0,0,d) +#define _declflaga(a,rs,re,s,o,d) _declflag_raw(a,atom,s,o,rs,re,d) + +flags[_atom_n] = { + {flag_none}, + _declflag(ascii_armor, value + feature_on, 'a', ascii_armor, + "emit ascii-encoded data instead of raw binary " + "data (default if stdout is a TTY)") + _declflag(raw, value + feature_off, 'r', ascii_armor, + "emit binary data instead of raw ascii-armored " + "data (default if stdout is not a TTY)") + _declflag(in, str, 'i', infile, + "read data from a file instead of stdin") + _declflag(infd, uint, 'I', infd, + "read data from a file instead of stdin") + _declflag(out, str, 'o', outfile, + "write data to a file instead of stdout") + _declflag(outfd, uint, 'O', outfd, + "write data to a file instead of stdout") + _declflag(pw, str, 'p', pwfile, + "load passphrase from file instead of stdin") + _declflag(pwfd, uint, 'P', pwfd, + "load passphrase from file instead of stdin") + _declflag(pwtty, switch, 'W', pw_force_tty, + "always read password from the input TTY unless a file is specified instead") + _declflag(sig, str, 'g', sigfile, + "read signature from specified file instead of extracting it from input data") + _declflag(sigfd, uint, 'G', sigfd, + "read signature from specified file descriptor instead of extracting it from input data") + _declflag(symmetric, switch, 's', symcrypt, + "secure sensitive key material with symmetric cryptography") + _declflag(fd, switch, 'f', fd, + "interpret filename arguments to commands as file descriptor numbers") + _declflag(color_on, value + feature_on, 'c', color, + "enable colorized output (default if stderr is a TTY)") + _declflag(color_off, value + feature_off, 'C', color, + "disable colorized output") + _declflag(verbose, dec, 'v', noise, + "increase noise level") + _declflag(quiet, value + noise_silent, 'q', noise, + "emit nothing to stderr") + _declflaga(noise, atom_debug, atom_silent, 'n', noise, + "set noise level") + _declflag(fmtv, uint, 'F', fmtv, + "use a specific data file format version instead of the native version (0)") +}; + +const char** flag_enum_desc[_atom_n] = { + [atom_noise] = (const char*[_noise_n]){ + [noise_silent] = "run in complete silence", + [noise_debug] = "emit all notices, including those useful only for debugging", + [noise_info] = "report on the progress of the operation", + [noise_warn] = "emit non-fatal warnings", + [noise_fatal] = "only emit fatal errors", + }, +}; + +#undef _declflag +#undef _declflaga +#undef _declflag_raw + +/* the casts here are a bit wacky. this is unfortunately necessary as C + * insists on evaluating each branch of the generic expression regardless + * of which type is picked. why anyone thought this was a good idea is + * beyond me. */ +#define _t(x) (_Generic((x), \ + const char**: (struct sayp){say_string_list, .strlist = (const char**)(size_t)(x)}, \ + char: (struct sayp){say_char, .ch = (char)(size_t)(x)}, \ + char*: (struct sayp){say_string, .str = (const char*)(size_t)(x)}, \ + const char*: (struct sayp){say_string, .str = (const char*)(size_t)(x)}, \ + int: (struct sayp){say_sint, .sint = (long long)(x)}, \ + short: (struct sayp){say_sint, .sint = (long long)(x)}, \ + long: (struct sayp){say_sint, .sint = (long long)(x)}, \ + long long: (struct sayp){say_sint, .sint = (long long)(x)}, \ + unsigned: (struct sayp){say_uint, .uint = (long long unsigned)(x)}, \ + unsigned short: (struct sayp){say_uint, .uint = (long long unsigned)(x)}, \ + long unsigned: (struct sayp){say_uint, .uint = (long long unsigned)(x)}, \ + long long unsigned: (struct sayp){say_uint, .uint = (long long unsigned)(x)}, \ + default: (struct sayp){say_hex, .uint = (size_t)(x)})), +#define _say(k,...) say(c, noise_##k, ((struct sayp[]){__VA_ARGS__ {say_stop}})) +#define _report(k,s) _say(k, _t(s)) + +void +usage(struct ctx c, const struct cmd_spec* tree) { + const char* bin = c.bin; + const char* fmt_flag,* fmt_usage,* fmt_enum,* head_start,* head_fin; + if (c.flags.color) { + fmt_usage = "\x1b[1musage:\x1b[m %s \x1b[32m[flags]\x1b[m%s \x1b[33mcommand\x1b[m\n"; + fmt_flag = " \x1b[1;36m%c%c \x1b[;95m--%s\x1b[m: %s\n"; + fmt_enum = " \x1b[94m%s\x1b[m: %s\n"; + head_start = "\x1b[1m"; head_fin = "\x1b[m"; + } else { + fmt_usage = "usage: %s [flags]%s \n"; + fmt_flag = " %c%c --%s: %s\n"; + fmt_enum = " %s: %s\n"; + head_start = head_fin = ""; + } + + char ccmd[32]; char *cptr = ccmd; *cptr = 0; + if (tree == NULL) tree = cmd_specs; + else if(c.cmdc > 1) for (size_t i = 1; i", tree[i].params[j]); + } + } + dprintf(2,"%s: %s\n", + tree[i].sub != NULL ? " …" : "", + tree[i].desc); + } + + dprintf(2,"%sflags:%s\n",head_start,head_fin); + for (size_t i = _atom_cmd_n + 1; i<_atom_flag_n; ++i) { + /* if (flags[i].kind == flag_none) continue; */ + dprintf(2,fmt_flag, + flags[i].shortname != 0 ? '-' : ' ', + flags[i].shortname != 0 ? flags[i].shortname : ' ', + reconstitute(i), flags[i].desc); + if (flags[i].kind == flag_atom || flags[i].kind == flag_raw_atom) { + for (atom a = flags[i].opts.first; a < flags[i].opts.last + 1; ++a) { + dprintf(2,fmt_enum,reconstitute(a),flag_enum_desc[i][a - flags[i].opts.first]); + } + } + } +} + +bool +atom_match(struct ctx c, size_t* match) { + /* primitive pattern-matching function */ + for (size_t i = 0; i < c.cmdc; ++i) { + if (match[i] == 0) return false; + + if (match[i] < atom_capture) { + if (match[i] != c.cmdv[i]) return false; + } else if (c.cmdv[i] >= _atom_n) { + const char** dest = (const char**)(match[i] - atom_capture); + *dest = c.argv[c.cmdv[i] - _atom_n]; + } else { + return false; + } + } + return match[c.cmdc] == 0; +} + +/* the ascii armory is intentionally limited to characters that are + * not only printable but that are unlikely to have special meaning + * such that they would require escaping for any form of input. + * while this is a more limited set than base64, it's significantly + * stronger armor -- a user can, for instance, simply paste it into + * most shells without quoting. */ +const char ascii_armory[] = "0123456789/abcdefghijklmopqrstuvwxyz." + "-ABCDEFGHIJKLMOPQRSTUVWXYZ_"; +const char ascii_value[] = + "\x25\x24\x0a\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\00\00\00" + "\00\00\00\00\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30" + "\x31\x32\00\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e" + "\00\00\00\00\x3f\00\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13" + "\x14\x15\x16\x17\00\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21" + "\x22"; + /* generated by a script. do not touch. */ + +enum { armor_split_every = 48 }; +char* +armor(const unsigned char* src, char* dest, size_t sz, size_t brstart, bool format) { + char* bufptr = dest; + unsigned char carry = 0; + for(size_t i = 0; i> 2)), + b2 = ascii_value[s[1] - '-'] + (64 * ( carry & 3)); + *(dest++)=b1; *(dest++)=b2; + } + return dest; +} + +struct pstr { + size_t len; + const char* str; +}; +#define _p(x) {sizeof(x)-1,(x)} +struct encoding { struct pstr bin; struct pstr asc; const char* desc;}; +enum doc { + doc_none, + doc_header, + doc_footer, + + _doc_versions, + doc_version_1, + doc_version_2, + doc_version_3, + + _doc_types, + doc_type_priv /* private key */, + doc_type_pub /* public key */, + doc_type_psk /* shared secret key */, + doc_type_asc /* arbitrary ascii-armored data */, + doc_type_sig /* separate signature */, + doc_type_signed /* document prefixed with signature */, + doc_type_master /* master key used to derive keys deterministically */, + + _doc_n +}; +const struct encoding docbytes[_doc_n] = { {0,NULL}, + [doc_header] = {_p("\x26\xe9\x11\x1a"), _p("[" _self_name)}, + [doc_footer] = {_p(""), _p("]")}, + [doc_version_1] = {_p("\01"), _p("1-")}, + [doc_version_2] = {_p("\02"), _p("2-")}, + [doc_version_3] = {_p("\03"), _p("3-")}, + [doc_type_priv] = {_p("\01"), _p("priv-"),"private key material"}, + [doc_type_pub] = {_p("\02"), _p("pub-"), "public key material"}, + [doc_type_psk] = {_p("\03"), _p("psk-"), "shared key material"}, + [doc_type_asc] = {_p("\04"), _p("asc-"), "ascii-armored binary data"}, + [doc_type_sig] = {_p("\05"), _p("sig-"), "separate signature"}, + [doc_type_signed] = {_p("\06"), _p("signed-"), "data with signature"}, + [doc_type_master] = {_p("\07"), _p("master-"), "master key"}, +}; + + +char* +compare_wsi(const char* s, char* d, size_t sz) { + for (size_t i=0; iraw + len, run - len)) { + if (e == -1) { + switch(errno) { + case EBADF: _say(fatal, _t("file descriptor") _t((unsigned)fd) _t("does not reference an open, readable file")); break; + case EFAULT: _say(fatal, _t("file descriptor") _t((unsigned)fd) _t("references a file outside your accessible address space")); break; + case EIO: _say(fatal, _t("file descriptor") _t((unsigned)fd) _t("could not be read due to an I/O error")); break; + case EISDIR: _say(fatal, _t("file descriptor") _t((unsigned)fd) _t("is a directory, not a readable file")); break; + default: _say(fatal, _t("file descriptor") _t((unsigned)fd) _t("could not be opened due to an unknown error - consult errno table for code") _t((unsigned)errno)); break; + } + goto err; + } + + len += e; + if (len == run) { + run *= 2; buf = realloc(buf, run + sizeof(struct file)); + } else if (len < run) continue; + } + + buf -> rawlen = len; + buf -> type = doc_none; + return buf; + + err: free(buf); + return NULL; +} + +struct file* read_file(struct ctx c, const int fd) { + struct file* buf = load_file(c, fd); + bool binary; size_t len = buf -> rawlen; + char* cur = buf -> raw; + + if (file_cmp(doc_header,&cur,true)) { + _report(debug, "parsing binary data"); + binary = true; + } else if (file_cmp(doc_header,&cur,false)) { + _report(debug, "parsing ascii-armored data"); + binary = false; + } else { + _say(fatal, _t("cannot parse data from fd") _t((unsigned)fd)); + goto err; + } + + /* write(2, cur, run - (cur - buf->raw)); */ + + for (enum doc i = _doc_versions + 1; i < _doc_types; ++ i) { + if (file_cmp(i, &cur, binary)) { + _say(debug, _t("file is format version") _t((unsigned) i - _doc_versions)); + buf -> version = i; goto found_version; + } + } /* else */ { + _say(fatal, _t("unrecognized file format version; are you using the most recent release of " _self_name "?")); + goto err; + } + found_version:; + + for (enum doc i = _doc_types + 1; i < _doc_n; ++ i) { + if (file_cmp(i, &cur, binary)) { + _say(debug, _t("file contains") _t(docbytes[i].desc)); + buf -> type = i; goto found_type; + } + } /* else */ { + _say(fatal, _t("unrecognized file type; are you using the most recent release of " _self_name "?")); + goto err; + } + found_type: if (!binary) { + size_t s = (len - (cur - buf->raw)) - docbytes[doc_footer].asc.len; + _say(debug, _t("disarmoring") _t(s) _t("bytes of encoded text")); + char* end = disarmor(cur, buf -> raw, s); + buf -> datlen = end - cur; + } else { + buf -> datlen = (len - (cur - buf->raw)) - docbytes[doc_footer].bin.len; + } + + buf -> datastart = cur; + return buf; + + err: free(buf); + return NULL; +} + +void +write_armored_v1(struct ctx c, const unsigned char* data, size_t sz) { + struct buffer o = {c.flags.outfd,0}; +# define _wra_fld(f) buffer_write(&o, docbytes[doc_##f].asc.str,\ + docbytes[doc_##f].asc.len); + _wra_fld(header); + _wra_fld(version_1); + _wra_fld(type_asc); + + char buf[sz*2]; + char* end = armor(data,buf,sz,13, isatty(c.flags.outfd)); + buffer_write(&o, buf, end - buf); + + _wra_fld(footer); +# undef _wra_fld + + if (isatty(c.flags.outfd)) buffer_push(&o, '\n'); + buffer_flush(&o); +} + +void +write_privkey_v1(struct ctx c, const unsigned char* material) { + struct buffer o = {c.flags.outfd,0}; +# define _wrp_fld(f) buffer_write(&o, \ + (c.flags.ascii_armor ? docbytes[doc_##f].asc.str \ + : docbytes[doc_##f].bin.str), \ + (c.flags.ascii_armor ? docbytes[doc_##f].asc.len \ + : docbytes[doc_##f].bin.len)) + + _wrp_fld(header); + _wrp_fld(version_1); + _wrp_fld(type_priv); + + if (c.flags.ascii_armor) { + char buf[crypto_sign_SECRETKEYBYTES * 2]; + char* end = armor(material,buf,crypto_sign_SECRETKEYBYTES,13, + isatty(c.flags.outfd)); + buffer_write(&o, buf, end - buf); + } else { + buffer_write(&o, material, crypto_sign_SECRETKEYBYTES); + } + + _wrp_fld(footer); + if (isatty(c.flags.outfd)) buffer_push(&o, '\n'); + buffer_flush(&o); +} + +typedef void (*write_privkey_t)(struct ctx, const unsigned char*); +typedef void (*write_armored_t)(struct ctx, const unsigned char*, size_t); + +write_privkey_t write_privkey[] = { + [0] = write_privkey_v1, /* current version */ + + [1] = write_privkey_v1, +}; +write_armored_t write_armored[] = { + [0] = write_armored_v1, /* current version */ + + [1] = write_armored_v1, +}; + +int +route(struct ctx c) { + if (sodium_init() < 0) { + _report(fatal, "could not initialize libsodium"); + return 2; + } + + switch (c.cmdv[0]) { + case atom_key: { + switch (c.cmdv[1]) { + case atom_gen: { + if (c.cmdc != 2) { usage(c,NULL); return 1; } + _report(info, "generating new private key"); + if (c.flags.symcrypt == false) { + _say(warn, _t("not using symmetric encryption; key will be emitted in") _t("plain text")); + } else { + /* read passphrase from stdin/file */ + } + + /* libsodium is a bit weird. it uses different key types for + * encrypting and signing. we emit a signing (ed25519) key + * because it can be converted to an encryption key, but not + * vice-versa. libsodium also does not have any means to + * derive a public key from a private key, even though this + * is trivial, but instead stores the public key in the second + * half of the private key (despite emitting it AGAIN into the + * second parameter). so we generate a signing keypair, discard + * the returned public key, and save the "private key" (including + * its copy of the public key). this is extremely awkward and + * hopefully a better solution will be found in the fullness of + * time. */ + unsigned char skey[crypto_sign_SECRETKEYBYTES]; + { unsigned char _[crypto_sign_PUBLICKEYBYTES]; + crypto_sign_keypair(_,skey); } + (*write_privkey[c.flags.fmtv])(c, skey); + + } break; + case atom_pub: { + if (c.cmdc != 2) { usage(c,NULL); return 1; } + struct file* skey = read_file(c, c.flags.infd); + if (skey == NULL) return 6; + + free(skey); + } break; + case atom_wrap: { + if (c.cmdc != 2) { usage(c,NULL); return 1; } + struct file* key = read_file(c, c.flags.infd); + c.flags.ascii_armor = feature_on; + + if (key -> type == doc_type_priv) { + (*write_privkey[c.flags.fmtv])(c, key -> datastart); + } else if (key -> type == doc_type_pub) { + /* (*write_pubkey[c.flags.fmtv])(c, key); */ + } else { + _report(fatal, "file is not a " _self_name " key"); + free(key); return 8; + } + free(key); return 0; + } break; + + default: goto no_cmd; + } + } break; + case atom_wrap: { + if (c.cmdc != 1) { usage(c,NULL); return 1; } + + struct file* data = load_file(c, c.flags.infd); + if (data == NULL) return 6; + + (*write_armored[c.flags.fmtv])(c, data->raw, data->rawlen); + + free(data); return 0; + } break; + case atom_unwrap: { + if (c.cmdc != 1) { usage(c,NULL); return 1; } + struct file* data = read_file(c, c.flags.infd); + if (data == NULL) return 6; + if (isatty(c.flags.outfd)) _say(warn, _t("printing binary values to tty")); + write(c.flags.outfd, data->datastart, data->datlen); + free(data); return 0; + } break; + case atom_help: { + if (c.cmdc == 1) usage(c,NULL); + else { + const struct cmd_spec* tree = cmd_specs; + for (size_t i = 1; i < c.cmdc; ++i) { + if (c.cmdv[i] < _atom_n) { + if (tree[c.cmdv[i]].sub != NULL) { + tree = tree[c.cmdv[i]].sub; + } else { + _say(fatal, _t("no help available for subcommand") _t(reconstitute(c.cmdv[i]))); + return 3; + } + } else { + _say(fatal, _t("no help available;") _t(c.argv[c.cmdv[i] - _atom_n]) _t("is not a recognized command")); + return 4; + } + } + usage(c,tree); + } + return 0; + } break; + default: goto no_cmd; + } + return 0; + + no_cmd: _say(fatal, _t("no such command")); + usage(c,NULL); + return 1; +} + + +int +open_file(struct ctx c, const char* path, int flags, word* retfd) { + int fd = open(path,flags,0600); + if (fd == -1) { + switch (errno) { + case EACCES: _say(fatal, _t("file") _t(path) _t("could not be opened because you do not have permission to read it")); break; + case EFAULT: _say(fatal, _t("file") _t(path) _t("could not be opened because the path is outside your accessible address space")); break; + case EISDIR: _say(fatal, _t("file") _t(path) _t("could not be opened because it is a directory, not a regular file")); break; + case ELOOP: _say(fatal, _t("file") _t(path) _t("could not be opened because too many symbolic links were encountered")); break; + case EMFILE: _say(fatal, _t("file") _t(path) _t("could not be opened because the per-process file limit is too low")); break; + case ENOENT: _say(fatal, _t("file") _t(path) _t("could not be opened because it does not exist")); break; + case ENOMEM: _say(fatal, _t("file") _t(path) _t("could not be opened, either because not enough memory remains to make use of a FIFO or because the kernel is running out of memory (!)")); break; + case ENOTDIR: _say(fatal, _t("file") _t(path) _t("could not be opened because a file was specified in place of a directory in the path")); break; + case ENXIO: _say(fatal, _t("file") _t(path) _t("could not be opened because it is not a regular file or FIFO")); break; + case EPERM: _say(fatal, _t("file") _t(path) _t("could not be opened because it is sealed against access")); break; + case EROFS: _say(fatal, _t("file") _t(path) _t("could not be opened for writing because it is located on a read-only filesystem")); break; + default: _say(fatal, _t("file") _t(path) _t("could not be opened for unclear reasons - consult errno entry for code") _t((unsigned)errno)); break; + } + return errno; + } + + *retfd = fd; return 0; +} + +int +main(unsigned int argc, const char** argv) { + atom cmdlist[argc]; + const char* paramlist[argc]; + size_t paramcount = 0; + + struct ctx c = { + .argv = paramlist, + .cmdc = 0, + .cmdv = cmdlist, + .bin = (argc > 0 ? argv[0] : NULL), + .flags = { + .ascii_armor = feature_auto, + .color = feature_auto, + .noise = noise_fatal, + .infile = NULL, .infd = 0, + .outfile = NULL, .outfd = 1, + .pwfile = NULL, .pwfd = 0, + .pw_force_tty = false, + .sigfile = NULL, .sigfd = 0, + /* if sigfd == infd, extract sig from input stream */ + .fd = false, + .symcrypt = false, + .fmtv = 0, /* 0 = native version */ + }, + }; + + if (argc == 0) { + _report(fatal, "invoked incorrectly or your environment is not sane; the 0th value of the argument array should be the name of or path to the program you are attempting to execute"); + return -1; + } + + struct flag* paramstack[argc]; + size_t stackptr = 0; + enum { + reading_params, + reading_commands, + reading_flags + } reading_mode = reading_flags; + _say(debug, _t("parsing command line") _t(argv)); + for (size_t i = 1; i < argc; ++i) { + const char* const p = argv[i]; + + if (reading_mode >= reading_flags && p[0] == '-') { /* flag */ + bool longflag; + if (p[1] == '-') { + if (p[2] == 0) { + /* "--" has been passed; all further arguments must be + * interpreted as parameters or commands, not flags */ + reading_mode = reading_commands; + continue; + } else if (p[2] == '-' && p[3] == 0) { + /* "---" has been passed; all further arguments must be + * interpreted as parameters, not flags or commands */ + reading_mode = reading_params; + continue; + } else longflag = true; + } else longflag = false; + _say(debug, _t("parsing flag") _t(p)); + for (size_t o = 1; p[o] != 0; ++o) { + atom option = atom_none; + + if (longflag) option = atomize(p+2); + else { /* short flag */ + _say(debug, _t("parsing short flag") _t(p[o])); + for (size_t j = 0; j < sz(flags); ++ j) + if (flags[j].kind != flag_none && flags[j].shortname == p[o]) { + option = (atom)j; break; + } + } + + if (option == atom_none) { + if (!longflag && p[o] == '-') { + if (p[o+1] == '-' && p[o+2] == 0) { + /* shorthand for --- */ + reading_mode = reading_params; + continue; + } else if (p[o+1] == 0) { + /* shorthand for -- */ + reading_mode = reading_commands; + continue; + } + } + char ftext[3] = {'-',p[o],0}; + _say(fatal, _t("flag") _t(longflag ? p+2 : ftext) _t("not meaningful")); + goto parse_fail; + } + + handle_flag:; + struct flag* f = flags + option; + void* fld = ((unsigned char*)&c) + (unsigned char)(f -> offset); + if (f -> kind > _flag_param) { + if (f->kind>=flag_value) { + *((unsigned*)fld) = f->kind - flag_value; + } else paramstack[stackptr++] = f; + } else if (f -> kind > _flag_noparam) { + switch(f -> kind) { + case flag_on: *((bool*)fld) = true; break; + case flag_off: *((bool*)fld) = false; break; + case flag_switch: *((bool*)fld) = !*((bool*)fld); break; + /* in order to avoid creepy behavior in pathological cases, + * we enforce saturation arithmetic for the increment/decrement + * flag types */ + case flag_inc: { + unsigned* v = fld; + if (*v < UINT_MAX) ++*v; + } break; + case flag_dec: { + unsigned* v = fld; + if (*v > 0) --*v; + } break; + case flag_sdec: { + signed* v = fld; + if (*v > INT_MIN) --*v; + } break; + } + } + + if (p[1] == '-') break; + } + continue; + } + + if (stackptr > 0) { + struct flag* f = paramstack[--stackptr]; + _say(debug, _t("handling argument for flag") _t(reconstitute(f - flags))); + void* fld = ((unsigned char*)&c) + (unsigned char)(f -> offset); + switch(f -> kind) { + case flag_str: *((const char**)fld) = p; break; + case flag_uint: { + iaia_error_type e = int_of_str(10, p, (unsigned long long*)fld); + switch (e) { + case iaia_e_ok: continue; + case iaia_e_domain: + _say(fatal, _t("argument") _t(p) _t("outside domain for base")); + } + return 5; + } break; + case flag_atom: case flag_raw_atom: { + atom a = atomize(p); + if (a == atom_none || a < f -> opts.first || a > f -> opts.last) { + _say(fatal, _t("invalid argument") _t(p) _t("for flag") + _t(reconstitute(f - flags))); + goto parse_fail; + } + *((atom*)fld) = f->kind == flag_raw_atom ? a : a - f -> opts.first; + } break; + + } + continue; + } + + if (reading_mode >= reading_commands) { + if (p[0] == '=') { /* parameter even if atomizable */ + paramlist[paramcount] = p + 1; + cmdlist[c.cmdc] = _atom_n + paramcount; + ++c.cmdc; ++ paramcount; + continue; + } else { + atom a = atomize(p); + if (a != atom_none) { + cmdlist[c.cmdc] = a; + ++ c.cmdc; + continue; + } + } + } + + /* only parameters remain to be read */ + paramlist[paramcount] = p; + cmdlist[c.cmdc] = _atom_n + paramcount; + ++ c.cmdc; ++ paramcount; + } + + parse_done: _say(debug, _t("stored") _t(c.cmdc) _t("arguments and") _t(paramcount) _t("parameters")); + + if (c.flags.noise <= noise_debug) for (size_t i = 0; i= sz(write_privkey)) { + _say(fatal, _t("data format version") _t(c.flags.fmtv) _t("is not known. the highest available version is") _t(sz(write_privkey) - 1) _t("- are you sure you're using the latest release of " _self_name "?")); + return -2; + } + int err; + if (c.flags.pwfile != NULL) { + err = open_file(c,c.flags.pwfile, O_RDONLY, &c.flags.pwfd); + if(err) return err; + } + if (c.flags.infile != NULL) { + err = open_file(c,c.flags.infile, O_RDONLY, &c.flags.infd); + if(err) return err; + } + if (c.flags.outfile != NULL) { + err = open_file(c,c.flags.outfile, O_WRONLY | O_TRUNC | O_CREAT, &c.flags.outfd); + if(err) return err; + } + if (c.flags.sigfile != NULL) { + err = open_file(c,c.flags.sigfile, O_RDONLY, &c.flags.sigfd); + if(err) return err; + } + + + if (c.flags.color == feature_auto) + c.flags.color = isatty(2); + if (c.flags.ascii_armor == feature_auto) + c.flags.ascii_armor = isatty(c.flags.outfd); + + /* this is hateful but i can't figure out a better way */ + if (c.flags.pw_force_tty && !isatty(c.flags.pwfd)) { + _say(debug, _t("passphrases cannot be read from fd") _t(c.flags.pwfd) _t("as it is not a tty and -W was passed;") _t("/dev/tty") _t("will be used instead")); + } else if (c.flags.pwfd == c.flags.infd) { + _say(debug, _t("passphrases cannot be read from fd") _t(c.flags.pwfd) _t("as input data is already being read from that descriptor;") _t("/dev/tty") _t("will be used instead")); + } else if (c.flags.pwfd == c.flags.sigfd) { + _say(warn, _t("passphrases cannot be read from fd") _t(c.flags.pwfd) _t("as the signature is already being read from that descriptor;") _t("/dev/tty") _t("will be used instead")); + } else goto skip_pwfd_change; + + pwfd_change: { + err = open_file(c,"/dev/tty", O_WRONLY, &c.flags.pwfd); + if(err) return err; + } + + skip_pwfd_change: + + _say(debug, _t("input fd =") _t(c.flags.infd)); + _say(debug, _t("output fd =") _t(c.flags.outfd)); + _say(debug, _t("pw fd =") _t(c.flags.pwfd)); + + if (c.cmdc > 0) return route(c); + + parse_fail: if (c.flags.noise < noise_silent) usage(c,NULL); + return 1; +} Index: tenki/tenki.c ================================================================== --- tenki/tenki.c +++ tenki/tenki.c @@ -74,11 +74,11 @@ #endif #define http _http "lang=" ds_lang " HTTP/1.1\r\nHost: api.darksky.net\r\n\r\n" #define start "GET /forecast/" ds_apikey "/" #define min(x,y) (x>y?y:x) -#define len(x) (sizeof(x)/sizeof(*x)) +#define len(x) (sizeof(x)/sizeof(x)[0]) typedef enum bool { false, true } bool; SSL_CTX* sc; bool head(size_t* len, char* txt, char** body) { @@ -188,11 +188,17 @@ printf("/%.*s",*key,key+1); key += *key + 1; }; printf("\n"); } */ -void pplt(const char* str, const char* const end, const char** keys, const char** const keyend, jsv* values) { +void pplt +( const char* str, + const char* const end, + const char** keys, + const char** const keyend, + jsv* values +) { // initialize parser state size_t depth = 0; const char* fld = NULL; const char* path[32]; path[0] = NULL; @@ -367,13 +373,13 @@ float pcpint = values[1].f, temp = values[2].f, windspd = values[3].f; printf("%.1f° %%{F#ff9bbb}%.*s\n", - summary.sz, - summary.a, - temp); + temp, + (int /*throwing table emoji*/)summary.sz, + summary.a); } // dark sky's API allows us to make 1000 free requests a day. // let's try not to get too near that. // hours in day 24 - minutes in day 24 * 60 = 1440 // so once a minute is almost half again too many