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