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

#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <fcntl.h>
#include <time.h>
#include <mhash.h>
#include "loglib.h"
#include "kakumei.h"
#include "kakumei_session.h"

char *
session_new(kakumei *ka, char *user, char *session, int sessionsize, char *authid, int authidsize)
{
        static int init=0;
        MHASH td;
        struct timeval tv;
        struct timezone tz;
        int i,k;
        long n;
        char c;
        char binhash[32];
        char filename[1024];
        int len;
        int fd;
        int authidlen;
        char oldsession[SESSIONSIZE+1];
        if(ka==NULL || user==NULL || session==NULL || sessionsize<(SESSIONSIZE+1) || authidsize<(AUTHIDSIZE+1)|| kakumei_uservalid(ka,user)!=0)
                return(NULL);
        if(init==0) {
                gettimeofday(&tv,&tz);
                srandom(tv.tv_sec+getpid()+tv.tv_usec);
                init=1;
        }
        /* generate a not-entirely-trivial-to-guess hash */
        if((td=mhash_init(MHASH_SHA256))==MHASH_FAILED)
                return(NULL);
        gettimeofday(&tv,&tz);
        mhash(td,&tv,sizeof(tv));
        mhash(td,user,strlen(user));
        for(i=0;i<20;i++) {
                n=random();
                mhash(td,&n,sizeof(n));
        }
        mhash_deinit(td,&binhash);
        for(i=0;i<sizeof(binhash) && i<(SESSIONSIZE/2);i++) {
                c=(((unsigned char *)binhash)[i]>>4);
                c=(c>=10)?(c-10+'a'):c+'0';
                session[i<<1]=c;
                c=(((unsigned char *)binhash)[i]&0xf);
                c=(c>=10)?(c-10+'a'):c+'0';
                session[(i<<1)+1]=c;
        }
        session[SESSIONSIZE]='\0';
        for(k=0;i<sizeof(binhash) && k<(AUTHIDSIZE/2);i++,k++) {
                c=(((unsigned char *)binhash)[i]>>4);
                c=(c>=10)?(c-10+'a'):c+'0';
                authid[k<<1]=c;
                c=(((unsigned char *)binhash)[i]&0xf);
                c=(c>=10)?(c-10+'a'):c+'0';
                authid[(k<<1)+1]=c;
        }
        authid[AUTHIDSIZE]='\0';
        /* save the hash */
        mkdir(DATADIR,0700);
        mkdir(SESSIONSDIR,0700);
        snprintf(filename,sizeof(filename)-1,"%s/%s",SESSIONSDIR,session);
        filename[sizeof(filename)-1]='\0';
        if((fd=open(filename,O_WRONLY|O_TRUNC|O_CREAT,0600))==-1)
                return(NULL);
        len=strlen(user);
        authidlen=strlen(authid);
        if(write(fd,user,len)!=len || write(fd,"\n",1)!=1 || write(fd,authid,authidlen)!=authidlen) {
                close(fd),fd=-1;
                return(NULL);
        }
        close(fd),fd=-1;
        /* delete the previous session of the user */
        mkdir(DATADIR,0700);
        mkdir(USERSDIR,0700);
        snprintf(filename,sizeof(filename)-1,"%s/%s/session",USERSDIR,user);
        filename[sizeof(filename)-1]='\0';
        if((fd=open(filename,O_RDONLY))!=-1) {
                memset(oldsession,0,sizeof(oldsession));
                read(fd,oldsession,sizeof(oldsession)-1);
                close(fd),fd=-1;
                session_del(ka,oldsession);
        }
        /* write the current session */
        if((fd=open(filename,O_WRONLY|O_TRUNC|O_CREAT,0600))!=-1) {
                write(fd,session,strlen(session));
                close(fd),fd=-1;
        }
        /* success */
        return(session);
}

char *
session_check(kakumei *ka, char *session, char *authid, char *user, int usersize)
{
        int i;
        int fd;
        char filename[1024];
        char sesbuf[MAXUSERSIZE+AUTHIDSIZE+2];
        char *sep;
        int len;
        if(ka==NULL || session==NULL || session[0]=='\0' || user==NULL || usersize<(MAXUSERSIZE+1))
                return(NULL);
        for(i=0;session[i]!='\0';i++) {
                if(!(session[i]>='0' && session[i]<='9') &&
                   !(session[i]>='a' && session[i]<='f')) {
                        return(NULL);
                }
        }
        snprintf(filename,sizeof(filename)-1,"%s/%s",SESSIONSDIR,session);
        filename[sizeof(filename)-1]='\0';
        if((fd=open(filename,O_RDONLY))==-1)
                return(NULL);
        memset(sesbuf,0,sizeof(sesbuf));
        read(fd,sesbuf,sizeof(sesbuf));
        sesbuf[sizeof(sesbuf)-1]='\0';
        close(fd),fd=-1;
        if((sep=strchr(sesbuf,'\n'))==NULL)
                return(NULL); /* invalid format */
        *sep='\0';
        memset(user,0,usersize);
        strncpy(user,sesbuf,usersize);
        user[usersize-1]='\0';
        /* position sep to authid and trim the last '\n' if it exists */
        sep++;
        if((len=strlen(sep))>0 && sep[len-1]=='\n')
                sep[len-1]='\0';
        /* check validity */
        if(strcmp(authid,sep)!=0)
                return(NULL); /* authid doesn't match */
        if(kakumei_uservalid(ka,user)!=0)
                return(NULL); /* invalid user */
        return(user);
}

int
session_del(kakumei *ka, char *session)
{
        int i;
        char filename[1024];
        if(ka==NULL || session==NULL || session[0]=='\0')
                return(-1);
        for(i=0;session[i]!='\0';i++) {
                if(!(session[i]>='0' && session[i]<='9') &&
                   !(session[i]>='a' && session[i]<='f')) {
                        return(-1);
                }
        }
        snprintf(filename,sizeof(filename)-1,"%s/%s",SESSIONSDIR,session);
        filename[sizeof(filename)-1]='\0';
        unlink(filename);
        return(0);
}