Code

2de8e77637fc0994807793212e4f6c5193040a4a
[nagiosplug.git] / plugins / check_apt.c
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$
35  
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;
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;
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;
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;
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;      
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;
402 /* informative help message */
403 void
404 print_help (void)
406   print_revision(progname, revision);
407   
408   printf(_(COPYRIGHT), copyright, email);
409   
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();
417   
418   printf(_(UT_HELP_VRSN));
419   
420   printf(_(UT_TIMEOUT), timeout_interval);
421   
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);
453          
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."));
462   
463 /* simple usage heading */
464 void
465 print_usage(void)
467   printf (_("Usage:"));
468   printf ("%s [[-d|-u|-U]opts] [-n] [-t timeout]\n", progname);