/* * 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 #define _BSD_SOURCE /* System library */ #include #include #include #include "config.h" #ifdef HAVE_GETOPT_LONG #include #endif #include #include #include #include #ifdef HAVE_XCRYPT #include #include #endif #ifdef HAVE_LINUX_CRYPT_GENSALT #define _OW_SOURCE #include #endif #ifdef HAVE_GETTIMEOFDAY #include #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"), ""); 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); }