Overview
Comment: | add soda |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA3-256: |
0d4aa1c43a9a2a03d2241d2f04c6f793 |
User & Date: | lexi on 2022-10-27 20:04:19 |
Other Links: | manifest | tags |
Context
2022-10-31
| ||
21:27 | add wgsync base check-in: 99704cfae0 user: lexi tags: trunk | |
2022-10-27
| ||
20:04 | add soda check-in: 0d4aa1c43a user: lexi tags: trunk | |
2022-04-28
| ||
22:52 | clean up rels mechanism, get parvan mostly working again check-in: bc37f02701 user: lexi tags: trunk | |
Changes
Modified clib/iaia.c from [1d0d9f385e] to [70661568bf].
27 27 /* this is an expression that will be evaluated to 28 28 * determine whether to compress ascii to a 7-bit 29 29 * representation or to store it in 8-bit space. 30 30 * 1 = 7-bit (compressed) 31 31 * 0 = 8-bit (uncompressed) */ 32 32 # define _IAIA_EXP_ASCFORM (0) 33 33 #endif 34 +#ifndef NULL 35 +# define NULL ((void*)0) 36 +#endif 34 37 35 38 enum /* constants */ { 36 39 base7bit = 128, 37 40 38 41 /* ascii address space */ 39 42 numspace = (0x39 - 0x30) + 1, /* 10 */ 40 43 alphaspace = (0x5a - 0x41) + 1, /* 26 */ ................................................................................ 52 55 iaia_e_overflow, 53 56 } iaia_error_type; 54 57 typedef unsigned long long iaia_word_type; 55 58 #endif 56 59 57 60 /* -- string to integer converters -- */ 58 61 59 -iaia_error_type _IAIA_FN_ASCTOI(const char* s, iaia_word_type* ret) { 62 +iaia_error_type 63 +_IAIA_FN_ASCTOI(const char* s, iaia_word_type* ret) { 60 64 iaia_word_type val = 0; 61 65 62 - for (;*s!=null;++s) { 66 + for (;*s!=0;++s) { 63 67 uint8_t v = *s; 64 68 if (v > base7bit) return iaia_e_domain; 65 69 66 70 if (_IAIA_EXP_ASCFORM) 67 71 val *= base7bit; 68 72 else val <<= 8; 69 73 70 74 val += v; 71 75 } 72 76 73 77 *ret = val; 74 - return ok; 78 + return iaia_e_ok; 75 79 } 76 80 77 -iaia_error_type _IAIA_FN_ATOI(iaia_word_type base, const char* s, iaia_word_type* ret) { 78 - /* s must be a null-terminated ASCII numeral string */ 81 +iaia_error_type 82 +_IAIA_FN_ATOI(iaia_word_type base, const char* s, iaia_word_type* ret) { 83 + /* s must be a NUL-terminated ASCII numeral string */ 79 84 if (base > maxbase) return iaia_e_base; 80 85 81 86 /* override the default base if it's a basèd literal */ 82 87 if (s[0] == '@' || base == 0) return _IAIA_FN_ASCTOI(s + (s[0]=='@'),ret); 83 88 else if (s[0] == '0' && s[1] == 'x') base = 16, s += 2; 84 89 else if (s[0] == '0' && s[1] == 'd') base = 10, s += 2; 85 90 else if (s[0] == '0' && s[1] == 'b') base = 2, s += 2; 86 91 else if (s[0] == '0' && s[1] == 't') base = 3, s += 2; 87 92 else if (s[0] == '0') base = 8, s += 1; 88 93 89 94 bool insens = (base <= imaxbase); 90 95 iaia_word_type val = 0; 91 96 92 - for (;*s!=null;++s) { 97 + for (;*s!=0;++s) { 93 98 uint8_t v = *s; 94 99 if(v >= 0x30 && v <= 0x39) v -= 0x30; else { 95 100 if(v >= 0x61 && v <= 0x7a) { 96 101 if (insens) v -= 0x20; else { 97 102 v = numspace + alphaspace + (v - 0x61); 98 103 goto checkval; 99 104 } ................................................................................ 112 117 } 113 118 114 119 115 120 /* -- integer to string converters -- */ 116 121 117 122 /* needed for efficiency's sake, but really sucky - 118 123 * this table needs to be kept in sync with the 119 - * itoa algorithm by hand. unfortunately, given C's 124 + * itoa algorithm manually. unfortunately, given C's 120 125 * abject lack of metaprogramming, we have to do this 121 126 * by hand. */ 122 127 const char iaia_ref_table[] = /* numerals[10] */ "0123456789" 123 128 /* bigalpha[26] */ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 124 129 /* smallalpha[26] */ "abcdefghijklmnopqrstuvwxyz"; 125 130 _Static_assert (sizeof iaia_ref_table - 1 == maxbase, "tables out of sync"); 126 131 127 -iaia_error_type _IAIA_FN_ITOASC(iaia_word_type val, const char* buf_start, char* buf_end, char** newbuf) { 132 +iaia_error_type 133 +_IAIA_FN_ITOASC(iaia_word_type val, const char* buf_start, char* buf_end, char** newbuf) { 128 134 char* ptr = buf_end; 129 135 130 136 *ptr-- = 0; 131 137 while(val > 0) { 132 138 if (ptr < buf_start) return iaia_e_overflow; 133 139 iaia_word_type rem = val % 128; 134 140 if (_IAIA_EXP_ASCFORM) 135 141 val /= base7bit; 136 142 else val >>= 8; 137 143 *ptr-- = (char)rem; 138 144 } 139 145 140 - if (newbuf != null) *newbuf = ptr + 1; 141 - return ok; 146 + if (newbuf != NULL) *newbuf = ptr + 1; 147 + return iaia_e_ok; 142 148 } 143 149 144 -iaia_error_type _IAIA_FN_ITOA(iaia_word_type base, iaia_word_type val, const char* buf_start, 150 +iaia_error_type 151 +_IAIA_FN_ITOA(iaia_word_type base, iaia_word_type val, const char* buf_start, 145 152 char* buf_end, char** newbuf, bool lowercase) { 146 153 147 154 char* ptr = buf_end; 148 155 149 156 if (base > maxbase) return iaia_e_base; 150 157 if (base == 0) return _IAIA_FN_ITOASC(val, buf_start, buf_end, newbuf); 151 158 ................................................................................ 158 165 char out = iaia_ref_table[rem]; 159 166 if (lowercase && base <= imaxbase) 160 167 if (out >= 'A' && out <= 'Z') 161 168 out += ('a' - 'A'); 162 169 *ptr-- = out; 163 170 } 164 171 165 - if (newbuf != null) *newbuf = ptr + 1; 172 + if (newbuf != NULL) *newbuf = ptr + 1; 166 173 return iaia_e_ok; 167 174 } 168 175
Modified makefile from [69ff088193] to [ca4a818574].
3 3 # pass debug=yes for debugging symbols, and to disable 4 4 # stripping and optimization of all binaries 5 5 6 6 # to build project contained in their own directory, run 7 7 # $ make (dir).proj 8 8 9 9 all: ctl conv xutil gen 10 +clean: 11 + rm xpriv safekill mkpw kpw.bin rosshil ord nkvd.so bgrd 12 +install: 13 + mkdir -p $(prefix)/bin $(prefix)/lib 14 + cp xpriv safekill mkpw rosshil ord bgrd ${out}/bin/ 15 + cp kpw.bin ${out}/bin/kpw 16 + cp nkvd.so $(prefix)/lib 10 17 11 18 xutil: xpriv safekill 12 19 # X11 tools 13 -gen: mkpw kpw rosshil 20 +gen: mkpw kpw.bin rosshil 14 21 # procedural generators 15 22 conv: ord 16 23 # converters 17 24 ctl: nkvd.so bgrd 18 25 # manipulating disobedient software 19 26 20 27 nkvd.so: nkvd.c
Modified makerules from [a224223f8c] to [e7dd2b0bcc].
14 14 cc-flags = $(if $(debug),-g,-O$(cc-opt)) 15 15 cc = $(CC) $(cc-flags) 16 16 17 17 post = $(if $(debug),, && strip $@) 18 18 cc-post = $(post) 19 19 sc-post = $(post) 20 20 mc-post = $(post) 21 + 22 +prefix = $(if ${out},${out},/usr) 21 23 22 24 os = $(shell uname -o) 23 25 24 26 %: %.c 25 27 $(cc) $< -o$@ $(cc-post) 26 28 27 29 %: %.ml
Modified ord.c from [9504e2c85c] to [4549cc99f3].
1 1 /* [ʞ] ord.c - integer converter 2 2 * ~ lexi hale <lexi@hale.su> 3 3 * © AGPLv3 4 4 * * ord has no dependencies except for libc. 5 5 * ? ord converts integers to ascii characters 6 6 * and back. written because the only fucking 7 7 * way to do this in shell is FUCKING PRINTF. 8 - * $ cc ord.c -o ord [-D_IO=(LIBC|POSIX)] 8 + * $ cc ord.c -o ord [-D_(POSIX|LIBC)_IO] 9 9 * - the flag D_IO will instruct ord.c whether 10 10 * to use POSIX io primitives (write and read) 11 11 * instead of libc primitives (printf). if 12 12 * you're on a UNIX system, POSIX primitives 13 13 * will be used by default, but you can block 14 14 * them with LIBC or force them with POSIX. 15 15 * if you are on a POSIX- compliant system, ................................................................................ 16 16 * you *should* use POSIX IO, for improved 17 17 * performance and safety. 18 18 19 19 TODO: take full advantage of write(2) by storing 20 20 output in single string & making single 21 21 write call */ 22 22 23 -#if (defined(__unix__) && _IO != LIBC) || (_IO == POSIX) 23 +#if (defined(__unix__) && !defined(_POSIX_IO)) && !defined(_LIBC_IO) 24 24 # define _POSIX_IO 25 25 #endif 26 26 27 27 #ifdef _POSIX_IO 28 28 # include <unistd.h> 29 29 # define say(x) (write(2, (x), (sizeof (x)))) 30 30 # define print(sz,x) (write(1, (x), (sz))) 31 31 # define forposix(x) x 32 32 # define forlibc(x) 33 33 #else 34 34 # include <stdio.h> 35 35 # define say(x) (fprintf(stderr, (x))) 36 -# define print(x) (printf("%s",(x))) 36 +# define print(sz,x) (printf("%s",(x))) 37 37 # define forposix(x) 38 38 # define forlibc(x) x 39 39 #endif 40 40 #include <stddef.h> 41 41 #include <stdint.h> 42 42 #include <string.h> 43 43 #include <limits.h>
Modified safekill.c from [59e57157c0] to [283d7b5c67].
176 176 177 177 usage: 178 178 #define info "\x1b[34m-- " 179 179 #define param "\x1b[32m" 180 180 #define eol "\x1b[m\n" 181 181 #define bold "\x1b[1m" 182 182 #define nl " " 183 - fprintf(stderr, bold "usage:\x1b[0m %s " " " info "kill active window if non-vital" eol 183 + fprintf(stderr, bold "usage:\x1b[0m %1$s " " " info "kill active window if non-vital" eol 184 184 nl "%1$s -v " param "[id] " info "make [id] or active window vital" eol 185 185 nl "%1$s -c " param "[id] " info "clear vital flag on [id] or active window" eol 186 186 nl "%1$s -q " param "[id] " info "'emergency' kill w/o reference to vital flag" eol, 187 187 argv[0]); 188 188 return 1; 189 189 }
Modified smake.c from [8f8960ef17] to [1bfb924710].
17 17 18 18 #include <stdio.h> 19 19 #include <stdlib.h> 20 20 #include <string.h> 21 21 #include <unistd.h> 22 22 #include <fcntl.h> 23 23 #include <sys/mman.h> 24 +#include <assert.h> 24 25 25 26 enum e { ok, badname, nofile }; 26 27 enum kind { css, sass, scss, bad }; 27 28 typedef enum { false, true } bool; 28 29 29 30 #define corebufsz 1024 30 31 char corebuf[corebufsz]; ................................................................................ 47 48 size_t rsz = bufptr-extbuf; 48 49 extbuf = realloc(extbuf, run); 49 50 bufptr = extbuf + rsz + sz; 50 51 return bufptr - sz; 51 52 } 52 53 } 53 54 } 54 - // this point should never be reached 55 + assert(false); /* this point should never be reached */ 55 56 mkptr: { 56 57 void* ret = bufptr; 57 58 bufptr += sz; 58 59 return ret; 59 60 } 60 61 } 61 62 ................................................................................ 123 124 *namecur = 0; 124 125 printf("found import to %s;\n", namebuf); 125 126 namecur = namebuf; 126 127 } 127 128 ++cur; goto read_start; 128 129 129 130 read_string: 130 - if (*cur == 0) goto read_done; //unterminated string!? 131 + if (*cur == 0) goto read_done; /* unterminated string!? */ 131 132 if (*cur++ == strqt) goto read_start; 132 133 goto read_string; 133 134 134 135 read_ml_comment: 135 - if (*cur == 0) goto read_done; //unterminated comment! 136 + if (*cur == 0) goto read_done; /* unterminated comment! */ 136 137 if (*cur == '*' && cur[1] == '/') { 137 138 cur += 2; 138 139 goto read_start; 139 140 } 140 141 ++cur; goto read_ml_comment; 141 142 142 143 skip_line:
Added soda.c version [0502e2ccfd].
1 +/* [ʞ] soda - libsodium front end 2 + * ~ lexi hale <lexi@hale.su> 3 + * © AGPLv3 4 + * @ vim: ft=c 5 + */ 6 + 7 +#define _POSIX_C_SOURCE 200809L 8 +#include <unistd.h> 9 +#include <stdio.h> 10 +#include <errno.h> /* boo hiss */ 11 +#include <stdint.h> 12 +#include <limits.h> 13 +#include <stdbool.h> 14 +#include <string.h> 15 +#include <sodium.h> 16 +#include <fcntl.h> 17 +#define sz(x) (sizeof(x)/sizeof((x)[0])) 18 + 19 +#define _IAIA_FN_ITOA str_of_int 20 +#define _IAIA_FN_ATOI int_of_str 21 +#include "clib/iaia.c" 22 + 23 +#ifdef _WIN32 24 +# define open _open 25 +# define close _close 26 +# define read _read 27 +# define write _write 28 +#endif 29 + 30 +#define _self_name "soda" 31 + 32 +typedef unsigned long long word; 33 + 34 +typedef enum atom { 35 + /* commands */ 36 + atom_none, 37 + atom_key, 38 + atom_gen, 39 + atom_pub, 40 + atom_psk, 41 + atom_pair, 42 + atom_cert, 43 + atom_sign, 44 + atom_verify, 45 + atom_enc, 46 + atom_dec, 47 + atom_wrap, 48 + atom_unwrap, 49 + atom_help, 50 + _atom_cmd_n, 51 + 52 + /* flags */ 53 + atom_verbose, 54 + atom_quiet, 55 + atom_noise, 56 + atom_ascii_armor, 57 + atom_color_on, 58 + atom_color_off, 59 + atom_out, atom_outfd, 60 + atom_in, atom_infd, 61 + atom_pw, atom_pwfd, atom_pwtty, 62 + atom_sig, atom_sigfd, 63 + atom_symmetric, 64 + atom_raw, 65 + atom_fd, 66 + atom_fmtv, 67 + _atom_flag_n, 68 + 69 + /* debug values */ 70 + atom_debug, 71 + atom_info, 72 + atom_warn, 73 + atom_fatal, 74 + atom_silent, 75 + 76 + _atom_n, 77 + atom_capture = _atom_n 78 +} atom; 79 + 80 +enum noise { 81 + noise_debug, 82 + noise_info, 83 + noise_warn, 84 + noise_fatal, 85 + noise_silent, 86 + 87 + _noise_n 88 +}; 89 +struct { const char* label; unsigned char color; } 90 +noise_level_text[] = { 91 + [noise_debug] = {"(debug",5}, 92 + [noise_info ] = {" (info", 4}, 93 + [noise_warn ] = {" (warn", 3}, 94 + [noise_fatal] = {"(fatal",1}, 95 +}; 96 + 97 +struct cmd_spec { const char* desc; const char** params; struct cmd_spec* sub; } 98 +cmd_specs[_atom_cmd_n] = { {NULL,NULL,NULL}, 99 + [atom_key] = { "create and manipulate keypairs for public-key crypto", NULL, (struct cmd_spec[_atom_cmd_n]){ 100 + [atom_gen] = { "generate new keypair", NULL, NULL }, 101 + [atom_pub] = { "extract the public key portion from a keypair", NULL, NULL}, 102 + [atom_wrap] = { "convert a binary key to its ascii-armor representation", NULL, NULL}, 103 + }}, 104 + [atom_psk] = { "create and manipulate keys for secret-key crypto", NULL, (struct cmd_spec[_atom_cmd_n]){ 105 + [atom_gen] = { "generate new PSK", NULL, NULL }, 106 + }}, 107 + [atom_help] = { "display usage listings for commands", 108 + (const char*[]){"commands…",NULL}, NULL }, 109 + [atom_sign] = { "sign data using public-key crypto", 110 + (const char*[]){"keyfile",NULL}, NULL }, 111 + [atom_verify] = { "verify signature on data", 112 + (const char*[]){"pubfile",NULL}, NULL }, 113 + [atom_enc] = { "encrypt data", NULL, (struct cmd_spec[_atom_cmd_n]){ 114 + [atom_pub] = { "encrypt against public key", (const char*[]){ 115 + "pubfile", NULL 116 + }, NULL}, 117 + [atom_psk] = { "encrypt against symmetric, pre-shared key", (const char*[]){ 118 + "pskfile", NULL 119 + }, NULL}, 120 + }}, 121 + [atom_wrap] = {"wrap binary input with ascii armor so it can survive transmission mechanisms that are not 8-bit clean", NULL,NULL}, 122 + [atom_unwrap] = {"translate ascii-armored input back to the original binary", NULL,NULL}, 123 +}; 124 + 125 +enum say { say_stop, say_string, say_string_list, say_char, say_uint, say_sint, say_hex }; 126 +struct sayp { 127 + enum say kind; union { 128 + const char* str; 129 + const char** strlist; 130 + char ch; 131 + long long unsigned int uint; 132 + long long signed int sint; 133 + }; 134 +}; 135 + 136 +enum feature { 137 + feature_off = 0, 138 + feature_on = 1, 139 + feature_auto = 2, 140 +}; 141 + 142 +struct ctx { 143 + size_t argc; const char** argv; 144 + size_t cmdc; atom* cmdv; 145 + const char* bin; 146 + struct { 147 + enum noise noise; 148 + enum feature 149 + ascii_armor, 150 + color; 151 + bool fd; 152 + bool pw_force_tty; 153 + const char* infile,* outfile,* pwfile,* sigfile; 154 + word infd, outfd, pwfd, sigfd; 155 + bool symcrypt; 156 + word fmtv; 157 + } flags; 158 +}; 159 + 160 +bool 161 +isws(const char c) { 162 + /* TODO: maybe ignore all characters outside the ascii armory? */ 163 + return (c == ' ' || c == '\t' || c == '\n'); 164 +} 165 + 166 +enum { _buffer_max = 512 }; 167 +struct buffer { 168 + int fd; /* where to flush */ 169 + size_t sz; 170 + char bytes[_buffer_max]; 171 +}; 172 +void buffer_flush(struct buffer* b) { write(b->fd, b->bytes, b->sz); b->sz = 0; } 173 +#define _buffer_write(s,b) buffer_write(s, b, sizeof(b)) 174 +void buffer_write(struct buffer* b, const char* str, size_t sz) { 175 + if (sz == 0) { 176 + size_t i; 177 + for (i = 0; str[i] != 0; ++i) { 178 + const size_t ofs = (b->sz + i) % _buffer_max; 179 + b->bytes[ofs] = str[i]; 180 + if (ofs == 0 && i != 0) buffer_flush(b); 181 + } 182 + b -> sz = (b->sz + i) % _buffer_max; 183 + } else if (sz > _buffer_max) { 184 + buffer_flush(b); 185 + write(b->fd, str, sz); 186 + b->sz = sz; 187 + } else if (_buffer_max - b->sz < sz) { 188 + buffer_flush(b); 189 + memcpy(b->bytes, str, sz); 190 + b->sz = sz; 191 + } else { 192 + memcpy(b->bytes + b->sz, str, sz); 193 + b->sz += sz; 194 + } 195 +} 196 +void buffer_push(struct buffer* b, const char c) { buffer_write(b, &c, 1); } 197 + 198 +void say(struct ctx c, enum noise kind, struct sayp* s) { 199 + struct buffer o = {2,0}; 200 + if (c.flags.noise > kind) return; 201 + if (noise_level_text -> label != NULL) 202 + if (c.flags.color) { 203 + buffer_write(&o,"\x1b[1;3",0); 204 + buffer_push(&o, 0x30 + noise_level_text[kind].color); 205 + _buffer_write(&o,"m"); 206 + buffer_write(&o, noise_level_text[kind].label, 0); 207 + _buffer_write(&o,")\x1b[m"); 208 + } else { 209 + buffer_write(&o, noise_level_text[kind].label, 0); 210 + buffer_push(&o,')'); 211 + } 212 + 213 + bool bright = false; 214 + while(s -> kind != say_stop) { 215 + if (c.flags.color) { 216 + if (bright) _buffer_write(&o," \x1b[1m"); 217 + else _buffer_write(&o," \x1b[m"); 218 + } else { 219 + buffer_push(&o,' '); 220 + } 221 + char intbuf[128]; 222 + char* const intbuf_end = intbuf + sizeof intbuf; 223 + char* start; 224 + 225 + explain: switch(s -> kind) { 226 + case say_string: buffer_write(&o, s -> str, 0); break; 227 + case say_char: buffer_push(&o, s -> ch); break; 228 + case say_uint: 229 + str_of_int(10, s -> uint, intbuf, intbuf_end - 1, &start, true); 230 + buffer_write(&o, start, intbuf_end - start); 231 + break; 232 + case say_sint: 233 + /* TODO oops! iaia doesn't have signed int support yet 🙃 */ 234 + /* str_of_int(10, s -> uint, intbuf, intbuf + sizeof intbuf, &end, true) */ 235 + /* buffer_write(&o, intbuf, end - intbuf); */ 236 + break; 237 + case say_hex: 238 + str_of_int(16, s -> uint, intbuf, intbuf_end - 1, &start, true); 239 + _buffer_write(&o, "0x"); 240 + buffer_write(&o, start, intbuf_end - start); 241 + break; 242 + case say_string_list: 243 + buffer_push(&o,'['); 244 + for(size_t i = 0; s -> strlist[i] != NULL; ++i) { 245 + if (i != 0) dprintf(2,", "); 246 + _buffer_write(&o,"“"); 247 + buffer_write(&o,s -> strlist[i],0); 248 + _buffer_write(&o,"”"); 249 + } 250 + buffer_push(&o,']'); 251 + break; 252 + } 253 + 254 + ++s; bright = !bright; 255 + } 256 + 257 + if (c.flags.color) _buffer_write(&o, "\x1b[m\n"); 258 + else buffer_push(&o, '\n'); 259 + 260 + buffer_flush(&o); 261 +} 262 + 263 +enum { _atom_max_str_len = 12 }; 264 +struct { atom val; const char* str;} 265 +/* first name listed for an atom is treated as the canonical name 266 + * by reconstitute() and displayed in usage listings */ 267 +atom_strings[] = { 268 + /* commands */ 269 + {atom_key, "key"}, {atom_key, "privkey"}, {atom_key, "priv"}, 270 + {atom_pub, "pub"}, {atom_pub, "pubkey"}, 271 + {atom_psk, "psk"}, 272 + {atom_cert, "cert"}, {atom_cert, "certificate"}, 273 + {atom_sign, "sign"}, 274 + {atom_verify, "verify"}, {atom_verify, "vfy"}, {atom_verify, "v"}, 275 + {atom_enc, "enc"}, {atom_enc, "encipher"}, {atom_enc, "encrypt"}, 276 + {atom_dec, "dec"}, {atom_dec, "decipher"}, {atom_dec, "decrypt"}, 277 + {atom_wrap, "wrap"}, {atom_wrap, "armor"}, 278 + {atom_unwrap, "unwrap"}, {atom_unwrap, "dearmor"}, {atom_unwrap, "disarmor"}, 279 + {atom_gen, "gen"}, {atom_gen, "generate"}, {atom_gen, "create"}, {atom_gen, "new"}, 280 + {atom_help, "help"}, 281 + 282 + /* flags */ 283 + {atom_quiet, "quiet"}, 284 + {atom_ascii_armor, "ascii-armor"}, 285 + {atom_raw, "raw"}, 286 + {atom_sig, "sig"}, {atom_sig, "sigfile"}, 287 + {atom_sigfd, "sig-fd"}, {atom_sig, "sigfd"}, 288 + {atom_verbose, "verbose"}, 289 + {atom_noise, "noise"}, 290 + {atom_color_on, "color"}, 291 + {atom_color_off, "no-color"}, 292 + {atom_out, "out"}, {atom_out, "output"}, {atom_out, "outfile"}, 293 + {atom_outfd, "out-fd"}, {atom_outfd, "outfd"}, 294 + {atom_in, "in"}, {atom_out, "input"}, {atom_out, "infile"}, 295 + {atom_infd, "in-fd"}, {atom_infd, "infd"}, 296 + {atom_pw, "pw"}, {atom_pw, "passphrase"}, {atom_pw, "password"}, 297 + {atom_pwfd, "pw-fd"}, {atom_pwfd, "pwfd"}, 298 + {atom_pwtty, "pw-tty"}, {atom_pwtty, "pwtty"}, 299 + {atom_fd, "fd"}, 300 + {atom_symmetric, "symmetric"}, {atom_symmetric, "sym"}, {atom_symmetric, "symcrypt"}, 301 + {atom_fmtv, "format"}, {atom_fmtv, "fmt"}, {atom_fmtv, "format-version"}, 302 + 303 + /* debug levels */ 304 + {atom_silent, "silent"}, 305 + {atom_debug, "debug"}, {atom_debug, "dbg"}, 306 + {atom_info, "info"}, 307 + {atom_warn, "warn"}, {atom_warn, "warnings"}, 308 + {atom_fatal, "fatal"}, {atom_fatal, "error"}, 309 +}; 310 + 311 +atom 312 +atomize(const char* str) { 313 + for(size_t i = 0; i < sz(atom_strings); ++i) { 314 + if (strncmp(atom_strings[i].str, str, _atom_max_str_len) == 0) { 315 + return atom_strings[i].val; 316 + } 317 + } 318 + return atom_none; 319 +} 320 + 321 +const char* 322 +reconstitute(atom a) { 323 + for(size_t i = 0; i < sz(atom_strings); ++i) { 324 + if (atom_strings[i].val == a) return atom_strings[i].str; 325 + } 326 + return NULL; 327 +} 328 + 329 +struct flag { 330 + enum flag_kind { 331 + flag_none, 332 + _flag_noparam, 333 + flag_on, flag_off, flag_switch, 334 + flag_inc, flag_dec, flag_sdec, 335 + _flag_param, 336 + flag_atom, flag_str, 337 + flag_uint, flag_sint, 338 + flag_raw_atom, 339 + flag_value 340 + } kind; 341 + char shortname; 342 + const char* desc; 343 + struct { atom first; atom last; } opts; 344 + size_t offset; 345 +} 346 + 347 +#define _declflag_raw(a,k,s,o,rs,re,d) [atom_##a] = { \ 348 + .kind = flag_##k, \ 349 + .shortname = s, \ 350 + .desc = d, \ 351 + .opts = { rs,re }, \ 352 + .offset = (size_t)&(((struct ctx*)0) -> flags.o) \ 353 +}, 354 +#define _declflag(a,k,s,o,d) _declflag_raw(a,k,s,o,0,0,d) 355 +#define _declflaga(a,rs,re,s,o,d) _declflag_raw(a,atom,s,o,rs,re,d) 356 + 357 +flags[_atom_n] = { 358 + {flag_none}, 359 + _declflag(ascii_armor, value + feature_on, 'a', ascii_armor, 360 + "emit ascii-encoded data instead of raw binary " 361 + "data (default if stdout is a TTY)") 362 + _declflag(raw, value + feature_off, 'r', ascii_armor, 363 + "emit binary data instead of raw ascii-armored " 364 + "data (default if stdout is not a TTY)") 365 + _declflag(in, str, 'i', infile, 366 + "read data from a file instead of stdin") 367 + _declflag(infd, uint, 'I', infd, 368 + "read data from a file instead of stdin") 369 + _declflag(out, str, 'o', outfile, 370 + "write data to a file instead of stdout") 371 + _declflag(outfd, uint, 'O', outfd, 372 + "write data to a file instead of stdout") 373 + _declflag(pw, str, 'p', pwfile, 374 + "load passphrase from file instead of stdin") 375 + _declflag(pwfd, uint, 'P', pwfd, 376 + "load passphrase from file instead of stdin") 377 + _declflag(pwtty, switch, 'W', pw_force_tty, 378 + "always read password from the input TTY unless a file is specified instead") 379 + _declflag(sig, str, 'g', sigfile, 380 + "read signature from specified file instead of extracting it from input data") 381 + _declflag(sigfd, uint, 'G', sigfd, 382 + "read signature from specified file descriptor instead of extracting it from input data") 383 + _declflag(symmetric, switch, 's', symcrypt, 384 + "secure sensitive key material with symmetric cryptography") 385 + _declflag(fd, switch, 'f', fd, 386 + "interpret filename arguments to commands as file descriptor numbers") 387 + _declflag(color_on, value + feature_on, 'c', color, 388 + "enable colorized output (default if stderr is a TTY)") 389 + _declflag(color_off, value + feature_off, 'C', color, 390 + "disable colorized output") 391 + _declflag(verbose, dec, 'v', noise, 392 + "increase noise level") 393 + _declflag(quiet, value + noise_silent, 'q', noise, 394 + "emit nothing to stderr") 395 + _declflaga(noise, atom_debug, atom_silent, 'n', noise, 396 + "set noise level") 397 + _declflag(fmtv, uint, 'F', fmtv, 398 + "use a specific data file format version instead of the native version (0)") 399 +}; 400 + 401 +const char** flag_enum_desc[_atom_n] = { 402 + [atom_noise] = (const char*[_noise_n]){ 403 + [noise_silent] = "run in complete silence", 404 + [noise_debug] = "emit all notices, including those useful only for debugging", 405 + [noise_info] = "report on the progress of the operation", 406 + [noise_warn] = "emit non-fatal warnings", 407 + [noise_fatal] = "only emit fatal errors", 408 + }, 409 +}; 410 + 411 +#undef _declflag 412 +#undef _declflaga 413 +#undef _declflag_raw 414 + 415 +/* the casts here are a bit wacky. this is unfortunately necessary as C 416 + * insists on evaluating each branch of the generic expression regardless 417 + * of which type is picked. why anyone thought this was a good idea is 418 + * beyond me. */ 419 +#define _t(x) (_Generic((x), \ 420 + const char**: (struct sayp){say_string_list, .strlist = (const char**)(size_t)(x)}, \ 421 + char: (struct sayp){say_char, .ch = (char)(size_t)(x)}, \ 422 + char*: (struct sayp){say_string, .str = (const char*)(size_t)(x)}, \ 423 + const char*: (struct sayp){say_string, .str = (const char*)(size_t)(x)}, \ 424 + int: (struct sayp){say_sint, .sint = (long long)(x)}, \ 425 + short: (struct sayp){say_sint, .sint = (long long)(x)}, \ 426 + long: (struct sayp){say_sint, .sint = (long long)(x)}, \ 427 + long long: (struct sayp){say_sint, .sint = (long long)(x)}, \ 428 + unsigned: (struct sayp){say_uint, .uint = (long long unsigned)(x)}, \ 429 + unsigned short: (struct sayp){say_uint, .uint = (long long unsigned)(x)}, \ 430 + long unsigned: (struct sayp){say_uint, .uint = (long long unsigned)(x)}, \ 431 + long long unsigned: (struct sayp){say_uint, .uint = (long long unsigned)(x)}, \ 432 + default: (struct sayp){say_hex, .uint = (size_t)(x)})), 433 +#define _say(k,...) say(c, noise_##k, ((struct sayp[]){__VA_ARGS__ {say_stop}})) 434 +#define _report(k,s) _say(k, _t(s)) 435 + 436 +void 437 +usage(struct ctx c, const struct cmd_spec* tree) { 438 + const char* bin = c.bin; 439 + const char* fmt_flag,* fmt_usage,* fmt_enum,* head_start,* head_fin; 440 + if (c.flags.color) { 441 + fmt_usage = "\x1b[1musage:\x1b[m %s \x1b[32m[flags]\x1b[m%s \x1b[33mcommand\x1b[m\n"; 442 + fmt_flag = " \x1b[1;36m%c%c \x1b[;95m--%s\x1b[m: %s\n"; 443 + fmt_enum = " \x1b[94m%s\x1b[m: %s\n"; 444 + head_start = "\x1b[1m"; head_fin = "\x1b[m"; 445 + } else { 446 + fmt_usage = "usage: %s [flags]%s <command>\n"; 447 + fmt_flag = " %c%c --%s: %s\n"; 448 + fmt_enum = " %s: %s\n"; 449 + head_start = head_fin = ""; 450 + } 451 + 452 + char ccmd[32]; char *cptr = ccmd; *cptr = 0; 453 + if (tree == NULL) tree = cmd_specs; 454 + else if(c.cmdc > 1) for (size_t i = 1; i<c.cmdc; ++i) { 455 + *(cptr++)=' '; 456 + cptr=stpcpy(cptr, reconstitute(c.cmdv[i])); 457 + } 458 + 459 + 460 + dprintf(2,fmt_usage,bin,ccmd); 461 + 462 + dprintf(2,"%scommands:%s\n",head_start,head_fin); 463 + for (size_t i = 0; i<_atom_cmd_n; ++i) { 464 + if (tree[i].desc == NULL) continue; 465 + 466 + dprintf(2, " %s", reconstitute(i)); 467 + if (tree[i].params != NULL) { 468 + for (size_t j = 0; tree[i].params[j] != NULL; ++j) { 469 + dprintf(2," <%s>", tree[i].params[j]); 470 + } 471 + } 472 + dprintf(2,"%s: %s\n", 473 + tree[i].sub != NULL ? " …" : "", 474 + tree[i].desc); 475 + } 476 + 477 + dprintf(2,"%sflags:%s\n",head_start,head_fin); 478 + for (size_t i = _atom_cmd_n + 1; i<_atom_flag_n; ++i) { 479 + /* if (flags[i].kind == flag_none) continue; */ 480 + dprintf(2,fmt_flag, 481 + flags[i].shortname != 0 ? '-' : ' ', 482 + flags[i].shortname != 0 ? flags[i].shortname : ' ', 483 + reconstitute(i), flags[i].desc); 484 + if (flags[i].kind == flag_atom || flags[i].kind == flag_raw_atom) { 485 + for (atom a = flags[i].opts.first; a < flags[i].opts.last + 1; ++a) { 486 + dprintf(2,fmt_enum,reconstitute(a),flag_enum_desc[i][a - flags[i].opts.first]); 487 + } 488 + } 489 + } 490 +} 491 + 492 +bool 493 +atom_match(struct ctx c, size_t* match) { 494 + /* primitive pattern-matching function */ 495 + for (size_t i = 0; i < c.cmdc; ++i) { 496 + if (match[i] == 0) return false; 497 + 498 + if (match[i] < atom_capture) { 499 + if (match[i] != c.cmdv[i]) return false; 500 + } else if (c.cmdv[i] >= _atom_n) { 501 + const char** dest = (const char**)(match[i] - atom_capture); 502 + *dest = c.argv[c.cmdv[i] - _atom_n]; 503 + } else { 504 + return false; 505 + } 506 + } 507 + return match[c.cmdc] == 0; 508 +} 509 + 510 +/* the ascii armory is intentionally limited to characters that are 511 + * not only printable but that are unlikely to have special meaning 512 + * such that they would require escaping for any form of input. 513 + * while this is a more limited set than base64, it's significantly 514 + * stronger armor -- a user can, for instance, simply paste it into 515 + * most shells without quoting. */ 516 +const char ascii_armory[] = "0123456789/abcdefghijklmopqrstuvwxyz." 517 + "-ABCDEFGHIJKLMOPQRSTUVWXYZ_"; 518 +const char ascii_value[] = 519 + "\x25\x24\x0a\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\00\00\00" 520 + "\00\00\00\00\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30" 521 + "\x31\x32\00\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e" 522 + "\00\00\00\00\x3f\00\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13" 523 + "\x14\x15\x16\x17\00\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21" 524 + "\x22"; 525 + /* generated by a script. do not touch. */ 526 + 527 +enum { armor_split_every = 48 }; 528 +char* 529 +armor(const unsigned char* src, char* dest, size_t sz, size_t brstart, bool format) { 530 + char* bufptr = dest; 531 + unsigned char carry = 0; 532 + for(size_t i = 0; i<sz; ++i) { 533 + /* insert line breaks every so many chars */ 534 + *(bufptr++)=ascii_armory[src[i] % 64]; 535 + if (format) if ((brstart + (bufptr - dest)) % armor_split_every == 0) 536 + *(bufptr++)='\n', *(bufptr++)=' '; 537 + 538 + carry<<=2; 539 + carry |= (src[i] / 64); 540 + if (i && (i%3 == 0)) { /* :( */ 541 + *(bufptr++)=ascii_armory[carry]; 542 + carry = 0; 543 + } 544 + if (format) if ((brstart + (bufptr - dest)) % armor_split_every == 0) 545 + *(bufptr++)='\n', *(bufptr++)=' '; 546 + } 547 + /* if(carry != 0) */ *(bufptr++)=ascii_armory[carry]; 548 + return bufptr; 549 +} 550 + 551 +char* disarmor(const unsigned char* src, char* dest, size_t sz) { 552 + /* transforms ascii armor into binary. can transform in place. */ 553 + for(size_t i = 0; i<sz; i += 3) { 554 + while (isws(src[i])) ++i; 555 + const char* s = src + i; 556 + unsigned char carry = ascii_value[s[2] - '-'], 557 + b1 = ascii_value[s[0] - '-'] + (64 * ((carry & 12) >> 2)), 558 + b2 = ascii_value[s[1] - '-'] + (64 * ( carry & 3)); 559 + *(dest++)=b1; *(dest++)=b2; 560 + } 561 + return dest; 562 +} 563 + 564 +struct pstr { 565 + size_t len; 566 + const char* str; 567 +}; 568 +#define _p(x) {sizeof(x)-1,(x)} 569 +struct encoding { struct pstr bin; struct pstr asc; const char* desc;}; 570 +enum doc { 571 + doc_none, 572 + doc_header, 573 + doc_footer, 574 + 575 + _doc_versions, 576 + doc_version_1, 577 + doc_version_2, 578 + doc_version_3, 579 + 580 + _doc_types, 581 + doc_type_priv /* private key */, 582 + doc_type_pub /* public key */, 583 + doc_type_psk /* shared secret key */, 584 + doc_type_asc /* arbitrary ascii-armored data */, 585 + doc_type_sig /* separate signature */, 586 + doc_type_signed /* document prefixed with signature */, 587 + doc_type_master /* master key used to derive keys deterministically */, 588 + 589 + _doc_n 590 +}; 591 +const struct encoding docbytes[_doc_n] = { {0,NULL}, 592 + [doc_header] = {_p("\x26\xe9\x11\x1a"), _p("[" _self_name)}, 593 + [doc_footer] = {_p(""), _p("]")}, 594 + [doc_version_1] = {_p("\01"), _p("1-")}, 595 + [doc_version_2] = {_p("\02"), _p("2-")}, 596 + [doc_version_3] = {_p("\03"), _p("3-")}, 597 + [doc_type_priv] = {_p("\01"), _p("priv-"),"private key material"}, 598 + [doc_type_pub] = {_p("\02"), _p("pub-"), "public key material"}, 599 + [doc_type_psk] = {_p("\03"), _p("psk-"), "shared key material"}, 600 + [doc_type_asc] = {_p("\04"), _p("asc-"), "ascii-armored binary data"}, 601 + [doc_type_sig] = {_p("\05"), _p("sig-"), "separate signature"}, 602 + [doc_type_signed] = {_p("\06"), _p("signed-"), "data with signature"}, 603 + [doc_type_master] = {_p("\07"), _p("master-"), "master key"}, 604 +}; 605 + 606 + 607 +char* 608 +compare_wsi(const char* s, char* d, size_t sz) { 609 + for (size_t i=0; i<sz; ++i) { 610 + while(isws(*s)) ++s; 611 + while(isws(*d)) ++d; 612 + if (*s != *d) return NULL; 613 + ++s; ++d; 614 + } 615 + return d; 616 +} 617 + 618 +bool 619 +file_cmp(enum doc fld, char** s, bool binary) { 620 + /* compares against a field and increments the callee's 621 + * pointer appropriately if a match is found */ 622 + if (binary) { 623 + if (memcmp(*s, docbytes[fld].bin.str, docbytes[fld].bin.len) == 0) { 624 + *s += docbytes[fld].bin.len; 625 + return true; 626 + } else return false; 627 + } else { 628 + char* p = compare_wsi(docbytes[fld].asc.str, *s, docbytes[fld].asc.len); 629 + if (p != NULL) { 630 + *s = p; 631 + return true; 632 + } else return false; 633 + } 634 +} 635 + 636 +struct file { 637 + enum doc version, type; 638 + union { 639 + struct file_signed { 640 + char* sig, *text; 641 + } signtext; 642 + struct file_key { 643 + char* priv, *pub; 644 + } key; 645 + char* pub; 646 + } data; 647 + char* datastart; 648 + size_t rawlen, datlen; 649 + char raw []; 650 +}* load_file(struct ctx c, const int fd) { 651 + _say(debug, _t("reading in file from fd") _t(fd)); 652 + size_t run = 512; size_t len = 0; 653 + struct file* buf = malloc(run + sizeof(struct file)); /* :( */ 654 + int e; while(e = read(fd, buf->raw + len, run - len)) { 655 + if (e == -1) { 656 + switch(errno) { 657 + case EBADF: _say(fatal, _t("file descriptor") _t((unsigned)fd) _t("does not reference an open, readable file")); break; 658 + case EFAULT: _say(fatal, _t("file descriptor") _t((unsigned)fd) _t("references a file outside your accessible address space")); break; 659 + case EIO: _say(fatal, _t("file descriptor") _t((unsigned)fd) _t("could not be read due to an I/O error")); break; 660 + case EISDIR: _say(fatal, _t("file descriptor") _t((unsigned)fd) _t("is a directory, not a readable file")); break; 661 + 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; 662 + } 663 + goto err; 664 + } 665 + 666 + len += e; 667 + if (len == run) { 668 + run *= 2; buf = realloc(buf, run + sizeof(struct file)); 669 + } else if (len < run) continue; 670 + } 671 + 672 + buf -> rawlen = len; 673 + buf -> type = doc_none; 674 + return buf; 675 + 676 + err: free(buf); 677 + return NULL; 678 +} 679 + 680 +struct file* read_file(struct ctx c, const int fd) { 681 + struct file* buf = load_file(c, fd); 682 + bool binary; size_t len = buf -> rawlen; 683 + char* cur = buf -> raw; 684 + 685 + if (file_cmp(doc_header,&cur,true)) { 686 + _report(debug, "parsing binary data"); 687 + binary = true; 688 + } else if (file_cmp(doc_header,&cur,false)) { 689 + _report(debug, "parsing ascii-armored data"); 690 + binary = false; 691 + } else { 692 + _say(fatal, _t("cannot parse data from fd") _t((unsigned)fd)); 693 + goto err; 694 + } 695 + 696 + /* write(2, cur, run - (cur - buf->raw)); */ 697 + 698 + for (enum doc i = _doc_versions + 1; i < _doc_types; ++ i) { 699 + if (file_cmp(i, &cur, binary)) { 700 + _say(debug, _t("file is format version") _t((unsigned) i - _doc_versions)); 701 + buf -> version = i; goto found_version; 702 + } 703 + } /* else */ { 704 + _say(fatal, _t("unrecognized file format version; are you using the most recent release of " _self_name "?")); 705 + goto err; 706 + } 707 + found_version:; 708 + 709 + for (enum doc i = _doc_types + 1; i < _doc_n; ++ i) { 710 + if (file_cmp(i, &cur, binary)) { 711 + _say(debug, _t("file contains") _t(docbytes[i].desc)); 712 + buf -> type = i; goto found_type; 713 + } 714 + } /* else */ { 715 + _say(fatal, _t("unrecognized file type; are you using the most recent release of " _self_name "?")); 716 + goto err; 717 + } 718 + found_type: if (!binary) { 719 + size_t s = (len - (cur - buf->raw)) - docbytes[doc_footer].asc.len; 720 + _say(debug, _t("disarmoring") _t(s) _t("bytes of encoded text")); 721 + char* end = disarmor(cur, buf -> raw, s); 722 + buf -> datlen = end - cur; 723 + } else { 724 + buf -> datlen = (len - (cur - buf->raw)) - docbytes[doc_footer].bin.len; 725 + } 726 + 727 + buf -> datastart = cur; 728 + return buf; 729 + 730 + err: free(buf); 731 + return NULL; 732 +} 733 + 734 +void 735 +write_armored_v1(struct ctx c, const unsigned char* data, size_t sz) { 736 + struct buffer o = {c.flags.outfd,0}; 737 +# define _wra_fld(f) buffer_write(&o, docbytes[doc_##f].asc.str,\ 738 + docbytes[doc_##f].asc.len); 739 + _wra_fld(header); 740 + _wra_fld(version_1); 741 + _wra_fld(type_asc); 742 + 743 + char buf[sz*2]; 744 + char* end = armor(data,buf,sz,13, isatty(c.flags.outfd)); 745 + buffer_write(&o, buf, end - buf); 746 + 747 + _wra_fld(footer); 748 +# undef _wra_fld 749 + 750 + if (isatty(c.flags.outfd)) buffer_push(&o, '\n'); 751 + buffer_flush(&o); 752 +} 753 + 754 +void 755 +write_privkey_v1(struct ctx c, const unsigned char* material) { 756 + struct buffer o = {c.flags.outfd,0}; 757 +# define _wrp_fld(f) buffer_write(&o, \ 758 + (c.flags.ascii_armor ? docbytes[doc_##f].asc.str \ 759 + : docbytes[doc_##f].bin.str), \ 760 + (c.flags.ascii_armor ? docbytes[doc_##f].asc.len \ 761 + : docbytes[doc_##f].bin.len)) 762 + 763 + _wrp_fld(header); 764 + _wrp_fld(version_1); 765 + _wrp_fld(type_priv); 766 + 767 + if (c.flags.ascii_armor) { 768 + char buf[crypto_sign_SECRETKEYBYTES * 2]; 769 + char* end = armor(material,buf,crypto_sign_SECRETKEYBYTES,13, 770 + isatty(c.flags.outfd)); 771 + buffer_write(&o, buf, end - buf); 772 + } else { 773 + buffer_write(&o, material, crypto_sign_SECRETKEYBYTES); 774 + } 775 + 776 + _wrp_fld(footer); 777 + if (isatty(c.flags.outfd)) buffer_push(&o, '\n'); 778 + buffer_flush(&o); 779 +} 780 + 781 +typedef void (*write_privkey_t)(struct ctx, const unsigned char*); 782 +typedef void (*write_armored_t)(struct ctx, const unsigned char*, size_t); 783 + 784 +write_privkey_t write_privkey[] = { 785 + [0] = write_privkey_v1, /* current version */ 786 + 787 + [1] = write_privkey_v1, 788 +}; 789 +write_armored_t write_armored[] = { 790 + [0] = write_armored_v1, /* current version */ 791 + 792 + [1] = write_armored_v1, 793 +}; 794 + 795 +int 796 +route(struct ctx c) { 797 + if (sodium_init() < 0) { 798 + _report(fatal, "could not initialize libsodium"); 799 + return 2; 800 + } 801 + 802 + switch (c.cmdv[0]) { 803 + case atom_key: { 804 + switch (c.cmdv[1]) { 805 + case atom_gen: { 806 + if (c.cmdc != 2) { usage(c,NULL); return 1; } 807 + _report(info, "generating new private key"); 808 + if (c.flags.symcrypt == false) { 809 + _say(warn, _t("not using symmetric encryption; key will be emitted in") _t("plain text")); 810 + } else { 811 + /* read passphrase from stdin/file */ 812 + } 813 + 814 + /* libsodium is a bit weird. it uses different key types for 815 + * encrypting and signing. we emit a signing (ed25519) key 816 + * because it can be converted to an encryption key, but not 817 + * vice-versa. libsodium also does not have any means to 818 + * derive a public key from a private key, even though this 819 + * is trivial, but instead stores the public key in the second 820 + * half of the private key (despite emitting it AGAIN into the 821 + * second parameter). so we generate a signing keypair, discard 822 + * the returned public key, and save the "private key" (including 823 + * its copy of the public key). this is extremely awkward and 824 + * hopefully a better solution will be found in the fullness of 825 + * time. */ 826 + unsigned char skey[crypto_sign_SECRETKEYBYTES]; 827 + { unsigned char _[crypto_sign_PUBLICKEYBYTES]; 828 + crypto_sign_keypair(_,skey); } 829 + (*write_privkey[c.flags.fmtv])(c, skey); 830 + 831 + } break; 832 + case atom_pub: { 833 + if (c.cmdc != 2) { usage(c,NULL); return 1; } 834 + struct file* skey = read_file(c, c.flags.infd); 835 + if (skey == NULL) return 6; 836 + 837 + free(skey); 838 + } break; 839 + case atom_wrap: { 840 + if (c.cmdc != 2) { usage(c,NULL); return 1; } 841 + struct file* key = read_file(c, c.flags.infd); 842 + c.flags.ascii_armor = feature_on; 843 + 844 + if (key -> type == doc_type_priv) { 845 + (*write_privkey[c.flags.fmtv])(c, key -> datastart); 846 + } else if (key -> type == doc_type_pub) { 847 + /* (*write_pubkey[c.flags.fmtv])(c, key); */ 848 + } else { 849 + _report(fatal, "file is not a " _self_name " key"); 850 + free(key); return 8; 851 + } 852 + free(key); return 0; 853 + } break; 854 + 855 + default: goto no_cmd; 856 + } 857 + } break; 858 + case atom_wrap: { 859 + if (c.cmdc != 1) { usage(c,NULL); return 1; } 860 + 861 + struct file* data = load_file(c, c.flags.infd); 862 + if (data == NULL) return 6; 863 + 864 + (*write_armored[c.flags.fmtv])(c, data->raw, data->rawlen); 865 + 866 + free(data); return 0; 867 + } break; 868 + case atom_unwrap: { 869 + if (c.cmdc != 1) { usage(c,NULL); return 1; } 870 + struct file* data = read_file(c, c.flags.infd); 871 + if (data == NULL) return 6; 872 + if (isatty(c.flags.outfd)) _say(warn, _t("printing binary values to tty")); 873 + write(c.flags.outfd, data->datastart, data->datlen); 874 + free(data); return 0; 875 + } break; 876 + case atom_help: { 877 + if (c.cmdc == 1) usage(c,NULL); 878 + else { 879 + const struct cmd_spec* tree = cmd_specs; 880 + for (size_t i = 1; i < c.cmdc; ++i) { 881 + if (c.cmdv[i] < _atom_n) { 882 + if (tree[c.cmdv[i]].sub != NULL) { 883 + tree = tree[c.cmdv[i]].sub; 884 + } else { 885 + _say(fatal, _t("no help available for subcommand") _t(reconstitute(c.cmdv[i]))); 886 + return 3; 887 + } 888 + } else { 889 + _say(fatal, _t("no help available;") _t(c.argv[c.cmdv[i] - _atom_n]) _t("is not a recognized command")); 890 + return 4; 891 + } 892 + } 893 + usage(c,tree); 894 + } 895 + return 0; 896 + } break; 897 + default: goto no_cmd; 898 + } 899 + return 0; 900 + 901 + no_cmd: _say(fatal, _t("no such command")); 902 + usage(c,NULL); 903 + return 1; 904 +} 905 + 906 + 907 +int 908 +open_file(struct ctx c, const char* path, int flags, word* retfd) { 909 + int fd = open(path,flags,0600); 910 + if (fd == -1) { 911 + switch (errno) { 912 + case EACCES: _say(fatal, _t("file") _t(path) _t("could not be opened because you do not have permission to read it")); break; 913 + case EFAULT: _say(fatal, _t("file") _t(path) _t("could not be opened because the path is outside your accessible address space")); break; 914 + case EISDIR: _say(fatal, _t("file") _t(path) _t("could not be opened because it is a directory, not a regular file")); break; 915 + case ELOOP: _say(fatal, _t("file") _t(path) _t("could not be opened because too many symbolic links were encountered")); break; 916 + case EMFILE: _say(fatal, _t("file") _t(path) _t("could not be opened because the per-process file limit is too low")); break; 917 + case ENOENT: _say(fatal, _t("file") _t(path) _t("could not be opened because it does not exist")); break; 918 + 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; 919 + 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; 920 + case ENXIO: _say(fatal, _t("file") _t(path) _t("could not be opened because it is not a regular file or FIFO")); break; 921 + case EPERM: _say(fatal, _t("file") _t(path) _t("could not be opened because it is sealed against access")); break; 922 + 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; 923 + default: _say(fatal, _t("file") _t(path) _t("could not be opened for unclear reasons - consult errno entry for code") _t((unsigned)errno)); break; 924 + } 925 + return errno; 926 + } 927 + 928 + *retfd = fd; return 0; 929 +} 930 + 931 +int 932 +main(unsigned int argc, const char** argv) { 933 + atom cmdlist[argc]; 934 + const char* paramlist[argc]; 935 + size_t paramcount = 0; 936 + 937 + struct ctx c = { 938 + .argv = paramlist, 939 + .cmdc = 0, 940 + .cmdv = cmdlist, 941 + .bin = (argc > 0 ? argv[0] : NULL), 942 + .flags = { 943 + .ascii_armor = feature_auto, 944 + .color = feature_auto, 945 + .noise = noise_fatal, 946 + .infile = NULL, .infd = 0, 947 + .outfile = NULL, .outfd = 1, 948 + .pwfile = NULL, .pwfd = 0, 949 + .pw_force_tty = false, 950 + .sigfile = NULL, .sigfd = 0, 951 + /* if sigfd == infd, extract sig from input stream */ 952 + .fd = false, 953 + .symcrypt = false, 954 + .fmtv = 0, /* 0 = native version */ 955 + }, 956 + }; 957 + 958 + if (argc == 0) { 959 + _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"); 960 + return -1; 961 + } 962 + 963 + struct flag* paramstack[argc]; 964 + size_t stackptr = 0; 965 + enum { 966 + reading_params, 967 + reading_commands, 968 + reading_flags 969 + } reading_mode = reading_flags; 970 + _say(debug, _t("parsing command line") _t(argv)); 971 + for (size_t i = 1; i < argc; ++i) { 972 + const char* const p = argv[i]; 973 + 974 + if (reading_mode >= reading_flags && p[0] == '-') { /* flag */ 975 + bool longflag; 976 + if (p[1] == '-') { 977 + if (p[2] == 0) { 978 + /* "--" has been passed; all further arguments must be 979 + * interpreted as parameters or commands, not flags */ 980 + reading_mode = reading_commands; 981 + continue; 982 + } else if (p[2] == '-' && p[3] == 0) { 983 + /* "---" has been passed; all further arguments must be 984 + * interpreted as parameters, not flags or commands */ 985 + reading_mode = reading_params; 986 + continue; 987 + } else longflag = true; 988 + } else longflag = false; 989 + _say(debug, _t("parsing flag") _t(p)); 990 + for (size_t o = 1; p[o] != 0; ++o) { 991 + atom option = atom_none; 992 + 993 + if (longflag) option = atomize(p+2); 994 + else { /* short flag */ 995 + _say(debug, _t("parsing short flag") _t(p[o])); 996 + for (size_t j = 0; j < sz(flags); ++ j) 997 + if (flags[j].kind != flag_none && flags[j].shortname == p[o]) { 998 + option = (atom)j; break; 999 + } 1000 + } 1001 + 1002 + if (option == atom_none) { 1003 + if (!longflag && p[o] == '-') { 1004 + if (p[o+1] == '-' && p[o+2] == 0) { 1005 + /* shorthand for --- */ 1006 + reading_mode = reading_params; 1007 + continue; 1008 + } else if (p[o+1] == 0) { 1009 + /* shorthand for -- */ 1010 + reading_mode = reading_commands; 1011 + continue; 1012 + } 1013 + } 1014 + char ftext[3] = {'-',p[o],0}; 1015 + _say(fatal, _t("flag") _t(longflag ? p+2 : ftext) _t("not meaningful")); 1016 + goto parse_fail; 1017 + } 1018 + 1019 + handle_flag:; 1020 + struct flag* f = flags + option; 1021 + void* fld = ((unsigned char*)&c) + (unsigned char)(f -> offset); 1022 + if (f -> kind > _flag_param) { 1023 + if (f->kind>=flag_value) { 1024 + *((unsigned*)fld) = f->kind - flag_value; 1025 + } else paramstack[stackptr++] = f; 1026 + } else if (f -> kind > _flag_noparam) { 1027 + switch(f -> kind) { 1028 + case flag_on: *((bool*)fld) = true; break; 1029 + case flag_off: *((bool*)fld) = false; break; 1030 + case flag_switch: *((bool*)fld) = !*((bool*)fld); break; 1031 + /* in order to avoid creepy behavior in pathological cases, 1032 + * we enforce saturation arithmetic for the increment/decrement 1033 + * flag types */ 1034 + case flag_inc: { 1035 + unsigned* v = fld; 1036 + if (*v < UINT_MAX) ++*v; 1037 + } break; 1038 + case flag_dec: { 1039 + unsigned* v = fld; 1040 + if (*v > 0) --*v; 1041 + } break; 1042 + case flag_sdec: { 1043 + signed* v = fld; 1044 + if (*v > INT_MIN) --*v; 1045 + } break; 1046 + } 1047 + } 1048 + 1049 + if (p[1] == '-') break; 1050 + } 1051 + continue; 1052 + } 1053 + 1054 + if (stackptr > 0) { 1055 + struct flag* f = paramstack[--stackptr]; 1056 + _say(debug, _t("handling argument for flag") _t(reconstitute(f - flags))); 1057 + void* fld = ((unsigned char*)&c) + (unsigned char)(f -> offset); 1058 + switch(f -> kind) { 1059 + case flag_str: *((const char**)fld) = p; break; 1060 + case flag_uint: { 1061 + iaia_error_type e = int_of_str(10, p, (unsigned long long*)fld); 1062 + switch (e) { 1063 + case iaia_e_ok: continue; 1064 + case iaia_e_domain: 1065 + _say(fatal, _t("argument") _t(p) _t("outside domain for base")); 1066 + } 1067 + return 5; 1068 + } break; 1069 + case flag_atom: case flag_raw_atom: { 1070 + atom a = atomize(p); 1071 + if (a == atom_none || a < f -> opts.first || a > f -> opts.last) { 1072 + _say(fatal, _t("invalid argument") _t(p) _t("for flag") 1073 + _t(reconstitute(f - flags))); 1074 + goto parse_fail; 1075 + } 1076 + *((atom*)fld) = f->kind == flag_raw_atom ? a : a - f -> opts.first; 1077 + } break; 1078 + 1079 + } 1080 + continue; 1081 + } 1082 + 1083 + if (reading_mode >= reading_commands) { 1084 + if (p[0] == '=') { /* parameter even if atomizable */ 1085 + paramlist[paramcount] = p + 1; 1086 + cmdlist[c.cmdc] = _atom_n + paramcount; 1087 + ++c.cmdc; ++ paramcount; 1088 + continue; 1089 + } else { 1090 + atom a = atomize(p); 1091 + if (a != atom_none) { 1092 + cmdlist[c.cmdc] = a; 1093 + ++ c.cmdc; 1094 + continue; 1095 + } 1096 + } 1097 + } 1098 + 1099 + /* only parameters remain to be read */ 1100 + paramlist[paramcount] = p; 1101 + cmdlist[c.cmdc] = _atom_n + paramcount; 1102 + ++ c.cmdc; ++ paramcount; 1103 + } 1104 + 1105 + parse_done: _say(debug, _t("stored") _t(c.cmdc) _t("arguments and") _t(paramcount) _t("parameters")); 1106 + 1107 + if (c.flags.noise <= noise_debug) for (size_t i = 0; i<c.cmdc; ++i) { 1108 + if (cmdlist[i] < _atom_n) { 1109 + dprintf(2, "\t%llu. atom %u (normal form “%s”)\n", i, cmdlist[i], reconstitute(cmdlist[i])); 1110 + } else { 1111 + dprintf(2,"\t%llu. parameter “%s”\n", i, paramlist[cmdlist[i] - _atom_n]); 1112 + } 1113 + } 1114 + 1115 + if (c.flags.fmtv >= sz(write_privkey)) { 1116 + _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 "?")); 1117 + return -2; 1118 + } 1119 + int err; 1120 + if (c.flags.pwfile != NULL) { 1121 + err = open_file(c,c.flags.pwfile, O_RDONLY, &c.flags.pwfd); 1122 + if(err) return err; 1123 + } 1124 + if (c.flags.infile != NULL) { 1125 + err = open_file(c,c.flags.infile, O_RDONLY, &c.flags.infd); 1126 + if(err) return err; 1127 + } 1128 + if (c.flags.outfile != NULL) { 1129 + err = open_file(c,c.flags.outfile, O_WRONLY | O_TRUNC | O_CREAT, &c.flags.outfd); 1130 + if(err) return err; 1131 + } 1132 + if (c.flags.sigfile != NULL) { 1133 + err = open_file(c,c.flags.sigfile, O_RDONLY, &c.flags.sigfd); 1134 + if(err) return err; 1135 + } 1136 + 1137 + 1138 + if (c.flags.color == feature_auto) 1139 + c.flags.color = isatty(2); 1140 + if (c.flags.ascii_armor == feature_auto) 1141 + c.flags.ascii_armor = isatty(c.flags.outfd); 1142 + 1143 + /* this is hateful but i can't figure out a better way */ 1144 + if (c.flags.pw_force_tty && !isatty(c.flags.pwfd)) { 1145 + _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")); 1146 + } else if (c.flags.pwfd == c.flags.infd) { 1147 + _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")); 1148 + } else if (c.flags.pwfd == c.flags.sigfd) { 1149 + _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")); 1150 + } else goto skip_pwfd_change; 1151 + 1152 + pwfd_change: { 1153 + err = open_file(c,"/dev/tty", O_WRONLY, &c.flags.pwfd); 1154 + if(err) return err; 1155 + } 1156 + 1157 + skip_pwfd_change: 1158 + 1159 + _say(debug, _t("input fd =") _t(c.flags.infd)); 1160 + _say(debug, _t("output fd =") _t(c.flags.outfd)); 1161 + _say(debug, _t("pw fd =") _t(c.flags.pwfd)); 1162 + 1163 + if (c.cmdc > 0) return route(c); 1164 + 1165 + parse_fail: if (c.flags.noise < noise_silent) usage(c,NULL); 1166 + return 1; 1167 +}
Modified tenki/tenki.c from [b2cd504148] to [be25cce95d].
72 72 #else 73 73 # define _http "?" 74 74 #endif 75 75 #define http _http "lang=" ds_lang " HTTP/1.1\r\nHost: api.darksky.net\r\n\r\n" 76 76 #define start "GET /forecast/" ds_apikey "/" 77 77 78 78 #define min(x,y) (x>y?y:x) 79 -#define len(x) (sizeof(x)/sizeof(*x)) 79 +#define len(x) (sizeof(x)/sizeof(x)[0]) 80 80 81 81 typedef enum bool { false, true } bool; 82 82 83 83 SSL_CTX* sc; 84 84 bool head(size_t* len, char* txt, char** body) { 85 85 char* hend = strstr(txt, "\r\n\r\n"); 86 86 if (hend == NULL) return false; ................................................................................ 186 186 size_t len = *key++; 187 187 for (size_t i = 0; i<len; ++i) { 188 188 printf("/%.*s",*key,key+1); 189 189 key += *key + 1; 190 190 }; 191 191 printf("\n"); 192 192 } */ 193 -void pplt(const char* str, const char* const end, const char** keys, const char** const keyend, jsv* values) { 193 +void pplt 194 +( const char* str, 195 + const char* const end, 196 + const char** keys, 197 + const char** const keyend, 198 + jsv* values 199 +) { 194 200 // initialize parser state 195 201 size_t depth = 0; 196 202 const char* fld = NULL; 197 203 const char* path[32]; 198 204 path[0] = NULL; 199 205 200 206 while(*str++!='{'); //cheat ................................................................................ 365 371 366 372 pstr summary = values[0].s; 367 373 float pcpint = values[1].f, 368 374 temp = values[2].f, 369 375 windspd = values[3].f; 370 376 371 377 printf("%.1f° %%{F#ff9bbb}%.*s\n", 372 - summary.sz, 373 - summary.a, 374 - temp); 378 + temp, 379 + (int /*throwing table emoji*/)summary.sz, 380 + summary.a); 375 381 } 376 382 // dark sky's API allows us to make 1000 free requests a day. 377 383 // let's try not to get too near that. 378 384 // hours in day 24 - minutes in day 24 * 60 = 1440 379 385 // so once a minute is almost half again too many 380 386 // 1440 / 2 = 720 leaving reasonable refresh time without 381 387 // going too near the limit. so we aim for one update 382 388 // every two minutes. 383 389 // sleep(60 * 2); 384 390 // } 385 391 386 392 return 0; 387 393 }