79767242567f6757396bfa2269e6722b17d7545e
[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 /**
88 * Maximum number of expiration dates we store (for subject + IM CAs + CA)
89 */
90 #define MAX_TRUSTCHAIN_LENGTH 7
91
92 /**
93 * Hashtable entry
94 */
95 typedef struct {
96 /** certificate subject as subjectAltName or CN of a DN */
97 char id[128];
98 /** list of expiration dates, 0 if no certificate */
99 time_t expire[MAX_TRUSTCHAIN_LENGTH];
100 } entry_t;
101
102 /**
103 * Hashtable hash function
104 */
105 static u_int hash(entry_t *key)
106 {
107 return chunk_hash(chunk_create(key->id, strlen(key->id)));
108 }
109
110 /**
111 * Hashtable equals function
112 */
113 static bool equals(entry_t *a, entry_t *b)
114 {
115 return streq(a->id, b->id);
116 }
117
118 /**
119 * Export a single trustchain to a path
120 */
121 static void export_csv(private_certexpire_export_t *this, char *path,
122 hashtable_t *chains)
123 {
124 enumerator_t *enumerator;
125 char buf[PATH_MAX];
126 entry_t *entry;
127 FILE *file;
128 struct tm tm;
129 time_t t;
130 int i;
131
132 t = time(NULL);
133 localtime_r(&t, &tm);
134
135 strftime(buf, sizeof(buf), path, &tm);
136 file = fopen(buf, "a");
137 if (file)
138 {
139 DBG1(DBG_CFG, "exporting expiration dates of %d trustchain%s to '%s'",
140 chains->get_count(chains),
141 chains->get_count(chains) == 1 ? "" : "s", buf);
142 this->mutex->lock(this->mutex);
143 enumerator = chains->create_enumerator(chains);
144 while (enumerator->enumerate(enumerator, NULL, &entry))
145 {
146 fprintf(file, "%s%s", entry->id, this->separator);
147 for (i = 0; i < MAX_TRUSTCHAIN_LENGTH; i++)
148 {
149 if (entry->expire[i])
150 {
151 localtime_r(&entry->expire[i], &tm);
152 strftime(buf, sizeof(buf), this->format, &tm);
153 fprintf(file, "%s", buf);
154 }
155 if (i == MAX_TRUSTCHAIN_LENGTH - 1)
156 {
157 fprintf(file, "\n");
158 }
159 else if (this->fixed_fields || entry->expire[i])
160 {
161 fprintf(file, "%s", this->separator);
162 }
163 }
164 chains->remove_at(chains, enumerator);
165 free(entry);
166 }
167 enumerator->destroy(enumerator);
168 this->mutex->unlock(this->mutex);
169 fclose(file);
170 }
171 else
172 {
173 DBG1(DBG_CFG, "opening CSV file '%s' failed: %s", buf, strerror(errno));
174 }
175 }
176
177 /**
178 * Export cached trustchain expiration dates to CSV files
179 */
180 static void cron_export(private_certexpire_export_t *this)
181 {
182 if (this->local_path)
183 {
184 export_csv(this, this->local_path, this->local);
185 }
186 if (this->remote_path)
187 {
188 export_csv(this, this->remote_path, this->remote);
189 }
190 }
191
192 METHOD(certexpire_export_t, add, void,
193 private_certexpire_export_t *this, linked_list_t *trustchain, bool local)
194 {
195 enumerator_t *enumerator;
196 certificate_t *cert;
197 int count;
198
199 /* don't store expiration dates if no path configured */
200 if (local)
201 {
202 if (!this->local_path)
203 {
204 return;
205 }
206 }
207 else
208 {
209 if (!this->remote_path)
210 {
211 return;
212 }
213 }
214
215 count = min(trustchain->get_count(trustchain), MAX_TRUSTCHAIN_LENGTH) - 1;
216
217 enumerator = trustchain->create_enumerator(trustchain);
218 /* get subject cert */
219 if (enumerator->enumerate(enumerator, &cert))
220 {
221 identification_t *id;
222 entry_t *entry;
223 int i;
224
225 INIT(entry);
226
227 /* prefer FQDN subjectAltName... */
228 if (cert->get_type(cert) == CERT_X509)
229 {
230 x509_t *x509 = (x509_t*)cert;
231 enumerator_t *sans;
232
233 sans = x509->create_subjectAltName_enumerator(x509);
234 while (sans->enumerate(sans, &id))
235 {
236 if (id->get_type(id) == ID_FQDN)
237 {
238 snprintf(entry->id, sizeof(entry->id), "%Y", id);
239 break;
240 }
241 }
242 sans->destroy(sans);
243 }
244 /* fallback to CN of DN */
245 if (!entry->id[0])
246 {
247 enumerator_t *parts;
248 id_part_t part;
249 chunk_t data;
250
251 id = cert->get_subject(cert);
252 parts = id->create_part_enumerator(id);
253 while (parts->enumerate(parts, &part, &data))
254 {
255 if (part == ID_PART_RDN_CN)
256 {
257 snprintf(entry->id, sizeof(entry->id), "%.*s",
258 (int)data.len, data.ptr);
259 break;
260 }
261 }
262 parts->destroy(parts);
263 }
264 /* no usable identity? skip */
265 if (!entry->id[0])
266 {
267 enumerator->destroy(enumerator);
268 free(entry);
269 return;
270 }
271
272 /* get intermediate CA expiration dates */
273 cert->get_validity(cert, NULL, NULL, &entry->expire[0]);
274 for (i = 1; i < count && enumerator->enumerate(enumerator, &cert); i++)
275 {
276 cert->get_validity(cert, NULL, NULL, &entry->expire[i]);
277 }
278 /* get CA expiration date, as last array entry */
279 if (enumerator->enumerate(enumerator, &cert))
280 {
281 cert->get_validity(cert, NULL, NULL,
282 &entry->expire[MAX_TRUSTCHAIN_LENGTH - 1]);
283 }
284 this->mutex->lock(this->mutex);
285 if (local)
286 {
287 entry = this->local->put(this->local, entry, entry);
288 }
289 else
290 {
291 entry = this->remote->put(this->remote, entry, entry);
292 }
293 this->mutex->unlock(this->mutex);
294 if (entry)
295 {
296 free(entry);
297 }
298 if (!this->cron)
299 { /* export directly if no cron job defined */
300 if (local)
301 {
302 export_csv(this, this->local_path, this->local);
303 }
304 else
305 {
306 export_csv(this, this->remote_path, this->remote);
307 }
308 }
309 }
310 enumerator->destroy(enumerator);
311 }
312
313 METHOD(certexpire_export_t, destroy, void,
314 private_certexpire_export_t *this)
315 {
316 entry_t *key, *value;
317 enumerator_t *enumerator;
318
319 enumerator = this->local->create_enumerator(this->local);
320 while (enumerator->enumerate(enumerator, &key, &value))
321 {
322 free(value);
323 }
324 enumerator->destroy(enumerator);
325 enumerator = this->remote->create_enumerator(this->remote);
326 while (enumerator->enumerate(enumerator, &key, &value))
327 {
328 free(value);
329 }
330 enumerator->destroy(enumerator);
331
332 this->local->destroy(this->local);
333 this->remote->destroy(this->remote);
334 DESTROY_IF(this->cron);
335 this->mutex->destroy(this->mutex);
336 free(this);
337 }
338
339 /**
340 * See header
341 */
342 certexpire_export_t *certexpire_export_create()
343 {
344 private_certexpire_export_t *this;
345 char *cron;
346
347 INIT(this,
348 .public = {
349 .add = _add,
350 .destroy = _destroy,
351 },
352 .local = hashtable_create((hashtable_hash_t)hash,
353 (hashtable_equals_t)equals, 4),
354 .remote = hashtable_create((hashtable_hash_t)hash,
355 (hashtable_equals_t)equals, 32),
356 .mutex = mutex_create(MUTEX_TYPE_DEFAULT),
357 .local_path = lib->settings->get_str(lib->settings,
358 "charon.plugins.certexpire.csv.local", NULL),
359 .remote_path = lib->settings->get_str(lib->settings,
360 "charon.plugins.certexpire.csv.remote", NULL),
361 .separator = lib->settings->get_str(lib->settings,
362 "charon.plugins.certexpire.csv.separator", ","),
363 .format = lib->settings->get_str(lib->settings,
364 "charon.plugins.certexpire.csv.format", "%d:%m:%Y"),
365 .fixed_fields = lib->settings->get_bool(lib->settings,
366 "charon.plugins.certexpire.csv.fixed_fields", TRUE),
367 );
368
369 cron = lib->settings->get_str(lib->settings,
370 "charon.plugins.certexpire.csv.cron", NULL);
371 if (cron)
372 {
373 this->cron = certexpire_cron_create(cron,
374 (certexpire_cron_job_t)cron_export, this);
375 }
376 return &this->public;
377 }