From 0df4ab80579544db421576d92e8a9b783edb1a58 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Sun, 13 Jul 2014 12:18:09 +0100 Subject: Fix TLS SNI, and add regression test cases --- doc/doc-txt/ChangeLog | 2 + src/src/tls-gnu.c | 11 +++-- src/src/tls-openssl.c | 21 ++++------ test/confs/2030 | 79 +++++++++++++++++++++++++++++++++++ test/confs/2031 | 92 +++++++++++++++++++++++++++++++++++++++++ test/confs/2130 | 79 +++++++++++++++++++++++++++++++++++ test/confs/2131 | 94 ++++++++++++++++++++++++++++++++++++++++++ test/log/2030 | 10 +++++ test/log/2031 | 17 ++++++++ test/log/2130 | 10 +++++ test/log/2131 | 17 ++++++++ test/scripts/2000-GnuTLS/2030 | 10 +++++ test/scripts/2000-GnuTLS/2031 | 19 +++++++++ test/scripts/2100-OpenSSL/2130 | 10 +++++ test/scripts/2100-OpenSSL/2131 | 19 +++++++++ 15 files changed, 471 insertions(+), 19 deletions(-) create mode 100644 test/confs/2030 create mode 100644 test/confs/2031 create mode 100644 test/confs/2130 create mode 100644 test/confs/2131 create mode 100644 test/log/2030 create mode 100644 test/log/2031 create mode 100644 test/log/2130 create mode 100644 test/log/2131 create mode 100644 test/scripts/2000-GnuTLS/2030 create mode 100644 test/scripts/2000-GnuTLS/2031 create mode 100644 test/scripts/2100-OpenSSL/2130 create mode 100644 test/scripts/2100-OpenSSL/2131 diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog index 83a066331..ac8fce5f9 100644 --- a/doc/doc-txt/ChangeLog +++ b/doc/doc-txt/ChangeLog @@ -142,6 +142,8 @@ TL/14 Enhance documentation of ${run expansion and how it parses the commandline after expansion, particularly in the case when an unquoted variable expansion results in an empty value. +JH/27 The TLS SNI feature was broken in 4.82. Fix it. + Exim version 4.82 ----------------- diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index 5bdb21e6e..266ab8909 100644 --- a/src/src/tls-gnu.c +++ b/src/src/tls-gnu.c @@ -108,7 +108,6 @@ typedef struct exim_gnutls_state { uschar *exp_tls_certificate; uschar *exp_tls_privatekey; - uschar *exp_tls_sni; uschar *exp_tls_verify_certificates; uschar *exp_tls_crl; uschar *exp_tls_require_ciphers; @@ -1086,15 +1085,15 @@ if (rc != OK) return rc; /* set SNI in client, only */ if (host) { - if (!expand_check(state->tlsp->sni, US"tls_out_sni", &state->exp_tls_sni)) + if (!expand_check(sni, US"tls_out_sni", &state->tlsp->sni)) return DEFER; - if (state->exp_tls_sni && *state->exp_tls_sni) + if (state->tlsp->sni && *state->tlsp->sni) { DEBUG(D_tls) - debug_printf("Setting TLS client SNI to \"%s\"\n", state->exp_tls_sni); - sz = Ustrlen(state->exp_tls_sni); + debug_printf("Setting TLS client SNI to \"%s\"\n", state->tlsp->sni); + sz = Ustrlen(state->tlsp->sni); rc = gnutls_server_name_set(state->session, - GNUTLS_NAME_DNS, state->exp_tls_sni, sz); + GNUTLS_NAME_DNS, state->tlsp->sni, sz); exim_gnutls_err_check(US"gnutls_server_name_set"); } } diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index 9609d6252..18994eaa9 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -436,14 +436,11 @@ const char *pem; if (!expand_check(dhparam, US"tls_dhparam", &dhexpanded)) return FALSE; -if (dhexpanded == NULL || *dhexpanded == '\0') - { +if (!dhexpanded || !*dhexpanded) bio = BIO_new_mem_buf(CS std_dh_prime_default(), -1); - } else if (dhexpanded[0] == '/') { - bio = BIO_new_file(CS dhexpanded, "r"); - if (bio == NULL) + if (!(bio = BIO_new_file(CS dhexpanded, "r"))) { tls_error(string_sprintf("could not read dhparams file %s", dhexpanded), host, US strerror(errno)); @@ -458,8 +455,7 @@ else return TRUE; } - pem = std_dh_prime_named(dhexpanded); - if (!pem) + if (!(pem = std_dh_prime_named(dhexpanded))) { tls_error(string_sprintf("Unknown standard DH prime \"%s\"", dhexpanded), host, US strerror(errno)); @@ -468,8 +464,7 @@ else bio = BIO_new_mem_buf(CS pem, -1); } -dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL); -if (dh == NULL) +if (!(dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL))) { BIO_free(bio); tls_error(string_sprintf("Could not read tls_dhparams \"%s\"", dhexpanded), @@ -770,8 +765,7 @@ if (!reexpand_tls_files_for_sni) not confident that memcpy wouldn't break some internal reference counting. Especially since there's a references struct member, which would be off. */ -server_sni = SSL_CTX_new(SSLv23_server_method()); -if (!server_sni) +if (!(server_sni = SSL_CTX_new(SSLv23_server_method()))) { ERR_error_string(ERR_get_error(), ssl_errstring); DEBUG(D_tls) debug_printf("SSL_CTX_new() failed: %s\n", ssl_errstring); @@ -805,8 +799,8 @@ OCSP information. */ rc = tls_expand_session_files(server_sni, cbinfo); if (rc != OK) return SSL_TLSEXT_ERR_NOACK; -rc = init_dh(server_sni, cbinfo->dhparam, NULL); -if (rc != OK) return SSL_TLSEXT_ERR_NOACK; +if (!init_dh(server_sni, cbinfo->dhparam, NULL)) + return SSL_TLSEXT_ERR_NOACK; DEBUG(D_tls) debug_printf("Switching SSL context.\n"); SSL_set_SSL_CTX(s, server_sni); @@ -1052,6 +1046,7 @@ else cbinfo->u_ocsp.client.verify_store = NULL; #endif cbinfo->dhparam = dhparam; +cbinfo->server_cipher_list = NULL; cbinfo->host = host; SSL_load_error_strings(); /* basic set up */ diff --git a/test/confs/2030 b/test/confs/2030 new file mode 100644 index 000000000..4ad1463b3 --- /dev/null +++ b/test/confs/2030 @@ -0,0 +1,79 @@ +# Exim test configuration 2030 +# SNI + +SERVER = + +exim_path = EXIM_PATH +host_lookup_order = bydns +primary_hostname = myhost.test.ex +rfc1413_query_timeout = 0s +spool_directory = DIR/spool +log_file_path = DIR/spool/log/SERVER%slog +gecos_pattern = "" +gecos_name = CALLER_NAME + + +# ----- Main settings ----- + +domainlist local_domains = test.ex : *.test.ex + +acl_smtp_rcpt = acl_log_sni +log_selector = +tls_peerdn +tls_sni +remote_max_parallel = 1 + +tls_advertise_hosts = * + +# Set certificate only if server + +tls_certificate = ${if eq {SERVER}{server}{DIR/aux-fixed/cert1}fail} + + +# ------ ACL ------ + +begin acl + +acl_log_sni: + accept + logwrite = SNI <$tls_in_sni> + +# ----- Routers ----- + +begin routers + +client: + driver = accept + condition = ${if !eq {SERVER}{server}} + transport = send_to_server${if eq{$local_part}{abcd}{2}{1}} + +server: + driver = redirect + data = :blackhole: + + +# ----- Transports ----- + +begin transports + +send_to_server1: + driver = smtp + allow_localhost + hosts = HOSTIPV4 + port = PORT_D + tls_sni = fred + +send_to_server2: + driver = smtp + allow_localhost + hosts = HOSTIPV4 + port = PORT_D + + +# ----- Retry ----- + + +begin retry + +* * F,5d,10s + + +# End diff --git a/test/confs/2031 b/test/confs/2031 new file mode 100644 index 000000000..a52b21eff --- /dev/null +++ b/test/confs/2031 @@ -0,0 +1,92 @@ +# Exim test configuration 2030 +# SNI + +SERVER = + +exim_path = EXIM_PATH +host_lookup_order = bydns +primary_hostname = myhost.test.ex +rfc1413_query_timeout = 0s +spool_directory = DIR/spool +log_file_path = DIR/spool/log/SERVER%slog +gecos_pattern = "" +gecos_name = CALLER_NAME + + +# ----- Main settings ----- + +domainlist local_domains = test.ex : *.test.ex + +acl_smtp_rcpt = acl_log_sni +log_selector = +tls_peerdn +tls_sni +remote_max_parallel = 1 + +tls_advertise_hosts = * + +# Set certificate only if server + +tls_certificate = ${if eq {SERVER}{server} \ + {DIR/aux-fixed/${if eq {$tls_in_sni}{bill} \ + {exim-ca/example.com/server1.example.com/server1.example.com.pem} \ + {cert1} \ + }\ + }fail} + +tls_privatekey = ${if eq {SERVER}{server} \ + {DIR/aux-fixed/${if eq {$tls_in_sni}{bill} \ + {exim-ca/example.com/server1.example.com/server1.example.com.unlocked.key} \ + {cert1} \ + }\ + }fail} + + +# ------ ACL ------ + +begin acl + +acl_log_sni: + accept + logwrite = SNI <$tls_in_sni> + +# ----- Routers ----- + +begin routers + +client: + driver = accept + condition = ${if !eq {SERVER}{server}} + transport = send_to_server${if eq{$local_part}{abcd}{2}{1}} + +server: + driver = redirect + data = :blackhole: + + +# ----- Transports ----- + +begin transports + +send_to_server1: + driver = smtp + allow_localhost + hosts = HOSTIPV4 + port = PORT_D + tls_sni = fred + +send_to_server2: + driver = smtp + allow_localhost + hosts = HOSTIPV4 + port = PORT_D + tls_sni = bill + + +# ----- Retry ----- + + +begin retry + +* * F,5d,10s + + +# End diff --git a/test/confs/2130 b/test/confs/2130 new file mode 100644 index 000000000..4143fc8ca --- /dev/null +++ b/test/confs/2130 @@ -0,0 +1,79 @@ +# Exim test configuration 2130 +# SNI + +SERVER = + +exim_path = EXIM_PATH +host_lookup_order = bydns +primary_hostname = myhost.test.ex +rfc1413_query_timeout = 0s +spool_directory = DIR/spool +log_file_path = DIR/spool/log/SERVER%slog +gecos_pattern = "" +gecos_name = CALLER_NAME + + +# ----- Main settings ----- + +domainlist local_domains = test.ex : *.test.ex + +acl_smtp_rcpt = acl_log_sni +log_selector = +tls_peerdn +tls_sni +remote_max_parallel = 1 + +tls_advertise_hosts = * + +# Set certificate only if server + +tls_certificate = ${if eq {SERVER}{server}{DIR/aux-fixed/cert1}fail} + + +# ------ ACL ------ + +begin acl + +acl_log_sni: + accept + logwrite = SNI <$tls_in_sni> + +# ----- Routers ----- + +begin routers + +client: + driver = accept + condition = ${if !eq {SERVER}{server}} + transport = send_to_server${if eq{$local_part}{abcd}{2}{1}} + +server: + driver = redirect + data = :blackhole: + + +# ----- Transports ----- + +begin transports + +send_to_server1: + driver = smtp + allow_localhost + hosts = HOSTIPV4 + port = PORT_D + tls_sni = fred + +send_to_server2: + driver = smtp + allow_localhost + hosts = HOSTIPV4 + port = PORT_D + + +# ----- Retry ----- + + +begin retry + +* * F,5d,10s + + +# End diff --git a/test/confs/2131 b/test/confs/2131 new file mode 100644 index 000000000..c52ceed0c --- /dev/null +++ b/test/confs/2131 @@ -0,0 +1,94 @@ +# Exim test configuration 2130 +# SNI + +SERVER = + +exim_path = EXIM_PATH +host_lookup_order = bydns +primary_hostname = myhost.test.ex +rfc1413_query_timeout = 0s +spool_directory = DIR/spool +log_file_path = DIR/spool/log/SERVER%slog +gecos_pattern = "" +gecos_name = CALLER_NAME + + +# ----- Main settings ----- + +domainlist local_domains = test.ex : *.test.ex + +acl_smtp_rcpt = acl_log_sni +log_selector = +tls_peerdn +tls_sni +remote_max_parallel = 1 + +tls_advertise_hosts = * + +# Set certificate only if server + +tls_certificate = ${if eq {SERVER}{server} \ + {DIR/aux-fixed/${if eq {$tls_in_sni}{bill} \ + {exim-ca/example.com/server1.example.com/server1.example.com.pem} \ + {cert1} \ + }\ + }fail} + +tls_privatekey = ${if eq {SERVER}{server} \ + {DIR/aux-fixed/${if eq {$tls_in_sni}{bill} \ + {exim-ca/example.com/server1.example.com/server1.example.com.unlocked.key} \ + {cert1} \ + }\ + }fail} + + +# ------ ACL ------ + +begin acl + +acl_log_sni: + accept + logwrite = SNI <$tls_in_sni> + +# ----- Routers ----- + +begin routers + +client: + driver = accept + condition = ${if !eq {SERVER}{server}} + transport = send_to_server${if eq{$local_part}{abcd}{2}{1}} + +server: + driver = redirect + data = :blackhole: + + +# ----- Transports ----- + +begin transports + +send_to_server1: + driver = smtp + allow_localhost + hosts = HOSTIPV4 + port = PORT_D + tls_sni = fred + hosts_require_tls = * + +send_to_server2: + driver = smtp + allow_localhost + hosts = HOSTIPV4 + port = PORT_D + tls_sni = bill + hosts_require_tls = * + + +# ----- Retry ----- + + +begin retry + +* * F,5d,10s + + +# End diff --git a/test/log/2030 b/test/log/2030 new file mode 100644 index 000000000..820b84f87 --- /dev/null +++ b/test/log/2030 @@ -0,0 +1,10 @@ +1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss +1999-03-02 09:44:33 10HmaX-0005vi-00 => CALLER@test.ex R=client T=send_to_server1 H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] X=TLS1.x:xxxxRSA_AES_256_CBC_SHAnnn:256 DN="C=UK,O=The Exim Maintainers,OU=Test Suite,CN=Phil Pennock" C="250 OK id=10HmaY-0005vi-00" +1999-03-02 09:44:33 10HmaX-0005vi-00 Completed + +******** SERVER ******** +1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 +1999-03-02 09:44:33 SNI +1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLS1.x:xxxxRSA_AES_256_CBC_SHAnnn:256 SNI="fred" S=sss id=E10HmaX-0005vi-00@myhost.test.ex +1999-03-02 09:44:33 10HmaY-0005vi-00 => :blackhole: R=server +1999-03-02 09:44:33 10HmaY-0005vi-00 Completed diff --git a/test/log/2031 b/test/log/2031 new file mode 100644 index 000000000..6d6b4f729 --- /dev/null +++ b/test/log/2031 @@ -0,0 +1,17 @@ +1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss +1999-03-02 09:44:33 10HmaX-0005vi-00 => CALLER@test.ex R=client T=send_to_server1 H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] X=TLS1.x:xxxxRSA_AES_256_CBC_SHAnnn:256 DN="C=UK,O=The Exim Maintainers,OU=Test Suite,CN=Phil Pennock" C="250 OK id=10HmaY-0005vi-00" +1999-03-02 09:44:33 10HmaX-0005vi-00 Completed +1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss +1999-03-02 09:44:33 10HmaZ-0005vi-00 => abcd@test.ex R=client T=send_to_server2 H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] X=TLS1.x:xxxxRSA_AES_256_CBC_SHAnnn:256 DN="CN=server1.example.com" C="250 OK id=10HmbA-0005vi-00" +1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed + +******** SERVER ******** +1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 +1999-03-02 09:44:33 SNI +1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLS1.x:xxxxRSA_AES_256_CBC_SHAnnn:256 SNI="fred" S=sss id=E10HmaX-0005vi-00@myhost.test.ex +1999-03-02 09:44:33 10HmaY-0005vi-00 => :blackhole: R=server +1999-03-02 09:44:33 10HmaY-0005vi-00 Completed +1999-03-02 09:44:33 SNI +1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLS1.x:xxxxRSA_AES_256_CBC_SHAnnn:256 SNI="bill" S=sss id=E10HmaZ-0005vi-00@myhost.test.ex +1999-03-02 09:44:33 10HmbA-0005vi-00 => :blackhole: R=server +1999-03-02 09:44:33 10HmbA-0005vi-00 Completed diff --git a/test/log/2130 b/test/log/2130 new file mode 100644 index 000000000..5b5b568bd --- /dev/null +++ b/test/log/2130 @@ -0,0 +1,10 @@ +1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss +1999-03-02 09:44:33 10HmaX-0005vi-00 => CALLER@test.ex R=client T=send_to_server1 H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] X=TLSv1:AES256-SHA:256 DN="/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock" C="250 OK id=10HmaY-0005vi-00" +1999-03-02 09:44:33 10HmaX-0005vi-00 Completed + +******** SERVER ******** +1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 +1999-03-02 09:44:33 SNI +1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLSv1:AES256-SHA:256 SNI="fred" S=sss id=E10HmaX-0005vi-00@myhost.test.ex +1999-03-02 09:44:33 10HmaY-0005vi-00 => :blackhole: R=server +1999-03-02 09:44:33 10HmaY-0005vi-00 Completed diff --git a/test/log/2131 b/test/log/2131 new file mode 100644 index 000000000..1fbd246f0 --- /dev/null +++ b/test/log/2131 @@ -0,0 +1,17 @@ +1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss +1999-03-02 09:44:33 10HmaX-0005vi-00 => CALLER@test.ex R=client T=send_to_server1 H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] X=TLSv1:AES256-SHA:256 DN="/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock" C="250 OK id=10HmaY-0005vi-00" +1999-03-02 09:44:33 10HmaX-0005vi-00 Completed +1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss +1999-03-02 09:44:33 10HmaZ-0005vi-00 => abcd@test.ex R=client T=send_to_server2 H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] X=TLSv1:AES256-SHA:256 DN="/CN=server1.example.com" C="250 OK id=10HmbA-0005vi-00" +1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed + +******** SERVER ******** +1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 +1999-03-02 09:44:33 SNI +1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLSv1:AES256-SHA:256 SNI="fred" S=sss id=E10HmaX-0005vi-00@myhost.test.ex +1999-03-02 09:44:33 10HmaY-0005vi-00 => :blackhole: R=server +1999-03-02 09:44:33 10HmaY-0005vi-00 Completed +1999-03-02 09:44:33 SNI +1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLSv1:AES256-SHA:256 SNI="bill" S=sss id=E10HmaZ-0005vi-00@myhost.test.ex +1999-03-02 09:44:33 10HmbA-0005vi-00 => :blackhole: R=server +1999-03-02 09:44:33 10HmbA-0005vi-00 Completed diff --git a/test/scripts/2000-GnuTLS/2030 b/test/scripts/2000-GnuTLS/2030 new file mode 100644 index 000000000..ef7a8dea2 --- /dev/null +++ b/test/scripts/2000-GnuTLS/2030 @@ -0,0 +1,10 @@ +# TLS: SNI +gnutls +exim -DSERVER=server -bd -oX PORT_D +**** +# Basic: is SNI set on tpt seen by server +exim CALLER@test.ex +Test message. +**** +millisleep 500 +killdaemon diff --git a/test/scripts/2000-GnuTLS/2031 b/test/scripts/2000-GnuTLS/2031 new file mode 100644 index 000000000..65b529093 --- /dev/null +++ b/test/scripts/2000-GnuTLS/2031 @@ -0,0 +1,19 @@ +# TLS server: SNI used to select certificate +gnutls +exim -DSERVER=server -bd -oX PORT_D +**** +# Extended: certificate choice is unchanged by received SNI +exim CALLER@test.ex +Test message. +**** +millisleep 500 +# +# +# Extended: server uses SNI to choose certificate +exim abcd@test.ex +Test message. +**** +millisleep 500 +# +# +killdaemon diff --git a/test/scripts/2100-OpenSSL/2130 b/test/scripts/2100-OpenSSL/2130 new file mode 100644 index 000000000..43695f648 --- /dev/null +++ b/test/scripts/2100-OpenSSL/2130 @@ -0,0 +1,10 @@ +# TLS: SNI +# +exim -DSERVER=server -bd -oX PORT_D +**** +# Basic: is SNI set on tpt seen by server +exim CALLER@test.ex +Test message. +**** +millisleep 500 +killdaemon diff --git a/test/scripts/2100-OpenSSL/2131 b/test/scripts/2100-OpenSSL/2131 new file mode 100644 index 000000000..74d3dbb49 --- /dev/null +++ b/test/scripts/2100-OpenSSL/2131 @@ -0,0 +1,19 @@ +# TLS server: SNI used to select certificate +# +exim -DSERVER=server -bd -oX PORT_D +**** +# Extended: certificate choice is unchanged by received SNI +exim CALLER@test.ex +Test message. +**** +millisleep 500 +# +# +# Extended: server uses SNI to change certificate +exim abcd@test.ex +Test message. +**** +millisleep 500 +# +# +killdaemon -- cgit v1.2.3