/************************************************************************ * * * Star Traders: A Game of Interstellar Trading * * Copyright (C) 1990-2018, John Zaitseff * * * ************************************************************************/ /* Author: John Zaitseff $Id$ This file, utils.c, contains the implementation of various utility functions used in Star Traders. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/. */ #include "trader.h" /************************************************************************ * Global variable definitions * ************************************************************************/ const char *program_name = NULL; // Canonical program name // Global copy, suitably modified, of localeconv() information struct lconv lconvinfo; // localeconv() information, converted to wide strings wchar_t *decimal_point; // Locale's radix character wchar_t *thousands_sep; // Locale's thousands separator wchar_t *currency_symbol; // Locale's currency symbol wchar_t *mon_decimal_point; // Locale's monetary radix character wchar_t *mon_thousands_sep; // Locale's monetary thousands separator /************************************************************************ * Module-specific constants and macros * ************************************************************************/ #define GAME_FILENAME_PROTO "game%d" #define GAME_FILENAME_BUFSIZE 16 // Values used to override the standard POSIX locale #define MOD_POSIX_DECIMAL_POINT "." #define MOD_POSIX_THOUSANDS_SEP "" #define MOD_POSIX_GROUPING "" #define MOD_POSIX_INT_CURR_SYMBOL "" #define MOD_POSIX_CURRENCY_SYMBOL "$" // Standard: "" #define MOD_POSIX_MON_DECIMAL_POINT "." // Standard: "" #define MOD_POSIX_MON_THOUSANDS_SEP "" #define MOD_POSIX_MON_GROUPING "" #define MOD_POSIX_POSITIVE_SIGN "" #define MOD_POSIX_NEGATIVE_SIGN "-" // Standard: "" #define MOD_POSIX_INT_FRAC_DIGITS 2 // Standard: CHAR_MAX #define MOD_POSIX_FRAC_DIGITS 2 // Standard: CHAR_MAX #define MOD_POSIX_P_CS_PRECEDES 1 // Standard: CHAR_MAX #define MOD_POSIX_P_SEP_BY_SPACE 0 // Standard: CHAR_MAX #define MOD_POSIX_N_CS_PRECEDES 1 // Standard: CHAR_MAX #define MOD_POSIX_N_SEP_BY_SPACE 0 // Standard: CHAR_MAX #define MOD_POSIX_P_SIGN_POSN 1 // Standard: CHAR_MAX #define MOD_POSIX_N_SIGN_POSN 1 // Standard: CHAR_MAX static const struct lconv mod_posix_lconv = { .decimal_point = MOD_POSIX_DECIMAL_POINT, .thousands_sep = MOD_POSIX_THOUSANDS_SEP, .grouping = MOD_POSIX_GROUPING, .int_curr_symbol = MOD_POSIX_INT_CURR_SYMBOL, .currency_symbol = MOD_POSIX_CURRENCY_SYMBOL, .mon_decimal_point = MOD_POSIX_MON_DECIMAL_POINT, .mon_thousands_sep = MOD_POSIX_MON_THOUSANDS_SEP, .mon_grouping = MOD_POSIX_MON_GROUPING, .positive_sign = MOD_POSIX_POSITIVE_SIGN, .negative_sign = MOD_POSIX_NEGATIVE_SIGN, .int_frac_digits = MOD_POSIX_INT_FRAC_DIGITS, .frac_digits = MOD_POSIX_FRAC_DIGITS, .p_cs_precedes = MOD_POSIX_P_CS_PRECEDES, .p_sep_by_space = MOD_POSIX_P_SEP_BY_SPACE, .n_cs_precedes = MOD_POSIX_N_CS_PRECEDES, .n_sep_by_space = MOD_POSIX_N_SEP_BY_SPACE, .p_sign_posn = MOD_POSIX_P_SIGN_POSN, .n_sign_posn = MOD_POSIX_N_SIGN_POSN // ISO/IEC 9945-1:2008 (SUSv4) defines additional fields, but // this program does not use them. }; // Constants used for scrambling and unscrambling game data #define SCRAMBLE_CRC_LEN 8 // Length of CRC in ASCII (excl NUL) #define SCRAMBLE_CHKSUM_LEN 3 // For checksum, excluding NUL byte #define SCRAMBLE_CRC_MASK 0xFFFFFFFF // Bits of CRC to keep #define SCRAMBLE_CHKSUM_MASK 0x0FFF // Bits of checksum to keep #define SCRAMBLE_PAD_CHAR '*' #define SCRAMBLE_IGNORE_CHAR '~' #define UNSCRAMBLE_INVALID (-1) #define UNSCRAMBLE_IGNORE (-2) #define UNSCRAMBLE_PAD_CHAR (-3) static const char scramble_table[] = "0123456789AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz-_"; #define _b(n) \ ((n) == '0' ? 0 : (n) == '1' ? 1 : (n) == '2' ? 2 : (n) == '3' ? 3 : \ (n) == '4' ? 4 : (n) == '5' ? 5 : (n) == '6' ? 6 : (n) == '7' ? 7 : \ (n) == '8' ? 8 : (n) == '9' ? 9 : (n) == 'A' ? 10 : (n) == 'a' ? 11 : \ (n) == 'B' ? 12 : (n) == 'b' ? 13 : (n) == 'C' ? 14 : (n) == 'c' ? 15 : \ (n) == 'D' ? 16 : (n) == 'd' ? 17 : (n) == 'E' ? 18 : (n) == 'e' ? 19 : \ (n) == 'F' ? 20 : (n) == 'f' ? 21 : (n) == 'G' ? 22 : (n) == 'g' ? 23 : \ (n) == 'H' ? 24 : (n) == 'h' ? 25 : (n) == 'I' ? 26 : (n) == 'i' ? 27 : \ (n) == 'J' ? 28 : (n) == 'j' ? 29 : (n) == 'K' ? 30 : (n) == 'k' ? 31 : \ (n) == 'L' ? 32 : (n) == 'l' ? 33 : (n) == 'M' ? 34 : (n) == 'm' ? 35 : \ (n) == 'N' ? 36 : (n) == 'n' ? 37 : (n) == 'O' ? 38 : (n) == 'o' ? 39 : \ (n) == 'P' ? 40 : (n) == 'p' ? 41 : (n) == 'Q' ? 42 : (n) == 'q' ? 43 : \ (n) == 'R' ? 44 : (n) == 'r' ? 45 : (n) == 'S' ? 46 : (n) == 's' ? 47 : \ (n) == 'T' ? 48 : (n) == 't' ? 49 : (n) == 'U' ? 50 : (n) == 'u' ? 51 : \ (n) == 'V' ? 52 : (n) == 'v' ? 53 : (n) == 'W' ? 54 : (n) == 'w' ? 55 : \ (n) == 'X' ? 56 : (n) == 'x' ? 57 : (n) == 'Y' ? 58 : (n) == 'y' ? 59 : \ (n) == 'Z' ? 60 : (n) == 'z' ? 61 : (n) == '-' ? 62 : (n) == '_' ? 63 : \ (n) == ' ' ? UNSCRAMBLE_IGNORE : \ (n) == '\t' ? UNSCRAMBLE_IGNORE : \ (n) == '\n' ? UNSCRAMBLE_IGNORE : \ (n) == '\r' ? UNSCRAMBLE_IGNORE : \ (n) == SCRAMBLE_IGNORE_CHAR ? UNSCRAMBLE_IGNORE : \ (n) == SCRAMBLE_PAD_CHAR ? UNSCRAMBLE_PAD_CHAR : \ UNSCRAMBLE_INVALID) static const signed char unscramble_table[] = { _b(0), _b(1), _b(2), _b(3), _b(4), _b(5), _b(6), _b(7), _b(8), _b(9), _b(10) , _b(11), _b(12), _b(13), _b(14), _b(15), _b(16), _b(17), _b(18), _b(19), _b(20), _b(21), _b(22), _b(23), _b(24), _b(25), _b(26), _b(27), _b(28), _b(29), _b(30), _b(31), _b(32), _b(33), _b(34), _b(35), _b(36), _b(37), _b(38), _b(39), _b(40), _b(41), _b(42), _b(43), _b(44), _b(45), _b(46), _b(47), _b(48), _b(49), _b(50), _b(51), _b(52), _b(53), _b(54), _b(55), _b(56), _b(57), _b(58), _b(59), _b(60), _b(61), _b(62), _b(63), _b(64), _b(65), _b(66), _b(67), _b(68), _b(69), _b(70), _b(71), _b(72), _b(73), _b(74), _b(75), _b(76), _b(77), _b(78), _b(79), _b(80), _b(81), _b(82), _b(83), _b(84), _b(85), _b(86), _b(87), _b(88), _b(89), _b(90), _b(91), _b(92), _b(93), _b(94), _b(95), _b(96), _b(97), _b(98), _b(99), _b(100), _b(101), _b(102), _b(103), _b(104), _b(105), _b(106), _b(107), _b(108), _b(109), _b(110), _b(111), _b(112), _b(113), _b(114), _b(115), _b(116), _b(117), _b(118), _b(119), _b(120), _b(121), _b(122), _b(123), _b(124), _b(125), _b(126), _b(127), _b(128), _b(129), _b(130), _b(131), _b(132), _b(133), _b(134), _b(135), _b(136), _b(137), _b(138), _b(139), _b(140), _b(141), _b(142), _b(143), _b(144), _b(145), _b(146), _b(147), _b(148), _b(149), _b(150), _b(151), _b(152), _b(153), _b(154), _b(155), _b(156), _b(157), _b(158), _b(159), _b(160), _b(161), _b(162), _b(163), _b(164), _b(165), _b(166), _b(167), _b(168), _b(169), _b(170), _b(171), _b(172), _b(173), _b(174), _b(175), _b(176), _b(177), _b(178), _b(179), _b(180), _b(181), _b(182), _b(183), _b(184), _b(185), _b(186), _b(187), _b(188), _b(189), _b(190), _b(191), _b(192), _b(193), _b(194), _b(195), _b(196), _b(197), _b(198), _b(199), _b(200), _b(201), _b(202), _b(203), _b(204), _b(205), _b(206), _b(207), _b(208), _b(209), _b(210), _b(211), _b(212), _b(213), _b(214), _b(215), _b(216), _b(217), _b(218), _b(219), _b(220), _b(221), _b(222), _b(223), _b(224), _b(225), _b(226), _b(227), _b(228), _b(229), _b(230), _b(231), _b(232), _b(233), _b(234), _b(235), _b(236), _b(237), _b(238), _b(239), _b(240), _b(241), _b(242), _b(243), _b(244), _b(245), _b(246), _b(247), _b(248), _b(249), _b(250), _b(251), _b(252), _b(253), _b(254), _b(255) }; #define UNSCRAMBLE_TABLE_SIZE (sizeof(unscramble_table) / sizeof(unscramble_table[0])) static const unsigned char xor_table[] = { /* Set of bytes 0x00 to 0xFF in random order; each byte in an input string is XORed with successive bytes in this table. */ 0x00, 0xCE, 0xB1, 0x9F, 0xE4, 0xE0, 0xE3, 0x79, 0xA1, 0x3B, 0x4E, 0x89, 0x81, 0x84, 0x43, 0xC8, 0xBE, 0x0F, 0x67, 0x2A, 0xB4, 0xD8, 0xBA, 0x5D, 0x94, 0x06, 0x69, 0x0E, 0x1C, 0x48, 0x9E, 0x0A, 0x1D, 0x09, 0x02, 0xCD, 0xD4, 0xF6, 0x5B, 0x8A, 0xAE, 0x65, 0xB3, 0xB5, 0xA7, 0x13, 0x03, 0xF2, 0x42, 0xF0, 0xA6, 0xAA, 0x35, 0xCB, 0x2C, 0x55, 0xF5, 0xC7, 0x32, 0xB7, 0x6B, 0xEA, 0xC3, 0x6F, 0x41, 0xFF, 0xD1, 0x24, 0x54, 0xA9, 0xC6, 0xC2, 0x74, 0xEE, 0xBC, 0x99, 0x59, 0x71, 0x3D, 0x85, 0x0B, 0xF7, 0x3A, 0x7E, 0xDB, 0x45, 0xE8, 0x96, 0xD0, 0xC1, 0xE6, 0xFD, 0x86, 0x8C, 0x9B, 0x0C, 0x66, 0x5F, 0xE5, 0x14, 0x98, 0x3C, 0xBD, 0xE2, 0x88, 0xA3, 0x30, 0x38, 0x2F, 0xA2, 0x37, 0x70, 0xB8, 0x11, 0x61, 0x93, 0x52, 0x1B, 0xDD, 0x20, 0x60, 0x19, 0xEF, 0xD2, 0xEC, 0x73, 0x07, 0x92, 0x4C, 0x6A, 0xA8, 0x9D, 0x34, 0x04, 0x87, 0x2E, 0x1E, 0xA4, 0xCA, 0x72, 0x63, 0xD7, 0x7F, 0xFB, 0x68, 0xE1, 0xBF, 0x10, 0x8E, 0xAF, 0x9A, 0xFA, 0xA0, 0xDE, 0x1F, 0x31, 0x15, 0x97, 0xED, 0x2B, 0x36, 0x8D, 0x12, 0xC5, 0x23, 0x95, 0x33, 0x56, 0x4F, 0xE7, 0xAD, 0x5C, 0x4B, 0x83, 0xDC, 0x29, 0xE9, 0xCF, 0x8F, 0x58, 0x4D, 0x5A, 0x08, 0x49, 0xFC, 0x6D, 0x7C, 0xB6, 0xD3, 0x7B, 0xD6, 0x53, 0x57, 0x82, 0x0D, 0xD9, 0x7D, 0xDA, 0x4A, 0xDF, 0x27, 0x40, 0x1A, 0x22, 0xC9, 0x51, 0x3E, 0x6C, 0xC4, 0x18, 0xCC, 0xAC, 0xEB, 0xA5, 0xF4, 0x44, 0xFE, 0x76, 0xF8, 0x75, 0xF3, 0x2D, 0xB0, 0xB9, 0x9C, 0x47, 0x7A, 0x28, 0xBB, 0xF1, 0x16, 0x64, 0x46, 0x21, 0x78, 0x90, 0xD5, 0x80, 0x3F, 0x39, 0x25, 0xB2, 0x6E, 0x8B, 0x77, 0xC0, 0x05, 0x50, 0x17, 0xF9, 0x01, 0x26, 0x91, 0x5E, 0x62, 0xAB }; #define XOR_TABLE_SIZE (sizeof(xor_table) / sizeof(xor_table[0])) /************************************************************************ * Module-specific variables * ************************************************************************/ static char *home_directory_str = NULL; // Full pathname to home static char *data_directory_str = NULL; // Writable data dir pathname static bool is_posix_locale = false; // Override strfmon()? /************************************************************************ * Module-specific function prototypes * ************************************************************************/ /* Function: apply_xor - Scramble a buffer using xor_table Parameters: dest - Location of destination buffer src - Location of source buffer n - Number of bytes to scramble key - Pointer to xor_table index Returns: (nothing) This function copies n bytes from *src into *dest, applying a XOR with the contents of xor_table in the process. It is a reversible function: apply_xor(apply_xor(buffer)) == buffer. It is used by both scramble() and unscramble(). */ static void apply_xor (char *restrict dest, const char *restrict src, size_t n, unsigned int *restrict key); /* Function: b64encode - Convert a block to non-standard Base64 encoding Parameters: in - Location of input buffer inlen - Size of input buffer out - Location of output buffer outlen - Size of output buffer Returns: size_t - Number of bytes placed in output buffer This function encodes inlen bytes in the input buffer into the output buffer using a non-standard Base64 encoding (as contained above in scramble_table[]). The resulting encoded string length is returned (including trailing '\n' but NOT including trailing NUL). Note that the output buffer must be at least 4/3 the size of the input buffer; if not, an assert is generated. This function is used by scramble(). */ static size_t b64encode (const void *restrict in, size_t inlen, void *restrict out, size_t outlen); /* Function: b64decode - Convert a block from non-standard Base64 encoding Parameters: in - Location of input buffer inlen - Size of input buffer out - Location of output buffer outlen - Size of output buffer Returns: ssize_t - Number of bytes placed in output buffer, or -1 This function decodes up to inlen bytes in the input buffer into the output buffer using a non-standard Base64 encoding (as contained above in unscramble_table[]). The resulting decoded buffer length is returned; that buffer may contain NUL bytes. If an error occurs during decoding, -1 is returned instead. This function is used by unscramble(). */ static ssize_t b64decode (const void *restrict in, size_t inlen, void *restrict out, size_t outlen); /************************************************************************ * Initialisation and environment function definitions * ************************************************************************/ // These functions are documented in the file "utils.h" /***********************************************************************/ // init_program_name: Make the program name canonical void init_program_name (const char *argv0) { /* This implementation assumes a POSIX environment with an ASCII-safe character encoding (such as ASCII or UTF-8). */ if (argv0 == NULL || *argv0 == '\0') { program_name = PACKAGE; } else { char *p = strrchr(argv0, '/'); if (p != NULL && *++p != '\0') { program_name = xstrdup(p); } else { program_name = xstrdup(argv0); } } } /***********************************************************************/ // home_directory: Return home directory pathname const char *home_directory (void) { if (home_directory_str == NULL) { // Use the HOME environment variable where possible const char *home = getenv("HOME"); if (home != NULL && *home != '\0') { home_directory_str = xstrdup(home); } } return home_directory_str; } /***********************************************************************/ // data_directory: Return writable data directory pathname const char *data_directory (void) { /* This implementation assumes a POSIX environment by using "/" as the directory separator. It also assumes a dot-starting directory name is permissible (again, true on POSIX systems) and that the character encoding is ASCII-safe. */ if (data_directory_str == NULL) { const char *home = home_directory(); if (program_name != NULL && home != NULL) { char *p = xmalloc(strlen(home) + strlen(program_name) + 3); strcpy(p, home); strcat(p, "/."); strcat(p, program_name); data_directory_str = p; } } return data_directory_str; } /***********************************************************************/ // game_filename: Convert an integer to a game filename char *game_filename (int gamenum) { /* This implementation assumes a POSIX environment and an ASCII-safe character encoding. */ char buf[GAME_FILENAME_BUFSIZE]; // Buffer for part of filename const char *dd; // Data directory if (gamenum < 1 || gamenum > 9) { return NULL; } dd = data_directory(); snprintf(buf, GAME_FILENAME_BUFSIZE, GAME_FILENAME_PROTO, gamenum); if (dd == NULL) { return xstrdup(buf); } else { char *p = xmalloc(strlen(dd) + strlen(buf) + 2); strcpy(p, dd); strcat(p, "/"); strcat(p, buf); return p; } } /************************************************************************ * Error-reporting function definitions * ************************************************************************/ // These functions are documented in the file "utils.h" /***********************************************************************/ // err_exit: Print an error and exit void err_exit (const char *restrict format, ...) { va_list args; end_screen(); fprintf(stderr, _("%s: "), program_name); va_start(args, format); vfprintf(stderr, format, args); va_end(args); fputs("\n", stderr); exit(EXIT_FAILURE); } /***********************************************************************/ // errno_exit: Print an error message (using errno) and exit void errno_exit (const char *restrict format, ...) { va_list args; int saved_errno = errno; end_screen(); fprintf(stderr, _("%s: "), program_name); if (format != NULL) { va_start(args, format); vfprintf(stderr, format, args); va_end(args); fputs(_(": "), stderr); } fprintf(stderr, "%s\n", strerror(saved_errno)); exit(EXIT_FAILURE); } /***********************************************************************/ // err_exit_nomem: Print an "out of memory" error and exit void err_exit_nomem (void) { err_exit(_("out of memory")); } /************************************************************************ * Random-number function definitions * ************************************************************************/ // These functions are documented in the file "utils.h" /***********************************************************************/ // init_rand: Initialise the random number generator void init_rand (void) { /* Ideally, initialisation of the random number generator should be made using seed48() and lcong48(). However, since this is "only a game", 32 bits of "randomness" as returned by gettimeofday() is probably more than enough... */ struct timeval tv; unsigned long int seed; gettimeofday(&tv, NULL); // If this fails, tv is random enough! seed = tv.tv_sec + tv.tv_usec; srand48(seed); } /***********************************************************************/ // randf: Return a random number between 0.0 and 1.0 extern double randf (void) { return drand48(); } /***********************************************************************/ // randi: Return a random number between 0 and limit extern int randi (int limit) { return drand48() * (double) limit; } /************************************************************************ * Locale-aware function definitions * ************************************************************************/ // These functions are documented in the file "utils.h" /***********************************************************************/ // init_locale: Initialise locale-specific variables void init_locale (void) { char *cur, *cloc; struct lconv *lc; wchar_t *buf; cur = xstrdup(setlocale(LC_MONETARY, NULL)); lc = localeconv(); assert(lc != NULL); is_posix_locale = false; lconvinfo = *lc; /* Are we in the POSIX locale? The string returned by setlocale() is supposed to be opaque, but in practise is not. To be on the safe side, we explicitly set the locale to "C", then test the returned value of that, too. */ cloc = setlocale(LC_MONETARY, "C"); if ( strcmp(cur, cloc) == 0 || strcmp(cur, "POSIX") == 0 || strcmp(cur, "POSIX.UTF-8") == 0 || strcmp(cur, "POSIX.utf8") == 0 || strcmp(cur, "C") == 0 || strcmp(cur, "C.UTF-8") == 0 || strcmp(cur, "C.utf8") == 0) { is_posix_locale = true; lconvinfo = mod_posix_lconv; } // Convert localeconv() information to wide strings buf = xmalloc(BUFSIZE * sizeof(wchar_t)); xmbstowcs(buf, lconvinfo.decimal_point, BUFSIZE); decimal_point = xwcsdup(buf); xmbstowcs(buf, lconvinfo.thousands_sep, BUFSIZE); thousands_sep = xwcsdup(buf); xmbstowcs(buf, lconvinfo.currency_symbol, BUFSIZE); currency_symbol = xwcsdup(buf); xmbstowcs(buf, lconvinfo.mon_decimal_point, BUFSIZE); mon_decimal_point = xwcsdup(buf); xmbstowcs(buf, lconvinfo.mon_thousands_sep, BUFSIZE); mon_thousands_sep = xwcsdup(buf); free(buf); setlocale(LC_MONETARY, cur); free(cur); } /***********************************************************************/ // xwcsfmon: Convert monetary value to a wide-character string ssize_t xwcsfmon (wchar_t *restrict buf, size_t maxsize, const char *restrict format, double val) { ssize_t n; char *s = xmalloc(BUFSIZE); /* Current and previous versions of ISO/IEC 9945-1 (POSIX), namely SUSv3 (2001) and SUSv4 (2008), require strfmon() to return rather meaningless strings when used with the POSIX "C" locale. In particular, the standard POSIX locale does not define a currency symbol, a monetary radix symbol (decimal point) or a negative sign. This means strfmon(..., "%n", -123.45) is supposed to produce "12345" instead of something like "-$123.45"! The following code overcomes these limitations by using snprintf(). */ if (! is_posix_locale) { n = strfmon(s, BUFSIZE, format, val); } else { /* The current implementation assumes the monetary decimal point is overridden to "." (ie, MOD_POSIX_MON_DECIMAL_POINT == "."), the currency symbol is to precede the value, no spaces are to separate currency symbol and value, and sign is to precede both currency symbol and value. */ assert(MOD_POSIX_P_CS_PRECEDES == 1); assert(MOD_POSIX_P_SEP_BY_SPACE == 0); assert(MOD_POSIX_N_CS_PRECEDES == 1); assert(MOD_POSIX_N_SEP_BY_SPACE == 0); assert(MOD_POSIX_P_SIGN_POSN == 1); assert(MOD_POSIX_N_SIGN_POSN == 1); # define MOD_POSIX_q(s) MOD_POSIX_qq(s) # define MOD_POSIX_qq(s) #s # define MOD_POSIX_FMT_POS MOD_POSIX_POSITIVE_SIGN MOD_POSIX_CURRENCY_SYMBOL "%." MOD_POSIX_q(MOD_POSIX_FRAC_DIGITS) "f" # define MOD_POSIX_FMT_NEG MOD_POSIX_NEGATIVE_SIGN MOD_POSIX_CURRENCY_SYMBOL "%." MOD_POSIX_q(MOD_POSIX_FRAC_DIGITS) "f" # define MOD_POSIX_FMT_POS_NOSYM MOD_POSIX_POSITIVE_SIGN "%." MOD_POSIX_q(MOD_POSIX_FRAC_DIGITS) "f" # define MOD_POSIX_FMT_NEG_NOSYM MOD_POSIX_NEGATIVE_SIGN "%." MOD_POSIX_q(MOD_POSIX_FRAC_DIGITS) "f" if (strcmp(format, "%n") == 0) { if (val >= 0.0) { n = snprintf(s, BUFSIZE, MOD_POSIX_FMT_POS, val); } else { n = snprintf(s, BUFSIZE, MOD_POSIX_FMT_NEG, -val); } } else if (strcmp(format, "%!n") == 0) { if (val >= 0.0) { n = snprintf(s, BUFSIZE, MOD_POSIX_FMT_POS_NOSYM, val); } else { n = snprintf(s, BUFSIZE, MOD_POSIX_FMT_NEG_NOSYM, -val); } } else { // Other strfmon() formats are not supported errno = EINVAL; n = -1; } } if (n >= BUFSIZE) { // Truncate the too-long output with a terminating NUL s[BUFSIZE - 1] = '\0'; } if (n >= 0) { xmbstowcs(buf, s, maxsize); /* Some buggy implementations of strfmon(), such as that on FreeBSD and Cygwin, assume localeconv.mon_thousands_sep and similar strings contain either a single char or NUL instead of a multibyte character string. However, this assumption fails on locales such as ru_RU.UTF-8 which use U+00A0 NO-BREAK SPACE for mon_thousands_sep (stored in UTF-8 as 0xC2 0xA0. As a result, incomplete character sequences are copied, which are translated to EILSEQ_REPL_WC characters by xmbstowcs() above. Fix such characters by replacing them with a space. */ for (wchar_t *p = buf; *p != L'\0'; p++) { if (*p == EILSEQ_REPL_WC) { *p = L' '; } } } free(s); return n; } /************************************************************************ * Encryption function definitions * ************************************************************************/ /* These functions are documented in the file "utils.h" or in the comments above. */ /***********************************************************************/ // scramble: Scramble (encrypt) the buffer char *scramble (char *restrict dest, const char *restrict src, size_t size, unsigned int *restrict key) { unsigned long int crc; unsigned int chksum; size_t srclen; char *xorbuf, *midxor; char *middest; char crcbuf[SCRAMBLE_CRC_LEN + 1]; char chksumbuf[SCRAMBLE_CHKSUM_LEN + 1]; assert(dest != NULL); assert(src != NULL); assert(size > 0); srclen = strlen(src); if (key == NULL) { // No encryption required assert(size >= srclen + 2); // Enough room to add "\n"? strcpy(dest, src); // Add "\n" if needed if (dest[srclen - 1] != '\n') { dest[srclen] = '\n'; dest[srclen + 1] = '\0'; } } else { // Scramble the input xorbuf = xmalloc(srclen + SCRAMBLE_CRC_LEN + 1); // Scramble src using *key, leaving room for CRC32 in front midxor = xorbuf + SCRAMBLE_CRC_LEN; apply_xor(midxor, src, srclen, key); // Calculate CRC32 checksum of XORed buffer crc = crc32(midxor, srclen) & SCRAMBLE_CRC_MASK; snprintf(crcbuf, SCRAMBLE_CRC_LEN + 1, "%08lx", crc); memcpy(xorbuf, crcbuf, SCRAMBLE_CRC_LEN); // Encode whole buffer (including CRC32) using Base64 middest = dest + SCRAMBLE_CHKSUM_LEN; b64encode(xorbuf, srclen + SCRAMBLE_CRC_LEN, middest, size - SCRAMBLE_CHKSUM_LEN); // Calculate simple checksum chksum = 0; for (char *p = middest; *p != '\0' && *p != '\n'; p++) { chksum += *p; } chksum &= SCRAMBLE_CHKSUM_MASK; // Place checksum in front of Base64 string snprintf(chksumbuf, SCRAMBLE_CHKSUM_LEN + 1, "%03x", chksum); memcpy(dest, chksumbuf, SCRAMBLE_CHKSUM_LEN); free(xorbuf); } return dest; } /***********************************************************************/ // unscramble: Unscramble (decrypt) the buffer char *unscramble (char *restrict dest, const char *restrict src, size_t size, unsigned int *restrict key) { unsigned long int crc, crc_input; unsigned int chksum, chksum_input; size_t srclen; char *xorbuf, *midxor; ssize_t xorlen; const char *midsrc; char crcbuf[SCRAMBLE_CRC_LEN + 2]; // Leave room for '\n\0' char chksumbuf[SCRAMBLE_CHKSUM_LEN + 2]; assert(dest != NULL); assert(src != NULL); assert(size > 0); srclen = strlen(src); if (key == NULL) { // No decryption required assert(size >= srclen + 1); strcpy(dest, src); } else { // Unscramble the input // Copy out simple checksum from input memcpy(chksumbuf, src, SCRAMBLE_CHKSUM_LEN); chksumbuf[SCRAMBLE_CHKSUM_LEN] = '\n'; chksumbuf[SCRAMBLE_CHKSUM_LEN + 1] = '\0'; if (sscanf(chksumbuf, "%x\n", &chksum_input) != 1) { return NULL; } // Calculate and compare checksums midsrc = src + SCRAMBLE_CHKSUM_LEN; chksum = 0; for (const char *p = midsrc; *p != '\0' && *p != '\n'; p++) { chksum += *p; } chksum &= SCRAMBLE_CHKSUM_MASK; if (chksum != chksum_input) { return NULL; } xorbuf = xmalloc(size + SCRAMBLE_CRC_LEN); // Decode buffer sans checksum using Base64 xorlen = b64decode(midsrc, srclen - SCRAMBLE_CHKSUM_LEN, xorbuf, size + SCRAMBLE_CRC_LEN); if (xorlen < SCRAMBLE_CRC_LEN) { free(xorbuf); return NULL; } // Copy out CRC32 checksum memcpy(crcbuf, xorbuf, SCRAMBLE_CRC_LEN); crcbuf[SCRAMBLE_CRC_LEN] = '\n'; crcbuf[SCRAMBLE_CRC_LEN + 1] = '\0'; if (sscanf(crcbuf, "%lx\n", &crc_input) != 1) { free(xorbuf); return NULL; } // Calculate and compare CRC32 checksums midxor = xorbuf + SCRAMBLE_CRC_LEN; crc = crc32(midxor, xorlen - SCRAMBLE_CRC_LEN) & SCRAMBLE_CRC_MASK; if (crc != crc_input) { free(xorbuf); return NULL; } // Unscramble xorbuf using *key, ignoring CRC32 in front apply_xor(dest, midxor, xorlen - SCRAMBLE_CRC_LEN, key); // Convert the output to a C string assert(size >= (size_t) xorlen - SCRAMBLE_CRC_LEN + 1); dest[xorlen - SCRAMBLE_CRC_LEN] = '\0'; free(xorbuf); } return dest; } /***********************************************************************/ // apply_xor: Scramble a buffer using xor_table void apply_xor (char *restrict dest, const char *restrict src, size_t n, unsigned int *restrict key) { assert(dest != NULL); assert(src != NULL); assert(key != NULL); assert(*key < XOR_TABLE_SIZE); for (size_t i = 0; i < n; i++, dest++, src++) { *(unsigned char *) dest = *(unsigned char *) src ^ xor_table[*key]; *key = (*key + 1) % XOR_TABLE_SIZE; } } /***********************************************************************/ // b64encode: Convert a block to non-standard Base64 encoding size_t b64encode (const void *restrict in, size_t inlen, void *restrict out, size_t outlen) { size_t count; size_t padding; // Note that bit manipulations on strings require unsigned char! const unsigned char *u_in = in; unsigned char *u_out = out; assert(u_in != NULL); assert(u_out != NULL); assert(outlen > 0); assert(outlen > inlen); count = 0; padding = inlen % 3; for (size_t i = 0; i < inlen; i += 3, u_in += 3) { unsigned long int n; unsigned char n0, n1, n2, n3; // Convert three input bytes into a 24-bit number n = u_in[0] << 16; if (i + 1 < inlen) { n += u_in[1] << 8; } if (i + 2 < inlen) { n += u_in[2]; } // Convert the 24-bit number into four Base64 bytes n0 = (unsigned char) (n >> 18) & 0x3F; n1 = (unsigned char) (n >> 12) & 0x3F; n2 = (unsigned char) (n >> 6) & 0x3F; n3 = (unsigned char) n & 0x3F; assert(count + 3 < outlen); *u_out++ = scramble_table[n0]; *u_out++ = scramble_table[n1]; count += 2; if (i + 1 < inlen) { *u_out++ = scramble_table[n2]; count++; } if (i + 2 < inlen) { *u_out++ = scramble_table[n3]; count++; } } if (padding > 0) { assert(count + 2 < outlen); for (; padding < 3; padding++) { *u_out++ = SCRAMBLE_PAD_CHAR; count++; } } assert(count + 2 <= outlen); *u_out++ = '\n'; *u_out = '\0'; count++; return count; } /***********************************************************************/ // b64decode: Convert a block from non-standard Base64 encoding ssize_t b64decode (const void *restrict in, size_t inlen, void *restrict out, size_t outlen) { size_t count; unsigned long int n; // Note that bit manipulations on strings require unsigned char! // Using char * results in very subtle bugs indeed... const unsigned char *u_in = in; unsigned char *u_out = out; assert(u_in != NULL); assert(u_out != NULL); assert(outlen > 0); assert(UNSCRAMBLE_TABLE_SIZE == UCHAR_MAX + 1); count = 0; n = 1; for (size_t i = 0; i < inlen && *u_in != '\0'; i++, u_in++) { int v = unscramble_table[*u_in]; switch (v) { case UNSCRAMBLE_INVALID: return -1; case UNSCRAMBLE_IGNORE: continue; case UNSCRAMBLE_PAD_CHAR: // Assume end of data i = inlen; continue; default: n = n << 6 | v; // v is 0 .. 63 if (n & 0x1000000) { // Convert 24-bit number into three output bytes count += 3; if (count > outlen) { return -1; } *u_out++ = n >> 16; *u_out++ = n >> 8; *u_out++ = n; n = 1; } } } if (n & 0x40000) { count += 2; if (count > outlen) { return -1; } *u_out++ = n >> 10; *u_out++ = n >> 2; } else if (n & 0x1000) { count += 1; if (count > outlen) { return -1; } *u_out++ = n >> 4; } return count; } /************************************************************************ * Miscellaneous function definitions * ************************************************************************/ // These functions are documented in the file "utils.h" /***********************************************************************/ // xmalloc: Allocate a new block of memory, with checking void *xmalloc (size_t size) { void *p; if (size < 1) size = 1; p = malloc(size); if (p == NULL) { err_exit_nomem(); } return p; } /***********************************************************************/ // xstrdup: Duplicate a string, with checking char *xstrdup (const char *str) { char *s; if (str == NULL) str = ""; s = strdup(str); if (s == NULL) { err_exit_nomem(); } return s; } /***********************************************************************/ // chstrdup: Duplicate a chtype buffer chtype *xchstrdup (const chtype *restrict chstr) { const chtype *p; int len; chtype *ret; // Determine chstr length, including ending NUL for (len = 1, p = chstr; *p != '\0'; p++, len++) ; ret = xmalloc(len * sizeof(chtype)); memcpy(ret, chstr, len * sizeof(chtype)); ret[len - 1] = '\0'; // Terminating NUL, just in case not present return ret; } /***********************************************************************/ // xwcsdup: Duplicate a wide-character string, with checking wchar_t *xwcsdup (const wchar_t *str) { wchar_t *s; if (str == NULL) str = L""; s = wcsdup(str); if (s == NULL) { err_exit_nomem(); } return s; } /***********************************************************************/ // xmbstowcs: Convert a multibyte string to a wide-character string size_t xmbstowcs (wchar_t *restrict dest, const char *restrict src, size_t len) { assert(dest != NULL); assert(len > 0); char *s = xstrdup(src); size_t n; while (true) { mbstate_t mbstate; char *p = s; memset(&mbstate, 0, sizeof(mbstate)); if ((n = mbsrtowcs(dest, (const char **) &p, len, &mbstate)) == (size_t) -1) { if (errno == EILSEQ) { // Illegal sequence detected: replace it and try again *p = EILSEQ_REPL; } else { errno_exit(_("xmbstowcs: '%s'"), src); } } else if (p != NULL) { // Multibyte string was too long: truncate dest dest[len - 1] = L'\0'; n--; break; } else { break; } } free(s); return n; } /***********************************************************************/ // xwcrtomb: Convert a wide character to a multibyte sequence size_t xwcrtomb (char *restrict dest, wchar_t wc, mbstate_t *restrict mbstate) { mbstate_t mbcopy; size_t n; assert(dest != NULL); assert(mbstate != NULL); memcpy(&mbcopy, mbstate, sizeof(mbcopy)); if ((n = wcrtomb(dest, wc, &mbcopy)) == (size_t) -1) { if (errno == EILSEQ) { /* wc cannot be represented in current locale. Note that the shift state in mbcopy is now undefined. Hence, restore the original, try to store an ending shift sequence, then EILSEQ_REPL. */ memcpy(&mbcopy, mbstate, sizeof(mbcopy)); if ((n = wcrtomb(dest, L'\0', &mbcopy)) == (size_t) -1) { errno_exit(_("xwcrtomb: NUL")); } dest[n++] = EILSEQ_REPL; dest[n] = '\0'; } else { errno_exit(_("xwcrtomb: '%lc'"), (wint_t) wc); } } memcpy(mbstate, &mbcopy, sizeof(mbcopy)); return n; } /***********************************************************************/ // End of file