Code

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