cjdnsadmin.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. /* vim: set expandtab ts=4 sw=4: */
  2. /*
  3. * You may redistribute this program and/or modify it under the terms of
  4. * the GNU General Public License as published by the Free Software Foundation,
  5. * either version 3 of the License, or (at your option) any later version.
  6. *
  7. * This program is distributed in the hope that it will be useful,
  8. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. * GNU General Public License for more details.
  11. *
  12. * You should have received a copy of the GNU General Public License
  13. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. var UDP = require('dgram');
  16. var Bencode = require('./bencode');
  17. var Crypto = require('crypto');
  18. var Fs = require('fs');
  19. var nThen = require('./nthen');
  20. var Semaphore = require('../../../node_build/Semaphore.js');
  21. var TIMEOUT_MILLISECONDS = 10000;
  22. var sendmsg = function (sock, addr, port, msg, txid, callback) {
  23. var to = setTimeout(function () {
  24. callback(new Error("timeout after " + TIMEOUT_MILLISECONDS + "ms"));
  25. delete sock.handlers[json.txid];
  26. }, TIMEOUT_MILLISECONDS);
  27. sock.handlers[txid] = {
  28. callback: callback,
  29. timeout: to
  30. };
  31. sock.send(msg, 0, msg.length, port, addr, function(err, bytes) {
  32. if (err) {
  33. clearTimeout(to);
  34. delete sock.handlers[txid];
  35. callback(err);
  36. }
  37. });
  38. };
  39. var callFunc = function (sock, addr, port, pass, func, args, callback) {
  40. var cookieTxid = String(sock.counter++);
  41. var cookieMsg = new Buffer(Bencode.encode({'q':'cookie','txid':cookieTxid}));
  42. sendmsg(sock, addr, port, cookieMsg, cookieTxid, function (err, ret) {
  43. if (err) { callback(err); return; }
  44. var cookie = ret.cookie;
  45. if (typeof(cookie) !== 'string') { throw new Error("invalid cookie in [" + ret + "]"); }
  46. var json = {
  47. txid: String(sock.counter++),
  48. q: 'auth',
  49. aq: func,
  50. args: {}
  51. };
  52. Object.keys(args).forEach(function (arg) {
  53. json.args[arg] = args[arg];
  54. });
  55. json.cookie = cookie;
  56. json.hash = Crypto.createHash('sha256').update(pass + cookie).digest('hex');
  57. json.hash = Crypto.createHash('sha256').update(Bencode.encode(json)).digest('hex');
  58. sendmsg(sock, addr, port, new Buffer(Bencode.encode(json)), json.txid, callback);
  59. });
  60. };
  61. var getArgs = function (func) {
  62. var rArgs = [];
  63. Object.keys(func).forEach(function (name) {
  64. if (func[name].required === '1') {
  65. rArgs.push({ name: name, type: func[name].type, required: true });
  66. }
  67. });
  68. // be sure that the arguments are always in the same order
  69. rArgs.sort(function (a,b) { a = a.name; b = b.name; return (a !== b) ? (a < b) ? 1 : -1 : 0 });
  70. var oArgs = [];
  71. Object.keys(func).forEach(function (name) {
  72. if (func[name].required === '0') {
  73. oArgs.push({ name: name, type: func[name].type, required: false });
  74. }
  75. });
  76. oArgs.sort(function (a,b) { a = a.name; b = b.name; return (a !== b) ? (a < b) ? 1 : -1 : 0 });
  77. rArgs.push.apply(rArgs, oArgs);
  78. return rArgs;
  79. };
  80. var makeFunctionDescription = function (funcName, func) {
  81. var args = getArgs(func);
  82. var outArgs = [];
  83. args.forEach(function (arg) {
  84. outArgs.push( ((arg.required) ? 'required ' : '') + arg.type + ' ' + arg.name );
  85. });
  86. return funcName + "(" + outArgs.join(', ') + ")";
  87. };
  88. var compatibleType = function (typeName, obj) {
  89. switch (typeName) {
  90. case 'Int': return (typeof(obj) === 'number' && Math.floor(obj) === obj);
  91. case 'String': return (typeof(obj) === 'string');
  92. case 'Dict': return (typeof(obj) === 'object');
  93. case 'List': return Array.isArray(obj);
  94. default: throw new Error();
  95. };
  96. };
  97. var makeFunction = function (sock, addr, port, pass, funcName, func) {
  98. var args = getArgs(func);
  99. return function () {
  100. var i;
  101. var argsOut = {};
  102. for (i = 0; i < arguments.length-1; i++) {
  103. var arg = arguments[i];
  104. if (!args[i].required && (arg === null || arg === undefined)) { continue; }
  105. if (!compatibleType(args[i].type, arg)) {
  106. throw new Error("argument [" + i + "] [" + args[i].type + " " + args[i].name + "]" +
  107. " is of type [" + typeof(arg) + "] which is not compatible with " +
  108. "required type " + args[i].type);
  109. }
  110. argsOut[args[i].name] = arg;
  111. }
  112. if (args.length > i && args[i].required) {
  113. throw new Error("argument [" + i + "] [" + args[i].type + " " + args[i].name + "] is " +
  114. "required and is not specified");
  115. }
  116. var callback = arguments[arguments.length-1];
  117. if (typeof(callback) !== 'function') {
  118. throw new Error("callback is unspecified");
  119. }
  120. sock.semaphore.take(function (returnAfter) {
  121. callFunc(sock, addr, port, pass, funcName, argsOut, returnAfter(callback));
  122. });
  123. };
  124. };
  125. var getFunctions = function (sock, addr, port, pass, callback) {
  126. var funcs = {};
  127. nThen(function (waitFor) {
  128. var next = function (i) {
  129. callFunc(sock, addr, port, pass, 'Admin_availableFunctions', {page:i},
  130. waitFor(function (err, ret) {
  131. if (err) { throw err; }
  132. Object.keys(ret.availableFunctions).forEach(function (funcName) {
  133. funcs[funcName] = ret.availableFunctions[funcName];
  134. })
  135. if (Object.keys(ret.availableFunctions).length > 0) {
  136. next(i+1);
  137. }
  138. })
  139. );
  140. }
  141. next(0);
  142. }).nThen(function (waitFor) {
  143. var funcDescriptions = [];
  144. var cjdns = {};
  145. Object.keys(funcs).forEach(function (name) {
  146. cjdns[name] = makeFunction(sock, addr, port, pass, name, funcs[name]);
  147. funcDescriptions.push(makeFunctionDescription(name, funcs[name]));
  148. });
  149. cjdns.functions = function (cb) { cb(undefined, funcDescriptions); };
  150. callback(cjdns);
  151. });
  152. };
  153. var connect = module.exports.connect = function (addr, port, pass, callback) {
  154. var sock = UDP.createSocket((addr.indexOf(':') !== -1) ? 'udp6' : 'udp4');
  155. sock.semaphore = Semaphore.create(4);
  156. sock.handlers = {};
  157. sock.counter = Math.floor(Math.random() * 4000000000);
  158. sock.on('message', function (msg) {
  159. var response = Bencode.decode(msg.toString('utf8'));
  160. if (!response.txid) {
  161. throw new Error("Response [" + msg + "] with no txid");
  162. }
  163. var handler = sock.handlers[response.txid];
  164. if (!handler) { return; }
  165. clearTimeout(handler.timeout);
  166. delete sock.handlers[response.txid];
  167. handler.callback(undefined, response);
  168. });
  169. nThen(function (waitFor) {
  170. callFunc(sock, addr, port, pass, 'ping', {}, waitFor(function (err, ret) {
  171. if (err) { throw err; }
  172. //console.log("got pong! [" + JSON.stringify(ret) + "]");
  173. }));
  174. }).nThen(function (waitFor) {
  175. getFunctions(sock, addr, port, pass, function (cjdns) {
  176. cjdns.disconnect = function () { sock.close() };
  177. callback(cjdns);
  178. });
  179. });
  180. };
  181. var connectWithAdminInfo = module.exports.connectWithAdminInfo = function (callback) {
  182. var cjdnsAdmin;
  183. nThen(function (waitFor) {
  184. Fs.readFile(process.env.HOME + '/.cjdnsadmin', waitFor(function (err, ret) {
  185. if (err) { throw err; }
  186. cjdnsAdmin = JSON.parse(String(ret));
  187. }));
  188. }).nThen(function (waitFor) {
  189. connect(cjdnsAdmin.addr, cjdnsAdmin.port, cjdnsAdmin.password, callback);
  190. });
  191. };