1/*	$NetBSD: ttl.c,v 1.10 2024/02/21 22:52:08 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/buffer.h>
26#include <isc/parseint.h>
27#include <isc/print.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
42static isc_result_t
43bind_ttl(isc_textregion_t *source, uint32_t *ttl);
44
45/*
46 * Helper for dns_ttl_totext().
47 */
48static isc_result_t
49ttlfmt(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 */
76isc_result_t
77dns_ttl_totext(uint32_t src, bool verbose, bool upcase, isc_buffer_t *target) {
78	unsigned 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		 * toupper() does not need its argument to be masked of cast
126		 * here because region.base is type unsigned char *.
127		 */
128		isc_buffer_usedregion(target, &region);
129		region.base[region.length - 1] =
130			toupper(region.base[region.length - 1]);
131	}
132	return (ISC_R_SUCCESS);
133}
134
135isc_result_t
136dns_counter_fromtext(isc_textregion_t *source, uint32_t *ttl) {
137	return (bind_ttl(source, ttl));
138}
139
140isc_result_t
141dns_ttl_fromtext(isc_textregion_t *source, uint32_t *ttl) {
142	isc_result_t result;
143
144	result = bind_ttl(source, ttl);
145	if (result != ISC_R_SUCCESS && result != ISC_R_RANGE) {
146		result = DNS_R_BADTTL;
147	}
148	return (result);
149}
150
151static isc_result_t
152bind_ttl(isc_textregion_t *source, uint32_t *ttl) {
153	uint64_t tmp = 0ULL;
154	uint32_t n;
155	char *s;
156	char buf[64];
157	char nbuf[64]; /* Number buffer */
158
159	/*
160	 * Copy the buffer as it may not be NULL terminated.
161	 * No legal counter / ttl is longer that 63 characters.
162	 */
163	if (source->length > sizeof(buf) - 1) {
164		return (DNS_R_SYNTAX);
165	}
166	/* Copy source->length bytes and NUL terminate. */
167	snprintf(buf, sizeof(buf), "%.*s", (int)source->length, source->base);
168	s = buf;
169
170	do {
171		isc_result_t result;
172
173		char *np = nbuf;
174		while (*s != '\0' && isdigit((unsigned char)*s)) {
175			*np++ = *s++;
176		}
177		*np++ = '\0';
178		INSIST(np - nbuf <= (int)sizeof(nbuf));
179		result = isc_parse_uint32(&n, nbuf, 10);
180		if (result != ISC_R_SUCCESS) {
181			return (DNS_R_SYNTAX);
182		}
183		switch (*s) {
184		case 'w':
185		case 'W':
186			tmp += (uint64_t)n * 7 * 24 * 3600;
187			s++;
188			break;
189		case 'd':
190		case 'D':
191			tmp += (uint64_t)n * 24 * 3600;
192			s++;
193			break;
194		case 'h':
195		case 'H':
196			tmp += (uint64_t)n * 3600;
197			s++;
198			break;
199		case 'm':
200		case 'M':
201			tmp += (uint64_t)n * 60;
202			s++;
203			break;
204		case 's':
205		case 'S':
206			tmp += (uint64_t)n;
207			s++;
208			break;
209		case '\0':
210			/* Plain number? */
211			if (tmp != 0ULL) {
212				return (DNS_R_SYNTAX);
213			}
214			tmp = n;
215			break;
216		default:
217			return (DNS_R_SYNTAX);
218		}
219	} while (*s != '\0');
220
221	if (tmp > 0xffffffffULL) {
222		return (ISC_R_RANGE);
223	}
224
225	*ttl = (uint32_t)(tmp & 0xffffffffUL);
226	return (ISC_R_SUCCESS);
227}
228