/*
Copyright (c) 2008-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 prqdctrl.c	Handle control requests.

This module handles
	control ...
requests passed to prqd.

*/



/**	Inside the prqdctrl module. */
#define PRQDCTRL_C	1
#include "prqd.h"

#line 52 "prqdctrl.ctr"




/** Control reqest types. */
static char *ctrl_commands[] = {
  "r$eset",
  "a$dd",
  "i$nfo",
  "d$atabase-cleanup",
  NULL
};



/** Trailing strings in db entries of interest for info files. */
static char *trailing_strings_for_info[] = { "a", "p", NULL };



/** Leading strings in db entries of interest for db cleanup. */
static char *leading_strings_for_cleanup[] = {
  "a",
  "p",
  "j",
  NULL
};


/** Argument names. */
static char *arg_types[] = {
  /*  0 */ "u$ser",
  /*  1 */ "c$lass",
  /*  2 */ "pa$ges",
  /*  3 */ "pr$inter",
  NULL
};



/** Database entry type for printer. */
static char dbentry_type_p[] = { "p" };



/** A name matching all patterns. */
static char pat_ast[] = { "*" };



/**	String comparison function for sorted storage.
	@param	l	Left side string.
	@param	r	Right side string.
	@param	cr	Comparison criteria (ignored).
	@return	String comparison result.
*/
static
int
char_compare DK_P3(void *,l, void *,r, int,cr)
{
  int back = 0;
  if(l) {
    if(r) {
      back = strcmp((char *)l, (char *)r);
    } else { back = 1; }
  } else {
    if(r) { back = -1; }
  }
  return back;
}



/**	Check whether a name matches a pattern.
	@param	pat	The pattern.
	@param	nam	The name (* may be used to match all patterns).
	@return	1 on match, 0 on mismatch.
*/
static
int
pattern_matches DK_P2(char *,pat, char *,nam)
{
  int back = 0;
  
  if((pat) && (nam)) {
    back = 1;
    if(strcmp(pat, pat_ast) != 0) {
      if(strcmp(pat, nam) != 0) {
        back = 0;
      }
    }
  } 
  return back;
}



/**	Check if the user exists.
	@param	n	User name to check.
	@return	1 if user exists, 0 if user does not exist.
*/
static
int
have_user DK_P1(char *,n) {
  int back = 0;
  struct passwd *pw = NULL;
  pw = getpwnam(n);
  if(pw) { back = 1; }
  return back;
}



/**	Database traversal function, used in cleanup request.
	@param	pj	Pointer to print job structure.
	@param	k	Key string.
	@param	v	Value string.
	@return	1 on success, 0 on error, -1 on fatal error
	(abort traversal).
*/
static
int
traverse_cleanup DK_P3(PJ *,pj, char *,k, char *,v)
{
  int	back = 1;
  char	keybuffer[PRQD_DBENTRY_SIZE];
  char	*p1;
  char	*p2;
  int	must_remove;
  must_remove = 0;
  if(strlen(k) < sizeof(keybuffer)) {
    strcpy(keybuffer, k);
    p1 = strchr(keybuffer, ':');
    if(p1) {
      *(p1++) = '\0';
      switch(dkstr_array_index(leading_strings_for_cleanup, keybuffer, 0)) {
	case 0: case 1: {	/* a, p */
	  p2 = strchr(p1, ':');
	  if(p2) {
	    *(p2++) = '\0';
	    if(dksto_it_find_like((pj->prqdc)->cli, p1, 1)) {
	      if(!have_user(p2)) {
	        must_remove = 1;
	      }
	    } else {
	      must_remove = 1;
	    }
	  }
	} break;
	case 2: {	/* j */
	  if(!dksto_it_find_like((pj->prqdc)->pri, p1, 1)) {
	    must_remove = 1;
	  }
	} break;
      }
    }
  } else {
    /* Key too long */
  }
  if(must_remove) {
    p1 = dkstr_dup(k);
    if(p1) {
      if(!dksto_add(pj->cr_st, (void *)p1)) {
        dk_delete(p1);
	back = 0;
	prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(42));
      }
    }
  }
  return back;
}



/**	Database traversal function, used in info request.
	@param	pj	Pointer to print job structure.
	@param	k	Key string.
	@param	v	Value string.
	@return	1 on success, 0 on error, -1 on fatal error
	(abort traversal).
*/
static
int
traverse_info DK_P3(PJ *,pj, char *,k, char *,v)
{
  int back = 1;
  char *p1, *p2, *p3;
  
  p1 = dkstr_start(k, NULL);
  if(p1) {
    p2 = dkstr_chr(p1, ':');
    if(p2) {
      *(p2++) = '\0';
      switch(dkstr_array_index(trailing_strings_for_info, p1, 0)) {
        case 0: case 1: {
	  p3 = dksto_it_find_like(pj->cr_sti, p2, 0);
	  if(!p3) {
	    p3 = dkstr_dup(p2);
	    if(p3) {
	      if(!dksto_add(pj->cr_st, (void *)p3)) {
	        dk_delete(p3);
		prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(42));
	      }
	    } else {
	      prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(42));
	    }
	  }
	} break;
      }
    }
  }
  
  return back;
}



/**	Database traversal function, used in reset request.
	@param	pj	Pointer to print job structure.
	@param	k	Key string.
	@param	v	Value string.
	@return	1 on success, 0 on error, -1 on fatal error
	(abort traversal).
*/
static
int
traverse_reset DK_P3(PJ *,pj, char *,k, char *,v)
{
  int back = 1;
  char keybuffer[PRQD_DBENTRY_SIZE], *p1, *p2, *p3;
  
  if(strlen(k) < sizeof(keybuffer)) {
    strcpy(keybuffer, k);
    p1 = dkstr_start(keybuffer, NULL);
    p2 = dkstr_chr(p1, ':');
    if(p2) {
      *(p2++) = '\0';
      p3 = dkstr_chr(p2, ':');
      if(p3) {
        *(p3++) = '\0';
	dkstr_chomp(p1, NULL);
	dkstr_chomp(p2, NULL);
	dkstr_chomp(p3, NULL);
	if(strcmp(dbentry_type_p, p1) == 0) {
	  
	  if(pattern_matches(pj->cr_cname, p2)) {
	    
	    if(pattern_matches(pj->cr_uname, p3)) {
	      
	      
	      p1 = dkstr_dup(k);
	      if(p1) {
	        if(!dksto_add(pj->cr_st, (void *)p1)) {
		  
		  dk_delete(p1);
		  back = -1;
		  prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(42));
		}
	      } else {		
	        back = -1;
		prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(42));
	      }
	    } else {		
	    }
	  } else {		
	  }
	} else {		
	}
      } else {			
      }
    } else {			
    }
  } else {
    back = 0;
  } 
  return back;
}



/**	Split arguments into substrings, set cr_xxx in pj.
	@param	pj	Pointer to print job structure.
	@param	args	Instruction arguments.
*/
static
void
split_args DK_P2(PJ *,pj, char *,args)
{
  char *p1, *p2, *p3;
  int act;
  p1 = dkstr_start(args, NULL);
  while(p1) {
    p2 = dkstr_next(p1, NULL);
    p3 = dkstr_chr(p1, '=');
    if(p3) {
      *(p3++) = '\0';
      dkstr_chomp(p1, NULL);
      p3 = dkstr_start(p3, NULL);
      if(p3) {
        act = dkstr_array_abbr(arg_types, p1, '$', 0);
	switch(act) {
	  case 0: {
	    pj->cr_uname = p3;
	  } break;
	  case 1: {
	    pj->cr_cname = p3;
	  } break;
	  case 2: {
	    long d;
	    pj->cr_deltap = 0L;
	    pj->cr_pages = p3;
	    if(sscanf(p3, "%ld", &d) == 1) {
	      pj->cr_deltap = d;
	    }
	  } break;
	  case 3: {
	    pj->cr_pname = p3;
	  } break;
	}
      }
    }
    p1 = p2;
  }
  
  
  
  
  
}



#if USE_KEYVALUE


static
void
kv_delete DK_P1(KEYVALUE *,p)
{
  char *x;
  if(p) {
    if(p->k) {
      x = p->k; dk_delete(x);
    }
    if(p->v) {
      x = p->v; dk_delete(x);
    }
    p->k = NULL; p->v = NULL;
    dk_delete(p);
  }
}



static
KEYVALUE *kv_new DK_P2(char *,k, char *,v)
{
  KEYVALUE *back = NULL;
  back = dk_new(KEYVALUE,1);
  if(back) {
    back->k = dkstr_dup(k);
    back->v = dkstr_dup(v);
    if(!((k) && (v))) {
      kv_delete(back); back = NULL;
    }
  }
  return back;
}



static
int
kv_compare DK_P3(void *,l, void *,r, int,cr)
{
  int back = 0;
  KEYVALUE *pl, *pr;
  pl = (KEYVALUE *)l; pr = (KEYVALUE *)r;
  if(l) {
    if(r) {
      if(pl->k) {
        if(pr->k) {
	  back = strcmp(pl->k, pr->k);
	} else { back = 1; }
      } else {
        if(pr->k) { back = -1; }
      }
    } else { back = 1; }
  } else {
    if(r) { back = -1; }
  }
  return back;
}



#endif



/**	Process a reset request.
	@param	pj	Pointer to print job structure.
	@param	args	Further arguments.
	@return	1 on success, 0 on error.
*/
static
int
reset_request DK_P2(PJ *,pj, char *,args)
{
  int back = 0;
  char *delptr, *p1, *p2;
  
  split_args(pj, args);
  pj->cr_st = NULL; pj->cr_sti = NULL;
  pj->cr_st = dksto_open(0);
  if(pj->cr_st) {
    dksto_set_comp(pj->cr_st, char_compare, 0);
    pj->cr_sti = dksto_it_open(pj->cr_st);
    if(pj->cr_sti) {
      prqdbe_traverse(pj, traverse_reset);
      dksto_it_reset(pj->cr_sti);
      while((delptr = (char *)dksto_it_next(pj->cr_sti)) != NULL) {
        
	prqdbe_delete(pj, delptr);
      }
      dksto_it_reset(pj->cr_sti);
      while((delptr = (char *)dksto_it_next(pj->cr_sti)) != NULL) {
        
	p1 = dkstr_chr(delptr, ':');
	if(p1) {
	  *(p1++) = '\0';
	  p2 = dkstr_chr(p1, ':');
	  if(p2) {
	    *(p2++) = '\0';
	    prqd_write_userinfo_file(pj, p2, p1);
	  }
	}
        dk_delete(delptr);
      }
      dksto_it_close(pj->cr_sti); pj->cr_sti = NULL;
    } else {
      pj->e1 = 1;
      prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(42));
    }
    dksto_close(pj->cr_st); pj->cr_st = NULL;
  } else {
    pj->e1 = 1;
    prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(42));
  }
  
  return back;
}



/**	Add pages to a users print account.
	@param	pj	Pointer to print job structure.
	@param	args	Further arguments.
	@return	1 on success, 0 on error.
*/
static
int
add_request DK_P2(PJ *,pj, char *,args)
{
  int back = 0;
  char keybuffer[PRQD_DBENTRY_SIZE], valbuffer[PRQD_DBENTRY_SIZE];
  long uap = 0L;
  
  split_args(pj, args);
  if(pj->cr_uname) {
    if(pj->cr_cname) {
      if(strcmp(pj->cr_uname, pat_ast)) {
        if(strcmp(pj->cr_cname, pat_ast)) {
	  if((3 + strlen(pj->cr_uname) + strlen(pj->cr_cname)) < sizeof(keybuffer)) {
	    sprintf(keybuffer, "a:%s:%s", pj->cr_cname, pj->cr_uname);
	    if(prqdbe_fetch(pj, keybuffer, valbuffer, sizeof(valbuffer))) {
	      long l;
	      if(sscanf(valbuffer, "%ld", &l) == 1) {
	        uap = l;
	      }
	    }
	    uap = uap + pj->cr_deltap;
	    sprintf(valbuffer, "%ld", uap);
	    if(prqdbe_store(pj, keybuffer, valbuffer)) {
	      back = 1;
	    }
	    prqd_write_userinfo_file(pj, pj->cr_uname, pj->cr_cname);
	    
	  } else {		
	    prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(41), pj->cr_uname, pj->cr_cname);
	  }
	} else {		
	  prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(83));
	}
      } else {			
        prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(83));
      }
    } else {			
      prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(82));
    }
  } else {			
    prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(81));
  }
  
  return back;
}




/**	Process "control info ..." request.
	@param	pj	Pointer to print job structure.
	@param	args	Pointer to further arguments.
	@return	1 on success, 0 on error.
*/
static
int
info_request DK_P2(PJ *,pj, char *,args)
{
  int back = 0;
  char *delptr, *p1, *p2;
  
  split_args(pj, args);
  pj->cr_st = NULL; pj->cr_sti = NULL;
  pj->cr_st = dksto_open(0);
  if(pj->cr_st) {
    dksto_set_comp(pj->cr_st, char_compare, 0);
    pj->cr_sti = dksto_it_open(pj->cr_st);
    if(pj->cr_sti) {
      prqdbe_traverse(pj, traverse_info);
      dksto_it_reset(pj->cr_sti);
      while((delptr = (char *)dksto_it_next(pj->cr_sti)) != NULL) {
        
	p1 = dkstr_start(delptr, NULL);
	if(p1) {
	  p2 = dkstr_chr(p1, ':');
	  if(p2) {
	    *(p2++) = '\0';
	    prqd_write_userinfo_file(pj, p2, p1);
	  }
	}
	dk_delete(delptr);
      }
      dksto_it_close(pj->cr_sti);
    } else {
      prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(42));
    }
    dksto_close(pj->cr_st); pj->cr_st = NULL;
  } else {
    prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(42));
  }
  
  return back;
}



/**	Clean up the database.
	Remove entries if:
	- the printer class does not longer exist or
	- the user does not longer exist.
	@param	pj	Pointer to print job structure.
	@return	1 on success, 0 on error.
*/
static
int
cleanup_database DK_P1(PJ *,pj) {
  int	back = 0;
  char	valbuffer[PRQD_DBENTRY_SIZE];
  char	*delptr = NULL;
  pj->cr_st = NULL; pj->cr_sti = NULL;
  pj->cr_st = dksto_open(0);
  if(pj->cr_st) {
    dksto_set_comp(pj->cr_st, char_compare, 0);
    pj->cr_sti = dksto_it_open(pj->cr_st);
    if(pj->cr_sti) {
      back = prqdbe_traverse(pj, traverse_cleanup);
      if(back == -1) { back = 0; }
      dksto_it_reset(pj->cr_sti);
      while((delptr = (char *)dksto_it_next(pj->cr_sti)) != NULL) {
        DK_MEMRES(valbuffer,sizeof(valbuffer));
        if(prqdbe_fetch(pj, delptr, valbuffer, sizeof(valbuffer))) {
	  /* LOG: Remove entry ...=... */
	  prqdlog(pj, PRQD_PRIO_INFO, prqd_get_kw(85), delptr, valbuffer);
	} else {
	  /* FAILED TO FETCH ENTRY */
	  back = 0;
	}
	if(!prqdbe_delete(pj, delptr)) {
	  /* FAILED TO DELETE DB entry */
	  back = 0;
	}
        dk_delete(delptr);
      }
      dksto_it_close(pj->cr_sti);
    } else {
      prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(42));
    }
    dksto_close(pj->cr_st);
  } else {
    prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(42));
  }
  return back;
}




/**	Perform a control request.
	@param	pj	Pointer to print job structure.
	@param	args	Pointer to further arguments.
	@return	1 on success, 0 on error.
*/
int
prqdctrl_request DK_P2(PJ *,pj, char *,args)
{
  int back = 0;
  int act = 0;
  char *pcmd, *parg;
  
  pj->cr_uname = NULL;
  pj->cr_cname = NULL;
  pj->cr_pages = NULL;
  pj->cr_pname = NULL;
  pj->cr_deltap = 0L;
  pcmd = dkstr_start(args, NULL);
  if(pcmd) {
    parg = dkstr_next(pcmd, NULL);
    dkstr_chomp(pcmd, NULL);
    act = dkstr_array_abbr(ctrl_commands, pcmd, '$', 0);
    switch(act) {
      case 0: {			
        back = reset_request(pj, parg);
      } break;
      case 1: {			
        back = add_request(pj, parg);
      } break;
      case 2: {
        back = info_request(pj, parg);
      } break;
      case 3: {			
        back = cleanup_database(pj);
      } break;
      default: {		
        prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(80), pcmd);
      } break;
    }
  } else {
    prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(79));
  }
  
  return back;
}



