├── .gitignore ├── todo.txt ├── asymcrypt_formats.5.md ├── README.md └── main.c /.gitignore: -------------------------------------------------------------------------------- 1 | help.inc 2 | asymcrypt.1 3 | asymcrypt -------------------------------------------------------------------------------- /todo.txt: -------------------------------------------------------------------------------- 1 | - test suite covering all documented use cases. 2 | - use frama-c interp mode 3 | - configure script that can use nacl, tweetnacl, libsodium, hacl. -------------------------------------------------------------------------------- /asymcrypt_formats.5.md: -------------------------------------------------------------------------------- 1 | % asymcrypt_formats(5) 2 | % Andrew Chambers 3 | % 2018 4 | 5 | # ASYMCRYPT FORMATS 6 | 7 | All base constants come from the NACL cryptographic api. 8 | 9 | ## Private key version 1 format 10 | 11 | ``` 12 | magic: "asymcrypt" 13 | version: be_u16(1) 14 | type: be_u16(0) 15 | keyid: byte[keyid_len=16] 16 | pubenckey: byte[crypto_box_pk_len] 17 | secenckey: byte[crypto_box_sk_len] 18 | pubsigkey: byte[crypto_sign_pk_len] 19 | secsigkey: byte[crypto_sign_sk_len] 20 | ``` 21 | 22 | ## Public key version 1 format 23 | 24 | ``` 25 | magic: "asymcrypt" 26 | version: be_u16(1) 27 | type: be_u16(1) 28 | keyid: byte[keyid_len] 29 | pubenckey: byte[crypto_box_pk_len] 30 | pubsigkey: byte[crypto_sign_pk_len] 31 | ``` 32 | 33 | ## Signature version 1 format 34 | 35 | ``` 36 | magic: "asymcrypt" 37 | version: be_u16(1) 38 | type: be_u16(2) 39 | keyid: byte[keyid_len] 40 | signature: byte[crypto_hash_sha256_BYTES + crypto_sign_BYTES] 41 | ``` 42 | 43 | where crypto_sign_open(signature, pubsigkey) == sha256(data_stream) 44 | 45 | ## Cipher text version 1 format 46 | 47 | ``` 48 | magic: "asymcrypt" 49 | version: be_u16(1) 50 | type: be_u16(3) 51 | keyid: byte[keyid_len] 52 | nonce: byte[crypto_box_NONCEBYTES] 53 | ephemeral_pubkey: byte[crypto_box_pk_len] 54 | ( 55 | message: byte[msgsize=16384] 56 | )+ 57 | ``` 58 | 59 | where message is: 60 | 61 | ``` 62 | padding: byte[crypto_box_BOXZEROBYTES] 63 | ciphertext: byte[msgsize-crypto_box_BOXZEROBYTES] 64 | ``` 65 | 66 | where crypto_box_open(message, nonce + msg_index, ephemeral_pubkey, secenckey) is: 67 | 68 | ``` 69 | padding: byte[crypto_box_ZEROBYTES] 70 | msglen: be_u16 71 | msg: byte[msglen] 72 | ... unused bytes till msgsize 73 | ``` 74 | 75 | The last message in the stream will have an underutilized msglen. 76 | 77 | 78 | # SEE ALSO 79 | 80 | **asymcrypt(1)** 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | % asymcrypt(3) 2 | % Andrew Chambers 3 | % 2018 4 | 5 | # NAME 6 | 7 | asymcrypt - A tool for asymmetric cryptography. 8 | 9 | # SYNOPSIS 10 | 11 | Generate public or private keys, sign, verify, encrypt or decrypt data. 12 | Simple, reliable and auditable. The tool does zero memory allocations during usage, so it should be very fast. 13 | 14 | Internally the tool uses the nacl cryptography API. 15 | 16 | # USAGE 17 | 18 | ``` 19 | asymcrypt - asymmetric cryptography 20 | 21 | asymcrypt k(ey) > secret.key 22 | asymcrypt p(ubkey) < secret.key > public.key 23 | asymcrypt p(ubkey) secret.key > public.key 24 | asymcrypt e(ncrypt) public.key < plain.txt > encrypted.txt 25 | asymcrypt e(ncrypt) <(cat public.key plain.txt) > encrypted.txt 26 | asymcrypt d(ecrypt) secret.key < plain.txt > encrypted.txt 27 | asymcrypt d(ecrypt) <(cat secret.key plain.txt) > encrypted.txt 28 | asymcrypt s(ign) secret.key < something > something.sig 29 | asymcrypt s(ign) <(cat secret.key something) > something.sig 30 | asymcrypt v(erify) public.key something.sig < something 31 | asymcrypt v(erify) public.key <(cat something.sig something) 32 | asymcrypt v(erify) <(cat public.key something.sig something) 33 | asymcrypt i(nfo) < encrypted.txt 34 | asymcrypt i(nfo) < secret.key 35 | asymcrypt i(nfo) < public.key 36 | asymcrypt i(nfo) < something.sig 37 | 38 | All commands exit with rc > 0 on error 0 on success. 39 | 40 | On error a single line is printed to stderr. 41 | 42 | On success no output is generated except for the requested data. 43 | 44 | Encrypt and sign reject world readable secret keys. 45 | 46 | When decrypting, only verified data is sent to stdout. 47 | 48 | When decrypting, a truncated data stream results in error. 49 | 50 | If arguments are not specified for pubkey ,encrypt, decrypt, 51 | sign and verify, they will be read in order from stdin. 52 | This allows scripts to never write key material to disk. 53 | 54 | The info command outputs a single line with 3 fields. 55 | 56 | $VERSION $TYPE $KEYID 57 | 58 | example: 59 | 60 | V1 secretkey KEYID 61 | V1 publickey KEYID 62 | V1 signature KEYID 63 | V1 ciphertext KEYID 64 | ``` 65 | 66 | 67 | # SEE ALSO 68 | 69 | **asymcrypt_formats(5)** 70 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define KEYID_BYTES 16 9 | /* must fit inside u16 */ 10 | #define MESSAGE_SIZE 16384 11 | 12 | #define TYPE_SECRETKEY 0 13 | #define TYPE_PUBLICKEY 1 14 | #define TYPE_SIG 2 15 | #define TYPE_CIPHERTEXT 3 16 | #define TYPE_END 4 17 | 18 | unsigned char key_id[KEYID_BYTES]; 19 | unsigned char crypto_box_pk[crypto_box_PUBLICKEYBYTES]; 20 | unsigned char crypto_box_sk[crypto_box_SECRETKEYBYTES]; 21 | unsigned char ephemeral_crypto_box_pk[crypto_box_PUBLICKEYBYTES]; 22 | unsigned char ephemeral_crypto_box_sk[crypto_box_SECRETKEYBYTES]; 23 | unsigned char crypto_sign_pk[crypto_sign_PUBLICKEYBYTES]; 24 | unsigned char crypto_sign_sk[crypto_sign_SECRETKEYBYTES]; 25 | unsigned char sha256[crypto_hash_sha256_BYTES]; 26 | unsigned char nonce[crypto_box_NONCEBYTES]; 27 | crypto_hash_sha256_state sha256_state; 28 | unsigned char signed_sha256[crypto_hash_sha256_BYTES + crypto_sign_BYTES]; 29 | unsigned long long signed_sha256_len; 30 | 31 | #define die(msg) \ 32 | do { \ 33 | fprintf(stderr, msg); \ 34 | exit(1); \ 35 | } while (0) 36 | 37 | void write_buf(unsigned char *buf, size_t n) { 38 | if (fwrite(buf, 1, n, stdout) != n) { 39 | die("error writing output\n"); 40 | } 41 | } 42 | 43 | void write_u16(int16_t type) { 44 | unsigned char buf[2]; 45 | buf[0] = (type >> 8) & 0xff; 46 | buf[1] = type & 0xff; 47 | write_buf(buf, sizeof(buf)); 48 | } 49 | 50 | void write_hdr(int16_t type) { 51 | unsigned char *ident = (unsigned char *)"asymcrypt"; 52 | write_buf(ident, strlen("asymcrypt")); 53 | // version 54 | write_u16(1); 55 | write_u16(type); 56 | } 57 | 58 | void must_read_buf(FILE *f, unsigned char *buf, size_t n) { 59 | if (fread(buf, 1, n, f) != n) { 60 | if (feof(f)) 61 | die("unexpected end of input"); 62 | else 63 | die("error reading input\n"); 64 | } 65 | } 66 | 67 | int read_buf(FILE *f, unsigned char *buf, size_t n) { 68 | int nread = fread(buf, 1, n, f); 69 | if (nread != n) 70 | if (!feof(f)) 71 | die("an error occured"); 72 | return nread; 73 | } 74 | 75 | void read_hdr(FILE *f, int16_t *version, int16_t *type) { 76 | unsigned char buf[9 + 2 + 2]; 77 | must_read_buf(f, buf, sizeof(buf)); 78 | 79 | if (strncmp((const char *)buf, "asymcrypt", 9) != 0) 80 | die("not a valid asymcrypt object\n"); 81 | 82 | *version = (int16_t)(buf[9] << 8) | buf[10]; 83 | *type = (int16_t)(buf[11] << 8) | buf[12]; 84 | 85 | if (*version != 1) 86 | die("unsupported version\n"); 87 | 88 | if (*type < 0 || *type >= TYPE_END) 89 | die("unknown data type\n"); 90 | } 91 | 92 | void read_secret_key(FILE *f) { 93 | int16_t version; 94 | int16_t type; 95 | 96 | read_hdr(f, &version, &type); 97 | 98 | if (version != 1) 99 | die("unknown key version\n"); 100 | 101 | if (type != TYPE_SECRETKEY) 102 | die("input data is not a asymcrypt secret key\n"); 103 | 104 | must_read_buf(f, key_id, sizeof(key_id)); 105 | must_read_buf(f, crypto_box_pk, sizeof(crypto_box_pk)); 106 | must_read_buf(f, crypto_box_sk, sizeof(crypto_box_sk)); 107 | must_read_buf(f, crypto_sign_pk, sizeof(crypto_sign_pk)); 108 | must_read_buf(f, crypto_sign_sk, sizeof(crypto_sign_sk)); 109 | } 110 | 111 | void read_public_key(FILE *f) { 112 | int16_t version; 113 | int16_t type; 114 | 115 | read_hdr(f, &version, &type); 116 | 117 | if (version != 1) 118 | die("unknown key version\n"); 119 | 120 | if (type != TYPE_PUBLICKEY) 121 | die("input data is not a asymcrypt public key\n"); 122 | 123 | must_read_buf(f, key_id, sizeof(key_id)); 124 | must_read_buf(f, crypto_box_pk, sizeof(crypto_box_pk)); 125 | must_read_buf(f, crypto_sign_pk, sizeof(crypto_sign_pk)); 126 | } 127 | 128 | void read_sig(FILE *f) { 129 | int16_t version; 130 | int16_t type; 131 | unsigned char buf[2]; 132 | 133 | read_hdr(f, &version, &type); 134 | 135 | if (version != 1) 136 | die("unknown signature version\n"); 137 | 138 | if (type != TYPE_SIG) 139 | die("input data is not a asymcrypt signature\n"); 140 | 141 | must_read_buf(f, key_id, sizeof(key_id)); 142 | must_read_buf(f, buf, sizeof(buf)); 143 | signed_sha256_len = (long long unsigned int)(buf[0] << 8) | buf[1]; 144 | 145 | if (signed_sha256_len > sizeof(signed_sha256)) 146 | die("bad signature length\n"); 147 | 148 | must_read_buf(f, signed_sha256, signed_sha256_len); 149 | } 150 | 151 | void cmd_key() { 152 | write_hdr(TYPE_SECRETKEY); 153 | if (crypto_box_keypair(crypto_box_pk, crypto_box_sk) != 0) 154 | die("error generating crypto_box keypair\n"); 155 | if (crypto_sign_keypair(crypto_sign_pk, crypto_sign_sk) != 0) 156 | die("error generating crypto_sign keypair\n"); 157 | 158 | randombytes_buf(key_id, sizeof(key_id)); 159 | 160 | write_buf(key_id, sizeof(key_id)); 161 | write_buf(crypto_box_pk, sizeof(crypto_box_pk)); 162 | write_buf(crypto_box_sk, sizeof(crypto_box_sk)); 163 | write_buf(crypto_sign_pk, sizeof(crypto_sign_pk)); 164 | write_buf(crypto_sign_sk, sizeof(crypto_sign_sk)); 165 | } 166 | 167 | void assert_sk_perms(char *secretkey) { 168 | struct stat buffer; 169 | if (stat(secretkey, &buffer)) 170 | die("error checking key permissions\n"); 171 | 172 | if ((buffer.st_mode & 0007) != 0) 173 | die("secret key is world accessible\n"); 174 | } 175 | 176 | void cmd_pubkey(char *secretkey) { 177 | 178 | if (secretkey) { 179 | assert_sk_perms(secretkey); 180 | 181 | FILE *f = fopen(secretkey, "rb"); 182 | if (!f) 183 | die("error opening secret key\n"); 184 | 185 | read_secret_key(f); 186 | 187 | if (fclose(f)) 188 | die("unable to close key"); 189 | } else { 190 | read_secret_key(stdin); 191 | } 192 | 193 | write_hdr(TYPE_PUBLICKEY); 194 | write_buf(key_id, sizeof(key_id)); 195 | write_buf(crypto_box_pk, sizeof(crypto_box_pk)); 196 | write_buf(crypto_sign_pk, sizeof(crypto_sign_pk)); 197 | } 198 | 199 | void hash_stdin() { 200 | crypto_hash_sha256_init(&sha256_state); 201 | 202 | // XXX swap everything to unistd read/write? 203 | // extra copy from that buf. 204 | unsigned char buf[4096]; 205 | while (1) { 206 | int n = read_buf(stdin, buf, sizeof(buf)); 207 | 208 | crypto_hash_sha256_update(&sha256_state, buf, n); 209 | 210 | if (n != sizeof(buf)) 211 | break; 212 | } 213 | 214 | crypto_hash_sha256_final(&sha256_state, sha256); 215 | } 216 | 217 | void cmd_sign(char *secretkey) { 218 | 219 | if (secretkey) { 220 | assert_sk_perms(secretkey); 221 | 222 | FILE *f = fopen(secretkey, "rb"); 223 | if (!f) 224 | die("error opening secret key\n"); 225 | 226 | read_secret_key(f); 227 | 228 | if (fclose(f)) 229 | die("unable to close key"); 230 | } else { 231 | read_secret_key(stdin); 232 | } 233 | 234 | hash_stdin(); 235 | 236 | crypto_sign(signed_sha256, &signed_sha256_len, sha256, sizeof(sha256), 237 | crypto_sign_sk); 238 | 239 | write_hdr(TYPE_SIG); 240 | write_buf(key_id, sizeof(key_id)); 241 | write_u16((int16_t)signed_sha256_len); 242 | write_buf(signed_sha256, signed_sha256_len); 243 | } 244 | 245 | void cmd_verify(char *publickey, char *sigfile) { 246 | 247 | if (strlen(publickey)) { 248 | FILE *f = fopen(publickey, "rb"); 249 | if (!f) 250 | die("error opening public key\n"); 251 | 252 | read_public_key(f); 253 | 254 | if (fclose(f)) 255 | die("unable to close key"); 256 | } else { 257 | read_public_key(stdin); 258 | } 259 | 260 | if (strlen(sigfile)) { 261 | FILE *f = fopen(sigfile, "rb"); 262 | if (!f) 263 | die("error opening secret key\n"); 264 | 265 | read_sig(f); 266 | 267 | if (fclose(f)) 268 | die("unable to close sig\n"); 269 | } else { 270 | read_sig(stdin); 271 | } 272 | 273 | hash_stdin(); 274 | 275 | unsigned char m[sizeof(signed_sha256)]; 276 | unsigned long long mlen; 277 | 278 | if (crypto_sign_open(m, &mlen, signed_sha256, signed_sha256_len, 279 | crypto_sign_pk) != 0) 280 | die("signature failed\n"); 281 | 282 | if (mlen != sizeof(sha256)) 283 | die("signature lengths differ\n"); 284 | 285 | if (memcmp(m, sha256, sizeof(sha256)) != 0) 286 | die("signature differs\n"); 287 | } 288 | 289 | void increment_nonce() { 290 | for (int i = 0; i < sizeof(nonce); i++) { 291 | int next = (nonce[i] + 1) & 0xff; 292 | nonce[i] = next; 293 | if (next != 0) 294 | break; 295 | } 296 | } 297 | 298 | void cmd_encrypt(char *publickey) { 299 | 300 | if (strlen(publickey)) { 301 | FILE *f = fopen(publickey, "rb"); 302 | if (!f) 303 | die("error opening public key\n"); 304 | 305 | read_public_key(f); 306 | 307 | if (fclose(f)) 308 | die("unable to close key\n"); 309 | } else { 310 | read_public_key(stdin); 311 | } 312 | 313 | randombytes_buf(nonce, sizeof(nonce)); 314 | if (crypto_box_keypair(ephemeral_crypto_box_pk, ephemeral_crypto_box_sk) != 315 | 0) { 316 | die("error generating ephemeral crypto_box keypair\n"); 317 | exit(1); 318 | } 319 | 320 | write_hdr(TYPE_CIPHERTEXT); 321 | write_buf(key_id, sizeof(key_id)); 322 | write_buf(ephemeral_crypto_box_pk, sizeof(ephemeral_crypto_box_pk)); 323 | write_buf(nonce, sizeof(nonce)); 324 | 325 | unsigned char buf[MESSAGE_SIZE]; 326 | unsigned char out_buf[sizeof(buf)]; 327 | 328 | for (int i = 0; i < sizeof(out_buf); i++) 329 | out_buf[i] = 0; 330 | for (int i = 0; i < sizeof(buf); i++) 331 | buf[i] = 0; 332 | 333 | while (1) { 334 | size_t n_to_read = sizeof(buf) - crypto_box_ZEROBYTES - 2; 335 | size_t n = read_buf(stdin, buf + crypto_box_ZEROBYTES + 2, n_to_read); 336 | 337 | buf[crypto_box_ZEROBYTES + 0] = (n >> 8) & 0xff; 338 | buf[crypto_box_ZEROBYTES + 1] = n & 0xff; 339 | 340 | if (crypto_box(out_buf, buf, sizeof(buf), nonce, crypto_box_pk, 341 | ephemeral_crypto_box_sk) != 0) 342 | die("error encrypting message\n"); 343 | 344 | write_buf(out_buf, sizeof(out_buf)); 345 | 346 | increment_nonce(); 347 | 348 | if (n < n_to_read) 349 | break; 350 | } 351 | } 352 | 353 | void cmd_decrypt(char *secretkey) { 354 | 355 | if (strlen(secretkey)) { 356 | assert_sk_perms(secretkey); 357 | 358 | FILE *f = fopen(secretkey, "rb"); 359 | if (!f) 360 | die("error opening public key\n"); 361 | 362 | read_secret_key(f); 363 | 364 | if (fclose(f)) 365 | die("unable to close key\n"); 366 | } else { 367 | read_secret_key(stdin); 368 | } 369 | 370 | int16_t version; 371 | int16_t type; 372 | 373 | read_hdr(stdin, &version, &type); 374 | 375 | if (version != 1) 376 | die("unknown ciphertext version\n"); 377 | 378 | if (type != TYPE_CIPHERTEXT) 379 | die("input data is not a asymcrypt ciphertext\n"); 380 | 381 | unsigned char stream_keyid[sizeof(key_id)]; 382 | must_read_buf(stdin, stream_keyid, sizeof(stream_keyid)); 383 | 384 | if (memcmp(stream_keyid, key_id, sizeof(key_id)) != 0) 385 | die("stream and secret key do not match\n"); 386 | 387 | must_read_buf(stdin, ephemeral_crypto_box_pk, 388 | sizeof(ephemeral_crypto_box_pk)); 389 | must_read_buf(stdin, nonce, sizeof(nonce)); 390 | 391 | unsigned char in_buf[MESSAGE_SIZE]; 392 | unsigned char out_buf[sizeof(in_buf)]; 393 | 394 | while (1) { 395 | must_read_buf(stdin, in_buf, sizeof(in_buf)); 396 | 397 | for (int i = 0; i < crypto_box_BOXZEROBYTES; i++) 398 | if (in_buf[i] != 0) 399 | die("message has corrupt padding\n"); 400 | 401 | if (crypto_box_open(out_buf, in_buf, sizeof(in_buf), nonce, 402 | ephemeral_crypto_box_pk, crypto_box_sk) != 0) 403 | die("error decrypting stream\n"); 404 | 405 | size_t data_size = (out_buf[crypto_box_ZEROBYTES + 0] << 8) | 406 | out_buf[crypto_box_ZEROBYTES + 1]; 407 | write_buf(out_buf + crypto_box_ZEROBYTES + 2, data_size); 408 | 409 | if (data_size + crypto_box_ZEROBYTES + 2 != MESSAGE_SIZE) 410 | break; 411 | 412 | increment_nonce(); 413 | } 414 | } 415 | 416 | void cmd_info() { 417 | 418 | int16_t version; 419 | int16_t type; 420 | 421 | read_hdr(stdin, &version, &type); 422 | read_buf(stdin, key_id, sizeof(key_id)); 423 | 424 | char *t = "unknown"; 425 | char *lut[] = {"secretkey", "publickey", "signature", "ciphertext"}; 426 | if (type >= 0 && type < sizeof(lut) / sizeof(char *)) 427 | t = lut[type]; 428 | 429 | char keyid_hex[(sizeof(key_id) * 2) + 1]; 430 | char *hex = "0123456789abcdef"; 431 | for (int i = 0; i < sizeof(key_id); i++) { 432 | int hi = (key_id[i] & 0xf0) >> 4; 433 | int lo = key_id[i] & 0xf; 434 | 435 | keyid_hex[i * 2] = hex[hi]; 436 | keyid_hex[i * 2 + 1] = hex[lo]; 437 | } 438 | keyid_hex[sizeof(keyid_hex) - 1] = 0; 439 | 440 | if (printf("V%d %s %s\n", version, t, keyid_hex) < 0) 441 | die("error writing output\n"); 442 | } 443 | 444 | void help() { 445 | #include "help.inc" 446 | exit(1); 447 | } 448 | 449 | int main(int argc, char **argv) { 450 | if (!freopen(NULL, "rb", stdin)) 451 | die("unable to switch stdin to binary mode\n"); 452 | 453 | if (!freopen(NULL, "wb", stdout)) 454 | die("unable to switch stdin to binary mode\n"); 455 | 456 | if (setvbuf(stdin, 0, _IOFBF, 4096) != 0) 457 | die("unable to set stdin buffering\n"); 458 | 459 | if (setvbuf(stdout, 0, _IOFBF, 4096) != 0) 460 | die("unable to set stdout buffering\n"); 461 | 462 | if (argc <= 1) 463 | help(); 464 | 465 | #define CMD(cmd) (strcmp(argv[1], cmd) == 0 || strncmp(argv[1], cmd, 1) == 0) 466 | if (CMD("key")) { 467 | if (argc == 2) 468 | cmd_key(); 469 | else 470 | die("bad argument count for key command\n"); 471 | } else if (CMD("pubkey")) { 472 | if (argc == 2) 473 | cmd_pubkey(0); 474 | else if (argc == 3) 475 | cmd_pubkey(argv[2]); 476 | else 477 | die("bad argument count for pubkey command\n"); 478 | } else if (CMD("sign")) { 479 | if (argc == 2) 480 | cmd_sign(0); 481 | else if (argc == 3) 482 | cmd_sign(argv[2]); 483 | else 484 | die("bad argument count for sign command\n"); 485 | } else if (CMD("verify")) { 486 | if (argc == 2) 487 | cmd_verify(0, 0); 488 | else if (argc == 3) 489 | cmd_verify(argv[2], 0); 490 | else if (argc == 4) 491 | cmd_verify(argv[2], argv[3]); 492 | else 493 | die("bad argument count for verify command\n"); 494 | } else if (CMD("encrypt")) { 495 | if (argc == 2) 496 | cmd_encrypt(0); 497 | else if (argc == 3) 498 | cmd_encrypt(argv[2]); 499 | else 500 | die("bad argument count for encrypt command\n"); 501 | } else if (CMD("decrypt")) { 502 | if (argc == 2) 503 | cmd_decrypt(0); 504 | else if (argc == 3) 505 | cmd_decrypt(argv[2]); 506 | else 507 | die("bad argument count for decrypt command\n"); 508 | } else if (CMD("info")) { 509 | if (argc == 2) 510 | cmd_info(); 511 | else 512 | die("bad argument count for info command\n"); 513 | } else { 514 | help(); 515 | } 516 | #undef CMD 517 | 518 | if (fflush(stdout) != 0) 519 | die("error flushing output\n"); 520 | 521 | if (fflush(stderr) != 0) 522 | die("error flushing error output\n"); 523 | 524 | return 0; 525 | } --------------------------------------------------------------------------------