1 /* $NetBSD: duration.c,v 1.4 2025/01/26 16:25:45 christos Exp $ */ 2 3 /* 4 * Copyright (C) Internet Systems Consortium, Inc. ("ISC") 5 * 6 * SPDX-License-Identifier: MPL-2.0 7 * 8 * This Source Code Form is subject to the terms of the Mozilla Public 9 * License, v. 2.0. If a copy of the MPL was not distributed with this 10 * file, you can obtain one at https://mozilla.org/MPL/2.0/. 11 * 12 * See the COPYRIGHT file distributed with this work for additional 13 * information regarding copyright ownership. 14 */ 15 16 /*! \file */ 17 18 #include <ctype.h> 19 #include <errno.h> 20 #include <inttypes.h> 21 #include <stdbool.h> 22 #include <stdint.h> 23 #include <stdio.h> 24 #include <stdlib.h> 25 26 #include <isc/buffer.h> 27 #include <isc/parseint.h> 28 #include <isc/region.h> 29 #include <isc/result.h> 30 #include <isc/string.h> 31 #include <isc/util.h> 32 33 #include <dns/ttl.h> 34 35 #include <isccfg/duration.h> 36 37 /* 38 * isccfg_duration_fromtext initially taken from OpenDNSSEC code base. 39 * Modified to fit the BIND 9 code. 40 */ 41 isc_result_t 42 isccfg_duration_fromtext(isc_textregion_t *source, 43 isccfg_duration_t *duration) { 44 char buf[CFG_DURATION_MAXLEN] = { 0 }; 45 char *P, *X, *T, *W, *str; 46 bool not_weeks = false; 47 int i; 48 long long int lli; 49 char *endptr; 50 51 /* 52 * Copy the buffer as it may not be NULL terminated. 53 */ 54 if (source->length > sizeof(buf) - 1) { 55 return ISC_R_BADNUMBER; 56 } 57 /* Copy source->length bytes and NULL terminate. */ 58 snprintf(buf, sizeof(buf), "%.*s", (int)source->length, source->base); 59 str = buf; 60 61 /* Clear out duration. */ 62 for (i = 0; i < 7; i++) { 63 duration->parts[i] = 0; 64 } 65 duration->iso8601 = false; 66 duration->unlimited = false; 67 68 /* Every duration starts with 'P' */ 69 if (toupper((unsigned char)str[0]) != 'P') { 70 return ISC_R_BADNUMBER; 71 } 72 P = str; 73 74 /* Record the time indicator. */ 75 T = strpbrk(str, "Tt"); 76 77 /* Record years. */ 78 X = strpbrk(str, "Yy"); 79 if (X != NULL) { 80 errno = 0; 81 endptr = NULL; 82 lli = strtoll(str + 1, &endptr, 10); 83 if (*endptr != *X) { 84 return ISC_R_BADNUMBER; 85 } 86 if (errno != 0 || lli < 0 || lli > UINT32_MAX) { 87 return ISC_R_BADNUMBER; 88 } 89 duration->parts[0] = (uint32_t)lli; 90 str = X; 91 not_weeks = true; 92 } 93 94 /* Record months. */ 95 X = strpbrk(str, "Mm"); 96 97 /* 98 * M could be months or minutes. This is months if there is no time 99 * part, or this M indicator is before the time indicator. 100 */ 101 if (X != NULL && (T == NULL || (size_t)(X - P) < (size_t)(T - P))) { 102 errno = 0; 103 lli = strtoll(str + 1, &endptr, 10); 104 if (*endptr != *X) { 105 return ISC_R_BADNUMBER; 106 } 107 if (errno != 0 || lli < 0 || lli > UINT32_MAX) { 108 return ISC_R_BADNUMBER; 109 } 110 duration->parts[1] = (uint32_t)lli; 111 str = X; 112 not_weeks = true; 113 } 114 115 /* Record days. */ 116 X = strpbrk(str, "Dd"); 117 if (X != NULL) { 118 errno = 0; 119 lli = strtoll(str + 1, &endptr, 10); 120 if (*endptr != *X) { 121 return ISC_R_BADNUMBER; 122 } 123 if (errno != 0 || lli < 0 || lli > UINT32_MAX) { 124 return ISC_R_BADNUMBER; 125 } 126 duration->parts[3] = (uint32_t)lli; 127 str = X; 128 not_weeks = true; 129 } 130 131 /* Time part? */ 132 if (T != NULL) { 133 str = T; 134 not_weeks = true; 135 } 136 137 /* Record hours. */ 138 X = strpbrk(str, "Hh"); 139 if (X != NULL && T != NULL) { 140 errno = 0; 141 lli = strtoll(str + 1, &endptr, 10); 142 if (*endptr != *X) { 143 return ISC_R_BADNUMBER; 144 } 145 if (errno != 0 || lli < 0 || lli > UINT32_MAX) { 146 return ISC_R_BADNUMBER; 147 } 148 duration->parts[4] = (uint32_t)lli; 149 str = X; 150 not_weeks = true; 151 } 152 153 /* Record minutes. */ 154 X = strpbrk(str, "Mm"); 155 156 /* 157 * M could be months or minutes. This is minutes if there is a time 158 * part and the M indicator is behind the time indicator. 159 */ 160 if (X != NULL && T != NULL && (size_t)(X - P) > (size_t)(T - P)) { 161 errno = 0; 162 lli = strtoll(str + 1, &endptr, 10); 163 if (*endptr != *X) { 164 return ISC_R_BADNUMBER; 165 } 166 if (errno != 0 || lli < 0 || lli > UINT32_MAX) { 167 return ISC_R_BADNUMBER; 168 } 169 duration->parts[5] = (uint32_t)lli; 170 str = X; 171 not_weeks = true; 172 } 173 174 /* Record seconds. */ 175 X = strpbrk(str, "Ss"); 176 if (X != NULL && T != NULL) { 177 errno = 0; 178 lli = strtoll(str + 1, &endptr, 10); 179 if (*endptr != *X) { 180 return ISC_R_BADNUMBER; 181 } 182 if (errno != 0 || lli < 0 || lli > UINT32_MAX) { 183 return ISC_R_BADNUMBER; 184 } 185 duration->parts[6] = (uint32_t)lli; 186 str = X; 187 not_weeks = true; 188 } 189 190 /* Or is the duration configured in weeks? */ 191 W = strpbrk(buf, "Ww"); 192 if (W != NULL) { 193 if (not_weeks) { 194 /* Mix of weeks and other indicators is not allowed */ 195 return ISC_R_BADNUMBER; 196 } else { 197 errno = 0; 198 lli = strtoll(str + 1, &endptr, 10); 199 if (*endptr != *W) { 200 return ISC_R_BADNUMBER; 201 } 202 if (errno != 0 || lli < 0 || lli > UINT32_MAX) { 203 return ISC_R_BADNUMBER; 204 } 205 duration->parts[2] = (uint32_t)lli; 206 str = W; 207 } 208 } 209 210 /* Deal with trailing garbage. */ 211 if (str[1] != '\0') { 212 return ISC_R_BADNUMBER; 213 } 214 215 duration->iso8601 = true; 216 return ISC_R_SUCCESS; 217 } 218 219 isc_result_t 220 isccfg_parse_duration(isc_textregion_t *source, isccfg_duration_t *duration) { 221 isc_result_t result; 222 223 REQUIRE(duration != NULL); 224 225 duration->unlimited = false; 226 result = isccfg_duration_fromtext(source, duration); 227 if (result == ISC_R_BADNUMBER) { 228 /* Fallback to dns_ttl_fromtext. */ 229 uint32_t ttl; 230 result = dns_ttl_fromtext(source, &ttl); 231 if (result == ISC_R_SUCCESS) { 232 /* 233 * With dns_ttl_fromtext() the information on optional 234 * units is lost, and is treated as seconds from now on. 235 */ 236 duration->iso8601 = false; 237 duration->parts[6] = ttl; 238 } 239 } 240 241 return result; 242 } 243 244 uint32_t 245 isccfg_duration_toseconds(const isccfg_duration_t *duration) { 246 uint64_t seconds = 0; 247 248 REQUIRE(duration != NULL); 249 250 seconds += (uint64_t)duration->parts[6]; /* Seconds */ 251 seconds += (uint64_t)duration->parts[5] * 60; /* Minutes */ 252 seconds += (uint64_t)duration->parts[4] * 3600; /* Hours */ 253 seconds += (uint64_t)duration->parts[3] * 86400; /* Days */ 254 seconds += (uint64_t)duration->parts[2] * 86400 * 7; /* Weeks */ 255 /* 256 * The below additions are not entirely correct 257 * because days may vary per month and per year. 258 */ 259 seconds += (uint64_t)duration->parts[1] * 86400 * 31; /* Months */ 260 seconds += (uint64_t)duration->parts[0] * 86400 * 365; /* Years */ 261 262 return seconds > UINT32_MAX ? UINT32_MAX : (uint32_t)seconds; 263 } 264