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