Basic support for SMTP STARTTLS.
[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 #define S_UNREACHABLE -1
29 #define S_NO_X509 -2
30 #define S_OK 0
31 #define S_WARNING 1
32 #define S_ERROR 2
33 #define S_UNKNOWN 3
34
35 int warning_after = 30;
36 int error_after = 7;
37 int verbose = 0;
38 int timeout = 9;
39 int use_starttls = 0;
40
41 #define BUFFER_SIZE 1024
42
43 #define LOG_LEVEL 0
44
45 #define die(msg) { fprintf(stderr, "Error: " msg "\n" ); exit(3); }
46 #define dief(msg, ...) { fprintf(stderr, "Error: " msg "\n", __VA_ARGS__ ); exit(3); }
47 #define gnutls_die(code) { gnutls_perror(code); exit(3); }
48
49 char errmsg[256];
50
51 void print_help();
52
53 int tcp_open( char *hostname, char *service ) {
54         struct addrinfo hints;
55         struct addrinfo *result, *result_ptr;
56         int err, sfd;
57
58         /* Set hints */
59         memset(&hints, 0, sizeof(struct addrinfo));
60         hints.ai_family = AF_UNSPEC;
61         hints.ai_socktype = SOCK_STREAM;
62         err = getaddrinfo(hostname, service, &hints, &result);
63         if (err) {
64                 fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(err));
65                 exit(3);
66         }
67
68         for (result_ptr = result; result_ptr != NULL; result_ptr = result_ptr->ai_next) {
69                 sfd = socket(result_ptr->ai_family, result_ptr->ai_socktype,
70                                                 result_ptr->ai_protocol);
71                 if (sfd == -1) continue; 
72
73                 if (connect(sfd, result_ptr->ai_addr, result_ptr->ai_addrlen) != -1)
74                         break; /* Success */
75                 
76                 close(sfd);     /* connect(2) failed. */
77         }
78
79         if (result_ptr == NULL) {
80                 /* No address succeeded */
81                 return -1;
82         }
83
84         freeaddrinfo(result);
85
86         return sfd;
87 }
88
89 /* Expects some data. This is just wrong and should be written correctly. */
90 char *expect(int fd, char *str) {
91         char buffer[BUFFER_SIZE];
92         int len = strlen(str);
93         int bytes = read(fd, buffer, BUFFER_SIZE);
94         if (bytes < len) goto fail;
95         if (!strncmp(buffer,str,len)) {
96                 while (bytes == BUFFER_SIZE)
97                         bytes = read(fd, buffer, BUFFER_SIZE) > 0;
98                 return NULL;
99         }
100
101         fail:;
102         char *ptr = strdup(buffer);
103         while (bytes == BUFFER_SIZE)
104                 bytes = read(fd, buffer, BUFFER_SIZE) > 0;
105         return ptr;
106 }
107
108 int check( char * hostname, char *service ) {
109         int state = S_OK;
110         int err;
111
112         gnutls_session_t session;
113         gnutls_certificate_credentials_t xcred;
114
115         /* x509 stuff */
116         
117         err = gnutls_certificate_allocate_credentials( &xcred );
118         if (err < 0) gnutls_die(err);
119
120         err = gnutls_init( &session, GNUTLS_CLIENT );
121         if (err < 0) gnutls_die(err);
122
123         /* priority init? */
124         err = gnutls_priority_set_direct(session, "EXPORT", NULL);
125         if (err < 0) gnutls_die(err);
126
127         err = gnutls_credentials_set( session, GNUTLS_CRD_CERTIFICATE, xcred );
128         if (err < 0) gnutls_die(err);
129
130         /* Connect to server */
131
132         long fd = tcp_open( hostname, service );
133         
134         if (fd == -1) {
135                 state= S_UNREACHABLE;
136                 goto cleanup;
137         }
138
139
140         /* StartTLS? */
141
142         if (use_starttls) {
143                 expect(fd, "");
144                 char buffer[] = "STARTTLS\n";
145                 write(fd, buffer, sizeof(buffer));
146                 char *b;
147                 if ((b = expect(fd, "220 "))) {
148                         dief("STARTTLS declined: %s.", b);
149                 }
150         }
151
152
153         /* Socket opened, establish tls connection */
154
155         /* Associate socket with session */
156         gnutls_transport_set_ptr( session, (gnutls_transport_ptr_t) fd );
157         
158         /* Do handshake */
159         err = gnutls_handshake( session );
160         if (err < 0) gnutls_die(err);
161         
162         /* Get server certificate. */
163         const gnutls_datum_t *cert_list;
164         unsigned int cert_list_size = 0;
165         time_t expiration_time, today;
166         gnutls_x509_crt_t cert;
167
168         if ( gnutls_certificate_type_get( session ) != GNUTLS_CRT_X509 ) {
169                 state = S_NO_X509;
170                 goto cleanup;
171         }
172
173         cert_list = gnutls_certificate_get_peers( session, &cert_list_size );
174
175         today = time(NULL);
176
177         for (int i = 0; i < cert_list_size; i++) {
178                 gnutls_x509_crt_init( &cert );
179                 gnutls_x509_crt_import( cert, &cert_list[0], GNUTLS_X509_FMT_DER );
180                 expiration_time = gnutls_x509_crt_get_expiration_time( cert );
181                 int expires_in = (expiration_time - today) / 86400;
182                 struct tm * t = gmtime( &expiration_time );
183                 if ((state == S_OK) && (expires_in <= warning_after)) {
184                         state = S_WARNING;
185                         sprintf(errmsg, "Warning - Will expire in %i days (%i-%02i-%02i).", expires_in, 
186                                 t->tm_year+1900, t->tm_mon+1, t->tm_mday );
187                 }
188                 if ((state <= S_WARNING) && (expires_in <= error_after)) {
189                         state = S_ERROR;
190                         sprintf(errmsg, "Critical - Will expire in %i days (%i-%02i-%02i).", expires_in,
191                                 t->tm_year+1900, t->tm_mon+1, t->tm_mday );
192                 }
193                 if (state == S_OK) {
194                         sprintf(errmsg, "OK - Will expire in %i days (%i-%02i-%02i).", expires_in,
195                                 t->tm_year+1900, t->tm_mon+1, t->tm_mday );
196                 }
197         }
198
199         /* Clean up */
200         err = gnutls_bye( session, GNUTLS_SHUT_WR );
201         if (err < 0) gnutls_die(err);
202         close( fd );
203         cleanup:
204         gnutls_deinit( session );
205         gnutls_certificate_free_credentials( xcred );
206
207         return state;
208 }
209
210 void log_func( int level, char *msg ) {
211         fprintf(stderr, "[%2i] %s", level, msg);
212 }
213
214 /* 
215  * This signal handler is wrong, but it's just a failsafe. 
216  */
217 void sig_handler(int k) {
218         fputs("Timeout.\n", stderr);    
219         exit(S_UNKNOWN);
220 }
221
222 int main(int argc, char **argv) {
223         char *hostname;
224         char *service = NULL;
225
226         int opt;
227
228         while ((opt = getopt(argc, argv, "hvSw:c:H:p:s:t:")) != -1) {
229                 switch (opt) {
230                         case 'w':
231                                 warning_after = atoi(optarg);
232                                 break;
233                         case 'c':
234                                 error_after = atoi(optarg);
235                                 break;
236                         case 'H':
237                                 hostname = strdup(optarg);
238                                 break;
239                         case 't':
240                                 timeout = atoi( optarg );
241                                 if (timeout < 0) die("Timeout must be >= 0"); 
242                                 break;
243                         case 'p':                       
244                         case 's':
245                                 if (service != NULL) die("Only one service can be specified.");
246                                 service = strdup(optarg);
247                                 break;
248                         case 'h':
249                                 print_help();
250                                 exit(0);
251                         case 'v':
252                                 verbose++;
253                         case 'S':
254                                 use_starttls = 1;
255                         default: break;
256                 }
257         }
258
259         if (argc <= 1) die("No address to try.");
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(3);
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                 sprintf(errmsg, "Internal error %i.", state);
286                 state = S_UNKNOWN;
287         }
288         
289         gnutls_global_deinit();
290
291         free(hostname);
292         free(service);
293
294         printf("%s\n", errmsg);
295         return (state < 0) ? 127 : state;
296 }
297
298 void print_help() {
299         printf(
300                 "Usage: cert-checker [options] -H hostname -p|s port|service\n"
301                 "  Where options could be: \n"
302                 "       -h hostname   this help\n"
303                 "       -w n          warning level (in days, default 30)\n"
304                 "       -c n          critical level (in days, default 7)\n"
305                 "       -v            verbosity level\n"        
306                 "       -t n          timeout (in seconds, n=0 disables timeout\n"
307                 "               -S            use SMTP/STARTLS\n"
308         );
309 }