Checking code reworked to be more robust.
[cert-checker.git] / main.c
1 /******************************************************************************
2  * Filename: main.c
3  * Description:
4  *
5  * Version: 1.0
6  * Created: Oct 09 2010 19:14:12
7  * Last modified: Oct 09 2010 19:14:12
8  *
9  * Author: Ladislav L├íska
10  * e-mail: ladislav.laska@gmail.com
11  *
12  ******************************************************************************/
13
14 #define _XOPEN_SOURCE 500
15
16 #include <gnutls/gnutls.h>
17 #include <gnutls/x509.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <sys/types.h>
21 #include <sys/socket.h>
22 #include <netdb.h>
23 #include <string.h>
24 #include <unistd.h>
25 #include <time.h>
26 #include <signal.h>
27
28 #include "smtp.h"
29 #include "main.h"
30
31 int warning_after = 30;
32 int error_after = 7;
33 int verbose = 0;
34 int timeout = 9;
35 int use_starttls = 0;
36
37 enum {
38         P_SMTP = 1,
39         P_IMAP = 2
40 };
41
42 #define LOG_LEVEL 0
43
44 char errmsg[256];
45
46 void print_help();
47
48 int tcp_open( char *hostname, char *service ) {
49         struct addrinfo hints;
50         struct addrinfo *result, *result_ptr;
51         int err, sfd;
52
53         /* Set hints */
54         memset(&hints, 0, sizeof(struct addrinfo));
55         hints.ai_family = AF_UNSPEC;
56         hints.ai_socktype = SOCK_STREAM;
57         err = getaddrinfo(hostname, service, &hints, &result);
58         if (err) {
59                 fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(err));
60                 exit(3);
61         }
62
63         for (result_ptr = result; result_ptr != NULL; result_ptr = result_ptr->ai_next) {
64                 sfd = socket(result_ptr->ai_family, result_ptr->ai_socktype,
65                                                 result_ptr->ai_protocol);
66                 if (sfd == -1) continue; 
67
68                 if (connect(sfd, result_ptr->ai_addr, result_ptr->ai_addrlen) != -1)
69                         break; /* Success */
70                 
71                 close(sfd);     /* connect(2) failed. */
72         }
73
74         if (result_ptr == NULL) {
75                 /* No address succeeded */
76                 return -1;
77         }
78
79         freeaddrinfo(result);
80
81         return sfd;
82 }
83
84 int check( char * hostname, char *service ) {
85         int state = S_OK;
86         int err;
87
88         gnutls_session_t session;
89         gnutls_certificate_credentials_t xcred;
90
91         /* x509 stuff */
92         
93         err = gnutls_certificate_allocate_credentials( &xcred );
94         if (err < 0) gnutls_die(err);
95
96         err = gnutls_init( &session, GNUTLS_CLIENT );
97         if (err < 0) gnutls_die(err);
98
99         /* priority init? */
100         err = gnutls_priority_set_direct(session, "EXPORT", NULL);
101         if (err < 0) gnutls_die(err);
102
103         err = gnutls_credentials_set( session, GNUTLS_CRD_CERTIFICATE, xcred );
104         if (err < 0) gnutls_die(err);
105
106         /* Connect to server */
107
108         long fd = tcp_open( hostname, service );
109         
110         if (fd == -1) {
111                 state= S_UNREACHABLE;
112                 goto cleanup;
113         }
114
115         /* StartTLS? */
116         int starttls_ok = 1;
117         switch (use_starttls) {
118                 case P_SMTP:
119                         starttls_ok = smtp_starttls(fd);
120                         break;
121                 case P_IMAP:
122                         die("STARTTLS for IMAP not implemented yet.");
123                         break;
124                 case 0:
125                         /* Don't use STARTTLS */
126                         starttls_ok = 0;
127                         break;
128                 default:
129                         die("Unknown STARTTLS protocol requested.");
130         }
131
132         if (starttls_ok)
133                 die("STARTTLS failed silently. This shouldn't happen.");
134
135         /* Socket opened, establish tls connection */
136
137         /* Associate socket with session */
138         gnutls_transport_set_ptr( session, (gnutls_transport_ptr_t) fd );
139         
140         /* Do handshake */
141         err = gnutls_handshake( session );
142         if (err < 0) gnutls_die(err);
143         
144         /* Get server certificate. */
145         const gnutls_datum_t *cert_list;
146         unsigned int cert_list_size = 0;
147         time_t expiration_time, today;
148         gnutls_x509_crt_t cert;
149
150         if ( gnutls_certificate_type_get( session ) != GNUTLS_CRT_X509 ) {
151                 state = S_NO_X509;
152                 goto cleanup;
153         }
154
155         cert_list = gnutls_certificate_get_peers( session, &cert_list_size );
156
157         today = time(NULL);
158         
159         for (int i = 0; i < cert_list_size; i++) {
160                 gnutls_x509_crt_init( &cert );
161                 gnutls_x509_crt_import( cert, &cert_list[0], GNUTLS_X509_FMT_DER );
162                 expiration_time = gnutls_x509_crt_get_expiration_time( cert );
163                 int expires_in = (expiration_time - today) / 86400;
164                 struct tm * t = gmtime( &expiration_time );
165                 if ((state == S_OK) && (expires_in <= warning_after)) {
166                         state = S_WARNING;
167                         sprintf(errmsg, "Warning - Will expire in %i days (%i-%02i-%02i).", expires_in, 
168                                 t->tm_year+1900, t->tm_mon+1, t->tm_mday );
169                 }
170                 if ((state <= S_WARNING) && (expires_in <= error_after)) {
171                         state = S_ERROR;
172                         sprintf(errmsg, "Critical - Will expire in %i days (%i-%02i-%02i).", expires_in,
173                                 t->tm_year+1900, t->tm_mon+1, t->tm_mday );
174                 }
175                 if (state == S_OK) {
176                         sprintf(errmsg, "OK - Will expire in %i days (%i-%02i-%02i).", expires_in,
177                                 t->tm_year+1900, t->tm_mon+1, t->tm_mday );
178                 }
179         }
180
181         /* Clean up */
182         
183         /* This could use some other parameter. */
184         switch (use_starttls) {
185                 case P_SMTP:
186                         smtp_quit(session);
187                         break;
188                 case P_IMAP:
189                         die("IMAP not implemented yet.");
190                         break;
191                 default:;
192         }
193
194 //      err = gnutls_bye( session, GNUTLS_SHUT_WR );
195 //      if (err < 0) gnutls_die(err);
196 //      close( fd );
197 cleanup:
198         gnutls_deinit( session );
199         gnutls_certificate_free_credentials( xcred );
200
201         return state;
202 }
203
204 void log_func( int level, char *msg ) {
205         fprintf(stderr, "[%2i] %s", level, msg);
206 }
207
208 /* 
209  * This signal handler is wrong, but it's just a failsafe. 
210  */
211 void sig_handler(int k) {
212         fputs("Timeout.\n", stderr);    
213         exit(S_ERROR);
214 }
215
216 int main(int argc, char **argv) {
217         char *hostname = NULL;
218         char *service = NULL;
219
220         int opt;
221
222         while ((opt = getopt(argc, argv, "hvSIw:c:H:p:s:t:")) != -1) {
223                 switch (opt) {
224                         case 'w':
225                                 warning_after = atoi(optarg);
226                                 break;
227                         case 'c':
228                                 error_after = atoi(optarg);
229                                 break;
230                         case 'H':
231                                 hostname = strdup(optarg);
232                                 break;
233                         case 't':
234                                 timeout = atoi( optarg );
235                                 if (timeout < 0) die("Timeout must be >= 0"); 
236                                 break;
237                         case 'p':                       
238                         case 's':
239                                 if (service != NULL) die("Only one service can be specified.");
240                                 service = strdup(optarg);
241                                 break;
242                         case 'h':
243                                 print_help();
244                                 exit(0);
245                         case 'v':
246                                 verbose++;
247                                 break;
248                         case 'S':
249                                 use_starttls = P_SMTP;
250                                 break;
251                         case 'I':
252                                 use_starttls = P_IMAP;
253                                 break;
254                         default: break;
255                 }
256         }
257
258         if (!hostname) die("No address to try.");
259         if (!service) die("No service given.");
260
261         gnutls_global_set_log_function((gnutls_log_func) log_func);
262         gnutls_global_set_log_level(LOG_LEVEL);
263
264
265         /* Initialize gnutls */
266         int err;
267         if ((err = gnutls_global_init())) {
268                 gnutls_perror(err);
269                 exit(S_UNKNOWN);
270         };
271
272         sprintf(errmsg, "OK");
273         int state = 0;
274
275         fflush(stdout);
276
277         /* Setup alarm */
278         /* TODO: doesn't work. */
279         signal(SIGALRM, sig_handler);
280         alarm( timeout );
281
282         /* Do checking */
283         state = check(hostname, service);
284         if (state < 0) {
285                 switch (state) {
286                         case S_UNREACHABLE:
287                                 state = S_ERROR;
288                                 sprintf(errmsg, "Connection refused.");
289                         break;
290                         case S_NO_X509:
291                                 state = S_UNKNOWN;
292                                 sprintf(errmsg, "No X509 certificate to check.");
293                         default:
294                                 sprintf(errmsg, "Internal error %i.", state);
295                                 state = S_UNKNOWN;
296                 }
297         }
298         
299         gnutls_global_deinit();
300
301         free(hostname);
302         free(service);
303
304         printf("%s\n", errmsg);
305         return (state < 0) ? 127 : state;
306 }
307
308 void print_help() {
309         printf(
310                 "Usage: cert-checker [options] -H hostname -p|s port|service\n"
311                 "  Where options could be: \n"
312                 "       -h            this help\n"
313                 "       -H hostname   target hostname\n"
314                 "       -s|p service  port or service (as in /etc/services)\n"
315                 "       -w n          warning level (in days, default 30)\n"
316                 "       -c n          critical level (in days, default 7)\n"
317                 "       -v            verbosity level\n"        
318                 "       -t n          timeout (in seconds, n=0 disables timeout\n"
319                 "       -S            use SMTP/STARTLS\n"
320                 "       -I            use IMAP/STARTLS\n"
321         );
322 }