/*
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 prqdcl.c	Prqd client. */


/**	Inside the prqdcl module. */
#define PRQDCL_C	1
#include "prqd.h"

#line 45 "prqdcl.ctr"




/** Default string to use if a string is missing. */
static char default_string[] = { "***" };


/** Use default string instead of NULL. */
#define XSTR(s) ((s) ? (s) : default_string)



/**	Retrieve short host name part from fully qualified host name.
	@param	hn	Full host name.
	@return	Pointer to short host name on success, NULL on error.
*/
static char *
short_host_name DK_P1(char *,hn)
{
  char *back = NULL;
  static char mybuffer[32], *ptr;
  size_t l;
  if(hn) {
    back = hn;
    l = strlen(hn); if(l > 32) l = 32;
    strncpy(mybuffer, hn, l);
    if(l > 31) l = 31;
    mybuffer[l] = '\0';
    ptr = strchr(mybuffer, '.');
    if(ptr) {
      *ptr = '\0';
      back = mybuffer;
    }
  }
  return back;
}


#if !ALWAYS_PERMIT
/**	Construct the request.
	The request received from stdin was split for sanity check,
	now rebuild the request before sending it.
	@param	pj	Pointer to prqd job structure.
	@return	1 on success, 0 on error.
*/
static
int
construct_request DK_P1(PJ *,pj)
{
  int back = 0;
  
  prqd_pj_get_names(pj);
  if((pj->uname) && (pj->pname)) {
    back = 1;
    if(pj->tname) {
      if(pj->pages) {
        if(prqd_pj_get_arg(pj, 'D')) {
          sprintf(
            pj->b2, "%s '-n%s' '-P%s' '-p%s' '-J%s' '-h%s' '-D%s'",
	    pj->rqtstr, pj->uname, pj->pname, pj->pages, pj->tname,
	    XSTR(short_host_name(pj->hname)), prqd_pj_get_arg(pj, 'D')
          );
        } else {
          sprintf(
            pj->b2, "%s '-n%s' '-P%s' '-p%s' '-J%s' '-h%s'",
	    pj->rqtstr, pj->uname, pj->pname, pj->pages, pj->tname,
	    XSTR(short_host_name(pj->hname))
          );
        }
      } else {
        if(prqd_pj_get_arg(pj, 'D')) {
          sprintf(
            pj->b2, "%s '-n%s' '-P%s' '-J%s' '-h%s' '-D%s'",
	    pj->rqtstr, pj->uname, pj->pname, pj->tname,
	    XSTR(short_host_name(pj->hname)), prqd_pj_get_arg(pj, 'D')
          );
        } else {
          sprintf(
            pj->b2, "%s '-n%s' '-P%s' '-J%s' '-h%s'",
	    pj->rqtstr, pj->uname, pj->pname, pj->tname,
	    XSTR(short_host_name(pj->hname))
          );
        }
      }
      pj->rqtstr = NULL;
      strcpy(pj->b1, pj->b2); (pj->b2)[0] = '\0';
    } else {
      if(pj->pages) {
        if(prqd_pj_get_arg(pj, 'D')) {
          sprintf(
            pj->b2, "%s '-n%s' '-P%s' '-p%s' '-JUNNAMED' '-h%s' '-D%s'",
	    pj->rqtstr, pj->uname, pj->pname, pj->pages,
	    XSTR(short_host_name(pj->hname)), prqd_pj_get_arg(pj, 'D')
          );
        } else {
          sprintf(
            pj->b2, "%s '-n%s' '-P%s' '-p%s' '-JUNNAMED' '-h%s'",
	    pj->rqtstr, pj->uname, pj->pname, pj->pages,
	    XSTR(short_host_name(pj->hname))
          );
        }
      } else {
        if(prqd_pj_get_arg(pj, 'D')) {
          sprintf(
            pj->b2, "%s '-n%s' '-P%s' '-JUNNAMED' '-h%s' '-D%s'",
	    pj->rqtstr, pj->uname, pj->pname,
	    XSTR(short_host_name(pj->hname)), prqd_pj_get_arg(pj, 'D')
          );
        } else {
          sprintf(
            pj->b2, "%s '-n%s' '-P%s' '-JUNNAMED' '-h%s'",
	    pj->rqtstr, pj->uname, pj->pname,
	    XSTR(short_host_name(pj->hname))
          );
        }
      }
      pj->rqtstr = NULL;
      strcpy(pj->b1, pj->b2); (pj->b2)[0] = '\0';
    }
  }
  if(!back) {
    prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(64));
  }
  
  return back;
}
#endif



/**	Pass request to prqd. Use the UNIX domain socket to connect
	to prqd, send request, wait for response and print response
	if necessary.
	@param	pj	Pointer to prqd job structure.
	@return	1 on success, 0 on error.
*/
static
int
run_request DK_P1(PJ *,pj)
{
  int back = 0;
  int sock = -1;
  int cc = 0;
  struct sockaddr_un soun;
  size_t sz, sz2;
  int is_first;
  
  if((pj->prqdc)->sockname) {						
    if(strlen((pj->prqdc)->sockname) < 108) {				
      sock = socket(PF_UNIX, SOCK_STREAM, 0);
      if(sock > -1) {							
        soun.sun_family = AF_UNIX;
	strcpy(soun.sun_path, (pj->prqdc)->sockname);
	if(connect(sock, (struct sockaddr*)(&soun), SZSOUN) == 0) {	
	  syslog(LOG_INFO, "Successfully connected to server.");
	  sz = 1 + strlen(pj->b1);
	  sz2 = send(sock, pj->b1, sz, 0);
	  if(sz2 >= sz) {						
	    shutdown(sock, SHUT_WR);
	    cc = 1; is_first = 1;
	    while(cc) {
	      sz2 = recv(sock, pj->b1, pj->sz_b1, 0);
	      if(sz2 >= pj->sz_b1) {
		(pj->b1)[pj->sz_b1 - 1] = '\0';
	      } else {
		(pj->b1)[sz2] = '\0';
	      } 
	      if(sz2 > 0) {
	        if(is_first) {						
		  is_first = 0;
		  if(pj->must_respond) {				
		    strcpy(pj->b2, pj->b1);
		    
		  }
		}
		back = 1;
	      } else {							
	        cc = 0;
	      }
	    }
	    if(!(pj->must_respond)) {
	      back = 1;
	    }
	  } else {							
	    prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(66));
	  }
	} else {							
	  prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(65));
	}
	close(sock); sock = -1;
      } else {								
        prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(4));
      }
    } else {								
      prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(5), (pj->prqdc)->sockname);
    }
  } else {								
    prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(24));
  }
  
  return back;
}



/**	Process one request.
	Before passing the request to prqd do sanity checks.
	When running as prqdinfo allow info requests only.
	For prqdcl allow jobstart, filestart, fileend and jobend
	requests.
	For prqdadm allow the full protocol (all of the above and
	control requests).
	@param	pj	Pointer to prqd job structure.
*/
static
void
process_request DK_P1(PJ *,pj)
{
  char *p1, *p2, **ptr;
  int i = 0;
  
  
#if DEBUG
  {
    time_t timer;
    struct tm *tm;
    FILE *f;
    f = dksf_fopen("/tmp/prqdcl.log", "a");
    if(f) {
      time(&timer);
      tm = localtime(&timer);
      fprintf(f, "PID=%lu ", (unsigned long)getpid());
      if(tm) {
        fprintf(f, "TIME=%04d/%02d/%02d-%02d:%02d:%02d",
	  (1900 + tm->tm_year),
	  (1 + tm->tm_mon),
	  tm->tm_mday,
	  tm->tm_hour,
	  tm->tm_min,
	  tm->tm_sec
	);
      }
      fputc('\n', f);
      fputs(pj->b1, f); fflush(f); fclose(f);
    }
  }
#endif
  ptr = pj->args; for(i = 0; i < 52; i++) { *(ptr++) = NULL; }
  pj->rqtstr = p1 = dkstr_start(pj->b1, NULL);
  if(p1) {
    dkstr_chomp(p1, NULL);
    p2 = dkstr_next(p1, NULL);
    prqd_pj_get_rq_type(pj, p1);
    switch(pj->rqt) {
#if !PRQDINFO
      case PRQD_RQ_T_JOBSTART: {	
        prqd_pj_set_all_args(pj, p2);
#if ALWAYS_PERMIT
        strcpy(pj->b2, "ACCEPT\n");
        pj->must_respond = 0x01;
#else
/* if ALWAYS_PERMIT */
        pj->must_respond = 0x01;
        if(construct_request(pj)) {
	  if(!run_request(pj)) {
	    prqd_pj_use_deny_action(pj);
	  }
	} else {
	  prqd_pj_use_deny_action(pj);
	}
#endif
/* if ALWAYS_PERMIT */
      } break;
      case PRQD_RQ_T_FILESTART: case PRQD_RQ_T_START: { 
        pj->must_respond = 0x00;
        prqd_pj_set_all_args(pj, p2);
	if(construct_request(pj)) {
	  (void)run_request(pj);
	}
      } break;
      case PRQD_RQ_T_END: case PRQD_RQ_T_FILEEND: {	
        pj->must_respond = 0x00;
        prqd_pj_set_all_args(pj, p2);
	if(construct_request(pj)) {
	  (void)run_request(pj);
	}
      } break;
      case PRQD_RQ_T_JOBEND: {		
        pj->must_respond = 0x00;
        prqd_pj_set_all_args(pj, p2);
	if(construct_request(pj)) {
	  (void)run_request(pj);
	}
      } break;
#if PRQDADM
      case PRQD_RQ_T_CONTROL: {
        
        pj->must_respond = 0x00;
	if(p2) {
	  sprintf(pj->b2, "control %s", p2);
	  strcpy(pj->b1, pj->b2);
	  (void)run_request(pj);
	}
      } break;
#else
/* if PRQDADM */
      case PRQD_RQ_T_CONTROL: {
        pj->must_respond = 0x00;

	
      } break;
#endif
/* if PRQDADM */
#endif
/* if !PRQDINFO */
      case PRQD_RQ_T_INFO: {
        pj->must_respond = 0x01;
	if(p2) {
	  sprintf(pj->b2, "info %s", p2);
	  strcpy(pj->b1, pj->b2);
	  (void)run_request(pj);
	}
	sleep(1);
      } break;
      default: {
        prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(63), p1);
      } break;
    }
  }
  
}



/**	Check whether to continue.
	@param	pj	Pointer to prqd job structure.
	@return	1 for ``can continue'', 0 for need to finish.
*/
static
int
prqd_cc1 DK_P1(PJ *,pj)
{
  int back = 1;
  if(pj->e1) back = 0;
  return back;
}



/**	Run a service session. Read input from standard input as
	long as possible and process the input requests.
	If the program is run as prqdcl, process only one request
	and exit.
*/
static
void
run DK_P1(PJ *,pj)
{
  int cc;
  char *p1;
  
  pj->prqdc = prqdconf_new_prqdc(pj, prqdconf_get_cfgfile());
  if(pj->prqdc) {
    cc = 1;
    pj->e1 = pj->e2 = pj->e3 = 0;
/*
	For info and adm handle multiple input lines in
	one session.
*/
#if PRQDINFO || PRQDADM
    while((cc) && prqd_cc1(pj)) {
      pj->must_respond = 0x00; (pj->b2)[0] = '\0';
      if(fgets(pj->b1, pj->sz_b1, stdin)) {
        p1 = dkstr_start(pj->b1, NULL);
	if(p1) {
	  dkstr_chomp(p1, NULL);
          process_request(pj);
	  if(pj->must_respond) {
	    fputs(pj->b2, stdout); fflush(stdout);
	  }
	}
      } else {
        cc = 0;
      }
    }
/*
	LPRng invokes prqdcl once at start of job and once
	at end of job, it waits for the return code.
	So prqdcl has to process one input line and exit.
*/
#else
/* if PRQDINFO || PRQDADM */
    pj->must_respond = 0x00; (pj->b2)[0] = '\0';
    if(fgets(pj->b1, pj->sz_b1, stdin)) {
      p1 = dkstr_start(pj->b1, NULL);
      if(p1) {
        dkstr_chomp(p1, NULL);
        process_request(pj);
        if(pj->must_respond) {
	  fputs(pj->b2, stdout); fflush(stdout);
        }
      }
    }
#endif
/* if PRQDINFO || PRQDADM */
    prqdconf_delete_prqdc(pj->prqdc); pj->prqdc = NULL;
  } else {
    
    prqdlog(pj, PRQD_PRIO_ERROR, prqd_get_kw(22));
  } 
}


/** Buffer to read input line. */
static char inbuffer[PRQD_BUFFER_SIZE];

/** Buffer to construct output line. */
static char outbuffer[PRQD_BUFFER_SIZE];

/** Buffer for database key. */
static char dbkey[PRQD_DBENTRY_SIZE];

/** Buffer for database value. */
static char dbval[PRQD_DBENTRY_SIZE];

/** Buffer for program name. */
static char pgnamebuffer[PRQD_DBENTRY_SIZE];

/** Array for up to 53 request arguments. */
static char *args[53];



/**	The main function.
	Initialize a prqd job structure and invoke run()
	for the real work.
	@param	argc	Number of command line arguments.
	@param	argv	Command line arguments array.
	@return	0 on success, positive numbers on error.
*/
#if DK_HAVE_PROTOTYPES
int main(int argc, char *argv[])
#else
int main(argc, argv) int argc; char *argv[];
#endif
{
  PJ pj;
  
#line 493 "prqdcl.ctr"

  
  prqd_pj_init(&pj);
  pj.b1 = inbuffer; pj.sz_b1 = sizeof(inbuffer);
  pj.b2 = outbuffer; pj.sz_b2 = sizeof(outbuffer);
  pj.b3 = pgnamebuffer; pj.sz_b3 = sizeof(pgnamebuffer);
  pj.args = args;
  pj.dbkey = dbkey; pj.sz_dbkey = sizeof(dbkey);
  pj.dbval = dbval; pj.sz_dbval = sizeof(dbval);
  openlog("prqdcl", 0, LOG_LPR);
  run(&pj);
  closelog();
  
  
#line 506 "prqdcl.ctr"

  exit(0); return 0;
}



