Source file loader.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
(*
 * Copyright (c) 2005-2006 Tim Deegan <tjd@phlegethon.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * dnsloader.ml -- how to build up a DNS trie from separate RRs
 *
 *)

open RR
open Trie
open Printf

(* Loader database: the DNS trie plus a hash table of other names in use *)
type db = {
    trie: dnstrie;		       	     (* Names that have RRSets *)
    mutable names: (Name.key, dnsnode) Hashtbl.t; (* All other names *)
  }

(* Get a new, empty database *)
let new_db () = { trie = new_trie ();
		  names = Hashtbl.create 101;
		}

(* Throw away the known names: call when guaranteed no more updates *)
let no_more_updates db = Hashtbl.clear db.names; db.names <- Hashtbl.create 1

(* Get the dnsnode that represents this name, making a new one if needed *)
let get_target_dnsnode owner db =
  let key = Name.to_key owner in
  match simple_lookup key db.trie with
    Some n -> n
  | None ->
      try
      	Hashtbl.find db.names key
      with Not_found ->
	let n = { owner = Name.hashcons owner;
		  rrsets = []; }
	in Hashtbl.add db.names key n ;
	n

(* Get the dnsnode that represents this name, making a new one if needed,
   inserting it into the trie, and returning both trie node and dnsnode *)
let get_owner_dnsnode owner db =
  let pull_name tbl key owner () =
    try
      match Hashtbl.find tbl key with
	d -> Hashtbl.remove tbl key; d
    with Not_found -> { owner = Name.hashcons owner;
			rrsets = []; }
  in
  let key = Name.to_key owner in
  lookup_or_insert key db.trie (pull_name db.names key owner)


(* How to add each type of RR to the database... *)
exception TTLMismatch

let add_rrset rrset owner db =
(* Merge a new RRSet into a list of RRSets. Returns the new list and the
   ttl of the resulting RRset. Reverses the order of the RRsets in the
   list *)
  let merge_rrset new_rrset rrsets =
    let cfn a b = compare (Hashtbl.hash a) (Hashtbl.hash b) in
    let mfn n o = List.merge cfn (List.fast_sort cfn n) o in
    let rec do_merge new_ttl new_rdata rrsets_done rrsets_rest =
      match rrsets_rest with
        | [] -> (new_ttl, { ttl = new_ttl; rdata = new_rdata } :: rrsets_done )
        | rrset :: rest -> match (new_rdata, rrset.rdata) with
            (A l1, A l2) ->
              (rrset.ttl, List.rev_append rest
                ({ ttl = rrset.ttl; rdata = A (mfn l1 l2) } :: rrsets_done))
            | (NS l1, NS l2) ->
                (rrset.ttl, List.rev_append rest
                  ({ ttl = rrset.ttl; rdata = NS (mfn l1 l2) } :: rrsets_done))
            | (CNAME l1, CNAME l2) ->
                (rrset.ttl, List.rev_append rest
                  ({ ttl = rrset.ttl; rdata = CNAME (mfn l1 l2) } :: rrsets_done))
            | (SOA l1, SOA l2) ->
                (rrset.ttl, List.rev_append rest
                  ({ ttl = rrset.ttl; rdata = SOA (mfn l1 l2) } :: rrsets_done))
            | (MB l1, MB l2) ->
                (rrset.ttl, List.rev_append rest
                  ({ ttl = rrset.ttl; rdata = MB (mfn l1 l2) } :: rrsets_done))
            | (MG l1, MG l2) ->
                (rrset.ttl, List.rev_append rest
                  ({ ttl = rrset.ttl; rdata = MG (mfn l1 l2) } :: rrsets_done))
            | (MR l1, MR l2) ->
                (rrset.ttl, List.rev_append rest
                  ({ ttl = rrset.ttl; rdata = MR (mfn l1 l2) } :: rrsets_done))
            | (WKS l1, WKS l2) ->
                (rrset.ttl, List.rev_append rest
                  ({ ttl = rrset.ttl; rdata = WKS (mfn l1 l2) } :: rrsets_done))
            | (PTR l1, PTR l2) ->
                (rrset.ttl, List.rev_append rest
                  ({ ttl = rrset.ttl; rdata = PTR (mfn l1 l2) } :: rrsets_done))
            | (HINFO l1, HINFO l2) ->
                (rrset.ttl, List.rev_append rest
                  ({ ttl = rrset.ttl; rdata = HINFO (mfn l1 l2) } :: rrsets_done))
            | (MINFO l1, MINFO l2) ->
                (rrset.ttl, List.rev_append rest
                  ({ ttl = rrset.ttl; rdata = MINFO (mfn l1 l2) } :: rrsets_done))
            | (MX l1, MX l2) ->
                (rrset.ttl, List.rev_append rest
                  ({ ttl = rrset.ttl; rdata = MX (mfn l1 l2) } :: rrsets_done))
            | (TXT l1, TXT l2) ->
                (rrset.ttl, List.rev_append rest
                  ({ ttl = rrset.ttl; rdata = TXT (mfn l1 l2) } :: rrsets_done))
            | (RP l1, RP l2) ->
                (rrset.ttl, List.rev_append rest
                  ({ ttl = rrset.ttl; rdata = RP (mfn l1 l2) } :: rrsets_done))
            | (AFSDB l1, AFSDB l2) ->
                (rrset.ttl, List.rev_append rest
                  ({ ttl = rrset.ttl; rdata = AFSDB (mfn l1 l2) } :: rrsets_done))
            | (X25 l1, X25 l2) ->
                (rrset.ttl, List.rev_append rest
                  ({ ttl = rrset.ttl; rdata = X25 (mfn l1 l2) } :: rrsets_done))
            | (ISDN l1, ISDN l2) ->
                (rrset.ttl, List.rev_append rest
                  ({ ttl = rrset.ttl; rdata = ISDN (mfn l1 l2) } :: rrsets_done))
            | (RT l1, RT l2) ->
                (rrset.ttl, List.rev_append rest
                  ({ ttl = rrset.ttl; rdata = RT (mfn l1 l2) } :: rrsets_done))
            | (AAAA l1, AAAA l2) ->
                (rrset.ttl, List.rev_append rest
                  ({ ttl = rrset.ttl; rdata = AAAA (mfn l1 l2) } :: rrsets_done))
            | (SRV l1, SRV l2) ->
                (rrset.ttl, List.rev_append rest
                  ({ ttl = rrset.ttl; rdata = SRV (mfn l1 l2) } :: rrsets_done))
            (* | (UNSPEC l1, UNSPEC l2) -> *)
            (*     (rrset.ttl, List.rev_append rest *)
            (*       ({ ttl = rrset.ttl; rdata = UNSPEC (mfn l1 l2) } :: rrsets_done)) *)
            | (DNSKEY l1, DNSKEY l2) ->
                (rrset.ttl, List.rev_append rest
                  ({ ttl = rrset.ttl; rdata = DNSKEY (mfn l1 l2) } :: rrsets_done))
            | (DS l1, DS l2) ->
                (rrset.ttl, List.rev_append rest
                  ({ ttl = rrset.ttl; rdata = DS (mfn l1 l2) } :: rrsets_done))
             | (Unknown (t1, l1), Unknown (t2, l2)) ->
                if t1 = t2 then
                  (rrset.ttl, List.rev_append rest
                    ({ ttl = rrset.ttl; rdata = Unknown (t1,(mfn l1 l2)) }
                     :: rrsets_done))
                else
                  do_merge new_ttl new_rdata (rrset :: rrsets_done) rest
            | (_, _) -> do_merge new_ttl new_rdata (rrset :: rrsets_done) rest
    in
    do_merge new_rrset.ttl new_rrset.rdata [] rrsets
  in
  let ownernode = get_owner_dnsnode owner db in
  let (old_ttl, new_rrsets) = merge_rrset rrset ownernode.rrsets in
  ownernode.rrsets <- new_rrsets;
  if not (old_ttl = rrset.ttl) then raise TTLMismatch

let add_generic_rr tcode str ttl owner db =
  let s = Name.hashcons_string str in
  add_rrset { ttl; rdata = Unknown (tcode, [ s ]) } owner db

let add_a_rr ip ttl owner db =
  add_rrset { ttl; rdata = A [ ip ] } owner db

let add_aaaa_rr ip ttl owner db =
  add_rrset { ttl; rdata = AAAA [ ip ] } owner db

let add_ns_rr target ttl owner db =
  try
    let targetnode = get_target_dnsnode target db in
    add_rrset { ttl; rdata = NS [ targetnode ] } owner db;
    fix_flags (Name.to_key owner) db.trie
  with TTLMismatch ->
    fix_flags (Name.to_key owner) db.trie; raise TTLMismatch

let add_cname_rr target ttl owner db =
  let targetnode = get_target_dnsnode target db in
  add_rrset { ttl; rdata = CNAME [ targetnode ] } owner db

let add_soa_rr master rp serial refresh retry expiry min ttl owner db =
  try
    let masternode = get_target_dnsnode master db in
    let rpnode = get_target_dnsnode rp db in
    let rdata = (masternode, rpnode, serial, refresh, retry, expiry, min) in
    add_rrset { ttl; rdata = SOA [ rdata ] } owner db;
    fix_flags (Name.to_key owner) db.trie
  with TTLMismatch ->
    fix_flags (Name.to_key owner) db.trie; raise TTLMismatch

let add_mb_rr target ttl owner db =
  let targetnode = get_target_dnsnode target db in
  add_rrset { ttl; rdata = MB [ targetnode ] } owner db

let add_mg_rr target ttl owner db =
  let targetnode = get_target_dnsnode target db in
  add_rrset { ttl; rdata = MG [ targetnode ] } owner db

let add_mr_rr target ttl owner db =
  let targetnode = get_target_dnsnode target db in
  add_rrset { ttl; rdata = MR [ targetnode ] } owner db

let add_wks_rr addr prot bitmap ttl owner db =
  let b = Name.hashcons_string bitmap in
  add_rrset { ttl; rdata = WKS [ (addr, prot, b) ] } owner db

let add_ptr_rr target ttl owner db =
  let targetnode = get_target_dnsnode target db in
  add_rrset { ttl; rdata = PTR [ targetnode ] } owner db

let add_hinfo_rr cpu os ttl owner db =
  let c = Name.hashcons_string cpu in
  let o = Name.hashcons_string os in
  add_rrset { ttl; rdata = HINFO [ (c, o) ] } owner db

let add_minfo_rr rmailbx emailbx ttl owner db =
  let rtarget = get_target_dnsnode rmailbx db in
  let etarget = get_target_dnsnode emailbx db in
  add_rrset { ttl; rdata = MINFO [ (rtarget, etarget) ] } owner db

let add_mx_rr pri target ttl owner db =
  let pri = pri in
  let targetnode = get_target_dnsnode target db in
  add_rrset { ttl; rdata = MX [ (pri, targetnode) ] } owner db

let add_txt_rr strl ttl owner db =
  let sl = List.map Name.hashcons_string strl in
  add_rrset { ttl; rdata = TXT [ sl ] } owner db

let add_rp_rr mbox txt ttl owner db =
  let mtarget = get_target_dnsnode mbox db in
  let ttarget = get_target_dnsnode txt db in
  add_rrset { ttl; rdata = RP [ (mtarget, ttarget) ] } owner db

let add_afsdb_rr subtype target ttl owner db =
  let st = subtype in
  let targetnode = get_target_dnsnode target db in
  add_rrset { ttl; rdata = AFSDB [ (st, targetnode) ] } owner db

let add_x25_rr addr ttl owner db =
  let a = Name.hashcons_string addr in
  add_rrset { ttl; rdata = X25 [ a ] } owner db

let add_isdn_rr addr sa ttl owner db =
  let a = Name.hashcons_string addr in
  let s = match sa with
    | None -> None
    | Some x -> Some (Name.hashcons_string x) in
  add_rrset { ttl; rdata = ISDN [ (a, s) ] } owner db

let add_rt_rr pref target ttl owner db =
  let pref = pref in
  let targetnode = get_target_dnsnode target db in
  add_rrset { ttl; rdata = RT [ (pref, targetnode) ] } owner db

let add_srv_rr pri weight port target ttl owner db =
  let pri = pri in
  let weight = weight in
  let port = port in
  let targetnode = get_target_dnsnode target db in
  add_rrset { ttl;
	      rdata = SRV [ (pri, weight, port, targetnode) ] } owner db

(* let add_unspec_rr str ttl owner db = *)
(*   let s = hashcons_charstring str in  *)
(*   add_rrset { ttl; rdata = UNSPEC [ s ] } owner db *)

let add_dnskey_rr flags typ key ttl owner db =
  let flags = flags in
  let typ = typ in
  let tmp = Base64.decode_exn key in
  let dnskey = Name.hashcons_string tmp in
  add_rrset { ttl; rdata = DNSKEY [ (flags, typ, dnskey) ] } owner db

(** valeur entière d'un chiffre hexa *)
let char_of_hex_value c =
  int_of_char c - (
    if c >= '0' && c <= '9' then 48 (*int_of_char '0'*)
              else if c >= 'A' && c <= 'F' then 55 (* int_of_char 'A' - 10 *)
    else if c >= 'a' && c <= 'f' then 87 (* int_of_char 'a' - 10
                  *)
              else assert false
  )

let init n f =
  if n >= 0
  then
    let s = Bytes.create n in
    for i = 0 to pred n do
      Bytes.set s i (f i)
    done ;
    s
    else
      let n = (- n) in
      let s = Bytes.create n in
      for i = pred n downto 0 do
        Bytes.set s i (f (n-i-1))
    done ;
    s

let string_of_hex s =
  let l = String.length s in
  if l land 1 = 1 then invalid_arg "Bytes.from_hex" ;
        init (l lsr 1) (
          fun i ->
            let i = i lsl 1 in
            Char.chr (
              (char_of_hex_value (String.get s i) lsl 4)
              + (char_of_hex_value (String.get s (i+1)))
            )
       ) |> Bytes.to_string


let add_ds_rr tag alg digest key ttl owner db =
  let alg =
    match (Packet.int_to_dnssec_alg alg) with
      | None -> failwith (sprintf "add_ds_rr: unsupported alg id %d" alg)
      | Some a -> a
  in
  let digest =
    match (Packet.int_to_digest_alg digest) with
      | Some a -> a
      | None -> failwith (sprintf "add_ds_rr : invalid hashing alg %d" digest)
  in
  let tmp = string_of_hex key in
  let ds = Name.hashcons_string tmp in
  add_rrset { ttl; rdata = DS [ (tag, alg, digest, ds) ] } owner db

let add_rrsig_rr typ alg lbl orig_ttl exp_ts inc_ts tag name sign ttl owner db =
  let typ =
    match (Packet.string_to_rr_type ("RR_"^typ)) with
      | None -> failwith (sprintf "add_rrsig_rr failed: uknown type %s" typ)
      | Some a -> a
            in
  let alg =
    match (Packet.int_to_dnssec_alg alg) with
      | None -> failwith (sprintf "add_rrsig_rr failed: uknown dnssec alg %d" alg)
      | Some a -> a
  in
    (* TODO: Check if sign is in the future or if the sign has expired *)
  let sign = Base64.decode_exn sign in
  let rr = RRSIG [{
    rrsig_type   = typ;
    rrsig_alg    = alg;
    rrsig_labels = char_of_int lbl;
    rrsig_ttl    = orig_ttl;
    rrsig_expiry = exp_ts;
    rrsig_incept = inc_ts;
    rrsig_keytag = tag;
    rrsig_name   = name;
    rrsig_sig    = sign;
  }] in
  add_rrset { ttl; rdata = rr; } owner db

  (* State variables for the parser & lexer *)
type parserstate = {
  mutable db: db;
  mutable paren: int;
  mutable filename: string;
  mutable lineno: int;
  mutable origin: Name.t;
  mutable ttl: int32;
  mutable owner: Name.t;
}

let new_state () = {
  db = new_db ();
  paren = 0;
  filename = "";
  lineno = 1;
  ttl = Int32.of_int 3600;
  origin = Name.empty;
  owner = Name.empty;
}

let state = new_state ()