enumerating loaded plugins in "ipsec statusall"
[strongswan.git] / src / charon / plugins / stroke / stroke_list.c
1 /*
2 * Copyright (C) 2008 Martin Willi
3 * Hochschule fuer Technik Rapperswil
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 * $Id$
16 */
17
18 #include "stroke_list.h"
19
20 #include <daemon.h>
21 #include <utils/linked_list.h>
22 #include <credentials/certificates/x509.h>
23 #include <credentials/certificates/ac.h>
24 #include <credentials/certificates/crl.h>
25
26 /* warning intervals for list functions */
27 #define CERT_WARNING_INTERVAL 30 /* days */
28 #define CRL_WARNING_INTERVAL 7 /* days */
29 #define AC_WARNING_INTERVAL 1 /* day */
30
31 typedef struct private_stroke_list_t private_stroke_list_t;
32
33 /**
34 * private data of stroke_list
35 */
36 struct private_stroke_list_t {
37
38 /**
39 * public functions
40 */
41 stroke_list_t public;
42
43 /**
44 * timestamp of daemon start
45 */
46 time_t uptime;
47 };
48
49 /**
50 * log an IKE_SA to out
51 */
52 static void log_ike_sa(FILE *out, ike_sa_t *ike_sa, bool all)
53 {
54 ike_sa_id_t *id = ike_sa->get_id(ike_sa);
55
56 fprintf(out, "%12s[%d]: %N, %H[%D]...%H[%D]\n",
57 ike_sa->get_name(ike_sa), ike_sa->get_unique_id(ike_sa),
58 ike_sa_state_names, ike_sa->get_state(ike_sa),
59 ike_sa->get_my_host(ike_sa), ike_sa->get_my_id(ike_sa),
60 ike_sa->get_other_host(ike_sa), ike_sa->get_other_id(ike_sa));
61
62 if (all)
63 {
64 char *ike_proposal = ike_sa->get_proposal(ike_sa);
65
66 fprintf(out, "%12s[%d]: IKE SPIs: %.16llx_i%s %.16llx_r%s",
67 ike_sa->get_name(ike_sa), ike_sa->get_unique_id(ike_sa),
68 id->get_initiator_spi(id), id->is_initiator(id) ? "*" : "",
69 id->get_responder_spi(id), id->is_initiator(id) ? "" : "*");
70
71
72 if (ike_sa->get_state(ike_sa) == IKE_ESTABLISHED)
73 {
74 u_int32_t rekey = ike_sa->get_statistic(ike_sa, STAT_REKEY_TIME);
75 u_int32_t reauth = ike_sa->get_statistic(ike_sa, STAT_REAUTH_TIME);
76
77 if (rekey)
78 {
79 fprintf(out, ", rekeying in %V", &rekey);
80 }
81 if (reauth)
82 {
83 fprintf(out, ", reauthentication in %V", &reauth);
84 }
85 if (!rekey && !reauth)
86 {
87 fprintf(out, ", rekeying disabled");
88 }
89 }
90 fprintf(out, "\n");
91
92 if (ike_proposal)
93 {
94 fprintf(out, "%12s[%d]: IKE proposal: %s\n",
95 ike_sa->get_name(ike_sa), ike_sa->get_unique_id(ike_sa),
96 ike_proposal);
97 }
98 }
99 }
100
101 /**
102 * log an CHILD_SA to out
103 */
104 static void log_child_sa(FILE *out, child_sa_t *child_sa, bool all)
105 {
106 u_int32_t rekey, now = time(NULL);
107 u_int32_t use_in, use_out, use_fwd;
108 encryption_algorithm_t encr_alg;
109 integrity_algorithm_t int_alg;
110 size_t encr_len, int_len;
111 mode_t mode;
112
113 child_sa->get_stats(child_sa, &mode, &encr_alg, &encr_len,
114 &int_alg, &int_len, &rekey, &use_in, &use_out,
115 &use_fwd);
116
117 fprintf(out, "%12s{%d}: %N, %N",
118 child_sa->get_name(child_sa), child_sa->get_reqid(child_sa),
119 child_sa_state_names, child_sa->get_state(child_sa),
120 mode_names, mode);
121
122 if (child_sa->get_state(child_sa) == CHILD_INSTALLED)
123 {
124 fprintf(out, ", %N SPIs: %.8x_i %.8x_o",
125 protocol_id_names, child_sa->get_protocol(child_sa),
126 htonl(child_sa->get_spi(child_sa, TRUE)),
127 htonl(child_sa->get_spi(child_sa, FALSE)));
128
129 if (all)
130 {
131 fprintf(out, "\n%12s{%d}: ", child_sa->get_name(child_sa),
132 child_sa->get_reqid(child_sa));
133
134
135 if (child_sa->get_protocol(child_sa) == PROTO_ESP)
136 {
137 fprintf(out, "%N", encryption_algorithm_names, encr_alg);
138
139 if (encr_len)
140 {
141 fprintf(out, "-%d", encr_len);
142 }
143 if (int_alg != AUTH_UNDEFINED)
144 {
145 fprintf(out, "/");
146 }
147 }
148
149 if (int_alg != AUTH_UNDEFINED)
150 {
151 fprintf(out, "%N", integrity_algorithm_names, int_alg);
152 if (int_len)
153 {
154 fprintf(out, "-%d", int_len);
155 }
156 }
157 fprintf(out, ", rekeying ");
158
159 if (rekey)
160 {
161 fprintf(out, "in %#V", &now, &rekey);
162 }
163 else
164 {
165 fprintf(out, "disabled");
166 }
167
168 fprintf(out, ", last use: ");
169 use_in = max(use_in, use_fwd);
170 if (use_in)
171 {
172 fprintf(out, "%ds_i ", now - use_in);
173 }
174 else
175 {
176 fprintf(out, "no_i ");
177 }
178 if (use_out)
179 {
180 fprintf(out, "%ds_o ", now - use_out);
181 }
182 else
183 {
184 fprintf(out, "no_o ");
185 }
186 }
187 }
188
189 fprintf(out, "\n%12s{%d}: %#R=== %#R\n",
190 child_sa->get_name(child_sa), child_sa->get_reqid(child_sa),
191 child_sa->get_traffic_selectors(child_sa, TRUE),
192 child_sa->get_traffic_selectors(child_sa, FALSE));
193 }
194
195 /**
196 * Implementation of stroke_list_t.status.
197 */
198 static void status(private_stroke_list_t *this, stroke_msg_t *msg, FILE *out, bool all)
199 {
200 enumerator_t *enumerator, *children;
201 iterator_t *iterator;
202 host_t *host;
203 peer_cfg_t *peer_cfg;
204 ike_cfg_t *ike_cfg;
205 child_cfg_t *child_cfg;
206 ike_sa_t *ike_sa;
207 char *name = NULL, *plugin;
208 bool found = FALSE;
209 time_t uptime;
210
211 name = msg->status.name;
212
213 if (all)
214 {
215 uptime = time(NULL) - this->uptime;
216 fprintf(out, "Performance:\n");
217 fprintf(out, " uptime: %V, since %#T\n", &uptime, &this->uptime, FALSE);
218 fprintf(out, " worker threads: %d idle of %d,",
219 charon->processor->get_idle_threads(charon->processor),
220 charon->processor->get_total_threads(charon->processor));
221 fprintf(out, " job queue load: %d,",
222 charon->processor->get_job_load(charon->processor));
223 fprintf(out, " scheduled events: %d\n",
224 charon->scheduler->get_job_load(charon->scheduler));
225 fprintf(out, " loaded plugins: ");
226 enumerator = lib->plugins->create_plugin_enumerator(lib->plugins);
227 while (enumerator->enumerate(enumerator, &plugin))
228 {
229 fprintf(out, "%s ", plugin);
230 }
231 enumerator->destroy(enumerator);
232 fprintf(out, "\n");
233
234 iterator = charon->kernel_interface->create_address_iterator(
235 charon->kernel_interface);
236 fprintf(out, "Listening IP addresses:\n");
237 while (iterator->iterate(iterator, (void**)&host))
238 {
239 fprintf(out, " %H\n", host);
240 }
241 iterator->destroy(iterator);
242
243 fprintf(out, "Connections:\n");
244 enumerator = charon->backends->create_peer_cfg_enumerator(charon->backends);
245 while (enumerator->enumerate(enumerator, (void**)&peer_cfg))
246 {
247 if (peer_cfg->get_ike_version(peer_cfg) != 2 ||
248 (name && !streq(name, peer_cfg->get_name(peer_cfg))))
249 {
250 continue;
251 }
252
253 ike_cfg = peer_cfg->get_ike_cfg(peer_cfg);
254 fprintf(out, "%12s: %s[%D]...%s[%D]\n", peer_cfg->get_name(peer_cfg),
255 ike_cfg->get_my_addr(ike_cfg), peer_cfg->get_my_id(peer_cfg),
256 ike_cfg->get_other_addr(ike_cfg), peer_cfg->get_other_id(peer_cfg));
257 /* TODO: list CAs and groups */
258 children = peer_cfg->create_child_cfg_enumerator(peer_cfg);
259 while (children->enumerate(children, &child_cfg))
260 {
261 linked_list_t *my_ts, *other_ts;
262 my_ts = child_cfg->get_traffic_selectors(child_cfg, TRUE, NULL, NULL);
263 other_ts = child_cfg->get_traffic_selectors(child_cfg, FALSE, NULL, NULL);
264 fprintf(out, "%12s: %#R=== %#R\n", child_cfg->get_name(child_cfg),
265 my_ts, other_ts);
266 my_ts->destroy_offset(my_ts, offsetof(traffic_selector_t, destroy));
267 other_ts->destroy_offset(other_ts, offsetof(traffic_selector_t, destroy));
268 }
269 children->destroy(children);
270 }
271 enumerator->destroy(enumerator);
272 }
273
274 fprintf(out, "Security Associations:\n");
275 enumerator = charon->controller->create_ike_sa_enumerator(charon->controller);
276 while (enumerator->enumerate(enumerator, &ike_sa))
277 {
278 bool ike_printed = FALSE;
279 child_sa_t *child_sa;
280 iterator_t *children = ike_sa->create_child_sa_iterator(ike_sa);
281
282 if (name == NULL || streq(name, ike_sa->get_name(ike_sa)))
283 {
284 log_ike_sa(out, ike_sa, all);
285 found = TRUE;
286 ike_printed = TRUE;
287 }
288
289 while (children->iterate(children, (void**)&child_sa))
290 {
291 if (name == NULL || streq(name, child_sa->get_name(child_sa)))
292 {
293 if (!ike_printed)
294 {
295 log_ike_sa(out, ike_sa, all);
296 found = TRUE;
297 ike_printed = TRUE;
298 }
299 log_child_sa(out, child_sa, all);
300 }
301 }
302 children->destroy(children);
303 }
304 enumerator->destroy(enumerator);
305
306 if (!found)
307 {
308 if (name)
309 {
310 fprintf(out, " no match\n");
311 }
312 else
313 {
314 fprintf(out, " none\n");
315 }
316 }
317 }
318
319 /**
320 * create a unique certificate list without duplicates
321 * certicates having the same issuer are grouped together.
322 */
323 static linked_list_t* create_unique_cert_list(certificate_type_t type)
324 {
325 linked_list_t *list = linked_list_create();
326 enumerator_t *enumerator = charon->credentials->create_cert_enumerator(
327 charon->credentials, type, KEY_ANY,
328 NULL, FALSE);
329 certificate_t *cert;
330
331 while (enumerator->enumerate(enumerator, (void**)&cert))
332 {
333 iterator_t *iterator = list->create_iterator(list, TRUE);
334 identification_t *issuer = cert->get_issuer(cert);
335 bool previous_same, same = FALSE, last = TRUE;
336 certificate_t *list_cert;
337
338 while (iterator->iterate(iterator, (void**)&list_cert))
339 {
340 /* exit if we have a duplicate? */
341 if (list_cert->equals(list_cert, cert))
342 {
343 last = FALSE;
344 break;
345 }
346 /* group certificates with same issuer */
347 previous_same = same;
348 same = list_cert->has_issuer(list_cert, issuer);
349 if (previous_same && !same)
350 {
351 iterator->insert_before(iterator, (void *)cert->get_ref(cert));
352 last = FALSE;
353 break;
354 }
355 }
356 iterator->destroy(iterator);
357
358 if (last)
359 {
360 list->insert_last(list, (void *)cert->get_ref(cert));
361 }
362 }
363 enumerator->destroy(enumerator);
364 return list;
365 }
366
367 /**
368 * list all X.509 certificates matching the flags
369 */
370 static void stroke_list_certs(linked_list_t *list, char *label,
371 x509_flag_t flags, bool utc, FILE *out)
372 {
373 bool first = TRUE;
374 time_t now = time(NULL);
375 enumerator_t *enumerator = list->create_enumerator(list);
376 certificate_t *cert;
377
378 while (enumerator->enumerate(enumerator, (void**)&cert))
379 {
380 x509_t *x509 = (x509_t*)cert;
381 x509_flag_t x509_flags = x509->get_flags(x509);
382
383 /* list only if flag is set, or flags == 0 (ignoring self-signed) */
384 if ((x509_flags & flags) || (flags == (x509_flags & ~X509_SELF_SIGNED)))
385 {
386 enumerator_t *enumerator;
387 identification_t *altName;
388 bool first_altName = TRUE;
389 chunk_t serial = x509->get_serial(x509);
390 identification_t *authkey = x509->get_authKeyIdentifier(x509);
391 time_t notBefore, notAfter;
392 public_key_t *public = cert->get_public_key(cert);
393
394 if (first)
395 {
396 fprintf(out, "\n");
397 fprintf(out, "List of %s:\n", label);
398 first = FALSE;
399 }
400 fprintf(out, "\n");
401
402 /* list subjectAltNames */
403 enumerator = x509->create_subjectAltName_enumerator(x509);
404 while (enumerator->enumerate(enumerator, (void**)&altName))
405 {
406 if (first_altName)
407 {
408 fprintf(out, " altNames: ");
409 first_altName = FALSE;
410 }
411 else
412 {
413 fprintf(out, ", ");
414 }
415 fprintf(out, "%D", altName);
416 }
417 if (!first_altName)
418 {
419 fprintf(out, "\n");
420 }
421 enumerator->destroy(enumerator);
422
423 fprintf(out, " subject: \"%D\"\n", cert->get_subject(cert));
424 fprintf(out, " issuer: \"%D\"\n", cert->get_issuer(cert));
425 fprintf(out, " serial: %#B\n", &serial);
426
427 /* list validity */
428 cert->get_validity(cert, &now, &notBefore, &notAfter);
429 fprintf(out, " validity: not before %#T, ", &notBefore, utc);
430 if (now < notBefore)
431 {
432 fprintf(out, "not valid yet (valid in %#V)\n", &now, &notBefore);
433 }
434 else
435 {
436 fprintf(out, "ok\n");
437 }
438 fprintf(out, " not after %#T, ", &notAfter, utc);
439 if (now > notAfter)
440 {
441 fprintf(out, "expired (%#V ago)\n", &now, &notAfter);
442 }
443 else
444 {
445 fprintf(out, "ok");
446 if (now > notAfter - CERT_WARNING_INTERVAL * 60 * 60 * 24)
447 {
448 fprintf(out, " (expires in %#V)", &now, &notAfter);
449 }
450 fprintf(out, " \n");
451 }
452
453 /* list public key information */
454 if (public)
455 {
456 private_key_t *private = NULL;
457 identification_t *id, *keyid;
458
459 id = public->get_id(public, ID_PUBKEY_SHA1);
460 keyid = public->get_id(public, ID_PUBKEY_INFO_SHA1);
461
462 private = charon->credentials->get_private(
463 charon->credentials,
464 public->get_type(public), keyid, NULL);
465 fprintf(out, " pubkey: %N %d bits%s\n",
466 key_type_names, public->get_type(public),
467 public->get_keysize(public) * 8,
468 private ? ", has private key" : "");
469 fprintf(out, " keyid: %D\n", keyid);
470 fprintf(out, " subjkey: %D\n", id);
471 DESTROY_IF(private);
472 public->destroy(public);
473 }
474
475 /* list optional authorityKeyIdentifier */
476 if (authkey)
477 {
478 fprintf(out, " authkey: %D\n", authkey);
479 }
480 }
481 }
482 enumerator->destroy(enumerator);
483 }
484
485 /**
486 * list all X.509 attribute certificates
487 */
488 static void stroke_list_acerts(linked_list_t *list, bool utc, FILE *out)
489 {
490 bool first = TRUE;
491 time_t thisUpdate, nextUpdate, now = time(NULL);
492 enumerator_t *enumerator = list->create_enumerator(list);
493 certificate_t *cert;
494
495 while (enumerator->enumerate(enumerator, (void**)&cert))
496 {
497 ac_t *ac = (ac_t*)cert;
498 chunk_t serial = ac->get_serial(ac);
499 chunk_t holderSerial = ac->get_holderSerial(ac);
500 identification_t *holderIssuer = ac->get_holderIssuer(ac);
501 identification_t *authkey = ac->get_authKeyIdentifier(ac);
502 identification_t *entityName = cert->get_subject(cert);
503
504 if (first)
505 {
506 fprintf(out, "\n");
507 fprintf(out, "List of X.509 Attribute Certificates:\n");
508 first = FALSE;
509 }
510 fprintf(out, "\n");
511
512 if (entityName)
513 {
514 fprintf(out, " holder: \"%D\"\n", entityName);
515 }
516 if (holderIssuer)
517 {
518 fprintf(out, " hissuer: \"%D\"\n", holderIssuer);
519 }
520 if (holderSerial.ptr)
521 {
522 fprintf(out, " hserial: %#B\n", &holderSerial);
523 }
524 fprintf(out, " issuer: \"%D\"\n", cert->get_issuer(cert));
525 fprintf(out, " serial: %#B\n", &serial);
526
527 /* list validity */
528 cert->get_validity(cert, &now, &thisUpdate, &nextUpdate);
529 fprintf(out, " updates: this %#T\n", &thisUpdate, utc);
530 fprintf(out, " next %#T, ", &nextUpdate, utc);
531 if (now > nextUpdate)
532 {
533 fprintf(out, "expired (%#V ago)\n", &now, &nextUpdate);
534 }
535 else
536 {
537 fprintf(out, "ok");
538 if (now > nextUpdate - AC_WARNING_INTERVAL * 60 * 60 * 24)
539 {
540 fprintf(out, " (expires in %#V)", &now, &nextUpdate);
541 }
542 fprintf(out, " \n");
543 }
544
545 /* list optional authorityKeyIdentifier */
546 if (authkey)
547 {
548 fprintf(out, " authkey: %D\n", authkey);
549 }
550 }
551 enumerator->destroy(enumerator);
552 }
553
554 /**
555 * list all X.509 CRLs
556 */
557 static void stroke_list_crls(linked_list_t *list, bool utc, FILE *out)
558 {
559 bool first = TRUE;
560 time_t thisUpdate, nextUpdate, now = time(NULL);
561 enumerator_t *enumerator = list->create_enumerator(list);
562 certificate_t *cert;
563
564 while (enumerator->enumerate(enumerator, (void**)&cert))
565 {
566 crl_t *crl = (crl_t*)cert;
567 chunk_t serial = crl->get_serial(crl);
568 identification_t *authkey = crl->get_authKeyIdentifier(crl);
569
570 if (first)
571 {
572 fprintf(out, "\n");
573 fprintf(out, "List of X.509 CRLs:\n");
574 first = FALSE;
575 }
576 fprintf(out, "\n");
577
578 fprintf(out, " issuer: \"%D\"\n", cert->get_issuer(cert));
579
580 /* list optional crlNumber */
581 if (serial.ptr)
582 {
583 fprintf(out, " serial: %#B\n", &serial);
584 }
585
586 /* count the number of revoked certificates */
587 {
588 int count = 0;
589 enumerator_t *enumerator = crl->create_enumerator(crl);
590
591 while (enumerator->enumerate(enumerator, NULL, NULL, NULL))
592 {
593 count++;
594 }
595 fprintf(out, " revoked: %d certificate%s\n", count,
596 (count == 1)? "" : "s");
597 enumerator->destroy(enumerator);
598 }
599
600 /* list validity */
601 cert->get_validity(cert, &now, &thisUpdate, &nextUpdate);
602 fprintf(out, " updates: this %#T\n", &thisUpdate, utc);
603 fprintf(out, " next %#T, ", &nextUpdate, utc);
604 if (now > nextUpdate)
605 {
606 fprintf(out, "expired (%#V ago)\n", &now, &nextUpdate);
607 }
608 else
609 {
610 fprintf(out, "ok");
611 if (now > nextUpdate - CRL_WARNING_INTERVAL * 60 * 60 * 24)
612 {
613 fprintf(out, " (expires in %#V)", &now, &nextUpdate);
614 }
615 fprintf(out, " \n");
616 }
617
618 /* list optional authorityKeyIdentifier */
619 if (authkey)
620 {
621 fprintf(out, " authkey: %D\n", authkey);
622 }
623 }
624 enumerator->destroy(enumerator);
625 }
626
627 /**
628 * list all OCSP responses
629 */
630 static void stroke_list_ocsp(linked_list_t* list, bool utc, FILE *out)
631 {
632 bool first = TRUE;
633 enumerator_t *enumerator = list->create_enumerator(list);
634 certificate_t *cert;
635
636 while (enumerator->enumerate(enumerator, (void**)&cert))
637 {
638 if (first)
639 {
640 fprintf(out, "\n");
641 fprintf(out, "List of OCSP responses:\n");
642 fprintf(out, "\n");
643 first = FALSE;
644 }
645
646 fprintf(out, " signer: \"%D\"\n", cert->get_issuer(cert));
647 }
648 enumerator->destroy(enumerator);
649 }
650
651 /**
652 * Implementation of stroke_list_t.list.
653 */
654 static void list(private_stroke_list_t *this, stroke_msg_t *msg, FILE *out)
655 {
656 linked_list_t *cert_list = NULL;
657
658 if (msg->list.flags & (LIST_CERTS | LIST_CACERTS | LIST_OCSPCERTS | LIST_AACERTS))
659 {
660 cert_list = create_unique_cert_list(CERT_X509);
661 }
662 if (msg->list.flags & LIST_CERTS)
663 {
664 stroke_list_certs(cert_list, "X.509 End Entity Certificates",
665 0, msg->list.utc, out);
666 }
667 if (msg->list.flags & LIST_CACERTS)
668 {
669 stroke_list_certs(cert_list, "X.509 CA Certificates",
670 X509_CA, msg->list.utc, out);
671 }
672 if (msg->list.flags & LIST_OCSPCERTS)
673 {
674 stroke_list_certs(cert_list, "X.509 OCSP Signer Certificates",
675 X509_OCSP_SIGNER, msg->list.utc, out);
676 }
677 if (msg->list.flags & LIST_AACERTS)
678 {
679 stroke_list_certs(cert_list, "X.509 AA Certificates",
680 X509_AA, msg->list.utc, out);
681 }
682 if (msg->list.flags & LIST_ACERTS)
683 {
684 linked_list_t *ac_list = create_unique_cert_list(CERT_X509_AC);
685
686 stroke_list_acerts(ac_list, msg->list.utc, out);
687 ac_list->destroy_offset(ac_list, offsetof(certificate_t, destroy));
688 }
689 if (msg->list.flags & LIST_CRLS)
690 {
691 linked_list_t *crl_list = create_unique_cert_list(CERT_X509_CRL);
692
693 stroke_list_crls(crl_list, msg->list.utc, out);
694 crl_list->destroy_offset(crl_list, offsetof(certificate_t, destroy));
695 }
696 if (msg->list.flags & LIST_OCSP)
697 {
698 linked_list_t *ocsp_list = create_unique_cert_list(CERT_X509_OCSP_RESPONSE);
699
700 stroke_list_ocsp(ocsp_list, msg->list.utc, out);
701 ocsp_list->destroy_offset(ocsp_list, offsetof(certificate_t, destroy));
702 }
703 DESTROY_OFFSET_IF(cert_list, offsetof(certificate_t, destroy));
704 }
705
706 /**
707 * Implementation of stroke_list_t.destroy
708 */
709 static void destroy(private_stroke_list_t *this)
710 {
711 free(this);
712 }
713
714 /*
715 * see header file
716 */
717 stroke_list_t *stroke_list_create()
718 {
719 private_stroke_list_t *this = malloc_thing(private_stroke_list_t);
720
721 this->public.list = (void(*)(stroke_list_t*, stroke_msg_t *msg, FILE *out))list;
722 this->public.status = (void(*)(stroke_list_t*, stroke_msg_t *msg, FILE *out,bool))status;
723 this->public.destroy = (void(*)(stroke_list_t*))destroy;
724
725 this->uptime = time(NULL);
726
727 return &this->public;
728 }
729