/*
 * http_auth: authentication
 *
 * This is a module for Apache 1.2.4, based on mod_auth.c
 *
 * User authentication is done by calling an external program. We are
 * passing login:password to that program on standard input, and expect
 * `true' on its standard output in case of a successful authentication.
 *
 * An example for such an external program is checkpw, which uses the
 * system password file to perform that authentication.
 *
 * Use by adding something like the following lines to .htaccess
 *
 *   AuthName: Whatever
 *   AuthType: Basic
 *   require valid-user
 *   AuthUserProg: /where/ever/is/checkpw
 *
 * (c) Frank Pilhofer 1999, fp@fpx.de.
 */

#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_log.h"
#include "http_protocol.h"
#include <stdio.h>
#include <unistd.h>

typedef struct fp_auth_config_struct {
    char *auth_pwprog;
} fp_auth_config_rec;

static void *
fp_create_auth_dir_config (pool *p, char *d)
{
  fp_auth_config_rec *sec =
    (fp_auth_config_rec *) pcalloc (p, sizeof(fp_auth_config_rec));
  sec->auth_pwprog = NULL; /* just to illustrate the default really */ 
  return sec;
}

static const char *
fp_set_auth_slot (cmd_parms *cmd, void *offset, char *f)
{
  return set_string_slot(cmd, offset, f);
}

static
command_rec fp_auth_cmds[] = {
  {
    "AuthUserProg", fp_set_auth_slot,
    (void*)XtOffsetOf(fp_auth_config_rec,auth_pwprog),
    OR_AUTHCFG, TAKE1, NULL
  },
  { NULL }
};

module auth_prog_module;

static int
fp_check_pw (request_rec * r,
	     const char * user, const char * pw,
	     const char * prog)
{
  char errstr[MAX_STRING_LEN];
  char result[256];
  int in[2], out[2];
  pid_t pid;
  int tot, cnt, ok=0, status;

  if (pipe(in) != 0) {
    log_reason ("Could not connect to auth prog", prog, r);
    return 0;
  }
  if (pipe(out) != 0) {
    log_reason ("Could not connect to auth prog", prog, r);
    close (in[0]);
    close (in[1]);
    return 0;
  }

  if ((pid = fork()) == (pid_t) -1) {
    log_reason ("Could not fork auth prog", prog, r);
    close (in[0]); close (out[0]);
    close (in[1]); close (out[1]);
    return 0;
  }

  if (pid == 0) {
    /*
     * Child
     */
    close (in[1]);
    close (out[0]);
    //    close (0);
    //    close (1);
    dup2  (in[0], 0);
    dup2  (out[1], 1);

    execl (prog, prog, NULL);
    _exit (1);
  }

  /*
   * Parent
   */

  close (in[0]);
  close (out[1]);

  write (in[1], user, strlen(user));
  write (in[1], ":",  1);
  write (in[1], pw,   strlen(pw));
  write (in[1], "\n", 1);
  close (in[1]);

  for (tot=0; tot<255;) {
    if ((cnt = read (out[0], result+tot, 255-tot)) <= 0) {
      if (errno == EINTR) continue;
      break;
    }
    tot += cnt;
  }

  if (tot > 0) {
    if (tot > 4 &&
	result[0] == 't' && result[1] == 'r' &&
	result[2] == 'u' && result[3] == 'e') {
      ok = 1;
    }
  }

  close (out[0]);

  if (waitpid (pid, &status, 0) == pid) {
    if (WIFEXITED(status) == 0) {
      log_reason ("pw checker terminated abnormally", prog, r);
      ok = 0;
    }
    else if (WEXITSTATUS(status) != 0) {
      log_reason ("pw checker terminated abnormally", prog, r);
      ok = 0;
    }
  }

  return ok;
}

/* These functions return 0 if client is OK, and proper error status
 * if not... either AUTH_REQUIRED, if we made a check, and it failed, or
 * SERVER_ERROR, if things are so totally confused that we couldn't
 * figure out how to tell if the client is authorized or not.
 *
 * If they return DECLINED, and all other modules also decline, that's
 * treated by the server core as a configuration error, logged and
 * reported as such.
 */

/* Determine user ID, and check if it really is that user, for HTTP
 * basic authentication...
 */

static int
fp_authenticate_basic_user (request_rec *r)
{
  fp_auth_config_rec *sec =
    (fp_auth_config_rec *) get_module_config (r->per_dir_config,
					      &auth_prog_module);
  conn_rec *c = r->connection;
  char *sent_pw, *real_pw;
  char errstr[MAX_STRING_LEN];
  int res;

  if ((res = get_basic_auth_pw (r, &sent_pw))) return res;
  
  if (!sec->auth_pwprog)
    return DECLINED;

  if (!fp_check_pw (r, c->user, sent_pw, sec->auth_pwprog)) {
    ap_snprintf(errstr, sizeof(errstr), "user %s could not be verified", c->user);
    log_reason (errstr, r->uri, r);
    note_basic_auth_failure (r);
    return AUTH_REQUIRED;
  }
  
  return OK;
}
    
/* Checking ID */
    
int fp_check_user_access (request_rec *r)
{
  fp_auth_config_rec *sec =
    (fp_auth_config_rec *) get_module_config (r->per_dir_config,
					      &auth_prog_module);

  char *user = r->connection->user;
  int m = r->method_number;
  int method_restricted = 0;
  register int x;
  const char *t, *w;
  array_header *reqs_arr = requires (r);
  require_line *reqs;

  /* BUG FIX: tadc, 11-Nov-1995.  If there is no "requires" directive, 
   * then any user will do.
   */

  if (!reqs_arr)
    return (OK);

  reqs = (require_line *)reqs_arr->elts;

  for(x=0; x < reqs_arr->nelts; x++) {
    
    if (! (reqs[x].method_mask & (1 << m))) continue;
    
    method_restricted = 1;

    t = reqs[x].requirement;
    w = getword(r->pool, &t, ' ');
    if(!strcmp(w,"valid-user"))
      return OK;
    if(!strcmp(w,"user")) {
      while(t[0]) {
	w = getword_conf (r->pool, &t);
	if(!strcmp(user,w))
	  return OK;
      }
    }
    else if(!strcmp(w,"group")) {
      return DECLINED;
    }
  }
    
  if (!method_restricted)
    return OK;
  
  note_basic_auth_failure (r);
  return AUTH_REQUIRED;
}

module auth_prog_module = {
   STANDARD_MODULE_STUFF,
   NULL,			/* initializer */
   fp_create_auth_dir_config,	/* dir config creater */
   NULL,			/* dir merger --- default is to override */
   NULL,			/* server config */
   NULL,			/* merge server config */
   fp_auth_cmds,		/* command table */
   NULL,			/* handlers */
   NULL,			/* filename translation */
   fp_authenticate_basic_user,	/* check_user_id */
   fp_check_user_access,	/* check auth */
   NULL,			/* check access */
   NULL,			/* type_checker */
   NULL,			/* fixups */
   NULL,			/* logger */
   NULL				/* header parser */
};
