/*
 * kakumei_pass.h
 *
 * Password handling for kakumei.
 *
 * Author: Dario Rodriguez dario@softhome.net
 * This program is licensed under the terms of the Affero GPL v1+
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "loglib.h"
#include "kakumei.h"
#include "libscrypt.h"

#define VALUE_N 16384
#define VALUE_r 8
#define VALUE_p 1

static int mcf_gen(const char *salt,const char *passwd, char *mcf, int mcfsize);
static int mcf_check(char *mcf, const char *passwd);

int
pass_new(kakumei *ka, char *user, char *passwd)
{
        int fd;
        char filename[1024];
        int len;
        int res;
        static const char mysalt[]={"NFkFsNdzMQ9Jfer"};
        char mcf[SCRYPT_MCF_LEN];
        if(kakumei_uservalid(ka,user)!=0) {
                log_write("PASS","Ilegal username %s",user);
                return(-1);
        }
        memset(mcf,0,sizeof(mcf));
        if((res=mcf_gen(mysalt,passwd,mcf,sizeof(mcf)))!=0) {
                log_write("PASS","Couln't create simple hash for user %s",user);
                return(-1);
        }
        if((res=mcf_check(mcf,passwd))!=0) {
                log_write("EINT","%s:%i",__FILE__,__LINE__);
                return(-1); /* internal error */
        }
        mkdir(DATADIR,0700);
        mkdir(USERSDIR,0700);
        snprintf(filename,sizeof(filename)-1,"%s/%s",USERSDIR,user);
        filename[sizeof(filename)-1]='\0';
        mkdir(filename,0700);
        snprintf(filename,sizeof(filename)-1,"%s/%s/passwd",USERSDIR,user);
        filename[sizeof(filename)-1]='\0';
        if((fd=open(filename,O_WRONLY|O_TRUNC|O_CREAT,0600))==-1) {
                log_write("PASS","Couln't open for writing to %s",filename);
                return(-1);
        }
        len=strlen(mcf);
        if(write(fd,mcf,len)!=len) {
                close(fd),fd=-1;
                log_write("PASS","Error writing to %s",filename);
                return(-1);
        }
        close(fd),fd=-1;
        return(0);
}

int
pass_check(kakumei *ka, char *user, char *passwd)
{
        int fd;
        char filename[1024];
        char mcf[SCRYPT_MCF_LEN];
        int res;
        if(kakumei_userexists(ka,user)!=0) {
                log_write("PASS","No username %s",user);
                return(-1);
        }
        snprintf(filename,sizeof(filename)-1,"%s/%s/passwd",USERSDIR,user);
        filename[sizeof(filename)-1]='\0';
        if((fd=open(filename,O_RDONLY))==-1) {
                log_write("PASS","Couldn't read passwd of user %s",user);
                return(-1);
        }
        memset(mcf,0,sizeof(mcf));
        read(fd,mcf,sizeof(mcf)-1);
        close(fd),fd=-1;
        if((res=mcf_check(mcf,passwd))!=0) {
                log_write("PASS","Wrong password trying to login for user %s",user);
                return(-1);
        }
        return(0);
}

/* utility functions to make libscrypt usable */
static int
mcf_gen(const char *salt,const char *passwd, char *mcf, int mcfsize)
{
        int res;
        uint8_t hashbuf[SCRYPT_HASH_LEN];
        char saltbuf[1024];
        char outbuf[1024];
        if(mcfsize<SCRYPT_MCF_LEN)
                return(-1);
        if(strlen(salt)>16)
                return(-1); /* large salts make libscrypt break */
        if((res=libscrypt_scrypt((uint8_t*)passwd,strlen(passwd),
          (uint8_t*)salt, strlen(salt), VALUE_N,VALUE_r,VALUE_p, hashbuf, sizeof(hashbuf)))!=0) {
                log_write("PASS","Couln't create hash");
                return(-1);
        }
        if((res=libscrypt_b64_encode(outbuf, (char*)hashbuf, sizeof(hashbuf)))==-1) {
                log_write("PASS","Couln't encode hash");
                return(-1);
        }
        if((res=libscrypt_b64_encode(saltbuf, salt, strlen(salt)))==-1) {
                log_write("PASS","Couln't encode salt");
                return(-1);
        }
        if(!(res=libscrypt_mcf(VALUE_N,VALUE_r,VALUE_p, saltbuf, outbuf, mcf))) {
                log_write("PASS","Couln't convert to mcf");
                return(-1);
        }
        return(0);
}

static int
mcf_check(char *mcf, const char *passwd)
{
        char mcftest[SCRYPT_MCF_LEN];
        int res;
        memcpy(mcftest,mcf,sizeof(mcftest));
        if((res=libscrypt_check(mcftest,(char *)passwd))<=0)
                return(-1);
        return(0);
}