/* 
   Unix SMB/Netbios implementation.
   Version 1.9.
   printing routines
   Copyright (C) Andrew Tridgell 1992-1995
   
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.
   
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
   
   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include "includes.h"
#include "loadparm.h"
extern int DEBUGLEVEL;
extern connection_struct Connections[];
extern files_struct Files[];

BOOL lpq_cache_reset = False;


/****************************************************************************
Build the print command in the supplied buffer. This means getting the
print command for the service and inserting the printer name and the
print file name. Return NULL on error, else the passed buffer pointer.
****************************************************************************/
static char *build_print_command(int cnum, char *command, char *syscmd, char *filename1)
{
  int snum = SNUM(cnum);
  char *tstr;
  pstring filename;
  
  /* get the print command for the service. */
  tstr = command;
  if (tstr == NULL) {
    DEBUG(0,("No print command for service `%s'\n", SERVICE(snum)));
    return (NULL);
  }

  /* copy the command into the buffer for extensive meddling. */
  strcpy(syscmd, tstr);
  
  /* look for "%s" in the string. If there is no %s, we cannot print. */   
  if (!strstr(syscmd, "%s") && !strstr(syscmd, "%f")) {
    DEBUG(2,("WARNING! No placeholder for the filename in the print command "
	     "for service %s!\n", SERVICE(snum)));
  }
  
  if (strstr(syscmd,"%s")) {
    int iOffset = strstr(syscmd, "%s") - syscmd;
    
    /* construct the full path for the filename, shouldn't be necessary unless
       the subshell causes a "cd" to be executed.
       Only use the full path if there isn't a / preceding the %s */
    if (iOffset==0 || syscmd[iOffset-1] != '/') {
      strcpy(filename,Connections[cnum].connectpath);
      trim_string(filename,"","/");
      strcat(filename,"/");
      strcat(filename,filename1);
    }
    else
      strcpy(filename,filename1);
    
    string_sub(syscmd, "%s", filename);
  }
  
  string_sub(syscmd, "%f", filename1);
  
  /* Does the service have a printername? If not, make a fake and empty    */
  /* printer name. That way a %p is treated sanely if no printer */
  /* name was specified to replace it. This eventuality is logged.         */
  tstr = PRINTERNAME(snum);
  if (tstr == NULL || tstr[0] == '\0') {
    DEBUG(3,( "No printer name - using %s.\n", SERVICE(snum)));
    tstr = SERVICE(snum);
  }
  
  string_sub(syscmd, "%p", tstr);
  
  standard_sub(cnum,syscmd);
  
  return (syscmd);
}


/****************************************************************************
print a file - called on closing the file
****************************************************************************/
void print_file(int fnum)
{
  pstring syscmd;
  int cnum = Files[fnum].cnum;
  char *tempstr;

  lpq_cache_reset = True;

  *syscmd = 0;

  if (file_size(Files[fnum].name) <= 0) {
    DEBUG(3,("Discarding null print job %s\n",Files[fnum].name));
    unlink(Files[fnum].name);
    return;
  }

  tempstr = build_print_command(cnum, PRINTCOMMAND(SNUM(cnum)), syscmd, Files[fnum].name);
  if (tempstr != NULL)
    {
      int ret = smbrun(syscmd,NULL);
      DEBUG(3,("Running the command `%s' gave %d\n",syscmd,ret));
    }
  else
    DEBUG(0,("Null print command?\n"));
}


/*******************************************************************
process time fields
********************************************************************/
static time_t EntryTime(string tok[], int ptr, int count, int minimum)
{
  time_t jobtime;

  jobtime = time(NULL);		/* default case: take current time */
  if (count >= minimum) {
    struct tm *t;
    int i, day, hour, min, sec;
    char   *c;
    const char *Months[13] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
				 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Err" };

    for (i=0; i<13; i++) if (!strncmp(tok[ptr], Months[i],3)) break; /* Find month */
    if (i<12) {
      t = localtime(&jobtime);
      day = atoi(tok[ptr+1]);
      c=(char *)(tok[ptr+2]);
      *(c+2)=0;
      hour = atoi(c);
      *(c+5)=0;
      min = atoi(c+3);
      if(*(c+6) != 0)sec = atoi(c+6);
      else  sec=0;

      if ((t->tm_mon < i)||
	  ((t->tm_mon == i)&&
	   ((t->tm_mday < day)||
	    ((t->tm_mday == day)&&
	     (t->tm_hour*60+t->tm_min < hour*60+min)))))
	t->tm_year--;		/* last year's print job */

      t->tm_mon = i;
      t->tm_mday = day;
      t->tm_hour = hour;
      t->tm_min = min;
      t->tm_sec = sec;
      jobtime = mktime(t);
    }
  }
  jobtime += GMT_TO_LOCAL*TimeDiff(jobtime);
  return jobtime;
}


/****************************************************************************
parse a lpq line

here is an example of lpq output under bsd

Warning: no daemon present
Rank   Owner      Job  Files                                 Total Size
1st    tridge     148  README                                8096 bytes
****************************************************************************/
static BOOL parse_lpq_bsd(char *line,print_queue_struct *buf,BOOL first)
{
  string tok[5];
  int count=0;

  /* handle the case of "(standard input)" as a filename */
  string_sub(line,"standard input","STDIN");
  string_sub(line,"(","\"");
  string_sub(line,")","\"");
  
  for (count=0; count<5 && next_token(&line,tok[count],NULL); count++) ;

  /* we must get 5 tokens */
  if (count < 5)
    return(False);

  /* the 3rd and 5th columns must be integer */
  if (!isdigit(*tok[2]) || !isdigit(*tok[4])) return(False);

  /* if the fname contains a space then use STDIN */
  if (strchr(tok[3],' '))
    strcpy(tok[3],"STDIN");

  /* only take the last part of the filename */
  {
    string tmp;
    char *p = strrchr(tok[3],'/');
    if (p)
      {
	strcpy(tmp,p+1);
	strcpy(tok[3],tmp);
      }
  }
	

  buf->job = atoi(tok[2]);
  buf->size = atoi(tok[4]);
  buf->status = strequal(tok[0],"active")?LPQ_PRINTING:LPQ_QUEUED;
  buf->time = time(NULL);
  StrnCpy(buf->user,tok[1],sizeof(buf->user)-1);
  StrnCpy(buf->file,tok[3],sizeof(buf->file)-1);
  return(True);
}



/*******************************************************************
parse lpq on an aix system

Queue   Dev   Status    Job Files              User         PP %   Blks  Cp Rnk
------- ----- --------- --- ------------------ ---------- ---- -- ----- --- ---
lazer   lazer READY
lazer   lazer RUNNING   537 6297doc.A          kvintus@IE    0 10  2445   1   1
              QUEUED    538 C.ps               root@IEDVB           124   1   2
              QUEUED    539 E.ps               root@IEDVB            28   1   3
              QUEUED    540 L.ps               root@IEDVB           172   1   4
              QUEUED    541 P.ps               root@IEDVB            22   1   5
********************************************************************/
static BOOL parse_lpq_aix(char *line,print_queue_struct *buf,BOOL first)
{
  string tok[11];
  int count=0;

  /* handle the case of "(standard input)" as a filename */
  string_sub(line,"standard input","STDIN");
  string_sub(line,"(","\"");
  string_sub(line,")","\"");

  for (count=0; count<10 && next_token(&line,tok[count],NULL); count++) ;

  /* we must get 6 tokens */
  if (count < 10)
  {
      if ((count == 7) && (strcmp(tok[0],"QUEUED") == 0))
      {
          /* the 2nd and 5th columns must be integer */
          if (!isdigit(*tok[1]) || !isdigit(*tok[4])) return(False);
          buf->size = atoi(tok[4]) * 1024;
          /* if the fname contains a space then use STDIN */
          if (strchr(tok[2],' '))
            strcpy(tok[2],"STDIN");

          /* only take the last part of the filename */
          {
            string tmp;
            char *p = strrchr(tok[2],'/');
            if (p)
              {
                strcpy(tmp,p+1);
                strcpy(tok[2],tmp);
              }
          }


          buf->job = atoi(tok[1]);
          buf->status = LPQ_QUEUED;
          buf->time = time(NULL);
          StrnCpy(buf->user,tok[3],sizeof(buf->user)-1);
          StrnCpy(buf->file,tok[2],sizeof(buf->file)-1);
      }
      else
      {
          DEBUG(6,("parse_lpq_aix count=%d\n", count));
          return(False);
      }
  }
  else
  {
      /* the 4th and 9th columns must be integer */
      if (!isdigit(*tok[3]) || !isdigit(*tok[8])) return(False);
      buf->size = atoi(tok[8]) * 1024;
      /* if the fname contains a space then use STDIN */
      if (strchr(tok[4],' '))
        strcpy(tok[4],"STDIN");

      /* only take the last part of the filename */
      {
        string tmp;
        char *p = strrchr(tok[4],'/');
        if (p)
          {
            strcpy(tmp,p+1);
            strcpy(tok[4],tmp);
          }
      }


      buf->job = atoi(tok[3]);
      buf->status = strequal(tok[2],"RUNNING")?LPQ_PRINTING:LPQ_QUEUED;
      buf->time = time(NULL);
      StrnCpy(buf->user,tok[5],sizeof(buf->user)-1);
      StrnCpy(buf->file,tok[4],sizeof(buf->file)-1);
  }


  return(True);
}


/****************************************************************************
parse a lpq line
here is an example of lpq output under hpux; note there's no space after -o !
navier> lpstat -oljplus
ljplus-2153         user           priority 0  Jan 19 08:14
      util.c                                  125697 bytes
      server.c				      110712 bytes
ljplus-2154         user           priority 0  Jan 19 08:14
      (standard input)                          7551 bytes
****************************************************************************/
static BOOL parse_lpq_hpux(char * line, print_queue_struct *buf, BOOL first)
{
  /* must read two lines to process, therefore keep some values static */
  static BOOL header_line_ok=False;
  static string jobuser;
  static int jobid;
  static time_t jobtime;
  static int jobstat=LPSTAT_OK;
 
  int count;
  char TAB = '\011';  
  string tok[8];

  /* If a line begins with a horizontal TAB, it is a subline type */
  
  if (line[0] == TAB) { /* subline */
    if (!header_line_ok) return (False); /* incorrect header line */
    /* handle the case of "(standard input)" as a filename */
    string_sub(line,"standard input","STDIN");
    string_sub(line,"(","\"");
    string_sub(line,")","\"");
    
    for (count=0; count<2 && next_token(&line,tok[count],NULL); count++) ;
    /* we must get 2 tokens */
    if (count < 2) return(False);
    
    /* the 2nd column must be integer */
    if (!isdigit(*tok[1])) return(False);
    
    /* if the fname contains a space then use STDIN */
    if (strchr(tok[0],' '))
      strcpy(tok[0],"STDIN");
    
    buf->size = atoi(tok[1]);
    StrnCpy(buf->file,tok[0],sizeof(buf->file)-1);
    
    /* fill things from header line */
    buf->time = jobtime;
    buf->job = jobid;
    buf->status = jobstat;
    StrnCpy(buf->user,jobuser,sizeof(buf->user)-1);
    
    return(True);
  }
  else { /* header line */
    jobstat = ((first)&&(strstr(line,"on"))? LPQ_PRINTING:LPQ_QUEUED);
    header_line_ok=False; /* reset it */
    /* handle the dash in the job id */
    string_sub(line,"-"," ");
    
    for (count=0; count<8 && next_token(&line,tok[count],NULL); count++) ;
      
    /* we must get 8 tokens for time calculation, otherwise 3 will do */
    if (count < 3) return(False);
    
    /* first token must be printer name (cannot check ??) */
    /* the 2nd column must be integer */
    if (!isdigit(*tok[1])) return(False);
        
    /* process time */
    jobtime=EntryTime(tok, 5, count, 8);

    jobid = atoi(tok[1]);
    StrnCpy(jobuser,tok[2],sizeof(buf->user)-1);
    
    header_line_ok=True; /* information is correct */
    return(False); /* need subline info to include into queuelist */
  }
}


/****************************************************************************
parse a lpq line

here is an example of "lpstat -o dcslw" output under sysv

dcslw-896               tridge            4712   Dec 20 10:30:30 on dcslw

****************************************************************************/
static BOOL parse_lpq_sysv(char *line,print_queue_struct *buf,BOOL first)
{
  string tok[8];
  int count=0;
  char *p;

  /* handle the dash in the job id */
  string_sub(line,"-"," ");
  
  for (count=0; count<8 && next_token(&line,tok[count],NULL); count++) ;

  /* we must get 7 tokens */
  if (count < 7)
    return(False);

  /* the 2nd and 4th, 6th columns must be integer */
  if (!isdigit(*tok[1]) || !isdigit(*tok[3])) return(False);
  if (!isdigit(*tok[5])) return(False);

  /* if the user contains a ! then trim the first part of it */  
  if ((p=strchr(tok[2],'!')))
    {
      string tmp;
      strcpy(tmp,p+1);
      strcpy(tok[2],tmp);
    }
    

  buf->job = atoi(tok[1]);
  buf->size = atoi(tok[3]);
  buf->status = strequal(tok[7],"on")?LPQ_PRINTING:LPQ_QUEUED;
  buf->time = EntryTime(tok, 4, count, 7);
  StrnCpy(buf->user,tok[2],sizeof(buf->user)-1);
  StrnCpy(buf->file,tok[2],sizeof(buf->file)-1);
  return(True);
}


char *stat0_strings[] = { "enabled", "online", "idle", "no entries", "free", "ready", NULL };
char *stat1_strings[] = { "offline", "disabled", "down", "off", "waiting", "no daemon", NULL };
char *stat2_strings[] = { "jam", "paper", "error", "responding", "not accepting", "not running", "turned off", NULL };

/****************************************************************************
parse a lpq line. Choose printing style
****************************************************************************/
static BOOL parse_lpq_entry(int snum,char *line,
			    print_queue_struct *buf,
			    print_status_struct *status,BOOL first)
{
  BOOL ret;

  switch (lp_printing())
    {
    case PRINT_SYSV:
      ret = parse_lpq_sysv(line,buf,first);
      break;
    case PRINT_AIX:      
      ret = parse_lpq_aix(line,buf,first);
      break;
    case PRINT_HPUX:
      ret = parse_lpq_hpux(line,buf,first);
      break;
    default:
      ret = parse_lpq_bsd(line,buf,first);
      break;
    }

#ifdef LPQ_GUEST_TO_USER
  if (ret) {
    extern pstring sesssetup_user;
    /* change guest entries to the current logged in user to make
       them appear deletable to windows */
    if (sesssetup_user[0] && strequal(buf->user,lp_guestaccount(snum)))
      strcpy(buf->user,sesssetup_user);
  }
#endif

  if (status && !ret)
    {
      /* a few simple checks to see if the line might be a
         printer status line */
      int i;
      strlower(line);

      for (i=0; stat0_strings[i]; i++)
        if (strstr(line,stat0_strings[i])) {
	  StrnCpy(status->message,line,sizeof(status->message)-1);
	  status->status=LPSTAT_OK;
	  return(ret);
        }
      for (i=0; stat1_strings[i]; i++)
        if (strstr(line,stat1_strings[i])) {
	  StrnCpy(status->message,line,sizeof(status->message)-1);
	  status->status=LPSTAT_STOPPED;
	  return(ret);
        }
      for (i=0; stat2_strings[i]; i++)
        if (strstr(line,stat2_strings[i])) {
	  StrnCpy(status->message,line,sizeof(status->message)-1);
	  status->status=LPSTAT_ERROR;
	  return(ret);
        }
    }

  return(ret);
}

/****************************************************************************
get a printer queue
****************************************************************************/
int get_printqueue(int snum,int cnum,print_queue_struct **queue,
		   print_status_struct *status)
{
  char *lpq_command = lp_lpqcommand(snum);
  char *printername = PRINTERNAME(snum);
  int ret=0,count=0;
  pstring syscmd;
  pstring outfile;
  pstring line;
  FILE *f;
  struct stat sbuf;
  BOOL dorun=True;
  int cachetime = lp_lpqcachetime();
  int lfd = -1;

  *line = 0;

  if (!printername || !*printername)
    {
      DEBUG(6,("replacing printer name with service (snum=(%s,%d))\n",
	    lp_servicename(snum),snum));
      printername = lp_servicename(snum);
    }
    
  if (!lpq_command || !(*lpq_command))
    {
      DEBUG(5,("No lpq command\n"));
      return(0);
    }
    
  strcpy(syscmd,lpq_command);
  string_sub(syscmd,"%p",printername);

  standard_sub(cnum,syscmd);

  sprintf(outfile,"/tmp/lpq.%08x",str_checksum(syscmd));
  
  if (!lpq_cache_reset && cachetime && !stat(outfile,&sbuf)) 
    {
      if (time(NULL) - sbuf.st_mtime < cachetime) {
	DEBUG(3,("Using cached lpq output\n"));
	dorun = False;
      }

      if (dorun) {
	lfd = file_lock(outfile,LPQ_LOCK_TIMEOUT);
	if (lfd<0 || 
	    (!fstat(lfd,&sbuf) && (time(NULL) - sbuf.st_mtime)<cachetime)) {
	  DEBUG(3,("Using cached lpq output\n"));
	  dorun = False;
	  file_unlock(lfd); lfd = -1;
	}
      }
    }

  if (dorun) {
    ret = smbrun(syscmd,outfile);
    DEBUG(3,("Running the command `%s' gave %d\n",syscmd,ret));
  }

  lpq_cache_reset = False;

  f = fopen(outfile,"r");
  if (!f) {
    if (lfd >= 0) file_unlock(lfd);
    return(0);
  }

  if (status)
    strcpy(status->message,"");
      
  while (fgets(line,sizeof(pstring),f))
    {
      DEBUG(6,("QUEUE2: %s\n",line));

      *queue = Realloc(*queue,sizeof(print_queue_struct)*(count+1));
      if (! *queue)
	{
	  count = 0;
	  break;
	}
	  
      /* parse it */
      if (!parse_lpq_entry(snum,line,&(*queue)[count],status,count==0))
	continue;
	  
      count++;
    }	      

  fclose(f);

  if (lfd >= 0) file_unlock(lfd);

  if (!cachetime) 
    unlink(outfile);
  else
    chmod(outfile,0666);
  return(count);
}


/****************************************************************************
delete a printer queue entry
****************************************************************************/
void del_printqueue(int cnum,int snum,int jobid)
{
  char *lprm_command = lp_lprmcommand(snum);
  char *printername = PRINTERNAME(snum);
  pstring syscmd;
  char jobstr[20];
  int ret;

  if (!printername || !*printername)
    {
      DEBUG(6,("replacing printer name with service (snum=(%s,%d))\n",
	    lp_servicename(snum),snum));
      printername = lp_servicename(snum);
    }
    
  if (!lprm_command || !(*lprm_command))
    {
      DEBUG(5,("No lprm command\n"));
      return;
    }
    
  sprintf(jobstr,"%d",jobid);

  strcpy(syscmd,lprm_command);
  string_sub(syscmd,"%p",printername);
  string_sub(syscmd,"%j",jobstr);
  standard_sub(cnum,syscmd);

  ret = smbrun(syscmd,NULL);
  DEBUG(3,("Running the command `%s' gave %d\n",syscmd,ret));  
}



