Files
ABSEC/mod_absec.c
T

442 lines
16 KiB
C
Executable File

/////
//
// 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 <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"
////////////////////////////////////////////////////////////////
/* Main routine - called after request processing */
static int absec_handler_last(request_rec *r)
{
// Is this module really called?
if (strcmp(r->handler, "absec")) {
return DECLINED;
}
////////
/* http method validate the perm asked (r/w vs get/post,put) */
ap_rprintf(r, "After Method: %s<br/>\r\n", r->method);
int permmask = 0;
if (strcmp(r->method,"GET")!=0)
{
// Not a GET, it's too late to do anything
ap_rprintf(r, "Not a GET post treatement is too late!\n<br/>");
return (DECLINED);
}
r->content_type = "text/html";
//ap_rprintf(r, "The sample page from mod_absec.c %s \n<br/>", r->args);
ap_rprintf(r, "After GET Url: %s from %s \n<br/>", r->filename, r->uri);
return (DECLINED);
}
////////////////////////////////////////////////////////////////
/* Main routine - called before request processing */
static int absec_handler_first(request_rec *r)
{
ap_rprintf(r, "Before Method: %s<br/>\r\n", r->method);
// Is this module really called?
/*if (strcmp(r->handler, "absec")) {
ap_rprintf(r, "DECLINED<br/>\r\n");
return DECLINED;
}*/
////////
/* 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
////////
/* check file permission on filesystem */
/* should include <sys/stat.h> */
struct stat fperm;
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);
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);
return (OK);
}
// 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");
return (DECLINED);
}
////////
/* Check if we have a basic auth user */
const char* auth64p;
// Check if we have an auth header
auth64p = apr_table_get(r->headers_in,"Authorization");
// If no basic auth, ask for one
if (auth64p==NULL) {
r->content_type = "text/html";
apr_table_setn(r->err_headers_out,
(PROXYREQ_PROXY == r->proxyreq) ? "Proxy-Authenticate"
: "WWW-Authenticate",
apr_pstrcat(r->pool, "Basic realm=\"PAS DE USER ", ap_auth_name(r),
"\"", NULL));
return HTTP_UNAUTHORIZED;
}
////////
/* Retrieve user/pass from http basic auth header */
// Get the basic auth base64 string and decode it
// Start at char 6 to skip 'Basic '
char *auth64;
auth64 = apr_pstrdup(r->pool, auth64p+6);
char *auth;
auth = apr_pcalloc(r->pool, 64);
apr_base64_decode(auth, auth64);
char *user;
char *pass;
user = apr_strtok(auth, ":", &pass);
r->content_type = "text/html";
ap_rprintf(r, "Url: %s from %s \n<br/>", r->filename, r->uri);
//ap_rprintf(r, "Headers Authorization: %s \n<br/>", auth64);
//ap_rprintf(r, "User/Pass: %s/%s \n<br/>", user, pass);
int pam_result = check_user(user, pass);
if ( (pam_result==PAM_ERROR_START) || (pam_result==PAM_ERROR_STOP) ) {
return HTTP_INTERNAL_SERVER_ERROR;
}
if (pam_result==PAM_ERROR_INVALID_CRED) {
r->content_type = "text/html";
apr_table_setn(r->err_headers_out,
(PROXYREQ_PROXY == r->proxyreq) ? "Proxy-Authenticate"
: "WWW-Authenticate",
apr_pstrcat(r->pool, "Basic realm=\"USER/PASS INVALIDE ", ap_auth_name(r),
"\"", NULL));
return HTTP_UNAUTHORIZED;
}
////////
// Continue checking permission
////////
/* Retrieve user details from /etc/passwd to get uid and primary group */
/* Should include <pwd.h> */
struct passwd *pw;
if((pw = getpwnam(user)) == NULL)
{
// Should never happend as already verified with PAM
ap_rprintf(r, "NULL \n<br/>");
apr_table_setn(r->err_headers_out,
(PROXYREQ_PROXY == r->proxyreq) ? "Proxy-Authenticate"
: "WWW-Authenticate",
apr_pstrcat(r->pool, "Basic realm=\"USER INCONNU ", ap_auth_name(r),
"\"", NULL));
// User cannot be found, unauthorized
return HTTP_UNAUTHORIZED;
}
// 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");
return (DECLINED);
}
// 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");
return (DECLINED);
}
////////
/* Check supplemental groups */
/* Should include <grp.h> */
//ap_rprintf(r, "Fichier propriétaire %d %d %o %o<br/>\r\n", fperm.st_uid, i, fperm.st_mode, 0400);
gid_t grouplist[16];
int grouplistsize = 16;
int groupreturn;
groupreturn = getgrouplist(user, pw->pw_gid, grouplist, &grouplistsize);
if (groupreturn >= 0) {
ap_rprintf(r, "OK liste des groupes (%d)<br/>\r\n", grouplistsize);
for (int i=0; i<grouplistsize; i++) {
ap_rprintf(r, "group: %d\r\n", 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_rprintf(r, "Fichier groupe supplementaire<br/>\r\n");
return (DECLINED);
}
}
}
// else decline request
ap_rprintf(r, "Aucuns droits de voir le fichier<br/>\r\n");
apr_table_setn(r->err_headers_out,
(PROXYREQ_PROXY == r->proxyreq) ? "Proxy-Authenticate"
: "WWW-Authenticate",
apr_pstrcat(r->pool, "Basic realm=\"NON AUTHORISE", ap_auth_name(r),
"\"", NULL));
return HTTP_UNAUTHORIZED;
}
////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////
// Validate 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 ressource access
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", 282, 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
////////
/* check file permission on filesystem */
/* should include <sys/stat.h> */
struct stat fperm;
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;
}
// 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;
}
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_hook_handler(absec_handler, NULL, NULL, APR_HOOK_MIDDLE);
//ap_hook_handler(absec_handler_last, NULL, NULL, APR_HOOK_LAST);
//ap_hook_handler(absec_handler_first, NULL, NULL, APR_HOOK_FIRST);
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);
}
////////////////////////////////////////////////////////////////
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_FLAG("AuthBasicAuthoritative", set_authoritative, NULL, OR_AUTHCFG,
"Set to 'Off' to allow access control to be passed along to "
"lower modules if the UserID is not known to this module"),
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}
};
////////////////////////////////////////////////////////////////
/* Dispatch list for API hooks */
module AP_MODULE_DECLARE_DATA absec_module;
AP_DECLARE_MODULE(absec) = {
STANDARD20_MODULE_STUFF,
NULL, /* 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 */
};