1/*****************************************************************
2**
3**	@(#) soaserial.c -- helper function for the dnssec zone key tools
4**
5**	Copyright (c) Jan 2005, Holger Zuleger HZnet. All rights reserved.
6**
7**	This software is open source.
8**
9**	Redistribution and use in source and binary forms, with or without
10**	modification, are permitted provided that the following conditions
11**	are met:
12**
13**	Redistributions of source code must retain the above copyright notice,
14**	this list of conditions and the following disclaimer.
15**
16**	Redistributions in binary form must reproduce the above copyright notice,
17**	this list of conditions and the following disclaimer in the documentation
18**	and/or other materials provided with the distribution.
19**
20**	Neither the name of Holger Zuleger HZnet nor the names of its contributors may
21**	be used to endorse or promote products derived from this software without
22**	specific prior written permission.
23**
24**	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25**	"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
26**	TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
27**	PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
28**	LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29**	CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30**	SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31**	INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
32**	CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33**	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34**	POSSIBILITY OF SUCH DAMAGE.
35**
36*****************************************************************/
37# include <stdio.h>
38# include <string.h>
39# include <stdlib.h>
40# include <ctype.h>
41# include <sys/types.h>
42# include <sys/stat.h>
43# include <time.h>
44# include <utime.h>
45# include <assert.h>
46#ifdef HAVE_CONFIG_H
47# include <config.h>
48#endif
49# include "config_zkt.h"
50# include "zconf.h"
51# include "log.h"
52# include "debug.h"
53#define extern
54# include "soaserial.h"
55#undef extern
56
57static	int	inc_soa_serial (FILE *fp, int use_unixtime);
58static	int	is_soa_rr (const char *line);
59static	const	char	*strfindstr (const char *str, const char *search);
60
61
62/****************************************************************
63**
64**	int	inc_serial (filename, use_unixtime)
65**
66**	This function depends on a special syntax formating the
67**	SOA record in the zone file!!
68**
69**	To match the SOA record, the SOA RR must be formatted
70**	like this:
71**	@ [ttl]   IN  SOA <master.fq.dn.> <hostmaster.fq.dn.> (
72**	<SPACEes or TABs>      1234567890; serial number
73**	<SPACEes or TABs>      86400	 ; other values
74**				...
75**	The space from the first digit of the serial number to
76**	the first none white space char or to the end of the line
77**	must be at least 10 characters!
78**	So you have to left justify the serial number in a field
79**	of at least 10 characters like this:
80**	<SPACEes or TABs>      1         ; Serial
81**
82****************************************************************/
83int	inc_serial (const char *fname, int use_unixtime)
84{
85	FILE	*fp;
86	char	buf[4095+1];
87	int	error;
88
89	/**
90	   since BIND 9.4, there is a dnssec-signzone option available for
91	   serial number increment.
92	   If the user requests "unixtime"; then use this mechanism.
93	**/
94#if defined(BIND_VERSION) && BIND_VERSION >= 940
95	if ( use_unixtime )
96		return 0;
97#endif
98	if ( (fp = fopen (fname, "r+")) == NULL )
99		return -1;
100
101		/* read until the line matches the beginning of a soa record ... */
102	while ( fgets (buf, sizeof buf, fp) && !is_soa_rr (buf) )
103		;
104
105	if ( feof (fp) )
106	{
107		fclose (fp);
108		return -2;
109	}
110
111	error = inc_soa_serial (fp, use_unixtime);	/* .. inc soa serial no ... */
112
113	if ( fclose (fp) != 0 )
114		return -5;
115	return error;
116}
117
118/*****************************************************************
119**	check if line is the beginning of a SOA RR record, thus
120**	containing the string "IN .* SOA" and ends with a '('
121**	returns 1 if true
122*****************************************************************/
123static	int	is_soa_rr (const char *line)
124{
125	const	char	*p;
126
127	assert ( line != NULL );
128
129	if ( (p = strfindstr (line, "IN")) && strfindstr (p+2, "SOA") )	/* line contains "IN" and "SOA" */
130	{
131		p = line + strlen (line) - 1;
132		while ( p > line && isspace (*p) )
133			p--;
134		if ( *p == '(' )	/* last character have to be a '(' to start a multi line record */
135			return 1;
136	}
137
138	return 0;
139}
140
141/*****************************************************************
142**	Find string 'search' in 'str' and ignore case in comparison.
143**	returns the position of 'search' in 'str' or NULL if not found.
144*****************************************************************/
145static	const	char	*strfindstr (const char *str, const char *search)
146{
147	const	char	*p;
148	int		c;
149
150	assert ( str != NULL );
151	assert ( search != NULL );
152
153	c = tolower (*search);
154	p = str;
155	do {
156		while ( *p && tolower (*p) != c )
157			p++;
158		if ( strncasecmp (p, search, strlen (search)) == 0 )
159			return p;
160		p++;
161	} while ( *p );
162
163	return NULL;
164}
165
166/*****************************************************************
167**	return the serial number of the given time in the form
168**	of YYYYmmdd00 as ulong value
169*****************************************************************/
170static	ulong	serialtime (time_t sec)
171{
172	struct	tm	*t;
173	ulong	serialtime;
174
175	t = gmtime (&sec);
176	serialtime = (t->tm_year + 1900) * 10000;
177	serialtime += (t->tm_mon+1) * 100;
178	serialtime += t->tm_mday;
179	serialtime *= 100;
180
181	return serialtime;
182}
183
184/*****************************************************************
185**	inc_soa_serial (fp, use_unixtime)
186**	increment the soa serial number of the file 'fp'
187**	'fp' must be opened "r+"
188*****************************************************************/
189static	int	inc_soa_serial (FILE *fp, int use_unixtime)
190{
191	int	c;
192	long	pos,	eos;
193	ulong	serial;
194	int	digits;
195	ulong	today;
196
197	/* move forward until any non ws reached */
198	while ( (c = getc (fp)) != EOF && isspace (c) )
199		;
200	ungetc (c, fp);		/* push back the last char */
201
202	pos = ftell (fp);	/* mark position */
203
204	serial = 0L;	/* read in the current serial number */
205	/* be aware of the trailing space in the format string !! */
206	if ( fscanf (fp, "%lu ", &serial) != 1 )	/* try to get serial no */
207		return -3;
208	eos = ftell (fp);	/* mark first non digit/ws character pos */
209
210	digits = eos - pos;
211	if ( digits < 10 )	/* not enough space for serial no ? */
212		return -4;
213
214	today = time (NULL);
215	if ( !use_unixtime )
216	{
217		today = serialtime (today);	/* YYYYmmdd00 */
218		if ( serial > 1970010100L && serial < today )
219			serial = today;			/* set to current time */
220		serial++;			/* increment anyway */
221	}
222
223	fseek (fp, pos, SEEK_SET);	/* go back to the beginning */
224	fprintf (fp, "%-*lu", digits, serial);	/* write as many chars as before */
225
226	return 1;	/* yep! */
227}
228
229/*****************************************************************
230**	return the error text of the inc_serial return coode
231*****************************************************************/
232const	char	*inc_errstr (int err)
233{
234	switch ( err )
235	{
236	case -1:	return "couldn't open zone file for modifying";
237	case -2:	return "unexpected end of file";
238	case -3:	return "no serial number found in zone file";
239	case -4:	return "not enough space left for serialno";
240	case -5:	return "error on closing zone file";
241	}
242	return "";
243}
244
245#ifdef SOA_TEST
246const char *progname;
247main (int argc, char *argv[])
248{
249	ulong	now;
250	int	err;
251	char	cmd[255];
252
253	progname = *argv;
254
255	now = time (NULL);
256	now = serialtime (now);
257	printf ("now = %lu\n", now);
258
259	if ( (err = inc_serial (argv[1], 0)) <= 0 )
260	{
261		error ("can't change serial errno=%d\n", err);
262		exit (1);
263	}
264
265	snprintf (cmd, sizeof(cmd), "head -15 %s", argv[1]);
266	system (cmd);
267}
268#endif
269
270