ucoind 16.3 KB
Newer Older
1
#!/usr/bin/env node
Cédric Moreau's avatar
Cédric Moreau committed
2 3 4 5 6 7 8 9 10 11 12 13 14 15
var fs          = require('fs');
var os          = require('os');
var async       = require('async');
var _           = require('underscore');
var program     = require('commander');
var mongoose    = require('mongoose');
var moment      = require('moment');
var inquirer    = require('inquirer');
var openpgp     = require('openpgp');
var jpgp        = require('../app/lib/jpgp');
var wizard      = require('../app/lib/wizard');
var router      = require('../app/lib/streams/router');
var multicaster = require('../app/lib/streams/multicaster');
var logger      = require('../app/lib/logger')('ucoind');
16
var signature   = require('../app/lib/signature');
Cédric Moreau's avatar
Cédric Moreau committed
17
var ucoin       = require('./..');
18

Cédric Moreau's avatar
Cédric Moreau committed
19 20 21 22
function keys (val) {
  return val.split(',');
}

23 24 25 26
// Constants
var LISTEN_HTTP = true;
var DO_NOT_LISTEN_HTTP = false;

27
program
Cédric Moreau's avatar
v0.6.1  
Cédric Moreau committed
28
  .version('0.6.1')
29
  .usage('<command> [options]')
30
  .option('-p, --port <port>', 'Port to listen for requests', parseInt)
31
  .option('-c, --currency <name>', 'Name of the currency managed by this node.')
Cédric Moreau's avatar
Cédric Moreau committed
32 33
  .option('--mhost <host>', 'MongoDB host.')
  .option('--mport <port>', 'MongoDB port.')
34
  .option('-d, --mdb <name>', 'MongoDB database name (defaults to "ucoin_default").')
35 36
  .option('--pgpkey <keyPath>', 'Path to the private key used for signing HTTP responses.')
  .option('--pgppasswd <password>', 'Password for the key provided with --httpgp-key option.')
Cédric Moreau's avatar
Cédric Moreau committed
37 38
  .option('--ipv4 <address>', 'IPV4 interface to listen for requests')
  .option('--ipv6 <address>', 'IPV6 interface to listen for requests')
Cédric Moreau's avatar
Cédric Moreau committed
39
  .option('--remoteh <host>', 'Remote interface others may use to contact this node')
Cédric Moreau's avatar
Cédric Moreau committed
40 41
  .option('--remote4 <host>', 'Remote interface for IPv4 access')
  .option('--remote6 <host>', 'Remote interface for IPv6 access')
Cédric Moreau's avatar
Cédric Moreau committed
42
  .option('--remotep <port>', 'Remote port others may use to contact this node')
43
  .option('--kmanagement <ALL|KEYS>', 'Define key management policy')
Cédric Moreau's avatar
Cédric Moreau committed
44
  .option('--kaccept <ALL|KEYS>', 'Define key acceptance policy')
45
  .option('--openpgpjs', 'Prefer using embedded Openpgpjs implementation for signing requests')
46
  ;
Cédric Moreau's avatar
Cédric Moreau committed
47

48 49 50 51 52 53 54 55 56 57 58
program
  .command('send-pubkey [host] [port]')
  .description('Send self pubkey to given node')
  .action(function (host, port) {
    // Only show message "Saved"
    require('log4js').configure({
      "appenders": [{
        category: "ucoind",
        type: "console"
      }]
    });
59
    service(DO_NOT_LISTEN_HTTP, ucoin.createPeerServer, function (server, conf) {
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
      // Only show message "Saved"
      require('log4js').configure({
        "appenders": [{
          category: "multicaster",
          type: "console"
        }]
      });
      async.waterfall([
        function (next){
          server.on('peerInited', next);
        },
        function (next){
          server.PublicKeyService.getTheOne(server.PeeringService.cert.fingerprint, next);
        },
        function (pubkey, next){
          var Peer = server.conn.model('Peer');
          var peer = new Peer({
            endpoints: [['BASIC_MERKLED_API', host, port].join(' ')]
          });
          multicaster().sendPubkey(peer, pubkey, next);
        },
      ], function (err, result) {
        if (err) logger.error(err);
        server.disconnect();
        process.exit();
      });
    })(null);
  });

89 90 91
program
  .command('wizard [step]')
  .description('Launch the configuration Wizard')
92 93 94 95 96 97 98
  .action(function (step) {
    // Only show message "Saved"
    require('log4js').configure({
      "appenders": [{
        category: "ucoind",
        type: "console"
      }]
99
    });
100
    connect(function (step, server, conf) {
101 102 103 104 105
      var wiz = wizard(server);
      var task = {
        'currency': wiz.configCurrency,
        'openpgp': wiz.configOpenpgp,
        'network': wiz.configNetwork,
106
        'key': wiz.configKey
107 108 109 110 111 112 113 114 115 116 117 118 119
      };
      var wizDo = task[step] || wiz.configAll;
      async.waterfall([
        function (next){
          wizDo(conf, next);
        },
        function (next){
          conf.save(function (err) {
            !err && logger.debug("Configuration saved.");
            next(err);
          });
        },
        function (next) {
120
          // Check config
121
          service(DO_NOT_LISTEN_HTTP, ucoin.createPeerServer, function (key, server, conf) {
122 123
            next();
          })(null, null);
124 125 126 127 128 129 130 131
        }
      ], function (err, result) {
        err && logger.error(err);
        server.disconnect();
        process.exit();
      });
    })(step, null);
  });
132

133 134 135
program
  .command('sync [host] [port]')
  .description('Tries to synchronise data with remote uCoin node')
136
  .action(service(DO_NOT_LISTEN_HTTP, ucoin.createRegistryServer, function (host, port, server, conf) {
137

138 139
    // Disable daemon
    conf.sync.AMDaemon = "OFF";
140
    conf.createNext = false;
141

142 143 144 145
    async.series([
      function (next){
        // Synchronize
        var Synchroniser = require('../app/lib/sync');
146
        var remote = new Synchroniser(server, host, port, false, conf);
147 148 149 150 151 152
        remote.sync(next);
      },
    ], function (err) {
      if(err){
        logger.error('Error during sync:', err);
      }
153
      server.disconnect();
154
      process.exit();
155 156 157
    });
  }));

158 159 160
program
  .command('allow-key [key]')
  .description('Add given key to authorized keys of this node')
161
  .action(service(DO_NOT_LISTEN_HTTP, ucoin.createHDCServer, function (key, server, conf) {
162 163 164 165 166 167 168 169
    key = key || "";
    key = key.toUpperCase();
    async.waterfall([
      function (next) {
        if (!key.isSha1()) {
          next("Key must match a SHA-1 hash");
          return;
        }
170
        next();
171
      },
172
      function (next){
173
        server.KeyService.setKnown(key, next);
174 175 176
      }
    ], function (err, result) {
      if(err){
177
        logger.error('Error: %s', err);
178 179
        server.disconnect();
        process.exit();
180 181
        return;
      }
182
      logger.debug("Key %s is now allowed to be stored" , key);
183
      server.disconnect();
184
      process.exit();
185 186 187
    });
  }));

188 189 190
program
  .command('manage-key [key]')
  .description('Add given key to stack of managed keys of this node')
191
  .action(service(DO_NOT_LISTEN_HTTP, ucoin.createHDCServer, function (key, server, conf) {
192
    handleKey(server, key, true, 'Key %s is now managed');
193 194 195 196 197
  }));

program
  .command('forget-key [key]')
  .description('Remove given key of the managed keys\' stack of this node')
198
  .action(service(DO_NOT_LISTEN_HTTP, ucoin.createHDCServer, function (key, server, conf) {
199
    handleKey(server, key, false, 'Key %s no more managed from now');
200 201
  }));

202
function handleKey (server, key, isManaged, message) {
203 204 205
  key = key || "";
  key = key.toUpperCase();
  async.waterfall([
206
    function (next){
207 208 209 210
      server.initServices(next);
    },
    function (next){
      server.KeyService.handleKey(key, isManaged, next);
211 212 213
    }
  ], function (err, result) {
    if(err){
214
      logger.error('Error: %s', err);
215
      server.disconnect();
216 217
      return;
    }
218
    logger.debug(message , key);
219
    server.disconnect();
220
    process.exit();
221 222 223
  });
}

224 225
program
  .command('gen-empty-next [host] [port] [difficulty]')
226 227
  .description('Tries to generate the next keyblock of the keychain without any changes')
  .action(service(DO_NOT_LISTEN_HTTP, ucoin.createWOTServer, generateAndSend("generateEmptyNext")));
Cédric Moreau's avatar
Cédric Moreau committed
228 229 230 231 232

program
  .command('gen-updates [host] [port] [difficulty]')
  .description('Tries to generate an update (#2+) keyblock, containing only update changes')
  .action(service(DO_NOT_LISTEN_HTTP, ucoin.createWOTServer, generateAndSend("generateUpdates")));
233

234 235 236 237 238
program
  .command('gen-newcomers [host] [port] [difficulty]')
  .description('Tries to generate a newcomers (#2+) keyblock, containing only newcomers changes')
  .action(service(DO_NOT_LISTEN_HTTP, ucoin.createWOTServer, generateAndSend("generateNewcomers")));

239 240 241 242 243
program
  .command('gen-newcomers-auto [host] [port] [difficulty]')
  .description('Tries to generate a newcomers (#2+) keyblock, containing only newcomers changes')
  .action(service(DO_NOT_LISTEN_HTTP, ucoin.createWOTServer, generateAndSend("generateNewcomersAuto")));

Cédric Moreau's avatar
Cédric Moreau committed
244 245 246 247 248 249 250 251 252 253 254
program
  .command('compute-distances')
  .description('Recompute distance between each member and the whole WoT and mark outdistanced ones as kicked')
  .action(service(DO_NOT_LISTEN_HTTP, ucoin.createWOTServer, function (server, conf) {
    server.KeychainService.computeDistances(function (err) {
      if (err) logger.error(err);
      server.disconnect();
      process.exit();
    });
  }));

255 256 257 258 259 260 261 262 263 264 265
program
  .command('show-keychain')
  .description('Recompute distance between each member and the whole WoT and mark outdistanced ones as kicked')
  .action(service(DO_NOT_LISTEN_HTTP, ucoin.createWOTServer, function (server, conf) {
    server.KeychainService.showKeychain(function (err) {
      if (err) logger.error(err);
      server.disconnect();
      process.exit();
    });
  }));

266 267 268 269 270 271 272 273 274 275 276
program
  .command('compute-certifications')
  .description('Recompute available certifications for next blocks')
  .action(service(DO_NOT_LISTEN_HTTP, ucoin.createWOTServer, function (server, conf) {
    server.KeychainService.updateCertifications(function (err) {
      if (err) logger.error(err);
      server.disconnect();
      process.exit();
    });
  }));

277 278
function generateAndSend (generationMethod) {
  return function (host, port, difficulty, server, conf) {
279 280
    async.waterfall([
      function (next){
281 282
        var method = eval('server.KeychainService.' + generationMethod);
        method(next);
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
      },
      function (block, next){
        var wiz = wizard(server);
        async.waterfall([
          function (next){
            wiz.configOpenpgp(conf, next);
          },
          function (next){
            wiz.configKey(conf, next);
          },
          function (next){
            signature(conf.pgpkey, conf.pgppasswd, conf.openpgpjs, next);
          },
          function (sigFunc, next){
            proveAndSend(server, block, sigFunc, difficulty, host, port, next);
          },
        ], next);
      },
    ], function (err) {
      if (err) {
        logger.error(err);
      }
      server.disconnect();
      process.exit();
    });
308 309
  };
}
310

311 312 313 314 315 316 317 318 319 320 321
function proveAndSend (server, block, sigFunc, difficulty, host, port, done) {
  var KeychainService = server.KeychainService;
  async.waterfall([
    function (next){
      KeychainService.prove(block, sigFunc, difficulty, next);
    },
    function (block, next){
      var Peer = server.conn.model('Peer');
      var peer = new Peer({
        endpoints: [['BASIC_MERKLED_API', host, port].join(' ')]
      });
322
      console.log(block.getRaw());
323 324 325 326 327 328
      // console.log(block.signature);
      multicaster().sendKeyblock(peer, block, next);
    },
  ], done);
}

329 330 331
program
  .command('check-config')
  .description('Checks the node\'s configuration')
332
  .action(service(function (server, conf) {
333 334 335 336 337 338 339
    server.checkConfig(function (err) {
      if (err)
        logger.warn(err);
      else
        logger.warn('Configuration seems correct.');
      server.disconnect();
      process.exit();
340 341 342 343
    });
    return;
  }));

344 345 346
program
  .command('config')
  .description('Register configuration in database')
347
  .action(service(false, function (server, conf) {
348
    conf.save(function (err) {
349
      if(err){
350
        logger.error("Configuration could not be saved: " + err);
351 352
      }
      else{
353
        logger.debug("Configuration saved.");
354
      }
355
      server.disconnect();
356
      process.exit();
357 358 359
      return;
    });
  }));
360

361 362 363
program
  .command('reset [config|data]')
  .description('Reset configuration or data in database')
364
  .action(service(function (type, server, conf) {
365
    if(!~['config', 'data'].indexOf(type)){
366
      logger.error('Bad command: usage `reset config` or `reset data`');
367
      server.disconnect();
368 369 370
      return;
    }
    if(type == 'data'){
371
      server.reset(function (err) {
372
        if(err)
373
          logger.error(err);
374 375 376
        else
          logger.warn('Data successfuly reseted.');
        server.disconnect();
377
        process.exit();
378 379 380
      });
    }
    if(type == 'config'){
381
      server.resetConf(function (err) {
382
        if(err)
383
          logger.error(err);
384 385 386
        else
          logger.warn('Configuration successfuly reseted.');
        server.disconnect();
387
        process.exit();
388 389 390 391
      });
    }
  }));

392 393 394
program
  .command('start')
  .description('Start uCoin server using given --currency')
395
  .action(service(LISTEN_HTTP, ucoin.createWOTServer, function (server, conf) {
396

397 398 399
    // server
    //   .pipe(router(server.PeeringService.cert.fingerprint, server.conn))
    //   .pipe(multicaster());
Cédric Moreau's avatar
Cédric Moreau committed
400

401
    // Launching server
402
    server.on('BMALoaded', function (err, app) {
403
      if(err){
404
        console.error(err);
405
        this.disconnect();
406 407 408
        process.exit();
        return;
      }
409
      logger.debug('Server ready!');
410 411 412
    });
  }));

413
function overrideConf(conf) {
414

415 416
  conf.sync = conf.sync || {};
  var cli = {
417
    currency: program.currency,
418 419 420 421 422 423 424 425 426 427
    server: {
      port: program.port,
      ipv4address: program.ipv4,
      ipv6address: program.ipv6,
      pgp: {
        key: program.pgpkey,
        password: program.pgppasswd
      },
      remote: {
        host: program.remoteh,
428 429
        ipv4: program.remote4,
        ipv6: program.remote6,
430
        port: program.remotep
431 432
      },
      openpgpjs: program.openpgpjs
433 434 435 436 437
    },
    db: {
      host: program.mhost,
      port: program.mport,
      database: program.mdb,
438
    },
439 440
    policy: {
      keys: program.kmanagement,
Cédric Moreau's avatar
Cédric Moreau committed
441
      pubkeys: program.kaccept
442 443
    }
  };
444

445
  if(cli.server.pgp.key) cli.server.pgp.key = fs.readFileSync(cli.server.pgp.key, 'utf8');
446 447 448 449 450 451 452 453 454 455 456 457 458 459 460

  // Update conf
  if (cli.currency)                         conf.currency       = cli.currency;
  if (cli.server.ipv4address)               conf.ipv4           = cli.server.ipv4address;
  if (cli.server.ipv6address)               conf.ipv6           = cli.server.ipv6address;
  if (cli.server.port)                      conf.port           = cli.server.port;
  if (cli.server.openpgpjs != undefined)    conf.openpgpjs      = cli.server.openpgpjs;
  if (cli.server.pgp.key)                   conf.pgpkey         = cli.server.pgp.key;
  if (cli.server.pgp.password != undefined) conf.pgppasswd      = cli.server.pgp.password;
  if (cli.server.remote.host != undefined)  conf.remotehost     = cli.server.remote.host;
  if (cli.server.remote.ipv4 != undefined)  conf.remoteipv4     = cli.server.remote.ipv4;
  if (cli.server.remote.ipv6 != undefined)  conf.remoteipv6     = cli.server.remote.ipv6;
  if (cli.server.remote.port != undefined)  conf.remoteport     = cli.server.remote.port;
  if (cli.policy.keys)                      conf.kmanagement    = cli.policy.keys;
  if (cli.policy.pubkeys)                   conf.kaccept        = cli.policy.pubkeys;
461 462 463

  // Specific internal settings
  conf.createNext = true;
464 465
  return conf;
}
466

467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492
function connect(callback) {
  return function () {
    var cbArgs = arguments;
    var dbName = program.mdb || "ucoin_default";

    var server = ucoin.connect({ name: dbName, host: program.mhost, port: program.mport }, overrideConf({}));

    // Connecting to DB
    server.on('connected', function (err) {

      if(err){
        logger.warn(err);
        server.disconnect();
        process.exit(1);
        return;
      }

      cbArgs.length--;
      cbArgs[cbArgs.length++] = server;
      cbArgs[cbArgs.length++] = server.conf;
      callback.apply(this, cbArgs);
    });
  };
}

function service(listenHTTP, serverFactory, callback) {
493
  if (arguments.length == 1) {
494 495
    callback = listenHTTP;
    listenHTTP = false;
496
    serverFactory = ucoin.createWOTServer;
497
  } else if (arguments.length == 2) {
498
    callback = serverFactory;
499
    serverFactory = ucoin.createHDCServer;
500
  }
501 502
  return function () {
    var cbArgs = arguments;
503
    var dbName = program.mdb || "ucoin_default";
504

505
    var server = serverFactory({ name: dbName, host: program.mhost, port: program.mport, listenBMA: listenHTTP }, overrideConf({}));
506

507
    // Connecting to DB
508
    server.on('services', function (err) {
509 510

      if(err){
511 512 513
        logger.warn(err);
        server.disconnect();
        process.exit(1);
514 515
        return;
      }
516

517
      cbArgs.length--;
518 519
      cbArgs[cbArgs.length++] = server;
      cbArgs[cbArgs.length++] = server.conf;
520 521 522 523
      callback.apply(this, cbArgs);
    });
  };
}
524

525 526 527
function parseMoment (d) {
  if (d.toLowerCase() == 'now') {
    return parseInt(moment().format("X"));
528
  } else if (d.match(/^\d{2}-\d{2}-\d{4}$/)) {
529
    return parseInt(moment(d, "DD-MM-YYYY").format("X"));
530 531
  } else if (d.match(/^\d{2}-\d{2}-\d{4} \d{2}:\d{2}:\d{2}$/)) {
    return parseInt(moment(d, "DD-MM-YYYY hh:mm:ss").format("X"));
532 533 534 535 536
  } else {
    return parseInt(d);
  }
}

537 538 539 540
function splitColon (str) {
  return str.split(':');
}

541
program.parse(process.argv);