dns: verify argument is valid function in resolve
[platform/upstream/nodejs.git] / lib / dns.js
1 // Copyright Joyent, Inc. and other Node contributors.
2 //
3 // Permission is hereby granted, free of charge, to any person obtaining a
4 // copy of this software and associated documentation files (the
5 // 'Software'), to deal in the Software without restriction, including
6 // without limitation the rights to use, copy, modify, merge, publish,
7 // distribute, sublicense, and/or sell copies of the Software, and to permit
8 // persons to whom the Software is furnished to do so, subject to the
9 // following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included
12 // in all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17 // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18 // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20 // USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22 var net = require('net');
23 var util = require('util');
24
25 var cares = process.binding('cares_wrap');
26 var uv = process.binding('uv');
27
28 var isIp = net.isIP;
29
30
31 function errnoException(err, syscall, hostname) {
32   // FIXME(bnoordhuis) Remove this backwards compatibility shite and pass
33   // the true error to the user. ENOTFOUND is not even a proper POSIX error!
34   if (err === uv.UV_EAI_MEMORY ||
35       err === uv.UV_EAI_NODATA ||
36       err === uv.UV_EAI_NONAME) {
37     err = 'ENOTFOUND';
38   }
39   var ex = null;
40   if (typeof err === 'string') {  // c-ares error code.
41     ex = new Error(syscall + ' ' + err);
42     ex.code = err;
43     ex.errno = err;
44     ex.syscall = syscall;
45   } else {
46     ex = util._errnoException(err, syscall);
47   }
48   if (hostname) {
49     ex.hostname = hostname;
50   }
51   return ex;
52 }
53
54
55 // c-ares invokes a callback either synchronously or asynchronously,
56 // but the dns API should always invoke a callback asynchronously.
57 //
58 // This function makes sure that the callback is invoked asynchronously.
59 // It returns a function that invokes the callback within nextTick().
60 //
61 // To avoid invoking unnecessary nextTick(), `immediately` property of
62 // returned function should be set to true after c-ares returned.
63 //
64 // Usage:
65 //
66 // function someAPI(callback) {
67 //   callback = makeAsync(callback);
68 //   channel.someAPI(..., callback);
69 //   callback.immediately = true;
70 // }
71 function makeAsync(callback) {
72   if (!util.isFunction(callback)) {
73     return callback;
74   }
75   return function asyncCallback() {
76     if (asyncCallback.immediately) {
77       // The API already returned, we can invoke the callback immediately.
78       callback.apply(null, arguments);
79     } else {
80       var args = arguments;
81       process.nextTick(function() {
82         callback.apply(null, args);
83       });
84     }
85   };
86 }
87
88
89 function onlookup(err, addresses) {
90   if (err) {
91     return this.callback(errnoException(err, 'getaddrinfo', this.hostname));
92   }
93   if (this.family) {
94     this.callback(null, addresses[0], this.family);
95   } else {
96     this.callback(null, addresses[0], addresses[0].indexOf(':') >= 0 ? 6 : 4);
97   }
98 }
99
100
101 // Easy DNS A/AAAA look up
102 // lookup(hostname, [family,] callback)
103 exports.lookup = function(hostname, family, callback) {
104   // parse arguments
105   if (arguments.length === 2) {
106     callback = family;
107     family = 0;
108   } else if (!family) {
109     family = 0;
110   } else {
111     family = +family;
112     if (family !== 4 && family !== 6) {
113       throw new Error('invalid argument: `family` must be 4 or 6');
114     }
115   }
116   callback = makeAsync(callback);
117
118   if (!hostname) {
119     callback(null, null, family === 6 ? 6 : 4);
120     return {};
121   }
122
123   var matchedFamily = net.isIP(hostname);
124   if (matchedFamily) {
125     callback(null, hostname, matchedFamily);
126     return {};
127   }
128
129   var req = {
130     callback: callback,
131     family: family,
132     hostname: hostname,
133     oncomplete: onlookup
134   };
135   var err = cares.getaddrinfo(req, hostname, family);
136   if (err) throw errnoException(err, 'getaddrinfo', hostname);
137
138   callback.immediately = true;
139   return req;
140 };
141
142
143 function onresolve(err, result) {
144   if (err)
145     this.callback(errnoException(err, this.bindingName, this.hostname));
146   else
147     this.callback(null, result);
148 }
149
150
151 function resolver(bindingName) {
152   var binding = cares[bindingName];
153
154   return function query(name, callback) {
155     callback = makeAsync(callback);
156     var req = {
157       bindingName: bindingName,
158       callback: callback,
159       hostname: name,
160       oncomplete: onresolve
161     };
162     var err = binding(req, name);
163     if (err) throw errnoException(err, bindingName);
164     callback.immediately = true;
165     return req;
166   }
167 }
168
169
170 var resolveMap = {};
171 exports.resolve4 = resolveMap.A = resolver('queryA');
172 exports.resolve6 = resolveMap.AAAA = resolver('queryAaaa');
173 exports.resolveCname = resolveMap.CNAME = resolver('queryCname');
174 exports.resolveMx = resolveMap.MX = resolver('queryMx');
175 exports.resolveNs = resolveMap.NS = resolver('queryNs');
176 exports.resolveTxt = resolveMap.TXT = resolver('queryTxt');
177 exports.resolveSrv = resolveMap.SRV = resolver('querySrv');
178 exports.resolveNaptr = resolveMap.NAPTR = resolver('queryNaptr');
179 exports.resolveSoa = resolveMap.SOA = resolver('querySoa');
180 exports.reverse = resolveMap.PTR = resolver('getHostByAddr');
181
182
183 exports.resolve = function(hostname, type_, callback_) {
184   var resolver, callback;
185   if (util.isString(type_)) {
186     resolver = resolveMap[type_];
187     callback = callback_;
188   } else if (util.isFunction(type_)) {
189     resolver = exports.resolve4;
190     callback = type_;
191   } else {
192     throw new Error('Type must be a string');
193   }
194
195   if (util.isFunction(resolver)) {
196     return resolver(hostname, callback);
197   } else {
198     throw new Error('Unknown type "' + type_ + '"');
199   }
200 };
201
202
203 exports.getServers = function() {
204   return cares.getServers();
205 };
206
207
208 exports.setServers = function(servers) {
209   // cache the original servers because in the event of an error setting the
210   // servers cares won't have any servers available for resolution
211   var orig = cares.getServers();
212
213   var newSet = [];
214
215   servers.forEach(function(serv) {
216     var ver = isIp(serv);
217
218     if (ver)
219       return newSet.push([ver, serv]);
220
221     var match = serv.match(/\[(.*)\](:\d+)?/);
222
223     // we have an IPv6 in brackets
224     if (match) {
225       ver = isIp(match[1]);
226       if (ver)
227         return newSet.push([ver, match[1]]);
228     }
229
230     var s = serv.split(/:\d+$/)[0];
231     ver = isIp(s);
232
233     if (ver)
234       return newSet.push([ver, s]);
235
236     throw new Error('IP address is not properly formatted: ' + serv);
237   });
238
239   var r = cares.setServers(newSet);
240
241   if (r) {
242     // reset the servers to the old servers, because ares probably unset them
243     cares.setServers(orig.join(','));
244
245     var err = cares.strerror(r);
246     throw new Error('c-ares failed to set servers: "' + err +
247                     '" [' + servers + ']');
248   }
249 };
250
251
252 // ERROR CODES
253 exports.NODATA = 'ENODATA';
254 exports.FORMERR = 'EFORMERR';
255 exports.SERVFAIL = 'ESERVFAIL';
256 exports.NOTFOUND = 'ENOTFOUND';
257 exports.NOTIMP = 'ENOTIMP';
258 exports.REFUSED = 'EREFUSED';
259 exports.BADQUERY = 'EBADQUERY';
260 exports.ADNAME = 'EADNAME';
261 exports.BADFAMILY = 'EBADFAMILY';
262 exports.BADRESP = 'EBADRESP';
263 exports.CONNREFUSED = 'ECONNREFUSED';
264 exports.TIMEOUT = 'ETIMEOUT';
265 exports.EOF = 'EOF';
266 exports.FILE = 'EFILE';
267 exports.NOMEM = 'ENOMEM';
268 exports.DESTRUCTION = 'EDESTRUCTION';
269 exports.BADSTR = 'EBADSTR';
270 exports.BADFLAGS = 'EBADFLAGS';
271 exports.NONAME = 'ENONAME';
272 exports.BADHINTS = 'EBADHINTS';
273 exports.NOTINITIALIZED = 'ENOTINITIALIZED';
274 exports.LOADIPHLPAPI = 'ELOADIPHLPAPI';
275 exports.ADDRGETNETWORKPARAMS = 'EADDRGETNETWORKPARAMS';
276 exports.CANCELLED = 'ECANCELLED';