/*
    Copyright (C) 1998  Andrey V. Savochkin <saw@msu.ru>

    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
 */

#include <stdio.h>
#include <unistd.h>
#include <malloc.h>
#include <assert.h>
#include <string.h>
#include <pwd.h>
#include <sys/types.h>
#include <security/pam_client.h>
#include "md5.h"

#define SH_SECRET_ID_FILE ".ssh/id_secret"

typedef unsigned int control_t;

static char *user = NULL;
static char host[256];
static char *home = NULL;

void fullread(unsigned char *buf, unsigned int len)
{
    ssize_t l;
    while(len) {
        l = read(STDIN_FILENO, buf, len);
        if (l <= 0) exit(1);
        if (l > len) exit(10); /* really strange */
        buf += l;
        len -= l; /* len remains >= 0 */
    }
}

void fullwrite(unsigned char *buf, unsigned int len)
{
    ssize_t l;
    while(len) {
        l = write(STDOUT_FILENO, buf, len);
        if (l <= 0) exit(1);
        if (l > len) exit(10); /* really strange */
        buf += l;
        len -= l; /* len remains >= 0 */
    }
}

static void simple_answer(unsigned int len, control_t answer)
{
    unsigned char tmp[4];
    for (; len > 0; len--) fullread(tmp, 1);
    pamc_write__u32(tmp, sizeof(tmp));
    fullwrite(tmp, sizeof(tmp));
    pamc_write__u32(tmp, answer);
    fullwrite(tmp, sizeof(tmp));
}

static control_t answer(const unsigned char *id, unsigned int id_len,
    const unsigned char *challenge, unsigned int chlen,
    unsigned char **resp, unsigned int *resplen)
{
    FILE *file;
    char buf[1024], *p;
    int linelen;
    int r;
    struct MD5Context context;

    *resp = NULL;
    *resplen = 0;

    if (user == NULL || home == NULL) return PAMC_CONTROL_FAIL;
    r = snprintf(buf, sizeof(buf), "%s/" SH_SECRET_ID_FILE, home);
    if (r < 0 || r >= sizeof(buf)) return PAMC_CONTROL_FAIL;
    file = fopen(buf, "r");
    if (file == NULL) return PAMC_CONTROL_FAIL;

    while (fgets(buf, sizeof(buf), file) != NULL) {
        linelen = strlen(buf);
        if (linelen > 0 && buf[linelen-1] == '\n') linelen--;
        if (linelen < id_len+1) continue;
        if (strncmp(buf, id, id_len) || buf[id_len] != ' ') continue;
        for (p = buf+id_len+1, linelen -= id_len+1;
             linelen > 0 && *p == ' ';
             p++, linelen--);
        if (linelen < 16) { /* magic minimal secret length */
             memset(p, 0, linelen);
             return PAMC_CONTROL_FAIL;
        }

        MD5Init(&context);
        MD5Update(&context, challenge, chlen);
        MD5Update(&context, p, linelen);
        memset(p, 0, linelen);

        linelen = snprintf(buf, sizeof(buf), "%s@%s", user, host);
        if (linelen < 0 || linelen >= sizeof(buf))
            return PAMC_CONTROL_FAIL;

        *resplen = 4+linelen+4+16;
        *resp = malloc(*resplen);
        if (*resp == NULL) return PAMC_CONTROL_FAIL;
        pamc_write__u32(*resp, linelen);
        memcpy(*resp+4, buf, linelen);
        pamc_write__u32(*resp+4+linelen, 16);
        MD5Final(*resp+4+linelen+4, &context);
        return PAMC_CONTROL_DONE;
    }

    fclose(file);
    return PAMC_CONTROL_FAIL;
}

void main(int argc, char **argv)
{
    unsigned char raw[4];
    unsigned int len;
    control_t control;
    unsigned char *id, *challenge, *resp;
    unsigned int idlen, chlen, resplen;
    struct passwd *pw;

    id = NULL;
    challenge = NULL;
    resp = NULL;

    /*
       XXX: shouldn't these values be taken from environment?
                                                 1998/01/26  SAW
     */
    if (gethostname(host, sizeof(host)) == -1) exit(2);
    pw = getpwuid(getuid());
    if (pw == NULL) exit(3);
    user = strdup(pw->pw_name);
    home = strdup(pw->pw_dir);
    pw = NULL;

    while (1) {
        fullread(raw, sizeof(raw));
        len = pamc_read__u32(raw);
        assert(len >= sizeof(raw));
        len -= sizeof(raw);
        fullread(raw, sizeof(raw));
        control = pamc_read__u32(raw);
        switch(control) {
            case PAMC_CONTROL_EXIT:
                exit(0);
            case PAMC_CONTROL_SELECT:
                simple_answer(len, PAMC_CONTROL_OK);
                continue;
            case PAMC_CONTROL_EXCHANGE:
                break;
            default:
                simple_answer(len, PAMC_CONTROL_FAIL);
                continue;
        }

        assert(len >= sizeof(raw));
        len -= sizeof(raw);
        fullread(raw, sizeof(raw));
        idlen = pamc_read__u32(raw);
        assert(len >= idlen);
        len -= idlen;
        id = malloc(idlen);
        fullread(id, idlen);

        assert(len >= sizeof(raw));
        len -= sizeof(raw);
        fullread(raw, sizeof(raw));
        chlen = pamc_read__u32(raw);
        assert(len >= chlen);
        len -= chlen;
        challenge = malloc(chlen);
        fullread(challenge, chlen);

        assert(len == 0);

        control = answer(id, idlen, challenge, chlen, &resp, &resplen);
        memset(id, 0, idlen); free(id); id = NULL;
        memset(challenge, 0, chlen); free(challenge); challenge = NULL;

        pamc_write__u32(raw, sizeof(raw)+resplen);
        fullwrite(raw, sizeof(raw));
        pamc_write__u32(raw, control);
        fullwrite(raw, sizeof(raw));
        if (resp != NULL) {
            /* assume resplen == 0 */
            fullwrite(resp, resplen);
            memset(resp, 0, resplen); free(resp); resp = NULL;
        }
    }
}
