1/* $NetBSD: ttl.c,v 1.1 2024/02/18 20:57:34 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/string.h> 30#include <isc/util.h> 31 32#include <dns/result.h> 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, ®ion); 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, ®ion); 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