xref: /netbsd-src/external/mpl/bind/dist/lib/dns/ttl.c (revision bcda20f65a8566e103791ec395f7f499ef322704)
1 /*	$NetBSD: ttl.c,v 1.11 2025/01/26 16:25:25 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 <stdio.h>
23 #include <stdlib.h>
24 
25 #include <isc/ascii.h>
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 #define RETERR(x)                        \
36 	do {                             \
37 		isc_result_t _r = (x);   \
38 		if (_r != ISC_R_SUCCESS) \
39 			return ((_r));   \
40 	} while (0)
41 
42 static isc_result_t
43 bind_ttl(isc_textregion_t *source, uint32_t *ttl);
44 
45 /*
46  * Helper for dns_ttl_totext().
47  */
48 static isc_result_t
49 ttlfmt(unsigned int t, const char *s, bool verbose, bool space,
50        isc_buffer_t *target) {
51 	char tmp[60];
52 	unsigned int len;
53 	isc_region_t region;
54 
55 	if (verbose) {
56 		len = snprintf(tmp, sizeof(tmp), "%s%u %s%s", space ? " " : "",
57 			       t, s, t == 1 ? "" : "s");
58 	} else {
59 		len = snprintf(tmp, sizeof(tmp), "%u%c", t, s[0]);
60 	}
61 
62 	INSIST(len + 1 <= sizeof(tmp));
63 	isc_buffer_availableregion(target, &region);
64 	if (len > region.length) {
65 		return ISC_R_NOSPACE;
66 	}
67 	memmove(region.base, tmp, len);
68 	isc_buffer_add(target, len);
69 
70 	return ISC_R_SUCCESS;
71 }
72 
73 /*
74  * Derived from bind8 ns_format_ttl().
75  */
76 isc_result_t
77 dns_ttl_totext(uint32_t src, bool verbose, bool upcase, isc_buffer_t *target) {
78 	unsigned int secs, mins, hours, days, weeks, x;
79 
80 	secs = src % 60;
81 	src /= 60;
82 	mins = src % 60;
83 	src /= 60;
84 	hours = src % 24;
85 	src /= 24;
86 	days = src % 7;
87 	src /= 7;
88 	weeks = src;
89 	src = 0;
90 	POST(src);
91 
92 	x = 0;
93 	if (weeks != 0) {
94 		RETERR(ttlfmt(weeks, "week", verbose, (x > 0), target));
95 		x++;
96 	}
97 	if (days != 0) {
98 		RETERR(ttlfmt(days, "day", verbose, (x > 0), target));
99 		x++;
100 	}
101 	if (hours != 0) {
102 		RETERR(ttlfmt(hours, "hour", verbose, (x > 0), target));
103 		x++;
104 	}
105 	if (mins != 0) {
106 		RETERR(ttlfmt(mins, "minute", verbose, (x > 0), target));
107 		x++;
108 	}
109 	if (secs != 0 || (weeks == 0 && days == 0 && hours == 0 && mins == 0)) {
110 		RETERR(ttlfmt(secs, "second", verbose, (x > 0), target));
111 		x++;
112 	}
113 	INSIST(x > 0);
114 	/*
115 	 * If only a single unit letter is printed, print it
116 	 * in upper case. (Why?  Because BIND 8 does that.
117 	 * Presumably it has a reason.)
118 	 */
119 	if (x == 1 && upcase && !verbose) {
120 		isc_region_t region;
121 		/*
122 		 * The unit letter is the last character in the
123 		 * used region of the buffer.
124 		 */
125 		isc_buffer_usedregion(target, &region);
126 		region.base[region.length - 1] =
127 			isc_ascii_toupper(region.base[region.length - 1]);
128 	}
129 	return ISC_R_SUCCESS;
130 }
131 
132 isc_result_t
133 dns_counter_fromtext(isc_textregion_t *source, uint32_t *ttl) {
134 	return bind_ttl(source, ttl);
135 }
136 
137 isc_result_t
138 dns_ttl_fromtext(isc_textregion_t *source, uint32_t *ttl) {
139 	isc_result_t result;
140 
141 	result = bind_ttl(source, ttl);
142 	if (result != ISC_R_SUCCESS && result != ISC_R_RANGE) {
143 		result = DNS_R_BADTTL;
144 	}
145 	return result;
146 }
147 
148 static isc_result_t
149 bind_ttl(isc_textregion_t *source, uint32_t *ttl) {
150 	uint64_t tmp = 0ULL;
151 	uint32_t n;
152 	char *s;
153 	char buf[64];
154 	char nbuf[64]; /* Number buffer */
155 
156 	/*
157 	 * Copy the buffer as it may not be NULL terminated.
158 	 * No legal counter / ttl is longer that 63 characters.
159 	 */
160 	if (source->length > sizeof(buf) - 1) {
161 		return DNS_R_SYNTAX;
162 	}
163 	/* Copy source->length bytes and NUL terminate. */
164 	snprintf(buf, sizeof(buf), "%.*s", (int)source->length, source->base);
165 	s = buf;
166 
167 	do {
168 		isc_result_t result;
169 
170 		char *np = nbuf;
171 		while (*s != '\0' && isdigit((unsigned char)*s)) {
172 			*np++ = *s++;
173 		}
174 		*np++ = '\0';
175 		INSIST(np - nbuf <= (int)sizeof(nbuf));
176 		result = isc_parse_uint32(&n, nbuf, 10);
177 		if (result != ISC_R_SUCCESS) {
178 			return DNS_R_SYNTAX;
179 		}
180 		switch (*s) {
181 		case 'w':
182 		case 'W':
183 			tmp += (uint64_t)n * 7 * 24 * 3600;
184 			s++;
185 			break;
186 		case 'd':
187 		case 'D':
188 			tmp += (uint64_t)n * 24 * 3600;
189 			s++;
190 			break;
191 		case 'h':
192 		case 'H':
193 			tmp += (uint64_t)n * 3600;
194 			s++;
195 			break;
196 		case 'm':
197 		case 'M':
198 			tmp += (uint64_t)n * 60;
199 			s++;
200 			break;
201 		case 's':
202 		case 'S':
203 			tmp += (uint64_t)n;
204 			s++;
205 			break;
206 		case '\0':
207 			/* Plain number? */
208 			if (tmp != 0ULL) {
209 				return DNS_R_SYNTAX;
210 			}
211 			tmp = n;
212 			break;
213 		default:
214 			return DNS_R_SYNTAX;
215 		}
216 	} while (*s != '\0');
217 
218 	if (tmp > 0xffffffffULL) {
219 		return ISC_R_RANGE;
220 	}
221 
222 	*ttl = (uint32_t)(tmp & 0xffffffffUL);
223 	return ISC_R_SUCCESS;
224 }
225