xref: /netbsd-src/external/mpl/bind/dist/lib/isccfg/duration.c (revision bcda20f65a8566e103791ec395f7f499ef322704)
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