| /* |
| * Copyright (C) 2001-2008 Marco d'Itri |
| * |
| * 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 2 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, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| |
| /* for crypt, snprintf and strcasecmp */ |
| #define _XOPEN_SOURCE |
| /* |
| * _BSD_SOURCE is deprecated as of GLIBC 2.20; _DEFAULT_SOURCE should be used |
| * instead. (https://lwn.net/Articles/611162/) |
| */ |
| #define _DEFAULT_SOURCE |
| #define _BSD_SOURCE |
| |
| /* System library */ |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include "config.h" |
| #ifdef HAVE_GETOPT_LONG |
| #include <getopt.h> |
| #endif |
| #include <fcntl.h> |
| #include <string.h> |
| #include <time.h> |
| #include <sys/types.h> |
| #ifdef HAVE_XCRYPT |
| #include <xcrypt.h> |
| #include <sys/stat.h> |
| #endif |
| #ifdef HAVE_LINUX_CRYPT_GENSALT |
| #define _OW_SOURCE |
| #include <crypt.h> |
| #endif |
| #ifdef HAVE_GETTIMEOFDAY |
| #include <sys/time.h> |
| #endif |
| |
| /* glibc without crypt() */ |
| #ifndef _XOPEN_CRYPT |
| #include <crypt.h> |
| #endif |
| |
| /* Application-specific */ |
| #include "utils.h" |
| |
| /* Global variables */ |
| #ifdef HAVE_GETOPT_LONG |
| static const struct option longopts[] = { |
| {"method", optional_argument, NULL, 'm'}, |
| /* for backward compatibility with versions < 4.7.25 (< 20080321): */ |
| {"hash", optional_argument, NULL, 'H'}, |
| {"help", no_argument, NULL, 'h'}, |
| {"password-fd", required_argument, NULL, 'P'}, |
| {"stdin", no_argument, NULL, 's'}, |
| {"salt", required_argument, NULL, 'S'}, |
| {"rounds", required_argument, NULL, 'R'}, |
| {"version", no_argument, NULL, 'V'}, |
| {NULL, 0, NULL, 0 } |
| }; |
| #else |
| extern char *optarg; |
| extern int optind; |
| #endif |
| |
| static const char valid_salts[] = "abcdefghijklmnopqrstuvwxyz" |
| "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./"; |
| |
| struct crypt_method { |
| const char *method; /* short name used by the command line option */ |
| const char *prefix; /* salt prefix */ |
| const unsigned int minlen; /* minimum salt length */ |
| const unsigned int maxlen; /* maximum salt length */ |
| const unsigned int rounds; /* supports a variable number of rounds */ |
| const char *desc; /* long description for the methods list */ |
| }; |
| |
| static const struct crypt_method methods[] = { |
| /* method prefix minlen, maxlen rounds description */ |
| { "des", "", 2, 2, 0, |
| N_("standard 56 bit DES-based crypt(3)") }, |
| { "md5", "$1$", 8, 8, 0, "MD5" }, |
| #if defined OpenBSD || defined FreeBSD || (defined __SVR4 && defined __sun) |
| { "bf", "$2a$", 22, 22, 1, "Blowfish" }, |
| #endif |
| #if defined HAVE_LINUX_CRYPT_GENSALT |
| { "bf", "$2a$", 22, 22, 1, "Blowfish, system-specific on 8-bit chars" }, |
| /* algorithm 2y fixes CVE-2011-2483 */ |
| { "bfy", "$2y$", 22, 22, 1, "Blowfish, correct handling of 8-bit chars" }, |
| #endif |
| #if defined FreeBSD |
| { "nt", "$3$", 0, 0, 0, "NT-Hash" }, |
| #endif |
| #if defined HAVE_SHA_CRYPT |
| /* http://people.redhat.com/drepper/SHA-crypt.txt */ |
| { "sha-256", "$5$", 8, 16, 1, "SHA-256" }, |
| { "sha-512", "$6$", 8, 16, 1, "SHA-512" }, |
| #endif |
| /* http://www.crypticide.com/dropsafe/article/1389 */ |
| /* |
| * Actually the maximum salt length is arbitrary, but Solaris by default |
| * always uses 8 characters: |
| * http://cvs.opensolaris.org/source/xref/onnv/onnv-gate/ \ |
| * usr/src/lib/crypt_modules/sunmd5/sunmd5.c#crypt_gensalt_impl |
| */ |
| #if defined __SVR4 && defined __sun |
| { "sunmd5", "$md5$", 8, 8, 1, "SunMD5" }, |
| #endif |
| { NULL, NULL, 0, 0, 0, NULL } |
| }; |
| |
| void generate_salt(char *const buf, const unsigned int len); |
| void *get_random_bytes(const int len); |
| void display_help(int error); |
| void display_version(void); |
| void display_methods(void); |
| |
| int main(int argc, char *argv[]) |
| { |
| int ch, i; |
| int password_fd = -1; |
| unsigned int salt_minlen = 0; |
| unsigned int salt_maxlen = 0; |
| unsigned int rounds_support = 0; |
| const char *salt_prefix = NULL; |
| const char *salt_arg = NULL; |
| unsigned int rounds = 0; |
| char *salt = NULL; |
| char rounds_str[30]; |
| char *password = NULL; |
| |
| #ifdef ENABLE_NLS |
| setlocale(LC_ALL, ""); |
| bindtextdomain(NLS_CAT_NAME, LOCALEDIR); |
| textdomain(NLS_CAT_NAME); |
| #endif |
| |
| /* prepend options from environment */ |
| argv = merge_args(getenv("MKPASSWD_OPTIONS"), argv, &argc); |
| |
| while ((ch = GETOPT_LONGISH(argc, argv, "hH:m:5P:R:sS:V", longopts, 0)) |
| > 0) { |
| switch (ch) { |
| case '5': |
| optarg = (char *) "md5"; |
| /* fall through */ |
| case 'm': |
| case 'H': |
| if (!optarg || strcaseeq("help", optarg)) { |
| display_methods(); |
| exit(0); |
| } |
| for (i = 0; methods[i].method != NULL; i++) |
| if (strcaseeq(methods[i].method, optarg)) { |
| salt_prefix = methods[i].prefix; |
| salt_minlen = methods[i].minlen; |
| salt_maxlen = methods[i].maxlen; |
| rounds_support = methods[i].rounds; |
| break; |
| } |
| if (!salt_prefix) { |
| fprintf(stderr, _("Invalid method '%s'.\n"), optarg); |
| exit(1); |
| } |
| break; |
| case 'P': |
| { |
| char *p; |
| password_fd = strtol(optarg, &p, 10); |
| if (p == NULL || *p != '\0' || password_fd < 0) { |
| fprintf(stderr, _("Invalid number '%s'.\n"), optarg); |
| exit(1); |
| } |
| } |
| break; |
| case 'R': |
| { |
| char *p; |
| rounds = strtol(optarg, &p, 10); |
| if (p == NULL || *p != '\0' || rounds < 0) { |
| fprintf(stderr, _("Invalid number '%s'.\n"), optarg); |
| exit(1); |
| } |
| } |
| break; |
| case 's': |
| password_fd = 0; |
| break; |
| case 'S': |
| salt_arg = optarg; |
| break; |
| case 'V': |
| display_version(); |
| exit(0); |
| case 'h': |
| display_help(EXIT_SUCCESS); |
| default: |
| fprintf(stderr, _("Try '%s --help' for more information.\n"), |
| argv[0]); |
| exit(1); |
| } |
| } |
| argc -= optind; |
| argv += optind; |
| |
| if (argc == 2 && !salt_arg) { |
| password = argv[0]; |
| salt_arg = argv[1]; |
| } else if (argc == 1) { |
| password = argv[0]; |
| } else if (argc == 0) { |
| } else { |
| display_help(EXIT_FAILURE); |
| } |
| |
| /* default: DES password */ |
| if (!salt_prefix) { |
| salt_minlen = methods[0].minlen; |
| salt_maxlen = methods[0].maxlen; |
| salt_prefix = methods[0].prefix; |
| } |
| |
| if (streq(salt_prefix, "$2a$") || streq(salt_prefix, "$2y$")) { |
| /* OpenBSD Blowfish and derivatives */ |
| if (rounds <= 5) |
| rounds = 5; |
| /* actually for 2a/2y it is the logarithm of the number of rounds */ |
| snprintf(rounds_str, sizeof(rounds_str), "%02u$", rounds); |
| } else if (rounds_support && rounds) |
| snprintf(rounds_str, sizeof(rounds_str), "rounds=%u$", rounds); |
| else |
| rounds_str[0] = '\0'; |
| |
| if (salt_arg) { |
| unsigned int c = strlen(salt_arg); |
| if (c < salt_minlen || c > salt_maxlen) { |
| if (salt_minlen == salt_maxlen) |
| fprintf(stderr, ngettext( |
| "Wrong salt length: %d byte when %d expected.\n", |
| "Wrong salt length: %d bytes when %d expected.\n", c), |
| c, salt_maxlen); |
| else |
| fprintf(stderr, ngettext( |
| "Wrong salt length: %d byte when %d <= n <= %d" |
| " expected.\n", |
| "Wrong salt length: %d bytes when %d <= n <= %d" |
| " expected.\n", c), |
| c, salt_minlen, salt_maxlen); |
| exit(1); |
| } |
| while (c-- > 0) { |
| if (strchr(valid_salts, salt_arg[c]) == NULL) { |
| fprintf(stderr, _("Illegal salt character '%c'.\n"), |
| salt_arg[c]); |
| exit(1); |
| } |
| } |
| |
| salt = NOFAIL(malloc(strlen(salt_prefix) + strlen(rounds_str) |
| + strlen(salt_arg) + 1)); |
| *salt = '\0'; |
| strcat(salt, salt_prefix); |
| strcat(salt, rounds_str); |
| strcat(salt, salt_arg); |
| } else { |
| #ifdef HAVE_SOLARIS_CRYPT_GENSALT |
| #error "This code path is untested on Solaris. Please send a patch." |
| salt = crypt_gensalt(salt_prefix, NULL); |
| if (!salt) |
| perror(stderr, "crypt_gensalt"); |
| #elif defined HAVE_LINUX_CRYPT_GENSALT |
| void *entropy = get_random_bytes(64); |
| |
| salt = crypt_gensalt(salt_prefix, rounds, entropy, 64); |
| if (!salt) { |
| fprintf(stderr, "crypt_gensalt failed.\n"); |
| exit(2); |
| } |
| free(entropy); |
| #else |
| unsigned int salt_len = salt_maxlen; |
| |
| if (salt_minlen != salt_maxlen) { /* salt length can vary */ |
| srand(time(NULL) + getpid()); |
| salt_len = rand() % (salt_maxlen - salt_minlen + 1) + salt_minlen; |
| } |
| |
| salt = NOFAIL(malloc(strlen(salt_prefix) + strlen(rounds_str) |
| + salt_len + 1)); |
| *salt = '\0'; |
| strcat(salt, salt_prefix); |
| strcat(salt, rounds_str); |
| generate_salt(salt + strlen(salt), salt_len); |
| #endif |
| } |
| |
| if (password) { |
| } else if (password_fd != -1) { |
| FILE *fp; |
| char *p; |
| |
| if (isatty(password_fd)) |
| fprintf(stderr, _("Password: ")); |
| password = NOFAIL(malloc(128)); |
| fp = fdopen(password_fd, "r"); |
| if (!fp) { |
| perror("fdopen"); |
| exit(2); |
| } |
| if (!fgets(password, 128, fp)) { |
| perror("fgets"); |
| exit(2); |
| } |
| |
| p = strpbrk(password, "\n\r"); |
| if (p) |
| *p = '\0'; |
| } else { |
| password = getpass(_("Password: ")); |
| if (!password) { |
| perror("getpass"); |
| exit(2); |
| } |
| } |
| |
| { |
| const char *result; |
| result = crypt(password, salt); |
| /* xcrypt returns "*0" on errors */ |
| if (!result || result[0] == '*') { |
| fprintf(stderr, "crypt failed.\n"); |
| exit(2); |
| } |
| /* yes, using strlen(salt_prefix) on salt. It's not |
| * documented whether crypt_gensalt may change the prefix */ |
| if (!strneq(result, salt, strlen(salt_prefix))) { |
| fprintf(stderr, _("Method not supported by crypt(3).\n")); |
| exit(2); |
| } |
| printf("%s\n", result); |
| } |
| |
| exit(0); |
| } |
| |
| #ifdef RANDOM_DEVICE |
| void* get_random_bytes(const int count) |
| { |
| char *buf; |
| int fd; |
| |
| buf = NOFAIL(malloc(count)); |
| fd = open(RANDOM_DEVICE, O_RDONLY); |
| if (fd < 0) { |
| perror("open(" RANDOM_DEVICE ")"); |
| exit(2); |
| } |
| if (read(fd, buf, count) != count) { |
| if (count < 0) |
| perror("read(" RANDOM_DEVICE ")"); |
| else |
| fprintf(stderr, "Short read of %s.\n", RANDOM_DEVICE); |
| exit(2); |
| } |
| close(fd); |
| |
| return buf; |
| } |
| #endif |
| |
| #ifdef RANDOM_DEVICE |
| |
| void generate_salt(char *const buf, const unsigned int len) |
| { |
| unsigned int i; |
| |
| unsigned char *entropy = get_random_bytes(len * sizeof(unsigned char)); |
| for (i = 0; i < len; i++) |
| buf[i] = valid_salts[entropy[i] % (sizeof valid_salts - 1)]; |
| buf[i] = '\0'; |
| } |
| |
| #else /* RANDOM_DEVICE */ |
| |
| void generate_salt(char *const buf, const unsigned int len) |
| { |
| unsigned int i; |
| |
| # ifdef HAVE_GETTIMEOFDAY |
| struct timeval tv; |
| |
| gettimeofday(&tv, NULL); |
| srand(tv.tv_sec ^ tv.tv_usec); |
| |
| # else /* HAVE_GETTIMEOFDAY */ |
| # warning "This system lacks a strong enough random numbers generator!" |
| |
| /* |
| * The possible values of time over one year are 31536000, which is |
| * two orders of magnitude less than the allowed entropy range (2^32). |
| */ |
| srand(time(NULL) + getpid()); |
| |
| # endif /* HAVE_GETTIMEOFDAY */ |
| |
| for (i = 0; i < len; i++) |
| buf[i] = valid_salts[rand() % (sizeof valid_salts - 1)]; |
| buf[i] = '\0'; |
| } |
| |
| #endif /* RANDOM_DEVICE */ |
| |
| void display_help(int error) |
| { |
| fprintf((EXIT_SUCCESS == error) ? stdout : stderr, |
| _("Usage: mkpasswd [OPTIONS]... [PASSWORD [SALT]]\n" |
| "Crypts the PASSWORD using crypt(3).\n\n")); |
| fprintf(stderr, _( |
| " -m, --method=TYPE select method TYPE\n" |
| " -5 like --method=md5\n" |
| " -S, --salt=SALT use the specified SALT\n" |
| " -R, --rounds=NUMBER use the specified NUMBER of rounds\n" |
| " -P, --password-fd=NUM read the password from file descriptor NUM\n" |
| " instead of /dev/tty\n" |
| " -s, --stdin like --password-fd=0\n" |
| " -h, --help display this help and exit\n" |
| " -V, --version output version information and exit\n" |
| "\n" |
| "If PASSWORD is missing then it is asked interactively.\n" |
| "If no SALT is specified, a random one is generated.\n" |
| "If TYPE is 'help', available methods are printed.\n" |
| "\n" |
| "Report bugs to %s.\n"), "<md+whois@linux.it>"); |
| exit(error); |
| } |
| |
| void display_version(void) |
| { |
| printf("mkpasswd %s\n\n", VERSION); |
| puts("Copyright (C) 2001-2008 Marco d'Itri\n" |
| "This is free software; see the source for copying conditions. There is NO\n" |
| "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."); |
| } |
| |
| void display_methods(void) |
| { |
| unsigned int i; |
| |
| printf(_("Available methods:\n")); |
| for (i = 0; methods[i].method != NULL; i++) |
| printf("%s\t%s\n", methods[i].method, methods[i].desc); |
| } |
| |