/*
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	prqdconf.c The prqd configuration module. */


/**	Inside the prqdconf module. */
#define PRQDCONF_C	1
#include "prqd.h"
#include "dktools-version.h"




#line 48 "prqdconf.ctr"




/**	The keywords to print to the log file.
*/
char *prqd_kw[] = {
  /*   0 */ "",
  /*   1 */ "prqd (dktools-" VERSNUMB ") starting",
  /*   2 */ "prqd exiting.",
  /*   3 */ "    For accounting we use abbreviations as follows:\n"
  /*   3 */ "     u ... user                        job             data about one job printed\n"
  /*   3 */ "     c ... printer class               summary         accumulated values\n"
  /*   3 */ "    pr ... printer                     balance-account default page limit exceeded,\n"
  /*   3 */ "    pa ... page number                                 switching to pages from\n"
  /*   3 */ "     l ... page limit (default)                        personal account\n"
  /*   3 */ "    ac ... pages in personal account",
  /*   4 */ "Failed to create socket!",
  /*   5 */ "Socket name \"%s\" is too long!",
  /*   6 */ "Failed to bind to UNIX socket \"%s\"!",
  /*   7 */ "Failed to listen on socket \"%s\"!",
  /*   8 */ "Retrieving open jobs from database.",
  /*   9 */ "Finished retrieving open jobs from database.",
  /*  10 */ "Accepting incoming connection request to start session.",
  /*  11 */ "Connection established.",
  /*  12 */ "Closing session.",
  /*  13 */ "Session closed.",
  /*  14 */ "Failed to accept incoming connection request!",
  /*  15 */ "Reading request.",
  /*  16 */ "Processing request.",
  /*  17 */ "Timeout specified.",
  /*  18 */ "No timeout specified.",
  /*  19 */ "Successfully read %lu bytes.",
  /*  20 */ "No data received!",
  /*  21 */ "Writing response if necessary.",
  /*  22 */ "Failed to read configuration!",
  /*  23 */ "Failed to open database!",
  /*  24 */ "No socket name specified!",
  /*  25 */ "Incomplete set of arguments for request!",
  /*  26 */ "No data found for printer \"%s\"",
  /*  27 */ "DB BE: store \"%s\"=\"%s\"",
  /*  28 */ "DB BE: fetched \"%s\"=\"%s\"",
  /*  29 */ "DB BE: Failed to fetch \"%s\"!",
  /*  30 */ "No user name for current job!",
  /*  31 */ "Stored job user name \"%s\" does not match current user name \"%s\"!",
  /*  32 */ "Failed to retrieve page number at start of job from \"%s\"!",
  /*  33 */ "Failed to retrieve page number at end of job from \"%s\"!",
  /*  34 */ "Page number at end of job must be larger than at start of job!",
  /*  35 */ "DB BE: Failed to store \"%s\"=\"%s\"!",
  /*  36 */ "Printer name \"%s\" is too long!",
  /*  37 */ "Failed to retrieve number of printed pages from \"%s\"!",
  /*  38 */ "Failed to retrieve number of pages in personal account from \"%s\"!",
  /*  39 */ "Printing allowed (1=yes, 0=no): %d",
  /*  40 */ "Insufficient arguments to write user information file!",
  /*  41 */ "User name \"%s\" and/or printer class name \"%s\" too long!",
  /*  42 */ "Not enough memory (RAM/swap space)!",
  /*  43 */ "Failed to open user information file \"%s\" for writing!",
  /*  44 */ "Printer class \"%s\" not found!",
  /*  45 */ "User \"%s\" not found!",
  /*  46 */ "Database entry \"%s\" too long to be stored as current job!",
  /*  47 */ "Cannot change group, failed to find group \"%s\"!",
  /*  48 */ "Cannot change user, failed to find user \"%s\"!",
  /*  49 */ "Potential security problem: No \"run as group\" option used.",
  /*  50 */ "Potential security problem: No \"run as user\" option used.",
  /*  51 */ "Switching user to \"%s\".",
  /*  52 */ "Switching group to \"%s\".",
  /*  53 */ "Finishing session (errors or reconfiguration or broken pipe).",
  /*  54 */ "Restarting service (errors or reconfiguration).",
  /*  55 */ "Finishing service (errors/reconfiguration/broken pipe/termination)!",
  /*  56 */ "Error(s) while reading configuration file!",
  /*  57 */ "No printer class definition found for printer \"%s\"!",
  /*  58 */ "Failed to open configuration file \"%s\" for reading!",
  /*  59 */ "%s:%lu: Syntax error!",
  /*  60 */ "%s:%lu: Printer class \"%s\" undefined!",
  /*  61 */ "%s:%lu: Printer class \"%s\" already defined!",
  /*  62 */ "%s:%lu: Multiple definitions for user information directory!",
  /*  63 */ "Illegal or unknown request type \"%s\"!",
  /*  64 */ "Insufficient data to construct a request!",
  /*  65 */ "Failed to connect to prqd!",
  /*  66 */ "Error while sending data!",
  /*  67 */ "DB BE: Failed to delete entry \"%s\"!",
  /*  68 */ "DB BE: Request for unsupported backend type!",
  /*  69 */ "DB BE: Failed to create database!",
  /*  70 */ "DB BE: Failed to open database \"%s\"!",
  /*  71 */ "DB BE: No database specified!",
  /*  72 */ "DB BE: Database is not a type:name pair!",
  /*  73 */ "DB BE: Empty database name!",
  /*  74 */ "DB BE: Invalid database type \"%s\", must be \"bdb\", \"ndbm\" or \"gdbm\"!",
  /*  75 */ "DB BE: Failed to change file ownership for \"%s\" to %lu:%lu!",
  /*  76 */ "DB BE: Failed to change permissions of \"%s\" to 0660!",
  /*  77 */ "DB BE: Error processing \"%s\"=\"%s\" (can continue iteration)!",
  /*  78 */ "DB BE: Error processing \"%s\"=\"%s\" (can not continue iteration)!",
  /*  79 */ "Missing control request type!",
  /*  80 */ "Unknown control request type \"%s\"!",
  /*  81 */ "Missing user name!",
  /*  82 */ "Missing printer class name!",
  /*  83 */ "Wildcard not allowed here!",
  /*  84 */ "DB BE: delete \"%s\"",
  /*  85 */ "Cleanup: Remove db entry \"%s\"=\"%s\"",
  NULL
};



/** Type definition for pointer to characters. */
typedef char *PCHR;



/**	Get an entry from the prqd_kw array.
	@param	s	index of the entry to get.
	@return	The string selected by the index.
*/
char *
prqd_get_kw DK_P1(size_t,s)
{
  char *back = NULL;
  
  if(s < (sizeof(prqd_kw)/sizeof(PCHR))) {
    back = prqd_kw[s];
  } else {
    back = prqd_kw[0];
  } 
  return back;
}


/** Scopes for limits. */
static char *le_scope_texts[] = { "u$ser", "g$roup", "d$efault", NULL };

/** Patterns to choose a default deny action. */
static char *da_texts[] = { "r$emove", "h$old", NULL };

/** Configuration file section headers. */
static char *conf_section_headers[] = {
  "o$ptions", "c$lass", "p$rinter", NULL
};

/** Keyword unlimited. */
static char key_unlimited[] = { "unlimited" };

/** Database configuration line in options section. */
static char *kw01_00[] = { "d$atabase", NULL };

/** Socket configuration line in options section. */
static char *kw01_01[] = { "s$ocket", NULL };

/** Run as user configuration line in options section. */
static char *kw01_02[] = { "r$un", "a$s", "u$ser", NULL };

/** Run as group configuration line in options section. */
static char *kw01_03[] = { "r$un", "a$s", "g$roup", NULL };

/** Socket timeout configuration line in options section. */
static char *kw01_04[] = { "s$ocket", "t$imeout", NULL };

/** User information directory configuration line in options section. */
static char *kw01_05[] = { "u$ser", "i$nformation", "d$irectory", NULL };

static char *kw01_06[] = {
"cr$eate", "a$ccounts", "a$utomatically", NULL
};

/** Configuration lines in options section. */
static char **kw01[] = {
  kw01_00, kw01_01, kw01_02, kw01_03, kw01_04, kw01_05, kw01_06, NULL
};

/** Limit configuration line in printer class definition. */
static char *kw02_00[] = { "l$imit", NULL };

/** Deny action configuration line in printer class definition. */
static char *kw02_01[] = { "d$eny", "a$ction", NULL };

/** Write user information files configuration line in printer class. */
static char *kw02_02[] = { "w$rite", "u$ser", "i$nformation", NULL };

/** Configuration lines used in printer class definition. */
static char **kw02[] = { kw02_00, kw02_01, kw02_02, NULL };

/** Alias configuration line in printer definition. */
static char *kw03_00[] = { "a$lias", NULL };

/** Class configuration line in printer definition. */
static char *kw03_01[] = { "c$lass", NULL };

/** Configuration lines used in printer definitions. */
static char **kw03[] = { kw03_00, kw03_01, NULL };

/** Program name, used in log messages. */
static char progname[] = { "prqd" };



/**	Compare two limit structures.
	@param	l	Pointer to left structure.
	@param	r	Pointer to right structure.
	@param	cr	Comparison criteria (unused).
	@return 1 for l>r, 0 for l=r or -1 for l<r.
*/
static
int
le_compare DK_P3(void *,l, void *,r, int,cr)
{
  int back = 0;
  LE *pl, *pr;
  pl = (LE *)l; pr = (LE *)r;
  if(l) {
    if(r) {
      if(pl->tp > pr->tp) {
        back = 1;
      } else {
        if(pl->tp < pr->tp) {
	  back = -1;
	} else {
	  if(pl->tp != LIMIT_ENTRY_TYPE_DEFAULT) {
	    back = strcmp(pl->who, pr->who);
	  }
	}
      }
    } else {
      back = 1;
    }
  } else {
    if(r) {
      back = -1;
    }
  }
  return back;
}



/**	Destroy a limit entry and release the memory.
	@param	le	Pointer to limit entry structure to destroy.
*/
static
void
le_delete DK_P1(LE *,le)
{
  
  if(le->who) {
    char *x; x = le->who; dk_delete(x);
  } le->who = NULL;
  le->tp = 0;
  le->val = 0;
  le->p = 0UL;
  dk_delete(le);
  
}



/**	Set limit in limit structure.
	@param	le	Limit entry structure.
	@param	s	Limit value in textual form.
*/
static
void
add_limit_value DK_P2(LE *,le, char *,s)
{
  le->val = LIMIT_VALUE_DENIED;
  le->p = 0UL;
  if(strcmp(key_unlimited, s) == 0) {
    le->val = LIMIT_VALUE_UNLIMITED;
  } else {
    unsigned long ul;
    if(sscanf(s, "%lu", &ul) == 1) {
      le->val = LIMIT_VALUE_PAGES;
      le->p = ul;
    }
  }
}



/**	Create limit entry structure.
	The structure is created in dynamically allocated memory and
	must be release using le_delete().
	@param	pj	Pointer to prqd job structure.
	@param	s	String containing the limit in textual form.
	@param	fn	Configuration file name.
	@param	lineno	The line number in the configuration file.
*/
static
LE *
le_new DK_P4(PJ *,pj, char *,s, char *,fn, unsigned long,lineno)
{
  LE *back = NULL;
  char buffer[256], *p1, *p2, *p3;
  int i;
  
  if(strlen(s) < sizeof(buffer)) {	
    strcpy(buffer, s);
    p1 = dkstr_start(buffer, NULL);
    if(p1) {				
      p2 = dkstr_chr(p1, ':');
      if(p2) {				
        *(p2++) = '\0';
	p3 = dkstr_chr(p2, ':');
	if(p3) {			
	  *(p3++) = '\0';
	}
	switch((i = dkstr_array_abbr(le_scope_texts, p1, '$', 0))) {
	  case 0: case 1: {		
	    if(p3) {
	      back = dk_new(LE,1);
	      if(back) {		
	        back->tp = 0; back->who = NULL; back->val = 0; back->p = 0UL;
	        if(i) {			
	          back->tp = LIMIT_ENTRY_TYPE_GROUP;
	        } else {		
	          back->tp = LIMIT_ENTRY_TYPE_USER;
	        }
	        back->who = dkstr_dup(p2);
	        if(back->who) {		
		  add_limit_value(back,p3);
	        } else {		
		  prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(42));
		  dk_delete(back); back = NULL;
	        }
	      } else {			
		prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(42));
	      }
	    } else {			
	      prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(59), fn, lineno);
	    }
	  } break;
	  case 2: {			
	    back = dk_new(LE,1);
	    if(back) {			
	      back->tp = 0; back->who = NULL; back->val = 0; back->p = 0UL;
	      back->tp = LIMIT_ENTRY_TYPE_DEFAULT;
	      add_limit_value(back,p2);
	    } else {			
	      prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(42));
	    }
	  } break;
	  default: {			
	    prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(59), fn, lineno);
	  } break;
	}
      } else {				
	prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(59), fn, lineno);
      }
    } else {				
      prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(59), fn, lineno);
    }
  }
  
  return back;
}



/**	Compare two printer class structures.
	@param	l	Pointer to left structure.
	@param	r	Pointer to right structure.
	@param	cr	Comparison criteria (unused).
	@return	1 for l>r, 0 for l=r, -1 for l<r.
*/
static
int
pc_compare DK_P3(void *,l, void *,r, int,cr)
{
  int back = 0;
  PC *pl, *pr;
  pl = (PC *)l; pr = (PC *)r;
  if(l) {
    if(r) {
      switch(cr) {
        case 1: {
	  if(pl->n) {
	    back = strcmp(pl->n, (char *)r);
	  } else {
	    back = -1;
	  }
	} break;
	default: {
	  if(pl->n) {
	    if(pr->n) {
	      back = strcmp(pl->n, pr->n);
	    } else {
	      back = 1;
	    }
	  } else {
	    if(pr->n) {
	      back = -1;
	    }
	  }
	} break;
      }
    } else {
      back = 1;
    }
  } else {
    if(r) {
      back = -1;
    }
  }
  return back;
}



/**	Destory printer class structure and release the memory.
	@param	pc	Pointer to printer class structure.
*/
static
void
pc_delete DK_P1(PC *,pc)
{
  LE *ple;
  
  if(pc->n) {
    char *x; x = pc->n; dk_delete(x);
  } pc->n = NULL;
  if(pc->l) {
    if(pc->li) {
      dksto_it_reset(pc->li);
      while((ple = (LE *)dksto_it_next(pc->li)) != NULL) {
        le_delete(ple);
      }
      dksto_it_close(pc->li);
    }
    dksto_close(pc->l);
  } pc->l = NULL; pc->li = NULL;
  dk_delete(pc);
  
}



/**	Create new printer class structure.
	The printer class structure is created in dynamically allocated
	memory and must be released using pc_delete().
	@param	pj	Pointer to prqd job structure.
	@param	prqdc	Pointer to configuration structure.
	@param	n	Printer class name.
	@return	Pointer to new printer class structure on success,
		0 on error.
*/
static
PC *
pc_new DK_P3(PJ *,pj, PRQDC *,prqdc, char *,n)
{
  PC *back = NULL;
  
  back = dk_new(PC,1);
  if(back) {
    back->n = NULL;
    back->l = NULL;
    back->li = NULL;
    back->n = dkstr_dup(n);
    back->l = dksto_open(0);
    back->da = DENY_ACTION_REMOVE;
    back->wi = 0;
    if(back->l) {
      dksto_set_comp(back->l, le_compare, 0);
      back->li = dksto_it_open(back->l);
    }
    if((back->n) && (back->l) && (back->li)) {
      if(!dksto_add(prqdc->cl, (void *)back)) {
        pc_delete(back); back = NULL;
      }
    } else {
      pc_delete(back); back = NULL;
    }
  }
  
  return back;
}



/**	Compare two printer structures.
	@param	l	Pointer to left structure.
	@param	r	Pointer to right printer structure.
	@param	cr	Comparison criteria (unused),
	@return 1 for l>r, 0 for l=r or -1 for l<r.
*/
static
int
pr_compare DK_P3(void *,l, void *,r, int,cr)
{
  int back = 0;
  PR *pl, *pr;
  pl = (PR *)l; pr = (PR *)r;
  if(l) {
    if(r) {
      switch(cr) {
        case 1: {
	  if(pl->n) {
	    back = strcmp(pl->n, (char *)r);
	  } else {
	    back = -1;
	  }
	} break;
	default: {
	  if(pl->n) {
	    if(pr->n) {
	      back = strcmp(pl->n, pr->n);
	    } else {
	      back = 1;
	    }
	  } else {
	    if(pr->n) {
	      back = -1;
	    }
	  }
	} break;
      }
    } else {
      back = 1;
    }
  } else {
    if(r) {
      back = -1;
    }
  }
  return back;
}



/**	Destroy a printer structure created by pr_new() and release the
	memory.
	@param	pr	Pointer to printer structure to release.
*/
static
void
pr_delete DK_P1(PR *,pr)
{
  
  if(pr->alt) {
    
  }
  if(pr->pc) {
    
  }
  if(pr->n) {
    char *x; x = pr->n; dk_delete(x);
  }
  if(pr->cj) {
    char *x; x = pr->cj; dk_delete(x);
  }
  pr->n = NULL;
  pr->cj = NULL;
  pr->pc = NULL;
  pr->alt = NULL;
  dk_delete(pr);
  
}



/**	Create new printer structure. the structure is created in
	dynamically allocated memory and must be released using
	the pr_delete() function.
	@param	pj	Pointer to prqd job structure.
	@param	prqdc	Pointer to configuration structure.
	@param	n	Printer name.
	@return	Pointer to new printer structure.
*/
static
PR *
pr_new DK_P3(PJ *,pj, PRQDC *,prqdc, char *,n)
{
  PR *back = NULL;
  
  if(n) {
    back = dk_new(PR,1);
    if(back) {
      back->pc = NULL;
      back->alt = NULL;
      back->n = dkstr_dup(n);
      back->cj = dk_new(char,64);
      if((back->n) && (back->cj)) {
        back->sz_cj = 64;
        if(!dksto_add(prqdc->pr, (void *)back)) {
	  pr_delete(back); back = NULL;
	}
      } else {
        pr_delete(back); back = NULL;
      }
    }
  }
  
  return back;
}



/**	Initialize elements in configuration structure to 0.
	@param	prqdc	Pointer to configuration structure.
*/
static
void
init_empty_prqdc DK_P1(PRQDC *,prqdc)
{
  
  prqdc->cl = NULL; prqdc->cli = NULL;
  prqdc->pr = NULL; prqdc->pri = NULL;
  prqdc->dbname = NULL;
  prqdc->sockname = NULL;
  prqdc->rau = NULL;
  prqdc->rag = NULL;
  prqdc->to_seconds = 0UL;
  prqdc->to_microseconds = 0UL;
  prqdc->userinfdir = NULL;
  prqdc->do_balance = 0;
  
}



/**	Initialize configuration structure elements after allocating
	the structure.
	@param	pj	Pointer to prqd job structure.
	@param	prqdc	Pointer to configuration structure.
	@return	1 on success, 0 on error.
*/
static
int
init_components_in_prqdc DK_P2(PJ *,pj, PRQDC *,prqdc)
{
  int back = 0;
  
  prqdc->cl = dksto_open(0); prqdc->pr = dksto_open(0);
  if(prqdc->cl) {
    dksto_set_comp(prqdc->cl, pc_compare, 0);
    prqdc->cli = dksto_it_open(prqdc->cl);
  }
  if(prqdc->pr) {
    dksto_set_comp(prqdc->pr, pr_compare, 0);
    prqdc->pri = dksto_it_open(prqdc->pr);
  }
  if((prqdc->pr) && (prqdc->cl) && (prqdc->pri) && (prqdc->cli)) {
    back = 1;
  }
  
  return back;
}



/**	Destroy configuration structure after usage and release memory.
	@param	p	Pointer to configuration structure.
*/
void
prqdconf_delete_prqdc DK_P1(PRQDC *,p)
{
  PR *ppr;
  PC *pcl;
  
  if(p) {
    if(p->dbname) {
      char *x;
      
      x = p->dbname; dk_delete(x);
    } p->dbname = NULL;
    if(p->sockname) {
      char *x;	
      x = p->sockname; dk_delete(x);
    } p->sockname = NULL;
    if(p->rau) {
      char *x;	
      x = p->rau; dk_delete(x);
    } p->rau = NULL;
    if(p->rag) {
      char *x;	
      x = p->rag; dk_delete(x);
    } p->rag = NULL;
    if(p->pr) {
      if(p->pri) {
        dksto_it_reset(p->pri);
	while((ppr = (PR *)dksto_it_next(p->pri)) != NULL) {
	  pr_delete(ppr);
	}
        dksto_it_close(p->pri);
      }
      dksto_close(p->pr);
    } p->pr = NULL; p->pri = NULL;
    if(p->cl) {
      if(p->cli) {
        dksto_it_reset(p->cli);
	while((pcl = (PC *)dksto_it_next(p->cli)) != NULL) {
	  pc_delete(pcl);
	}
        dksto_it_close(p->cli);
      }
    } p->cl = NULL; p->cli = NULL;
    if(p->userinfdir) {
      char *x; 
      x = p->userinfdir; dk_delete(x);
    } p->userinfdir = NULL;
    dk_delete(p);
  }
  
}



/**	Read configuration file into configuration structure.
	@param	pj	Pointer to prqd job structure.
	@param	prqdc	Pointer to configuration structure.
	@param	fn	Configuration file name.
	@return	1 on success, 0 on error.
*/
static
int
read_configuration_file DK_P3(PJ *,pj, PRQDC *,prqdc, char *,fn)
{
  int back = 0, cc = 0, i = 0;
  char buffer[512], *parts[16], *p1, *p2, *newvalue;
  FILE *f;
  unsigned long lineno;
  PR *cpr, *alt; PC *cpc; size_t u_parts;
  int st; /* 0=unknown, 1=options, 2=class, 3=printer */
  
  f = dksf_fopen(fn, "r");
  if(f) {
    back = 1; cc = 1; lineno = 0UL; cpr = NULL; cpc = NULL; st = 0;
    while((cc) && (back)) {
      if(fgets(buffer, sizeof(buffer), f)) {
        lineno++;
	dkstr_delcomm(buffer, '#');
	p1 = dkstr_start(buffer, NULL);
	if(p1) {
	  if(*p1 == '[') {
	    st = 0; cpr = NULL; cpc = NULL;
	    p1++;
	    p1 = dkstr_start(p1, NULL);
	    if(p1) {
	      p2 = dkstr_chr(p1, ']');
	      if(p2) {
	        *p2 = '\0';
		p2 = dkstr_next(p1, NULL);
		if(p2) {
		  dkstr_chomp(p2, NULL);
		}
		st = dkstr_array_abbr(conf_section_headers, p1, '$', 0);
		if(st > -1) {
		  st += 1;
		  switch(st) {
		    case 2: {
		      if(p2) {
		        cpc = dksto_it_find_like(prqdc->cli, p2, 1);
			if(!cpc) {
			  cpc = pc_new(pj, prqdc, p2);
			  if(!cpc) {
			    prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(42));
			    back = cc = 0;
			  }
			} else {
			  prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(61), fn, lineno, p2);
			  back = cc = 0;
			}
		      } else {
			prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(59), fn, lineno);
			back = cc = 0;
		      }
		    } break;
		    case 3: {
		      if(p2) {
		        cpr = dksto_it_find_like(prqdc->pri, p2, 1);
			if(!cpr) {
			  cpr = pr_new(pj, prqdc, p2);
			  if(!cpr) {
			    prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(42));
			    back = cc = 0;
			  }
			} else {
			  prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(61), fn, lineno, p2);
			  back = cc = 0;
			}
		      } else {
			prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(59), fn, lineno);
			back = cc = 0;
		      }
		    } break;
		  }
		} else {
		  prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(59), fn, lineno);
		  back = cc = 0;
		}
	      } else {
		prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(59), fn, lineno);
		back = cc = 0;
	      }
	    } else {
	      back = cc = 0;
	      prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(59), fn, lineno);
	    }
	  } else {
	    p1 = dkstr_start(buffer, NULL);
	    if(p1) {
	      p2 = dkstr_chr(p1, '=');
	      if(p2) {
	        *(p2++) = '\0';
		dkstr_chomp(p1, NULL);
		p2 = dkstr_start(p2, NULL);
		if(p2) {
		  dkstr_chomp(p2, NULL);
		  u_parts = dkstr_explode(parts, 16, p1, NULL);
		  if(u_parts > 0) {
		    switch(st) {
		      case 1: {
		        i = dkstr_find_multi_part_abbr(parts, kw01, '$', 0);
			switch(i) {
			  case 0: {	
			    newvalue = dkstr_dup(p2);
			    if(newvalue) {
			      if(prqdc->dbname) {
			        char *x; x = prqdc->dbname; dk_delete(x);
			      }
			      prqdc->dbname = newvalue;
			    } else {
			      prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(42));
			      back = cc = 0;
			    }
			  } break;
			  case 1: {	
			    newvalue = dkstr_dup(p2);
			    if(newvalue) {
			      if(prqdc->sockname) {
			        char *x; x = prqdc->sockname; dk_delete(x);
			      }
			      prqdc->sockname = newvalue;
			    } else {
			      prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(42));
			      back = cc = 0;
			    }
			  } break;
			  case 2: {	
			    newvalue = dkstr_dup(p2);
			    if(newvalue) {
			      if(prqdc->rau) {
			        char *x; x = prqdc->rau; dk_delete(x);
			      }
			      prqdc->rau = newvalue;
			    } else {
			      prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(42));
			      back = cc = 0;
			    }
			  } break;
			  case 3: {	
			    newvalue = dkstr_dup(p2);
			    if(newvalue) {
			      if(prqdc->rag) {
			        char *x; x = prqdc->rag; dk_delete(x);
			      }
			      prqdc->rag = newvalue;
			    } else {
			      prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(42));
			      back = cc = 0;
			    }
			  } break;
			  case 4: {	
			    {
			      int ec = 0;
			      unsigned long s1, s2;
			      double d, d1;
			      if(sscanf(p2, "%lf", &d) == 1) {	
			        d1 = floor(d);	
				d = d - d1;	
				s1 = dkma_double_to_ul_ok(d1, &ec);
				
				d = dkma_mul_double_ok(1000000.0, d, &ec);
				
				s2 = dkma_double_to_ul_ok(d, &ec);
				
				if(ec == 0) {	
				  prqdc->to_seconds = ((long)s1);
				  prqdc->to_microseconds = ((long)s2);
				}
			      }
			    }
			  } break;
			  case 5: {	
			    if(p2) {
			      newvalue = dkstr_dup(p2);
			      if(newvalue) {
			        if(prqdc->userinfdir) {
				  char *x; x = prqdc->userinfdir; dk_delete(x);
				  prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(62), fn, lineno);
				}
				prqdc->userinfdir = newvalue;
			      } else {
				prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(42));
				back = cc = 0;
			      }
			    }
			  } break;
			  case 6: {
			    if(p2) {
			      if(dkstr_is_bool(p2)) {
			        if(dkstr_is_on(p2)) {
				  prqdc->do_balance = 1;
				} else {
				  prqdc->do_balance = 0;
				}
			      }
			    }
			  } break;
			  default: {
			    prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(59), fn, lineno);
			    back = cc = 0;
			  } break;
			}
		      } break;
		      case 2: {
		        if(cpc) {
			  i = dkstr_find_multi_part_abbr(parts, kw02, '$', 0);
			  switch(i) {
			    case 0: {	
			      LE *le;
			      le = le_new(pj, p2, fn, lineno);
			      if(le) {
			        if(!dksto_add(cpc->l, (void *)le)) {
				  prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(42));
				   back = cc = 0;
				}
			      } else {
				prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(42));
				back = cc = 0;
			      }
			    } break;
			    case 1: {	
			      cpc->da = dkstr_array_abbr(da_texts, p2, '$', 0);
			      if(cpc->da < 0) {
				prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(59), fn, lineno);
				back = cc = 0;
			      }
			    } break;
			    case 2: {
			      cpc->wi = 0;
			      if(p2) {
			        if(dkstr_is_bool(p2)) {
				  if(dkstr_is_on(p2)) {
				    cpc->wi = 1;
				  } else {
				    cpc->wi = 0;
				  }
				}
			      }
			    } break;
			    default: {
			      prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(59), fn, lineno);
			      back = cc = 0;
			    } break;
			  }
			}
		      } break;
		      case 3: {
		        if(cpr) {
			  i = dkstr_find_multi_part_abbr(parts, kw03, '$', 0);
			  switch(i) {
			    case 0: {	
			      alt = pr_new(pj, prqdc, p2);
			      if(alt) {
			        alt->alt = cpr;
			      } else {
				prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(42));
				back = cc = 0;
			      }
			    } break;
			    case 1: {	
			      cpc = dksto_it_find_like(prqdc->cli, p2, 1);
			      if(cpc) {
				cpr->pc = cpc;
				cpc = NULL;
			      } else {
				prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(60), fn, lineno, p2);
				back = cc = 0;
			      }
			    } break;
			    default: {
			      prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(59), fn, lineno);
			      back = cc = 0;
			    } break;
			  }
			}
		      } break;
		    }
		  }
		}
	      } else {
		prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(59), fn, lineno);
		back = cc = 0;
	      }
	    }
	  }
	}
      } else {
        cc = 0;	
      }
      if(!back) {
        
      }
    }
    fclose(f); f = NULL;
  } else {		
    prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(58), fn);
  }
  
  return back;
}



/**	Check configuration. Each printer which is not an alias
	must have a class assigned.
	@param	pj	Pointer to prqd job structure.
	@param	prqdc	Pointer to configuration structure.
	@return	1 if the configuration is ok, 0 on error.
*/
static
int
check_prqdc DK_P2(PJ *,pj, PRQDC *,prqdc)
{
  PR *pr;
  int back = 1;
  
  dksto_it_reset(prqdc->pri);
  while((pr = (PR *)dksto_it_next(prqdc->pri)) != NULL) {
    if(!(pr->alt)) {
      if(!(pr->pc)) {	
        back = 0;
	prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(57), (pr->n ? pr->n : "(NONE)"));
      }
    }
  } 
  return back;
}



/**	Create new prqd configuration structure.
	The structure is created in dynamically allocated memory.
	Use prqdconf_delete_prqdc() to release the memory after usage.
	@param	pj	Pointer to prqd job structure.
	@param	fn	File name.
	@return	Pointer to the new configuration structure on success,
		NULL on error.
*/
PRQDC *
prqdconf_new_prqdc DK_P2(PJ *,pj, char *,fn)
{
  PRQDC *back = NULL;
  
  back = dk_new(PRQDC,1);
  if(back) {
    init_empty_prqdc(back);
    if(init_components_in_prqdc(pj,back)) {
      if(read_configuration_file(pj, back, fn)) {
        if(!check_prqdc(pj, back)) {
	  prqdconf_delete_prqdc(back); back = NULL;
	  prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(56));
	}
      } else {
        prqdconf_delete_prqdc(back); back = NULL;
	prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(56));
      }
    } else {					
      prqdconf_delete_prqdc(back); back = NULL;
      prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(42));
    }
  } else {
    prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(42));
  }
  
  return back;
}



/**	Return the program name.
	@return The program name.
*/
char *
prqdconf_get_progname DK_P0()
{
  return progname;
}



/**	Retrieve debug level.
	@param	pj	Pointer to prqd job structure.
	@return	The log level in the pj.
*/
int
prqdconf_get_debug DK_P1(PJ *,pj)
{
  return pj->deb;
}



/** Default configuration file name. */
char prqd_conf_cfgfile[] = { DK_SYSCONFDIR "/prqd/prqd.conf" };

/** Default log file name. */
char prqd_conf_logfile[] = { DK_LOCALSTATEDIR "/log/prqd/prqd.log" };



/**	Return log file name.
	@return	The log file name.
*/
char *
prqdconf_get_logfile DK_P0()
{
  return prqd_conf_logfile;
}



/**	Return configuration file name.
	@return The configuration file name.
*/
char *
prqdconf_get_cfgfile DK_P0()
{
  return prqd_conf_cfgfile;
}



#if PRQDCONF_TEST
int main(int argc,  char *argv[])
{
  PJ pj;
  PRQDC *prqdc;
  
#line 1183 "prqdconf.ctr"

  
  pj.prqdc = prqdconf_new_prqdc(&pj, "prqd.conf");
  if(pj.prqdc) {	
    prqdconf_delete_prqdc(pj.prqdc);
    pj.prqdc = NULL;
  } else {		
  }
  
  
#line 1192 "prqdconf.ctr"

  exit(0); return 0;
}
#endif



