/*
 * Copyright 2019 Offensive Security
 * Copyright 2019 Raphaël Hertzog <raphael@offensive-security.com>
 *
 * 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 package 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <sys/random.h>
#include <crypt.h>

void
usage(const char* name, int exitcode)
{
    fprintf(stderr, "Usage: %s <command>\n", name);
    fprintf(stderr, "Valid commands:\n");
    fprintf(stderr, "  --help: display this output\n");
    fprintf(stderr, "  --generate <salt>: read password from stdin and print hashed version\n");
    fprintf(stderr, "  --check <hash>: read password from stdin and check it against supplied hash\n");
    fprintf(stderr, "                  Exits with error 0 when password matched, 1 otherwise.\n");
    exit(exitcode);
}

void
read_password(char *buffer, int bufsize)
{
    int size = -1, total = 0, i = 0;

    while ((size != 0) && (total < bufsize - 1)) {
	size = read(0, buffer+total, bufsize - total);
	if (size != -1) {
	    total += size;
	}
    }
    buffer[total+1] = '\0';
    for(i = 0; i < total; i++) {
	if ((buffer[i] == '\n') || (buffer[i] == '\r')) {
	    buffer[i] = '\0';
	    break;
	}
    }
    if (strlen(buffer) == 0) {
	fprintf(stderr, "ERROR: no password supplied");
	exit(1);
    }
}

char*
gen_salt()
{
    static const char alphanum[] = "123456789abcdefghijklmnopqrstuvwxyz"
	"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    static char salt[16];
    int i, index;
    unsigned int seed;
    ssize_t ret;

    ret = getrandom(&seed, sizeof(seed), 0);
    if (ret != sizeof(seed)) {
	fprintf(stderr, "ERROR: failed to get random data\n");
	exit(2);
    }
    srandom(seed);

    salt[0] = '$';
    salt[1] = '6';
    salt[2] = '$';
    for (i = 3; i < 11; i++) {
	index = random() % (sizeof(alphanum) - 1);
	salt[i] = alphanum[index];
    }
    salt[11] = '$';
    salt[12] = '\0';
    return salt;
}

char*
extract_salt(char* hash)
{
    static char salt[24];

    if (hash[0] != '$') {
	/* Traditional crypt hash with 2 characters */
	salt[0] = hash[0];
	salt[1] = hash[1];
	salt[2] = '\0';
    } else {
	/* Hash delimited by $ characters */
	char *p, *end;
	int i;

	p = hash;
       	end = strrchr(hash, '$');
	for(i = 0; p <= end && i < sizeof(salt) - 1; i++) {
	    salt[i] = *p;
	    p++;
	}
	salt[i] = '\0';
    }
    return salt;
}

int
main (int argc, char* argv[]) 
{
    static char buffer[256];
    char *crypted, *salt;

    if (argc == 1) {
	usage(argv[0], 1);
    }

    if (strcmp(argv[1], "--help") == 0) {
	usage(argv[0], 0);
    } else if (strcmp(argv[1], "--generate") == 0) {
	read_password(buffer, sizeof(buffer));
	salt = (argc > 2 && strlen(argv[2])) ? argv[2] : gen_salt();
	crypted = crypt(buffer, salt);
	if (crypted == NULL || (
            strlen(crypted) < 13 &&
            strncmp(crypted, "*", 1) == 0 &&
            strcmp(crypted, salt) != 0
            )) {
	    fprintf(stderr, "ERROR: crypt() failed to return anything with salt '%s'", salt);
	    exit(1);
	}
	printf("%s\n", crypted);
    } else if (strcmp(argv[1], "--check") == 0) {
	read_password(buffer, sizeof(buffer));
	if (argc < 3) {
	    fprintf(stderr, "ERROR: missing <hash> argument after --check");
	    exit(1);
	}
	salt = extract_salt(argv[2]);
	crypted = crypt(buffer, salt);
	if (crypted == NULL)
	    exit(1);
	if (strcmp(crypted, argv[2]) == 0)
	    exit(0);
	else
	    exit(1);
    } else {
	usage(argv[0], 1);
    }

    return 0;
}

