blob: 2cc7a886ff489732960e1edd77657a2ef99591ed [file] [log] [blame]
Giovanni Bajoe292e932012-04-22 14:32:02 +02001
2#include "dnsmasq.h"
Giovanni Bajod322de02012-04-23 00:30:00 +02003#include "dnssec-crypto.h"
Giovanni Bajoe292e932012-04-22 14:32:02 +02004#include <assert.h>
5
6#define SERIAL_UNDEF -100
7#define SERIAL_EQ 0
8#define SERIAL_LT -1
9#define SERIAL_GT 1
10
Giovanni Bajoe292e932012-04-22 14:32:02 +020011/* Implement RFC1982 wrapped compare for 32-bit numbers */
12static int serial_compare_32(unsigned long s1, unsigned long s2)
13{
14 if (s1 == s2)
15 return SERIAL_EQ;
16
17 if ((s1 < s2 && (s2 - s1) < (1UL<<31)) ||
18 (s1 > s2 && (s1 - s2) > (1UL<<31)))
19 return SERIAL_LT;
20 if ((s1 < s2 && (s2 - s1) > (1UL<<31)) ||
21 (s1 > s2 && (s1 - s2) < (1UL<<31)))
22 return SERIAL_GT;
23 return SERIAL_UNDEF;
24}
25
26/* Extract a DNS name from wire format, without handling compression. This is
27 faster than extract_name() and does not require access to the full dns
28 packet. */
29static int extract_name_no_compression(unsigned char *rr, int maxlen, char *buf)
30{
31 unsigned char *start=rr, *end = rr+maxlen;
32 int count;
33
34 while (rr < end && *rr != 0)
35 {
36 count = *rr++;
Giovanni Bajo6445c8e2012-04-24 02:02:29 +020037 while (count-- > 0 && rr < end)
Giovanni Bajoe292e932012-04-22 14:32:02 +020038 {
39 *buf = *rr++;
Giovanni Bajob98f7712012-04-22 15:59:27 +020040 if (!isascii(*buf) || iscntrl(*buf) || *buf == '.')
41 return 0;
Giovanni Bajoe292e932012-04-22 14:32:02 +020042 if (*buf >= 'A' && *buf <= 'Z')
43 *buf += 'a' - 'A';
44 buf++;
Giovanni Bajo79333a22012-04-28 01:03:10 +020045 }
Giovanni Bajoe292e932012-04-22 14:32:02 +020046 *buf++ = '.';
47 }
Giovanni Bajodd090562012-04-28 03:47:10 +020048 /* Remove trailing dot (if any) */
Giovanni Bajo2ef843d2012-04-25 17:48:40 +020049 if (rr != start)
50 *(--buf) = 0;
Giovanni Bajoe292e932012-04-22 14:32:02 +020051 if (rr == end)
52 return 0;
Giovanni Bajodd090562012-04-28 03:47:10 +020053 /* Trailing \0 in source data must be consumed */
Giovanni Bajo79333a22012-04-28 01:03:10 +020054 return rr-start+1;
Giovanni Bajoe292e932012-04-22 14:32:02 +020055}
56
57/* Check whether today/now is between date_start and date_end */
58static int check_date_range(unsigned long date_start, unsigned long date_end)
59{
60 /* TODO: double-check that time(0) is the correct time we are looking for */
61 /* TODO: dnssec requires correct timing; implement SNTP in dnsmasq? */
62 unsigned long curtime = time(0);
63
64 /* We must explicitly check against wanted values, because of SERIAL_UNDEF */
Giovanni Bajo41de7442012-04-28 03:59:49 +020065 return serial_compare_32(curtime, date_start) == SERIAL_GT
66 && serial_compare_32(curtime, date_end) == SERIAL_LT;
Giovanni Bajoe292e932012-04-22 14:32:02 +020067}
68
69/* Sort RRs within a RRset in canonical order, according to RFC4034, §6.3
70 Notice that the RRDATA sections have been already normalized, so a memcpy
71 is sufficient.
72 NOTE: r1/r2 point immediately after the owner name. */
73static int rrset_canonical_order(const void *r1, const void *r2)
74{
75 int r1len, r2len, res;
Giovanni Bajo0decc862012-04-24 02:23:11 +020076 const unsigned char *pr1=*(unsigned char**)r1, *pr2=*(unsigned char**)r2;
Giovanni Bajoe292e932012-04-22 14:32:02 +020077
78 pr1 += 8; pr2 += 8;
79 GETSHORT(r1len, pr1); GETSHORT(r2len, pr2);
80
81 /* Lexicographically compare RDATA (thus, if equal, smaller length wins) */
82 res = memcmp(pr1, pr2, MIN(r1len, r2len));
83 if (res == 0)
84 {
85 if (r1len < r2len)
86 return -1;
87 else
88 /* NOTE: RFC2181 says that an RRset is not allowed to contain duplicate
89 records. If it happens, it is a protocol error and anything goes. */
90 return 1;
91 }
92
93 return res;
94}
95
Giovanni Bajoadca3e92012-04-25 17:46:53 +020096typedef struct PendingRRSIGValidation
97{
98 VerifyAlgCtx *alg;
99 char *signer_name;
100 int keytag;
101} PendingRRSIGValidation;
102
Giovanni Bajo13e435e2012-04-27 03:19:40 +0200103/* Pass a domain name through a verification hash function.
104
105 We must pass domain names in DNS wire format, but uncompressed.
106 This means that we cannot directly use raw data from the original
107 message since it might be compressed. */
108static void verifyalg_add_data_domain(VerifyAlgCtx *alg, char* name)
109{
110 unsigned char len; char *p;
111
112 while ((p = strchr(name, '.')))
113 {
114 len = p-name;
115 alg->vtbl->add_data(alg, &len, 1);
116 alg->vtbl->add_data(alg, name, len);
117 name = p+1;
118 }
119 len = strlen(name);
120 alg->vtbl->add_data(alg, &len, 1);
121 alg->vtbl->add_data(alg, name, len+1);
122}
123
Giovanni Bajo0852d762012-04-28 03:49:24 +0200124/* Pass a DNS domain name in wire format through a hash function. Returns the
125 total number of bytes passed through the function or 0 in case of errors.
126 Updates the rdata pointer moving it further within the RR.
127
128 If alg is NULL, go in dry run mode (still correctly updates rdata and return
129 the correct total).
130
131 The function canonicalizes the domain name (RFC 4034, §6.2), which basically
132 means conversion to lower case, and uncompression. */
133static int verifyalg_add_data_wire_domain(VerifyAlgCtx *alg, struct dns_header *header, size_t pktlen,
134 unsigned char** rdata)
135{
136 int hops = 0, total = 0;
137 unsigned char label_type;
138 unsigned char *end = (unsigned char *)header + pktlen;
139 unsigned char count; unsigned char *p = *rdata;
140
141 while (1)
142 {
143 if (p >= end)
144 return 0;
145 if (!(count = *p++))
146 break;
147 label_type = count & 0xC0;
148 if (label_type == 0xC0)
149 {
150 if (p >= end)
151 return 0;
152 p = (unsigned char*)header + (count & 0x3F) * 256 + *p;
153 if (hops == 0)
154 *rdata = p;
155 if (++hops == 256)
156 return 0;
157 }
158 else if (label_type == 0x00)
159 {
160 if (p+count-1 >= end)
161 return 0;
162 if (alg)
163 {
164 alg->vtbl->add_data(alg, &count, 1);
165 /* TODO: missing conversion to lower-case and alphabet check */
166 alg->vtbl->add_data(alg, p, count);
167 }
168 total += count+1;
169 p += count;
170 }
171 else
172 return 0; /* unsupported label_type */
173 }
174
175 if (hops == 0)
176 *rdata = p;
177 if (alg)
178 alg->vtbl->add_data(alg, &count, 1);
179 return total+1;
180}
181
182/* Pass a resource record's rdata field through a verification hash function.
183
184 We must pass the record in DNS wire format, but if the record contains domain names,
185 they must be uncompressed. This makes things very tricky, because */
186static int verifyalg_add_rdata(VerifyAlgCtx *alg, int sigtype, struct dns_header *header, size_t pktlen,
187 unsigned char *rdata)
188{
189 unsigned char *p;
190 int res; unsigned short rdlen;
191
192 GETSHORT(rdlen, rdata);
193 p = rdata;
194
195 switch (sigtype)
196 {
197 /* TODO: missing lots of RR types, see RFC4034, §6.2 */
198 case T_CNAME:
199 if (!(res = verifyalg_add_data_wire_domain(NULL, header, pktlen, &p)))
200 return 0;
201 if (p - rdata > rdlen)
202 return 0;
203 rdlen = htons(res);
204 alg->vtbl->add_data(alg, &rdlen, 2);
205 verifyalg_add_data_wire_domain(alg, header, pktlen, &rdata);
206 break;
207
208 default:
209 alg->vtbl->add_data(alg, rdata-2, rdlen+2);
210 break;
211 }
212 return 1;
213}
214
215
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200216static int begin_rrsig_validation(struct dns_header *header, size_t pktlen,
217 unsigned char *reply, int count, char *owner,
218 int sigclass, int sigrdlen, unsigned char *sig,
219 PendingRRSIGValidation *out)
Giovanni Bajoe292e932012-04-22 14:32:02 +0200220{
221 int i, res;
222 int sigtype, sigalg, siglbl;
223 unsigned char *sigrdata = sig;
224 unsigned long sigttl, date_end, date_start;
225 unsigned char* p = reply;
226 char* signer_name = daemon->namebuff;
Giovanni Bajo13e435e2012-04-27 03:19:40 +0200227 int signer_name_rdlen;
Giovanni Bajoe292e932012-04-22 14:32:02 +0200228 int keytag;
229 void *rrset[16]; /* TODO: max RRset size? */
230 int rrsetidx = 0;
231
232 if (sigrdlen < 18)
233 return 0;
234 GETSHORT(sigtype, sig);
235 sigalg = *sig++;
236 siglbl = *sig++;
237 GETLONG(sigttl, sig);
238 GETLONG(date_end, sig);
239 GETLONG(date_start, sig);
240 GETSHORT(keytag, sig);
241 sigrdlen -= 18;
242
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200243 if (!verifyalg_supported(sigalg))
Giovanni Bajoe292e932012-04-22 14:32:02 +0200244 {
245 printf("RRSIG algorithm not supported: %d\n", sigalg);
246 return 0;
247 }
248
Giovanni Bajod31d0572012-04-24 02:02:55 +0200249 if (!check_date_range(date_start, date_end))
Giovanni Bajoe292e932012-04-22 14:32:02 +0200250 {
251 printf("RRSIG outside date range\n");
252 return 0;
253 }
254
255 /* Iterate within the answer and find the RRsets matching the current RRsig */
256 for (i = 0; i < count; ++i)
257 {
258 int qtype, qclass, rdlen;
259 if (!(res = extract_name(header, pktlen, &p, owner, 0, 10)))
260 return 0;
261 rrset[rrsetidx] = p;
262 GETSHORT(qtype, p);
263 GETSHORT(qclass, p);
264 p += 4; /* skip ttl */
265 GETSHORT(rdlen, p);
266 if (res == 1 && qtype == sigtype && qclass == sigclass)
267 {
268 ++rrsetidx;
Giovanni Bajo382e38f2012-04-24 01:46:47 +0200269 if (rrsetidx == countof(rrset))
270 {
271 /* Internal buffer too small */
272 printf("internal buffer too small for this RRset\n");
273 return 0;
274 }
Giovanni Bajoe292e932012-04-22 14:32:02 +0200275 }
276 p += rdlen;
277 }
278
279 /* Sort RRset records in canonical order. */
280 qsort(rrset, rrsetidx, sizeof(void*), rrset_canonical_order);
281
Giovanni Bajo50a96b62012-04-28 01:04:56 +0200282 /* Skip through the signer name; we don't extract it right now because
283 we don't want to overwrite the single daemon->namebuff which contains
284 the owner name. We'll get to this later. */
285 if (!(p = skip_name(sig, header, pktlen, 0)))
Giovanni Bajoe292e932012-04-22 14:32:02 +0200286 return 0;
Giovanni Bajo50a96b62012-04-28 01:04:56 +0200287 signer_name_rdlen = p - sig;
288 sig = p; sigrdlen -= signer_name_rdlen;
Giovanni Bajo13e435e2012-04-27 03:19:40 +0200289
Giovanni Bajoe292e932012-04-22 14:32:02 +0200290 /* Now initialize the signature verification algorithm and process the whole
291 RRset */
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200292 VerifyAlgCtx *alg = verifyalg_alloc(sigalg);
293 if (!alg)
294 return 0;
295 if (!alg->vtbl->set_signature(alg, sig, sigrdlen))
Giovanni Bajoe292e932012-04-22 14:32:02 +0200296 return 0;
297
Giovanni Bajo4b0eecb2012-04-27 03:18:52 +0200298 sigtype = htons(sigtype);
299 sigclass = htons(sigclass);
300 sigttl = htonl(sigttl);
301
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200302 alg->vtbl->begin_data(alg);
Giovanni Bajo13e435e2012-04-27 03:19:40 +0200303 alg->vtbl->add_data(alg, sigrdata, 18+signer_name_rdlen);
Giovanni Bajoe292e932012-04-22 14:32:02 +0200304 for (i = 0; i < rrsetidx; ++i)
305 {
Giovanni Bajo13e435e2012-04-27 03:19:40 +0200306 p = (unsigned char*)(rrset[i]);
307
308 verifyalg_add_data_domain(alg, owner);
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200309 alg->vtbl->add_data(alg, &sigtype, 2);
310 alg->vtbl->add_data(alg, &sigclass, 2);
311 alg->vtbl->add_data(alg, &sigttl, 4);
Giovanni Bajoe292e932012-04-22 14:32:02 +0200312
Giovanni Bajoe292e932012-04-22 14:32:02 +0200313 p += 8;
Giovanni Bajo0852d762012-04-28 03:49:24 +0200314 if (!verifyalg_add_rdata(alg, ntohs(sigtype), header, pktlen, p))
315 return 0;
Giovanni Bajoe292e932012-04-22 14:32:02 +0200316 }
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200317 alg->vtbl->end_data(alg);
Giovanni Bajoe292e932012-04-22 14:32:02 +0200318
Giovanni Bajo50a96b62012-04-28 01:04:56 +0200319 /* We don't need the owner name anymore; now extract the signer name */
320 if (!extract_name_no_compression(sigrdata+18, signer_name_rdlen, signer_name))
321 return 0;
322
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200323 out->alg = alg;
324 out->keytag = keytag;
325 out->signer_name = signer_name;
326 return 1;
327}
328
329static int end_rrsig_validation(PendingRRSIGValidation *val, struct crec *crec_dnskey)
330{
Giovanni Bajoa7338642012-04-26 14:37:22 +0200331 /* FIXME: keydata is non-contiguous */
Giovanni Bajo708bcd22012-04-25 20:19:07 +0200332 return val->alg->vtbl->verify(val->alg, crec_dnskey->addr.key.keydata, crec_dnskey->uid);
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200333}
334
335static void dnssec_parserrsig(struct dns_header *header, size_t pktlen,
336 unsigned char *reply, int count, char *owner,
337 int sigclass, int sigrdlen, unsigned char *sig)
338{
339 PendingRRSIGValidation val;
340
341 /* Initiate the RRSIG validation process. The pending state is returned into val. */
342 if (!begin_rrsig_validation(header, pktlen, reply, count, owner, sigclass, sigrdlen, sig, &val))
343 return;
344
345 printf("RRSIG: querying cache for DNSKEY %s (keytag: %d)\n", val.signer_name, val.keytag);
Giovanni Bajo20bccd42012-04-25 20:22:16 +0200346
347 /* Look in the cache for *all* the DNSKEYs with matching signer_name and keytag */
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200348 char onekey = 0;
349 struct crec *crecp = NULL;
Giovanni Bajoa55ce082012-04-28 03:48:09 +0200350 while ((crecp = cache_find_by_name(crecp, val.signer_name, time(0), F_DNSKEY))) /* TODO: time(0) */
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200351 {
352 onekey = 1;
353
Giovanni Bajo20bccd42012-04-25 20:22:16 +0200354 if (crecp->addr.key.keytag == val.keytag
355 && crecp->addr.key.algo == verifyalg_algonum(val.alg))
356 {
357 printf("RRSIG: found DNSKEY %d in cache, attempting validation\n", val.keytag);
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200358
Giovanni Bajo20bccd42012-04-25 20:22:16 +0200359 if (end_rrsig_validation(&val, crecp))
360 printf("Validation OK\n");
361 else
362 printf("Validation FAILED\n");
363 }
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200364 }
365
366 if (!onekey)
367 {
Giovanni Bajoccca70c2012-04-25 20:15:35 +0200368 printf("DNSKEY not found, need to fetch it\n");
Giovanni Bajoadca3e92012-04-25 17:46:53 +0200369 /* TODO: store PendingRRSIGValidation in routing table,
370 fetch key (and make it go through dnssec_parskey), then complete validation. */
371 }
Giovanni Bajoe292e932012-04-22 14:32:02 +0200372}
373
Giovanni Bajo3471f182012-04-25 17:49:16 +0200374/* Compute keytag (checksum to quickly index a key). See RFC4034 */
375static int dnskey_keytag(unsigned char *rdata, int rdlen)
376{
377 unsigned long ac;
378 int i;
379
380 ac = 0;
381 for (i = 0; i < rdlen; ++i)
382 ac += (i & 1) ? rdata[i] : rdata[i] << 8;
383 ac += (ac >> 16) & 0xFFFF;
384 return ac & 0xFFFF;
385}
386
387int dnssec_parsekey(struct dns_header *header, size_t pktlen, char *owner, unsigned long ttl,
388 int rdlen, unsigned char *rdata)
389{
390 int flags, proto, alg;
391 struct keydata *key; struct crec *crecp;
Giovanni Bajo3471f182012-04-25 17:49:16 +0200392 unsigned char *ordata = rdata; int ordlen = rdlen;
393
394 CHECKED_GETSHORT(flags, rdata, rdlen);
395 CHECKED_GETCHAR(proto, rdata, rdlen);
396 CHECKED_GETCHAR(alg, rdata, rdlen);
397
398 if (proto != 3)
399 return 0;
Giovanni Bajo0d829eb2012-04-25 18:17:50 +0200400 /* Skip non-signing keys (as specified in RFC4034 */
401 if (!(flags & 0x100))
402 return 0;
Giovanni Bajo3471f182012-04-25 17:49:16 +0200403
Giovanni Bajoa55ce082012-04-28 03:48:09 +0200404 key = keydata_alloc((char*)rdata, rdlen);
Giovanni Bajo3471f182012-04-25 17:49:16 +0200405
Giovanni Bajo3471f182012-04-25 17:49:16 +0200406 /* TODO: time(0) is correct here? */
407 crecp = cache_insert(owner, NULL, time(0), ttl, F_FORWARD | F_DNSKEY);
408 if (crecp)
409 {
410 /* TODO: improve union not to name "uid" this field */
411 crecp->uid = rdlen;
412 crecp->addr.key.keydata = key;
413 crecp->addr.key.algo = alg;
414 crecp->addr.key.keytag = dnskey_keytag(ordata, ordlen);
415 printf("DNSKEY: storing key for %s (keytag: %d)\n", owner, crecp->addr.key.keytag);
416 }
417 else
418 {
419 keydata_free(key);
420 /* TODO: if insertion really might fail, verify we don't depend on cache
421 insertion success for validation workflow correctness */
422 printf("DNSKEY: cache insertion failure\n");
423 return 0;
424 }
Giovanni Bajo3471f182012-04-25 17:49:16 +0200425 return 1;
426}
427
428int dnssec_parseds(struct dns_header *header, size_t pktlen, char *owner, unsigned long ttl,
429 int rdlen, unsigned char *rdata)
430{
431 return 0;
432}
Giovanni Bajoe292e932012-04-22 14:32:02 +0200433
434int dnssec_validate(struct dns_header *header, size_t pktlen)
435{
436 unsigned char *p, *reply;
437 char *owner = daemon->namebuff;
Giovanni Bajo23c21762012-04-28 12:22:41 +0200438 int i, s, qtype, qclass, rdlen;
Giovanni Bajoe292e932012-04-22 14:32:02 +0200439 unsigned long ttl;
Giovanni Bajo23c21762012-04-28 12:22:41 +0200440 int slen[3] = { ntohs(header->ancount), ntohs(header->nscount), ntohs(header->arcount) };
Giovanni Bajoe292e932012-04-22 14:32:02 +0200441
Giovanni Bajo23c21762012-04-28 12:22:41 +0200442 if (slen[0] + slen[1] + slen[2] == 0)
Giovanni Bajoe292e932012-04-22 14:32:02 +0200443 return 0;
444 if (!(reply = p = skip_questions(header, pktlen)))
445 return 0;
Giovanni Bajod0edff72012-04-25 20:16:22 +0200446
447 /* First, process DNSKEY/DS records and add them to the cache. */
448 cache_start_insert();
Giovanni Bajo23c21762012-04-28 12:22:41 +0200449 for (i = 0; i < slen[0]; i++)
Giovanni Bajoe292e932012-04-22 14:32:02 +0200450 {
451 if (!extract_name(header, pktlen, &p, owner, 1, 10))
452 return 0;
453 GETSHORT(qtype, p);
454 GETSHORT(qclass, p);
455 GETLONG(ttl, p);
456 GETSHORT(rdlen, p);
Giovanni Bajo3471f182012-04-25 17:49:16 +0200457 if (qtype == T_DS)
458 {
459 printf("DS found\n");
460 dnssec_parseds(header, pktlen, owner, ttl, rdlen, p);
461 }
462 else if (qtype == T_DNSKEY)
463 {
464 printf("DNSKEY found\n");
Giovanni Bajo47f99dd2012-04-25 18:03:52 +0200465 dnssec_parsekey(header, pktlen, owner, ttl, rdlen, p);
Giovanni Bajo3471f182012-04-25 17:49:16 +0200466 }
Giovanni Bajo4137b842012-04-25 18:13:41 +0200467 p += rdlen;
468 }
Giovanni Bajod0edff72012-04-25 20:16:22 +0200469 cache_end_insert();
Giovanni Bajo4137b842012-04-25 18:13:41 +0200470
Giovanni Bajod0edff72012-04-25 20:16:22 +0200471 /* After we have cached DNSKEY/DS records, start looking for RRSIGs.
Giovanni Bajo4137b842012-04-25 18:13:41 +0200472 We want to do this in a separate step because we want the cache
473 to be already populated with DNSKEYs before parsing signatures. */
474 p = reply;
Giovanni Bajo23c21762012-04-28 12:22:41 +0200475 for (s = 0; s < 3; ++s)
Giovanni Bajo4137b842012-04-25 18:13:41 +0200476 {
Giovanni Bajo23c21762012-04-28 12:22:41 +0200477 reply = p;
478 for (i = 0; i < slen[s]; i++)
Giovanni Bajoe292e932012-04-22 14:32:02 +0200479 {
Giovanni Bajo23c21762012-04-28 12:22:41 +0200480 if (!extract_name(header, pktlen, &p, owner, 1, 10))
481 return 0;
482 GETSHORT(qtype, p);
483 GETSHORT(qclass, p);
484 GETLONG(ttl, p);
485 GETSHORT(rdlen, p);
486 if (qtype == T_RRSIG)
487 {
488 printf("RRSIG found (owner: %s)\n", owner);
489 /* TODO: missing logic. We should only validate RRSIGs for which we
490 have a valid DNSKEY that is referenced by a DS record upstream.
491 There is a memory vs CPU conflict here; should we validate everything
492 to save memory and thus waste CPU, or better first acquire all information
493 (wasting memory) and then doing the minimum CPU computations required? */
494 dnssec_parserrsig(header, pktlen, reply, slen[s], owner, qclass, rdlen, p);
495 }
496 p += rdlen;
Giovanni Bajo4137b842012-04-25 18:13:41 +0200497 }
Giovanni Bajoe292e932012-04-22 14:32:02 +0200498 }
Giovanni Bajo4137b842012-04-25 18:13:41 +0200499
Giovanni Bajoe292e932012-04-22 14:32:02 +0200500 return 1;
501}