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_OBJECT_FLAG_CRITICAL ASN1_OBJECT_FLAG_DYNAMIC
24	ASN1_OBJECT_FLAG_DYNAMIC_DATA ASN1_OBJECT_FLAG_DYNAMIC_STRINGS
25	ASN1_STRING_FLAG_BITS_LEFT ASN1_STRING_FLAG_CONT
26	ASN1_STRING_FLAG_MSTRING ASN1_STRING_FLAG_NDEF
27	CHARTYPE_FIRST_ESC_2253 CHARTYPE_LAST_ESC_2253 CHARTYPE_PRINTABLESTRING
28    )],
29    objects => [qw(
30	OBJ_bsearch OBJ_bsearch_ OBJ_bsearch_ex OBJ_bsearch_ex_
31    )],
32    x509_vfy => [qw(
33	X509_VERIFY_PARAM_ID
34    )]
35);
36
37my %obsolete = (
38    asn1 => [qw(
39	ASN1_const_CTX ASN1_CTX
40	ASN1_dup ASN1_d2i_bio ASN1_d2i_bio_of ASN1_d2i_fp ASN1_d2i_fp_of
41	ASN1_i2d_bio ASN1_i2d_bio_of ASN1_i2d_bio_of_const
42	ASN1_i2d_fp ASN1_i2d_fp_of ASN1_i2d_fp_of_const
43	ASN1_LONG_UNDEF
44	d2i_NETSCAPE_X509 i2d_NETSCAPE_X509
45	NETSCAPE_X509 NETSCAPE_X509_free NETSCAPE_X509_new
46	ub_title
47	V_ASN1_PRIMATIVE_TAG
48	X509_algor_st
49    )],
50    objects => [qw(
51	_DECLARE_OBJ_BSEARCH_CMP_FN
52	DECLARE_OBJ_BSEARCH_CMP_FN DECLARE_OBJ_BSEARCH_GLOBAL_CMP_FN
53	IMPLEMENT_OBJ_BSEARCH_CMP_FN IMPLEMENT_OBJ_BSEARCH_GLOBAL_CMP_FN
54    )],
55    x509 => [qw(
56	X509_EX_V_INIT X509_EX_V_NETSCAPE_HACK
57	X509_EXT_PACK_STRING X509_EXT_PACK_UNKNOWN
58    )]
59);
60
61my %postponed = (
62    asn1 => [qw(
63	ASN1_ITEM_EXP ASN1_ITEM_ptr ASN1_ITEM_ref ASN1_ITEM_rptr
64	ASN1_TEMPLATE ASN1_TLC
65	CHECKED_D2I_OF CHECKED_I2D_OF CHECKED_NEW_OF
66	CHECKED_PPTR_OF CHECKED_PTR_OF
67	DECLARE_ASN1_ALLOC_FUNCTIONS DECLARE_ASN1_ALLOC_FUNCTIONS_name
68	DECLARE_ASN1_ENCODE_FUNCTIONS DECLARE_ASN1_ENCODE_FUNCTIONS_const
69	DECLARE_ASN1_FUNCTIONS DECLARE_ASN1_FUNCTIONS_const
70	DECLARE_ASN1_FUNCTIONS_fname DECLARE_ASN1_FUNCTIONS_name
71	DECLARE_ASN1_ITEM
72	DECLARE_ASN1_NDEF_FUNCTION
73	DECLARE_ASN1_PRINT_FUNCTION DECLARE_ASN1_PRINT_FUNCTION_fname
74	DECLARE_ASN1_SET_OF
75	D2I_OF
76	IMPLEMENT_ASN1_SET_OF
77	I2D_OF I2D_OF_const
78	TYPEDEF_D2I_OF TYPEDEF_D2I2D_OF TYPEDEF_I2D_OF
79    )],
80    x509 => [qw(
81	d2i_PBEPARAM d2i_PBE2PARAM d2i_PBKDF2PARAM
82	i2d_PBEPARAM i2d_PBE2PARAM i2d_PBKDF2PARAM
83	NETSCAPE_SPKAC NETSCAPE_SPKI
84	PBEPARAM PBEPARAM_free PBEPARAM_new
85	PBE2PARAM PBE2PARAM_free PBE2PARAM_new
86	PBKDF2PARAM PBKDF2PARAM_free PBKDF2PARAM_new
87	PKCS5_pbe_set PKCS5_pbe_set0_algor
88	PKCS5_pbe2_set PKCS5_pbe2_set_iv
89	PKCS5_pbkdf2_set
90    )]
91);
92
93my $MANW = 'man -M /usr/share/man -w';
94my $srcdir = '/usr/src/lib/libcrypto/man';
95my $hfile = '/usr/include/openssl';
96
97my $in_cplusplus = 0;
98my $in_comment = 0;
99my $in_define = 0;
100my $in_function = 0;
101my $in_struct = 0;
102my $in_typedef_struct = 0;
103my %undoc = ();
104my $verbose = 0;
105
106if (defined $ARGV[0] && $ARGV[0] eq '-v') {
107	$verbose = 1;
108	shift @ARGV;
109}
110$#ARGV == 0 or die "usage: $0 [-v] headername";
111$hfile .= "/$ARGV[0].h";
112open my $in_fh, '<', $hfile or die "$hfile: $!";
113
114$undoc{$_} = 1 foreach @{$internal{$ARGV[0]}};
115$undoc{$_} = 1 foreach @{$obsolete{$ARGV[0]}};
116$undoc{$_} = 1 foreach @{$postponed{$ARGV[0]}};
117
118while (<$in_fh>) {
119try_again:
120	chomp;
121	my $line = $_;
122
123	# C language comments.
124
125	if ($in_comment) {
126		unless (s/.*?\*\///) {
127			print "-- $line\n" if $verbose;
128			next;
129		}
130		$in_comment = 0;
131	}
132	while (/\/\*/) {
133		s/\s*\/\*.*?\*\/// and next;
134		s/\s*\/\*.*// and $in_comment = 1;
135	}
136
137	# End C++ stuff.
138
139	if ($in_cplusplus) {
140		/^#endif$/ and $in_cplusplus = 0;
141		print "-- $line\n" if $verbose;
142		next;
143	}
144
145	# End declarations of structs.
146
147	if ($in_struct) {
148		if (/^\s*union\s+{$/) {
149			print "-s $line\n" if $verbose;
150			$in_struct++;
151			next;
152		}
153		unless (s/^\s*\}//) {
154			print "-s $line\n" if $verbose;
155			next;
156		}
157		if (--$in_struct && /^\s+\w+;$/) {
158			print "-s $line\n" if $verbose;
159			next;
160		}
161		unless ($in_typedef_struct) {
162			/^\s*;$/ or die "at end of struct: $_";
163			print "-s $line\n" if $verbose;
164			next;
165		}
166		$in_typedef_struct = 0;
167		my ($id) = /^\s*(\w+);$/
168		    or die "at end of typedef struct: $_";
169		unless (system "$MANW -k Vt=$id > /dev/null 2>&1") {
170			print "Vt $line\n" if $verbose;
171			next;
172		}
173		if ($undoc{$id}) {
174			print "V- $line\n" if $verbose;
175			delete $undoc{$id};
176			next;
177		}
178		if ($verbose) {
179			print "XX $line\n";
180		} else {
181			warn "not found: typedef struct $id";
182		}
183		next;
184	}
185
186	# End macro definitions.
187
188	if ($in_define) {
189		/\\$/ or $in_define = 0;
190		print "-d $line\n" if $verbose;
191		next;
192	}
193
194	# End function declarations.
195
196	if ($in_function) {
197		/^\s/ or die "function arguments not indented: $_";
198		/\);$/ and $in_function = 0;
199		print "-f $line\n" if $verbose;
200		next;
201	}
202
203	# Begin C++ stuff.
204
205	if (/^#ifdef\s+__cplusplus$/) {
206		$in_cplusplus = 1;
207		print "-- $line\n" if $verbose;
208		next;
209	}
210
211	# Uninteresting lines.
212
213	if (/^\s*$/ ||
214	    /^DECLARE_STACK_OF\(\w+\)$/ ||
215	    /^TYPEDEF_D2I2D_OF\(\w+\);$/ ||
216	    /^#define HEADER_\w+_H$/ ||
217	    /^#define USE_OBJ_MAC$/ ||
218	    /^#endif$/ ||
219	    /^#else$/ ||
220	    /^extern\s+const\s+ASN1_ITEM\s+\w+_it;$/ ||
221	    /^#include\s/ ||
222	    /^#ifn?def\s/ ||
223	    /^#if defined/) {
224		print "-- $line\n" if $verbose;
225		next;
226	}
227
228	# Begin declarations of structs.
229
230	if (/^(typedef )?(?:struct|enum)(?: \w+)? \{$/) {
231		$in_struct = 1;
232		$1 and $in_typedef_struct = 1;
233		print "-s $line\n" if $verbose;
234		next;
235	}
236
237	# Handle macros.
238
239	if (my ($id) = /^#\s*define\s+(\w+)\s+\S/) {
240		/\\$/ and $in_define = 1;
241		unless (system "$MANW -k Dv=$id > /dev/null 2>&1") {
242			print "Dv $line\n" if $verbose;
243			next;
244		}
245		unless (system "$MANW $id > /dev/null 2>&1") {
246			print "Fn $line\n" if $verbose;
247			next;
248		}
249		unless (system qw/grep -qR/, '^\.\\\\" .*\<' . $id . '\>',
250		    "$srcdir/") {
251			print "D- $line\n" if $verbose;
252			next;
253		}
254		if ($id =~ /^ASN1_PCTX_FLAGS_\w+$/) {
255			print "D- $line\n" if $verbose;
256			next;
257		}
258		if ($id =~ /^(?:ASN1|X509(?:V3)?)_[FR]_\w+$/) {
259			print "D- $line\n" if $verbose;
260			next;
261		}
262		if ($id =~ /^X509_V_ERR_\w+$/) {
263			print "D- $line\n" if $verbose;
264			next;
265		}
266		if ($id =~ /^(?:SN|LN|NID|OBJ)_\w+$/) {
267			print "D- $line\n" if $verbose;
268			next;
269		}
270		if ($undoc{$id}) {
271			print "D- $line\n" if $verbose;
272			delete $undoc{$id};
273			next;
274		}
275		if ($verbose) {
276			print "XX $line\n";
277		} else {
278			warn "not found: #define $id";
279		}
280		next;
281	}
282	if (my ($id) = /^#\s*define\s+(\w+)\(/) {
283		/\\$/ and $in_define = 1;
284		unless (system "$MANW $id > /dev/null 2>&1") {
285			print "Fn $line\n" if $verbose;
286			next;
287		}
288		unless (system qw/grep -qR/, '^\.\\\\" .*\<' . $id . '\>',
289		    "$srcdir/") {
290			print "F- $line\n" if $verbose;
291			next;
292		}
293		if ($undoc{$id}) {
294			print "F- $line\n" if $verbose;
295			delete $undoc{$id};
296			next;
297		}
298		if ($verbose) {
299			print "XX $line\n";
300		} else {
301			warn "not found: #define $id()";
302		}
303		next;
304	}
305
306	# Handle global variables.
307
308	if (my ($id) = /^extern\s+int\s+(\w+);$/) {
309		unless (system "$MANW -k Va=$id > /dev/null 2>&1") {
310			print "Va $line\n" if $verbose;
311			next;
312		}
313		if ($verbose) {
314			print "XX $line\n";
315		} else {
316			warn "not found: extern int $id";
317		}
318		next;
319	}
320
321	# Handle variable type declarations.
322
323	if (my ($id) = /^struct\s+(\w+);$/) {
324		unless (system "$MANW -k Vt=$id > /dev/null 2>&1") {
325			print "Vt $line\n" if $verbose;
326			next;
327		}
328		if ($undoc{$id}) {
329			print "V- $line\n" if $verbose;
330			delete $undoc{$id};
331			next;
332		}
333		if ($verbose) {
334			print "XX $line\n";
335		} else {
336			warn "not found: struct $id";
337		}
338		next;
339	}
340
341	if (my ($id) = /^typedef\s+(?:const\s+)?(?:struct\s+)?\S+\s+(\w+);$/) {
342		unless (system "$MANW -k Vt=$id > /dev/null 2>&1") {
343			print "Vt $line\n" if $verbose;
344			next;
345		}
346		if ($undoc{$id}) {
347			print "V- $line\n" if $verbose;
348			delete $undoc{$id};
349			next;
350		}
351		if ($verbose) {
352			print "XX $line\n";
353		} else {
354			warn "not found: typedef $id";
355		}
356		next;
357	}
358
359	if (my ($id) =/^typedef\s+\w+(?:\s+\*)?\s+\(\*(\w+)\)\(/) {
360		/\);$/ or $in_function = 1;
361		unless (system "$MANW $id > /dev/null 2>&1") {
362			print "Fn $line\n" if $verbose;
363			next;
364		}
365		if ($verbose) {
366			print "XX $line\n";
367		} else {
368			warn "not found: function type (*$id)()";
369		}
370		next;
371	}
372
373	# Handle function declarations.
374
375	if (/^\w+(?:\(\w+\))?(?:\s+\w+)*\s+(?:\(?\*\s*)?(\w+)\(/) {
376		my $id = $1;
377		/\);$/ or $in_function = 1;
378		unless (system "$MANW $id > /dev/null 2>&1") {
379			print "Fn $line\n" if $verbose;
380			next;
381		}
382		# These functions are still provided by OpenSSL
383		# and still used by the Python test suite,
384		# but intentionally undocumented because nothing
385		# else uses them according to tb@, Dec 3, 2021.
386		if ($id =~ /NETSCAPE_(?:CERT_SEQUENCE|SPKAC|SPKI)/) {
387			print "F- $line\n" if $verbose;
388			next;
389		}
390		unless (system qw/grep -qR/, '^\.\\\\" .*\<' . $id . '\>',
391		    "$srcdir/") {
392			print "F- $line\n" if $verbose;
393			next;
394		}
395		if ($undoc{$id}) {
396			print "F- $line\n" if $verbose;
397			delete $undoc{$id};
398			next;
399		}
400		if ($id =~ /^ASN1_PCTX_\w+$/) {
401			print "F- $line\n" if $verbose;
402			next;
403		}
404		if ($verbose) {
405			print "XX $line\n";
406		} else {
407			warn "not found: function $id()";
408		}
409		next;
410	}
411	if (/ \*$/) {
412		$_ .= <$in_fh>;
413		goto try_again;
414	}
415	die "parse error: $_";
416}
417close $in_fh;
418warn "expected as undocumented but not found: $_" foreach keys %undoc;
419exit 0;
420