1 /*****************************************************************************
2 *
3 * Nagios check_apt plugin
4 *
5 * License: GPL
6 * Copyright (c) 2006-2008 Nagios Plugins Development Team
7 *
8 * Original author: Sean Finney
9 *
10 * Description:
11 *
12 * This file contains the check_apt plugin
13 *
14 * Check for available updates in apt package management systems
15 *
16 *
17 * This program is free software: you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation, either version 3 of the License, or
20 * (at your option) any later version.
21 *
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * You should have received a copy of the GNU General Public License
28 * along with this program. If not, see <http://www.gnu.org/licenses/>.
29 *
30 *****************************************************************************/
32 const char *progname = "check_apt";
33 const char *copyright = "2006-2008";
34 const char *email = "nagiosplug-devel@lists.sourceforge.net";
36 #include "common.h"
37 #include "runcmd.h"
38 #include "utils.h"
39 #include "regex.h"
41 /* some constants */
42 typedef enum { UPGRADE, DIST_UPGRADE, NO_UPGRADE } upgrade_type;
44 /* the default opts can be overridden via the cmdline */
45 #define UPGRADE_DEFAULT_OPTS "-o 'Debug::NoLocking=true' -s -qq"
46 #define UPDATE_DEFAULT_OPTS "-q"
47 /* until i commit the configure.in patch which gets this, i'll define
48 * it here as well */
49 #ifndef PATH_TO_APTGET
50 # define PATH_TO_APTGET "/usr/bin/apt-get"
51 #endif /* PATH_TO_APTGET */
52 /* the RE that catches security updates */
53 #define SECURITY_RE "^[^\\(]*\\([^ ]* (Debian-Security:|Ubuntu:[^/]*/[^-]*-security)"
55 /* some standard functions */
56 int process_arguments(int, char **);
57 void print_help(void);
58 void print_usage(void);
60 /* construct the appropriate apt-get cmdline */
61 char* construct_cmdline(upgrade_type u, const char *opts);
62 /* run an apt-get update */
63 int run_update(void);
64 /* run an apt-get upgrade */
65 int run_upgrade(int *pkgcount, int *secpkgcount);
66 /* add another clause to a regexp */
67 char* add_to_regexp(char *expr, const char *next);
69 /* configuration variables */
70 static int verbose = 0; /* -v */
71 static int do_update = 0; /* whether to call apt-get update */
72 static upgrade_type upgrade = UPGRADE; /* which type of upgrade to do */
73 static char *upgrade_opts = NULL; /* options to override defaults for upgrade */
74 static char *update_opts = NULL; /* options to override defaults for update */
75 static char *do_include = NULL; /* regexp to only include certain packages */
76 static char *do_exclude = NULL; /* regexp to only exclude certain packages */
77 static char *do_critical = NULL; /* regexp specifying critical packages */
79 /* other global variables */
80 static int stderr_warning = 0; /* if a cmd issued output on stderr */
81 static int exec_warning = 0; /* if a cmd exited non-zero */
83 int main (int argc, char **argv) {
84 int result=STATE_UNKNOWN, packages_available=0, sec_count=0;
86 /* Parse extra opts if any */
87 argv=np_extra_opts(&argc, argv, progname);
89 if (process_arguments(argc, argv) == ERROR)
90 usage_va(_("Could not parse arguments"));
92 /* Set signal handling and alarm timeout */
93 if (signal (SIGALRM, timeout_alarm_handler) == SIG_ERR) {
94 usage_va(_("Cannot catch SIGALRM"));
95 }
97 /* handle timeouts gracefully... */
98 alarm (timeout_interval);
100 /* if they want to run apt-get update first... */
101 if(do_update) result = run_update();
103 /* apt-get upgrade */
104 result = max_state(result, run_upgrade(&packages_available, &sec_count));
106 if(sec_count > 0){
107 result = max_state(result, STATE_CRITICAL);
108 } else if(packages_available > 0){
109 result = max_state(result, STATE_WARNING);
110 } else {
111 result = max_state(result, STATE_OK);
112 }
114 printf(_("APT %s: %d packages available for %s (%d critical updates). %s%s%s%s\n"),
115 state_text(result),
116 packages_available,
117 (upgrade==DIST_UPGRADE)?"dist-upgrade":"upgrade",
118 sec_count,
119 (stderr_warning)?" warnings detected":"",
120 (stderr_warning && exec_warning)?",":"",
121 (exec_warning)?" errors detected":"",
122 (stderr_warning||exec_warning)?". run with -v for information.":""
123 );
125 return result;
126 }
128 /* process command-line arguments */
129 int process_arguments (int argc, char **argv) {
130 int c;
132 static struct option longopts[] = {
133 {"version", no_argument, 0, 'V'},
134 {"help", no_argument, 0, 'h'},
135 {"verbose", no_argument, 0, 'v'},
136 {"timeout", required_argument, 0, 't'},
137 {"update", optional_argument, 0, 'u'},
138 {"upgrade", optional_argument, 0, 'U'},
139 {"no-upgrade", no_argument, 0, 'n'},
140 {"dist-upgrade", optional_argument, 0, 'd'},
141 {"include", required_argument, 0, 'i'},
142 {"exclude", required_argument, 0, 'e'},
143 {"critical", required_argument, 0, 'c'},
144 {0, 0, 0, 0}
145 };
147 while(1) {
148 c = getopt_long(argc, argv, "hVvt:u::U::d::ni:e:c:", longopts, NULL);
150 if(c == -1 || c == EOF || c == 1) break;
152 switch(c) {
153 case 'h':
154 print_help();
155 exit(STATE_OK);
156 case 'V':
157 print_revision(progname, NP_VERSION);
158 exit(STATE_OK);
159 case 'v':
160 verbose++;
161 break;
162 case 't':
163 timeout_interval=atoi(optarg);
164 break;
165 case 'd':
166 upgrade=DIST_UPGRADE;
167 if(optarg!=NULL){
168 upgrade_opts=strdup(optarg);
169 if(upgrade_opts==NULL) die(STATE_UNKNOWN, "strdup failed");
170 }
171 break;
172 case 'U':
173 upgrade=UPGRADE;
174 if(optarg!=NULL){
175 upgrade_opts=strdup(optarg);
176 if(upgrade_opts==NULL) die(STATE_UNKNOWN, "strdup failed");
177 }
178 break;
179 case 'n':
180 upgrade=NO_UPGRADE;
181 break;
182 case 'u':
183 do_update=1;
184 if(optarg!=NULL){
185 update_opts=strdup(optarg);
186 if(update_opts==NULL) die(STATE_UNKNOWN, "strdup failed");
187 }
188 break;
189 case 'i':
190 do_include=add_to_regexp(do_include, optarg);
191 break;
192 case 'e':
193 do_exclude=add_to_regexp(do_exclude, optarg);
194 break;
195 case 'c':
196 do_critical=add_to_regexp(do_critical, optarg);
197 break;
198 default:
199 /* print short usage statement if args not parsable */
200 usage5();
201 }
202 }
204 return OK;
205 }
208 /* run an apt-get upgrade */
209 int run_upgrade(int *pkgcount, int *secpkgcount){
210 int i=0, result=STATE_UNKNOWN, regres=0, pc=0, spc=0;
211 struct output chld_out, chld_err;
212 regex_t ireg, ereg, sreg;
213 char *cmdline=NULL, rerrbuf[64];
214 const char *include_ptr=NULL, *crit_ptr=NULL;
216 if(upgrade==NO_UPGRADE) return STATE_OK;
218 /* compile the regexps */
219 if(do_include!=NULL) include_ptr=do_include;
220 else include_ptr="^Inst";
221 if(do_critical!=NULL) crit_ptr=do_critical;
222 else crit_ptr=SECURITY_RE;
224 regres=regcomp(&ireg, include_ptr, REG_EXTENDED);
225 if(regres!=0) {
226 regerror(regres, &ireg, rerrbuf, 64);
227 die(STATE_UNKNOWN, _("%s: Error compiling regexp: %s"), progname, rerrbuf);
228 }
230 if(do_exclude!=NULL){
231 regres=regcomp(&ereg, do_exclude, REG_EXTENDED);
232 if(regres!=0) {
233 regerror(regres, &ereg, rerrbuf, 64);
234 die(STATE_UNKNOWN, _("%s: Error compiling regexp: %s"),
235 progname, rerrbuf);
236 }
237 }
238 regres=regcomp(&sreg, crit_ptr, REG_EXTENDED);
239 if(regres!=0) {
240 regerror(regres, &ereg, rerrbuf, 64);
241 die(STATE_UNKNOWN, _("%s: Error compiling regexp: %s"),
242 progname, rerrbuf);
243 }
245 cmdline=construct_cmdline(upgrade, upgrade_opts);
246 /* run the upgrade */
247 result = np_runcmd(cmdline, &chld_out, &chld_err, 0);
248 /* apt-get upgrade only changes exit status if there is an
249 * internal error when run in dry-run mode. therefore we will
250 * treat such an error as UNKNOWN */
251 if(result != 0){
252 exec_warning=1;
253 result = STATE_UNKNOWN;
254 fprintf(stderr, _("'%s' exited with non-zero status.\n"),
255 cmdline);
256 }
258 /* parse the output, which should only consist of lines like
259 *
260 * Inst package ....
261 * Conf package ....
262 *
263 * so we'll filter based on "Inst" for the time being. later
264 * we may need to switch to the --print-uris output format,
265 * in which case the logic here will slightly change.
266 */
267 for(i = 0; i < chld_out.lines; i++) {
268 if(verbose){
269 printf("%s\n", chld_out.line[i]);
270 }
271 /* if it is a package we care about */
272 if(regexec(&ireg, chld_out.line[i], 0, NULL, 0)==0){
273 /* if we're not excluding, or it's not in the
274 * list of stuff to exclude */
275 if(do_exclude==NULL ||
276 regexec(&ereg, chld_out.line[i], 0, NULL, 0)!=0){
277 pc++;
278 if(regexec(&sreg, chld_out.line[i], 0, NULL, 0)==0){
279 spc++;
280 if(verbose) printf("*");
281 }
282 if(verbose){
283 printf("*%s\n", chld_out.line[i]);
284 }
285 }
286 }
287 }
288 *pkgcount=pc;
289 *secpkgcount=spc;
291 /* If we get anything on stderr, at least set warning */
292 if(chld_err.buflen){
293 stderr_warning=1;
294 result = max_state(result, STATE_WARNING);
295 if(verbose){
296 for(i = 0; i < chld_err.lines; i++) {
297 fprintf(stderr, "%s\n", chld_err.line[i]);
298 }
299 }
300 }
301 regfree(&ireg);
302 regfree(&sreg);
303 if(do_exclude!=NULL) regfree(&ereg);
304 free(cmdline);
305 return result;
306 }
308 /* run an apt-get update (needs root) */
309 int run_update(void){
310 int i=0, result=STATE_UNKNOWN;
311 struct output chld_out, chld_err;
312 char *cmdline;
314 /* run the upgrade */
315 cmdline = construct_cmdline(NO_UPGRADE, update_opts);
316 result = np_runcmd(cmdline, &chld_out, &chld_err, 0);
317 /* apt-get update changes exit status if it can't fetch packages.
318 * since we were explicitly asked to do so, this is treated as
319 * a critical error. */
320 if(result != 0){
321 exec_warning=1;
322 result = STATE_CRITICAL;
323 fprintf(stderr, _("'%s' exited with non-zero status.\n"),
324 cmdline);
325 }
327 if(verbose){
328 for(i = 0; i < chld_out.lines; i++) {
329 printf("%s\n", chld_out.line[i]);
330 }
331 }
333 /* If we get anything on stderr, at least set warning */
334 if(chld_err.buflen){
335 stderr_warning=1;
336 result = max_state(result, STATE_WARNING);
337 if(verbose){
338 for(i = 0; i < chld_err.lines; i++) {
339 fprintf(stderr, "%s\n", chld_err.line[i]);
340 }
341 }
342 }
343 free(cmdline);
344 return result;
345 }
347 char* add_to_regexp(char *expr, const char *next){
348 char *re=NULL;
350 if(expr==NULL){
351 re=malloc(sizeof(char)*(strlen("^Inst () ")+strlen(next)+1));
352 if(!re) die(STATE_UNKNOWN, "malloc failed!\n");
353 sprintf(re, "^Inst (%s) ", next);
354 } else {
355 /* resize it, adding an extra char for the new '|' separator */
356 re=realloc(expr, sizeof(char)*strlen(expr)+1+strlen(next)+1);
357 if(!re) die(STATE_UNKNOWN, "realloc failed!\n");
358 /* append it starting at ')' in the old re */
359 sprintf((char*)(re+strlen(re)-2), "|%s) ", next);
360 }
362 return re;
363 }
365 char* construct_cmdline(upgrade_type u, const char *opts){
366 int len=0;
367 const char *opts_ptr=NULL, *aptcmd=NULL;
368 char *cmd=NULL;
370 switch(u){
371 case UPGRADE:
372 if(opts==NULL) opts_ptr=UPGRADE_DEFAULT_OPTS;
373 else opts_ptr=opts;
374 aptcmd="upgrade";
375 break;
376 case DIST_UPGRADE:
377 if(opts==NULL) opts_ptr=UPGRADE_DEFAULT_OPTS;
378 else opts_ptr=opts;
379 aptcmd="dist-upgrade";
380 break;
381 case NO_UPGRADE:
382 if(opts==NULL) opts_ptr=UPDATE_DEFAULT_OPTS;
383 else opts_ptr=opts;
384 aptcmd="update";
385 break;
386 }
388 len+=strlen(PATH_TO_APTGET)+1; /* "/usr/bin/apt-get " */
389 len+=strlen(opts_ptr)+1; /* "opts " */
390 len+=strlen(aptcmd)+1; /* "upgrade\0" */
392 cmd=(char*)malloc(sizeof(char)*len);
393 if(cmd==NULL) die(STATE_UNKNOWN, "malloc failed");
394 sprintf(cmd, "%s %s %s", PATH_TO_APTGET, opts_ptr, aptcmd);
395 return cmd;
396 }
398 /* informative help message */
399 void
400 print_help (void)
401 {
402 print_revision(progname, NP_VERSION);
404 printf(_(COPYRIGHT), copyright, email);
406 printf("%s\n", _("This plugin checks for software updates on systems that use"));
407 printf("%s\n", _("package management systems based on the apt-get(8) command"));
408 printf("%s\n", _("found in Debian GNU/Linux"));
410 printf ("\n\n");
412 print_usage();
414 printf(_(UT_HELP_VRSN));
415 printf(_(UT_EXTRA_OPTS));
417 printf(_(UT_TIMEOUT), timeout_interval);
419 printf (" %s\n", "-U, --upgrade=OPTS");
420 printf (" %s\n", _("[Default] Perform an upgrade. If an optional OPTS argument is provided,"));
421 printf (" %s\n", _("apt-get will be run with these command line options instead of the"));
422 printf (" %s", _("default "));
423 printf ("(%s).\n", UPGRADE_DEFAULT_OPTS);
424 printf (" %s\n", _("Note that you may be required to have root privileges if you do not use"));
425 printf (" %s\n", _("the default options."));
426 printf (" %s\n", "-d, --dist-upgrade=OPTS");
427 printf (" %s\n", _("Perform a dist-upgrade instead of normal upgrade. Like with -U OPTS"));
428 printf (" %s\n", _("can be provided to override the default options."));
429 printf (" %s\n", " -n, --no-upgrade");
430 printf (" %s\n", _("Do not run the upgrade. Probably not useful (without -u at least)."));
431 printf (" %s\n", "-i, --include=REGEXP");
432 printf (" %s\n", _("Include only packages matching REGEXP. Can be specified multiple times"));
433 printf (" %s\n", _("the values will be combined together. Any patches matching this list"));
434 printf (" %s\n", _("cause the plugin to return WARNING status. Others will be ignored."));
435 printf (" %s\n", _("Default is to include all packages."));
436 printf (" %s\n", "-e, --exclude=REGEXP");
437 printf (" %s\n", _("Exclude packages matching REGEXP from the list of packages that would"));
438 printf (" %s\n", _("otherwise be included. Can be specified multiple times; the values"));
439 printf (" %s\n", _("will be combined together. Default is to exclude no packages."));
440 printf (" %s\n", "-c, --critical=REGEXP");
441 printf (" %s\n", _("If the full package information of any of the upgradable packages match"));
442 printf (" %s\n", _("this REGEXP, the plugin will return CRITICAL status. Can be specified"));
443 printf (" %s\n", _("multiple times like above. Default is a regexp matching security"));
444 printf (" %s\n", _("upgrades for Debian and Ubuntu:"));
445 printf (" \t\%s\n", SECURITY_RE);
446 printf (" %s\n", _("Note that the package must first match the include list before its"));
447 printf (" %s\n\n", _("information is compared against the critical list."));
449 printf ("%s\n\n", _("The following options require root privileges and should be used with care:"));
450 printf (" %s\n", "-u, --update=OPTS");
451 printf (" %s\n", _("First perform an 'apt-get update'. An optional OPTS parameter overrides"));
452 printf (" %s\n", _("the default options. Note: you may also need to adjust the global"));
453 printf (" %s\n", _("timeout (with -t) to prevent the plugin from timing out if apt-get"));
454 printf (" %s\n", _("upgrade is expected to take longer than the default timeout."));
456 #ifdef NP_EXTRA_OPTS
457 printf("\n");
458 printf("%s\n", _("Notes:"));
459 printf(_(UT_EXTRA_OPTS_NOTES));
460 #endif
462 printf(_(UT_SUPPORT));
463 }
466 /* simple usage heading */
467 void
468 print_usage(void)
469 {
470 printf (_("Usage:"));
471 printf ("%s [[-d|-u|-U]opts] [-n] [-t timeout]\n", progname);
472 }