/*
 * mod_auth_imap: authentication via IMAP server
 * Version: 1.1 - BETA release.
 *
 * brillat-apachemodule@mainsheet.org (Ben Brillat), 
 *   based on mod_auth_pop by Milos Prodanovic <awl@verat.net>
 *
 * Follows the IMAP v4rev1 RFC - RFC 2060
 * http://www.ietf.org/rfc/rfc2060.txt
 */


#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_log.h"
#include "http_protocol.h"
#include "http_conf_globals.h"

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <netdb.h>
#include <syslog.h>
 
#define _OK 1

int Sock;


/*******************************************************************************
 * tcp_gets
 * 	reads a line from the network (up to \n or EOF)
 *     Always returns 0
 *******************************************************************************/
int tcp_gets(int s, char *res,int len) {
	char c;
	int cur=0,rc;
 
	memset(res,0,len+1);
  
	while( (rc = read(s, &c, 1))!=EOF) {
		if(cur<len)
		res[cur]=c;
		cur++;
		if(c=='\n')
        		break;     
	}
	return 0;
}

/*******************************************************************************
 * clean_up
 * 	closes the socket
 *******************************************************************************/
void clean_up(int s) { 
	close(s);
}

/*******************************************************************************
 * tcp_puts
 * 	write a line to the network
 *     performs a bzero (via memset) on the memory to be used...
 *******************************************************************************/
int tcp_puts(int s, char *sttr) {
	int rr,len;
	char line[512];

	memset(line,0,512);
	len=strlen(sttr);
	if(len>510) len=510;
	strncpy(line,sttr,len);
	rr=write(s,line,strlen(line));
	return rr;
}


/*******************************************************************************
 * imap_tcp_open
 * 	connects to the remote server
 * 	returns a socket
 *******************************************************************************/
int imap_tcp_open(char *hostname, int port) {
    struct hostent *HOST;
    struct sockaddr_in sai;
    int s;


    if((HOST = gethostbyname(hostname)) == (struct hostent *)NULL) {
        ap_log_error(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,NULL,"mod_auth_imap: Hostname unknown.");
        return !_OK;
    }
    memset((char *)&sai, '\0', sizeof(sai));
    memcpy((char *)&sai.sin_addr, (char *)HOST -> h_addr,
           HOST -> h_length);
    sai.sin_family = HOST -> h_addrtype;
    sai.sin_port = htons(port);
 
    if((s = socket(HOST -> h_addrtype, SOCK_STREAM, 0)) == -1) {
        ap_log_error(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,NULL,"mod_auth_imap: socket problem");
        clean_up(s);
        return !_OK;
    }
 
    if(connect(s, (struct sockaddr *)&sai, sizeof(sai)) == -1) {
        ap_log_error(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,NULL,"mod_auth_imap: connect() problem");
        clean_up(s);
 	return !_OK;
   }
    return s;
}
 

/******************************************************************
 *imap_do_rfc2060
 * Check the U/P against the selected IMAP server
 *   according to IMAP v4rev1 RFC
 * Return: 1 or 0
 ******************************************************************/
int imap_do_rfc2060(char *host, char *username, char *pass, char *cport, int logflag) {
  char result[512],buf[512];
  int ret=0;
  int port;

	
	port=atoi(cport);

	// Verify that the username and password are of a reasonable length (<100)
	if( strlen(username)>100 || strlen(pass)>100 ) {
		ap_log_error(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,NULL,"username/password too long for mod_auth_imap");
		printf("Ouch - u/p too long!\n");
		return !_OK;
	}


	
	Sock=imap_tcp_open(host,port);
	if(!Sock)
	{
		return !_OK;
	}

	
	//Just eat the initial response from the server, up to 500 chars.
	tcp_gets(Sock,result,500);


	//Send a request for CAPABILITY
	memset(buf,0,500);
	sprintf(buf,"A001 CAPABILITY\r\n");
	tcp_puts(Sock,buf);
	//get the capability line...
	tcp_gets(Sock,result,500);
	//get the "A001 OK CAPABILITY completed" line..
	tcp_gets(Sock,result,500);
	if(strncmp(result,"A001 OK CAPABILITY",18)) {
		ap_log_error(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,NULL,"mod_auth_imap: Server does not support imap CAPABILITY.");
		ret=!_OK;
		clean_up(Sock);
		return ret;  //BAIL!
	}

	//Log In w/given Username & Password
	memset(buf,0,500);
	sprintf(buf,"A002 LOGIN %s %s\r\n", username, pass);
	tcp_puts(Sock,buf);
	tcp_gets(Sock,result,500);
	if(!strncmp(result,"A002 OK",7))
		{
		if(logflag){
			ap_log_error(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,NULL,"mod_auth_imap: Verified login for user %s.", username);
		}
		ret=_OK;
		}
	else if(!strncmp(result,"A002 NO",7))
	{
		if(logflag){
			ap_log_error(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,NULL,"mod_auth_imap: Login failed for user %s.", username);
			ap_log_error(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,NULL,"mod_auth_imap: Server said: %s", result);
		}
		ret=!_OK;
	}
	else //it must have told us BYE and disconnected
	{
		if(logflag){
			ap_log_error(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,NULL,"mod_auth_imap: Premature server disconnect for user %s.", username);
			ap_log_error(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,NULL,"mod_auth_imap: Server said: %s", result);
		}
		ret=!_OK;
		clean_up(Sock);
		return ret;  //BAIL!
	}


	//Log out...
	memset(buf,0,500);
	sprintf(buf,"A003 LOGOUT\r\n");
	tcp_puts(Sock,buf);
	//read the BYE line
	tcp_gets(Sock,result,500);
	//read the OK LOGOUT
	tcp_gets(Sock,result,500);
	if(!strncmp(result,"A003 OK",7)) {
		/*if(logflag){
			ap_log_error(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,NULL,"mod_auth_imap: OK logout for %s.", username);
		}*/
		//don't change the return here - we still need to know if
		//the user/pass was good from the LOGIN command!
	}
	else {
		if(logflag){
			ap_log_error(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,NULL,"mod_auth_imap: Error in logout for %s.", username);
			ap_log_error(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,NULL,"mod_auth_imap: Server said: %s", result);
		}
		ret=!_OK;
	}

	
	//close the connection
	clean_up(Sock);


	return ret;
}

 
/******************************************************************
* auth_config_struct 
* The struct with all data passed from .htaccess or httpd.conf
 ******************************************************************/
typedef struct auth_config_struct {
    char *imap_server;
    char *imap_port;
    int   imap_log;
    int   imap_authoritative;
    int   enabled;
} imap_config_rec;

typedef struct {
  char *name, *pw;
} auth_imap_userinfo;


/******************************************************************
 * Defaults for the .htaccess options
 ******************************************************************/
static void *create_imap_dir_config (pool *p, char *d)
{
    imap_config_rec *sec =
	(imap_config_rec *) ap_palloc (p, sizeof(imap_config_rec));
    sec->imap_server = "127.0.0.1";     /* localhost is server by default */
    sec->imap_port = "143"; 	/* port is 143 by default */
    sec->imap_log = 0; /* Logging is off by default */
    sec->imap_authoritative = 1; /* keep the fortress secure by default */
    sec->enabled = 0; /* disable this authentication by defualt */
    return sec;
}


/*******************************************************************************
 * set_imap_slot
 *******************************************************************************/
static const char *set_imap_slot (cmd_parms *cmd, char *struct_ptr, char *arg)
{
   int offset = (int)cmd->info;
   *(char **)(struct_ptr + offset) = ap_pstrdup (cmd->pool, arg);
   return NULL;
}

/*******************************************************************************
 * set_flag_slot - transfers the Auth_IMAP_Authoritative directive to the structure
 *******************************************************************************/
static const char *set_flag_slot (cmd_parms *cmd, imap_config_rec *sec, char *arg)
{  /*warning - the next line generates warnings.  it's ok. */
   sec->imap_authoritative = (unsigned char) arg;
   return NULL;
}

/*******************************************************************************
 * set_log_slot - transfers the Auth_IMAP_Log directive to the structure
 *******************************************************************************/
static const char *set_log_slot (cmd_parms *cmd, imap_config_rec *sec, char *arg)
{  /*warning - the next line generates warnings.  it's ok. */
   sec->imap_log = (unsigned char) arg;
   return NULL;
}

/*******************************************************************************
 * auth_enable
 *******************************************************************************/
static
char* auth_enable(cmd_parms *cmd, imap_config_rec *config, int arg)
{
  config->enabled = arg;
  return NULL;
}

/*******************************************************************************
 * .htaccess and httpd.conf configuration options
 *******************************************************************************/
static command_rec imap_cmds[] = {
{ "Auth_IMAP_Server", set_imap_slot,
  (void*)XtOffsetOf(imap_config_rec,imap_server), OR_AUTHCFG, TAKE1,
     "IMAP server . " },
{ "Auth_IMAP_Port", set_imap_slot,
  (void*)XtOffsetOf(imap_config_rec,imap_port), OR_AUTHCFG, TAKE1,
     "IMAP port . " },
{ "Auth_IMAP_Log", set_log_slot,
  (void*)XtOffsetOf(imap_config_rec,imap_log),
    OR_AUTHCFG, FLAG, 
   "Set to 'yes' to enable some logging in the Apache ErrorLog" },
{ "Auth_IMAP_Authoritative", set_flag_slot,
  (void*)XtOffsetOf(imap_config_rec,imap_authoritative),
    OR_AUTHCFG, FLAG, 
   "Set to 'no' to allow access control to be passed along to lower modules if the UserID is not known to this module" },
{ "Auth_IMAP_Enabled", (const char*(*)())auth_enable, NULL, OR_AUTHCFG, FLAG,
    "on|off - determines if IMAP authentication is enabled; default is off" },
{ NULL }
};






module auth_imap_module;


/*******************************************************************************
 * imap_authenticate_basic_user  -    the main function
 * 	connects remotely, verifies if the user/pass combo is correct
 * 	returns "OK", "DECLINED", or "AUTH_REQUIRED" (see defines)
 *******************************************************************************/
static int imap_authenticate_basic_user (request_rec *r)
{
    imap_config_rec *sec =
      (imap_config_rec *)ap_get_module_config (r->per_dir_config, &auth_imap_module);
    conn_rec *c = r->connection;
    int res,i;
    char *server = sec->imap_server;
    char *port = sec->imap_port;
    auth_imap_userinfo userinfo = { NULL, NULL }; 


/* is this module disabled? */
  if (!sec->enabled)
    return DECLINED;
 
    if ((res = ap_get_basic_auth_pw (r, (const char**)&(userinfo.pw)))) return res;
   

  /* this is only set after get_basic_auth_pw was called */
  userinfo.name = r->connection->user;
 
    if(!sec->imap_server)  
        return DECLINED;

    /* send IMAP authen, server port, username, sent_pw */
    i=imap_do_rfc2060(server,userinfo.name,userinfo.pw,port,sec->imap_log);    

    if (i==1) return (OK);
    else return AUTH_REQUIRED;
}
    

/*******************************************************************************
 * imap_check_user_access
 * 	checks the username against the allowed users in the .htaccess or httpd.conf
 * 	returns "OK", "DECLINED", or "AUTH_REQUIRED" (see defines)
 *******************************************************************************/    
static int imap_check_user_access (request_rec *r) {
    imap_config_rec *sec =
     (imap_config_rec *)ap_get_module_config (r->per_dir_config, &auth_imap_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 = ap_requires (r);
    require_line *reqs;

    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 = ap_getword(r->pool, &t, ' ');
        if(!strcmp(w,"valid-user"))
            return OK;
        if(!strcmp(w,"user")) {
            while(t[0]) {
                w = ap_getword_conf (r->pool, &t);
                if(!strcmp(user,w))
                    return OK;
            }
        }
	else if(!strcmp(w,"group"))
	        return DECLINED;
    }
    
    if (!method_restricted)
      return OK;

    if (!(sec->imap_authoritative))
      return DECLINED;

    ap_note_basic_auth_failure (r);
    return AUTH_REQUIRED;
}


/*******************************************************************************
 * module function definitions
 * 	defines the functions called by apache for this module...
 *******************************************************************************/
module auth_imap_module = {
   STANDARD_MODULE_STUFF,
   NULL,                        /* initializer */
   create_imap_dir_config,       /* dir config creater */
   NULL,			/* dir merger --- default is to override */
   NULL,			/* server config */
   NULL,			/* merge server config */
   imap_cmds,                    /* command table */
   NULL,			/* handlers */
   NULL,			/* filename translation */
   imap_authenticate_basic_user, /* check_user_id */
   imap_check_user_access,       /* check auth */
   NULL,			/* check access */
   NULL,			/* type_checker */
   NULL,			/* fixups */
   NULL,			/* logger */
   NULL				/* header parser */
};




