Code

all plugins now using centralized ssl functions in netutils.c
[nagiosplug.git] / plugins / check_smtp.c
1 /******************************************************************************
3  This program is free software; you can redistribute it and/or modify
4  it under the terms of the GNU General Public License as published by
5  the Free Software Foundation; either version 2 of the License, or
6  (at your option) any later version.
8  This program is distributed in the hope that it will be useful,
9  but WITHOUT ANY WARRANTY; without even the implied warranty of
10  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  GNU General Public License for more details.
13  You should have received a copy of the GNU General Public License
14  along with this program; if not, write to the Free Software
15  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  $Id$
18  
19 ******************************************************************************/
21 const char *progname = "check_smtp";
22 const char *revision = "$Revision$";
23 const char *copyright = "2000-2004";
24 const char *email = "nagiosplug-devel@lists.sourceforge.net";
26 #include "common.h"
27 #include "netutils.h"
28 #include "utils.h"
30 #ifdef HAVE_SSL
31 int check_cert = FALSE;
32 int days_till_exp;
33 #  define my_recv(buf, len) ((use_ssl && ssl_established) ? np_net_ssl_read(buf, len) : read(sd, buf, len))
34 #  define my_send(buf, len) ((use_ssl && ssl_established) ? np_net_ssl_write(buf, len) : send(sd, buf, len, 0))
35 #else /* ifndef HAVE_SSL */
36 #  define my_recv(buf, len) read(sd, buf, len)
37 #  define my_send(buf, len) send(sd, buf, len, 0)
38 #endif
40 enum {
41         SMTP_PORT       = 25
42 };
43 #define SMTP_EXPECT "220"
44 #define SMTP_HELO "HELO "
45 #define SMTP_EHLO "EHLO "
46 #define SMTP_QUIT "QUIT\r\n"
47 #define SMTP_STARTTLS "STARTTLS\r\n"
49 #ifndef HOST_MAX_BYTES
50 #define HOST_MAX_BYTES 255
51 #endif
53 #define EHLO_SUPPORTS_STARTTLS 1
55 int process_arguments (int, char **);
56 int validate_arguments (void);
57 void print_help (void);
58 void print_usage (void);
59 int my_close(void);
61 #ifdef HAVE_REGEX_H
62 #include <regex.h>
63 char regex_expect[MAX_INPUT_BUFFER] = "";
64 regex_t preg;
65 regmatch_t pmatch[10];
66 char timestamp[20] = "";
67 char errbuf[MAX_INPUT_BUFFER];
68 int cflags = REG_EXTENDED | REG_NOSUB | REG_NEWLINE;
69 int eflags = 0;
70 int errcode, excode;
71 #endif
73 int server_port = SMTP_PORT;
74 char *server_address = NULL;
75 char *server_expect = NULL;
76 int smtp_use_dummycmd = 0;
77 char *mail_command = NULL;
78 char *from_arg = NULL;
79 int ncommands=0;
80 int command_size=0;
81 int nresponses=0;
82 int response_size=0;
83 char **commands = NULL;
84 char **responses = NULL;
85 int warning_time = 0;
86 int check_warning_time = FALSE;
87 int critical_time = 0;
88 int check_critical_time = FALSE;
89 int verbose = 0;
90 int use_ssl = FALSE;
91 short use_ehlo = FALSE;
92 short ssl_established = 0;
93 char *localhostname = NULL;
94 int sd;
95 char buffer[MAX_INPUT_BUFFER];
96 enum {
97   TCP_PROTOCOL = 1,
98   UDP_PROTOCOL = 2,
99   MAXBUF = 1024
100 };
102 int
103 main (int argc, char **argv)
105         short supports_tls=FALSE;
106         int n = 0;
107         double elapsed_time;
108         long microsec;
109         int result = STATE_UNKNOWN;
110         char *cmd_str = NULL;
111         char *helocmd = NULL;
112         struct timeval tv;
113         struct hostent *hp;
115         setlocale (LC_ALL, "");
116         bindtextdomain (PACKAGE, LOCALEDIR);
117         textdomain (PACKAGE);
119         if (process_arguments (argc, argv) == ERROR)
120                 usage4 (_("Could not parse arguments"));
122         /* initialize the HELO command with the localhostname */
123         if(! localhostname){
124                 localhostname = malloc (HOST_MAX_BYTES);
125                 if(!localhostname){
126                         printf(_("malloc() failed!\n"));
127                         return STATE_CRITICAL;
128                 }
129                 if(gethostname(localhostname, HOST_MAX_BYTES)){
130                         printf(_("gethostname() failed!\n"));
131                         return STATE_CRITICAL;
132                 }
133                 hp = gethostbyname(localhostname);
134                 if(!hp) helocmd = localhostname;
135                 else helocmd = hp->h_name;
136         } else {
137                 helocmd = localhostname;
138         }
139         if(use_ehlo)
140                 asprintf (&helocmd, "%s%s%s", SMTP_EHLO, helocmd, "\r\n");
141         else
142                 asprintf (&helocmd, "%s%s%s", SMTP_HELO, helocmd, "\r\n");
144         /* initialize the MAIL command with optional FROM command  */
145         asprintf (&cmd_str, "%sFROM: %s%s", mail_command, from_arg, "\r\n");
147         if (verbose && smtp_use_dummycmd)
148                 printf ("FROM CMD: %s", cmd_str);
149         
150         /* initialize alarm signal handling */
151         (void) signal (SIGALRM, socket_timeout_alarm_handler);
153         /* set socket timeout */
154         (void) alarm (socket_timeout);
156         /* start timer */
157         gettimeofday (&tv, NULL);
159         /* try to connect to the host at the given port number */
160         result = my_tcp_connect (server_address, server_port, &sd);
162         if (result == STATE_OK) { /* we connected */
164                 /* watch for the SMTP connection string and */
165                 /* return a WARNING status if we couldn't read any data */
166                 if (recv (sd, buffer, MAX_INPUT_BUFFER - 1, 0) == -1) {
167                         printf (_("recv() failed\n"));
168                         result = STATE_WARNING;
169                 }
170                 else {
171                         if (verbose)
172                                 printf ("%s", buffer);
173                         /* strip the buffer of carriage returns */
174                         strip (buffer);
175                         /* make sure we find the response we are looking for */
176                         if (!strstr (buffer, server_expect)) {
177                                 if (server_port == SMTP_PORT)
178                                         printf (_("Invalid SMTP response received from host\n"));
179                                 else
180                                         printf (_("Invalid SMTP response received from host on port %d\n"),
181                                                                         server_port);
182                                 result = STATE_WARNING;
183                         }
184                 }
186                 /* send the HELO/EHLO command */
187                 send(sd, helocmd, strlen(helocmd), 0);
189                 /* allow for response to helo command to reach us */
190                 if(read (sd, buffer, MAXBUF - 1) < 0){
191                         printf (_("recv() failed\n"));
192                         return STATE_WARNING;
193                 } else if(use_ehlo){
194                         buffer[MAXBUF-1]='\0';
195                         if(strstr(buffer, "250 STARTTLS") != NULL ||
196                            strstr(buffer, "250-STARTTLS") != NULL){
197                                 supports_tls=TRUE;
198                         }
199                 }
201                 if(use_ssl && ! supports_tls){
202                         printf(_("WARNING - TLS not supported by server\n"));
203                         send (sd, SMTP_QUIT, strlen (SMTP_QUIT), 0);
204                         return STATE_WARNING;
205                 }
207 #ifdef HAVE_SSL
208                 if(use_ssl) {
209                   /* send the STARTTLS command */
210                   send(sd, SMTP_STARTTLS, strlen(SMTP_STARTTLS), 0);
212                   recv(sd,buffer, MAX_INPUT_BUFFER-1, 0); /* wait for it */
213                   if (!strstr (buffer, server_expect)) {
214                     printf (_("Server does not support STARTTLS\n"));
215                     send (sd, SMTP_QUIT, strlen (SMTP_QUIT), 0);
216                     return STATE_UNKNOWN;
217                   }
218                   result = np_net_ssl_init(sd);
219                   if(result != STATE_OK) {
220                     printf (_("CRITICAL - Cannot create SSL context.\n"));
221                     np_net_ssl_cleanup();
222                     close(sd);
223                     return STATE_CRITICAL;
224                   } else {
225                         ssl_established = 1;
226                   }
227 #  ifdef USE_OPENSSL
228                   if ( check_cert ) {
229                     result = np_net_ssl_check_cert(days_till_exp);
230                     if(result != STATE_OK){
231                       printf (_("CRITICAL - Cannot retrieve server certificate.\n"));
232                     }
233                     my_close();
234                     return result;
235                   }
236 #  endif /* USE_OPENSSL */
237                 }
238 #endif
239                                 
240                 /* sendmail will syslog a "NOQUEUE" error if session does not attempt
241                  * to do something useful. This can be prevented by giving a command
242                  * even if syntax is illegal (MAIL requires a FROM:<...> argument)
243                  *
244                  * According to rfc821 you can include a null reversepath in the from command
245                  * - but a log message is generated on the smtp server.
246                  *
247                  * You can disable sending mail_command with '--nocommand'
248                  * Use the -f option to provide a FROM address
249                  */
250                 if (smtp_use_dummycmd) {
251                   my_send(cmd_str, strlen(cmd_str));
252                   my_recv(buffer, MAX_INPUT_BUFFER-1);
253                   if (verbose) 
254                     printf("%s", buffer);
255                 }
257                 while (n < ncommands) {
258                         asprintf (&cmd_str, "%s%s", commands[n], "\r\n");
259                         my_send(cmd_str, strlen(cmd_str));
260                         my_recv(buffer, MAX_INPUT_BUFFER-1);
261                         if (verbose) 
262                                 printf("%s", buffer);
263                         strip (buffer);
264                         if (n < nresponses) {
265 #ifdef HAVE_REGEX_H
266                                 cflags |= REG_EXTENDED | REG_NOSUB | REG_NEWLINE;
267                                 errcode = regcomp (&preg, responses[n], cflags);
268                                 if (errcode != 0) {
269                                         regerror (errcode, &preg, errbuf, MAX_INPUT_BUFFER);
270                                         printf (_("Could Not Compile Regular Expression"));
271                                         return ERROR;
272                                 }
273                                 excode = regexec (&preg, buffer, 10, pmatch, eflags);
274                                 if (excode == 0) {
275                                         result = STATE_OK;
276                                 }
277                                 else if (excode == REG_NOMATCH) {
278                                         result = STATE_WARNING;
279                                         printf (_("SMTP %s - Invalid response '%s' to command '%s'\n"), state_text (result), buffer, commands[n]);
280                                 }
281                                 else {
282                                         regerror (excode, &preg, errbuf, MAX_INPUT_BUFFER);
283                                         printf (_("Execute Error: %s\n"), errbuf);
284                                         result = STATE_UNKNOWN;
285                                 }
286 #else
287                                 if (strstr(buffer, responses[n])!=buffer) {
288                                         result = STATE_WARNING;
289                                         printf (_("SMTP %s - Invalid response '%s' to command '%s'\n"), state_text (result), buffer, commands[n]);
290                                 }
291 #endif
292                         }
293                         n++;
294                 }
296                 /* tell the server we're done */
297                 my_send (SMTP_QUIT, strlen (SMTP_QUIT));
299                 /* finally close the connection */
300                 close (sd);
301         }
303         /* reset the alarm */
304         alarm (0);
306         microsec = deltime (tv);
307         elapsed_time = (double)microsec / 1.0e6;
309         if (result == STATE_OK) {
310                 if (check_critical_time && elapsed_time > (double) critical_time)
311                         result = STATE_CRITICAL;
312                 else if (check_warning_time && elapsed_time > (double) warning_time)
313                         result = STATE_WARNING;
314         }
316         printf (_("SMTP %s - %.3f sec. response time%s%s|%s\n"),
317                 state_text (result), elapsed_time,
318           verbose?", ":"", verbose?buffer:"",
319                 fperfdata ("time", elapsed_time, "s",
320                           (int)check_warning_time, warning_time,
321                           (int)check_critical_time, critical_time,
322                           TRUE, 0, FALSE, 0));
324         return result;
329 /* process command-line arguments */
330 int
331 process_arguments (int argc, char **argv)
333         int c;
335         int option = 0;
336         static struct option longopts[] = {
337                 {"hostname", required_argument, 0, 'H'},
338                 {"expect", required_argument, 0, 'e'},
339                 {"critical", required_argument, 0, 'c'},
340                 {"warning", required_argument, 0, 'w'},
341                 {"timeout", required_argument, 0, 't'},
342                 {"port", required_argument, 0, 'p'},
343                 {"from", required_argument, 0, 'f'},
344                 {"fqdn", required_argument, 0, 'F'},
345                 {"command", required_argument, 0, 'C'},
346                 {"response", required_argument, 0, 'R'},
347                 {"nocommand", required_argument, 0, 'n'},
348                 {"verbose", no_argument, 0, 'v'},
349                 {"version", no_argument, 0, 'V'},
350                 {"use-ipv4", no_argument, 0, '4'},
351                 {"use-ipv6", no_argument, 0, '6'},
352                 {"help", no_argument, 0, 'h'},
353                 {"starttls",no_argument,0,'S'},
354                 {"certificate",required_argument,0,'D'},
355                 {0, 0, 0, 0}
356         };
358         if (argc < 2)
359                 return ERROR;
361         for (c = 1; c < argc; c++) {
362                 if (strcmp ("-to", argv[c]) == 0)
363                         strcpy (argv[c], "-t");
364                 else if (strcmp ("-wt", argv[c]) == 0)
365                         strcpy (argv[c], "-w");
366                 else if (strcmp ("-ct", argv[c]) == 0)
367                         strcpy (argv[c], "-c");
368         }
370         while (1) {
371                 c = getopt_long (argc, argv, "+hVv46t:p:f:e:c:w:H:C:R:SD:F:",
372                                  longopts, &option);
374                 if (c == -1 || c == EOF)
375                         break;
377                 switch (c) {
378                 case 'H':                                                                       /* hostname */
379                         if (is_host (optarg)) {
380                                 server_address = optarg;
381                         }
382                         else {
383                                 usage2 (_("Invalid hostname/address"), optarg);
384                         }
385                         break;
386                 case 'p':                                                                       /* port */
387                         if (is_intpos (optarg))
388                                 server_port = atoi (optarg);
389                         else
390                                 usage4 (_("Port must be a positive integer"));
391                         break;
392                 case 'F':
393                 /* localhostname */
394                         localhostname = strdup(optarg);
395                         break;
396                 case 'f':                                                                       /* from argument */
397                         from_arg = optarg;
398                         smtp_use_dummycmd = 1;
399                         break;
400                 case 'e':                                                                       /* server expect string on 220  */
401                         server_expect = optarg;
402                         break;
403                 case 'C':                                                                       /* commands  */
404                         if (ncommands >= command_size) {
405                                 commands = realloc (commands, command_size+8);
406                                 if (commands == NULL)
407                                         die (STATE_UNKNOWN,
408                                              _("Could not realloc() units [%d]\n"), ncommands);
409                         }
410                         commands[ncommands] = optarg;
411                         ncommands++;
412                         break;
413                 case 'R':                                                                       /* server responses */
414                         if (nresponses >= response_size) {
415                                 responses = realloc (responses, response_size+8);
416                                 if (responses == NULL)
417                                         die (STATE_UNKNOWN,
418                                              _("Could not realloc() units [%d]\n"), nresponses);
419                         }
420                         responses[nresponses] = optarg;
421                         nresponses++;
422                         break;
423                 case 'c':                                                                       /* critical time threshold */
424                         if (is_intnonneg (optarg)) {
425                                 critical_time = atoi (optarg);
426                                 check_critical_time = TRUE;
427                         }
428                         else {
429                                 usage4 (_("Critical time must be a positive integer"));
430                         }
431                         break;
432                 case 'w':                                                                       /* warning time threshold */
433                         if (is_intnonneg (optarg)) {
434                                 warning_time = atoi (optarg);
435                                 check_warning_time = TRUE;
436                         }
437                         else {
438                                 usage4 (_("Warning time must be a positive integer"));
439                         }
440                         break;
441                 case 'v':                                                                       /* verbose */
442                         verbose++;
443                         break;
444                 case 't':                                                                       /* timeout */
445                         if (is_intnonneg (optarg)) {
446                                 socket_timeout = atoi (optarg);
447                         }
448                         else {
449                                 usage4 (_("Timeout interval must be a positive integer"));
450                         }
451                         break;
452                 case 'S':
453                 /* starttls */
454                         use_ssl = TRUE;
455                         use_ehlo = TRUE;
456                         break;
457                 case 'D':
458                 /* Check SSL cert validity */
459 #ifdef USE_OPENSSL
460                         if (!is_intnonneg (optarg))
461                                 usage2 ("Invalid certificate expiration period",optarg);
462                                 days_till_exp = atoi (optarg);
463                                 check_cert = TRUE;
464 #else
465                                 usage (_("SSL support not available - install OpenSSL and recompile"));
466 #endif
467                         break;
468                 case '4':
469                         address_family = AF_INET;
470                         break;
471                 case '6':
472 #ifdef USE_IPV6
473                         address_family = AF_INET6;
474 #else
475                         usage4 (_("IPv6 support not available"));
476 #endif
477                         break;
478                 case 'V':                                                                       /* version */
479                         print_revision (progname, revision);
480                         exit (STATE_OK);
481                 case 'h':                                                                       /* help */
482                         print_help ();
483                         exit (STATE_OK);
484                 case '?':                                                                       /* help */
485                         usage2 (_("Unknown argument"), optarg);
486                 }
487         }
489         c = optind;
490         if (server_address == NULL) {
491                 if (argv[c]) {
492                         if (is_host (argv[c]))
493                                 server_address = argv[c];
494                         else
495                                 usage2 (_("Invalid hostname/address"), argv[c]);
496                 }
497                 else {
498                         asprintf (&server_address, "127.0.0.1");
499                 }
500         }
502         if (server_expect == NULL)
503                 server_expect = strdup (SMTP_EXPECT);
505         if (mail_command == NULL)
506                 mail_command = strdup("MAIL ");
508         if (from_arg==NULL)
509                 from_arg = strdup(" ");
511         return validate_arguments ();
516 int
517 validate_arguments (void)
519         return OK;
524 void
525 print_help (void)
527         char *myport;
528         asprintf (&myport, "%d", SMTP_PORT);
530         print_revision (progname, revision);
532         printf ("Copyright (c) 1999-2001 Ethan Galstad <nagios@nagios.org>\n");
533         printf (COPYRIGHT, copyright, email);
535         printf(_("This plugin will attempt to open an SMTP connection with the host.\n\n"));
537         print_usage ();
539         printf (_(UT_HELP_VRSN));
541         printf (_(UT_HOST_PORT), 'p', myport);
543         printf (_(UT_IPv46));
545         printf (_("\
546  -e, --expect=STRING\n\
547    String to expect in first line of server response (default: '%s')\n\
548  -n, nocommand\n\
549    Suppress SMTP command\n\
550  -C, --command=STRING\n\
551    SMTP command (may be used repeatedly)\n\
552  -R, --command=STRING\n\
553    Expected response to command (may be used repeatedly)\n\
554  -f, --from=STRING\n\
555    FROM-address to include in MAIL command, required by Exchange 2000\n"),
556                 SMTP_EXPECT);
557 #ifdef HAVE_SSL
558         printf (_("\
559  -D, --certificate=INTEGER\n\
560     Minimum number of days a certificate has to be valid.\n\
561  -S, --starttls\n\
562     Use STARTTLS for the connection.\n"));
563 #endif
565         printf (_(UT_WARN_CRIT));
567         printf (_(UT_TIMEOUT), DEFAULT_SOCKET_TIMEOUT);
569         printf (_(UT_VERBOSE));
571         printf(_("\n\
572 Successul connects return STATE_OK, refusals and timeouts return\n\
573 STATE_CRITICAL, other errors return STATE_UNKNOWN.  Successful\n\
574 connects, but incorrect reponse messages from the host result in\n\
575 STATE_WARNING return values.\n"));
577         printf (_(UT_SUPPORT));
582 void
583 print_usage (void)
585         printf ("\
586 Usage: %s -H host [-p port] [-e expect] [-C command] [-f from addr]\n\
587                   [-w warn] [-c crit] [-t timeout] [-S] [-D days] [-n] [-v] [-4|-6]\n", progname);
590 int 
591 my_close (void)
593 #ifdef HAVE_SSL
594         np_net_ssl_cleanup();
595 #endif
596         return close(sd);