1 /***************************************************************************
2  * LPRng - An Extended Print Spooler System
3  *
4  * Copyright 1988-2003, Patrick Powell, San Diego, CA
5  *     papowell@lprng.com
6  * See LICENSE for conditions of use.
7  *
8  ***************************************************************************/
9 
10  static char *const _id =
11 "$Id: accounting.c,v 1.74 2004/09/24 20:19:57 papowell Exp $";
12 
13 
14 #include "lp.h"
15 #include "accounting.h"
16 #include "getqueue.h"
17 #include "errorcodes.h"
18 #include "child.h"
19 #include "linksupport.h"
20 #include "fileopen.h"
21 /**** ENDINCLUDE ****/
22 
23 /*
24  Do_accounting is called with:
25    status = Do_accounting( 0, Accounting_start_DYN, job, Send_job_rw_timeout_DYN );
26   OR
27    status = Do_accounting( 0, Accounting_end_DYN, job, Send_job_rw_timeout_DYN );
28 
29  The general approach is:
30 
31   You are going to do accounting.  You either write accounting information to a
32   file,  or to a program.   If the 'achk' flag is set then you write it to
33   a program and then get back a 'proceed' indication.
34 
35   What you write is specified by the 'Accounting_start_DYN' (:as=) or
36   'Accounting_end_DYN' (:ae=) entries,  or the 'Accounting_file_DYN' (:af=)
37   entries.
38 
39   By default,  the :as or :ae entries are a simple string and are interpreted as
40   text to send.  If they are a path (starting with "/" or "|") then they are
41   a program to run.  In this case we run the program with no input,  expecting
42   that the necessary options will be passed on the command line.
43 
44   If the :as (or :ae) is a string,  then then :af is checked to see if it is a
45   filter  ("|/..."), network port  ("host@port"), or file.  If it is a filter,
46   then the program is run,  a network port then a connection is made to the remote
47   host, and if a file, the file is opened.  In all three cases the :as or
48   :ae string is written to the program, socket, or file respectively.
49 
50   If we are starting the job and the :achk flag is set and have
51   specified a filter (program) or host, then we now read the status
52   back from the filter or remote host.  The connection is then
53   closed.  If we are writing to a program then the exit status is
54   first used to determine the disposition:
55     JHOLD, JREMOVE, JABORT (or unknown) will cause the job to be
56       held, removed, or aborted respectively.
57     JSUCC will cause a line (or lines) to be read
58 
59   The first input line read from the remote program or host is used
60   to determine job disposal.  If the line is blank or starts with
61   ACCEPT then the job will be printed, HOLD will hold the job,  REMOVE
62   will remove the job, and ABORT or a non-recognizable response will
63   cause printing to be aborted.
64 
65 */
66 
Do_accounting(int end,char * command,struct job * job,int timeout)67 int Do_accounting( int end, char *command, struct job *job, int timeout )
68 {
69 	int n, err, len, tempfd;
70 	char msg[SMALLBUFFER];
71 	char *s, *t;
72 	struct line_list args;
73 	struct stat statb;
74 
75 
76 	Init_line_list(&args);
77 	msg[0] = 0;
78 	err = JSUCC;
79 
80 	while( isspace(cval(command)) ) ++command;
81 	s = command;
82 	if( cval(s) == '|' ) ++s;
83 	Add_line_list(&args, s,0,0,0);
84 	Fix_dollars(&args, job, 1, Filter_options_DYN );
85 	s = args.list[0];
86 	DEBUG1("Do_accounting: command '%s', af '%s', expanded '%s'",
87 		command, Accounting_file_DYN, s );
88 	s = safeextend2(s,"\n",__FILE__,__LINE__);
89 	args.list[0] = s;
90 
91 	tempfd = -1;
92 
93 	if( (cval(command) == '|') || (cval(command) == '/') ){
94 		if( end == 0 && Accounting_check_DYN ){
95 			tempfd = Make_temp_fd( 0 );
96 		}
97 		err = Filter_file( Send_query_rw_timeout_DYN,-1, tempfd, "ACCOUNTING_FILTER",
98 			command, Filter_options_DYN, job, 0, 1 );
99 		if( tempfd > 0 && lseek(tempfd,0,SEEK_SET) == -1 ){
100 			Errorcode = JABORT;
101 			logerr_die(LOG_INFO, "Do_accounting: lseek tempfile failed");
102 		}
103 	} else if( !ISNULL(Accounting_file_DYN) ){
104 		if( (cval(Accounting_file_DYN) == '|') ){
105 			int fd = Make_temp_fd(0);
106 			if( Write_fd_str( fd, args.list[0] ) < 0 ){
107 				Errorcode= JFAIL;
108 				logerr_die(LOG_INFO, "Do_accounting: write to tempfile of '%s' failed", command);
109 			}
110 			if( fd > 0 && lseek(fd,0,SEEK_SET) == -1 ){
111 				Errorcode= JFAIL;
112 				logerr_die(LOG_INFO, "Do_accounting: seek of tempfile failed" );
113 			}
114 			if( end == 0 && Accounting_check_DYN ){
115 				tempfd = Make_temp_fd( 0 );
116 			}
117 			err = Filter_file( Send_query_rw_timeout_DYN,fd, tempfd, "ACCOUNTING_FILTER",
118 				Accounting_file_DYN, Filter_options_DYN, job, 0, 1 );
119 			if( tempfd > 0 && lseek(tempfd,0,SEEK_SET) == -1 ){
120 				Errorcode= JFAIL;
121 				logerr_die(LOG_INFO, "Do_accounting: seek of tempfile failed" );
122 			}
123 			close(fd);
124 		} else if( isalnum(cval(Accounting_file_DYN))
125 			&& safestrchr( Accounting_file_DYN, '%' ) ){
126 			/* now try to open a connection to a server */
127 			char *host = Accounting_file_DYN;
128 
129 			DEBUG2("Do_accounting: connecting to '%s'",host);
130 			if( (tempfd = Link_open(host,timeout,0, 0, msg, sizeof(msg) )) < 0 ){
131 				err = errno;
132 				Errorcode= JFAIL;
133 				logerr_die(LOG_INFO,
134 					_("connection to accounting server '%s' failed '%s'"),
135 					Accounting_file_DYN, msg);
136 			}
137 			DEBUG2("Setup_accounting: socket %d", tempfd );
138 			if( Write_fd_str( tempfd, args.list[0] ) < 0 ){
139 				Errorcode= JFAIL;
140 				logerr_die(LOG_INFO, "Do_accounting: write to '%s' failed", command);
141 			}
142 			shutdown(tempfd,1);
143 		} else {
144 			tempfd = Checkwrite( Accounting_file_DYN, &statb, 0, Create_files_DYN, 0 );
145 			if( !end ){
146 				tempfd = Trim_status_file( tempfd, Accounting_file_DYN, Max_accounting_file_size_DYN,
147 					Min_accounting_file_size_DYN );
148 			}
149 			DEBUG2("Do_accounting: fd %d", tempfd );
150 			if( tempfd > 0 ){
151 				if( Write_fd_str( tempfd, args.list[0] ) < 0 ){
152 					err = errno;
153 					Errorcode= JFAIL;
154 					logerr_die(LOG_INFO, "Do_accounting: write to '%s' failed", command);
155 				}
156 				close(tempfd); tempfd = -1;
157 			}
158 		}
159 	}
160 
161 	if( tempfd > 0 && err == 0 && end == 0 && Accounting_check_DYN ){
162 		msg[0] = 0;
163 		len = 0;
164 		while( len < (int)(sizeof(msg)-1)
165 			&& (n = Read_fd_len_timeout(Send_query_rw_timeout_DYN,tempfd,msg+len,sizeof(msg)-1-len)) > 0 ){
166 			msg[len+n] = 0;
167 			DEBUG1("Do_accounting: read %d, '%s'", n, msg );
168 		}
169 		Free_line_list(&args);
170 		lowercase(msg);
171 		Split(&args,msg,Whitespace,0,0,0,0,0,0);
172 		err = JSUCC;
173 		if( args.count && (s = args.list[0]) ){
174 			if( (t = safestrchr(s,'\n')) ) *t = 0;
175 			setstatus(job, "accounting filter replied with '%s'", s);
176 			if( *s == 0 || !safestrncasecmp( s, "accept", 6 ) ){
177 				err = JSUCC;
178 			} else if( !safestrncasecmp( s, "hold", 4 ) ){
179 				err = JHOLD;
180 			} else if( !safestrncasecmp( s, "remove", 6 ) ){
181 				err = JREMOVE;
182 			} else if( !safestrncasecmp( s, "fail", 4 ) ){
183 				err = JFAIL;
184 			} else {
185 				plp_snprintf( msg, sizeof(msg),
186 					"accounting check failed - status message '%s'", s );
187 				err = JABORT;
188 			}
189 		}
190 	}
191 	if( tempfd > 0 ) close(tempfd); tempfd = -1;
192 	Free_line_list(&args);
193 	DEBUG2("Do_accounting: status %s", Server_status(err) );
194 	return( err );
195 }
196