/*
Copyright (c) 2010, Dirk Krause
All rights reserved.

Redistribution and use in source and binary forms,
with or without modification, are permitted provided
that the following conditions are met:

* Redistributions of source code must retain the above
  copyright notice, this list of conditions and the
  following disclaimer.
* Redistributions in binary form must reproduce the above 
  opyright notice, this list of conditions and the following
  disclaimer in the documentation and/or other materials
  provided with the distribution.
* Neither the name of the Dirk Krause nor the names of
  contributors may be used to endorse or promote
  products derived from this software without specific
  prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
*/


/**	@file useraudi.c	Useraudi main module. */


/** In the useraudi module. */
#define USERAUDI_C	1

#include "useraudi.h"




#line 49 "useraudi.ctr"




/**	Name of socket to connect to backend daemon.
*/
static char			*sockname = NULL;

/**	Storage of allowed IP addresses.
*/
static dk_storage_t		*s_allow = NULL;

/**	Iterator for storage of allowed IP addresses.
*/
static dk_storage_iterator_t	*i_allow = NULL;

/**	Input line buffer.
*/
static char il[USERAUD_LINESIZE];

/**	Output line buffer.
*/
static char ol[USERAUD_LINESIZE];

/**	Socket.
*/
static char *cmd_socket[] = { "socket", NULL };

/**	Allow.
*/
static char *cmd_allow[] = { "allow", NULL };

/**	Network user name test.
*/
static char *cmd_net_user_name_test[] = {
"net", "user", "name", "test", NULL
};

/**	Configuration options of interest for useraudi.
*/
static char **cmds[] = {
  /*  0 */ cmd_socket,
  /*  1 */ cmd_allow,
  /*  2 */ cmd_net_user_name_test,
  NULL
};


/**	Flag: Allow user name tests.
*/
static int allow_username_test = 0;

/**	Flag: A user name test was attempted.
*/
static int attempt_username_test = 0;

/**	Analyze whether or not the text \a t has the given
	number \a n of strings.
	@param	n	Number of strings to find.
	@param	t	Text to split.
	@return	Flag: Number matches.
*/
static
int
text_parts DK_P2(int,n, char *,t) {
  int back = 0;
  int i = 0;
  char *p;
  p = dkstr_start(t, NULL);
  while(p) {
    i++;
    p = dkstr_next(p, NULL);
  }
  if(i == n) { back = 1; }
  return back;
}


/**	Analyze whether or not to contact backend.
	@param	opcode	Request type.
	@param	args	Request argument.
	@return	Flag whether or not to contact backend.
*/
static
int
analyze DK_P2(int,opcode, char *,args) {
  int back = 0;
  switch(opcode) {
    case 100: case 101: {
      back = text_parts(2, args);
    } break;
    case 102: case 103: {
      back = text_parts(3, args);
    } break;
    case 104: case 105: {
      back = text_parts(2, args);
    } break;
    case 106: {
      back = text_parts(2, args);
    } break;
    case 107: case 108: {
      back = text_parts(4, args);
    } break;
    case 109: {
      back = text_parts(2, args);
    } break;
    case 110: case 111: {
      if(allow_username_test) {
        back = text_parts(1, args);
      }
    } break;
  }
  return back;
}

/**	Run service loop.
	@param	is_allowed	Flag: Client is allowed to use service.
*/
static
void
run_service_loop DK_P1(int,is_allowed) {
  char *p1;			/* Start of input line. */
  char *p2;			/* Start of arguments. */
  char *l;
  int cc = 1;
  int lineok;
  int opcode;
  USERAUD_CONNECTION *uc = NULL;
  USERAUD_RESPONSE *ur = NULL;
  if(is_allowed) {
    uc = uacl_open(sockname);
  }
  while(cc) {
    cc = 0;
    attempt_username_test = 0;
    if(fgets(il, sizeof(il), stdin)) {
      cc = 1; lineok = 0;
      p1 = dkstr_start(il, NULL);
      if(p1) {
        dkstr_chomp(p1, NULL);
	strcpy(ol, p1);
	strcpy(il, ol);
	p1 = il;
	p2 = dkstr_next(ol, NULL);
	if(sscanf(p1, "%d", &opcode) == 1) {
	  lineok = analyze(opcode, p2);
	}
      }
      if(lineok) {
        if(is_allowed) {	/* Contact backend server. */
	  if(uc) {
	    ur = uacl_process(uc, p1);
	    if(ur) {
	      uacl_response_reset(ur);
	      while((l = uacl_response_line(ur))) {
	        printf("%s\n", l); fflush(stdout);
	      }
	      uacl_response_close(ur); ur = NULL;
	    } else {
	      /* printf("409\n"); fflush(stdout); */
	      switch(uc->ec) {
	        case UA_ERROR_MEMORY: {
		  printf("409\n"); fflush(stdout);
		} break;
		case UA_ERROR_NO_RESPONSE:
		case UA_ERROR_SOCKET_FAILED:
		case UA_ERROR_CONNECT_FAILED: {
		  printf("400\n"); fflush(stdout);
		} break;
		case UA_ERROR_ILLEGAL_ARGUMENTS:
		case UA_ERROR_SOCKET_NAME_TOO_LONG: {
		  printf("410\n"); fflush(stdout);
		} break;
	      }
	    }
	  } else {
	    printf("409\n"); fflush(stdout);
	  }
	} else {		/* Send failure code. */
	  switch(opcode) {
	    case 100: case 101: case 102: case 103: {
	      printf("401\n"); fflush(stdout);
	    } break;
	    case 104: case 105: {
	      printf("403\n"); fflush(stdout);
	    } break;
	    case 106: {
	      printf("406\n"); fflush(stdout);
	    } break;
	    case 107: case 108: {
	      printf("407\n"); fflush(stdout);
	    } break;
	    case 109: {
	      printf("200\n");
	    } break;
	  }
	}
      } else {
        if(attempt_username_test && (!allow_username_test)) {
	  printf("400\n"); fflush(stdout);
	} else {
          printf("411\n"); fflush(stdout);
	}
      }
    }
  }
  if(uc) { uacl_close(uc); }
}



/**	Run a session.
	Read request (one input line), check request, forward it to
	backend daemon, receive response, send response back.
*/
static
void
run_session DK_P0() {
  int is_allowed = 1;		/* Flag: Peer is allowed to connect. */
  struct sockaddr_in	sin;
  socklen_t		st;
  UAPEER		*p;
  unsigned long		ip;
  
  st = sizeof(sin);
  if((s_allow) && (i_allow)) {
    if(getpeername(0, (struct sockaddr *)(&sin), &st) == 0) {
      is_allowed = 0;
      ip = sin.sin_addr.s_addr;
      dksto_it_reset(i_allow);
      while(((p = (UAPEER *)dksto_it_next(i_allow)) != NULL) && (!is_allowed)) {
        if((ip & (p->mask)) == ((p->ip) & (p->mask))) {
	  is_allowed = 1;	
	}
      }
    }
  }
  if(is_allowed) {
    run_service_loop(is_allowed);
  }
  
}



/**	Read configuration.
	@param	fn	Configuration file name.
	@return	1 on success, 0 on error.
*/
static
int
read_conf DK_P1(char *,fn) {
  int back = 1;
  int active_section = 0;
  unsigned long ip, ma;
  FILE *fipo;
  char *p1, *p2, *p3;
  UAPEER *uap;
  char *parts[16];
  size_t nparts = 0;
  int	ac = 0;
  
  fipo = fopen(fn, "r");
  if(fipo) {						
    while((back) && fgets(il, sizeof(il), fipo)) {
      p1 = dkstr_start(il, NULL);
      if(p1) {
        dkstr_chomp(p1, NULL);			
	if(*p1 != '#') {
	  if(*p1 == '[') {
	    active_section = 0;
	    p1++;
	    p1 = dkstr_start(p1, NULL);
	    if(p1) {
	      p2 = dkstr_chr(p1, ']');
	      if(p2) {
	        *p2 = '\0';
		dkstr_chomp(p1, NULL);
		if(strcmp(p1, "options") == 0) {
		  active_section = 1;
		}
	      } else {
	        back = 0;
	      }
	    } else {
	      back= 0;
	    }			
	  } else {
	    if(active_section) {	
	      p2 = dkstr_chr(p1, '=');
	      if(p2) {
	        *(p2++) = '\0';
		p2 = dkstr_start(p2, NULL);
		if(p2) {
		  dkstr_chomp(p1, NULL);
		  dkstr_chomp(p2, NULL);
	          nparts = dkstr_explode(parts, 15, p1, NULL);
	          if(nparts > 0) {
	            ac = dkstr_find_multi_part_cmd(parts,cmds,0);
		    switch(ac) {
		      case 0: {	/* socket */
		        if(sockname == NULL) {
		          sockname = dkstr_dup(p2);
		          if(!(sockname)) {		
		            back = 0;
		          }			
		        } else {			
		          back = 0;
		        }
		      } break;
		      case 1: {	/* allow */
		        if(!(s_allow)) {
		          s_allow = dksto_open(0);
			  if(s_allow) {
			    i_allow = dksto_it_open(s_allow);
			    if(!(i_allow)) {	
			      dksto_close(s_allow);
			      s_allow = NULL;
			      back = 0;
			    }
			  } else {		
			    back = 0;
			  }
		        }
		        if((s_allow) && (i_allow)) {
		          p3 = dkstr_chr(p2, '/');
			  if(p3) {		
			    *(p3++) = '\0';
			    p3 = dkstr_start(p3, NULL);
			    ip = uatcs_dotted_string_to_ip(p2);
			    ma = 0xFFFFFFFFUL;
			    if(p3) {
			      ma = uatcs_dotted_string_to_ip(p3);
			    }
			  } else {		
			    ip = uatcs_dotted_string_to_ip(p2);
			    ma = 0xFFFFFFFFUL;
			  }
			  uap = dk_new(UAPEER,1);
			  if(uap) {
			    uap->ip = htonl(ip);
			    uap->mask = htonl(ma);
			    if(!dksto_add(s_allow, (void *)uap)) {
			      dk_delete(uap);	
			      back = 0;
			    }
			  } else {		
			    back = 0;
			  }
		        } else {			
		          back = 0;
		        }
		      } break;
		      case 2: {	/* net user name test */
		        if(dkstr_is_bool(p2)) {
			  allow_username_test = (dkstr_is_on(p2) ? 1 : 0);
			} else {
			  allow_username_test = 0;
			}
		      } break;
		    }
	          } else { back = 0; }
	 	} else { back = 0; }
	      } else { back = 0; }
	    }
	  }
	}
      }
    }
    fclose(fipo);
  } else {					
    back = 0;
  } 
  return back;
}



/**	Clean up. Release dynamically allocated memory.
*/
static
void
cleanup DK_P0() {
  UAPEER *p;
  if(s_allow) {
    if(i_allow) {
      dksto_it_reset(i_allow);
      while((p = (UAPEER *)dksto_it_next(i_allow)) != NULL) {
        dk_delete(p);
      }
      dksto_it_close(i_allow);
    }
    dksto_close(s_allow);
  } s_allow = NULL; i_allow = NULL;
  if(sockname) {
    dk_delete(sockname); sockname = NULL;
  }
}



/**	The main function.
	@param	argc	Number of command line arguments.
	@param	argv	Command line arguments array.
	@return	0.
*/
#if DK_HAVE_PROTOTYPES
int main(int argc, char *argv[])
#else
int main(argc, argv) int argc; char *argv[];
#endif
{
  char *cfgname;
  
#line 462 "useraudi.ctr"

  
  if(argc > 1) { cfgname = argv[1]; }
  else { cfgname = uatcs_get_default_config_file_name(); }
  if(read_conf(cfgname)) {
    run_session();
  }
  cleanup();
  
  
#line 471 "useraudi.ctr"

  exit(0); return 0;
}


