1 /*-
2  * Copyright (c) 2005-2006 The FreeBSD Project
3  * All rights reserved.
4  *
5  * Author: Victor Cruceru <soc-victor@freebsd.org>
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 *
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
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 *
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
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
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
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
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
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
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
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