![Stefan Becker](/assets/img/avatar_default.png)
crypt() is an optional glibc feature. Some distros, like Fedora 28, are phasing it out to be replaced with libxcrypt [1]. Unfortunately this change is only ABI compatible, not source code compatible, i.e. the code will compile with warnings about undefined crypt(), but the resulting binary will crash. Follow the guidance in the Fedora bug and include crypt.h when _XOPEN_CRYPT is not defined. [1] https://bugzilla.redhat.com/show_bug.cgi?id=1566464 Signed-off-by: Stefan Becker <chemobejk@gmail.com> Reviewed-by: "Yann E. MORIN" <yann.morin.1998@free.fr> Signed-off-by: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
462 lines
12 KiB
C
462 lines
12 KiB
C
/*
|
|
* 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 <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);
|
|
}
|
|
|