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, ®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 */ 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, ®ion); 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