Handle hostnames with upper-case letters
[webmin.git] / lpadmin / lprng-lib.pl
1 # lprng-lib.pl
2 # Functions for lprng-style printer management
3
4 # list_printers()
5 # Returns a list of known printer names
6 sub list_printers
7 {
8 local($l, @rv);
9 foreach $l (&list_printcap()) {
10         $l->{'name'} =~ /^([^\|]+)/;
11         push(@rv, $1);
12         }
13 return @rv;
14 }
15
16 # get_printer(name, [nostatus])
17 sub get_printer
18 {
19 local($l, %prn, @w, @n, $w, %cap, @jobs);
20 foreach $l (&list_printcap()) {
21         @n = split(/\|/, $l->{'name'});
22         if ($n[0] eq $_[0]) {
23                 # found the printer.. get info from printcap
24                 $prn{'name'} = $n[0];
25                 if (@n > 1) { $prn{'alias'} =
26                                 [ grep { $_ ne $l->{'cm'} } @n[1..$#n] ]; }
27                 $prn{'desc'} = $l->{'cm'} if ($l->{'cm'});
28                 $prn{'iface'} = $l->{'if'};
29                 $prn{'banner'} = !defined($l->{'sh'});
30                 if ($l->{'rm'}) {
31                         $prn{'rhost'} = $l->{'rm'};
32                         $prn{'rqueue'} = $l->{'rp'};
33                         }
34                 elsif ($l->{'lp'} =~ /^(\S+)\%(\d+)$/) {
35                         $prn{'dhost'} = $1;
36                         $prn{'dport'} = $2;
37                         }
38                 else {
39                         $prn{'dev'} = $l->{'lp'};
40                         }
41                 $prn{'msize'} = $l->{'mx'};
42                 $prn{'comment'} = $l->{'comment'};
43                 $prn{'ro'} = $l->{'file'} eq $config{'ro_printcap_file'} &&
44                      $config{'printcap_file'} ne $config{'ro_printcap_file'};
45
46                 if (!$_[1]) {
47                         # call lpc to get status
48                         local $esc = quotemeta($prn{'name'});
49                         $out = &backquote_command("lpc -P$esc status 2>&1", 1);
50                         if ($out =~ /\n(\S+)\s+(\S+)\s+(\S+)/) {
51                                 $prn{'accepting'} = $3 eq 'enabled';
52                                 $prn{'enabled'} = $2 eq 'enabled';
53                                 }
54                         }
55
56                 return \%prn;
57                 }
58         }
59 return undef;
60 }
61
62 # get_jobs(printer)
63 sub get_jobs
64 {
65 local @jobs;
66 local $esc = quotemeta($_[0]);
67 open(LPQ, "lpq -P$esc |");
68 while(<LPQ>) {
69         chop;
70         if (/^\s+Rank\s+Owner/i) { $doneheader++; }
71         elsif ($doneheader && /^(\S+)\s+(\S+)\s+\S+\s+(\d+)\s+(.*\S)\s+(\d+)\s+(\S+)$/) {
72                 # Local print queue
73                 next if ($1 eq "done");
74                 local(%job, $f, @pq);
75                 $job{'id'} = $3;
76                 $job{'user'} = $2;
77                 $job{'size'} = $5;
78                 $job{'file'} = $4;
79                 $job{'printing'} = ($1 eq "active");
80                 $job{'user'} =~ s/\+\d+$//g;
81                 local $d = "$config{'spool_dir'}/$_[0]";
82                 opendir(DIR, $d);
83                 while($f = readdir(DIR)) {
84                         if ($f =~ /df.(\d+)/ && $1 == $job{'id'}) {
85                                 push(@pq, "$d/$f");
86                                 }
87                         }
88                 closedir(DIR);
89                 $job{'printfile'} = @pq ? \@pq : undef;
90                 push(@jobs, \%job);
91                 }
92         elsif (/^(\S+):\s+(\S+)\s+\[job\s+(\d+)/) {
93                 # remote print queue
94                 local(%job);
95                 $job{'id'} = $3;
96                 $job{'user'} = $1;
97                 if (<LPQ> =~ /^\s+(.*\S)\s+(\d+)/) {
98                         $job{'file'} = $1;
99                         $job{'size'} = $2;
100                         push(@jobs, \%job);
101                         }
102                 }
103         }
104 close(LPQ);
105 return @jobs;
106 }
107
108 sub printer_support
109 {
110 return $_[0] !~ /^(why|allow|default|ctype|sysv|ipp)$/;
111 }
112
113
114 # create_printer(&details)
115 # Create a new printer in /etc/printcap
116 sub create_printer
117 {
118 local(%cap);
119 if (!-d $config{'spool_dir'}) {
120         &lock_file($config{'spool_dir'});
121         mkdir($config{'spool_dir'}, 0755);
122         &unlock_file($config{'spool_dir'});
123         &system_logged("chown $config{'iface_owner'} $config{'spool_dir'}");
124         }
125 $cap{'sd'} = "$config{'spool_dir'}/$_[0]->{'name'}";
126 &lock_file($cap{'sd'});
127 mkdir($cap{'sd'}, 0755);
128 &unlock_file($cap{'sd'});
129 &open_lock_tempfile(TOUCH, ">$cap{'sd'}/control.$_[0]->{'name'}");
130 &close_tempfile(TOUCH);
131 &open_lock_tempfile(TOUCH, ">$cap{'sd'}/status.$_[0]->{'name'}");
132 &close_tempfile(TOUCH);
133 &open_lock_tempfile(TOUCH, ">$cap{'sd'}/unspooler.$_[0]->{'name'}");
134 &close_tempfile(TOUCH);
135 if ($config{'iface_owner'}) {
136         &system_logged("chown -R $config{'iface_owner'} $cap{'sd'}");
137         }
138 else {
139         local @st = stat($config{'spool_dir'});
140         &system_logged("chown -R $st[4]:$st[5] $cap{'sd'}");
141         }
142 &system_logged("chmod 2700 $cap{'sd'}");
143 &system_logged("chmod 600 $cap{'sd'}/*");
144 &system_logged("chmod 700 $cap{'sd'}/printfilter")
145         if (-r "$cap{'sd'}/printfilter");
146
147 &lock_file($config{'printcap_file'});
148 local $lref = &read_file_lines($config{'printcap_file'});
149 push(@$lref, &make_printcap($_[0], \%cap));
150 &flush_file_lines($config{'printcap_file'});
151 &unlock_file($config{'printcap_file'});
152
153 &system_logged("lpc reread all >/dev/null 2>&1");
154 &apply_status($_[0]);
155 }
156
157 # modify_printer(&details)
158 sub modify_printer
159 {
160 local(@old, $o, $old, @cap);
161 &lock_file($config{'printcap_file'});
162 @old = &list_printcap();
163 foreach $o (@old) {
164         $o->{'name'} =~ /^([^\|]+)/;
165         if ($1 eq $_[0]->{'name'}) {
166                 # found current details
167                 $old = $o;
168                 last;
169                 }
170         }
171 if (!$old) { &error("Printer '$_[0]->{'name'}' no longer exists"); }
172
173 &lock_file($config{'printcap_file'});
174 local $lref = &read_file_lines($config{'printcap_file'});
175 splice(@$lref, $old->{'line'}, $old->{'eline'} - $old->{'line'} + 1,
176        &make_printcap($_[0], $old));
177 &flush_file_lines($config{'printcap_file'});
178 &unlock_file($config{'printcap_file'});
179
180 &system_logged("lpc reread all >/dev/null 2>&1");
181 &apply_status($_[0]);
182 }
183
184 # delete_printer(name)
185 sub delete_printer
186 {
187 local(@old, $o, $old, @cap);
188 &lock_file($config{'printcap_file'});
189 @old = &list_printcap();
190 foreach $o (@old) {
191         $o->{'name'} =~ /^([^\|]+)/;
192         if ($1 eq $_[0]) {
193                 # found current details
194                 $old = $o;
195                 last;
196                 }
197         }
198 if (!$old) { &error("Printer '$_[0]' no longer exists"); }
199
200 &lock_file($config{'printcap_file'});
201 local $lref = &read_file_lines($config{'printcap_file'});
202 splice(@$lref, $old->{'line'}, $old->{'eline'} - $old->{'line'} + 1);
203 &flush_file_lines($config{'printcap_file'});
204 &unlock_file($config{'printcap_file'});
205
206 if ($old->{'sd'} =~ /$_[0]$/) {
207         &system_logged("rm -rf '$old->{'sd'}'");
208         }
209 &system_logged("lpc reread all >/dev/null 2>&1");
210 }
211
212 # cancel_job(printer, job)
213 # Calls lprm to remove some job
214 sub cancel_job
215 {
216 local($out);
217 local $esc = quotemeta($_[0]);
218 local $iesc = quotemeta($_[1]);
219 $out = &backquote_logged("lprm -P$esc $iesc 2>&1");
220 if ($?) { &error("lprm failed : $out"); }
221 }
222
223 # make_printcap(&details, &old)
224 # Updates or creates a printcap line
225 sub make_printcap
226 {
227 local(%prn, %cap, $a, $rv, $c);
228 %prn = %{$_[0]}; %cap = %{$_[1]};
229 $cap{'if'} = $prn{'iface'} ? $prn{'iface'} : undef;
230 $cap{'sh'} = $prn{'banner'} ? undef : "";
231 $cap{'lp'} = $prn{'dhost'} ? "$prn{'dhost'}%$prn{'dport'}" :
232              $prn{'dev'} ? $prn{'dev'} : undef;
233 $cap{'rm'} = $prn{'rhost'} ? $prn{'rhost'} : undef;
234 $cap{'rp'} = $prn{'rqueue'} ? $prn{'rqueue'} : undef;
235 $cap{'mx'} = defined($prn{'msize'}) ? $prn{'msize'} : undef;
236 $cap{'cm'} = $prn{'desc'} ? $prn{'desc'} : undef;
237 $rv = $prn{'comment'}."\n" if ($prn{'comment'});
238 $rv .= $prn{'name'};
239 foreach $a (@{$prn{'alias'}}) { $rv .= "|$a"; }
240 $rv .= "|$prn{'desc'}" if ($prn{'desc'});
241 foreach $c (keys %cap) {
242         if ($c =~ /^([A-z0-9\-\_]+)(#?)$/ && defined($cap{$c}) &&
243             $1 ne 'eline' && $1 ne 'line' && $1 ne 'name' && $1 ne 'comment' &&
244             $1 ne 'file') {
245                 if ($cap{$c} eq "") { $rv .= ":$c"; }
246                 elsif ($cap{$c} =~ /^\d+$/) { $rv .= ":$c#$cap{$c}"; }
247                 else { $rv .= ":$c=$cap{$c}"; }
248                 }
249         }
250 $rv .= ":";
251 return $rv;
252 }
253
254 # list_printcap()
255 # Returns an array of associative arrays containing printcap fields
256 sub list_printcap
257 {
258 return @list_printcap_cache if (scalar(@list_printcap_cache));
259 local(@rv, @line, @comment, $line, $cont, $lnum, $i, %done, $capfile);
260 foreach $capfile ($config{'printcap_file'}, $config{'ro_printcap_file'}) {
261         next if (!$capfile || $done{$capfile}++);
262         open(CAP, $capfile);
263         $lnum = 0;
264         while($line = <CAP>) {
265                 $line =~ s/\s+$//g;     # remove trailing spaces
266                 if ($line =~ /^(##.*)/) {
267                         # special commented line .. keep it
268                         $comment[@line] = $line;
269                         }
270                 else {
271                         local $oline = $line;
272                         $line =~ s/^#.*$//g;    # remove comments
273                         $line =~ s/^\s+//g;     # remove leading spaces
274                         if ($line =~ /\S/) {
275                                 $ncont = ($line =~ s/\\$//g);
276                                 if ($cont || $oline =~ /^\s+:/) {
277                                         $line[$#line] .= $line;
278                                         $eline[@line - 1] = $lnum;
279                                         }
280                                 else {
281                                         push(@line, $line);
282                                         $eline[@line - 1] =
283                                                 $sline[@line - 1] = $lnum;
284                                         }
285                                 $cont = $ncont;
286                                 }
287                         else {
288                                 # only keep comments immediately before an entry
289                                 $comment[@line] = undef;
290                                 }
291                         }
292                 $lnum++;
293                 }
294         close(CAP);
295         for($i=0; $i<@line; $i++) {
296                 local(%cap);
297                 next if ($line[$i] =~ /^include\s+(\S+)/);
298                 @w = split(/:+/, $line[$i]);
299                 $cap{'name'} = $w[0];
300                 next if ($done{$w[0]}++);
301                 $cap{'line'} = $sline[$i] - ($comment[$i] ? 1 : 0);
302                 $cap{'eline'} = $eline[$i];
303                 $cap{'comment'} = $comment[$i];
304                 $cap{'file'} = $capfile;
305                 foreach $w (@w[1..$#w]) {
306                         if ($w =~ /^([A-z0-9\-\_]+)[=#](.*)$/) { $cap{$1} = $2;}
307                         elsif ($w =~ /^([A-z0-9\-\_]+)$/) { $cap{$w} = ""; }
308                         }
309                 push(@rv, \%cap);
310                 }
311         }
312 @list_printcap_cache = @rv;
313 return @rv;
314 }
315
316 # apply_status(&details)
317 # Calls lpc to enable or disable a printer.
318 # Restarting lpd doesn't seem to be necessary?
319 sub apply_status
320 {
321 local $out;
322 local $esc = quotemeta($prn{'name'});
323 if ($prn{'enabled'}) {
324         $out = &backquote_logged("lpc -P$esc start 2>&1");
325         }
326 else {
327         $out = &backquote_logged("lpc -P$esc stop 2>&1");
328         }
329 if ($prn{'accepting'}) {
330         $out = &backquote_logged("lpc -P$esc enable 2>&1");
331         }
332 else {
333         $out = &backquote_logged("lpc -P$esc disable 2>&1");
334         }
335 }
336
337 # sched_running()
338 # Returns the pid if lpsched is running, 0 if not, -1 if cannot be stopped
339 sub sched_running
340 {
341 local $out = &backquote_command("lpc lpd all 2>&1", 1);
342 return $out =~ /pid\s+(\d+)/ ? $1 : 0;
343 }
344
345 # start_sched()
346 # Start lpsched
347 sub start_sched
348 {
349 local $out = &backquote_logged("lpd 2>&1");
350 if ($? || $out =~ /error/i) { &error("<tt>$out</tt>"); }
351 }
352
353 # stop_sched(pid)
354 # Stop the running lpsched process
355 sub stop_sched
356 {
357 &kill_logged('INT', $_[0]) || &error(&text("estop", $!));
358 }
359
360 # print_command(printer, file)
361 # Returns the command to print some file on some printer
362 sub print_command
363 {
364 local $esc = quotemeta($_[0]);
365 local $fesc = quotemeta($_[1]);
366 return "lpr -P$esc $fesc";
367 }
368
369 # check_print_system()
370 sub check_print_system
371 {
372 &has_command("lpr") || return &text('lprng_ecmd', "<tt>lpr</tt>");
373 -d $config{'spool_dir'} || return &text('lprng_espool', "<tt>$config{'spool_dir'}</tt>");
374 return undef;
375 }
376
377 if (-r "/dev/lp0") {
378         @device_files = ("/dev/lp0", "/dev/lp1", "/dev/lp2", "/dev/lp3",
379                          "/dev/ttyS0", "/dev/ttyS1", "/dev/null");
380         }
381 else {
382         @device_files = ("/dev/lp1", "/dev/lp2", "/dev/lp2", "/dev/lp3",
383                          "/dev/ttyS0", "/dev/ttyS1", "/dev/null");
384         }
385 if (-r "/dev/usblp0") {
386         push(@device_files, "/dev/usblp0", "/dev/usblp1",
387                             "/dev/usblp2", "/dev/usblp3");
388         }
389 elsif (-r "/dev/usb/lp0") {
390         push(@device_files, "/dev/usb/lp0", "/dev/usb/lp1",
391                             "/dev/usb/lp2", "/dev/usb/lp3");
392         }
393 @device_names = (&text('lprng_paralel', "1"), &text('lprng_paralel', "2"),
394                  &text('lprng_paralel', "3"), &text('lprng_paralel', "4"),
395                  &text('lprng_serial', "1"), &text('lprng_serial', "2"),
396                  $text{'lprng_null'}, &text('linux_usb', 1),
397                  &text('linux_usb', 2), &text('linux_usb', 3),
398                  &text('linux_usb', 4));
399
400