ucoind 15.1 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 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
program
  .command('gen-root [host] [port] [difficulty]')
  .description('Tries to generate the root keyblock of the keychain using already received keys & memberships')
  .action(service(DO_NOT_LISTEN_HTTP, ucoin.createWOTServer, function (host, port, difficulty, server, conf) {
    var Membership = server.conn.model('Membership');
    var KeychainService = server.KeychainService;
    async.waterfall([
      function (next){
        if (!host || !port) {
          next('usage: gen-root [host] [port]');
          return;
        }
        KeychainService.current(function (err, current) {
          if (current) {
            next('Local keychain is already started.');
            return;
          }
          else next();
        })
      },
      function (next){
        Membership.find({}, next);
      },
      function (mss, next){
        var uids = [];
        mss.forEach(function(ms){
          uids.push(ms.userid);
        });
        inquirer.prompt([{
          type: "checkbox",
          name: "uids",
          message: "Initial members of the Web of Trust",
          choices: uids,
          default: uids[0]
        }], function (answers) {
          next(null, answers.uids);
        });
      },
      function (uids, next){
        if (uids.length == 0) {
          next('You must select at least 1 user');
          return;
        }
        KeychainService.generateRoot(uids, next);
      },
      function (root, 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){
            KeychainService.prove(root, sigFunc, difficulty, next);
          },
          function (block, next){
            var Peer = server.conn.model('Peer');
            var peer = new Peer({
              endpoints: [['BASIC_MERKLED_API', host, port].join(' ')]
            });
            // console.log(block.getRaw());
            // console.log(block.signature);
            multicaster().sendKeyblock(peer, block, next);
          },
        ], next);
      },
    ], function (err) {
      if (err) {
        logger.error(err);
      }
      server.disconnect();
      process.exit();
    });
  }));

304 305 306
program
  .command('check-config')
  .description('Checks the node\'s configuration')
307
  .action(service(function (server, conf) {
308 309 310 311 312 313 314
    server.checkConfig(function (err) {
      if (err)
        logger.warn(err);
      else
        logger.warn('Configuration seems correct.');
      server.disconnect();
      process.exit();
315 316 317 318
    });
    return;
  }));

319 320 321
program
  .command('config')
  .description('Register configuration in database')
322
  .action(service(false, function (server, conf) {
323
    conf.save(function (err) {
324
      if(err){
325
        logger.error("Configuration could not be saved: " + err);
326 327
      }
      else{
328
        logger.debug("Configuration saved.");
329
      }
330
      server.disconnect();
331
      process.exit();
332 333 334
      return;
    });
  }));
335

336 337 338
program
  .command('reset [config|data]')
  .description('Reset configuration or data in database')
339
  .action(service(function (type, server, conf) {
340
    if(!~['config', 'data'].indexOf(type)){
341
      logger.error('Bad command: usage `reset config` or `reset data`');
342
      server.disconnect();
343 344 345
      return;
    }
    if(type == 'data'){
346
      server.reset(function (err) {
347
        if(err)
348
          logger.error(err);
349 350 351
        else
          logger.warn('Data successfuly reseted.');
        server.disconnect();
352
        process.exit();
353 354 355
      });
    }
    if(type == 'config'){
356
      server.resetConf(function (err) {
357
        if(err)
358
          logger.error(err);
359 360 361
        else
          logger.warn('Configuration successfuly reseted.');
        server.disconnect();
362
        process.exit();
363 364 365 366
      });
    }
  }));

367 368 369
program
  .command('start')
  .description('Start uCoin server using given --currency')
370
  .action(service(LISTEN_HTTP, ucoin.createWOTServer, function (server, conf) {
371

372 373 374
    // server
    //   .pipe(router(server.PeeringService.cert.fingerprint, server.conn))
    //   .pipe(multicaster());
Cédric Moreau's avatar
Cédric Moreau committed
375

376
    // Launching server
377
    server.on('BMALoaded', function (err, app) {
378
      if(err){
379
        console.error(err);
380
        this.disconnect();
381 382 383
        process.exit();
        return;
      }
384
      logger.debug('Server ready!');
385 386 387
    });
  }));

388
function overrideConf(conf) {
389

390 391
  conf.sync = conf.sync || {};
  var cli = {
392
    currency: program.currency,
393 394 395 396 397 398 399 400 401 402
    server: {
      port: program.port,
      ipv4address: program.ipv4,
      ipv6address: program.ipv6,
      pgp: {
        key: program.pgpkey,
        password: program.pgppasswd
      },
      remote: {
        host: program.remoteh,
403 404
        ipv4: program.remote4,
        ipv6: program.remote6,
405
        port: program.remotep
406 407
      },
      openpgpjs: program.openpgpjs
408 409 410 411 412
    },
    db: {
      host: program.mhost,
      port: program.mport,
      database: program.mdb,
413
    },
414 415
    policy: {
      keys: program.kmanagement,
Cédric Moreau's avatar
Cédric Moreau committed
416
      pubkeys: program.kaccept
417 418
    }
  };
419

420
  if(cli.server.pgp.key) cli.server.pgp.key = fs.readFileSync(cli.server.pgp.key, 'utf8');
421 422 423 424 425 426 427 428 429 430 431 432 433 434 435

  // 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;
436 437 438

  // Specific internal settings
  conf.createNext = true;
439 440
  return conf;
}
441

442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467
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) {
468
  if (arguments.length == 1) {
469 470
    callback = listenHTTP;
    listenHTTP = false;
471
    serverFactory = ucoin.createWOTServer;
472
  } else if (arguments.length == 2) {
473
    callback = serverFactory;
474
    serverFactory = ucoin.createHDCServer;
475
  }
476 477
  return function () {
    var cbArgs = arguments;
478
    var dbName = program.mdb || "ucoin_default";
479

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

482
    // Connecting to DB
483
    server.on('services', function (err) {
484 485

      if(err){
486 487 488
        logger.warn(err);
        server.disconnect();
        process.exit(1);
489 490
        return;
      }
491

492
      cbArgs.length--;
493 494
      cbArgs[cbArgs.length++] = server;
      cbArgs[cbArgs.length++] = server.conf;
495 496 497 498
      callback.apply(this, cbArgs);
    });
  };
}
499

500 501 502
function parseMoment (d) {
  if (d.toLowerCase() == 'now') {
    return parseInt(moment().format("X"));
503
  } else if (d.match(/^\d{2}-\d{2}-\d{4}$/)) {
504
    return parseInt(moment(d, "DD-MM-YYYY").format("X"));
505 506
  } 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"));
507 508 509 510 511
  } else {
    return parseInt(d);
  }
}

512 513 514 515
function splitColon (str) {
  return str.split(':');
}

516
program.parse(process.argv);