1 /**
2  * @file user_yang_types.c
3  * @author Michal Vasko <mvasko@cesnet.cz>
4  * @brief ietf-yang-types typedef validation and conversion to canonical format
5  *
6  * Copyright (c) 2018 CESNET, z.s.p.o.
7  *
8  * This source code is licensed under BSD 3-Clause License (the "License").
9  * You may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *     https://opensource.org/licenses/BSD-3-Clause
13  */
14 #define _GNU_SOURCE
15 
16 #include <stdlib.h>
17 #include <string.h>
18 #include <stdint.h>
19 #include <errno.h>
20 #include <time.h>
21 #include <ctype.h>
22 
23 #include "compat.h"
24 #include "../user_types.h"
25 
26 /**
27  * @brief Storage for ID used to check plugin API version compatibility.
28  */
29 LYTYPE_VERSION_CHECK
30 
31 #ifdef __GNUC__
32 #  define UNUSED(x) UNUSED_ ## x __attribute__((__unused__))
33 #else
34 #  define UNUSED(x) UNUSED_ ## x
35 #endif
36 
37 static int
date_and_time_store_clb(struct ly_ctx * UNUSED (ctx),const char * UNUSED (type_name),const char ** value_str,lyd_val * UNUSED (value),char ** err_msg)38 date_and_time_store_clb(struct ly_ctx *UNUSED(ctx), const char *UNUSED(type_name), const char **value_str,
39                         lyd_val *UNUSED(value), char **err_msg)
40 {
41     struct tm tm, tm2;
42     uint32_t i, j;
43     const char *val_str = *value_str;
44     int ret;
45 
46     /* \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[\+\-]\d{2}:\d{2})
47      * 2018-03-21T09:11:05(.55785...)(Z|+02:00) */
48     memset(&tm, 0, sizeof tm);
49     i = 0;
50 
51     /* year */
52     tm.tm_year = atoi(val_str + i);
53     /* if there was some invalid number, it will either be discovered in the loop below or by mktime() */
54     tm.tm_year -= 1900;
55     for (j = i + 4; i < j; ++i) {
56         if (!isdigit(val_str[i])) {
57             ret = asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", a digit expected.", val_str[i], i, val_str);
58             goto error;
59         }
60     }
61     if (val_str[i] != '-') {
62         ret = asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", '-' expected.", val_str[i], i, val_str);
63         goto error;
64     }
65     ++i;
66 
67     /* month */
68     tm.tm_mon = atoi(val_str + i);
69     tm.tm_mon -= 1;
70     for (j = i + 2; i < j; ++i) {
71         if (!isdigit(val_str[i])) {
72             ret = asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", a digit expected.", val_str[i], i, val_str);
73             goto error;
74         }
75     }
76     if (val_str[i] != '-') {
77         ret = asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", '-' expected.", val_str[i], i, val_str);
78         goto error;
79     }
80     ++i;
81 
82     /* day */
83     tm.tm_mday = atoi(val_str + i);
84     for (j = i + 2; i < j; ++i) {
85         if (!isdigit(val_str[i])) {
86             ret = asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", a digit expected.", val_str[i], i, val_str);
87             goto error;
88         }
89     }
90     if (val_str[i] != 'T') {
91         ret = asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", 'T' expected.", val_str[i], i, val_str);
92         goto error;
93     }
94     ++i;
95 
96     /* hours */
97     tm.tm_hour = atoi(val_str + i);
98     for (j = i + 2; i < j; ++i) {
99         if (!isdigit(val_str[i])) {
100             ret = asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", a digit expected.", val_str[i], i, val_str);
101             goto error;
102         }
103     }
104     if (val_str[i] != ':') {
105         ret = asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", ':' expected.", val_str[i], i, val_str);
106         goto error;
107     }
108     ++i;
109 
110     /* minutes */
111     tm.tm_min = atoi(val_str + i);
112     for (j = i + 2; i < j; ++i) {
113         if (!isdigit(val_str[i])) {
114             ret = asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", a digit expected.", val_str[i], i, val_str);
115             goto error;
116         }
117     }
118     if (val_str[i] != ':') {
119         ret = asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", ':' expected.", val_str[i], i, val_str);
120         goto error;
121     }
122     ++i;
123 
124     /* seconds */
125     tm.tm_sec = atoi(val_str + i);
126     for (j = i + 2; i < j; ++i) {
127         if (!isdigit(val_str[i])) {
128             ret = asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", a digit expected.", val_str[i], i, val_str);
129             goto error;
130         }
131     }
132     if ((val_str[i] != '.') && (val_str[i] != 'Z') && (val_str[i] != '+') && (val_str[i] != '-')) {
133         ret = asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", '.', 'Z', '+', or '-' expected.",
134                        val_str[i], i, val_str);
135         goto error;
136     }
137 
138     /* validate using mktime() */
139     tm2 = tm;
140     errno = 0;
141     mktime(&tm);
142     /* ENOENT is set when "/etc/localtime" is missing but the function suceeeds */
143     if (errno && (errno != ENOENT)) {
144         ret = asprintf(err_msg, "Checking date-and-time value \"%s\" failed (%s).", val_str, strerror(errno));
145         goto error;
146     }
147     /* we now have correctly filled the remaining values, use them */
148     memcpy(((char *)&tm2) + (6 * sizeof(int)), ((char *)&tm) + (6 * sizeof(int)), sizeof(struct tm) - (6 * sizeof(int)));
149     /* back it up again */
150     tm = tm2;
151     /* let mktime() correct date & time with having the other values correct now */
152     errno = 0;
153     mktime(&tm);
154     if (errno && (errno != ENOENT)) {
155         ret = asprintf(err_msg, "Checking date-and-time value \"%s\" failed (%s).", val_str, strerror(errno));
156         goto error;
157     }
158     /* detect changes in the filled values */
159     if (memcmp(&tm, &tm2, 6 * sizeof(int))) {
160         ret = asprintf(err_msg, "Checking date-and-time value \"%s\" failed, canonical date and time is \"%04d-%02d-%02dT%02d:%02d:%02d\".",
161                        val_str, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
162         goto error;
163     }
164 
165     /* tenth of a second */
166     if (val_str[i] == '.') {
167         ++i;
168         if (!isdigit(val_str[i])) {
169             ret = asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", a digit expected.", val_str[i], i, val_str);
170             goto error;
171         }
172         do {
173             ++i;
174         } while (isdigit(val_str[i]));
175     }
176 
177     switch (val_str[i]) {
178     case 'Z':
179         /* done */
180         break;
181     case '+':
182     case '-':
183         /* timezone shift */
184         if ((val_str[i + 1] < '0') || (val_str[i + 1] > '2')) {
185             ret = asprintf(err_msg, "Invalid timezone \"%.6s\" in date-and-time value \"%s\".", val_str + i, val_str);
186             goto error;
187         }
188         if ((val_str[i + 2] < '0') || ((val_str[i + 1] == '2') && (val_str[i + 2] > '3')) || (val_str[i + 2] > '9')) {
189             ret = asprintf(err_msg, "Invalid timezone \"%.6s\" in date-and-time value \"%s\".", val_str + i, val_str);
190             goto error;
191         }
192 
193         if (val_str[i + 3] != ':') {
194             ret = asprintf(err_msg, "Invalid timezone \"%.6s\" in date-and-time value \"%s\".", val_str + i, val_str);
195             goto error;
196         }
197 
198         if ((val_str[i + 4] < '0') || (val_str[i + 4] > '5')) {
199             ret = asprintf(err_msg, "Invalid timezone \"%.6s\" in date-and-time value \"%s\".", val_str + i, val_str);
200             goto error;
201         }
202         if ((val_str[i + 5] < '0') || (val_str[i + 5] > '9')) {
203             ret = asprintf(err_msg, "Invalid timezone \"%.6s\" in date-and-time value \"%s\".", val_str + i, val_str);
204             goto error;
205         }
206 
207         i += 5;
208         break;
209     default:
210         ret = asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", 'Z', '+', or '-' expected.", val_str[i], i, val_str);
211         goto error;
212     }
213 
214     /* no other characters expected */
215     ++i;
216     if (val_str[i]) {
217         ret = asprintf(err_msg, "Invalid character '%c'[%d] in date-and-time value \"%s\", no characters expected.", val_str[i], i, val_str);
218         goto error;
219     }
220 
221     /* validation succeeded and we do not want to change how it is stored */
222     return 0;
223 
224 error:
225     if (ret == -1) {
226         *err_msg = NULL;
227     }
228     return 1;
229 }
230 
231 static int
hex_string_store_clb(struct ly_ctx * ctx,const char * UNUSED (type_name),const char ** value_str,lyd_val * value,char ** err_msg)232 hex_string_store_clb(struct ly_ctx *ctx, const char *UNUSED(type_name), const char **value_str, lyd_val *value, char **err_msg)
233 {
234     char *str;
235     uint32_t i, len;
236 
237     str = strdup(*value_str);
238     if (!str) {
239         /* we can hardly allocate an error message */
240         *err_msg = NULL;
241         return 1;
242     }
243 
244     len = strlen(str);
245     for (i = 0; i < len; ++i) {
246         if ((str[i] >= 'A') && (str[i] <= 'Z')) {
247             /* make it lowercase (canonical format) */
248             str[i] += 32;
249         }
250     }
251 
252     /* update the value correctly */
253     lydict_remove(ctx, *value_str);
254     *value_str = lydict_insert_zc(ctx, str);
255     value->string = *value_str;
256     return 0;
257 }
258 
259 /* Name of this array must match the file name! */
260 struct lytype_plugin_list user_yang_types[] = {
261     {"ietf-yang-types", "2013-07-15", "date-and-time", date_and_time_store_clb, NULL},
262     {"ietf-yang-types", "2013-07-15", "phys-address", hex_string_store_clb, NULL},
263     {"ietf-yang-types", "2013-07-15", "mac-address", hex_string_store_clb, NULL},
264     {"ietf-yang-types", "2013-07-15", "hex-string", hex_string_store_clb, NULL},
265     {"ietf-yang-types", "2013-07-15", "uuid", hex_string_store_clb, NULL},
266     {NULL, NULL, NULL, NULL, NULL} /* terminating item */
267 };
268