1 /******************************************************************************
2 *
3 * Program: PostgreSQL plugin for Nagios
4 * License: GPL
5 *
6 * License Information:
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 *
22 * $Id$
23 *
24 *****************************************************************************/
26 const char *progname = "check_pgsql";
27 #define REVISION "$Revision$"
28 #define COPYRIGHT "1999-2001"
29 #define AUTHOR "Karl DeBisschop"
30 #define EMAIL "kdebisschop@users.sourceforge.net"
31 #define SUMMARY "Tests to see if a PostgreSQL DBMS is accepting connections.\n"
33 #define OPTIONS "\
34 \[-c critical_time] [-w warning_time] [-t timeout] [-H host]\n\
35 [-P port] [-d database] [-l logname] [-p password]"
37 #define LONGOPTIONS "\
38 -c, --critical=INTEGER\n\
39 Exit STATE_CRITICAL if connection time exceeds threshold (default: %d)\n\
40 -w, --warning=INTEGER\n\
41 Exit STATE_WARNING if connection time exceeds threshold (default: %d)\n\
42 -t, --timeout=INTEGER\n\
43 Terminate test if timeout limit is exceeded (default: %d)\n\
44 -H, --hostname=STRING\n\
45 Name or numeric IP address of machine running backend\n\
46 -P, --port=INTEGER\n\
47 Port running backend (default: %d)\n\
48 -d, --database=STRING\n\
49 Database to check (default: %s)\n\
50 -l, --logname = STRING\n\
51 Login name of user\n\
52 -p, --password = STRING\n\
53 Password (BIG SECURITY ISSUE)\n"
55 #define DESCRIPTION "All parameters are optional.\n\
56 \n\
57 This plugin tests a PostgreSQL DBMS to determine whether it is active and\n\
58 accepting queries. In its current operation, it simply connects to the\n\
59 specified database, and then disconnects. If no database is specified, it\n\
60 connects to the template1 database, which is present in every functioning \n\
61 PostgreSQL DBMS.\n\
62 \n\
63 The plugin will connect to a local postmaster if no host is specified. To\n\
64 connect to a remote host, be sure that the remote postmaster accepts TCP/IP\n\
65 connections (start the postmaster with the -i option).\n\
66 \n\
67 Typically, the nagios user (unless the --logname option is used) should be\n\
68 able to connect to the database without a password. The plugin can also send\n\
69 a password, but no effort is made to obsure or encrypt the password.\n"
71 #define DEFAULT_DB "template1"
72 #define DEFAULT_HOST "127.0.0.1"
73 enum {
74 DEFAULT_PORT = 5432,
75 DEFAULT_WARN = 2,
76 DEFAULT_CRIT = 8,
77 DEFAULT_TIMEOUT = 30
78 };
80 #include "config.h"
81 #include "common.h"
82 #include "utils.h"
83 #include <netdb.h>
84 #include <libpq-fe.h>
86 int process_arguments (int, char **);
87 int validate_arguments (void);
88 void print_usage (void);
89 void print_help (void);
90 int is_pg_dbname (char *);
91 int is_pg_logname (char *);
93 char *pghost = NULL; /* host name of the backend server */
94 char *pgport = NULL; /* port of the backend server */
95 int default_port = DEFAULT_PORT;
96 char *pgoptions = NULL;
97 char *pgtty = NULL;
98 char dbName[NAMEDATALEN] = DEFAULT_DB;
99 char *pguser = NULL;
100 char *pgpasswd = NULL;
101 int twarn = DEFAULT_WARN;
102 int tcrit = DEFAULT_CRIT;
104 PGconn *conn;
105 /*PGresult *res;*/
106 \f
108 /******************************************************************************
110 The (psuedo?)literate programming XML is contained within \@\@\- <XML> \-\@\@
111 tags in the comments. With in the tags, the XML is assembled sequentially.
112 You can define entities in tags. You also have all the #defines available as
113 entities.
115 Please note that all tags must be lowercase to use the DocBook XML DTD.
117 @@-<article>
119 <sect1>
120 <title>Quick Reference</title>
121 <!-- The refentry forms a manpage -->
122 <refentry>
123 <refmeta>
124 <manvolnum>5<manvolnum>
125 </refmeta>
126 <refnamdiv>
127 <refname>&progname;</refname>
128 <refpurpose>&SUMMARY;</refpurpose>
129 </refnamdiv>
130 </refentry>
131 </sect1>
133 <sect1>
134 <title>FAQ</title>
135 </sect1>
137 <sect1>
138 <title>Theory, Installation, and Operation</title>
140 <sect2>
141 <title>General Description</title>
142 <para>
143 &DESCRIPTION;
144 </para>
145 </sect2>
147 <sect2>
148 <title>Future Enhancements</title>
149 <para>ToDo List</para>
150 <itemizedlist>
151 <listitem>Add option to get password from a secured file rather than the command line</listitem>
152 <listitem>Add option to specify the query to execute</listitem>
153 </itemizedlist>
154 </sect2>
157 <sect2>
158 <title>Functions</title>
159 -@@
160 ******************************************************************************/
162 int
163 main (int argc, char **argv)
164 {
165 int elapsed_time;
167 /* begin, by setting the parameters for a backend connection if the
168 * parameters are null, then the system will try to use reasonable
169 * defaults by looking up environment variables or, failing that,
170 * using hardwired constants */
172 pgoptions = NULL; /* special options to start up the backend server */
173 pgtty = NULL; /* debugging tty for the backend server */
175 if (process_arguments (argc, argv) == ERROR)
176 usage ("Could not parse arguments");
178 /* Set signal handling and alarm */
179 if (signal (SIGALRM, timeout_alarm_handler) == SIG_ERR) {
180 printf ("Cannot catch SIGALRM");
181 return STATE_UNKNOWN;
182 }
183 alarm (timeout_interval);
185 /* make a connection to the database */
186 time (&start_time);
187 conn =
188 PQsetdbLogin (pghost, pgport, pgoptions, pgtty, dbName, pguser, pgpasswd);
189 time (&end_time);
190 elapsed_time = (int) (end_time - start_time);
192 /* check to see that the backend connection was successfully made */
193 if (PQstatus (conn) == CONNECTION_BAD) {
194 printf ("PGSQL: CRITICAL - no connection to '%s' (%s).\n", dbName,
195 PQerrorMessage (conn));
196 PQfinish (conn);
197 return STATE_CRITICAL;
198 }
199 else if (elapsed_time > tcrit) {
200 PQfinish (conn);
201 printf ("PGSQL: CRITICAL - database %s (%d sec.)\n", dbName,
202 elapsed_time);
203 return STATE_CRITICAL;
204 }
205 else if (elapsed_time > twarn) {
206 PQfinish (conn);
207 printf ("PGSQL: WARNING - database %s (%d sec.)\n", dbName, elapsed_time);
208 return STATE_WARNING;
209 }
210 else {
211 PQfinish (conn);
212 printf ("PGSQL: ok - database %s (%d sec.)\n", dbName, elapsed_time);
213 return STATE_OK;
214 }
215 }
216 \f
220 void
221 print_help (void)
222 {
223 print_revision (progname, REVISION);
224 printf
225 ("Copyright (c) %s %s <%s>\n\n%s\n",
226 COPYRIGHT, AUTHOR, EMAIL, SUMMARY);
227 print_usage ();
228 printf
229 ("\nOptions:\n" LONGOPTIONS "\n" DESCRIPTION "\n",
230 DEFAULT_WARN, DEFAULT_CRIT, DEFAULT_TIMEOUT, DEFAULT_PORT, DEFAULT_DB);
231 support ();
232 }
234 void
235 print_usage (void)
236 {
237 printf ("Usage:\n" " %s %s\n"
238 " %s (-h | --help) for detailed help\n"
239 " %s (-V | --version) for version information\n",
240 progname, OPTIONS, progname, progname);
241 }
242 \f
245 /* process command-line arguments */
246 int
247 process_arguments (int argc, char **argv)
248 {
249 int c;
251 #ifdef HAVE_GETOPT_H
252 int option_index = 0;
253 static struct option long_options[] = {
254 {"help", no_argument, 0, 'h'},
255 {"version", no_argument, 0, 'V'},
256 {"timeout", required_argument, 0, 't'},
257 {"critical", required_argument, 0, 'c'},
258 {"warning", required_argument, 0, 'w'},
259 {"hostname", required_argument, 0, 'H'},
260 {"logname", required_argument, 0, 'l'},
261 {"password", required_argument, 0, 'p'},
262 {"authorization", required_argument, 0, 'a'},
263 {"port", required_argument, 0, 'P'},
264 {"database", required_argument, 0, 'd'},
265 {0, 0, 0, 0}
266 };
267 #endif
269 while (1) {
270 #ifdef HAVE_GETOPT_H
271 c = getopt_long (argc, argv, "hVt:c:w:H:P:d:l:p:a:",
272 long_options, &option_index);
273 #else
274 c = getopt (argc, argv, "hVt:c:w:H:P:d:l:p:a:");
275 #endif
276 if (c == EOF)
277 break;
279 switch (c) {
280 case '?': /* usage */
281 usage3 ("Unknown argument", optopt);
282 case 'h': /* help */
283 print_help ();
284 exit (STATE_OK);
285 case 'V': /* version */
286 print_revision (progname, REVISION);
287 exit (STATE_OK);
288 case 't': /* timeout period */
289 if (!is_integer (optarg))
290 usage2 ("Timeout Interval must be an integer", optarg);
291 timeout_interval = atoi (optarg);
292 break;
293 case 'c': /* critical time threshold */
294 if (!is_integer (optarg))
295 usage2 ("Invalid critical threshold", optarg);
296 tcrit = atoi (optarg);
297 break;
298 case 'w': /* warning time threshold */
299 if (!is_integer (optarg))
300 usage2 ("Invalid critical threshold", optarg);
301 twarn = atoi (optarg);
302 break;
303 case 'H': /* host */
304 if (!is_host (optarg))
305 usage2 ("You gave an invalid host name", optarg);
306 pghost = optarg;
307 break;
308 case 'P': /* port */
309 if (!is_integer (optarg))
310 usage2 ("Port must be an integer", optarg);
311 pgport = optarg;
312 break;
313 case 'd': /* database name */
314 if (!is_pg_dbname (optarg))
315 usage2 ("Database name is not valid", optarg);
316 strncpy (dbName, optarg, NAMEDATALEN - 1);
317 dbName[NAMEDATALEN - 1] = 0;
318 break;
319 case 'l': /* login name */
320 if (!is_pg_logname (optarg))
321 usage2 ("user name is not valid", optarg);
322 pguser = optarg;
323 break;
324 case 'p': /* authentication password */
325 case 'a':
326 pgpasswd = optarg;
327 break;
328 }
329 }
331 return validate_arguments ();
332 }
335 /******************************************************************************
337 @@-
338 <sect3>
339 <title>validate_arguments</title>
341 <para>&PROTO_validate_arguments;</para>
343 <para>Given a database name, this function returns TRUE if the string
344 is a valid PostgreSQL database name, and returns false if it is
345 not.</para>
347 <para>Valid PostgreSQL database names are less than &NAMEDATALEN;
348 characters long and consist of letters, numbers, and underscores. The
349 first character cannot be a number, however.</para>
351 </sect3>
352 -@@
353 ******************************************************************************/
355 int
356 validate_arguments ()
357 {
358 return OK;
359 }
360 \f
363 /******************************************************************************
365 @@-
366 <sect3>
367 <title>is_pg_dbname</title>
369 <para>&PROTO_is_pg_dbname;</para>
371 <para>Given a database name, this function returns TRUE if the string
372 is a valid PostgreSQL database name, and returns false if it is
373 not.</para>
375 <para>Valid PostgreSQL database names are less than &NAMEDATALEN;
376 characters long and consist of letters, numbers, and underscores. The
377 first character cannot be a number, however.</para>
379 </sect3>
380 -@@
381 ******************************************************************************/
383 int
384 is_pg_dbname (char *dbname)
385 {
386 char txt[NAMEDATALEN];
387 char tmp[NAMEDATALEN];
388 if (strlen (dbname) > NAMEDATALEN - 1)
389 return (FALSE);
390 strncpy (txt, dbname, NAMEDATALEN - 1);
391 txt[NAMEDATALEN - 1] = 0;
392 if (sscanf (txt, "%[_a-zA-Z]%[^_a-zA-Z0-9]", tmp, tmp) == 1)
393 return (TRUE);
394 if (sscanf (txt, "%[_a-zA-Z]%[_a-zA-Z0-9]%[^_a-zA-Z0-9]", tmp, tmp, tmp) ==
395 2) return (TRUE);
396 return (FALSE);
397 }
399 /**
401 the tango program should eventually create an entity here based on the
402 function prototype
404 @@-
405 <sect3>
406 <title>is_pg_logname</title>
408 <para>&PROTO_is_pg_logname;</para>
410 <para>Given a username, this function returns TRUE if the string is a
411 valid PostgreSQL username, and returns false if it is not. Valid PostgreSQL
412 usernames are less than &NAMEDATALEN; characters long and consist of
413 letters, numbers, dashes, and underscores, plus possibly some other
414 characters.</para>
416 <para>Currently this function only checks string length. Additional checks
417 should be added.</para>
419 </sect3>
420 -@@
421 ******************************************************************************/
423 int
424 is_pg_logname (char *username)
425 {
426 if (strlen (username) > NAMEDATALEN - 1)
427 return (FALSE);
428 return (TRUE);
429 }
431 /******************************************************************************
432 @@-
433 </sect2>
434 </sect1>
435 </article>
436 -@@
437 ******************************************************************************/