Added a certexpire empty_string option
[strongswan.git] / src / libcharon / plugins / certexpire / certexpire_export.c
1 /*
2 * Copyright (C) 2011 Martin Willi
3 * Copyright (C) 2011 revosec AG
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 */
15
16 #include "certexpire_export.h"
17
18 #include "certexpire_cron.h"
19
20 #include <time.h>
21 #include <limits.h>
22 #include <errno.h>
23
24 #include <debug.h>
25 #include <utils/hashtable.h>
26 #include <threading/mutex.h>
27 #include <credentials/certificates/x509.h>
28
29 typedef struct private_certexpire_export_t private_certexpire_export_t;
30
31 /**
32 * Private data of an certexpire_export_t object.
33 */
34 struct private_certexpire_export_t {
35
36 /**
37 * Public certexpire_export_t interface.
38 */
39 certexpire_export_t public;
40
41 /**
42 * hashtable caching local trustchains, mapping entry_t => entry_t
43 */
44 hashtable_t *local;
45
46 /**
47 * hashtable caching remote trustchains, mapping entry_t => entry_t
48 */
49 hashtable_t *remote;
50
51 /**
52 * Mutex to lock hashtables
53 */
54 mutex_t *mutex;
55
56 /**
57 * Cronjob for export
58 */
59 certexpire_cron_t *cron;
60
61 /**
62 * strftime() format to generate local CSV file
63 */
64 char *local_path;
65
66 /**
67 * strftime() format to generate remote CSV file
68 */
69 char *remote_path;
70
71 /**
72 * stftime() format of the exported expiration date
73 */
74 char *format;
75
76 /**
77 * CSV field separator
78 */
79 char *separator;
80
81 /**
82 * TRUE to use fixed field count, CA at end
83 */
84 bool fixed_fields;
85
86 /**
87 * String to use in empty fields, if using fixed_fields
88 */
89 char *empty_string;
90 };
91
92 /**
93 * Maximum number of expiration dates we store (for subject + IM CAs + CA)
94 */
95 #define MAX_TRUSTCHAIN_LENGTH 7
96
97 /**
98 * Hashtable entry
99 */
100 typedef struct {
101 /** certificate subject as subjectAltName or CN of a DN */
102 char id[128];
103 /** list of expiration dates, 0 if no certificate */
104 time_t expire[MAX_TRUSTCHAIN_LENGTH];
105 } entry_t;
106
107 /**
108 * Hashtable hash function
109 */
110 static u_int hash(entry_t *key)
111 {
112 return chunk_hash(chunk_create(key->id, strlen(key->id)));
113 }
114
115 /**
116 * Hashtable equals function
117 */
118 static bool equals(entry_t *a, entry_t *b)
119 {
120 return streq(a->id, b->id);
121 }
122
123 /**
124 * Export a single trustchain to a path
125 */
126 static void export_csv(private_certexpire_export_t *this, char *path,
127 hashtable_t *chains)
128 {
129 enumerator_t *enumerator;
130 char buf[PATH_MAX];
131 entry_t *entry;
132 FILE *file;
133 struct tm tm;
134 time_t t;
135 int i;
136
137 t = time(NULL);
138 localtime_r(&t, &tm);
139
140 strftime(buf, sizeof(buf), path, &tm);
141 file = fopen(buf, "a");
142 if (file)
143 {
144 DBG1(DBG_CFG, "exporting expiration dates of %d trustchain%s to '%s'",
145 chains->get_count(chains),
146 chains->get_count(chains) == 1 ? "" : "s", buf);
147 this->mutex->lock(this->mutex);
148 enumerator = chains->create_enumerator(chains);
149 while (enumerator->enumerate(enumerator, NULL, &entry))
150 {
151 fprintf(file, "%s%s", entry->id, this->separator);
152 for (i = 0; i < MAX_TRUSTCHAIN_LENGTH; i++)
153 {
154 if (entry->expire[i])
155 {
156 localtime_r(&entry->expire[i], &tm);
157 strftime(buf, sizeof(buf), this->format, &tm);
158 fprintf(file, "%s", buf);
159 }
160 if (i == MAX_TRUSTCHAIN_LENGTH - 1)
161 {
162 fprintf(file, "\n");
163 }
164 else if (entry->expire[i])
165 {
166 fprintf(file, "%s", this->separator);
167 }
168 else if (this->fixed_fields)
169 {
170 fprintf(file, "%s%s", this->empty_string, this->separator);
171 }
172 }
173 chains->remove_at(chains, enumerator);
174 free(entry);
175 }
176 enumerator->destroy(enumerator);
177 this->mutex->unlock(this->mutex);
178 fclose(file);
179 }
180 else
181 {
182 DBG1(DBG_CFG, "opening CSV file '%s' failed: %s", buf, strerror(errno));
183 }
184 }
185
186 /**
187 * Export cached trustchain expiration dates to CSV files
188 */
189 static void cron_export(private_certexpire_export_t *this)
190 {
191 if (this->local_path)
192 {
193 export_csv(this, this->local_path, this->local);
194 }
195 if (this->remote_path)
196 {
197 export_csv(this, this->remote_path, this->remote);
198 }
199 }
200
201 METHOD(certexpire_export_t, add, void,
202 private_certexpire_export_t *this, linked_list_t *trustchain, bool local)
203 {
204 enumerator_t *enumerator;
205 certificate_t *cert;
206 int count;
207
208 /* don't store expiration dates if no path configured */
209 if (local)
210 {
211 if (!this->local_path)
212 {
213 return;
214 }
215 }
216 else
217 {
218 if (!this->remote_path)
219 {
220 return;
221 }
222 }
223
224 count = min(trustchain->get_count(trustchain), MAX_TRUSTCHAIN_LENGTH) - 1;
225
226 enumerator = trustchain->create_enumerator(trustchain);
227 /* get subject cert */
228 if (enumerator->enumerate(enumerator, &cert))
229 {
230 identification_t *id;
231 entry_t *entry;
232 int i;
233
234 INIT(entry);
235
236 /* prefer FQDN subjectAltName... */
237 if (cert->get_type(cert) == CERT_X509)
238 {
239 x509_t *x509 = (x509_t*)cert;
240 enumerator_t *sans;
241
242 sans = x509->create_subjectAltName_enumerator(x509);
243 while (sans->enumerate(sans, &id))
244 {
245 if (id->get_type(id) == ID_FQDN)
246 {
247 snprintf(entry->id, sizeof(entry->id), "%Y", id);
248 break;
249 }
250 }
251 sans->destroy(sans);
252 }
253 /* fallback to CN of DN */
254 if (!entry->id[0])
255 {
256 enumerator_t *parts;
257 id_part_t part;
258 chunk_t data;
259
260 id = cert->get_subject(cert);
261 parts = id->create_part_enumerator(id);
262 while (parts->enumerate(parts, &part, &data))
263 {
264 if (part == ID_PART_RDN_CN)
265 {
266 snprintf(entry->id, sizeof(entry->id), "%.*s",
267 (int)data.len, data.ptr);
268 break;
269 }
270 }
271 parts->destroy(parts);
272 }
273 /* no usable identity? skip */
274 if (!entry->id[0])
275 {
276 enumerator->destroy(enumerator);
277 free(entry);
278 return;
279 }
280
281 /* get intermediate CA expiration dates */
282 cert->get_validity(cert, NULL, NULL, &entry->expire[0]);
283 for (i = 1; i < count && enumerator->enumerate(enumerator, &cert); i++)
284 {
285 cert->get_validity(cert, NULL, NULL, &entry->expire[i]);
286 }
287 /* get CA expiration date, as last array entry */
288 if (enumerator->enumerate(enumerator, &cert))
289 {
290 cert->get_validity(cert, NULL, NULL,
291 &entry->expire[MAX_TRUSTCHAIN_LENGTH - 1]);
292 }
293 this->mutex->lock(this->mutex);
294 if (local)
295 {
296 entry = this->local->put(this->local, entry, entry);
297 }
298 else
299 {
300 entry = this->remote->put(this->remote, entry, entry);
301 }
302 this->mutex->unlock(this->mutex);
303 if (entry)
304 {
305 free(entry);
306 }
307 if (!this->cron)
308 { /* export directly if no cron job defined */
309 if (local)
310 {
311 export_csv(this, this->local_path, this->local);
312 }
313 else
314 {
315 export_csv(this, this->remote_path, this->remote);
316 }
317 }
318 }
319 enumerator->destroy(enumerator);
320 }
321
322 METHOD(certexpire_export_t, destroy, void,
323 private_certexpire_export_t *this)
324 {
325 entry_t *key, *value;
326 enumerator_t *enumerator;
327
328 enumerator = this->local->create_enumerator(this->local);
329 while (enumerator->enumerate(enumerator, &key, &value))
330 {
331 free(value);
332 }
333 enumerator->destroy(enumerator);
334 enumerator = this->remote->create_enumerator(this->remote);
335 while (enumerator->enumerate(enumerator, &key, &value))
336 {
337 free(value);
338 }
339 enumerator->destroy(enumerator);
340
341 this->local->destroy(this->local);
342 this->remote->destroy(this->remote);
343 DESTROY_IF(this->cron);
344 this->mutex->destroy(this->mutex);
345 free(this);
346 }
347
348 /**
349 * See header
350 */
351 certexpire_export_t *certexpire_export_create()
352 {
353 private_certexpire_export_t *this;
354 char *cron;
355
356 INIT(this,
357 .public = {
358 .add = _add,
359 .destroy = _destroy,
360 },
361 .local = hashtable_create((hashtable_hash_t)hash,
362 (hashtable_equals_t)equals, 4),
363 .remote = hashtable_create((hashtable_hash_t)hash,
364 (hashtable_equals_t)equals, 32),
365 .mutex = mutex_create(MUTEX_TYPE_DEFAULT),
366 .local_path = lib->settings->get_str(lib->settings,
367 "charon.plugins.certexpire.csv.local", NULL),
368 .remote_path = lib->settings->get_str(lib->settings,
369 "charon.plugins.certexpire.csv.remote", NULL),
370 .separator = lib->settings->get_str(lib->settings,
371 "charon.plugins.certexpire.csv.separator", ","),
372 .format = lib->settings->get_str(lib->settings,
373 "charon.plugins.certexpire.csv.format", "%d:%m:%Y"),
374 .fixed_fields = lib->settings->get_bool(lib->settings,
375 "charon.plugins.certexpire.csv.fixed_fields", TRUE),
376 .empty_string = lib->settings->get_str(lib->settings,
377 "charon.plugins.certexpire.csv.empty_string", ""),
378 );
379
380 cron = lib->settings->get_str(lib->settings,
381 "charon.plugins.certexpire.csv.cron", NULL);
382 if (cron)
383 {
384 this->cron = certexpire_cron_create(cron,
385 (certexpire_cron_job_t)cron_export, this);
386 }
387 return &this->public;
388 }