1#!/usr/bin/perl
2#
3# Copyright (c) 2021 Ingo Schwarze <schwarze@openbsd.org>
4#
5# Permission to use, copy, modify, and distribute this software for any
6# purpose with or without fee is hereby granted, provided that the above
7# copyright notice and this permission notice appear in all copies.
8#
9# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16
17use strict;
18use warnings;
19
20my %internal = (
21    asn1 => [qw(
22	ASN1_ENCODING
23	ASN1_STRING_FLAG_BITS_LEFT ASN1_STRING_FLAG_CONT
24	ASN1_STRING_FLAG_MSTRING ASN1_STRING_FLAG_NDEF
25	CHARTYPE_FIRST_ESC_2253 CHARTYPE_LAST_ESC_2253 CHARTYPE_PRINTABLESTRING
26    )],
27    bn => [qw(
28	BN_BITS BN_BITS4 BN_BYTES
29	BN_DEC_CONV BN_DEC_FMT1 BN_DEC_FMT2 BN_DEC_NUM BN_LLONG BN_LONG
30	BN_MASK2 BN_MASK2h BN_MASK2h1 BN_MASK2l
31	BN_TBIT BN_ULLONG
32    )],
33    evp => [qw(
34        EVP_MD_CTRL_ALG_CTRL
35        EVP_MD_CTX_FLAG_CLEANED EVP_MD_CTX_FLAG_REUSE
36    )],
37    objects => [qw(
38	OBJ_bsearch_ OBJ_bsearch_ex_
39    )],
40    x509_vfy => [qw(
41	X509_VERIFY_PARAM_ID
42    )]
43);
44
45my %obsolete = (
46    asn1 => [qw(
47	ASN1_dup ASN1_d2i_bio ASN1_d2i_bio_of ASN1_d2i_fp ASN1_d2i_fp_of
48	ASN1_i2d_bio ASN1_i2d_bio_of ASN1_i2d_bio_of_const
49	ASN1_i2d_fp ASN1_i2d_fp_of ASN1_i2d_fp_of_const
50	ASN1_LONG_UNDEF
51	BIT_STRING_BITNAME
52	ub_title
53	V_ASN1_PRIMATIVE_TAG
54	X509_algor_st
55    )],
56    bio => [qw(
57	asn1_ps_func
58	BIO_C_GET_PROXY_PARAM BIO_C_GET_SOCKS
59	BIO_C_SET_PROXY_PARAM BIO_C_SET_SOCKS
60	BIO_get_no_connect_return BIO_get_proxies
61	BIO_get_proxy_header BIO_get_url
62	BIO_set_filter_bio BIO_set_no_connect_return BIO_set_proxies
63	BIO_set_proxy_cb BIO_set_proxy_header BIO_set_url
64    )],
65    bn => [qw(
66	BN_HEX_FMT1 BN_HEX_FMT2 BN_MASK
67    )],
68    evp => [qw(
69        EVP_MD_CTRL_DIGALGID
70        EVP_MD_CTX_FLAG_NON_FIPS_ALLOW EVP_MD_CTX_FLAG_PAD_MASK
71        EVP_MD_CTX_FLAG_PAD_PKCS1 EVP_MD_CTX_FLAG_PAD_PSS
72    )],
73);
74
75my %postponed = (
76    asn1 => [qw(
77	ASN1_ITEM_EXP ASN1_ITEM_ptr ASN1_ITEM_ref ASN1_ITEM_rptr
78	ASN1_TEMPLATE ASN1_TLC
79	CHECKED_D2I_OF CHECKED_I2D_OF CHECKED_NEW_OF
80	CHECKED_PPTR_OF CHECKED_PTR_OF
81	DECLARE_ASN1_ALLOC_FUNCTIONS DECLARE_ASN1_ALLOC_FUNCTIONS_name
82	DECLARE_ASN1_ENCODE_FUNCTIONS DECLARE_ASN1_ENCODE_FUNCTIONS_const
83	DECLARE_ASN1_FUNCTIONS DECLARE_ASN1_FUNCTIONS_const
84	DECLARE_ASN1_FUNCTIONS_fname DECLARE_ASN1_FUNCTIONS_name
85	DECLARE_ASN1_ITEM
86	DECLARE_ASN1_NDEF_FUNCTION
87	DECLARE_ASN1_PRINT_FUNCTION DECLARE_ASN1_PRINT_FUNCTION_fname
88	DECLARE_ASN1_SET_OF
89	D2I_OF
90	IMPLEMENT_ASN1_SET_OF
91	I2D_OF I2D_OF_const
92	TYPEDEF_D2I_OF TYPEDEF_D2I2D_OF TYPEDEF_I2D_OF
93    )],
94    x509 => [qw(
95	d2i_PBEPARAM d2i_PBE2PARAM d2i_PBKDF2PARAM
96	i2d_PBEPARAM i2d_PBE2PARAM i2d_PBKDF2PARAM
97	NETSCAPE_SPKAC NETSCAPE_SPKI
98	PBEPARAM PBEPARAM_free PBEPARAM_new
99	PBE2PARAM PBE2PARAM_free PBE2PARAM_new
100	PBKDF2PARAM PBKDF2PARAM_free PBKDF2PARAM_new
101	PKCS5_pbe_set PKCS5_pbe_set0_algor
102	PKCS5_pbe2_set PKCS5_pbe2_set_iv
103	PKCS5_pbkdf2_set
104    )]
105);
106
107my $MANW = 'man -M /usr/share/man -w';
108my $srcdir = '/usr/src/lib/libcrypto/man';
109my $hfile = '/usr/include/openssl';
110
111my $in_cplusplus = 0;
112my $in_comment = 0;
113my $in_define = 0;
114my $in_function = 0;
115my $in_struct = 0;
116my $in_typedef_struct = 0;
117my %expect_undoc = ();
118my %found_undoc = ();
119my $verbose = 0;
120
121if (defined $ARGV[0] && $ARGV[0] eq '-v') {
122	$verbose = 1;
123	shift @ARGV;
124}
125$#ARGV == 0 or die "usage: $0 [-v] headername";
126$hfile .= "/$ARGV[0].h";
127open my $in_fh, '<', $hfile or die "$hfile: $!";
128
129$expect_undoc{$_} = 1 foreach @{$internal{$ARGV[0]}};
130$expect_undoc{$_} = 1 foreach @{$obsolete{$ARGV[0]}};
131$expect_undoc{$_} = 1 foreach @{$postponed{$ARGV[0]}};
132
133while (<$in_fh>) {
134try_again:
135	chomp;
136	my $line = $_;
137
138	# C language comments.
139
140	if ($in_comment) {
141		unless (s/.*?\*\///) {
142			print "-- $line\n" if $verbose;
143			next;
144		}
145		$in_comment = 0;
146	}
147	while (/\/\*/) {
148		s/\s*\/\*.*?\*\/// and next;
149		s/\s*\/\*.*// and $in_comment = 1;
150	}
151
152	# End C++ stuff.
153
154	if ($in_cplusplus) {
155		/^#endif$/ and $in_cplusplus = 0;
156		print "-- $line\n" if $verbose;
157		next;
158	}
159
160	# End declarations of structs.
161
162	if ($in_struct) {
163		if (/^\s*union\s+{$/) {
164			print "-s $line\n" if $verbose;
165			$in_struct++;
166			next;
167		}
168		unless (s/^\s*\}//) {
169			print "-s $line\n" if $verbose;
170			next;
171		}
172		if (--$in_struct && /^\s+\w+;$/) {
173			print "-s $line\n" if $verbose;
174			next;
175		}
176		unless ($in_typedef_struct) {
177			/^\s*;$/ or die "at end of struct: $_";
178			print "-s $line\n" if $verbose;
179			next;
180		}
181		$in_typedef_struct = 0;
182		my ($id) = /^\s*(\w+);$/
183		    or die "at end of typedef struct: $_";
184		unless (system "$MANW -k 'Vt~^$id\$' > /dev/null 2>&1") {
185			print "Vt $line\n" if $verbose;
186			next;
187		}
188		if ($expect_undoc{$id}) {
189			print "V- $line\n" if $verbose;
190			$found_undoc{$id} = 1;
191			next;
192		}
193		if ($verbose) {
194			print "XX $line\n";
195		} else {
196			warn "not found: typedef struct $id";
197		}
198		next;
199	}
200
201	# End macro definitions.
202
203	if ($in_define) {
204		/\\$/ or $in_define = 0;
205		print "-d $line\n" if $verbose;
206		next;
207	}
208
209	# End function declarations.
210
211	if ($in_function) {
212		/^\s/ or die "function arguments not indented: $_";
213		/\);$/ and $in_function = 0;
214		print "-f $line\n" if $verbose;
215		next;
216	}
217
218	# Begin C++ stuff.
219
220	if (/^#ifdef\s+__cplusplus$/) {
221		$in_cplusplus = 1;
222		print "-- $line\n" if $verbose;
223		next;
224	}
225
226	# Uninteresting lines.
227
228	if (/^\s*$/ ||
229	    /^DECLARE_STACK_OF\(\w+\)$/ ||
230	    /^TYPEDEF_D2I2D_OF\(\w+\);$/ ||
231	    /^#define __bounded__\(\w+, \w+, \w+\)$/ ||
232	    /^#define HEADER_\w+_H$/ ||
233	    /^#endif$/ ||
234	    /^#else$/ ||
235	    /^extern\s+const\s+ASN1_ITEM\s+\w+_it;$/ ||
236	    /^#\s*include\s/ ||
237	    /^#ifn?def\s/ ||
238	    /^#if !?defined/ ||
239	    /^#undef\s+BN_LLONG$/) {
240		print "-- $line\n" if $verbose;
241		next;
242	}
243
244	# Begin declarations of structs.
245
246	if (/^(typedef )?(?:struct|enum)(?: \w+)? \{$/) {
247		$in_struct = 1;
248		$1 and $in_typedef_struct = 1;
249		print "-s $line\n" if $verbose;
250		next;
251	}
252
253	# Handle macros.
254
255	if (my ($id) = /^#\s*define\s+(\w+)\s+\S/) {
256		/\\$/ and $in_define = 1;
257		if ($id eq 'BN_ULONG' &&
258		    not system "$MANW -k 'Vt~^$id\$' > /dev/null 2>&1") {
259			print "Vt $line\n" if $verbose;
260			next;
261		}
262		unless (system "$MANW -k 'Dv~^$id\$' > /dev/null 2>&1") {
263			print "Dv $line\n" if $verbose;
264			next;
265		}
266		unless (system "$MANW $id > /dev/null 2>&1") {
267			print "Fn $line\n" if $verbose;
268			next;
269		}
270		unless (system qw/grep -qR/, '^\.\\\\" .*\<' . $id . '\>',
271		    "$srcdir/") {
272			print "D- $line\n" if $verbose;
273			next;
274		}
275		if ($id =~ /^ASN1_PCTX_FLAGS_\w+$/) {
276			print "D- $line\n" if $verbose;
277			next;
278		}
279		if ($id =~ /^(?:ASN1|BIO|BN|EVP|X509(?:V3)?)_[FR]_\w+$/) {
280			print "D- $line\n" if $verbose;
281			next;
282		}
283		if ($id =~ /^X509_V_ERR_\w+$/) {
284			print "D- $line\n" if $verbose;
285			next;
286		}
287		if ($id =~ /^(?:SN|LN|NID|OBJ)_\w+$/) {
288			print "D- $line\n" if $verbose;
289			next;
290		}
291		if ($expect_undoc{$id}) {
292			print "D- $line\n" if $verbose;
293			$found_undoc{$id} = 1;
294			next;
295		}
296		if ($verbose) {
297			print "XX $line\n";
298		} else {
299			warn "not found: #define $id";
300		}
301		next;
302	}
303	if (my ($id) = /^#\s*define\s+(\w+)\(/) {
304		/\\$/ and $in_define = 1;
305		unless (system "$MANW $id > /dev/null 2>&1") {
306			print "Fn $line\n" if $verbose;
307			next;
308		}
309		unless (system qw/grep -qR/, '^\.\\\\" .*\<' . $id . '\>',
310		    "$srcdir/") {
311			print "F- $line\n" if $verbose;
312			next;
313		}
314		if ($expect_undoc{$id}) {
315			print "F- $line\n" if $verbose;
316			$found_undoc{$id} = 1;
317			next;
318		}
319		if ($verbose) {
320			print "XX $line\n";
321		} else {
322			warn "not found: #define $id()";
323		}
324		next;
325	}
326	if (my ($id) = /^#\s*define\s+(\w+)$/) {
327		if ($expect_undoc{$id}) {
328			print "-- $line\n" if $verbose;
329			$found_undoc{$id} = 1;
330			next;
331		}
332		if ($verbose) {
333			print "XX $line\n";
334		} else {
335			warn "not found: #define $id";
336		}
337		next;
338	}
339
340	# Handle global variables.
341
342	if (my ($id) = /^extern\s+int\s+(\w+);$/) {
343		unless (system "$MANW -k 'Va~^$id\$' > /dev/null 2>&1") {
344			print "Va $line\n" if $verbose;
345			next;
346		}
347		if ($verbose) {
348			print "XX $line\n";
349		} else {
350			warn "not found: extern int $id";
351		}
352		next;
353	}
354
355	# Handle variable type declarations.
356
357	if (my ($id) = /^struct\s+(\w+);$/) {
358		unless (system "$MANW -k 'Vt~^$id\$' > /dev/null 2>&1") {
359			print "Vt $line\n" if $verbose;
360			next;
361		}
362		if ($expect_undoc{$id}) {
363			print "V- $line\n" if $verbose;
364			$found_undoc{$id} = 1;
365			next;
366		}
367		if ($verbose) {
368			print "XX $line\n";
369		} else {
370			warn "not found: struct $id";
371		}
372		next;
373	}
374
375	if (my ($id) = /^typedef\s+(?:const\s+)?(?:struct\s+)?\S+\s+(\w+);$/) {
376		unless (system "$MANW -k 'Vt~^$id\$' > /dev/null 2>&1") {
377			print "Vt $line\n" if $verbose;
378			next;
379		}
380		if ($expect_undoc{$id}) {
381			print "V- $line\n" if $verbose;
382			$found_undoc{$id} = 1;
383			next;
384		}
385		if ($verbose) {
386			print "XX $line\n";
387		} else {
388			warn "not found: typedef $id";
389		}
390		next;
391	}
392
393	if (my ($id) =/^typedef\s+\w+(?:\s+\*)?\s+\(\*(\w+)\)\(/) {
394		/\);$/ or $in_function = 1;
395		unless (system "$MANW $id > /dev/null 2>&1") {
396			print "Fn $line\n" if $verbose;
397			next;
398		}
399		if ($verbose) {
400			print "XX $line\n";
401		} else {
402			warn "not found: function type (*$id)()";
403		}
404		next;
405	}
406
407	# Handle function declarations.
408
409	if (/^\w+(?:\(\w+\))?(?:\s+\w+)*\s+(?:\(?\*\s*)?(\w+)\(/) {
410		my $id = $1;
411		/\);$/ or $in_function = 1;
412		unless (system "$MANW $id > /dev/null 2>&1") {
413			print "Fn $line\n" if $verbose;
414			next;
415		}
416		# These functions are still provided by OpenSSL
417		# and still used by the Python test suite,
418		# but intentionally undocumented because nothing
419		# else uses them according to tb@, Dec 3, 2021.
420		if ($id =~ /NETSCAPE_(?:CERT_SEQUENCE|SPKAC|SPKI)/) {
421			print "F- $line\n" if $verbose;
422			next;
423		}
424		unless (system qw/grep -qR/, '^\.\\\\" .*\<' . $id . '\>',
425		    "$srcdir/") {
426			print "F- $line\n" if $verbose;
427			next;
428		}
429		if ($expect_undoc{$id}) {
430			print "F- $line\n" if $verbose;
431			$found_undoc{$id} = 1;
432			next;
433		}
434		if ($id =~ /^ASN1_PCTX_\w+$/) {
435			print "F- $line\n" if $verbose;
436			next;
437		}
438		if ($verbose) {
439			print "XX $line\n";
440		} else {
441			warn "not found: function $id()";
442		}
443		next;
444	}
445	if (/^int$/) {
446		$_ .= ' ' . <$in_fh>;
447		goto try_again;
448	}
449	if (/ \*$/) {
450		$_ .= <$in_fh>;
451		goto try_again;
452	}
453	# The name of the function return type is so long
454	# that it requires a line break afterwards.
455	if (/^\w{30,}$/) {
456		my $next_line = <$in_fh>;
457		if ($next_line =~ /^ {4}\w/) {
458			$_ .= $next_line;
459			goto try_again;
460		}
461	}
462	die "parse error: $_";
463}
464close $in_fh;
465foreach (keys %expect_undoc) {
466	warn "expected as undocumented but not found: $_"
467	    unless $found_undoc{$_};
468}
469exit 0;
470