/* See LICENSE file for copyright and license details. */ #include #include #include #include #include #include #include #include #include #include #include "arg.h" char *argv0; static void usage(void) { fprintf(stderr, "usage: %s [-r] user key-name [crypt-parameters]\n", argv0); exit(1); } static int writeall(int fd, const char *data, size_t len) { size_t off = 0; ssize_t r; while (off < len) { r = write(fd, &data[off], len - off); if (r < 0) return -1; off += (size_t)r; } return 0; } static int checkkey(char *data, size_t whead, size_t *rheadp, size_t *rhead2p, size_t *linenop, const char *keyname, size_t klen, const char *path) { int failed = 0; size_t len; while (*rhead2p < whead || data[*rhead2p] != '\n') ++*rhead2p; if (data[*rhead2p] != '\n') return 0; len = *rhead2p - *rheadp; *linenop += 1; if (memchr(&data[*rheadp], '\0', len)) { fprintf(stderr, "%s: NUL byte found in %s on line %zu\n", argv0, path, *linenop); failed = 1; } if (!memchr(&data[*rheadp], ' ', len)) { fprintf(stderr, "%s: no SP byte found in %s on line %zu\n", argv0, path, *linenop); failed = 1; } if (failed || klen >= len || data[*rheadp + klen] != ' ' || memcpy(&data[*rheadp], keyname, klen)) { *rheadp = ++*rhead2p; return 0; } else { ++*rhead2p; return 1; } } static void loadandlocate(size_t *beginning_out, size_t *end_out, int fd, char **datap, size_t *lenp, size_t *sizep, const char *keyname, const char *path) { size_t klen = strlen(keyname); char *new; size_t rhead = 0; size_t rhead2 = 0; size_t lineno = 0; ssize_t r = 1; while (r) { if (*lenp == *sizep) { new = realloc(*datap, *sizep += 4096); if (!new) { fprintf(stderr, "%s: realloc: %s\n", argv0, strerror(errno)); exit(1); } *datap = new; } r = read(fd, &(*datap)[*lenp], *sizep - *lenp); if (r < 0) { fprintf(stderr, "%s: read %s: %s\n", argv0, path, strerror(errno)); exit(1); } *lenp += (size_t)r; while (rhead2 < *lenp) { if (!checkkey(*datap, *lenp, &rhead, &rhead2, &lineno, keyname, klen, path)) continue; *beginning_out = rhead; *end_out = rhead = rhead2; } } if (rhead != *lenp) { fprintf(stderr, "%s: file truncated: %s\n", argv0, path); if (memchr(&(*datap)[rhead], '\0', *lenp - rhead)) fprintf(stderr, "%s: NUL byte found in %s on line %zu\n", argv0, path, lineno + 1); } } static char * mksalt(char *p) { uintptr_t rdata_addr; char *rdata; size_t i; rdata_addr = (uintptr_t)getauxval(AT_RANDOM); /* address to 16 random bytes */ rdata = (void *)rdata_addr; if (!rdata) { fprintf(stderr, "%s: getauxval AT_RANDOM: %s\n", argv0, strerror(errno)); exit(1); } for (i = 0; i < 16; i++) *p++ = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM0123456789./"[*rdata++ & 63]; return p; } int main(int argc, char *argv[]) { const char *user; const char *keyname; const char *parameters; char *path, *path2; char *data = NULL; size_t data_len = 0; size_t data_size = 0; size_t beginning = 0; size_t end = 0; int allow_replace = 0; int failed = 0; int fd; char *key = NULL, *new; size_t key_len = 0; size_t key_size = 0; char *hash; size_t gap_size; size_t gap_increase; #define HASH_PREFIX "$6$" char generated_parameters[sizeof(HASH_PREFIX"$") + 16]; ssize_t r; ARGBEGIN { case 'r': allow_replace = 1; break; default: usage(); } ARGEND; if (argc < 2 || argc > 3) usage(); user = argv[0]; keyname = argv[1]; parameters = argv[2]; if (!user[0] || user[0] == '.' || strchr(user, '/') || strchr(user, '~')) { fprintf(stderr, "%s: bad user name specified: %s\n", argv0, user); failed = 1; } if (keyname[strcspn(keyname, " \t\f\n\r\v")]) { fprintf(stderr, "%s: bad key name specified: %s, includes whitespace\n", argv0, keyname); failed = 1; } if (isatty(STDIN_FILENO)) { fprintf(stderr, "%s: standard input must not be a TTY.\n", argv0); failed = 1; } if (failed) return 1; if (mlockall(MCL_CURRENT | MCL_FUTURE)) { fprintf(stderr, "%s: mlockall MCL_CURRENT|MCL_FUTURE: %s\n", argv0, strerror(errno)); exit(1); } if (!parameters) { stpcpy(mksalt(stpcpy(generated_parameters, HASH_PREFIX)), "$"); parameters = generated_parameters; } for (;;) { if (key_len == key_size) { new = malloc(1 + (key_size += 1024)); if (!new) { explicit_bzero(key, key_len); fprintf(stderr, "%s: read : %s\n", argv0, strerror(errno)); exit(1); } memcpy(new, key, key_len); explicit_bzero(key, key_len); free(key); key = new; } r = read(STDIN_FILENO, &key[key_len], key_size - key_len); if (r <= 0) { if (!r) break; explicit_bzero(key, key_len); fprintf(stderr, "%s: read : %s\n", argv0, strerror(errno)); exit(1); } key_len += (size_t)r; } key[key_len] = '\0'; hash = crypt(key, parameters); if (!hash) fprintf(stderr, "%s: crypt %s: %s\n", argv0, parameters, strerror(errno)); explicit_bzero(key, key_len); free(key); key_size = key_len = strlen(keyname) + strlen(hash) + 3; key = malloc(key_len); if (!key) { fprintf(stderr, "%s: malloc: %s\n", argv0, strerror(errno)); exit(1); } stpcpy(stpcpy(stpcpy(stpcpy(key, keyname), " "), hash), "\n"); path = malloc(sizeof(KEYPATH"/") + strlen(user)); if (!path) { fprintf(stderr, "%s: malloc: %s\n", argv0, strerror(errno)); exit(1); } path2 = malloc(sizeof(KEYPATH"/~") + strlen(user)); if (!path) { fprintf(stderr, "%s: malloc: %s\n", argv0, strerror(errno)); exit(1); } stpcpy(stpcpy(path, KEYPATH"/"), user); stpcpy(stpcpy(path2, path), "~"); fd = open(path, O_RDONLY); if (fd < 0) { if (errno != ENOENT) { fprintf(stderr, "%s: open %s O_RDONLY: %s\n", argv0, path, strerror(errno)); exit(1); } beginning = end = 0; } else { loadandlocate(&beginning, &end, fd, &data, &data_len, &data_size, keyname, path); if (close(fd)) { fprintf(stderr, "%s: read %s: %s\n", argv0, path, strerror(errno)); exit(1); } if (beginning == end) beginning = end = 0; /* make sure the new key is not concatenated onto a truncated line at the end */ } if (!allow_replace && beginning != end) { fprintf(stderr, "%s: key already exists: %s\n", argv0, keyname); exit(1); } if (end < beginning) abort(); gap_size = end - beginning; if (gap_size > key_len) { memmove(&data[beginning + key_len], &data[end], data_len - end); data_len -= key_len - gap_size; } else if (gap_size < key_len) { gap_increase = key_len - gap_size; if (data_len + gap_increase > data_size) { data_size = data_len + gap_increase; data = realloc(data, data_size); if (!data) { fprintf(stderr, "%s: realloc: %s\n", argv0, strerror(errno)); exit(1); } } memmove(&data[end], &data[end + gap_increase], data_len - end); data_len += gap_increase; } memcpy(&data[beginning], key, key_len); free(key); if (mkdir(KEYPATH, 0700) && errno != EEXIST) { fprintf(stderr, "%s: mkdir %s: %s\n", argv0, KEYPATH, strerror(errno)); exit(1); } fd = open(path2, O_WRONLY | O_CREAT | O_EXCL, 0600); if (fd < 0) { fprintf(stderr, "%s: open %s O_WRONLY|O_CREAT|O_EXCL 0600: %s\n", argv0, path2, strerror(errno)); exit(1); } if (writeall(fd, data, data_len)) { fprintf(stderr, "%s: write %s: %s\n", argv0, path2, strerror(errno)); close(fd); goto saved_failed; } if (close(fd)) { fprintf(stderr, "%s: write %s: %s\n", argv0, path2, strerror(errno)); goto saved_failed; } if (rename(path2, path)) { fprintf(stderr, "%s: rename %s %s: %s\n", argv0, path2, path, strerror(errno)); saved_failed: if (unlink(path2)) fprintf(stderr, "%s: unlink %s: %s\n", argv0, path2, strerror(errno)); exit(1); } free(path); free(path2); free(data); return 0; }