1 /*-
2 * Copyright (c) 2005-2006 The FreeBSD Project
3 * All rights reserved.
4 *
5 * Author: Victor Cruceru <[email protected]>
6 *
7 * Redistribution of this software and documentation and use in source and
8 * binary forms, with or without modification, are permitted provided that
9 * the following conditions are met:
10 *
11 * 1. Redistributions of source code or documentation must retain the above
12 * copyright notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 *
29 * $FreeBSD$
30 */
31
32 /*
33 * Host Resources MIB implementation for SNMPd: instrumentation for
34 * hrPrinterTable
35 */
36
37 #include <sys/param.h>
38 #include <sys/stat.h>
39
40 #include <assert.h>
41 #include <err.h>
42 #include <errno.h>
43 #include <paths.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <syslog.h>
47 #include <unistd.h>
48
49 #include "hostres_snmp.h"
50 #include "hostres_oid.h"
51 #include "hostres_tree.h"
52
53 #include <sys/dirent.h>
54 #include "lp.h"
55
56 /* Constants */
57 static const struct asn_oid OIDX_hrDevicePrinter_c = OIDX_hrDevicePrinter;
58
59 enum PrinterStatus {
60 PS_OTHER = 1,
61 PS_UNKNOWN = 2,
62 PS_IDLE = 3,
63 PS_PRINTING = 4,
64 PS_WARMUP = 5
65 };
66
67 /*
68 * This structure is used to hold a SNMP table entry
69 * for HOST-RESOURCES-MIB's hrPrinterTable.
70 */
71 struct printer_entry {
72 int32_t index;
73 int32_t status; /* values from PrinterStatus enum above */
74 u_char detectedErrorState[2];
75 TAILQ_ENTRY(printer_entry) link;
76 #define HR_PRINTER_FOUND 0x001
77 uint32_t flags;
78
79 };
80 TAILQ_HEAD(printer_tbl, printer_entry);
81
82 /* the hrPrinterTable */
83 static struct printer_tbl printer_tbl = TAILQ_HEAD_INITIALIZER(printer_tbl);
84
85 /* last (agent) tick when hrPrinterTable was updated */
86 static uint64_t printer_tick;
87
88 /**
89 * Create entry into the printer table.
90 */
91 static struct printer_entry *
printer_entry_create(const struct device_entry * devEntry)92 printer_entry_create(const struct device_entry *devEntry)
93 {
94 struct printer_entry *entry = NULL;
95
96 assert(devEntry != NULL);
97 if (devEntry == NULL)
98 return (NULL);
99
100 if ((entry = malloc(sizeof(*entry))) == NULL) {
101 syslog(LOG_WARNING, "hrPrinterTable: %s: %m", __func__);
102 return (NULL);
103 }
104 memset(entry, 0, sizeof(*entry));
105 entry->index = devEntry->index;
106 INSERT_OBJECT_INT(entry, &printer_tbl);
107 return (entry);
108 }
109
110 /**
111 * Delete entry from the printer table.
112 */
113 static void
printer_entry_delete(struct printer_entry * entry)114 printer_entry_delete(struct printer_entry *entry)
115 {
116
117 assert(entry != NULL);
118 if (entry == NULL)
119 return;
120
121 TAILQ_REMOVE(&printer_tbl, entry, link);
122 free(entry);
123 }
124
125 /**
126 * Find a printer by its index
127 */
128 static struct printer_entry *
printer_find_by_index(int32_t idx)129 printer_find_by_index(int32_t idx)
130 {
131 struct printer_entry *entry;
132
133 TAILQ_FOREACH(entry, &printer_tbl, link)
134 if (entry->index == idx)
135 return (entry);
136
137 return (NULL);
138 }
139
140 /**
141 * Get the status of a printer
142 */
143 static enum PrinterStatus
get_printer_status(const struct printer * pp)144 get_printer_status(const struct printer *pp)
145 {
146 char statfile[MAXPATHLEN];
147 char lockfile[MAXPATHLEN];
148 char fline[128];
149 int fd;
150 FILE *f = NULL;
151 enum PrinterStatus ps = PS_UNKNOWN;
152
153 if (pp->lock_file[0] == '/')
154 strlcpy(lockfile, pp->lock_file, sizeof(lockfile));
155 else
156 snprintf(lockfile, sizeof(lockfile), "%s/%s",
157 pp->spool_dir, pp->lock_file);
158
159 fd = open(lockfile, O_RDONLY);
160 if (fd < 0 || flock(fd, LOCK_SH | LOCK_NB) == 0) {
161 ps = PS_IDLE;
162 goto LABEL_DONE;
163 }
164
165 if (pp->status_file[0] == '/')
166 strlcpy(statfile, pp->status_file, sizeof(statfile));
167 else
168 snprintf(statfile, sizeof(statfile), "%s/%s",
169 pp->spool_dir, pp->status_file);
170
171 f = fopen(statfile, "r");
172 if (f == NULL) {
173 syslog(LOG_ERR, "cannot open status file: %s", strerror(errno));
174 ps = PS_UNKNOWN;
175 goto LABEL_DONE;
176 }
177
178 memset(&fline[0], '\0', sizeof(fline));
179 if (fgets(fline, sizeof(fline) -1, f) == NULL) {
180 ps = PS_UNKNOWN;
181 goto LABEL_DONE;
182 }
183
184 if (strstr(fline, "is ready and printing") != NULL) {
185 ps = PS_PRINTING;
186 goto LABEL_DONE;
187 }
188
189 if (strstr(fline, "to become ready (offline?)") != NULL) {
190 ps = PS_OTHER;
191 goto LABEL_DONE;
192 }
193
194 LABEL_DONE:
195 if (fd >= 0)
196 (void)close(fd); /* unlocks as well */
197
198 if (f != NULL)
199 (void)fclose(f);
200
201 return (ps);
202 }
203
204 /**
205 * Called for each printer found in /etc/printcap.
206 */
207 static void
handle_printer(struct printer * pp)208 handle_printer(struct printer *pp)
209 {
210 struct device_entry *dev_entry;
211 struct printer_entry *printer_entry;
212 char dev_only[128];
213 struct stat sb;
214
215 if (pp->remote_host != NULL) {
216 HRDBG("skipped %s -- remote", pp->printer);
217 return;
218 }
219
220 if (strncmp(pp->lp, _PATH_DEV, strlen(_PATH_DEV)) != 0) {
221 HRDBG("skipped %s [device %s] -- remote", pp->printer, pp->lp);
222 return;
223 }
224
225 memset(dev_only, '\0', sizeof(dev_only));
226 snprintf(dev_only, sizeof(dev_only), "%s", pp->lp + strlen(_PATH_DEV));
227
228 HRDBG("printer %s has device %s", pp->printer, dev_only);
229
230 if (stat(pp->lp, &sb) < 0) {
231 if (errno == ENOENT) {
232 HRDBG("skipped %s -- device %s missing",
233 pp->printer, pp->lp);
234 return;
235 }
236 }
237
238 if ((dev_entry = device_find_by_name(dev_only)) == NULL) {
239 HRDBG("%s not in hrDeviceTable", pp->lp);
240 return;
241 }
242 HRDBG("%s found in hrDeviceTable", pp->lp);
243 dev_entry->type = &OIDX_hrDevicePrinter_c;
244
245 dev_entry->flags |= HR_DEVICE_IMMUTABLE;
246
247 /* Then check hrPrinterTable for this device */
248 if ((printer_entry = printer_find_by_index(dev_entry->index)) == NULL &&
249 (printer_entry = printer_entry_create(dev_entry)) == NULL)
250 return;
251
252 printer_entry->flags |= HR_PRINTER_FOUND;
253 printer_entry->status = get_printer_status(pp);
254 memset(printer_entry->detectedErrorState, 0,
255 sizeof(printer_entry->detectedErrorState));
256 }
257
258 static void
hrPrinter_get_OS_entries(void)259 hrPrinter_get_OS_entries(void)
260 {
261 int status, more;
262 struct printer myprinter, *pp = &myprinter;
263
264 init_printer(pp);
265 HRDBG("---->Getting printers .....");
266 more = firstprinter(pp, &status);
267 if (status)
268 goto errloop;
269
270 while (more) {
271 do {
272 HRDBG("---->Got printer %s", pp->printer);
273
274 handle_printer(pp);
275 more = nextprinter(pp, &status);
276 errloop:
277 if (status)
278 syslog(LOG_WARNING,
279 "hrPrinterTable: printcap entry for %s "
280 "has errors, skipping",
281 pp->printer ? pp->printer : "<noname?>");
282 } while (more && status);
283 }
284
285 lastprinter();
286 printer_tick = this_tick;
287 }
288
289 /**
290 * Init the things for hrPrinterTable
291 */
292 void
init_printer_tbl(void)293 init_printer_tbl(void)
294 {
295
296 hrPrinter_get_OS_entries();
297 }
298
299 /**
300 * Finalization routine for hrPrinterTable
301 * It destroys the lists and frees any allocated heap memory
302 */
303 void
fini_printer_tbl(void)304 fini_printer_tbl(void)
305 {
306 struct printer_entry *n1;
307
308 while ((n1 = TAILQ_FIRST(&printer_tbl)) != NULL) {
309 TAILQ_REMOVE(&printer_tbl, n1, link);
310 free(n1);
311 }
312 }
313
314 /**
315 * Refresh the printer table if needed.
316 */
317 void
refresh_printer_tbl(void)318 refresh_printer_tbl(void)
319 {
320 struct printer_entry *entry;
321 struct printer_entry *entry_tmp;
322
323 if (this_tick <= printer_tick) {
324 HRDBG("no refresh needed");
325 return;
326 }
327
328 /* mark each entry as missing */
329 TAILQ_FOREACH(entry, &printer_tbl, link)
330 entry->flags &= ~HR_PRINTER_FOUND;
331
332 hrPrinter_get_OS_entries();
333
334 /*
335 * Purge items that disappeared
336 */
337 entry = TAILQ_FIRST(&printer_tbl);
338 while (entry != NULL) {
339 entry_tmp = TAILQ_NEXT(entry, link);
340 if (!(entry->flags & HR_PRINTER_FOUND))
341 printer_entry_delete(entry);
342 entry = entry_tmp;
343 }
344
345 printer_tick = this_tick;
346
347 HRDBG("refresh DONE ");
348 }
349
350 int
op_hrPrinterTable(struct snmp_context * ctx __unused,struct snmp_value * value,u_int sub,u_int iidx __unused,enum snmp_op curr_op)351 op_hrPrinterTable(struct snmp_context *ctx __unused, struct snmp_value *value,
352 u_int sub, u_int iidx __unused, enum snmp_op curr_op)
353 {
354 struct printer_entry *entry;
355
356 refresh_printer_tbl();
357
358 switch (curr_op) {
359
360 case SNMP_OP_GETNEXT:
361 if ((entry = NEXT_OBJECT_INT(&printer_tbl, &value->var,
362 sub)) == NULL)
363 return (SNMP_ERR_NOSUCHNAME);
364 value->var.len = sub + 1;
365 value->var.subs[sub] = entry->index;
366 goto get;
367
368 case SNMP_OP_GET:
369 if ((entry = FIND_OBJECT_INT(&printer_tbl, &value->var,
370 sub)) == NULL)
371 return (SNMP_ERR_NOSUCHNAME);
372 goto get;
373
374 case SNMP_OP_SET:
375 if ((entry = FIND_OBJECT_INT(&printer_tbl, &value->var,
376 sub)) == NULL)
377 return (SNMP_ERR_NO_CREATION);
378 return (SNMP_ERR_NOT_WRITEABLE);
379
380 case SNMP_OP_ROLLBACK:
381 case SNMP_OP_COMMIT:
382 abort();
383 }
384 abort();
385
386 get:
387 switch (value->var.subs[sub - 1]) {
388
389 case LEAF_hrPrinterStatus:
390 value->v.integer = entry->status;
391 return (SNMP_ERR_NOERROR);
392
393 case LEAF_hrPrinterDetectedErrorState:
394 return (string_get(value, entry->detectedErrorState,
395 sizeof(entry->detectedErrorState)));
396 }
397 abort();
398 }
399