diff --git a/lib/commands/admin-rpc.js b/lib/commands/admin-rpc.js index e3f0c6c58..3c328611b 100644 --- a/lib/commands/admin-rpc.js +++ b/lib/commands/admin-rpc.js @@ -2,6 +2,8 @@ const BatchRead = require("../batch-read"); const nThen = require("nthen"); const getFolderSize = require("get-folder-size"); +const Util = require("../common-util"); + var Fs = require("fs"); var Admin = module.exports; @@ -90,9 +92,10 @@ var getDiskUsage = function (Env, cb) { }); }; -Admin.command = function (Env, Server, publicKey, data, cb) { +Admin.command = function (Env, safeKey, data, cb, Server) { var admins = Env.admins; - if (admins.indexOf(publicKey) === -1) { + var unsafeKey = Util.unescapeKeyCharacters(safeKey); + if (admins.indexOf(unsafeKey) === -1) { return void cb("FORBIDDEN"); } diff --git a/lib/commands/block.js b/lib/commands/block.js index 90837745e..3a264c167 100644 --- a/lib/commands/block.js +++ b/lib/commands/block.js @@ -31,7 +31,7 @@ const Util = require("../common-util"); author of the block, since we assume that the block will have been encrypted with xsalsa20-poly1305 which is authenticated. */ -Block.validateLoginBlock = function (Env, publicKey, signature, block, cb) { // FIXME BLOCKS +var validateLoginBlock = function (Env, publicKey, signature, block, cb) { // FIXME BLOCKS // convert the public key to a Uint8Array and validate it if (typeof(publicKey) !== 'string') { return void cb('E_INVALID_KEY'); } @@ -86,13 +86,13 @@ var createLoginBlockPath = function (Env, publicKey) { // FIXME BLOCKS return Path.join(Env.paths.block, safeKey.slice(0, 2), safeKey); }; -Block.writeLoginBlock = function (Env, msg, cb) { // FIXME BLOCKS +Block.writeLoginBlock = function (Env, safeKey, msg, cb) { // FIXME BLOCKS //console.log(msg); var publicKey = msg[0]; var signature = msg[1]; var block = msg[2]; - Block.validateLoginBlock(Env, publicKey, signature, block, function (e, validatedBlock) { + validateLoginBlock(Env, publicKey, signature, block, function (e, validatedBlock) { if (e) { return void cb(e); } if (!(validatedBlock instanceof Uint8Array)) { return void cb('E_INVALID_BLOCK'); } @@ -141,12 +141,12 @@ Block.writeLoginBlock = function (Env, msg, cb) { // FIXME BLOCKS information, we can just sign some constant and use that as proof. */ -Block.removeLoginBlock = function (Env, msg, cb) { // FIXME BLOCKS +Block.removeLoginBlock = function (Env, safeKey, msg, cb) { // FIXME BLOCKS var publicKey = msg[0]; var signature = msg[1]; var block = Nacl.util.decodeUTF8('DELETE_BLOCK'); // clients and the server will have to agree on this constant - Block.validateLoginBlock(Env, publicKey, signature, block, function (e /*::, validatedBlock */) { + validateLoginBlock(Env, publicKey, signature, block, function (e /*::, validatedBlock */) { if (e) { return void cb(e); } // derive the filepath var path = createLoginBlockPath(Env, publicKey); diff --git a/lib/commands/channel.js b/lib/commands/channel.js index 318911bd8..bbb83b45a 100644 --- a/lib/commands/channel.js +++ b/lib/commands/channel.js @@ -160,7 +160,7 @@ Channel.isNewChannel = function (Env, channel, cb) { Otherwise behaves the same as sending to a channel */ -Channel.writePrivateMessage = function (Env, args, Server, cb) { +Channel.writePrivateMessage = function (Env, args, cb, Server) { // XXX odd signature var channelId = args[0]; var msg = args[1]; diff --git a/lib/commands/core.js b/lib/commands/core.js index cbcf291bb..d7add69b4 100644 --- a/lib/commands/core.js +++ b/lib/commands/core.js @@ -184,5 +184,7 @@ Core.isPendingOwner = function (metadata, unsafeKey) { return metadata.pending_owners.indexOf(unsafeKey) !== -1; }; - +Core.haveACookie = function (Env, safeKey, cb) { + cb(); +}; diff --git a/lib/commands/metadata.js b/lib/commands/metadata.js index fe05aa40d..09cf1f1d6 100644 --- a/lib/commands/metadata.js +++ b/lib/commands/metadata.js @@ -8,10 +8,12 @@ const Core = require("./core"); const Util = require("../common-util"); const batchMetadata = BatchRead("GET_METADATA"); -Data.getMetadata = function (Env, channel, cb) { +Data.getMetadata = function (Env, channel, cb/* , Server */) { if (!Core.isValidId(channel)) { return void cb('INVALID_CHAN'); } if (channel.length !== 32) { return cb("INVALID_CHAN_LENGTH"); } + // XXX get metadata from the server cache if it is available + // Server isn't always passed, though... batchMetadata(channel, cb, function (done) { var ref = {}; var lineHandler = Meta.createLineHandler(ref, Env.Log.error); diff --git a/lib/commands/pin-rpc.js b/lib/commands/pin-rpc.js index 663faa58b..2478910a7 100644 --- a/lib/commands/pin-rpc.js +++ b/lib/commands/pin-rpc.js @@ -454,10 +454,10 @@ Pinning.loadChannelPins = function (Env) { Pinning.isChannelPinned = function (Env, channel, cb) { Env.evPinnedPadsReady.reg(() => { if (Env.pinnedPads[channel] && Object.keys(Env.pinnedPads[channel]).length) { - cb(true); + cb(void 0, true); } else { - delete Env.pinnedPads[channel]; - cb(false); + delete Env.pinnedPads[channel]; // XXX WAT + cb(void 0, false); } }); }; diff --git a/lib/rpc.js b/lib/rpc.js index 9c48a187e..c8b6b56a9 100644 --- a/lib/rpc.js +++ b/lib/rpc.js @@ -18,98 +18,30 @@ var RPC = module.exports; const Store = require("../storage/file"); const BlobStore = require("../storage/blob"); -const UNAUTHENTICATED_CALLS = [ - 'GET_FILE_SIZE', - 'GET_METADATA', - 'GET_MULTIPLE_FILE_SIZE', - 'IS_CHANNEL_PINNED', - 'IS_NEW_CHANNEL', - 'GET_DELETED_PADS', - 'WRITE_PRIVATE_MESSAGE', -]; - -var isUnauthenticatedCall = function (call) { - return UNAUTHENTICATED_CALLS.indexOf(call) !== -1; -}; - -const AUTHENTICATED_CALLS = [ - 'COOKIE', - 'RESET', - 'PIN', - 'UNPIN', - 'GET_HASH', - 'GET_TOTAL_SIZE', - 'UPDATE_LIMITS', - 'GET_LIMIT', - 'UPLOAD_STATUS', - 'UPLOAD_COMPLETE', - 'OWNED_UPLOAD_COMPLETE', - 'UPLOAD_CANCEL', - 'EXPIRE_SESSION', - 'TRIM_OWNED_CHANNEL_HISTORY', - 'CLEAR_OWNED_CHANNEL', - 'REMOVE_OWNED_CHANNEL', - 'REMOVE_PINS', - 'TRIM_PINS', - 'WRITE_LOGIN_BLOCK', - 'REMOVE_LOGIN_BLOCK', - 'ADMIN', - 'SET_METADATA' -]; - -var isAuthenticatedCall = function (call) { - return AUTHENTICATED_CALLS.indexOf(call) !== -1; +const UNAUTHENTICATED_CALLS = { + GET_FILE_SIZE: Pinning.getFileSize, // XXX TEST + GET_MULTIPLE_FILE_SIZE: Pinning.getMultipleFileSize, + GET_DELETED_PADS: Pinning.getDeletedPads, + IS_CHANNEL_PINNED: Pinning.isChannelPinned, + IS_NEW_CHANNEL: Channel.isNewChannel, + WRITE_PRIVATE_MESSAGE: Channel.writePrivateMessage, }; var isUnauthenticateMessage = function (msg) { - return msg && msg.length === 2 && isUnauthenticatedCall(msg[0]); + return msg && msg.length === 2 && typeof(UNAUTHENTICATED_CALLS[msg[0]]) === 'function'; }; var handleUnauthenticatedMessage = function (Env, msg, respond, Server) { Env.Log.silly('LOG_RPC', msg[0]); - switch (msg[0]) { - case 'GET_FILE_SIZE': - return void Pinning.getFileSize(Env, msg[1], function (e, size) { - Env.WARN(e, msg[1]); - respond(e, [null, size, null]); - }); - case 'GET_METADATA': - return void Metadata.getMetadata(Env, msg[1], function (e, data) { - Env.WARN(e, msg[1]); - respond(e, [null, data, null]); - }); - case 'GET_MULTIPLE_FILE_SIZE': // XXX not actually used on the client? - return void Pinning.getMultipleFileSize(Env, msg[1], function (e, dict) { - if (e) { - Env.WARN(e, dict); - return respond(e); - } - respond(e, [null, dict, null]); - }); - case 'GET_DELETED_PADS': - return void Pinning.getDeletedPads(Env, msg[1], function (e, list) { - if (e) { - Env.WARN(e, msg[1]); - return respond(e); - } - respond(e, [null, list, null]); - }); - case 'IS_CHANNEL_PINNED': - return void Pinning.isChannelPinned(Env, msg[1], function (isPinned) { - respond(null, [null, isPinned, null]); - }); - case 'IS_NEW_CHANNEL': - return void Channel.isNewChannel(Env, msg[1], function (e, isNew) { - respond(e, [null, isNew, null]); - }); - case 'WRITE_PRIVATE_MESSAGE': - return void Channel.writePrivateMessage(Env, msg[1], Server, function (e, output) { - respond(e, output); - }); - default: - Env.Log.warn("UNSUPPORTED_RPC_CALL", msg); - return respond('UNSUPPORTED_RPC_CALL', msg); - } + + var method = UNAUTHENTICATED_CALLS[msg[0]]; + method(Env, msg[1], function (err, value) { + if (err) { + Env.WARN(err, msg[1]); + return void respond(err); + } + respond(err, [null, value, null]); + }, Server); }; const AUTHENTICATED_USER_TARGETED = { @@ -124,6 +56,9 @@ const AUTHENTICATED_USER_TARGETED = { UPLOAD_COMPLETE: Upload.complete, UPLOAD_CANCEL: Upload.cancel, OWNED_UPLOAD_COMPLETE: Upload.complete_owned, + WRITE_LOGIN_BLOCK: Block.writeLoginBlock, + REMOVE_LOGIN_BLOCK: Block.removeLoginBlock, + ADMIN: Admin.command, }; const AUTHENTICATED_USER_SCOPED = { @@ -135,13 +70,33 @@ const AUTHENTICATED_USER_SCOPED = { REMOVE_PINS: Pinning.removePins, TRIM_PINS: Pinning.trimPins, SET_METADATA: Metadata.setMetadata, + COOKIE: Core.haveACookie, +}; + +var isAuthenticatedCall = function (call) { + if (call === 'UPLOAD') { return false; } + return typeof(AUTHENTICATED_USER_TARGETED[call] || AUTHENTICATED_USER_SCOPED[call]) === 'function'; }; -var handleAuthenticatedMessage = function (Env, map) { - var msg = map.msg; - var safeKey = map.safeKey; - var Respond = map.Respond; - var Server = map.Server; +var handleAuthenticatedMessage = function (Env, unsafeKey, msg, respond, Server) { + /* If you have gotten this far, you have signed the message with the + public key which you provided. + */ + + var safeKey = Util.escapeKeyCharacters(unsafeKey); + + var Respond = function (e, value) { + var session = Env.Sessions[safeKey]; + var token = session? session.tokens.slice(-1)[0]: ''; + var cookie = Core.makeCookie(token).join('|'); + respond(e ? String(e): e, [cookie].concat(typeof(value) !== 'undefined' ?value: [])); + }; + + msg.shift(); + // discard validated cookie from message + if (!msg.length) { + return void Respond('INVALID_MSG'); + } var TYPE = msg[0]; @@ -151,7 +106,7 @@ var handleAuthenticatedMessage = function (Env, map) { return void AUTHENTICATED_USER_TARGETED[TYPE](Env, safeKey, msg[1], function (e, value) { Env.WARN(e, value); return void Respond(e, value); - }); + }, Server); } if (typeof(AUTHENTICATED_USER_SCOPED[TYPE]) === 'function') { @@ -164,35 +119,7 @@ var handleAuthenticatedMessage = function (Env, map) { }); } - switch (msg[0]) { - case 'COOKIE': return void Respond(void 0); - case 'WRITE_LOGIN_BLOCK': - return void Block.writeLoginBlock(Env, msg[1], function (e) { // XXX SPECIAL - if (e) { - Env.WARN(e, 'WRITE_LOGIN_BLOCK'); - return void Respond(e); - } - Respond(e); - }); - case 'REMOVE_LOGIN_BLOCK': - return void Block.removeLoginBlock(Env, msg[1], function (e) { // XXX SPECIAL - if (e) { - Env.WARN(e, 'REMOVE_LOGIN_BLOCK'); - return void Respond(e); - } - Respond(e); - }); - case 'ADMIN': - return void Admin.command(Env, Server, safeKey, msg[1], function (e, result) { // XXX SPECIAL - if (e) { - Env.WARN(e, result); - return void Respond(e); - } - Respond(void 0, result); - }); - default: - return void Respond('UNSUPPORTED_RPC_CALL', msg); - } + return void Respond('UNSUPPORTED_RPC_CALL', msg); }; var rpc = function (Env, Server, data, respond) { @@ -241,45 +168,23 @@ var rpc = function (Env, Server, data, respond) { return void respond('INVALID_MESSAGE_OR_PUBLIC_KEY'); } - if (isAuthenticatedCall(msg[1])) { - if (Core.checkSignature(Env, serialized, signature, publicKey) !== true) { - return void respond("INVALID_SIGNATURE_OR_PUBLIC_KEY"); - } - } else if (msg[1] !== 'UPLOAD') { - Env.Log.warn('INVALID_RPC_CALL', msg[1]); - return void respond("INVALID_RPC_CALL"); - } + var command = msg[1]; - var safeKey = Util.escapeKeyCharacters(publicKey); - /* If you have gotten this far, you have signed the message with the - public key which you provided. - - We can safely modify the state for that key - - OR it's an unauthenticated call, which must not modify the state - for that key in a meaningful way. - */ - - // discard validated cookie from message - msg.shift(); - - var Respond = function (e, msg) { - var session = Env.Sessions[safeKey]; - var token = session? session.tokens.slice(-1)[0]: ''; - var cookie = Core.makeCookie(token).join('|'); - respond(e ? String(e): e, [cookie].concat(typeof(msg) !== 'undefined' ?msg: [])); - }; - - if (typeof(msg) !== 'object' || !msg.length) { - return void Respond('INVALID_MSG'); + if (command === 'UPLOAD') { + // UPLOAD is a special case that skips signature validation + // intentional fallthrough behaviour + return void handleAuthenticatedMessage(Env, publicKey, msg, respond, Server); } - - handleAuthenticatedMessage(Env, { - msg: msg, - safeKey: safeKey, - Respond: Respond, - Server: Server, - }); + if (isAuthenticatedCall(command)) { + // check the signature on the message + // refuse the command if it doesn't validate + if (Core.checkSignature(Env, serialized, signature, publicKey) === true) { + return void handleAuthenticatedMessage(Env, publicKey, msg, respond, Server); + } + return void respond("INVALID_SIGNATURE_OR_PUBLIC_KEY"); + } + Env.Log.warn('INVALID_RPC_CALL', command); + return void respond("INVALID_RPC_CALL"); }; RPC.create = function (config, cb) { @@ -302,6 +207,10 @@ RPC.create = function (config, cb) { } }; + if (typeof(config.domain) !== 'undefined') { + throw new Error('fuck'); + } + var Env = { historyKeeper: config.historyKeeper, intervals: config.intervals || {},