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$
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;
117 }
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;
196 }
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"));
252 }
254 /* simple usage heading */
255 void print_usage(void){
256 printf ("Usage: %s [[-d|-u|-U]opts] [-n] [-t timeout]\n", progname);
257 }
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;
357 }
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;
396 }
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;
414 }
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;
447 }