/*
 * http_auth: authentication
 * 
 * Rob McCool
 * 
 */


#include "httpd.h"

static pthread_key_t obj_static_data_key = -1;
static pthread_mutex_t obj_static_data_mutex = PTHREAD_MUTEX_INITIALIZER;

struct obj_static_data {
  char user[MAX_STRING_LEN];
#ifdef PEM_AUTH
  int doing_pem;
  char pem_user[MAX_STRING_LEN];
  int encrypt_pid,decrypt_pid;
  char tn[L_tmpnam];
#endif
};

#ifdef PEM_AUTH
#define ENCODING_NONE -1
#define ENCODING_PEM 1
#define ENCODING_PGP 2
#endif

void auth_bong(char *s, FILE *out) {
    char errstr[MAX_STRING_LEN];
    struct obj_static_data *obj_data;

    obj_data = pthread_getspecific(obj_static_data_key);

/* debugging */
    if(s) {
        sprintf(errstr,"%s authorization: %s",remote_name,s);
        log_error(errstr);
    }
    if(!strcasecmp(auth_type,"Basic")) {
        sprintf(errstr,"Basic realm=\"%s\"",auth_name);
        die(AUTH_REQUIRED,errstr,out);
    }
#ifdef PEM_AUTH
    if(!strcasecmp(auth_type,"PEM")) {
        sprintf(errstr,"PEM entity=\"%s\"",
                auth_pem_entity);
        die(AUTH_REQUIRED,errstr,out);
    }
    if(!strcasecmp(auth_type,"PGP")) {
        sprintf(errstr,"PGP entity=\"%s\"",auth_pgp_entity);
        die(AUTH_REQUIRED,errstr,out);
    }
#endif
    else {
        sprintf(errstr,"Unknown authorization method %s",auth_type);
        die(SERVER_ERROR,errstr,out);
    }
}

void check_auth(security_data *sec, int m, FILE *out) {
    char at[MAX_STRING_LEN];
    char ad[MAX_STRING_LEN];
    char sent_pw[MAX_STRING_LEN];
    char real_pw[MAX_STRING_LEN];
    char t[MAX_STRING_LEN];
    char w[MAX_STRING_LEN];
    char errstr[MAX_STRING_LEN];
    register int x,y;
    int grpstatus;
    struct obj_static_data *obj_data;

    pthread_mutex_lock(&obj_static_data_mutex);
    if(obj_static_data_key = -1) {
      pthread_key_create(&obj_static_data_key, free);
    }
    pthread_mutex_unlock(&obj_static_data_mutex);

    if (!(obj_data = pthread_getspecific(obj_static_data_key))) {
      if (!(obj_data = malloc(sizeof(struct obj_static_data)))) {
	fprintf(stderr, "error allocating memory\n");
	pthread_exit();
      }
      pthread_setspecific(obj_static_data_key, obj_data);
    }

    if(!auth_type) {
        sprintf(errstr,
"httpd: authorization required for %s but not configured",sec->d);
        die(SERVER_ERROR,errstr,out);
    }

    if(!strcasecmp(auth_type,"Basic")) {
        if(!auth_name) {
            sprintf(errstr,"httpd: need AuthName for %s",sec->d);
            die(SERVER_ERROR,errstr,out);
        }
        if(!auth_line[0])
            auth_bong(NULL,out);
        if(!auth_pwfile) {
            sprintf(errstr,"httpd: need AuthUserFile for %s",sec->d);
            die(SERVER_ERROR,errstr,out);
        }
        sscanf(auth_line,"%s %s",at,t);
        if(strcmp(at,auth_type))
            auth_bong("type mismatch",out);
        uudecode(t,(unsigned char *)ad,MAX_STRING_LEN);
        getword(obj_data->user,ad,':');
        strcpy(sent_pw,ad);
        if(!get_pw(obj_data->user,real_pw,out)) {
            sprintf(errstr,"user %s not found",obj_data->user);
            auth_bong(errstr,out);
        }
        /* anyone know where the prototype for crypt is? */
        if(strcmp(real_pw,(char *)crypt(sent_pw,real_pw))) {
            sprintf(errstr,"user %s: password mismatch",obj_data->user);
            auth_bong(errstr,out);
        }
    }
#ifdef PEM_AUTH
    else if(!strcasecmp(auth_type,"PEM")) {
        /* see if we're already handling the request... */
        switch(obj_data->doing_pem) {
          case ENCODING_NONE:
            auth_bong(NULL,out);
          case ENCODING_PGP:
            auth_bong("request with pgp for pem-protected directory",out);
          default:
            strcpy(obj_data->user,obj_data->pem_user);
        }
    }
    else if(!strcasecmp(auth_type,"PGP")) {
        switch(obj_data->doing_pem) {
          case ENCODING_NONE:
            auth_bong(NULL,out);
          case ENCODING_PEM:
            auth_bong("request with pem for pgp-protected directory",out);
          default:
            strcpy(obj_data->user,obj_data->pem_user);
        }
        strcpy(obj_data->user,obj_data->pem_user);
    }
#endif
    else {
        sprintf(errstr,"unknown authorization type %s for %s",auth_type,
                sec->d);
        auth_bong(errstr,out);
    }

    /* Common stuff: Check for valid user */
    if(auth_grpfile)
        grpstatus = init_group(auth_grpfile,out);
    else
        grpstatus = 0;

    for(x=0;x<sec->num_auth[m];x++) {
        strcpy(t,sec->auth[m][x]);
        getword(w,t,' ');
        if(!strcmp(w,"valid-user"))
            goto found;
        if(!strcmp(w,"user")) {
            while(t[0]) {
                if(t[0] == '\"') {
                    getword(w,&t[1],'\"');
                    for(y=0;t[y];y++)
                        t[y] = t[y+1];
                }
                getword(w,t,' ');
                if(!strcmp(obj_data->user,w))
                    goto found;
            }
        }
        else if(!strcmp(w,"group")) {
            if(!grpstatus) {
                sprintf(errstr,"group required for %s, bad groupfile",
                        sec->d);
                auth_bong(errstr,out);
            }
            while(t[0]) {
                getword(w,t,' ');
                if(in_group(obj_data->user,w))
                    goto found;
            }
        }
        else
            auth_bong("require not followed by user or group",out);
    }
    if(grpstatus) kill_group();
    sprintf(errstr,"user %s denied",obj_data->user);
    auth_bong(errstr,out);
  found:
    if(grpstatus)
        kill_group();
}

#ifdef PEM_AUTH

void pem_cleanup(int status, FILE *out) {
  struct obj_static_data *obj_data;

  obj_data = pthread_getspecific(obj_static_data_key);

  if(obj_data->doing_pem != ENCODING_NONE) {
    fclose(out);
    waitpid(obj_data->decrypt_pid,NULL,0);
    waitpid(obj_data->encrypt_pid,NULL,0);
    unlink(obj_data->tn);
  }
}

int decrypt_request(int sfd, char *req, FILE **out) {
    int tfd,nr,pid,p[2],decrypt_fd,pem,pgp;
    char w[MAX_STRING_LEN],w2[MAX_STRING_LEN];
    char c;
    FILE *tmp;
    char *decrypt,*encrypt,*entity;
    struct obj_static_data *obj_data;

    pthread_mutex_lock(&obj_static_data_mutex);
    if(obj_static_data_key = -1) {
      pthread_key_create(&obj_static_data_key, free);
    }
    pthread_mutex_unlock(&obj_static_data_mutex);

    if (!(obj_data = pthread_getspecific(obj_static_data_key))) {
      if (!(obj_data = malloc(sizeof(struct obj_static_data)))) {
	fprintf(stderr, "error allocating memory\n");
	pthread_exit();
      }
      pthread_setspecific(obj_static_data_key, obj_data);
    }

    obj_data->doing_pem = ENCODING_NONE;
    if(strcmp(req,"/"))
        return -1;
        
    pem = !(strcmp(content_type,"application/x-www-pem-request"));
    pgp = !(strcmp(content_type,"application/x-www-pgp-request"));

    if((!pem) && (!pgp))
        return -1;

    auth_type = (pem ? "PEM" : "PGP");

    sscanf(auth_line,"%s %s",w,w2);

    if(pem) {
        if(strcasecmp(w,"PEM"))
            auth_bong("PEM content in reply to non-PEM request",*out);
    }
    else {
        if(strcasecmp(w,"PGP"))
            auth_bong("PGP content in reply to non-PGP request",*out);
    }

    getword(w,w2,'=');
    if(strcasecmp(w,"entity"))
        auth_bong("Garbled entity line",*out);

    auth_type = NULL; /* prevent its being freed later */
    if(w2[0] == '\"')
        getword(obj_data->pem_user,&w2[1],'\"');
    else
        strcpy(obj_data->pem_user,w2);

    escape_shell_cmd(obj_data->pem_user); /* protect from security threats */
    if(pem) {
        decrypt = auth_pem_decrypt;
        encrypt = auth_pem_encrypt;
        entity = auth_pem_entity;
    }
    else {
        decrypt = auth_pgp_decrypt;
        encrypt = auth_pgp_encrypt;
        entity = auth_pgp_entity;
    }

    if((!encrypt[0]) || (!decrypt[0]) || (!entity[0]))
        die(SERVER_ERROR,"PEM/PGP authorization required but not configured",
            *out);

    tmpnam(obj_data->tn);
    if((tfd = open(obj_data->tn,O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR)) == -1)
        die(SERVER_ERROR,"Could not open a temp file for writing",*out);

    nr=0;
    while(nr != content_length) {
        char buf[1024];
        int n,t;
        t = content_length-nr;
        if((n = read(sfd,buf,(t > 1024 ? 1024 : t))) < 1) {
            close(tfd);
            unlink(obj_data->tn);
            die(SERVER_ERROR,"Failed to read the full encrypted request",*out);
        }
        if(write(tfd,buf,n) != n) {
            close(tfd);
            unlink(obj_data->tn);
            die(SERVER_ERROR,"Could not write to temp file",*out);
        }
        nr+=n;
    }
    close(tfd);
    /* this is done here instead of below for error recovery */
    if((tfd = open(obj_data->tn,O_RDONLY)) == -1) {
        unlink(obj_data->tn);
        die(SERVER_ERROR,"Could not open temp file for reading",*out);
    }

    if(pipe(p) == -1)
        die(SERVER_ERROR,"Could not create IPC pipe",*out);
    if((pid = fork()) == -1)
        die(SERVER_ERROR,"Could not fork a new process",*out);

    if(!pid) {
        char *argv0;
        close(p[0]);
        signal(SIGHUP,SIG_DFL);
        if(tfd != STDIN_FILENO) {
            dup2(tfd,STDIN_FILENO);
            close(tfd);
        }
        if(p[1] != STDOUT_FILENO) {
            dup2(p[1],STDOUT_FILENO);
            close(p[1]);
        }
        error_log2stderr();
        if(!(argv0 = strrchr(decrypt,'/')))
            argv0 = decrypt;
        else
            ++argv0;
        if(execlp(decrypt,argv0,obj_data->pem_user,(char *)0) == -1)
            die(SERVER_ERROR,"Could not exec decrypt command",*out); /* !!! */
    }
    close(tfd);
    close(p[1]);
    
    decrypt_fd = p[0];
    obj_data->decrypt_pid = pid;

    /* create encryption stream */
    if(pipe(p) == -1)
        die(SERVER_ERROR,"Could not open an IPC pipe",*out);
    if((pid = fork()) == -1)
        die(SERVER_ERROR,"Could not fork new process",*out);

    if(!pid) {
        char *argv0;
        signal(SIGHUP,SIG_DFL);
        close(p[1]);
        if(fileno(*out) != STDOUT_FILENO) {
            dup2(fileno(*out),STDOUT_FILENO);
            fclose(*out);
        }
        if(p[0] != STDIN_FILENO) {
            dup2(p[0],STDIN_FILENO);
            close(p[0]);
        }
        if(!(argv0 = strrchr(encrypt,'/')))
            argv0 = encrypt;
        else
            ++argv0;
        if(execlp(encrypt,argv0,obj_data->pem_user,(char*)0) == -1)
            die(SERVER_ERROR,"Could not exec encrypt command",*out); /*!!!*/
    }
    close(p[0]);
    tmp = *out;
    if(!(*out = fdopen(p[1],"w")))
        die(SERVER_ERROR,"Could not open stream to encrypt command",tmp);
    strcpy(content_type,(pem ? "application/x-www-pem-reply" : 
                         "application/x-www-pgp-reply"));
    obj_data->doing_pem = (pem ? ENCODING_PEM : ENCODING_PGP);
    location[0] = '\0';
    set_content_length(-1);
    send_http_header(tmp);
    fclose(tmp);

    obj_data->encrypt_pid = pid;
    return(decrypt_fd);
}

#endif
