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