/////
//
// File : mod_absec.c
// Author : Jean-Luc Cyr
// Date : 2018-10
//
// Description: apache module for authentification and autorisation management
//

/* 
**  mod_absec.c -- Apache absec module
**  [base Autogenerated via ``apxs -n absec -g'']
**
**  To play with this sample module first compile it into a
**  DSO file and install it into Apache's modules directory 
**  by running:
**
**    $ apxs -lpam -lpam_misc -c -i mod_absec.c
**
**  Then activate it in Apache's httpd.conf file for instance
**  for the URL /absec in as follows:
**
**    #   httpd.conf
**    LoadModule absec_module modules/mod_absec.so
**    <Location /absec>
**    SetHandler absec
**    </Location>
**
**  Then after restarting Apache via
**
**    $ apachectl restart
**
*/ 

/*
    TEST URL
    http://10.211.55.15/absec/<fichier>
    
    INFORMATION SOURCES 

    https://apr.apache.org/docs/apr/1.5/group__apr__strings.html
    https://apr.apache.org/docs/apr-util/1.6/files.html

    https://httpd.apache.org/docs/2.4/developer/modguide.html
    http://www.ziviani.net/2011/how-to-create-an-apache-module

    https://en.wikipedia.org/wiki/Basic_access_authentication

*/

#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_protocol.h"
#include "http_request.h"
#include "http_log.h"
#include "ap_config.h"
#include "apr_base64.h"
#include "apr_strings.h"
#include "apr_portable.h"
#include "apr_user.h"
#include "ap_provider.h"
#include "mod_auth.h"

#include <math.h>

#include <pwd.h>
#include <grp.h>
#include <sys/types.h>
#include <unistd.h>
#include "apr_want.h"

#include <shadow.h>
#include <sys/stat.h>

#include "absec_authen.h"
#include "absec_auto.h"

////////////////////////////////////////////////////////////////
// Macro declaration du module
module AP_MODULE_DECLARE_DATA absec_module;

////////////////////////////////////////////////////////////////
// structure de configuration du module
typedef struct {
    int default_uid;
    int default_gid;
    int default_perms;
} authnz_config_rec;

static void *authnz_absec_config(apr_pool_t *pool, char *x)
{
    return apr_pcalloc(pool, sizeof(authnz_config_rec));
}

static const char* set_default_perms(cmd_parms* cmd, void* cfg, const char* val) {
    int octal, decimal;
    octal = atoi(val);
    decimal = 0;
    int i=0;
    while (octal != 0)
    {
        decimal =  decimal +(octal % 10)* pow(8, i++);
        octal = octal / 10;
    }

    ((authnz_config_rec*)cfg)->default_perms = decimal;
    return NULL;
}

static const char* set_default_uid(cmd_parms* cmd, void* cfg, const char* val) {
    ((authnz_config_rec*)cfg)->default_uid = atoi(val);
    return NULL;
}

static const char* set_default_gid(cmd_parms* cmd, void* cfg, const char* val) {
    ((authnz_config_rec*)cfg)->default_gid = atoi(val);
    return NULL;
}

////////////////////////////////////////////////////////////////
static const command_rec absec_auth_basic_cmds[] =
{
/*    AP_INIT_ITERATE("AuthBasicProvider", add_authn_provider, NULL, OR_AUTHCFG,
                    "specify the auth providers for a directory or location"),*/
    AP_INIT_TAKE1("ABSECDefaultPerms", set_default_perms, NULL, OR_AUTHCFG,
                 "Set to default octal value of perms "),
    AP_INIT_TAKE1("ABSECDefaultUID", set_default_uid, NULL, OR_AUTHCFG,
                 "Set to default octal value of perms "),
    AP_INIT_TAKE1("ABSECDefaultGID", set_default_gid, NULL, OR_AUTHCFG,
                 "Set to default octal value of perms "),
/*    AP_INIT_TAKE12("AuthBasicFake", add_basic_fake, NULL, OR_AUTHCFG,
                  "Fake basic authentication using the given expressions for "
                  "username and password, 'off' to disable. Password defaults "
                  "to 'password' if missing."),
    AP_INIT_TAKE1("AuthBasicUseDigestAlgorithm", set_use_digest_algorithm,
                  NULL, OR_AUTHCFG,
                  "Set to 'MD5' to use the auth provider's authentication "
                  "check for digest auth, using a hash of 'user:realm:pass'"),*/
    {NULL}
};

////////////////////////////////////////////////////////////////
// Validate authentification (user/pass)
static authn_status authn_check_absec(request_rec *r, const char* user, const char* password)
{
    ap_log_rerror("mod_absec.c", 269, 1, APLOG_ERR, APR_SUCCESS, r, "authn_check_absec : user : %s, pass : %s", user, password);
    
    int pam_result = check_user(user, password);
    if ( (pam_result==PAM_ERROR_START) || (pam_result==PAM_ERROR_STOP) ) {
        return HTTP_INTERNAL_SERVER_ERROR;
    }
    if (pam_result==PAM_ERROR_INVALID_CRED) {
        ap_log_rerror("mod_absec.c", 269, 1, APLOG_ERR, APR_SUCCESS, r, "authn_check_absec : DENIED");
        return AUTH_DENIED;
    }
    ap_log_rerror("mod_absec.c", 269, 1, APLOG_ERR, APR_SUCCESS, r, "authn_check_absec : GRANTED");
    return AUTH_GRANTED;

    // Example code
    /*
    if (strcmp(user, "joe")) {
        return AUTH_USER_NOT_FOUND;
    } else {
        if (strcmp(password, "poi")) {
            return AUTH_DENIED;
        } else {
            return AUTH_GRANTED;
        }
    }
    */

    // Possible return status
    // AUTH_GENERAL_ERROR
    // AUTH_USER_NOT_FOUND
    // AUTH_USER_FOUND
    // AUTH_DENIED
    // AUTH_GRANTED
}

////////////////////////////////////////////////////////////////
// Validate autorisation (ressource access)
// request_rec contain user/pass if basic auth
// it could also contain cookies if cookies based auth
static authz_status authz_check_absec(request_rec *r, const char *require_args, const void *parsed_require_args)
{
    char *user = r->user;
    ap_log_rerror("mod_absec.c", 342, 1, APLOG_ERR, APR_SUCCESS, r, "authz_check_absec : user : %s", user);

    ////////
    /* http method validate the perm asked (r/w vs get/post,put) */
    //ap_rprintf(r, "Before Method: %s<br/>\r\n", r->method);

    int permmask = 0;
    if (strcmp(r->method,"GET")==0) permmask=0444;     // r
    if (strcmp(r->method,"PUT")==0) permmask=0222;     // w
    if (strcmp(r->method,"POST")==0) permmask=0222;    // w
    if (strcmp(r->method,"DELETE")==0) permmask=0111;  // x

    authnz_config_rec *cfg = ap_get_module_config(r->per_dir_config, &absec_module);
    struct stat fperm;
    fperm.st_mode = cfg->default_perms;
    ap_log_rerror("mod_absec.c", 329, 1, APLOG_ERR, APR_SUCCESS, r, "authz_check_absec : default perms : %o", cfg->default_perms);
    fperm.st_uid = cfg->default_uid;
    ap_log_rerror("mod_absec.c", 329, 1, APLOG_ERR, APR_SUCCESS, r, "authz_check_absec : default uid : %o", cfg->default_uid);
    fperm.st_gid = cfg->default_gid;
    ap_log_rerror("mod_absec.c", 329, 1, APLOG_ERR, APR_SUCCESS, r, "authz_check_absec : default gid : %o", cfg->default_gid);

    ////////
    /* check file permission on filesystem */
    /* should include <sys/stat.h> */
    int status;
    //status = stat(r->filename, &fperm);
    status = perms_lookup(r, &fperm);
    //ap_rprintf(r, "Result mysql: %d<br/>\r\n", );
    if (status==-1) {
        ap_rprintf(r, "stat erreur %d", errno);
        ap_log_rerror("mod_absec.c", 282, 1, APLOG_ERR, APR_SUCCESS, r, "authz_check_absec : stat erreur %d", errno);
        return (OK);
    }

    //ap_rprintf(r, "File perms %o, owner %d, group %d (status %d)<br/>\r\n", fperm.st_mode, fperm.st_uid, fperm.st_gid, status);

    /* check if any permission (ogw) match method (get r, put/post w, delete x) */
    if ((fperm.st_mode & permmask)==0) {
        /* no permission match, return don't even have to check user perms */
        ap_rprintf(r, "Aucune permission pour la methode %s (%o, %o)", r->method, fperm.st_mode, permmask);
        ap_log_rerror("mod_absec.c", 282, 1, APLOG_ERR, APR_SUCCESS, r, "authz_check_absec : Aucune permission pour la methode %s (%o, %o)", r->method, fperm.st_mode, permmask);
        return AUTHZ_DENIED;
    }

    // If file is world accessible for asked method return content
    if (fperm.st_mode & 0x7 & permmask) {
        /* if so, return, no need to check user perms */
        //ap_rprintf(r, "Fichier public<br/>\r\n");
        ap_log_rerror("mod_absec.c", 282, 1, APLOG_ERR, APR_SUCCESS, r, "authz_check_absec : Fichier accessible a tous");
        return AUTHZ_GRANTED;
    }

    if (!user) {
        ap_log_rerror("mod_absec.c", 282, 1, APLOG_ERR, APR_SUCCESS, r, "authz_check_absec : No user");
        return AUTHZ_DENIED_NO_USER;
    }

    struct passwd *pw; 
    pw = getpwnam(user);

    // If file is user readable and user match return content
    if ((fperm.st_uid==pw->pw_uid) && (fperm.st_mode & 0700 & permmask)) {
        //ap_rprintf(r, "Fichier propriétaire<br/>\r\n");
        ap_log_rerror("mod_absec.c", 282, 1, APLOG_ERR, APR_SUCCESS, r, "authz_check_absec : Fichier propriétaire<br/>\r\n");
        return AUTHZ_GRANTED;
    } else {
        ap_log_rerror("mod_absec.c", 282, 1, APLOG_ERR, APR_SUCCESS, r, "authz_check_absec : No user access %d", pw->pw_uid);
    }

    // If file is group readable and primary group match return content
    if ((fperm.st_gid==pw->pw_gid) && (fperm.st_mode & 0070 & permmask)) {
        //ap_rprintf(r, "Fichier groupe<br/>\r\n");
        ap_log_rerror("mod_absec.c", 282, 1, APLOG_ERR, APR_SUCCESS, r, "authz_check_absec : Fichier groupe<br/>\r\n");
        return AUTHZ_GRANTED;
    } else {
        ap_log_rerror("mod_absec.c", 282, 1, APLOG_ERR, APR_SUCCESS, r, "authz_check_absec : No group access %d", pw->pw_gid);
    }

    // check supplemental groups
    gid_t grouplist[16];
    int grouplistsize = 16;
    int groupreturn;
    groupreturn = getgrouplist(user, pw->pw_gid, grouplist, &grouplistsize);
    if (groupreturn >= 0) {
        ap_log_rerror("mod_absec.c", 282, 1, APLOG_ERR, APR_SUCCESS, r, "authz_check_absec : liste groups (%d)", grouplistsize);
        for (int i=0; i<grouplistsize; i++) {
            ap_log_rerror("mod_absec.c", 282, 1, APLOG_ERR, APR_SUCCESS, r, "authz_check_absec : group %d", grouplist[i]);
            // If file is group readable and match a supplemental group return content
            if ((fperm.st_gid==grouplist[i]) && (fperm.st_mode & 0070 & permmask)) {
                ap_log_rerror("mod_absec.c", 282, 1, APLOG_ERR, APR_SUCCESS, r, "authz_check_absec : Fichier group supp");
                return AUTHZ_GRANTED;
            }
        }
    }

    ap_log_rerror("mod_absec.c", 282, 1, APLOG_ERR, APR_SUCCESS, r, "authz_check_absec : DENIED<br/>\r\n");
    return AUTHZ_DENIED;

    if (r->user==NULL) {
        return AUTHZ_DENIED;
    }

    return AUTHZ_GRANTED;
}

////////////////////////////////////////////////////////////////
static const authn_provider authn_absec_provider = 
{
    &authn_check_absec,
    NULL
};

////////////////////////////////////////////////////////////////
static const authz_provider authz_absec_provider = 
{
    &authz_check_absec,
    NULL
};

////////////////////////////////////////////////////////////////
static void absec_register_hooks(apr_pool_t *p)
{
    ap_register_auth_provider(p, AUTHN_PROVIDER_GROUP, "absec", "0", &authn_absec_provider, AP_AUTH_INTERNAL_PER_CONF);
    ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "absec", "0", &authz_absec_provider, AP_AUTH_INTERNAL_PER_CONF);
}

////////////////////////////////////////////////////////////////
/* Dispatch list for API hooks */
AP_DECLARE_MODULE(absec) = {
    STANDARD20_MODULE_STUFF, 
    authnz_absec_config,   /* create per-dir    config structures */
    NULL,                  /* merge  per-dir    config structures */
    NULL,                  /* create per-server config structures */
    NULL,                  /* merge  per-server config structures */
    absec_auth_basic_cmds, /* table of config file commands       */
    absec_register_hooks   /* register hooks                      */
};

