/**************************************************************************
 mod_auth_remote.c
 v 0.1

srp@symonds.net
srp@grex.org

License : GPL (http://www.gnu.org/licenses/licenses.html) 
***************************************************************************/

#include "apr_strings.h"
#include "apr_network_io.h"
#include "ap_config.h"
#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_log.h"
#include "http_protocol.h"
#include "http_request.h"


typedef struct {
  int port;
  char *remote_server;
  char *remote_uri;
} remote_auth_config_rec;

static int get_remote_auth(char *, const char *,remote_auth_config_rec *,request_rec
*) ;

static void *create_auth_remote_dir_config(apr_pool_t *p, char *d)
{
  remote_auth_config_rec *conf = (remote_auth_config_rec *)
  apr_palloc(p,sizeof(*conf));
  conf->port = 80;
  conf->remote_server = NULL;
  conf->remote_uri = NULL;
  return conf;
}

static const command_rec auth_remote_cmds[] = 
  {
        AP_INIT_TAKE1("AuthRemotePort", ap_set_int_slot, 
                  (void *)APR_OFFSETOF(remote_auth_config_rec, port),
                  OR_AUTHCFG, "remote port to authenticate against"),
        AP_INIT_TAKE1("AuthRemoteServer", ap_set_string_slot, 
                  (void *)APR_OFFSETOF(remote_auth_config_rec, remote_server),
                  OR_AUTHCFG, "remote server to authenticate against"),
        AP_INIT_TAKE1("AuthRemoteURL", ap_set_string_slot,
                  (void *)APR_OFFSETOF(remote_auth_config_rec, remote_uri),
                  OR_AUTHCFG, "remote url to use"),
    {NULL}
  };

module AP_MODULE_DECLARE_DATA auth_remote_module;

/****
         The authenticating server is supposed to authorize you too
****/
static int check_user_access(request_rec *r)
{
  return OK;  // comment this line if you want to authorize locally ( ? )
  return DECLINED;
}

static int authenticate_basic_user(request_rec *r)
{
  remote_auth_config_rec *conf = ap_get_module_config(r->per_dir_config,
                                                      &auth_remote_module);

  const char *client_pass;
  int what;

  if((what = ap_get_basic_auth_pw(r, &client_pass)))
    return what;     /** error occurred **/

  if(!conf->remote_server || !conf->remote_uri)
    return DECLINED;
  
  /** 
      we have the users passwd, now connect to the remote server
      as this user and validate that he is who he says he is
  **/
 
 if(get_remote_auth(r->user, client_pass, conf, r) != OK)
    {
      ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                    "user %s: authentication failure",
                    r->user);
      ap_note_basic_auth_failure(r);
      return HTTP_UNAUTHORIZED;
    }
  return OK;
}

/**
   Connects to remote server - and auth's the user
**/

static int get_remote_auth(char *user, const char *passwd,
                           remote_auth_config_rec *conf, request_rec *r)
{
  int bytes = 0, itemp = 0;
  char *user_pass, *encoded, *req_b,*rbuff;
  apr_status_t val;
  apr_socket_t *socket;
  apr_sockaddr_t *addr;
  

  if((val =apr_socket_create(&socket, APR_INET, SOCK_STREAM, r->pool)) != APR_SUCCESS)
    { 
      ap_log_rerror(APLOG_MARK, APLOG_ERR, val, r,
                    "auth_remote: could not create socket");
      return HTTP_INTERNAL_SERVER_ERROR;
    }
  
   apr_setsocketopt(socket,  APR_SO_TIMEOUT, (int)r->server->timeout);
   if((val = apr_sockaddr_info_get(&addr, conf->remote_server, APR_INET,
                                   conf->port,0,r->pool)) != APR_SUCCESS)
     {
       ap_log_rerror(APLOG_MARK, APLOG_ERR, val, r,
                     "auth_remote: could not look up remote server %s:%d",
                     conf->remote_server, conf->port);
       return HTTP_INTERNAL_SERVER_ERROR;
     }
   val = apr_connect(socket, addr);
   if(val != APR_SUCCESS)
     {
       ap_log_rerror(APLOG_MARK, APLOG_ERR, val, r,
                     "auth_remote:could not connect to authentication server %s:%d",
                      conf->remote_server, conf->port);
       return HTTP_INTERNAL_SERVER_ERROR;
     }
   
   /***
       form a authorization header with the username/passwd obtained from the
      client. Send a HTTP request to the remote server and read the status line.
   ***/

   user_pass = apr_pstrcat(r->pool, user, ":", passwd, NULL);
   itemp = strlen(user_pass);
   encoded = apr_palloc(r->pool, (((itemp+2)/3)*4)+2); 
   apr_base64_encode(encoded, user_pass, itemp);
   req_b = apr_pstrcat(r->pool, "HEAD ", conf->remote_uri," HTTP/1.0 ", 
                       "\nAuthorization: Basic ",encoded,CRLF,CRLF,NULL);
   bytes = strlen(req_b);
   val = apr_send(socket, req_b, &bytes);
   if(val != APR_SUCCESS)
     {
       ap_log_rerror(APLOG_MARK, APLOG_ERR, val, r,
                     "auth_remote: could not make http request");
       return HTTP_INTERNAL_SERVER_ERROR;
     }
   /**
     20 bytes is enough to read the HTTP status line
   **/
   rbuff = apr_palloc(r->pool, 20); 
   val = apr_recv(socket, rbuff, &bytes);
   apr_socket_close(socket);
   if(val != APR_SUCCESS)
     {
       ap_log_rerror(APLOG_MARK, APLOG_ERR, val, r,
                     "auth_remote: could not get http response");
       return HTTP_INTERNAL_SERVER_ERROR;
     }

   while(*++rbuff != ' ');while(*++rbuff == ' ');
   *(rbuff+1) = '\0';

   //itemp now has the 1st digit of the response code.

   itemp = atoi(rbuff);
   if(itemp == 2)
         return OK;
   return  HTTP_UNAUTHORIZED;
}

static void register_hooks(apr_pool_t *p)
{
  ap_hook_check_user_id(authenticate_basic_user, NULL, NULL, APR_HOOK_MIDDLE);
  /**
         check_user_access will return OK without performing __any__ checks.
         the remote server is supposed to authenticate & authorize
  **/
  ap_hook_auth_checker(check_user_access,NULL,NULL,APR_HOOK_MIDDLE);
}
    
module AP_MODULE_DECLARE_DATA auth_remote_module =
{
  STANDARD20_MODULE_STUFF,
  create_auth_remote_dir_config,
  NULL,
  NULL,
  NULL,
  auth_remote_cmds,
  register_hooks
};