/*****************************************************************
**
** @(#) soaserial.c -- helper function for the dnssec zone key tools
**
** Copyright (c) Jan 2005, Holger Zuleger HZnet. All rights reserved.
**
** This software is open source.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** Redistributions of source code must retain the above copyright notice,
** this list of conditions and the following disclaimer.
**
** Redistributions in binary form must reproduce the above copyright notice,
** this list of conditions and the following disclaimer in the documentation
** and/or other materials provided with the distribution.
**
** Neither the name of Holger Zuleger HZnet nor the names of its contributors may
** be used to endorse or promote products derived from this software without
** specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
** TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
** PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
** POSSIBILITY OF SUCH DAMAGE.
**
*****************************************************************/
# include <stdio.h>
# include <string.h>
# include <stdlib.h>
# include <ctype.h>
# include <sys/types.h>
# include <sys/stat.h>
# include <time.h>
# include <utime.h>
# include <assert.h>
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
# include "config_zkt.h"
# include "zconf.h"
# include "log.h"
# include "debug.h"
#define extern
# include "soaserial.h"
#undef extern
static int inc_soa_serial (FILE *fp, int use_unixtime);
static int is_soa_rr (const char *line);
static const char *strfindstr (const char *str, const char *search);
/****************************************************************
**
** int inc_serial (filename, use_unixtime)
**
** This function depends on a special syntax formating the
** SOA record in the zone file!!
**
** To match the SOA record, the SOA RR must be formatted
** like this:
** @ [ttl] IN SOA <master.fq.dn.> <hostmaster.fq.dn.> (
** <SPACEes or TABs> 1234567890; serial number
** <SPACEes or TABs> 86400 ; other values
** ...
** The space from the first digit of the serial number to
** the first none white space char or to the end of the line
** must be at least 10 characters!
** So you have to left justify the serial number in a field
** of at least 10 characters like this:
** <SPACEes or TABs> 1 ; Serial
**
****************************************************************/
int inc_serial (const char *fname, int use_unixtime)
{
FILE *fp;
char buf[4095+1];
int error;
/**
since BIND 9.4, there is a dnssec-signzone option available for
serial number increment.
If the user requests "unixtime"; then use this mechanism.
**/
#if defined(BIND_VERSION) && BIND_VERSION >= 940
if ( use_unixtime )
return 0;
#endif
if ( (fp = fopen (fname, "r+")) == NULL )
return -1;
/* read until the line matches the beginning of a soa record ... */
while ( fgets (buf, sizeof buf, fp) && !is_soa_rr (buf) )
;
if ( feof (fp) )
{
fclose (fp);
return -2;
}
error = inc_soa_serial (fp, use_unixtime); /* .. inc soa serial no ... */
if ( fclose (fp) != 0 )
return -5;
return error;
}
/*****************************************************************
** check if line is the beginning of a SOA RR record, thus
** containing the string "IN .* SOA" and ends with a '('
** returns 1 if true
*****************************************************************/
static int is_soa_rr (const char *line)
{
const char *p;
assert ( line != NULL );
if ( (p = strfindstr (line, "IN")) && strfindstr (p+2, "SOA") ) /* line contains "IN" and "SOA" */
{
p = line + strlen (line) - 1;
while ( p > line && isspace (*p) )
p--;
if ( *p == '(' ) /* last character have to be a '(' to start a multi line record */
return 1;
}
return 0;
}
/*****************************************************************
** Find string 'search' in 'str' and ignore case in comparison.
** returns the position of 'search' in 'str' or NULL if not found.
*****************************************************************/
static const char *strfindstr (const char *str, const char *search)
{
const char *p;
int c;
assert ( str != NULL );
assert ( search != NULL );
c = tolower (*search);
p = str;
do {
while ( *p && tolower (*p) != c )
p++;
if ( strncasecmp (p, search, strlen (search)) == 0 )
return p;
p++;
} while ( *p );
return NULL;
}
/*****************************************************************
** return the serial number of the given time in the form
** of YYYYmmdd00 as ulong value
*****************************************************************/
static ulong serialtime (time_t sec)
{
struct tm *t;
ulong serialtime;
t = gmtime (&sec);
serialtime = (t->tm_year + 1900) * 10000;
serialtime += (t->tm_mon+1) * 100;
serialtime += t->tm_mday;
serialtime *= 100;
return serialtime;
}
/*****************************************************************
** inc_soa_serial (fp, use_unixtime)
** increment the soa serial number of the file 'fp'
** 'fp' must be opened "r+"
*****************************************************************/
static int inc_soa_serial (FILE *fp, int use_unixtime)
{
int c;
long pos, eos;
ulong serial;
int digits;
ulong today;
/* move forward until any non ws reached */
while ( (c = getc (fp)) != EOF && isspace (c) )
;
ungetc (c, fp); /* push back the last char */
pos = ftell (fp); /* mark position */
serial = 0L; /* read in the current serial number */
/* be aware of the trailing space in the format string !! */
if ( fscanf (fp, "%lu ", &serial) != 1 ) /* try to get serial no */
return -3;
eos = ftell (fp); /* mark first non digit/ws character pos */
digits = eos - pos;
if ( digits < 10 ) /* not enough space for serial no ? */
return -4;
today = time (NULL);
if ( !use_unixtime )
{
today = serialtime (today); /* YYYYmmdd00 */
if ( serial > 1970010100L && serial < today )
serial = today; /* set to current time */
serial++; /* increment anyway */
}
fseek (fp, pos, SEEK_SET); /* go back to the beginning */
fprintf (fp, "%-*lu", digits, serial); /* write as many chars as before */
return 1; /* yep! */
}
/*****************************************************************
** return the error text of the inc_serial return coode
*****************************************************************/
const char *inc_errstr (int err)
{
switch ( err )
{
case -1: return "couldn't open zone file for modifying";
case -2: return "unexpected end of file";
case -3: return "no serial number found in zone file";
case -4: return "not enough space left for serialno";
case -5: return "error on closing zone file";
}
return "";
}
#ifdef SOA_TEST
const char *progname;
main (int argc, char *argv[])
{
ulong now;
int err;
char cmd[255];
progname = *argv;
now = time (NULL);
now = serialtime (now);
printf ("now = %lu\n", now);
if ( (err = inc_serial (argv[1], 0)) <= 0 )
{
error ("can't change serial errno=%d\n", err);
exit (1);
}
snprintf (cmd, sizeof(cmd), "head -15 %s", argv[1]);
system (cmd);
}
#endif