/*
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	fsnmp.c	The fsnmp module in the fsnmp program.
*/



/**	Inside the fsnmp module.
*/
#define FSNMP_C		1

#include "fsnmp.h"



#include "dktools-version.h"




#line 56 "fsnmp.ctr"




#if DK_HAVE_SYS_TIME_H
#include <sys/time.h>
#endif



/**	Ctrl-D character.
*/
#define CTRL_D (0x04)



/**	Normal filter: read input and save contents to temporary file.
	@param	fc	Fsnmp job.
*/
static
void
filter_input DK_P1(FC *,fc)
{
  FILE *fipo;
  ssize_t s1; size_t s2;
  
  if(fsnmp_cc(fc)) {
    fipo = dksf_fopen(fc->temp_file, "wb");
    do {
      s1 = read(0, fc->buffer_in, fc->sz_buffer_in);
      if(s1 > 0) {
        
        if(fipo) {
          s2 = fwrite(fc->buffer_in, 1, s1, fipo);
	  
	  if(s2 < s1) {
	    fc->exit_code = EXIT_FAILED;
	    fsnmplog(fc, PRIO_ERROR, fsnmp_kw[47]);
	  }
        } else {
          s2 = s1;
        }
      }
    } while((s1 > 0) && (s2 == s1) && fsnmp_cc(fc));
    
    if(fipo) {
      fclose(fipo); fipo = NULL;
    } else {
      fc->exit_code = EXIT_FAILED;
      fsnmplog(fc, PRIO_ERROR, fsnmp_kw[48]);
    }
  }
  
}



/**	Application name.
*/
char fsnmp_application_name[] = { "fsnmp" };



/**	OID for printer status.
*/
static char oid_printer_status[] = { ".1.3.6.1.2.1.25.3.5.1.1.1" };

/**	OID for device status.
*/
static char oid_device_status[] =  { "1.3.6.1.2.1.25.3.2.1.5.1" };

/**	OID for pagecount value.
*/
static char oid_pagecount[] =      { ".1.3.6.1.2.1.43.10.2.1.4.1.1" };



/**	Prepare for one SNMP request.
	@param	fc	Fsnmp job.
*/
static
void
initialize_snmp DK_P1(FC *,fc)
{
  
  if(fsnmp_cc(fc)) {
    init_snmp(fsnmp_application_name);
    read_objid(oid_device_status, fc->oid_ds, &(fc->sz_oid_ds));
    read_objid(oid_printer_status, fc->oid_ps, &(fc->sz_oid_ps));
    read_objid(oid_pagecount, fc->oid_pc, &(fc->sz_oid_pc));
  }
  
}



/**	Compare two OIDs.
	@param	o1	Left OID
	@param	l1	Number of parts in o1.
	@param	o2	Right OID.
	@param	l2	Number of parts in o2
	@return	Comparison result (1=equal, 0=unequal).
*/
static
int
oids_equal DK_P4(oid *,o1,size_t,l1,oid *,o2,size_t,l2)
{
  int back = 0;
  size_t i;
  
  if(l1 == l2) {
    back = 1;
    for(i = 0; i < l1; i++) {
      if(o1[i] != o2[i]) {
        back = 0;
      }
    }
  }
  
  return back;
}




/**	Get int value from SNMP response PDU.
	@param	fc	Fsnmp job.
	@param	v	One variable from response PDU.
	@param	dest	Pointer to result variable.
	@return	1 on success, 0 on error.
*/
static
int
get_int DK_P3(FC *,fc, struct variable_list *,v, int *,dest)
{
  int back = 0;
  int value = 0;
  int i;
  char mybuffer[512];
  
  switch(v->type) {
    case (int)(ASN_OCTET_STR): {
      if(v->val_len < sizeof(mybuffer)) {
        char *sptr, *dptr;
	sptr = (char *)((v->val).string); dptr = mybuffer;
	for(i = 0; i < v->val_len; i++) { *(dptr++) = *(sptr++); }
	*dptr = '\0';
	if(sscanf(mybuffer, "%d", &i) == 1) {
	  value = i; back = 1;
	}
      }
    } break;
    case (int)(ASN_TIMETICKS):
    case (int)(ASN_GAUGE):
    case (int)(ASN_COUNTER):
    case (int)(ASN_INTEGER):
    {
      value = *(v->val).integer; back = 1;
    } break;
  }
  
  if(back) { *dest = value; }
  return back;
}



/**	Get unsigned long value from SNMP response PDU.
	@param	fc	Fsnmp job.
	@param	v	One variable from response PDU.
	@param	dest	Pointer to result variable.
	@return	1 on success, 0 on error.
*/
static
int
get_ul DK_P3(FC *,fc, struct variable_list *,v, unsigned long *,dest)
{
  unsigned long back = 0UL, u;
  int value = 0;
  int i;
  char mybuffer[512];
  
  switch(v->type) {
    case (int)(ASN_OCTET_STR): {
      if(v->val_len < sizeof(mybuffer)) {
        char *sptr, *dptr;
	sptr = (char *)((v->val).string); dptr = mybuffer;
	for(i = 0; i < v->val_len; i++) { *(dptr++) = *(sptr++); }
	*dptr = '\0';
	if(sscanf(mybuffer, "%lu", &u) == 1) {
	  value = u; back = 1;
	}
      }
    } break;
    case (int)(ASN_TIMETICKS):
    case (int)(ASN_GAUGE):
    case (int)(ASN_COUNTER):
    case (int)(ASN_INTEGER):
    {
      value = (unsigned long)(*(v->val).integer); back = 1;
    } break;
  }
  
  if(back) { *dest = value; }
  return back;
}



/**	Apply response PDU to state description.
	@param	fc	Fsnmp job.
	@param	cs	State description.
	@param	rs	Response PDU.
*/
static
int
apply_response_to_state DK_P3(FC *,fc, SUMMARY *,cs, struct snmp_pdu *,rs)
{
  int back = 1;
  struct variable_list *v;
  v = rs->variables;
  while(v) {
    if(oids_equal(v->name, v->name_length, fc->oid_ds, fc->sz_oid_ds)) {
      if(!get_int(fc, v, &(cs->ds))) back = 0;
    }
    if(oids_equal(v->name, v->name_length, fc->oid_ps, fc->sz_oid_ps)) {
      if(!get_int(fc, v, &(cs->ps))) back = 0;
    }
    if(oids_equal(v->name, v->name_length, fc->oid_pc, fc->sz_oid_pc)) {
      if(!get_ul(fc, v, &(cs->pc))) back = 0;
    }
    v = v->next_variable;
  }
  return back;
}



/**	Intervals for logging. */
static int log_intervals[] = {
  1, 2, 5, 10, 15, 30, 60, 120, 300, 600, 1800, 3600
};
/**	Number of entries in log_intervals array. */
static size_t no_log_intervals = sizeof(log_intervals)/sizeof(int);


/**	Keywords for accounting lines.
*/
static char *begin_kw[] = { "filestart", "fileend", "start", "end", NULL };



/**	Add option.
	@param	fc	Fsnmp job.
	@param	k	Option to add.
*/
static
void
add_opt DK_P2(FC *,fc, char,k)
{
  char *v;
  size_t sz;
  char buffer[5];
  v = fsnmpcmd_get_argv(fc, k);
  if(v) {
    sz = 6 + strlen(v);
    if((sz + strlen(fc->buffer_out)) < fc->sz_buffer_out) {
      buffer[0] = ' '; buffer[1] = '\''; buffer[2] = '-';
      buffer[3] = k; buffer[4] = '\0';
      strcat(fc->buffer_out, buffer);
      strcat(fc->buffer_out, v);
      buffer[0] = '\''; buffer[1] = '\0';
      strcat(fc->buffer_out, buffer);
    }
  }
}



/**	Add option if it does not yet exist.
	@param	fc	Fsnmp job.
	@param	k	Character for option to add.
*/
static
void
add_if_not_done DK_P2(FC *,fc, char,k)
{
  switch(k) {
    case 'H': case 'n': case 'P': case 'b':
    case 't': case 'C': case 'J': { } break;
    default: {
      add_opt(fc, k);
    } break;
  }
}



/**	Bind local address.
	@param	fc	Fsnmp job.
	@param	sock	Socket fd.
	@param	pmin	Minimum local port number.
	@param	pmax	Maximum local port number.
	@return	1 on success, 0 on error.
*/
static
int
bind_local_address DK_P4(FC *,fc, int,sock, unsigned,pmin, unsigned,pmax)
{
  int back = 1;
#if USE_SETSOCKOPT
  int yes = 1;
#endif
  struct sockaddr_in sain;
  unsigned u;
  
  if(fsnmp_cc(fc)) {
#if USE_SETSOCKOPT
    if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) {
      fsnmplog(fc, PRIO_ERROR, fsnmp_kw[81]);
    }
#endif
  }
  if(!((pmin == 0) && (pmax == 0))) {
    back = 0;
    u = pmin;
    while((u <= pmax) && (!back) && fsnmp_cc(fc)) {
      sain.sin_family = AF_INET;
      sain.sin_port = htons(u);
      sain.sin_addr.s_addr = htonl(INADDR_ANY);
      if(bind(sock, (struct sockaddr *)(&sain), SZSOIN) == 0) {
        back = 1;
      } else {
        u++;
      }
    }
    if(back) {
      fsnmplog(fc, PRIO_INFO, fsnmp_kw[76], u);
    } else {
      if(fsnmp_cc(fc)) {
        fsnmplog(fc, PRIO_ERROR, fsnmp_kw[77], pmin, pmax);
	fc->exit_code = EXIT_FAILED;
      }
    }
  }
  
  return back;
}



/** Timeout for socket operations. */
typedef struct timeval TV;



/** Size of timeval. */
#define SZTV sizeof(TV)



/**	Pass pagecount to accounting system.
	@param	fc	Fsnmp job.
	@param	isof	Flag: Is OF filter.
	@param	passno	Pass (0=before, 1=after data transfer).
	@param	pc	Page count.
*/
static
void
do_accounting DK_P4(FC *,fc, int,isof, int,passno, unsigned long,pc)
{
  int back = 0;
  char c;
  char *fn;
  char *ptr, *optr;
  FILE *fipo;
  unsigned long ipaddr;
  int sock, res;
  struct sockaddr_in sain; unsigned u;
  struct timeval tv;
  fd_set rfds;
  size_t sz;
  sprintf(fc->buffer_out, "%s '-p%lu'", begin_kw[2*isof+passno], pc);
  
  add_opt(fc, 'H');
  add_opt(fc, 'n');
  add_opt(fc, 'P');
  add_opt(fc, 'b');
  add_opt(fc, 't');
  add_opt(fc, 'C');
  add_opt(fc, 'J');
  for(c = 'a'; c <= 'z'; c++) add_if_not_done(fc, c);
  for(c = 'A'; c <= 'Z'; c++) add_if_not_done(fc, c);
  
  fn = fc->a_acctdest;
  if(!fn) {
    fn = fsnmpcmd_get_argv(fc, 'a');
  }
  if(fn) {
    fn = dkstr_start(fn, NULL);
    if(fn) {
      dkstr_chomp(fn, NULL);
      fsnmplog(fc, PRIO_PROGRESS, fsnmp_kw[61], fn);
      if(*fn == '|') {	
        fn++;
	fsnmplog(fc, PRIO_PROGRESS, fsnmp_kw[85], fn);
	fipo = popen(fn, "w");
	if(fipo) {
	  fputs(fc->buffer_out, fipo);
	  fputc('\n', fipo); fflush(fipo);
	  back = 1;
	  pclose(fipo); fipo = NULL;
	  fsnmplog(fc, PRIO_PROGRESS, fsnmp_kw[84], fn);
	} else {
	  fsnmplog(fc, PRIO_ERROR, fsnmp_kw[65]);
	  fc->exit_code = EXIT_ABORT;
	}
      } else {
        ptr = dkstr_chr(fn, '%');
	if(ptr) {	
	  optr = ptr;
	  *(ptr++) = '\0';
	  ipaddr = fsnmp_dotted_string_to_ip(fn);
	  if(ipaddr) {
	    ipaddr = htonl(ipaddr);
	  } else {
	    ipaddr = fsnmp_lookup_host(fn);
	  }
	  if(ipaddr) {
	    if(sscanf(ptr, "%u", &u) == 1) {
	      sain.sin_family = AF_INET;
	      sain.sin_port = htons(u);
	      sain.sin_addr.s_addr = ipaddr;
	      sock = socket(AF_INET, SOCK_STREAM, 0);
	      if(sock > -1) {
		if(bind_local_address(fc, sock, fc->ac_pmin, fc->ac_pmax)) {
	          res = connect(sock, (struct sockaddr *)(&sain), SZSOIN);
		  if(res == 0) {
		    sz = strlen(fc->buffer_out);
		    res = send(sock, fc->buffer_out, sz, 0);
		    if(res == sz) {
		      back = 1;
		      if((fc->flags) & FSNMP_SHUTDOWN_PAGECOUNT) {
		        fsnmplog(fc, PRIO_PROGRESS, fsnmp_kw[72]);
		        if(shutdown(sock, SHUT_WR) == 0) {
			  res = 1;
			  fsnmplog(fc, PRIO_PROGRESS, fsnmp_kw[73]);
			  while((res > 0) && fsnmp_cc(fc)) {
			    res = 0;
			    if((fc->flags) & FSNMP_TIMEOUT_PAGECOUNT) {
			      dkmem_cpy((void *)(&tv), (void *)(&(fc->shp)), SZTV);
			      FD_ZERO(&rfds); FD_SET(sock, &rfds);
			      if(select((1 + sock), &rfds, NULL, NULL, &tv) > 0) {
			        if(FD_ISSET(sock, &rfds)) {
			          res = recv(sock, fc->buffer_in, fc->sz_buffer_in, 0);
			        }
			      }
			    } else {
			      res = recv(sock, fc->buffer_in, fc->sz_buffer_in, 0);
			    }
			  }
		        } else {
			  fsnmplog(fc, PRIO_ERROR, fsnmp_kw[71]);
			  fc->exit_code = EXIT_FAILED;
		        }
		      }
		    } else {
		      fsnmplog(fc, PRIO_ERROR, fsnmp_kw[70]);
		      fc->exit_code = EXIT_FAILED;
		    }
		  } else {
		    fsnmplog(fc, PRIO_ERROR, fsnmp_kw[69], fn);
		    fc->exit_code = EXIT_FAILED;
		  }
		} else {
		  fc->exit_code = EXIT_FAILED;
		}
		fsnmplog(fc, PRIO_PROGRESS, fsnmp_kw[74]);
	        close(sock); sock = -1;
	      } else {
		fsnmplog(fc, PRIO_ERROR, fsnmp_kw[68]);
		fc->exit_code = EXIT_FAILED;
	      }
	    } else {
	      fsnmplog(fc, PRIO_ERROR, fsnmp_kw[67], ptr);
	      fc->exit_code = EXIT_ABORT;
	    }
	  } else {
	    fsnmplog(fc, PRIO_ERROR, fsnmp_kw[66], fn);
	    fc->exit_code = EXIT_ABORT;
	  }
	  *optr = '%';
	} else {	
	  fipo = dksf_fopen(fn, "a");
	  if(fipo) {
	    fputs(fc->buffer_out, fipo);
	    fputc('\n', fipo); fflush(fipo);
	    back = 1;
	    fclose(fipo); fipo = NULL;
	  } else {
	    fsnmplog(fc, PRIO_ERROR, fsnmp_kw[64]);
	    fc->exit_code = EXIT_ABORT;
	  }
	}
      }
      if(back) {
        fsnmplog(fc, PRIO_PROGRESS, fsnmp_kw[62]);
      } else {
        fsnmplog(fc, PRIO_ERROR, fsnmp_kw[63]);
      }
    } else {
      
      fsnmplog(fc, PRIO_ERROR, fsnmp_kw[60]);
      fc->exit_code = EXIT_ABORT;
    }
  } else {
    
    fsnmplog(fc, PRIO_ERROR, fsnmp_kw[59]);
  }
  
}



/**	Obtain pagecount from printer, pass pagecount to the
	accounting system.
	@param	fc	Fsnmp job.
	@param	isof	Flag: Is OF filter.
	@param	passno	Which pass (0=before, 1=after data transfer).
*/
static
void
do_pagecount DK_P3(FC *,fc, int,isof, int,passno)
{
  char ipnamebuffer[32];
  unsigned long ipaddr;
  struct snmp_session session, *ss = NULL;
  struct snmp_pdu *rq, *rs = NULL;
  int cc;
  int log_reason, last_log_reason, status;
  int next_log_index, must_log, have_pagecount;
  time_t next_log_time, current_time;
  SUMMARY cs, os;
  
  fsnmplog(fc, PRIO_PROGRESS, fsnmp_kw[13], fsnmp_kw[passno ? 15 : 14]);
  ipaddr = (fc->sain).sin_addr.s_addr;
  ipaddr = ntohl(ipaddr);
  sprintf(ipnamebuffer, "%lu.%lu.%lu.%lu",
    ((ipaddr >> 24) & 0xFFUL),
    ((ipaddr >> 16) & 0xFFUL),
    ((ipaddr >>  8) & 0xFFUL),
    ( ipaddr        & 0xFFUL)
  ); 
  if(fc->a_community) {				
    time(&(fc->snmp_start_time));
    snmp_sess_init(&session);
    session.version = fc->snmp_vers;
    session.peername = ipnamebuffer;
    session.community = (unsigned char *)(fc->a_community);
    session.community_len = strlen(fc->a_community);
    ss = snmp_open(&session);
    if(ss) {					
      next_log_time = 0; next_log_index = 0;
      last_log_reason = 0;
      os.ds = -1; os.ps = -1; os.pc = 0UL;
      have_pagecount = 0;
      do {					
        time(&current_time);
        log_reason = cc = 0; rq = rs = NULL;
        /* attempt to get pagecount */
	cs.ds = -1; cs.ps = -1; cs.pc = 0UL;
	rq = snmp_pdu_create(SNMP_MSG_GET);
	if(rq) {				
	  snmp_add_null_var(rq, fc->oid_ds, fc->sz_oid_ds);
	  snmp_add_null_var(rq, fc->oid_ps, fc->sz_oid_ps);
	  snmp_add_null_var(rq, fc->oid_pc, fc->sz_oid_pc);
	  rs = NULL;
	  status = snmp_synch_response(ss, rq, &rs);
	  time(&current_time);
	  if(status == STAT_SUCCESS) {		
	    if(rs) {				
	      if(rs->errstat == SNMP_ERR_NOERROR) {	
	        if(apply_response_to_state(fc, &cs, rs)) {	
		  if((cs.ds != -1) && (cs.ps != -1)) {	
		    log_reason = FSNMP_LOG_HAVE_SNMP;
		    if(((cs.ds == DEVICE_RUNNING) || (cs.ds == DEVICE_WARNING))
		       && ((cs.ps == PRINTER_IDLE) || (cs.ps == PRINTER_OTHER)))
		    { 
		      
		      log_reason = FSNMP_LOG_PRINTER_READY; have_pagecount = 1;
		      if(passno) {		
		        if(cs.pc > fc->pc1) {	
			   fc->pc2 = cs.pc;
			} else {		
			  if(current_time > fc->snmp_start_time + fc->mintimepc) {
			    fc->pc2 = cs.pc;	
			  } else {		
			    log_reason = FSNMP_LOG_NOT_YET_PRINTED;
			    cc = 1;
			    have_pagecount = 0;
			  }
			}
		      } else {			
		        fc->pc1 = cs.pc;
		      }
		    }
		    else
		    {
		      
		      if(cs.ps == PRINTER_PRINTING) {
		        log_reason = FSNMP_LOG_PRINTER_BUSY;
		      }
		      cc = 1;
		    }
		  } else {			
		    cc = 1; log_reason = FSNMP_LOG_ERROR_RETRIEVING;
		  }
		} else {			
		  cc = 1;
		  log_reason = FSNMP_LOG_ERROR_RETRIEVING;
		}
	      } else {				
		log_reason = FSNMP_LOG_ERROR_IN_RESPONSE;
		cc = 1;
	      }
	      snmp_free_pdu(rs); rs = NULL;
	    } else {				
	      log_reason = FSNMP_LOG_NO_RESPONSE;
	      cc = 1;
	    }
	  } else {				
	    /* snmp_sess_perror(fsnmp_application_name, ss); */
	    log_reason = FSNMP_LOG_NO_RESPONSE;
	    cc = 1;
	  }
	} else {				
	  fc->exit_code = EXIT_FAILED;
	  fsnmplog(fc, PRIO_ERROR, fsnmp_kw[49]);
	}
	/* log */
	must_log = 0;
	if(log_reason != last_log_reason) {	
	  must_log = 1;
	  time(&next_log_time); next_log_time += 1; next_log_index = 0;
	} else {				
	  if(current_time >= next_log_time) {	
	    next_log_index++;
	    if(next_log_index >= no_log_intervals) next_log_index = no_log_intervals - 1;
	    next_log_time = current_time + log_intervals[next_log_index];
	    must_log = 1;
	  }
	}
	if(must_log) {
	  switch(log_reason) {
	    case FSNMP_LOG_NO_RESPONSE: {
	      fsnmplog(fc, PRIO_ERROR, fsnmp_kw[20]);
	    } break;
	    case FSNMP_LOG_ERROR_IN_RESPONSE: {
	      fsnmplog(fc, PRIO_ERROR, fsnmp_kw[21]);
	    } break;
	    case FSNMP_LOG_PRINTER_BUSY: {
	      fsnmplog(fc, PRIO_INFO, fsnmp_kw[22]);
	    } break;
	    case FSNMP_LOG_PRINTER_READY: {
	      fsnmplog(fc, PRIO_INFO, fsnmp_kw[23]);
	    } break;
	    case FSNMP_LOG_ERROR_RETRIEVING: {
	      fsnmplog(fc, PRIO_ERROR, fsnmp_kw[24]);
	    } break;
	    case FSNMP_LOG_PRINTER_PROBLEM:
	    case FSNMP_LOG_HAVE_SNMP: {
	      if(cs.ds < 1) cs.ds = 1; if(cs.ds > 5) cs.ds = 5;
	      if(cs.ps < 1) cs.ps = 2; if(cs.ps > 5) cs.ps = 5;
	      fsnmplog(
	        fc, PRIO_INFO, fsnmp_kw[25],
		fsnmp_kw[25+cs.ds], cs.ds, fsnmp_kw[30+cs.ps], cs.ps
	      );
	      if((cs.ds == DEVICE_WARNING) || (cs.ds == DEVICE_DOWN)
	         || (cs.ds == DEVICE_UNKNOWN))
	      {
	        fsnmplog(fc, PRIO_ERROR, fsnmp_kw[36]);
	      }
	    } break;
	    case FSNMP_LOG_NOT_YET_PRINTED: {
	      fsnmplog(fc, PRIO_PROGRESS, fsnmp_kw[37]);
	    } break;
	  }
	}
	last_log_reason = log_reason;
	/* sleep */
	if(cc && fsnmp_cc(fc)) {		
	  switch(log_reason) {
	    case FSNMP_LOG_PRINTER_BUSY: {
	      
	      fd_set rfds; struct timeval tv;
	      tv.tv_sec = 0; tv.tv_usec = 100000;
	      FD_ZERO(&rfds);
	      (void)select(0,&rfds,NULL,NULL,&tv);
	    } break;
	    default: {
	      
	      sleep(1);
	    } break;
	  }
	}					
      } while((cc) && fsnmp_cc(fc));
      snmp_close(ss); ss = NULL;
      if(fsnmp_cc(fc)) {
        if(have_pagecount) {
	  do_accounting(fc, isof, passno, cs.pc);
	  fsnmplog(fc, PRIO_PROGRESS, fsnmp_kw[19], cs.pc);
	} else {
	  fsnmplog(fc, PRIO_DEBUG, fsnmp_kw[18]);
	}
      }
    } else {					
      fc->exit_code = EXIT_FAILED;
      fsnmplog(fc, PRIO_ERROR, fsnmp_kw[17]);
    }
  } else {					
    fc->exit_code = EXIT_ABORT;
    fsnmplog(fc, PRIO_ERROR, fsnmp_kw[16]);
  }
  
}



/**	Normal filter: Prepare output, pagecount before print job.
	@param	fc	Fsnmp job.
*/
static
void
filter_start DK_P1(FC *,fc)
{
  
  if(fsnmp_cc(fc)) {
    initialize_snmp(fc);
    do_pagecount(fc, 0, 0);
  }
  
}



/**	Normal filter: Output finished, pagecount after print job.
	@param	fc	Fsnmp job.
*/
static
void
filter_end DK_P1(FC *,fc)
{
  
  if(fsnmp_cc(fc)) {
    do_pagecount(fc, 0, 1);
  }
  
}



/**	Normal filter: process response from printer.
	@param	fc	Fsnmp job.
	@param	s	Number of bytes in printer response.
*/
static
void
printer_response DK_P2(FC *,fc, size_t,s)
{
  char *ptr; size_t i;

  ptr = fc->buffer_in;
  for(i = 0; i < s; i++) {
    if(!((isascii(*ptr)) && (isprint(*ptr)))) {
      *ptr = ' ';
    }
    ptr++;
  }
  fsnmplog_response(fc, s);
}



/**	Normal filter: flush output buffer to printer.
	@param	fc	Fsnmp job.
*/
static
void
flush_output DK_P1(FC *,fc)
{
  ssize_t s1;
  fd_set rfds; struct timeval tv;
  if(fc->sz_bo_used && fsnmp_cc(fc)) {
    if((fc->flags) & FSNMP_FLAG_STDOUT) {
      s1 = write(fc->os, fc->buffer_out, fc->sz_bo_used);
    } else {
      s1 = send(fc->os, fc->buffer_out, fc->sz_bo_used, 0);
    }
    if(s1 < fc->sz_bo_used) {
      fc->exit_code = EXIT_FAILED;
      fsnmplog(fc, PRIO_ERROR, fsnmp_kw[50]);
    }
    if(fsnmp_cc(fc)) {
      FD_ZERO(&rfds); FD_SET(fc->os, &rfds);
      tv.tv_sec = 0; tv.tv_usec = 0;
      if(select((1 + fc->os), &rfds, NULL, NULL, &tv) > 0) {
        if(FD_ISSET(fc->os, &rfds)) {
	  if((fc->flags) & FSNMP_FLAG_STDOUT) {
	    s1 = read(fc->os, fc->buffer_in, fc->sz_buffer_in);
	  } else {
	    s1 = recv(fc->os, fc->buffer_in, fc->sz_buffer_in, 0);
	  }
	  if(s1 > 0) {
	    printer_response(fc, s1);
	  }
	}
      }
    }
  }
  fc->sz_bo_used = 0;
}



/**	Normal filter: add output character to output buffer,
	flush buffer if necessary.
	@param	fc	Fsnmp job.
	@param	c	Character (byte) to add.
*/
static
void
add_out_char DK_P2(FC *,fc, char,c)
{
  (fc->buffer_out)[fc->sz_bo_used] = c;
  fc->sz_bo_used += 1;
  if(fc->sz_bo_used >= fc->sz_buffer_out) {
    flush_output(fc);
  }
}



/**	Normal filter: Transfer contents of temporary file
	(created by filter_input()) to printer.
	@param	fc	Fsnmp job.
*/
static
void
write_temp_file DK_P1(FC *,fc)
{
  FILE *fipo;
  ssize_t s1 = 0, i = 0;
  int is_first = 1, last_was_ctrld = 0, had_data = 0;
  char *ptr;
  if(fsnmp_cc(fc)) {
    fipo = dksf_fopen(fc->temp_file, "rb");
    if(fipo) {
      fc->sz_bo_used = 0;
      do {
        s1 = fread(fc->buffer_in, 1, fc->sz_buffer_in, fipo);
	if(s1 > 0) {
	  had_data = 1;
	  if(is_first) {
	    is_first = 0;
	    if((fc->flags) & FSNMP_FLAG_CTRL_D_START) {
	      if((fc->buffer_in)[0] != CTRL_D) {
	        add_out_char(fc, CTRL_D);
	      }
	    }
	  }
	  ptr = fc->buffer_in;
	  for(i = 0; i < s1; i++) { add_out_char(fc, *(ptr++)); }
	  if((fc->buffer_in)[s1 - 1] == CTRL_D) {
	    last_was_ctrld = 1;
	  } else {
	    last_was_ctrld = 0;
	  }
	}
      } while(fsnmp_cc(fc) && (s1 > 0));
      if(fsnmp_cc(fc)) {
        if((fc->flags) & FSNMP_FLAG_CTRL_D_END) {
          if(!last_was_ctrld) {
	    if(had_data) {
	      add_out_char(fc, CTRL_D);
	    }
	  }
        }
        flush_output(fc);
      }
      fclose(fipo); fipo = NULL;
    } else {
      fc->exit_code = EXIT_FAILED;
      fsnmplog(fc, PRIO_ERROR, fsnmp_kw[51]);
    }
  }
}



/**	Find host name and port number.
	@param	fc	Fsnmp job.
*/
static
void
find_host_and_port DK_P1(FC *,fc)
{
  
  
}



/**	Normal filter: Send print job data to output.
	@param	fc	Fsnmp job.
*/
static
void
filter_output DK_P1(FC *,fc)
{
  struct sockaddr_in sain;
  struct timeval tv;
  fd_set rfds;
  int res;
  
  if(fsnmp_cc(fc)) {
    if((fc->flags) & FSNMP_FLAG_STDOUT) {
      fc->os = 1;
      fsnmplog(fc, PRIO_PROGRESS, fsnmp_kw[40]);
      write_temp_file(fc);
      if(fsnmp_cc(fc)) { fsnmplog(fc, PRIO_PROGRESS, fsnmp_kw[41]); }
    } else {
      find_host_and_port(fc);
      if(fsnmp_cc(fc)) {
	fsnmplog(fc, PRIO_PROGRESS, fsnmp_kw[38]);
	fc->os = socket(AF_INET, SOCK_STREAM, 0);
	if(fc->os > -1) {	
	  if(bind_local_address(fc, fc->os, fc->dt_pmin, fc->dt_pmax)) {
	    dkmem_cpy((void *)(&sain), (void *)(&(fc->sain)), SZSOIN);
	    res = connect(fc->os, (struct sockaddr *)(&sain), SZSOIN);
	    if(res == 0) {	
	      fsnmplog(fc, PRIO_PROGRESS, fsnmp_kw[39]);
	      write_temp_file(fc);
	      if(fsnmp_cc(fc)) { fsnmplog(fc, PRIO_PROGRESS, fsnmp_kw[41]); }
	      if((fc->flags) & FSNMP_SHUTDOWN_TRANSFER) {
	        fsnmplog(fc, PRIO_PROGRESS, fsnmp_kw[42]);
	        if(shutdown(fc->os, SHUT_WR) == 0) {
	          fsnmplog(fc, PRIO_PROGRESS, fsnmp_kw[43]);
	          res = 1;
	          while((res > 0) && fsnmp_cc(fc)) {
		    res = 0;
	            if((fc->flags) & FSNMP_TIMEOUT_TRANSFER) {
		      dkmem_cpy((void *)(&tv), (void *)(&(fc->shd)), SZTV);
		      FD_ZERO(&rfds); FD_SET(fc->os, &rfds);
		      if(select((1 + fc->os), &rfds, NULL, NULL, &tv) > 0) {
		        if(FD_ISSET(fc->os, &rfds)) {
		          res = recv(fc->os, fc->buffer_in, fc->sz_buffer_in, 0);
			  if(res > 0) { printer_response(fc, res); }
		        }
		      }
		    } else {
		      res = recv(fc->os, fc->buffer_in, fc->sz_buffer_in, 0);
		      if(res > 0) { printer_response(fc, res); }
		    }
		  }
	        } else {
		  fsnmplog(fc, PRIO_ERROR, fsnmp_kw[80]);
		  fc->exit_code = EXIT_FAILED;
		}
	      }
	    } else {		
	      fsnmplog(fc, PRIO_ERROR, fsnmp_kw[78]);
	      fc->exit_code = EXIT_FAILED;
	    }
	  } else {		
	  }
	  close(fc->os); fc->os = -1;
	  fsnmplog(fc, PRIO_PROGRESS, fsnmp_kw[44]);
	} else {		
	  fc->exit_code = EXIT_FAILED;
	  fsnmplog(fc, PRIO_ERROR, fsnmp_kw[79]);
	}
      }
    }
  }
  
}



/**	Normal filter: Run filter processing.
	@param	fc	Fsnmp job.
*/
void
fsnmp_filter DK_P1(FC *,fc)
{
  
  filter_input(fc);
  filter_start(fc);
  filter_output(fc);
  filter_end(fc);
  
}



/**	Of filter: Start processing (pagecount before job).
	@param	fc	Fsnmp job.
*/
static
void
of_start DK_P1(FC *,fc)
{
  
  if(fsnmp_cc(fc)) {
    initialize_snmp(fc);
    do_pagecount(fc, 1, 0);
  }
  
}



/**	Of filter: Finish processing (pagecount after job).
	@param	fc	Fsnmp job.
*/
static
void
of_end DK_P1(FC *,fc)
{
  
  if(fsnmp_cc(fc)) {
    do_pagecount(fc, 1, 1);
  }
  
}



/**	Of filter: Input sequence to suspend filter.
*/
static char filter_stop[] = { "\031\001" };



/**	Of filter: Transfer standard input to standard output,
	suspend itself if sequence is detected.
	@param	fc	Fsnmp job.
*/
static
void
of_transfer DK_P1(FC *,fc)
{
  int state = 0; char c;
  
  while((state < 2) && fsnmp_cc(fc)) {
    c = getchar();
    if(c == EOF) {	
      if(state == 1) {
        putchar(filter_stop[0]);
      }
      state = 2;
    } else {		
      switch(state) {
        case 0: {
          if(c == filter_stop[0]) {
	    state = 1;
	  } else {
	    putchar(c);
	  }
        } break;
        case 1: {
          if(c == filter_stop[1]) {
	    fflush(stdout);
	    state = 0;
	    
	    fsnmplog(fc, PRIO_PROGRESS, fsnmp_kw[45]);
	    kill(getpid(), SIGSTOP);
	    
	    fsnmplog(fc, PRIO_PROGRESS, fsnmp_kw[46]);
	    state = 0;
	  } else {
	    if(c == filter_stop[0]) {
	      putchar(filter_stop[0]);
	    } else {
	      putchar(filter_stop[0]);
	      putchar(c);
	      state = 0;
	    }
	  }
        } break;
      }
    }
  }
  fflush(stdout);
  
}



/**	Of filter: Filter processing.
	@param	fc	Fsnmp job.
*/
void
fsnmp_of DK_P1(FC *,fc)
{
  of_start(fc);
  of_transfer(fc);
  of_end(fc);
}



/**	Check whether or not standard output is already connected to
	a socket. Retrieve peer name to be used as hostname for SNMP.
	@param	fc	Fsnmp job.
*/
void
fsnmp_check_peer DK_P1(FC *,fc)
{
  size_t szsain;
  szsain = SZSOIN;
  if(getpeername(1, (struct sockaddr *)(&(fc->sain)), &szsain) == 0) {
    if(szsain == SZSOIN) {
      fc->flags |= FSNMP_FLAG_STDOUT;
      fsnmplog(fc, PRIO_INFO, fsnmp_kw[9]);
    }
  }
}



