1
0

test-s2s-tls 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997
  1. #!/usr/bin/perl
  2. #%# Copyright (C) 2014-2024 Christoph Biedl <debian.axhn@manchmal.in-ulm.de>
  3. #%# License: GPL-2.0+
  4. =head1 NAME
  5. ngircd-test-tls-link - test certificate validation in ngircd's TLS based s2s links
  6. =head1 VERSION
  7. Version YYYY.MM.DD
  8. =cut
  9. our $VERSION = 'YYYY.MM.DD';
  10. =head1 SYNOPSIS
  11. ngircd-test-tls-link --x509-dir /path/to/dir --x509-populate
  12. ngircd-test-tls-link --x509-dir /path/to/dir [<test-name> ...]
  13. =cut
  14. use 5.010;
  15. use strict;
  16. use warnings;
  17. use Test::More;
  18. use File::Basename;
  19. use File::Copy;
  20. use File::Slurp;
  21. use File::Temp qw<tempdir>;
  22. use Getopt::Long;
  23. use Pod::Usage;
  24. use Proc::Simple;
  25. use Socket;
  26. use Time::HiRes qw<sleep time>;
  27. my $ngircd_exe_default = '/usr/sbin/ngircd';
  28. my @ngircd_exe = ( $ngircd_exe_default );
  29. my @tls_mode;
  30. my $x509_dir;
  31. my $x509_populate;
  32. my $verbose = 0;
  33. =head1 OPTIONS
  34. =over
  35. =cut
  36. {
  37. my $help;
  38. my $man;
  39. my %GetOptions = (
  40. 'help|?' => \$help,
  41. 'man' => \$man,
  42. );
  43. =item B<--x509-dir> F<directory>
  44. A directory to hold the x509 credentials needed. Default: Use a
  45. temporary directory which is deleted upon exit. This delays every
  46. execution of this program by a few seconds.
  47. =cut
  48. $GetOptions{'x509-dir=s'} = \$x509_dir;
  49. =item B<--x509-populate>
  50. Populate a given C<--x509-dir> with the files needed, then exit.
  51. =cut
  52. $GetOptions{'x509-populate'} = \$x509_populate;
  53. =item B<--tls-mode> C<mode>
  54. One of C<openssl> or C<gnutls>.
  55. The type of TLS configuration to use. Must match ngircd's compilation
  56. option. Use only if auto-detection failed, and report a bug then.
  57. For TLS interoperability tests, you can provide two strings, separated
  58. by C<:> (colon).
  59. Default: Detect from the binary.
  60. =cut
  61. $GetOptions{'tls-mode=s'} = sub {
  62. @tls_mode = split (/:/, $_[1], 2);
  63. };
  64. =item B<--ngircd> F<program>
  65. The ngircd binary. For TLS interoperability tests, you can provide two
  66. programs, separated by C<:> (colon).
  67. Default: F</usr/sbin/ngircd>
  68. =cut
  69. $GetOptions{'ngircd=s'} = sub {
  70. @ngircd_exe = split (/:/, $_[1], 2);
  71. };
  72. =item B<--verbose>
  73. Add verbosity.
  74. =cut
  75. $GetOptions{'verbose+'} = \$verbose++;
  76. =item B<--Version>
  77. Show the version number and exit.
  78. =cut
  79. $GetOptions{'Version'} = sub {
  80. printf "%s version %s\n",
  81. (split (/\//, $0))[-1],
  82. $VERSION;
  83. exit 1;
  84. };
  85. =item [ <test> ... ]
  86. Select specific tests to run only. Default: Run all tests.
  87. =back
  88. =cut
  89. GetOptions (%GetOptions) or pod2usage (2);
  90. $help and pod2usage (1);
  91. $man and pod2usage (-exitstatus => 0, -verbose => 2);
  92. if (@tls_mode) {
  93. foreach my $m (@tls_mode) {
  94. ($m =~ /^(openssl|gnutls)$/) or
  95. die ("Invalid '--tls-mode' value '$m'");
  96. }
  97. } else {
  98. for (my $i = 0; $i < scalar (@ngircd_exe); $i++) {
  99. my $x = $ngircd_exe[$i];
  100. my $tls_mode;
  101. (-x $x) or
  102. die ("Not an executable: '$x'");
  103. my $pipe;
  104. open ($pipe, '-|', 'ldd', $x) or
  105. die ("Cannot run ldd on '$x': $!");
  106. while (defined (my $line = <$pipe>)) {
  107. if ($line =~ /^\tlibssl\.so\./) {
  108. $tls_mode = 'openssl';
  109. last;
  110. } elsif ($line =~ /^\tlibgnutls/) {
  111. $tls_mode = 'gnutls';
  112. last;
  113. }
  114. }
  115. close ($pipe);
  116. if ($tls_mode) {
  117. note ("Auto-detected TLS mode for '$x' is '$tls_mode'");
  118. $tls_mode[$i] = $tls_mode;
  119. } else {
  120. die ("BUG: Failed to determine TLS mode for '$x'");
  121. }
  122. }
  123. }
  124. $ngircd_exe[1] //= $ngircd_exe[0];
  125. $tls_mode[1] //= $tls_mode[0];
  126. }
  127. my $temp_dir = tempdir (
  128. "ngircd-test-tls-link.$$.XXXXX",
  129. 'TMPDIR' => 1,
  130. 'CLEANUP' => 1,
  131. );
  132. my $exit_after_x509_populate;
  133. if ($x509_dir) {
  134. (-d $x509_dir || $x509_populate) or
  135. die ("Not a directory: '$x509_dir' - perhaps you forgot --x509-populate?");
  136. $exit_after_x509_populate = $x509_populate;
  137. } else {
  138. # not provided, use a temporary directoryy
  139. $x509_dir = "$temp_dir/CA";
  140. # always populate it, and continue
  141. $x509_populate and
  142. note ('Ignoring --x509-populate option as no --x509-dir was provided');
  143. $x509_populate = 1;
  144. $exit_after_x509_populate = undef;
  145. }
  146. my $conf_dir = "$temp_dir/conf";
  147. my $hosts_file ="$temp_dir/hosts";
  148. write_file (
  149. $hosts_file,
  150. <<__EOS__,
  151. 127.0.0.1 server1
  152. 127.0.0.1 server2
  153. 127.0.0.1 server1.example.com
  154. 127.0.0.1 server2.example.com
  155. __EOS__
  156. );
  157. foreach my $dir (($x509_dir, $conf_dir)) {
  158. (
  159. -d $dir ||
  160. mkdir ($dir)
  161. ) or die ("Cannot create directory '$dir'");
  162. }
  163. my $src_dir = dirname ($0);
  164. my $one_month_future = $^T + 30*86400;
  165. my %tests = (
  166. 'regular' => {
  167. 'expect' => 'pass',
  168. 'fail' => qr/Connection [0-9]+ with "server2:6692" closed/,
  169. ':order' => __LINE__,
  170. },
  171. 'different-CAs' => {
  172. 'expect' => 'pass',
  173. 'server1-extra' => {
  174. 'SSL' => {
  175. 'CAFile' => "$x509_dir/CA2/root-ca.crt",
  176. },
  177. },
  178. 'server2-extra' => {
  179. 'SSL' => {
  180. 'CertFile' => "$x509_dir/CA2/server2.example.com.crt",
  181. 'KeyFile' => "$x509_dir/CA2/server2.example.com.key",
  182. },
  183. },
  184. ':order' => __LINE__,
  185. },
  186. 'CN-mismatch' => {
  187. # using certificate with wrong CN, must fail
  188. 'expect' => 'fail',
  189. 'server2-extra' => {
  190. 'SSL' => {
  191. 'CertFile' => "$x509_dir/CA1/server3.example.com.crt",
  192. 'KeyFile' => "$x509_dir/CA1/server3.example.com.key",
  193. },
  194. },
  195. 'pass' => qr/Peer certificate check failed for/,
  196. ':order' => __LINE__,
  197. },
  198. 'using-peer-cert-as-CA' => {
  199. # using peer cert as CA a.k.a. self-signed
  200. 'unsupported' => 1, # fails on openssl
  201. 'expect' => 'pass',
  202. 'server1-extra' => {
  203. 'SSL' => {
  204. 'CAFile' => "$x509_dir/CA1/server2.example.com.crt",
  205. },
  206. },
  207. ':order' => __LINE__,
  208. },
  209. 'peer-cert-signed-by-unknown-CA' => {
  210. # peer cert signed by unknown CA, must fail
  211. 'expect' => 'fail',
  212. 'server2-extra' => {
  213. 'SSL' => {
  214. 'CertFile' => "$x509_dir/CA2/server2.example.com.crt",
  215. 'KeyFile' => "$x509_dir/CA2/server2.example.com.key",
  216. },
  217. },
  218. ':order' => __LINE__,
  219. },
  220. 'peer-cert-signed-by-unknown-CA-but-verify-disabled' => {
  221. 'expect' => 'pass',
  222. 'server1-extra' => {
  223. 'Server' => {
  224. 'SSLVerify' => 'no',
  225. }
  226. },
  227. 'server2-extra' => {
  228. 'SSL' => {
  229. 'CertFile' => "$x509_dir/CA2/server2.example.com.crt",
  230. 'KeyFile' => "$x509_dir/CA2/server2.example.com.key",
  231. },
  232. },
  233. 'pass' => qr/Synchronization with "ngircd\.test\.server2" done/,
  234. 'noop' => qr/Certificate validation failed/,
  235. ':order' => __LINE__,
  236. },
  237. 'peer-cert-revoked' => {
  238. # certificate is revoked, must fail
  239. 'expect' => 'fail',
  240. 'server1-extra' => {
  241. 'SSL' => {
  242. 'CAFile' => "$x509_dir/CA3/root-ca.crt",
  243. 'CertFile' => "$x509_dir/CA3/server1.example.com.crt",
  244. 'KeyFile' => "$x509_dir/CA3/server1.example.com.key",
  245. 'CRLFile' => "$x509_dir/CA3/crl.pem",
  246. },
  247. },
  248. 'server2-extra' => {
  249. 'SSL' => {
  250. 'CAFile' => "$x509_dir/CA3/root-ca.crt",
  251. 'CertFile' => "$x509_dir/CA3/server2.example.com.crt",
  252. 'KeyFile' => "$x509_dir/CA3/server2.example.com.key",
  253. },
  254. },
  255. ':order' => __LINE__,
  256. },
  257. 'peer-cert-revoked-but-verify-disabled' => {
  258. 'expect' => 'pass',
  259. 'server1-extra' => {
  260. 'SSL' => {
  261. 'CAFile' => "$x509_dir/CA3/root-ca.crt",
  262. 'CertFile' => "$x509_dir/CA3/server1.example.com.crt",
  263. 'KeyFile' => "$x509_dir/CA3/server1.example.com.key",
  264. 'CRLFile' => "$x509_dir/CA3/crl.pem",
  265. },
  266. 'Server' => {
  267. 'SSLVerify' => 'no',
  268. }
  269. },
  270. 'server2-extra' => {
  271. 'SSL' => {
  272. 'CAFile' => "$x509_dir/CA3/root-ca.crt",
  273. 'CertFile' => "$x509_dir/CA3/server2.example.com.crt",
  274. 'KeyFile' => "$x509_dir/CA3/server2.example.com.key",
  275. },
  276. },
  277. 'pass' => qr/Synchronization with "ngircd\.test\.server2" done/,
  278. 'noop' => qr/Certificate validation failed/,
  279. ':order' => __LINE__,
  280. },
  281. 'revocation-by-wrong-CA' => {
  282. # CRL signed by a different CA
  283. 'expect' => 'fail',
  284. 'server1-extra' => {
  285. 'SSL' => {
  286. 'CRLFile' => "$x509_dir/CA2/crl.pem",
  287. },
  288. },
  289. ':order' => __LINE__,
  290. },
  291. 'wildcard-cert' => {
  292. # server uses a wildcard certificate
  293. 'expect' => 'pass',
  294. 'server1-extra' => {
  295. 'SSL' => {
  296. 'CAFile' => "$x509_dir/CA4/root-ca.crt",
  297. 'CertFile' => "$x509_dir/CA4/server1.example.com.crt",
  298. 'KeyFile' => "$x509_dir/CA4/server1.example.com.key",
  299. },
  300. },
  301. 'server2-extra' => {
  302. 'SSL' => {
  303. 'CAFile' => "$x509_dir/CA4/root-ca.crt",
  304. 'CertFile' => "$x509_dir/CA4/*.example.com.crt",
  305. 'KeyFile' => "$x509_dir/CA4/*.example.com.key",
  306. },
  307. },
  308. ':order' => __LINE__,
  309. },
  310. 'mixed-case' => {
  311. # uppercase letters in certificate name
  312. 'expect' => 'pass',
  313. 'server1-extra' => {
  314. 'SSL' => {
  315. 'CAFile' => "$x509_dir/CA5/root-ca.crt",
  316. 'CertFile' => "$x509_dir/CA5/server1.example.com.crt",
  317. 'KeyFile' => "$x509_dir/CA5/server1.example.com.key",
  318. },
  319. },
  320. 'server2-extra' => {
  321. 'SSL' => {
  322. 'CAFile' => "$x509_dir/CA5/root-ca.crt",
  323. 'CertFile' => "$x509_dir/CA5/Server2.example.com.crt",
  324. 'KeyFile' => "$x509_dir/CA5/Server2.example.com.key",
  325. },
  326. },
  327. ':order' => __LINE__,
  328. },
  329. 'subject-alternate-name' => {
  330. 'expect' => 'pass',
  331. 'server1-extra' => {
  332. 'SSL' => {
  333. 'CAFile' => "$x509_dir/CA6/root-ca.crt",
  334. 'CertFile' => "$x509_dir/CA6/server1.example.com.crt",
  335. 'KeyFile' => "$x509_dir/CA6/server1.example.com.key",
  336. },
  337. },
  338. 'server2-extra' => {
  339. 'SSL' => {
  340. 'CAFile' => "$x509_dir/CA6/root-ca.crt",
  341. 'CertFile' => "$x509_dir/CA6/server2.example.com.crt",
  342. 'KeyFile' => "$x509_dir/CA6/server2.example.com.key",
  343. },
  344. },
  345. ':order' => __LINE__,
  346. },
  347. 'subject-alternate-name-mismatch' => {
  348. 'expect' => 'fail',
  349. 'pass' => qr/Failed to verify the hostname, expected/,
  350. 'server1-extra' => {
  351. 'SSL' => {
  352. 'CAFile' => "$x509_dir/CA6/root-ca.crt",
  353. 'CertFile' => "$x509_dir/CA6/server1.example.com.crt",
  354. 'KeyFile' => "$x509_dir/CA6/server1.example.com.key",
  355. },
  356. },
  357. 'server2-extra' => {
  358. 'SSL' => {
  359. 'CAFile' => "$x509_dir/CA6/root-ca.crt",
  360. 'CertFile' => "$x509_dir/CA6/server3.example.com.crt",
  361. 'KeyFile' => "$x509_dir/CA6/server3.example.com.key",
  362. },
  363. },
  364. ':order' => __LINE__,
  365. },
  366. 'cert-expired' => {
  367. 'expect' => 'fail',
  368. 'pass' => qr/SSL error: A TLS fatal alert has been received/,
  369. 'prefix' => [ 'faketime', "\@$one_month_future" ],
  370. ':order' => __LINE__,
  371. },
  372. );
  373. sub write_config {
  374. my ($file, $number, $tls_mode, @extras) = @_;
  375. my $cipher_list = ($tls_mode =~ /^o/ ?
  376. 'HIGH:!aNULL:@STRENGTH:!SSLv3' :
  377. 'SECURE128:-VERS-SSL3.0'
  378. );
  379. my $peer_number = 3 - $number;
  380. # write server configurations
  381. my %config = (
  382. 'Global' => {
  383. 'Name' => "ngircd.test.server$number",
  384. 'Info' => "ngIRCd Test-Server $number",
  385. 'Listen' => '127.0.0.1',
  386. 'Ports' => "678$number",
  387. 'AdminEMail' => "admin\@server$number.example",
  388. 'ServerUID' => $<,
  389. 'ServerGID' => $(,
  390. 'MotdFile' => '/dev/null',
  391. },
  392. 'Options' => {
  393. 'OperCanUseMode' => 'yes',
  394. 'Ident' => 'no',
  395. 'IncludeDir' => '',
  396. 'PAM' => 'no',
  397. },
  398. 'Operator' => {
  399. 'Name' => 'TestOp',
  400. 'Password' => '123',
  401. },
  402. 'Server' => {
  403. 'Name' => "ngircd.test.server$peer_number",
  404. 'Host' => "server$peer_number.example.com",
  405. 'Port' => "669$peer_number",
  406. 'MyPassword' => "pwd$number",
  407. 'PeerPassword' => "pwd$peer_number",
  408. 'SSLConnect' => 'yes',
  409. 'SSLVerify' => 'yes',
  410. },
  411. 'SSL' => {
  412. 'CAFile' => "$x509_dir/CA1/root-ca.crt",
  413. 'CertFile' => "$x509_dir/CA1/server$number.example.com.crt",
  414. 'CipherList' => $cipher_list,
  415. 'DHFile' => "$x509_dir/dhparams.pem",
  416. 'KeyFile' => "$x509_dir/CA1/server$number.example.com.key",
  417. 'Ports' => "669$number",
  418. },
  419. );
  420. foreach my $extra (@extras) {
  421. foreach my $section (keys %$extra) {
  422. foreach my $key (keys %{$extra->{$section}}) {
  423. if (
  424. exists ($config{$section}{$key}) &&
  425. defined ($config{$section}{$key}) &&
  426. exists ($extra->{$section}{$key}) &&
  427. defined ($extra->{$section}{$key}) &&
  428. $config{$section}{$key} eq $extra->{$section}{$key}
  429. ) {
  430. note ("W: Identical re-definition of server/section/key: $number/$section/$key");
  431. }
  432. $config{$section}{$key} = $extra->{$section}{$key};
  433. }
  434. }
  435. }
  436. my $return = '';
  437. my $fh;
  438. open ($fh, '>', \$return);
  439. foreach my $section (sort keys %config) {
  440. print $fh "[$section]\n";
  441. foreach my $key (sort keys %{$config{$section}}) {
  442. my $value = $config{$section}{$key};
  443. defined ($value) and
  444. printf $fh " %s = %s\n", $key, $value;
  445. }
  446. }
  447. close ($fh);
  448. write_file ($file, $return);
  449. }
  450. sub write_configs {
  451. my (
  452. $server1_config_file,
  453. $server2_config_file,
  454. $server1_config_extra,
  455. $server2_config_extra,
  456. ) = @_;
  457. write_config (
  458. $server1_config_file,
  459. '1',
  460. $tls_mode[0],
  461. {
  462. 'SSL' => {
  463. 'Ports' => undef,
  464. },
  465. },
  466. $server1_config_extra,
  467. );
  468. write_config (
  469. $server2_config_file,
  470. '2',
  471. $tls_mode[1],
  472. {
  473. 'Server' => {
  474. 'Passive' => 'yes',
  475. },
  476. },
  477. $server2_config_extra,
  478. );
  479. }
  480. sub test1 {
  481. my ($name, $test) = @_;
  482. {
  483. my $l = length ($name);
  484. note ('+-' . ('-' x $l) . '-+');
  485. note ("| $name | ");
  486. note ('+-' . ('-' x $l) . '-+');
  487. }
  488. my $server1_config = "$conf_dir/ngircd-test1.conf";
  489. my $server2_config = "$conf_dir/ngircd-test2.conf";
  490. write_configs (
  491. $server1_config,
  492. $server2_config,
  493. $test->{'server1-extra'},
  494. $test->{'server2-extra'},
  495. ),
  496. my $prefix = $test->{'prefix'} // [];
  497. # start the receiving server2 first, it might need a
  498. # little extra time
  499. my $server2_log = "$temp_dir/server2.log";
  500. write_file ($server2_log, ''); # so open below won't fail
  501. my $server2 = Proc::Simple->new;
  502. $server2->redirect_output ($server2_log, $server2_log);
  503. $server2->start ((
  504. @$prefix,
  505. $ngircd_exe[1],
  506. '--config', $server2_config,
  507. '--nodaemon',
  508. ));
  509. $server2->kill_on_destroy (1);
  510. sleep (0.5);
  511. # start connecting server1
  512. my $server1_log = "$temp_dir/server1.log";
  513. write_file ($server1_log, ''); # so open below won't fail
  514. my $server1 = Proc::Simple->new;
  515. $server1->redirect_output ($server1_log, $server1_log);
  516. $server1->start ((
  517. @$prefix,
  518. $ngircd_exe[0],
  519. '--config', $server1_config,
  520. '--nodaemon',
  521. ));
  522. $server1->kill_on_destroy (1);
  523. sleep (0.5);
  524. my $fh;
  525. open ($fh, '<', $server1_log) or
  526. die ("Cannot read '$server1_log': $!");
  527. # read output from server1, find 'pass' or 'fail' line
  528. my $timeout = time + 10;
  529. my $t0 = time;
  530. my $firstline;
  531. my $got_verdict;
  532. TAIL:
  533. while (1) {
  534. if (time > $timeout) {
  535. fail (sprintf ('Timeout (%u sec)', time - $t0));
  536. last TAIL;
  537. }
  538. my $curpos;
  539. my $line;
  540. for ($curpos = tell ($fh); $line = <$fh>; $curpos = tell ($fh)) {
  541. chomp ($line);
  542. $firstline //= $line;
  543. $verbose and note (sprintf ('%.2f %s', time-$t0, $line));
  544. # generic pass/fail pattern
  545. my $passed;
  546. if ($line =~ /(Can't bind socket to address 127\.0\.0\.1.*$)/) {
  547. # previous instance running
  548. fail ($1);
  549. $got_verdict = 1;
  550. last TAIL;
  551. } elsif (
  552. # configured pass/fail pattern
  553. $test->{'pass'} && $line =~ /$test->{'pass'}/
  554. ) {
  555. $passed = 1;
  556. } elsif ($test->{'fail'} && $line =~ /$test->{'fail'}/) {
  557. $passed = 0;
  558. } elsif ($test->{'noop'} && $line =~ /$test->{'noop'}/) {
  559. next;
  560. } elsif ($line =~ /Synchronization with "ngircd\.test\.server2" done/) {
  561. $passed = $test->{'expect'};
  562. } elsif ($line =~ /Certificate validation failed/) {
  563. $passed = 1 - $test->{'expect'};
  564. } elsif ($line =~ /Fatail: /) {
  565. # some fatal error
  566. fail ($1);
  567. }
  568. if (defined ($passed)) {
  569. if ($passed) {
  570. pass ("Got expected line: '$line'");
  571. } else {
  572. fail ("Got line that should not be there: '$line'");
  573. }
  574. $got_verdict = 1;
  575. last TAIL;
  576. }
  577. }
  578. if (!$server1->poll) {
  579. note ('server1 has left the building');
  580. $got_verdict = 1;
  581. last TAIL;
  582. }
  583. sleep (0.1);
  584. seek ($fh, $curpos, 0);
  585. }
  586. close ($fh);
  587. $server1->kill;
  588. $server2->kill;
  589. wait;
  590. ok ($got_verdict, 'have a verdict');
  591. if ($firstline && $firstline =~ /^\[[0-9]+:[0-9] +[0-9]+\./) {
  592. # have absolute timestamps
  593. my @log = read_file ($server1_log);
  594. my $first_server2_line = scalar (@log);
  595. push @log, read_file ($server2_log);
  596. my $RED = "\e[1;31m";
  597. my $GREEN = "\e[1;32m";
  598. my $NORMAL = "\e[0m";
  599. my @log_sorter;
  600. for (my $i = 0; $i < scalar (@log); $i++) {
  601. my $line = $log[$i];
  602. chomp ($line);
  603. my $srt;
  604. my $server = $i < $first_server2_line ? 1 : 2;
  605. if ($line =~ /^
  606. \[
  607. (?<pid>[0-9]+):
  608. (?<prio>[0-9])\s+
  609. (?<time>[0-9]+\.[0-9]+)
  610. \]\s(?<msg>.+)
  611. $/x) {
  612. $srt = [
  613. $+{'time'},
  614. $server,
  615. $+{'prio'},
  616. $+{'msg'},
  617. ];
  618. } else {
  619. fail ("Cannot parse log line '$line'");
  620. $srt = [ 0, $server, 0, $line ];
  621. }
  622. $log_sorter[$i] = $srt;
  623. }
  624. # sort the log file by time, process, line
  625. my @idx = sort {
  626. $log_sorter[$a][0] <=> $log_sorter[$b][0] ||
  627. $log_sorter[$a][1] <=> $log_sorter[$b][1] ||
  628. $log_sorter[$a][3] cmp $log_sorter[$b][3] ||
  629. $a cmp $b
  630. } 0..$#log_sorter;
  631. note ('combined log ([server:prio ms])');
  632. my $t1 = $log_sorter[$idx[0]][0];
  633. foreach my $idx (@idx) {
  634. my $data = $log_sorter[$idx];
  635. note (sprintf (
  636. '| %s[%s:%d %.6f] %s%s',
  637. ($data->[1] == 1 ? $GREEN : $RED),
  638. $data->[1],
  639. $data->[2],
  640. $data->[0] - $t1,
  641. $data->[3],
  642. $NORMAL,
  643. ));
  644. }
  645. } else {
  646. # Just combine
  647. my @log;
  648. @log = read_file ($server1_log);
  649. note ('server 1 log:');
  650. foreach my $line (@log) {
  651. note ("1: $line");
  652. }
  653. @log = read_file ($server2_log);
  654. note ('server 2 log:');
  655. foreach my $line (@log) {
  656. note ("2: $line");
  657. }
  658. }
  659. }
  660. # start the show
  661. if ($x509_populate) {
  662. note ('setting up x509 stuff');
  663. my $fail_x509;
  664. $ENV{'RNDFILE'} = "$temp_dir/.rnd";
  665. for my $f (qw<dhparams.pem openssl.cnf openssl.san.cnf>) {
  666. copy ("$src_dir/data/$f", "$x509_dir/$f") or
  667. die ("Failed to copy $f: $!");
  668. }
  669. foreach my $command ((
  670. [ "$src_dir/gen-x509-stuff", $x509_dir, qw<CA1 server1.example.com> ],
  671. [ "$src_dir/gen-x509-stuff", $x509_dir, qw<CA1 server2.example.com> ],
  672. [ "$src_dir/gen-x509-stuff", $x509_dir, qw<CA1 server3.example.com> ],
  673. [ "$src_dir/gen-x509-stuff", $x509_dir, qw<CA2 server2.example.com> ],
  674. [ "$src_dir/gen-x509-stuff", $x509_dir, qw<CA3 server1.example.com> ],
  675. [ "$src_dir/gen-x509-stuff", $x509_dir, qw<--revoke CA3 server2.example.com> ],
  676. [ "$src_dir/gen-x509-stuff", $x509_dir, qw<CA4 server1.example.com> ],
  677. [ "$src_dir/gen-x509-stuff", $x509_dir, qw<CA4 *.example.com> ],
  678. [ "$src_dir/gen-x509-stuff", $x509_dir, qw<CA5 server1.example.com> ],
  679. [ "$src_dir/gen-x509-stuff", $x509_dir, qw<CA5 Server2.example.com> ],
  680. [ "$src_dir/gen-x509-stuff", $x509_dir, qw<CA6 server1.example.com> ],
  681. [ "$src_dir/gen-x509-stuff", $x509_dir, qw<--san CA6 server2.example.com> ],
  682. [ "$src_dir/gen-x509-stuff", $x509_dir, qw<--san CA6 server3.example.com> ],
  683. )) {
  684. my $run = Proc::Simple->new;
  685. my $log = "$temp_dir/gen-stuff.log";
  686. write_file ($log, '');
  687. $run->redirect_output ($log, $log);
  688. $run->start (@$command);
  689. my $exit = $run->wait;
  690. my $fail = !is (
  691. $exit,
  692. 0,
  693. 'gen-x509-stuff ' . join (' ', @$command[2..$#$command]),
  694. );
  695. $fail and $fail_x509++;
  696. if ($fail || $verbose) {
  697. my $out = read_file ($log);
  698. $log and note ("Output:\n$out");
  699. }
  700. }
  701. $fail_x509 and exit 1;
  702. if ($exit_after_x509_populate) {
  703. pass ('here we go');
  704. done_testing;
  705. exit 0;
  706. }
  707. }
  708. $ENV{'LD_PRELOAD'} = 'libnss_wrapper.so';
  709. $ENV{'NSS_WRAPPER_HOSTS'} = $hosts_file;
  710. note ('checking mocked resolver');
  711. {
  712. my $fail;
  713. foreach my $hostname (qw<server1 server2>) {
  714. my $output = `getent hosts $hostname`;
  715. is ($?, 0, "resolve $hostname") or $fail++;
  716. is ($?, 0, "resolve $hostname.example.com") or $fail++;
  717. }
  718. }
  719. my @tests;
  720. # check test description integrity
  721. {
  722. my $fail;
  723. my @required = qw<expect :order>; # NB: pass and fail may be missing
  724. foreach my $test (sort keys %tests) {
  725. foreach my $r (@required) {
  726. exists ($tests{$test}{$r}) and next;
  727. fail ("No '$r' field in test '$test'");
  728. $fail++;
  729. }
  730. }
  731. $fail and die ('Cannot continue');
  732. @tests =
  733. sort { $tests{$a}{':order'} <=> $tests{$b}{':order'} }
  734. keys %tests;
  735. foreach my $test (@tests) {
  736. my $got = $tests{$test}{'expect'};
  737. if ($got =~ /^(pass|fail)$/) {
  738. $tests{$test}{'expect'} = $got eq 'pass' ? 1 : 0;
  739. next;
  740. }
  741. fail ("The 'expect' in test '$test' is '$got', not 'pass' or 'fail'");
  742. $fail++;
  743. }
  744. $fail and die ('Cannot continue');
  745. # drop those who are expected to fail
  746. @tests = grep { !$tests{$_}{'unsupported'} } @tests;
  747. }
  748. if (@ARGV) {
  749. my $warned;
  750. foreach my $t (@ARGV) {
  751. if (exists ($tests{$t})) {
  752. $tests{$t}{'unsupported'} and
  753. note ("Warn: Test '$t' is marked unsupported. Expect breakage");
  754. test1 ($t, $tests{$t});
  755. next;
  756. }
  757. fail ("Don't know how to test '$t'");
  758. if (!$warned) {
  759. note ("Available tests:\n" . join ("\n", map { " $_" } @tests));
  760. $warned = 1;
  761. }
  762. }
  763. } else {
  764. # run all
  765. foreach my $test (@tests) {
  766. test1 ($test, $tests{$test});
  767. }
  768. }
  769. done_testing;
  770. exit 0;
  771. =head1 DESCRIPTION
  772. Build ngircd for both TLS linkages, possibly using the following script:
  773. #!/bin/sh
  774. set -e
  775. case "$1" in
  776. openssl | gnutls)
  777. [ -f ./configure ] || ./autogen.sh
  778. ./configure \
  779. --prefix=/usr \
  780. --mandir="\${prefix}/share/man" \
  781. --infodir="\${prefix}/share/info" \
  782. --sysconfdir=/etc/ngircd \
  783. "--with-$1"
  784. make 2>&1 | tee "../build.$1.log"
  785. cp src/ngircd/ngircd "../ngircd-$1"
  786. ;;
  787. *)
  788. echo "Usage: $0 <gnutls|openssl>"
  789. exit 1
  790. ;;
  791. esac
  792. One time only: Create the certificates
  793. perl ngircd-test-tls-link \
  794. --x509-populate \
  795. --x509-dir ../x509-data/ \
  796. --ngircd <path to any ngircd binary>
  797. Then run this program
  798. For GnuTLS:
  799. ngircd-test-tls-link \
  800. --x509-dir ../x509-data/ \
  801. --ngircd ../ngircd-gnutls
  802. Likewise for OpenSSL:
  803. (...)
  804. --ngircd ../ngircd-openssl
  805. For interoperability tests:
  806. ngircd-test-tls-link \
  807. --x509-dir ../x509-data/ \
  808. --ngircd ../ngircd-gnutls:../ngircd-openssl
  809. ... also with the two binaries swapped.
  810. =head1 DEPENDENCIES
  811. The following programs, libraries and Perl modules must be installed:
  812. faketime
  813. openssl
  814. libnss-wrapper
  815. File::Slurp
  816. Proc::Simple
  817. =head1 BUGS
  818. Hack.
  819. =head1 SEE ALSO
  820. ngircd(8)
  821. =head1 AUTHOR
  822. Christoph Biedl C<< <debian.axhn@manchmal.in-ulm.de> >>
  823. =head1 ACKNOWLEDGEMENTS
  824. Alex Barton for ngircd.
  825. =head1 COPYRIGHT & LICENSE
  826. Copyright (C) 2014-2024 Christoph Biedl <debian.axhn@manchmal.in-ulm.de>
  827. This program is free software; you can redistribute it and/or
  828. modify it under the terms of the GNU General Public License as
  829. published by the Free Software Foundation; either version 2 of the
  830. License, or (at your option) any later version.
  831. This package is distributed in the hope that it will be useful,
  832. but WITHOUT ANY WARRANTY; without even the implied warranty of
  833. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  834. GNU General Public License for more details.
  835. You should have received a copy of the GNU General Public License
  836. along with this program. If not, see <http://www.gnu.org/licenses/>
  837. On Debian systems, the complete text of the GNU General Public
  838. License version 2 can be found in
  839. "/usr/share/common-licenses/GPL-2".
  840. =cut