aboutsummaryrefslogtreecommitdiff
path: root/samples/CoffeeScript/xipd.coffee
blob: 8b49f1ffc5d8f1a29f7d810496fe8269915ca135 (plain)
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
dnsserver = require "dnsserver"

exports.Server = class Server extends dnsserver.Server
  NS_T_A            = 1
  NS_T_NS           = 2
  NS_T_CNAME        = 5
  NS_T_SOA          = 6
  NS_C_IN           = 1
  NS_RCODE_NXDOMAIN = 3

  constructor: (domain, @rootAddress) ->
    super
    @domain = domain.toLowerCase()
    @soa = createSOA @domain
    @on "request", @handleRequest

  handleRequest: (req, res) =>
    question  = req.question
    subdomain = @extractSubdomain question.name

    if subdomain? and isARequest question
      res.addRR question.name, NS_T_A, NS_C_IN, 600, subdomain.getAddress()
    else if subdomain?.isEmpty() and isNSRequest question
      res.addRR question.name, NS_T_SOA, NS_C_IN, 600, @soa, true
    else
      res.header.rcode = NS_RCODE_NXDOMAIN

    res.send()

  extractSubdomain: (name) ->
    Subdomain.extract name, @domain, @rootAddress

  isARequest = (question) ->
    question.type is NS_T_A and question.class is NS_C_IN

  isNSRequest = (question) ->
    question.type is NS_T_NS and question.class is NS_C_IN

  createSOA = (domain) ->
    mname   = "ns-1.#{domain}"
    rname   = "hostmaster.#{domain}"
    serial  = parseInt new Date().getTime() / 1000
    refresh = 28800
    retry   = 7200
    expire  = 604800
    minimum = 3600
    dnsserver.createSOA mname, rname, serial, refresh, retry, expire, minimum

exports.createServer = (domain, address = "127.0.0.1") ->
  new Server domain, address

exports.Subdomain = class Subdomain
  @extract: (name, domain, address) ->
    return unless name
    name = name.toLowerCase()
    offset = name.length - domain.length

    if domain is name.slice offset
      subdomain = if 0 >= offset then null else name.slice 0, offset - 1
      new constructor subdomain, address if constructor = @for subdomain

  @for: (subdomain = "") ->
    if IPAddressSubdomain.pattern.test subdomain
      IPAddressSubdomain
    else if EncodedSubdomain.pattern.test subdomain
      EncodedSubdomain
    else
      Subdomain

  constructor: (@subdomain, @address) ->
    @labels = subdomain?.split(".") ? []
    @length = @labels.length

  isEmpty: ->
    @length is 0

  getAddress: ->
    @address

class IPAddressSubdomain extends Subdomain
  @pattern = /// (^|\.)
    ((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}
    (25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)
  $ ///

  getAddress: ->
    @labels.slice(-4).join "."

class EncodedSubdomain extends Subdomain
  @pattern = /(^|\.)[a-z0-9]{1,7}$/

  getAddress: ->
    decode @labels[@length - 1]

exports.encode = encode = (ip) ->
  value = 0
  for byte, index in ip.split "."
    value += parseInt(byte, 10) << (index * 8)
  (value >>> 0).toString 36

PATTERN = /^[a-z0-9]{1,7}$/

exports.decode = decode = (string) ->
  return unless PATTERN.test string
  value = parseInt string, 36
  ip = []
  for i in [1..4]
    ip.push value & 0xFF
    value >>= 8
  ip.join "."