WIP: Fix the Proxy-State issue.
[libradsec.git] / tlscommon.c
1 /* Copyright (c) 2007-2009, UNINETT AS
2  * Copyright (c) 2010-2011, NORDUnet A/S */
3 /* See LICENSE for licensing information. */
4
5 #if defined(RADPROT_TLS) || defined(RADPROT_DTLS)
6 #include <signal.h>
7 #include <sys/socket.h>
8 #include <netinet/in.h>
9 #include <netdb.h>
10 #include <string.h>
11 #include <unistd.h>
12 #include <limits.h>
13 #ifdef SYS_SOLARIS9
14 #include <fcntl.h>
15 #endif
16 #include <sys/time.h>
17 #include <sys/types.h>
18 #include <sys/select.h>
19 #include <ctype.h>
20 #include <sys/wait.h>
21 #include <arpa/inet.h>
22 #include <regex.h>
23 #include <libgen.h>
24 #include <pthread.h>
25 #include <openssl/ssl.h>
26 #include <openssl/rand.h>
27 #include <openssl/err.h>
28 #include <openssl/md5.h>
29 #include <openssl/x509v3.h>
30 #include "debug.h"
31 #include "hash.h"
32 #include "util.h"
33 #include "hostport.h"
34 #include "radsecproxy.h"
35
36 static struct hash *tlsconfs = NULL;
37
38 static int pem_passwd_cb(char *buf, int size, int rwflag, void *userdata) {
39     int pwdlen = strlen(userdata);
40     if (rwflag != 0 || pwdlen > size) /* not for decryption or too large */
41         return 0;
42     memcpy(buf, userdata, pwdlen);
43     return pwdlen;
44 }
45
46 static int verify_cb(int ok, X509_STORE_CTX *ctx) {
47     char *buf = NULL;
48     X509 *err_cert;
49     int err, depth;
50
51     err_cert = X509_STORE_CTX_get_current_cert(ctx);
52     err = X509_STORE_CTX_get_error(ctx);
53     depth = X509_STORE_CTX_get_error_depth(ctx);
54
55     if (depth > MAX_CERT_DEPTH) {
56         ok = 0;
57         err = X509_V_ERR_CERT_CHAIN_TOO_LONG;
58         X509_STORE_CTX_set_error(ctx, err);
59     }
60
61     if (!ok) {
62         if (err_cert)
63             buf = X509_NAME_oneline(X509_get_subject_name(err_cert), NULL, 0);
64         debug(DBG_WARN, "verify error: num=%d:%s:depth=%d:%s", err, X509_verify_cert_error_string(err), depth, buf ? buf : "");
65         free(buf);
66         buf = NULL;
67
68         switch (err) {
69         case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
70             if (err_cert) {
71                 buf = X509_NAME_oneline(X509_get_issuer_name(err_cert), NULL, 0);
72                 if (buf) {
73                     debug(DBG_WARN, "\tIssuer=%s", buf);
74                     free(buf);
75                     buf = NULL;
76                 }
77             }
78             break;
79         case X509_V_ERR_CERT_NOT_YET_VALID:
80         case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
81             debug(DBG_WARN, "\tCertificate not yet valid");
82             break;
83         case X509_V_ERR_CERT_HAS_EXPIRED:
84             debug(DBG_WARN, "Certificate has expired");
85             break;
86         case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
87             debug(DBG_WARN, "Certificate no longer valid (after notAfter)");
88             break;
89         case X509_V_ERR_NO_EXPLICIT_POLICY:
90             debug(DBG_WARN, "No Explicit Certificate Policy");
91             break;
92         }
93     }
94 #ifdef DEBUG
95     printf("certificate verify returns %d\n", ok);
96 #endif
97     return ok;
98 }
99
100 #ifdef DEBUG
101 static void ssl_info_callback(const SSL *ssl, int where, int ret) {
102     const char *s;
103     int w;
104
105     w = where & ~SSL_ST_MASK;
106
107     if (w & SSL_ST_CONNECT)
108         s = "SSL_connect";
109     else if (w & SSL_ST_ACCEPT)
110         s = "SSL_accept";
111     else
112         s = "undefined";
113
114     if (where & SSL_CB_LOOP)
115         debug(DBG_DBG, "%s:%s\n", s, SSL_state_string_long(ssl));
116     else if (where & SSL_CB_ALERT) {
117         s = (where & SSL_CB_READ) ? "read" : "write";
118         debug(DBG_DBG, "SSL3 alert %s:%s:%s\n", s, SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret));
119     }
120     else if (where & SSL_CB_EXIT) {
121         if (ret == 0)
122             debug(DBG_DBG, "%s:failed in %s\n", s, SSL_state_string_long(ssl));
123         else if (ret < 0)
124             debug(DBG_DBG, "%s:error in %s\n", s, SSL_state_string_long(ssl));
125     }
126 }
127 #endif
128
129 static X509_VERIFY_PARAM *createverifyparams(char **poids) {
130     X509_VERIFY_PARAM *pm;
131     ASN1_OBJECT *pobject;
132     int i;
133
134     pm = X509_VERIFY_PARAM_new();
135     if (!pm)
136         return NULL;
137
138     for (i = 0; poids[i]; i++) {
139         pobject = OBJ_txt2obj(poids[i], 0);
140         if (!pobject) {
141             X509_VERIFY_PARAM_free(pm);
142             return NULL;
143         }
144         X509_VERIFY_PARAM_add0_policy(pm, pobject);
145     }
146
147     X509_VERIFY_PARAM_set_flags(pm, X509_V_FLAG_POLICY_CHECK | X509_V_FLAG_EXPLICIT_POLICY);
148     return pm;
149 }
150
151 static int tlsaddcacrl(SSL_CTX *ctx, struct tls *conf) {
152     STACK_OF(X509_NAME) *calist;
153     X509_STORE *x509_s;
154     unsigned long error;
155
156     if (!SSL_CTX_load_verify_locations(ctx, conf->cacertfile, conf->cacertpath)) {
157         while ((error = ERR_get_error()))
158             debug(DBG_ERR, "SSL: %s", ERR_error_string(error, NULL));
159         debug(DBG_ERR, "tlsaddcacrl: Error updating TLS context %s", conf->name);
160         return 0;
161     }
162
163     calist = conf->cacertfile ? SSL_load_client_CA_file(conf->cacertfile) : NULL;
164     if (!conf->cacertfile || calist) {
165         if (conf->cacertpath) {
166             if (!calist)
167                 calist = sk_X509_NAME_new_null();
168             if (!SSL_add_dir_cert_subjects_to_stack(calist, conf->cacertpath)) {
169                 sk_X509_NAME_free(calist);
170                 calist = NULL;
171             }
172         }
173     }
174     if (!calist) {
175         while ((error = ERR_get_error()))
176             debug(DBG_ERR, "SSL: %s", ERR_error_string(error, NULL));
177         debug(DBG_ERR, "tlsaddcacrl: Error adding CA subjects in TLS context %s", conf->name);
178         return 0;
179     }
180     ERR_clear_error(); /* add_dir_cert_subj returns errors on success */
181     SSL_CTX_set_client_CA_list(ctx, calist);
182
183     SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb);
184     SSL_CTX_set_verify_depth(ctx, MAX_CERT_DEPTH + 1);
185
186     if (conf->crlcheck || conf->vpm) {
187         x509_s = SSL_CTX_get_cert_store(ctx);
188         if (conf->crlcheck)
189             X509_STORE_set_flags(x509_s, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL);
190         if (conf->vpm)
191             X509_STORE_set1_param(x509_s, conf->vpm);
192     }
193
194     debug(DBG_DBG, "tlsaddcacrl: updated TLS context %s", conf->name);
195     return 1;
196 }
197
198 static SSL_CTX *tlscreatectx(uint8_t type, struct tls *conf) {
199     SSL_CTX *ctx = NULL;
200     unsigned long error;
201     long sslversion = SSLeay();
202
203     switch (type) {
204 #ifdef RADPROT_TLS
205     case RAD_TLS:
206         ctx = SSL_CTX_new(TLSv1_method());
207 #ifdef DEBUG
208         SSL_CTX_set_info_callback(ctx, ssl_info_callback);
209 #endif
210         break;
211 #endif
212 #ifdef RADPROT_DTLS
213     case RAD_DTLS:
214         ctx = SSL_CTX_new(DTLSv1_method());
215 #ifdef DEBUG
216         SSL_CTX_set_info_callback(ctx, ssl_info_callback);
217 #endif
218         SSL_CTX_set_read_ahead(ctx, 1);
219         break;
220 #endif
221     }
222     if (!ctx) {
223         debug(DBG_ERR, "tlscreatectx: Error initialising SSL/TLS in TLS context %s", conf->name);
224         return NULL;
225     }
226
227     if (sslversion < 0x00908100L ||
228         (sslversion >= 0x10000000L && sslversion < 0x10000020L)) {
229         debug(DBG_WARN, "%s: %s seems to be of a version with a "
230               "certain security critical bug (fixed in OpenSSL 0.9.8p and "
231               "1.0.0b).  Disabling OpenSSL session caching for context %p.",
232               __func__, SSLeay_version(SSLEAY_VERSION), ctx);
233         SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
234     }
235
236     if (conf->certkeypwd) {
237         SSL_CTX_set_default_passwd_cb_userdata(ctx, conf->certkeypwd);
238         SSL_CTX_set_default_passwd_cb(ctx, pem_passwd_cb);
239     }
240     if (!SSL_CTX_use_certificate_chain_file(ctx, conf->certfile) ||
241         !SSL_CTX_use_PrivateKey_file(ctx, conf->certkeyfile, SSL_FILETYPE_PEM) ||
242         !SSL_CTX_check_private_key(ctx)) {
243         while ((error = ERR_get_error()))
244             debug(DBG_ERR, "SSL: %s", ERR_error_string(error, NULL));
245         debug(DBG_ERR, "tlscreatectx: Error initialising SSL/TLS in TLS context %s", conf->name);
246         SSL_CTX_free(ctx);
247         return NULL;
248     }
249
250     if (conf->policyoids) {
251         if (!conf->vpm) {
252             conf->vpm = createverifyparams(conf->policyoids);
253             if (!conf->vpm) {
254                 debug(DBG_ERR, "tlscreatectx: Failed to add policyOIDs in TLS context %s", conf->name);
255                 SSL_CTX_free(ctx);
256                 return NULL;
257             }
258         }
259     }
260
261     if (!tlsaddcacrl(ctx, conf)) {
262         if (conf->vpm) {
263             X509_VERIFY_PARAM_free(conf->vpm);
264             conf->vpm = NULL;
265         }
266         SSL_CTX_free(ctx);
267         return NULL;
268     }
269
270     debug(DBG_DBG, "tlscreatectx: created TLS context %s", conf->name);
271     return ctx;
272 }
273
274 struct tls *tlsgettls(char *alt1, char *alt2) {
275     struct tls *t;
276
277     t = hash_read(tlsconfs, alt1, strlen(alt1));
278     if (!t)
279         t = hash_read(tlsconfs, alt2, strlen(alt2));
280     return t;
281 }
282
283 SSL_CTX *tlsgetctx(uint8_t type, struct tls *t) {
284     struct timeval now;
285
286     if (!t)
287         return NULL;
288     gettimeofday(&now, NULL);
289
290     switch (type) {
291 #ifdef RADPROT_TLS
292     case RAD_TLS:
293         if (t->tlsexpiry && t->tlsctx) {
294             if (t->tlsexpiry < now.tv_sec) {
295                 t->tlsexpiry = now.tv_sec + t->cacheexpiry;
296                 tlsaddcacrl(t->tlsctx, t);
297             }
298         }
299         if (!t->tlsctx) {
300             t->tlsctx = tlscreatectx(RAD_TLS, t);
301             if (t->cacheexpiry)
302                 t->tlsexpiry = now.tv_sec + t->cacheexpiry;
303         }
304         return t->tlsctx;
305 #endif
306 #ifdef RADPROT_DTLS
307     case RAD_DTLS:
308         if (t->dtlsexpiry && t->dtlsctx) {
309             if (t->dtlsexpiry < now.tv_sec) {
310                 t->dtlsexpiry = now.tv_sec + t->cacheexpiry;
311                 tlsaddcacrl(t->dtlsctx, t);
312             }
313         }
314         if (!t->dtlsctx) {
315             t->dtlsctx = tlscreatectx(RAD_DTLS, t);
316             if (t->cacheexpiry)
317                 t->dtlsexpiry = now.tv_sec + t->cacheexpiry;
318         }
319         return t->dtlsctx;
320 #endif
321     }
322     return NULL;
323 }
324
325 X509 *verifytlscert(SSL *ssl) {
326     X509 *cert;
327     unsigned long error;
328
329     if (SSL_get_verify_result(ssl) != X509_V_OK) {
330         debug(DBG_ERR, "verifytlscert: basic validation failed");
331         while ((error = ERR_get_error()))
332             debug(DBG_ERR, "verifytlscert: TLS: %s", ERR_error_string(error, NULL));
333         return NULL;
334     }
335
336     cert = SSL_get_peer_certificate(ssl);
337     if (!cert)
338         debug(DBG_ERR, "verifytlscert: failed to obtain certificate");
339     return cert;
340 }
341
342 static int subjectaltnameaddr(X509 *cert, int family, struct in6_addr *addr) {
343     int loc, i, l, n, r = 0;
344     char *v;
345     X509_EXTENSION *ex;
346     STACK_OF(GENERAL_NAME) *alt;
347     GENERAL_NAME *gn;
348
349     debug(DBG_DBG, "subjectaltnameaddr");
350
351     loc = X509_get_ext_by_NID(cert, NID_subject_alt_name, -1);
352     if (loc < 0)
353         return r;
354
355     ex = X509_get_ext(cert, loc);
356     alt = X509V3_EXT_d2i(ex);
357     if (!alt)
358         return r;
359
360     n = sk_GENERAL_NAME_num(alt);
361     for (i = 0; i < n; i++) {
362         gn = sk_GENERAL_NAME_value(alt, i);
363         if (gn->type != GEN_IPADD)
364             continue;
365         r = -1;
366         v = (char *)ASN1_STRING_data(gn->d.ia5);
367         l = ASN1_STRING_length(gn->d.ia5);
368         if (((family == AF_INET && l == sizeof(struct in_addr)) || (family == AF_INET6 && l == sizeof(struct in6_addr)))
369             && !memcmp(v, &addr, l)) {
370             r = 1;
371             break;
372         }
373     }
374     GENERAL_NAMES_free(alt);
375     return r;
376 }
377
378 static int subjectaltnameregexp(X509 *cert, int type, char *exact,  regex_t *regex) {
379     int loc, i, l, n, r = 0;
380     char *s, *v;
381     X509_EXTENSION *ex;
382     STACK_OF(GENERAL_NAME) *alt;
383     GENERAL_NAME *gn;
384
385     debug(DBG_DBG, "subjectaltnameregexp");
386
387     loc = X509_get_ext_by_NID(cert, NID_subject_alt_name, -1);
388     if (loc < 0)
389         return r;
390
391     ex = X509_get_ext(cert, loc);
392     alt = X509V3_EXT_d2i(ex);
393     if (!alt)
394         return r;
395
396     n = sk_GENERAL_NAME_num(alt);
397     for (i = 0; i < n; i++) {
398         gn = sk_GENERAL_NAME_value(alt, i);
399         if (gn->type != type)
400             continue;
401         r = -1;
402         v = (char *)ASN1_STRING_data(gn->d.ia5);
403         l = ASN1_STRING_length(gn->d.ia5);
404         if (l <= 0)
405             continue;
406 #ifdef DEBUG
407         printfchars(NULL, gn->type == GEN_DNS ? "dns" : "uri", NULL, v, l);
408 #endif
409         if (exact) {
410             if (memcmp(v, exact, l))
411                 continue;
412         } else {
413             s = stringcopy((char *)v, l);
414             if (!s) {
415                 debug(DBG_ERR, "malloc failed");
416                 continue;
417             }
418             if (regexec(regex, s, 0, NULL, 0)) {
419                 free(s);
420                 continue;
421             }
422             free(s);
423         }
424         r = 1;
425         break;
426     }
427     GENERAL_NAMES_free(alt);
428     return r;
429 }
430
431 static int cnregexp(X509 *cert, char *exact, regex_t *regex) {
432     int loc, l;
433     char *v, *s;
434     X509_NAME *nm;
435     X509_NAME_ENTRY *e;
436     ASN1_STRING *t;
437
438     nm = X509_get_subject_name(cert);
439     loc = -1;
440     for (;;) {
441         loc = X509_NAME_get_index_by_NID(nm, NID_commonName, loc);
442         if (loc == -1)
443             break;
444         e = X509_NAME_get_entry(nm, loc);
445         t = X509_NAME_ENTRY_get_data(e);
446         v = (char *) ASN1_STRING_data(t);
447         l = ASN1_STRING_length(t);
448         if (l < 0)
449             continue;
450         if (exact) {
451             if (l == strlen(exact) && !strncasecmp(exact, v, l))
452                 return 1;
453         } else {
454             s = stringcopy((char *)v, l);
455             if (!s) {
456                 debug(DBG_ERR, "malloc failed");
457                 continue;
458             }
459             if (regexec(regex, s, 0, NULL, 0)) {
460                 free(s);
461                 continue;
462             }
463             free(s);
464             return 1;
465         }
466     }
467     return 0;
468 }
469
470 /* this is a bit sloppy, should not always accept match to any */
471 int certnamecheck(X509 *cert, struct list *hostports) {
472     struct list_node *entry;
473     struct hostportres *hp;
474     int r;
475     uint8_t type = 0; /* 0 for DNS, AF_INET for IPv4, AF_INET6 for IPv6 */
476     struct in6_addr addr;
477
478     for (entry = list_first(hostports); entry; entry = list_next(entry)) {
479         hp = (struct hostportres *)entry->data;
480         if (hp->prefixlen != 255) {
481             /* we disable the check for prefixes */
482             return 1;
483         }
484         if (inet_pton(AF_INET, hp->host, &addr))
485             type = AF_INET;
486         else if (inet_pton(AF_INET6, hp->host, &addr))
487             type = AF_INET6;
488         else
489             type = 0;
490
491         r = type ? subjectaltnameaddr(cert, type, &addr) : subjectaltnameregexp(cert, GEN_DNS, hp->host, NULL);
492         if (r) {
493             if (r > 0) {
494                 debug(DBG_DBG, "certnamecheck: Found subjectaltname matching %s %s", type ? "address" : "host", hp->host);
495                 return 1;
496             }
497             debug(DBG_WARN, "certnamecheck: No subjectaltname matching %s %s", type ? "address" : "host", hp->host);
498         } else {
499             if (cnregexp(cert, hp->host, NULL)) {
500                 debug(DBG_DBG, "certnamecheck: Found cn matching host %s", hp->host);
501                 return 1;
502             }
503             debug(DBG_WARN, "certnamecheck: cn not matching host %s", hp->host);
504         }
505     }
506     return 0;
507 }
508
509 int verifyconfcert(X509 *cert, struct clsrvconf *conf) {
510     if (conf->certnamecheck) {
511         if (!certnamecheck(cert, conf->hostports)) {
512             debug(DBG_WARN, "verifyconfcert: certificate name check failed");
513             return 0;
514         }
515         debug(DBG_WARN, "verifyconfcert: certificate name check ok");
516     }
517     if (conf->certcnregex) {
518         if (cnregexp(cert, NULL, conf->certcnregex) < 1) {
519             debug(DBG_WARN, "verifyconfcert: CN not matching regex");
520             return 0;
521         }
522         debug(DBG_DBG, "verifyconfcert: CN matching regex");
523     }
524     if (conf->certuriregex) {
525         if (subjectaltnameregexp(cert, GEN_URI, NULL, conf->certuriregex) < 1) {
526             debug(DBG_WARN, "verifyconfcert: subjectaltname URI not matching regex");
527             return 0;
528         }
529         debug(DBG_DBG, "verifyconfcert: subjectaltname URI matching regex");
530     }
531     return 1;
532 }
533
534 int conftls_cb(struct gconffile **cf, void *arg, char *block, char *opt, char *val) {
535     struct tls *conf;
536     long int expiry = LONG_MIN;
537
538     debug(DBG_DBG, "conftls_cb called for %s", block);
539
540     conf = malloc(sizeof(struct tls));
541     if (!conf) {
542         debug(DBG_ERR, "conftls_cb: malloc failed");
543         return 0;
544     }
545     memset(conf, 0, sizeof(struct tls));
546
547     if (!getgenericconfig(cf, block,
548                           "CACertificateFile", CONF_STR, &conf->cacertfile,
549                           "CACertificatePath", CONF_STR, &conf->cacertpath,
550                           "CertificateFile", CONF_STR, &conf->certfile,
551                           "CertificateKeyFile", CONF_STR, &conf->certkeyfile,
552                           "CertificateKeyPassword", CONF_STR, &conf->certkeypwd,
553                           "CacheExpiry", CONF_LINT, &expiry,
554                           "CRLCheck", CONF_BLN, &conf->crlcheck,
555                           "PolicyOID", CONF_MSTR, &conf->policyoids,
556                           NULL
557             )) {
558         debug(DBG_ERR, "conftls_cb: configuration error in block %s", val);
559         goto errexit;
560     }
561     if (!conf->certfile || !conf->certkeyfile) {
562         debug(DBG_ERR, "conftls_cb: TLSCertificateFile and TLSCertificateKeyFile must be specified in block %s", val);
563         goto errexit;
564     }
565     if (!conf->cacertfile && !conf->cacertpath) {
566         debug(DBG_ERR, "conftls_cb: CA Certificate file or path need to be specified in block %s", val);
567         goto errexit;
568     }
569     if (expiry != LONG_MIN) {
570         if (expiry < 0) {
571             debug(DBG_ERR, "error in block %s, value of option CacheExpiry is %ld, may not be negative", val, expiry);
572             goto errexit;
573         }
574         conf->cacheexpiry = expiry;
575     }
576
577     conf->name = stringcopy(val, 0);
578     if (!conf->name) {
579         debug(DBG_ERR, "conftls_cb: malloc failed");
580         goto errexit;
581     }
582
583     if (!tlsconfs)
584         tlsconfs = hash_create();
585     if (!hash_insert(tlsconfs, val, strlen(val), conf)) {
586         debug(DBG_ERR, "conftls_cb: malloc failed");
587         goto errexit;
588     }
589     if (!tlsgetctx(RAD_TLS, conf))
590         debug(DBG_ERR, "conftls_cb: error creating ctx for TLS block %s", val);
591     debug(DBG_DBG, "conftls_cb: added TLS block %s", val);
592     return 1;
593
594 errexit:
595     free(conf->cacertfile);
596     free(conf->cacertpath);
597     free(conf->certfile);
598     free(conf->certkeyfile);
599     free(conf->certkeypwd);
600     freegconfmstr(conf->policyoids);
601     free(conf);
602     return 0;
603 }
604
605 int addmatchcertattr(struct clsrvconf *conf) {
606     char *v;
607     regex_t **r;
608
609     if (!strncasecmp(conf->matchcertattr, "CN:/", 4)) {
610         r = &conf->certcnregex;
611         v = conf->matchcertattr + 4;
612     } else if (!strncasecmp(conf->matchcertattr, "SubjectAltName:URI:/", 20)) {
613         r = &conf->certuriregex;
614         v = conf->matchcertattr + 20;
615     } else
616         return 0;
617     if (!*v)
618         return 0;
619     /* regexp, remove optional trailing / if present */
620     if (v[strlen(v) - 1] == '/')
621         v[strlen(v) - 1] = '\0';
622     if (!*v)
623         return 0;
624
625     *r = malloc(sizeof(regex_t));
626     if (!*r) {
627         debug(DBG_ERR, "malloc failed");
628         return 0;
629     }
630     if (regcomp(*r, v, REG_EXTENDED | REG_ICASE | REG_NOSUB)) {
631         free(*r);
632         *r = NULL;
633         debug(DBG_ERR, "failed to compile regular expression %s", v);
634         return 0;
635     }
636     return 1;
637 }
638 #else
639 /* Just to makes file non-empty, should rather avoid compiling this file when not needed */
640 static void tlsdummy() {
641 }
642 #endif
643
644 /* Local Variables: */
645 /* c-file-style: "stroustrup" */
646 /* End: */