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