1
0

cjdnsctl.js 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056
  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. (function () { 'use strict'
  16. let Cjdns = require('./cjdnsadmin/cjdnsadmin');
  17. let Benc = require('./cjdnsadmin/bencode');
  18. let nThen = require('nthen');
  19. let Fs = require('fs');
  20. let Spawn = require('child_process').spawn;
  21. let DEFAULT_BCONF = '/etc/cjdroute.bconf';
  22. let usage = function () {
  23. console.log("Usage: cjdnsctl [ OPTIONS ] OBJECT { COMMAND | help }");
  24. console.log("where OBJECT := { iptun | conf | peers }");
  25. console.log(" OPTIONS := { -f { cjdns-binary-config-file }");
  26. };
  27. let confHelp = function () {
  28. console.log("Usage: cjdnsctl conf gen [ B_LOCATION ] [ yes ]");
  29. console.log(" cjdnsctl conf convert INPUT [ B_LOCATION ] [ yes ]");
  30. console.log(" cjdnsctl conf json [ B_LOCATION ]");
  31. console.log(" cjdnsctl conf get PATH");
  32. console.log(" cjdnsctl conf set PATH VALUE");
  33. console.log("where B_LOCATION := binary file path, default: " + DEFAULT_BCONF);
  34. console.log(" INPUT := a legacy format cjdroute.conf file");
  35. console.log(" PATH := a json path for example authorizedPasswords[0].password");
  36. console.log(" VALUE := any valid json");
  37. };
  38. let iptunHelp = function () {
  39. console.log("Usage: cjdnsctl iptun add KEY APA [ APA ] [ comment COMMENT ]");
  40. console.log(" cjdnsctl iptun rm KEY");
  41. console.log(" cjdnsctl iptun conn KEY");
  42. console.log(" cjdnsctl iptun disconn KEY");
  43. console.log(" cjdnsctl iptun ls");
  44. console.log("where KEY := base-32 encoded string ending in .k");
  45. console.log(" APA := ADDRESS[ [ /PREFIX ] /ALLOCATION ]");
  46. console.log(" PREFIX := 0-32 for IPv4, 0-128 for IPv6 (announced to client)");
  47. console.log(" ALLOCATION := 0-32 for IPv4, 0-128 for IPv6 (issued to client)");
  48. console.log(" COMMENT := a string, never sent, shown in `cjdnsctl iptun ls`");
  49. };
  50. let peersHelp = function () {
  51. console.log("Usage: cjdnsctl peers conn ADDR KEY PASSWD [ user LOGIN ] [ dev DEV ] " +
  52. "[ comment COMMENT ]");
  53. console.log(" cjdnsctl peers disconn KEY");
  54. console.log(" cjdnsctl peers auth PASSWD LOGIN [ comment COMMENT ]");
  55. console.log(" cjdnsctl peers deauth LOGIN");
  56. console.log(" cjdnsctl peers ls");
  57. console.log(" cjdnsctl peers lsdev");
  58. console.log(" cjdnsctl peers lsauth");
  59. console.log("where KEY := base-32 encoded string ending in .k");
  60. console.log(" ADDR := a string representation of a Sockaddr for connecting to the peer");
  61. console.log(" PASSWD := a string");
  62. console.log(" LOGIN := a string");
  63. console.log(" COMMENT := a string, never sent, shown in `cjdnsctl peers ls`");
  64. }
  65. let syncConf = function (ctx) {
  66. let bstr = Benc.encode(ctx.conf);
  67. //console.log('>' + ctx.confFile);
  68. Fs.writeFile(ctx.confFile, bstr, function (err, ret) {
  69. if (err) {
  70. console.error("Failed to sync changes to cjdns config file [" + ctx.confFile + "]");
  71. throw err;
  72. }
  73. });
  74. };
  75. let assertNum = function (str, lowerBound, upperBound)
  76. {
  77. let num = Number(str);
  78. if (num <= upperBound && num >= lowerBound) { return num; }
  79. throw new Error("[" + str + "] must be a number between [" + lowerBound + "] and [" +
  80. upperBound + "]");
  81. };
  82. let iptunAdd = function (ctx, argIndex, cb) {
  83. let ip6;
  84. let prefix6;
  85. let alloc6;
  86. let ip4;
  87. let prefix4;
  88. let alloc4;
  89. let key;
  90. let comment;
  91. if ((/[a-z0-9]+\.k/).test(process.argv[argIndex+1])) {
  92. key = process.argv[argIndex+1];
  93. } else {
  94. console.error("ERROR: `cjdnsctl iptun add` requires the public key of the connecting node");
  95. return cb();
  96. }
  97. for (let i = argIndex+2; i < process.argv.length; i++) {
  98. if ((/[0-9a-fA-F]+:/).test(process.argv[i])) {
  99. let arr = process.argv[i].split('/');
  100. ip6 = arr[0];
  101. prefix6 = arr[1] && assertNum(arr[1], 0, 128);
  102. alloc6 = arr[2] && assertNum(arr[2], 0, 128);
  103. }
  104. if ((/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/).test(process.argv[i])) {
  105. let arr = process.argv[i].split('/');
  106. ip4 = arr[0];
  107. prefix4 = arr[1] && assertNum(arr[1], 0, 32);
  108. alloc4 = arr[2] && assertNum(arr[2], 0, 32);
  109. }
  110. if (process.argv[i] === 'comment') {
  111. if (!process.argv[i+1]) {
  112. console.error("ERROR: `comment` must be followed by a string");
  113. return cb();
  114. }
  115. comment = process.argv[i+1];
  116. }
  117. }
  118. if (!ip4 && !ip6) {
  119. console.error("ERROR: `cjdnsctl iptun add` requires an ipv4 and/or ipv6 address to issue");
  120. return cb();
  121. }
  122. for (let i = 0; i < ctx.conf.router.ipTunnel.allowedConnections.length; i++) {
  123. if (ctx.conf.router.ipTunnel.allowedConnections[i].publicKey === key) {
  124. console.error("ERROR: public key [" + key + "] is already authorized");
  125. return cb();
  126. }
  127. }
  128. nThen(function (waitFor) {
  129. ctx.cjdns.IpTunnel_allowConnection(key, prefix6, alloc6, ip6, prefix4, alloc4, ip4,
  130. waitFor(function (err, ret) {
  131. if (err) { throw err; }
  132. if (ret.error && ret.error !== 'none') {
  133. throw new Error("Response from cjdns: " + ret.error);
  134. }
  135. }));
  136. }).nThen(function (waitFor) {
  137. let conn = {
  138. publicKey: key
  139. };
  140. if (ip4 !== undefined) { conn.ip4Address = ip4; }
  141. if (prefix4 !== undefined) { conn.ip4Prefix = prefix4; }
  142. if (alloc4 !== undefined) { conn.ip4Alloc = alloc4; }
  143. if (ip6 !== undefined) { conn.ip6Address = ip6; }
  144. if (prefix6 !== undefined) { conn.ip6Prefix = prefix6; }
  145. if (alloc6 !== undefined) { conn.ip6Alloc = alloc6; }
  146. if (comment !== undefined) { conn.comment = comment; }
  147. ctx.conf.router.ipTunnel.allowedConnections.push(conn);
  148. syncConf(ctx);
  149. cb();
  150. });
  151. };
  152. let getIptunConnections = function (ctx, cb) {
  153. let connTable;
  154. nThen(function (waitFor) {
  155. ctx.cjdns.IpTunnel_listConnections(waitFor(function (err, ret) {
  156. if (err) { throw err; }
  157. if (ret.error && ret.error !== 'none') {
  158. throw new Error("Response from cjdns: " + ret.error);
  159. }
  160. connTable = ret.connections;
  161. }));
  162. }).nThen(function (waitFor) {
  163. let nt = nThen;
  164. for (let i = 0; i < connTable.length; i++) {
  165. (function(i) {
  166. nt = nt(function (waitFor) {
  167. ctx.cjdns.IpTunnel_showConnection(i, waitFor(function (err, ret) {
  168. if (err) { throw err; }
  169. if (ret.error && ret.error !== 'none') {
  170. throw new Error("Response from cjdns: " + ret.error);
  171. }
  172. ret._connNum = connTable[i];
  173. connTable[i] = ret;
  174. }));
  175. }).nThen;
  176. })(i);
  177. }
  178. nt(waitFor());
  179. }).nThen(function (waitFor) {
  180. cb(connTable);
  181. });
  182. };
  183. let iptunRm = function (ctx, argIndex, cb) {
  184. if (argIndex+2 !== process.argv.length) {
  185. console.error("ERROR: `cjdnsctl iptun rm` takes one argument");
  186. iptunHelp();
  187. return cb();
  188. }
  189. let publicKey = process.argv[argIndex+1];
  190. let index = -1;
  191. let entry;
  192. for (let i = 0; i < ctx.conf.router.ipTunnel.allowedConnections.length; i++) {
  193. if (ctx.conf.router.ipTunnel.allowedConnections[i].publicKey === publicKey) {
  194. index = i;
  195. entry = ctx.conf.router.ipTunnel.allowedConnections[i];
  196. break;
  197. }
  198. }
  199. if (index === -1) {
  200. console.error("ERROR: no connection found in conf matching key [" + publicKey + "]");
  201. return cb();
  202. }
  203. ctx.conf.router.ipTunnel.allowedConnections.splice(index, 1);
  204. let connTable;
  205. nThen(function (waitFor) {
  206. getIptunConnections(waitFor(function (ct) { connTable = ct; }));
  207. }).nThen(function (waitFor) {
  208. let found = false;
  209. for (let i = 0; i < connTable.length; i++) {
  210. if (Number(connTable[i].outgoing)) {
  211. // fallthrough
  212. } else if (connTable[i].key === publicKey) {
  213. found = true;
  214. ctx.cjdns.IpTunnel_removeConnection(Number(connTable[i]._connNum),
  215. waitFor(function (err, ret) {
  216. if (err) { throw err; }
  217. if (ret.error && ret.error !== 'none') {
  218. throw new Error("Response from cjdns: " + ret.error);
  219. }
  220. cb();
  221. }));
  222. }
  223. }
  224. if (!found) {
  225. throw new Error("no such connection inside of running cjdns");
  226. }
  227. }).nThen(function (waitFor) {
  228. syncConf(ctx);
  229. cb();
  230. });
  231. };
  232. let iptunLs = function (ctx, argIndex, cb) {
  233. if (argIndex+1 !== process.argv.length) {
  234. console.error("ERROR: `cjdnsctl iptun ls` takes no arguments");
  235. iptunHelp();
  236. return cb();
  237. }
  238. console.log(JSON.stringify(ctx.conf.router.ipTunnel.allowedConnections, null, ' '));
  239. cb();
  240. };
  241. let iptunConnDisconn = function (ctx, i, cb) {
  242. if (i+2 !== process.argv.length) {
  243. console.error("ERROR: `cjdnsctl iptun " + process.argv[i] + "` takes exactly one argument");
  244. return cb();
  245. }
  246. if (process.argv[i] === 'conn') {
  247. if (ctx.conf.router.ipTunnel.outgoingConnections.length > 0) {
  248. console.error("ERROR: already connected to an IPTunnel (multiple is not well tested)");
  249. return cb();
  250. }
  251. ctx.conf.router.ipTunnel.outgoingConnections.push(process.argv[i+1]);
  252. ctx.cjdns.IpTunnel_connectTo(process.argv[i+1], function (err, ret) {
  253. if (err) { throw err; }
  254. if (ret.error !== 'none') { throw new Error("Response from cjdns: " + ret.error); }
  255. syncConf(ctx);
  256. cb();
  257. });
  258. } else if (process.argv[i] !== 'disconn') {
  259. throw new Error();
  260. }
  261. if (ctx.conf.router.ipTunnel.outgoingConnections.length === 0) {
  262. console.error("ERROR: not connected to any IPTunnel");
  263. return cb();
  264. }
  265. let idx = ctx.conf.router.ipTunnel.outgoingConnections.indexOf(process.argv[i+1]);
  266. if (idx === -1) {
  267. console.error("ERROR: no connection for given key");
  268. return cb();
  269. }
  270. ctx.conf.router.ipTunnel.outgoingConnections.splice(idx, 1);
  271. let connTable;
  272. nThen(function (waitFor) {
  273. getIptunConnections(waitFor(function (ct) { connTable = ct; }));
  274. }).nThen(function (waitFor) {
  275. let found = false;
  276. for (let i = 0; i < connTable.length; i++) {
  277. if (!connTable[i].outgoing) {
  278. // fallthrough
  279. } else if (connTable[i].key === process.argv[i+1]) {
  280. found = true;
  281. ctx.cjdns.IpTunnel_removeConnection(connTable[i]._connNum,
  282. waitFor(function (err, ret) {
  283. if (err) { throw err; }
  284. if (ret.error && ret.error !== 'none') {
  285. throw new Error("Response from cjdns: " + ret.error);
  286. }
  287. }));
  288. }
  289. }
  290. if (!found) {
  291. throw new Error("no such connection inside of running cjdns");
  292. }
  293. }).nThen(function (waitFor) {
  294. syncConf(ctx);
  295. cb();
  296. });
  297. };
  298. let iptun = function (ctx, i, cb) {
  299. switch (process.argv[i+1]) {
  300. case 'add': return iptunAdd(ctx, i+1, cb);
  301. case 'rm': return iptunRm(ctx, i+1, cb);
  302. case 'ls': return iptunLs(ctx, i+1, cb);
  303. case 'conn':
  304. case 'disconn': return iptunConnDisconn(ctx, i+1, cb);
  305. case undefined:
  306. case 'help': iptunHelp(); cb(); return;
  307. default: {
  308. console.error("Unrecognized argument: " + process.argv[i+1]);
  309. iptunHelp();
  310. cb();
  311. }
  312. }
  313. };
  314. let runCjdroute = function (args, stdin, cb) {
  315. let cjdroute = 'cjdroute';
  316. nThen(function (waitFor) {
  317. Fs.exists(process.cwd() + '/cjdroute', waitFor(function (ex) {
  318. if (ex) { cjdroute = process.cwd() + '/cjdroute'; }
  319. }));
  320. }).nThen(function (waitFor) {
  321. let proc = Spawn(cjdroute, args);
  322. let err = '';
  323. let out = '';
  324. proc.stderr.on('data', function (dat) { err += dat.toString(); });
  325. proc.stdout.on('data', function (dat) { out += dat.toString(); });
  326. proc.on('close', waitFor(function (ret) {
  327. cb(ret, out, err);
  328. }));
  329. proc.on('error', function (err) { throw err; });
  330. if (stdin) {
  331. proc.stdin.write(stdin, function (err) {
  332. if (err) { throw err; }
  333. proc.stdin.end();
  334. });
  335. }
  336. });
  337. };
  338. let lockConfFile = function (confFile, cb) {
  339. let uid;
  340. let mode;
  341. nThen(function (waitFor) {
  342. Fs.stat(confFile, waitFor(function (err, stat) {
  343. if (err) { throw err; }
  344. mode = stat.mode;
  345. uid = stat.uid;
  346. }));
  347. }).nThen(function (waitFor) {
  348. if (uid === 0) { return; }
  349. Fs.chown(confFile, process.getuid(), process.getgid(), waitFor(function (err) {
  350. if (err) { throw err; }
  351. }));
  352. }).nThen(function (waitFor) {
  353. if (mode.toString(8).slice(-3) === '600') { return; }
  354. Fs.chmod(confFile, parseInt('600',8), waitFor(function (err) {
  355. if (err) { throw err; }
  356. }));
  357. }).nThen(cb);
  358. };
  359. let confGen = function (i) {
  360. let confFile = DEFAULT_BCONF;
  361. let force = false;
  362. let conf;
  363. let confStr;
  364. if (process.argv.length > i+3) {
  365. console.error("ERROR: `cjdnsctl conf gen` requires maximum 2 arguments");
  366. return;
  367. }
  368. if (process.argv[i+2] === 'yes') { force = true; }
  369. if (process.argv.length > i+1) {
  370. if (force || process.argv[i+1] !== 'yes') {
  371. confFile = process.argv[i+1];
  372. } else {
  373. force = true;
  374. }
  375. }
  376. nThen(function (waitFor) {
  377. Fs.exists(confFile, waitFor(function (ex) {
  378. if (ex && !force) {
  379. console.error("ERROR: `cjdnsctl conf gen` output file [" + confFile + "] exists.");
  380. let cf = (confFile === DEFAULT_BCONF) ? "" : (confFile + " ");
  381. console.error(" use `cjdnsctl conf gen " + cf + "yes` to overwrite.");
  382. confFile = null;
  383. }
  384. }));
  385. }).nThen(function (waitFor) {
  386. if (!confFile) { return; }
  387. runCjdroute(['--genconf'], undefined, waitFor(function (ret, out, err) {
  388. confStr = out;
  389. }));
  390. }).nThen(function (waitFor) {
  391. if (!confFile) { return; }
  392. runCjdroute(['--cleanconf'], confStr, waitFor(function (ret, out, err) {
  393. conf = JSON.parse(out);
  394. // Don't need default auth'd passwords.
  395. conf.authorizedPasswords = [];
  396. }));
  397. }).nThen(function (waitFor) {
  398. if (!confFile) { return; }
  399. let bstr = Benc.encode(conf)
  400. if (bstr !== Benc.encode(Benc.decode(bstr))) { throw new Error(); }
  401. Fs.writeFile(confFile, bstr, waitFor(function (err, ret) {
  402. if (err) { throw err; }
  403. }));
  404. }).nThen(function (waitFor) {
  405. if (!confFile) { return; }
  406. lockConfFile(confFile, waitFor());
  407. });
  408. };
  409. let fixConf = function (conf) {
  410. conf.authorizedPasswords = conf.authorizedPasswords.map(function (pwd, i) {
  411. let out = JSON.parse(JSON.stringify(pwd));
  412. if (!('user' in out)) {
  413. // This is synchronized with Configurator.c
  414. out.user = '_noname_' + i;
  415. }
  416. return out;
  417. });
  418. Object.keys(conf.interfaces).forEach(function (iface) {
  419. if (!Array.isArray(conf.interfaces[iface])) {
  420. conf.interfaces[iface] = [ conf.interfaces[iface] ];
  421. }
  422. conf.interfaces[iface].forEach(function (ifaceB) {
  423. if (!('bind' in ifaceB)) {
  424. if (iface === 'ETHInterface') {
  425. ifaceB.bind = 'all';
  426. } else {
  427. ifaceB.bind = '0.0.0.0/' + Math.floor(Math.random() * (65535 - 1025)) + 1025;
  428. }
  429. }
  430. });
  431. });
  432. };
  433. let confConvert = function (i) {
  434. let confFile = DEFAULT_BCONF;
  435. let force = false;
  436. let conf;
  437. if (process.argv.length > i+4 || process.argv.length <= i+1) {
  438. console.error("ERROR: `cjdnsctl conf convert` requires between 1 and 3 arguments");
  439. return;
  440. }
  441. let inputFile = process.argv[i+1];
  442. if (process.argv.length > i+2 && process.argv[process.argv.length-1] === 'yes') {
  443. force = true;
  444. process.argv.pop();
  445. }
  446. if (process.argv.length > i+2) {
  447. confFile = process.argv.pop();
  448. }
  449. let inputContent;
  450. nThen(function (waitFor) {
  451. Fs.exists(confFile, waitFor(function (ex) {
  452. if (ex && !force) {
  453. console.error("ERROR: `cjdnsctl conf convert` output file [" + confFile + "] exists");
  454. let cf = (confFile === DEFAULT_BCONF) ? "" : (confFile + " ");
  455. console.error(" use `cjdnsctl conf convert " + cf + "yes` to overwrite.");
  456. confFile = null;
  457. }
  458. }));
  459. }).nThen(function (waitFor) {
  460. Fs.readFile(inputFile, waitFor(function (err, ret) {
  461. if (err) { throw err; }
  462. inputContent = ret.toString('utf8');
  463. }));
  464. }).nThen(function (waitFor) {
  465. if (!confFile) { return; }
  466. runCjdroute(['--cleanconf'], inputContent, waitFor(function (ret, out, err) {
  467. conf = JSON.parse(out);
  468. }));
  469. }).nThen(function (waitFor) {
  470. if (!confFile) { return; }
  471. fixConf(conf);
  472. let bstr = Benc.encode(conf)
  473. if (bstr !== Benc.encode(Benc.decode(bstr))) { throw new Error(); }
  474. Fs.writeFile(confFile, bstr, waitFor(function (err, ret) {
  475. if (err) { throw err; }
  476. }));
  477. }).nThen(function (waitFor) {
  478. if (!confFile) { return; }
  479. lockConfFile(confFile, waitFor());
  480. });
  481. };
  482. let confJson = function (i) {
  483. let confFile = DEFAULT_BCONF;
  484. if (process.argv.length > i+2) {
  485. console.error("ERROR: `cjdnsctl conf json` takes at most, 1 argument");
  486. return;
  487. }
  488. if (process.argv.length > i+1) {
  489. confFile = process.argv[i+1];
  490. }
  491. Fs.readFile(confFile, function (err, ret) {
  492. if (err) { throw err; }
  493. let conf = Benc.decode(ret.toString('utf8'));
  494. if (!conf) {
  495. console.error("ERROR: failed to parse bconf file [" + confFile + "]");
  496. }
  497. process.stdout.write(JSON.stringify(conf, null, ' '));
  498. });
  499. };
  500. let confGet = function (ctx, i, cb) {
  501. if (process.argv.length !== i+2) {
  502. console.error("ERROR: `cjdnsctl conf get` requires exactly one argument");
  503. cb();
  504. return;
  505. }
  506. console.log(JSON.stringify(eval('ctx.conf.' + process.argv[i+1]), null, ' '));
  507. cb();
  508. };
  509. let confSet = function (ctx, i, cb) {
  510. if (process.argv.length !== i+3) {
  511. console.error("ERROR: `cjdnsctl conf set` requires exactly two arguments");
  512. cb();
  513. return;
  514. }
  515. if (process.argv[i+2] === 'undefined') {
  516. eval('delete ctx.conf.' + process.argv[i+1] + ';');
  517. } else {
  518. let val = JSON.parse(process.argv[i+2]);
  519. eval('ctx.conf.' + process.argv[i+1] + ' = val;');
  520. }
  521. syncConf(ctx);
  522. cb();
  523. };
  524. let conf = function (ctx, i, cb) {
  525. switch (process.argv[i+1]) {
  526. case 'gen': return confGen(i+1);
  527. case 'convert': return confConvert(i+1);
  528. case 'json': return confJson(i+1);
  529. case 'get': return confGet(ctx, i+1, cb);
  530. case 'set': return confSet(ctx, i+1, cb);
  531. case undefined:
  532. case 'help': confHelp(); return;
  533. default: {
  534. console.error("Unrecognized argument: " + process.argv[i+1]);
  535. confHelp();
  536. }
  537. }
  538. };
  539. let getAllConnectBlocks = function (ctx) {
  540. let connectBlocks = {};
  541. [ "UDPInterface", "ETHInterface" ].forEach(function (ifaceName) {
  542. if (!(ifaceName in ctx.conf.interfaces)) { return; }
  543. let iface = ctx.conf.interfaces[ifaceName];
  544. for (let i = 0; i < iface.length; i++) {
  545. for (let ipPort in iface[i].connectTo) {
  546. connectBlocks[iface[i].connectTo[ipPort].publicKey] = {
  547. ipPort: ipPort,
  548. block: iface[i].connectTo[ipPort],
  549. iface: ifaceName.substring(0,3) + '/' + iface[i].bind
  550. };
  551. }
  552. }
  553. });
  554. return connectBlocks;
  555. };
  556. let getDevices = function (ctx, cb) {
  557. let ifaces = [];
  558. let page = 0;
  559. let handler = function (err, ret) {
  560. if (err) { throw err; }
  561. if (ret.error && ret.error !== 'none') {
  562. throw new Error("Response from cjdns: " + ret.error);
  563. }
  564. if (!ret.ifaces) { throw new Error("response missing ifaces"); }
  565. ifaces.push.apply(ifaces, ret.ifaces);
  566. if (ret.more) {
  567. page++;
  568. ctx.cjdns.InterfaceController_getInterfaces(page, handler);
  569. } else {
  570. ifaces.sort();
  571. cb(ifaces);
  572. }
  573. };
  574. ctx.cjdns.InterfaceController_getInterfaces(page, handler);
  575. };
  576. let peersConn = function (ctx, i, cb) {
  577. if (process.argv.length < i+4) {
  578. console.error("ERROR: `cjdnsctl peers conn` requires at least 3 arguments");
  579. cb();
  580. return;
  581. }
  582. for (let ii = 0; ii <= i; ii++) { process.argv.shift(); }
  583. let values = {
  584. addr: process.argv.shift(),
  585. key: process.argv.shift(),
  586. pass: process.argv.shift(),
  587. user: null,
  588. dev: null,
  589. comment: null
  590. };
  591. let opts = ['user','dev','comment'];
  592. while (process.argv.length) {
  593. let nextName = process.argv.shift();
  594. let nextVal = process.argv.shift();
  595. let idx = opts.indexOf(nextName);
  596. if (idx === -1) {
  597. console.error("ERROR: `cjdnsctl peers conn` extra parameters include " +
  598. JSON.stringify(opts) + " argument " + JSON.stringify(nextName) + " unhandled");
  599. cb();
  600. return;
  601. }
  602. if (typeof(nextVal) === 'undefined') {
  603. console.error("ERROR: `cjdnsctl peers conn` extra parameter " +
  604. JSON.stringify(nextName) + " requires a value");
  605. cb();
  606. return;
  607. }
  608. values[nextName] = nextVal;
  609. }
  610. if (values.key in getAllConnectBlocks(ctx)) {
  611. console.error("ERROR: node " + JSON.stringify(values.key) + " already in a connectTo block");
  612. cb();
  613. return;
  614. }
  615. let connectBlock = null;
  616. let ifaces;
  617. nThen(function (waitFor) {
  618. getDevices(ctx, waitFor(function (ifs) { ifaces = ifs; }));
  619. }).nThen(function (waitFor) {
  620. if (values.dev === null) {
  621. // No device specified, we need to guess.
  622. let matcher;
  623. let type;
  624. if (/^([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2}$/.test(values.addr)) {
  625. // It's ethernet, pick the first eth device
  626. matcher = /^ETH\//;
  627. type = "Ethernet/MAC";
  628. } else if (/^\[[a-fA-F0-9:]+\]:[0-9]+$/.test(values.addr)) {
  629. // It's ipv6
  630. matcher = /^UDP\/\[/;
  631. type = "UDP/IPv6";
  632. } else if (/^([0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]+$/.test(values.addr)) {
  633. // It's ipv4
  634. matcher = /^UDP\/[0-9]+/;
  635. type = "UDP/IPv4";
  636. } else {
  637. console.error("ERROR: address " + JSON.stringify(values.addr) + " is not " +
  638. "recognized as Ethernet/MAC, UDP/IPv6 or UDP/IPv4");
  639. return;
  640. }
  641. for (let i = 0; i < ifaces.length; i++) {
  642. if (matcher.test(ifaces[i].name)) {
  643. values.dev = ifaces[i].name;
  644. console.log("Using iface " + JSON.stringify(values.dev));
  645. break;
  646. }
  647. }
  648. if (values.dev === null) {
  649. console.error("ERROR: address " + JSON.stringify(values.addr) + " is recognized " +
  650. "as type " + JSON.stringify(type) + " and there is no existing interface " +
  651. "for this address type.\nTry `cjdnsctl peers lsdev` for a list");
  652. return;
  653. }
  654. } else {
  655. let found = false;
  656. for (let i = 0; i < ifaces.length; i++) {
  657. if (ifaces[i].name === values.dev) { found = true; break; }
  658. }
  659. if (!found) {
  660. console.error("ERROR: dev " + JSON.stringify(values.dev) + " does not exist.\n" +
  661. "Try `cjdnsctl peers lsdev` for a list");
  662. return;
  663. }
  664. }
  665. // Now we have to guess the correct block in the conf to insert the connect block into
  666. Object.keys(ctx.conf.interfaces).some(function (iface) {
  667. // Check iface type (UDP|ETH)
  668. if (iface.indexOf(values.dev.split('/')[0]) !== 0) { return; }
  669. ctx.conf.interfaces[iface].some(function (ifaceB, i) {
  670. if (ifaceB.bind === values.dev.split('/')[1]) {
  671. values.confBlock = ctx.conf.interfaces[iface][i].connectTo;
  672. return true;
  673. }
  674. });
  675. return "confBlock" in values;
  676. });
  677. if (!("confBlock" in values)) {
  678. console.error("ERROR: dev " + JSON.stringify(values.dev) + " cannot be matched " +
  679. "to a block in the configuration db");
  680. return;
  681. }
  682. let block = {
  683. password: values.pass,
  684. publicKey: values.key
  685. };
  686. if (values.user) { block.login = values.user; }
  687. if (values.comment) { block.comment = values.comment; }
  688. values.confBlock[values.addr] = block;
  689. ctx.cjdns.InterfaceController_connectTo(
  690. values.key, values.pass, values.dev, values.addr, values.user,
  691. waitFor(function (err, ret) {
  692. if (err) { throw err; }
  693. if (ret.error !== 'none') { throw new Error("Response from cjdns: " + ret.error); }
  694. syncConf(ctx);
  695. }));
  696. }).nThen(function (waitFor) {
  697. if (values.dev.indexOf('UDP') !== 0) { return; }
  698. ctx.cjdns.RouteGen_addException(values.addr.replace(/:[0-9]+$/, ''),
  699. waitFor(function (err, ret) {
  700. if (err) { throw err; }
  701. if (ret.error !== 'none') { throw new Error("Response from cjdns: " + ret.error); }
  702. }));
  703. }).nThen(function (waitFor) {
  704. if (values.dev.indexOf('UDP') !== 0) { return; }
  705. ctx.cjdns.RouteGen_commit(waitFor(function (err, ret) {
  706. if (err) { throw err; }
  707. if (ret.error !== 'none') { throw new Error("Response from cjdns: " + ret.error); }
  708. }));
  709. }).nThen(function (waitFor) {
  710. cb();
  711. });
  712. };
  713. let peersDisconn = function (ctx, i, cb) {
  714. if (process.argv.length !== i+2) {
  715. console.error("ERROR: `cjdnsctl peers disconn` requires exactly one argument");
  716. cb();
  717. return;
  718. }
  719. let key = process.argv[i+1];
  720. let addr;
  721. let dev;
  722. Object.keys(ctx.conf.interfaces).forEach(function (iface) {
  723. ctx.conf.interfaces[iface].forEach(function (ifaceB) {
  724. Object.keys(ifaceB.connectTo).forEach(function (ip) {
  725. if (ifaceB.connectTo[ip].publicKey === key) {
  726. addr = ip;
  727. dev = iface;
  728. delete ifaceB.connectTo[ip];
  729. }
  730. });
  731. });
  732. });
  733. if (!addr) {
  734. console.error("not found");
  735. cb();
  736. return;
  737. }
  738. nThen(function (waitFor) {
  739. ctx.cjdns.InterfaceController_disconnectPeer(key, function (err, ret) {
  740. if (err) { throw err; }
  741. if (ret.error !== 'none') { throw new Error("Response from cjdns: " + ret.error); }
  742. syncConf(ctx);
  743. });
  744. }).nThen(function (waitFor) {
  745. if (dev.indexOf('UDP') !== 0) { return; }
  746. ctx.cjdns.RouteGen_removeException(addr.replace(/:[0-9]+$/, ''),
  747. waitFor(function (err, ret) {
  748. if (err) { throw err; }
  749. if (ret.error !== 'none') { throw new Error("Response from cjdns: " + ret.error); }
  750. }));
  751. }).nThen(function (waitFor) {
  752. if (dev.indexOf('UDP') !== 0) { return; }
  753. ctx.cjdns.RouteGen_commit(waitFor(function (err, ret) {
  754. if (err) { throw err; }
  755. if (ret.error !== 'none') { throw new Error("Response from cjdns: " + ret.error); }
  756. }));
  757. }).nThen(function (waitFor) {
  758. cb();
  759. });
  760. };
  761. let peersAuth = function (ctx, i, cb) {
  762. let comment;
  763. if (process.argv.length !== i+3) {
  764. if (process.argv.length === i+5 && process.argv[i+3] === 'comment') {
  765. comment = process.argv[i+4];
  766. } else {
  767. console.err("ERROR: `cjdnsctl peers auth` requires 2 arguments plus optional comment");
  768. cb();
  769. return;
  770. }
  771. }
  772. let block = { password: process.argv[i+1], user: process.argv[i+2] };
  773. if (comment) { block.comment = comment; }
  774. ctx.conf.authorizedPasswords.push(block);
  775. ctx.cjdns.AuthorizedPasswords_add(block.password, block.user, function (err, ret) {
  776. if (err) { throw err; }
  777. if (ret.error !== 'none') {
  778. throw new Error("Response from cjdns: " + ret.error);
  779. }
  780. syncConf(ctx);
  781. cb();
  782. });
  783. };
  784. let peersDeauth = function (ctx, i, cb) {
  785. if (process.argv.length !== i+2) {
  786. console.error("ERROR: `cjdnsctl peers deauth` requires exactly one argument");
  787. cb();
  788. return;
  789. }
  790. let login = process.argv[i+1];
  791. let found = false;
  792. for (let i = 0; i < ctx.conf.authorizedPasswords.length; i++) {
  793. if (ctx.conf.authorizedPasswords[i].user === login) {
  794. ctx.conf.authorizedPasswords.splice(i, 1);
  795. found = true;
  796. break;
  797. }
  798. }
  799. if (!found) {
  800. console.error("not found");
  801. cb();
  802. return;
  803. }
  804. ctx.cjdns.AuthorizedPasswords_remove(login, function (err, ret) {
  805. if (err) { throw err; }
  806. if (ret.error !== 'none') {
  807. throw new Error("Response from cjdns: " + ret.error);
  808. }
  809. syncConf(ctx);
  810. cb();
  811. });
  812. };
  813. let peersLsAuth = function (ctx, i, cb) {
  814. ctx.conf.authorizedPasswords.forEach(function (pwd, i) {
  815. let out = pwd.user;
  816. if ('comment' in pwd) { out += ' comment ' + JSON.stringify(pwd.comment); }
  817. console.log(out);
  818. });
  819. cb();
  820. };
  821. let parseAddr = function (addr) {
  822. let out;
  823. addr.replace(/^(v[0-9]+)((\.[0-9a-f]{4}){4})\.([a-z0-9]{52}\.k)$/, function (all, v, l, x, k) {
  824. out = { ver: v, path: l.substring(1), key: k };
  825. });
  826. if (!out) { throw new Error("failed to parse address [" + addr + "]"); }
  827. return out;
  828. };
  829. let formatPeerData = function (peer) {
  830. let p = peer.linkAddr + '\t' + peer.addr + ' ' + peer.state +
  831. ' in ' + peer.recvKbps + 'kb/s' + ' out ' + peer.sendKbps + 'kb/s';
  832. if (peer.duplicates !== 0) {
  833. p += ' ' + ' DUP ' + peer.duplicates;
  834. }
  835. if (peer.lostPackets !== 0) {
  836. p += ' ' + ' LOS ' + peer.lostPackets;
  837. }
  838. if (peer.receivedOutOfRange !== 0) {
  839. p += ' ' + ' OOR ' + peer.receivedOutOfRange;
  840. }
  841. if ('user' in peer) {
  842. p += ' ' + JSON.stringify(peer.user);
  843. }
  844. ['_passwdBlock', '_connectBlock'].forEach(function (blk) {
  845. if (blk in peer && 'comment' in peer[blk].block) {
  846. p += ' comment ' + JSON.stringify(peer[blk].block.comment);
  847. }
  848. });
  849. return p;
  850. };
  851. let peersLs = function (ctx, _i, cb) {
  852. let peerStats = [];
  853. let connectBlocks = getAllConnectBlocks(ctx);
  854. let passwords = {};
  855. ctx.conf.authorizedPasswords.forEach(function (pwd, i) { passwords[pwd.user] = pwd; });
  856. nThen(function (waitFor) {
  857. let again = function (i) {
  858. ctx.cjdns.InterfaceController_peerStats(i, waitFor(function (err, ret) {
  859. if (err) { throw err; }
  860. peerStats.push.apply(peerStats, ret.peers);
  861. if ('more' in ret) {
  862. again(i+1);
  863. } else {
  864. cb(peers);
  865. }
  866. }));
  867. };
  868. again(0);
  869. }).nThen(function (waitFor) {
  870. let ifaces = {};
  871. for (let i = 0; i < peerStats.length; i++) {
  872. peerStats[i]._parsedAddr = parseAddr(peerStats[i].addr);
  873. if (peerStats[i]._parsedAddr.key in connectBlocks) {
  874. peerStats[i]._connectBlock = connectBlocks[peerStats[i]._parsedAddr.key];
  875. delete connectBlocks[peerStats[i]._parsedAddr.key];
  876. }
  877. if ('user' in peerStats[i] && peerStats[i].user in passwords) {
  878. peerStats[i]._passwdBlock = passwords[peerStats[i].user];
  879. delete passwords[peerStats[i].user];
  880. }
  881. (ifaces[peerStats[i].ifName] = ifaces[peerStats[i].ifName] || []).push(peerStats[i]);
  882. }
  883. for (let iface in ifaces) {
  884. console.log("iface: " + iface);
  885. let pc = ifaces[iface];
  886. for (let i = 0; i < pc.length; i++) {
  887. console.log(' ' + formatPeerData(pc[i]));
  888. }
  889. }
  890. /*if (Object.keys(passwords).length) {
  891. console.log("unconnected authorized passwords:");
  892. for (let uname in passwords) {
  893. let out = uname;
  894. if ('comment' in passwords[uname]) {
  895. out += ' comment ' + passwords[uname].comment;
  896. }
  897. console.log(' ' + out);
  898. }
  899. }*/
  900. if (Object.keys(connectBlocks).length) {
  901. console.log("unsynced connect blocks:");
  902. console.log(JSON.stringify(connectBlocks, null, ' '));
  903. }
  904. cb();
  905. return;
  906. });
  907. };
  908. let peersLsdev = function (ctx, i, cb) {
  909. let ifaces;
  910. nThen(function (waitFor) {
  911. getDevices(ctx, waitFor(function (ifs) { ifaces = ifs; }));
  912. }).nThen(function (waitFor) {
  913. for (let i = 0; i < ifaces.length; i++) {
  914. console.log(ifaces[i].name);
  915. }
  916. cb();
  917. });
  918. };
  919. let peers = function (ctx, i, cb) {
  920. switch (process.argv[i+1]) {
  921. case 'conn': return peersConn(ctx, i+1, cb);
  922. case 'disconn': return peersDisconn(ctx, i+1, cb);
  923. case 'auth': return peersAuth(ctx, i+1, cb);
  924. case 'deauth': return peersDeauth(ctx, i+1, cb);
  925. case 'lsauth': return peersLsAuth(ctx, i+1, cb);
  926. case 'ls': return peersLs(ctx, i+1, cb);
  927. case 'lsdev': return peersLsdev(ctx, i+1, cb);
  928. case undefined:
  929. case 'help': peersHelp(); cb(); return;
  930. default: {
  931. console.error("Unrecognized argument: " + process.argv[i+1]);
  932. peersHelp();
  933. cb();
  934. }
  935. }
  936. };
  937. let funMap = {
  938. 'iptun': iptun,
  939. 'conf': conf,
  940. 'peers': peers
  941. };
  942. let main = module.exports.main = function () {
  943. let confFile = DEFAULT_BCONF;
  944. let func;
  945. let i = 0;
  946. for (; i < process.argv.length; i++) {
  947. if (funMap[process.argv[i]]) {
  948. func = funMap[process.argv[i]];
  949. break;
  950. }
  951. if (process.argv[i] === '-f') {
  952. confFile = process.argv[++i];
  953. }
  954. }
  955. if (func === conf && ['get','set'].indexOf(process.argv[i+1]) === -1) {
  956. conf(null, i);
  957. return;
  958. }
  959. if (confFile && func) {
  960. let out = {
  961. confFile: confFile
  962. };
  963. nThen(function (waitFor) {
  964. Fs.readFile(confFile, waitFor(function (err, ret) {
  965. if (err) {
  966. console.error("Need access to the cjdns configuration db [" + confFile + "]");
  967. throw err;
  968. }
  969. let str = ret.toString('utf8');
  970. out.conf = Benc.decode(str);
  971. if (!out.conf) { throw new Error("Failed to parse [" + confFile + "]"); }
  972. if (Benc.encode(out.conf) !== str) { throw new Error("bencode roundtrip failed"); }
  973. fixConf(out.conf);
  974. }));
  975. }).nThen(function (waitFor) {
  976. let addrPort = out.conf.admin.bind;
  977. let lColon = addrPort.lastIndexOf(':');
  978. let addr = addrPort.substring(0, lColon);
  979. let port = Number(addrPort.substring(lColon+1));
  980. Cjdns.connect(addr, port, out.conf.admin.password, waitFor(function (cjdns) {
  981. out.cjdns = cjdns;
  982. }));
  983. }).nThen(function (waitFor) {
  984. func(out, i, waitFor());
  985. }).nThen(function (waitFor) {
  986. out.cjdns.disconnect();
  987. });
  988. } else {
  989. return usage();
  990. }
  991. };
  992. }());