openbus.lua 34.3 KB
Newer Older
1 2
local _G = require "_G"
local assert = _G.assert
3
local error = _G.error
4 5 6
local ipairs = _G.ipairs
local next = _G.next
local pairs = _G.pairs
7
local pcall = _G.pcall
8
local rawget = _G.rawget
9
local setmetatable = _G.setmetatable
10
local tostring = _G.tostring
11 12 13

local array = require "table"
local unpack = array.unpack or _G.unpack
14 15

local coroutine = require "coroutine"
16
local running = coroutine.running
17 18
local newthread = coroutine.create

19
local string = require "string"
20
local findstring = string.find
21
local repeatstring = string.rep
22
local substring = string.sub
23

24 25
local math = require "math"
local inf = math.huge
26
local max = math.max
27

28 29 30
local io = require "io"
local openfile = io.open

31 32
local hash = require "lce.hash"
local sha256 = hash.sha256
33
local pubkey = require "lce.pubkey"
34
local newkey = pubkey.create
35
local decodeprvkey = pubkey.decodeprivate
36
local decodepubkey = pubkey.decodepublic
37 38
local x509 = require "lce.x509"
local decodecertificate = x509.decode
39 40

local table = require "loop.table"
Carlos Eduardo Lara Augusto's avatar
Carlos Eduardo Lara Augusto committed
41
local copy = table.copy
42 43
local memoize = table.memoize

44
local LRUCache = require "loop.collection.LRUCache"
45
local Wrapper = require "loop.object.Wrapper"
46

Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
47
local cothread = require "cothread"
48 49 50 51
local resume = cothread.next
local threadtrap = cothread.trap
local unschedule = cothread.unschedule

52
local log = require "openbus.util.logger"
53 54 55
local msg = require "openbus.util.messages"
local oo = require "openbus.util.oo"
local class = oo.class
56 57 58 59 60 61
local sysex = require "openbus.util.sysex"
local NO_PERMISSION = sysex.NO_PERMISSION
local is_NO_PERMISSION = sysex.is_NO_PERMISSION
local is_TRANSIENT = sysex.is_TRANSIENT
local is_COMM_FAILURE = sysex.is_COMM_FAILURE
local is_OBJECT_NOT_EXIST = sysex.is_OBJECT_NOT_EXIST
62 63
local server = require "openbus.util.server"
local blockencrypt = server.blockencrypt
64

65
local libidl = require "openbus.idl"
66 67 68 69 70
local libthrow = libidl.throw
local AlreadyLoggedIn = libthrow.AlreadyLoggedIn
local InvalidBusAddress = libthrow.InvalidBusAddress
local InvalidLoginProcess = libthrow.InvalidLoginProcess
local InvalidPropertyValue = libthrow.InvalidPropertyValue
71 72
local InvalidEncodedStream = libthrow.InvalidEncodedStream
local WrongBus = libthrow.WrongBus
73
local coreidl = require "openbus.core.idl"
74
local coreconst = coreidl.const
75
local BusEntity = coreconst.BusEntity
76 77 78 79 80 81 82 83 84 85 86 87
local BusObjectKey = coreconst.BusObjectKey
local EncryptedBlockSize = coreconst.EncryptedBlockSize
local CredentialContextId = coreconst.credential.CredentialContextId
local loginconst = coreconst.services.access_control
local InvalidPublicKeyCode = loginconst.InvalidPublicKeyCode
local NoLoginCode = loginconst.NoLoginCode
local InvalidRemoteCode = loginconst.InvalidRemoteCode
local InvalidLoginCode = loginconst.InvalidLoginCode
local NoCredentialCode = loginconst.NoCredentialCode
local UnknownBusCode = loginconst.UnknownBusCode
local UnverifiedLoginCode = loginconst.UnverifiedLoginCode
local UnavailableBusCode = loginconst.UnavailableBusCode
88
local coresrvtypes = coreidl.types.services
89 90 91 92 93 94 95 96
local logintypes = coresrvtypes.access_control
local AccessControlRepId = logintypes.AccessControl
local LoginRegistryRepId = logintypes.LoginRegistry
local InvalidLoginsRepId = logintypes.InvalidLogins
local LoginAuthenticationInfoRepId = logintypes.LoginAuthenticationInfo
local OfferRegistryRepId = coresrvtypes.offer_registry.OfferRegistry
local coresrvthrow = coreidl.throw.services
local loginthrow = coresrvthrow.access_control
97 98
local AccessDenied = loginthrow.AccessDenied
local InvalidLogins = loginthrow.InvalidLogins
99 100
local ServiceFailure = coresrvthrow.ServiceFailure
local exportconst = coreidl.const.data_export
101
local ExportVersion = exportconst.ExportVersion
102
local exporttypes = coreidl.types.data_export
103
local VersionedDataSeqRepId = exporttypes.VersionedDataSeq
104 105
local ExportedCallChainRepId = exporttypes.ExportedCallChain
local ExportedSharedAuthRepId = exporttypes.ExportedSharedAuth
106
local access = require "openbus.core.Access"
107
local neworb = access.initORB
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
108
local setNoPermSysEx = access.setNoPermSysEx
109 110 111 112 113
local BaseContext = access.Context
local BaseInterceptor = access.Interceptor
local sendBusRequest = BaseInterceptor.sendrequest
local receiveBusReply = BaseInterceptor.receivereply
local receiveBusRequest = BaseInterceptor.receiverequest
114
local unmarshalCredential = BaseInterceptor.unmarshalCredential
115
local unmarshalSignedChain = BaseInterceptor.unmarshalSignedChain
116
local oldidl = require "openbus.core.legacy.idl"
117
local LegacyAccessControlRepId = oldidl.types.v2_0.services.access_control.AccessControl
118
local LegacyConverterRepId = oldidl.types.v2_1.services.legacy_support.LegacyConverter
119 120
local LegacyExportVersion = oldidl.const.v2_0.data_export.CurrentVersion
local oldexporttypes = oldidl.types.v2_0.data_export
121
local LegacyExportedCallChainRepId = oldexporttypes.ExportedCallChain
122
local LegacyExportedSharedAuthRepId = oldexporttypes.ExportedSharedAuth
123

124 125
-- must be loaded after OiL is loaded because OiL is the one that installs
-- the cothread plug-in that supports the 'now' operation.
126
cothread.plugin(require "cothread.plugin.sleep")
127
local delay = cothread.delay
128
local time = cothread.now
129

130 131
local IComponentRepId = "IDL:scs/core/IComponent:1.0"

132 133 134 135 136 137
do
  local isNoPerm = is_NO_PERMISSION
  function is_NO_PERMISSION(except, minor, completed)
    return isNoPerm(except, minor, completed or "COMPLETED_NO")
  end
end
138

139 140 141 142 143 144 145 146 147 148 149 150
local function unmarshalJustSignedChain(self, conn, signed)
  local chain = unmarshalSignedChain(self, signed, self.types.CallChain)
  local legacy = conn.legacy
  if legacy ~= nil then
    local converter = legacy.converter
    if converter ~= nil then
      chain.legacy = converter:signChainFor(chain.target)
    end
  end
  return chain
end

151
local function getLoginEntry(self, loginId)
152
  local LoginRegistry = self.__object
153 154 155 156 157

  -- use the cache to get information about the login
  local cache = self.cache
  local entry = cache:get(loginId)
  if entry == nil then
158
    local ok, result, enckey = pcall(LoginRegistry.getLoginInfo,
159
                                        LoginRegistry, loginId)
160 161 162 163
    entry = cache:get(loginId) -- Check again to see if anything changed
                               -- after the completion of the remote call.
                               -- The information in the cache should be
                               -- more reliable because it must be older
164 165
                               -- so the 'deadline' must be tighter than
                               -- the one generated now.
166 167 168 169
    if entry == nil then
      if ok then
        local pubkey, exception = decodepubkey(enckey)
        if pubkey == nil then
170
          NO_PERMISSION{
171
            completed = "COMPLETED_NO",
172
            minor = InvalidPublicKeyCode,
173 174 175 176 177 178
          }
        end
        entry = result
        entry.encodedkey = enckey
        entry.pubkey = pubkey
        entry.deadline = time() -- valid until this moment
179
      elseif result._repid == InvalidLoginsRepId then
180 181 182 183 184 185
        entry = false
      else
        error(result)
      end
      cache:put(loginId, entry)
      return entry or nil
186 187
    end
  end
188

189 190 191
  -- use the cache to validate the login
  if entry then
    if entry.deadline < time() then -- update deadline
192
      local validity = LoginRegistry:getLoginValidity(loginId)
193 194 195 196 197 198 199 200 201
      if validity <= 0 then
        cache:put(loginId, false)
        return nil -- invalid login
      end
      entry.deadline = time() + validity
    end
    return entry -- valid login
  end
end
202

203
local function getLoginInfo(self, loginId)
204 205 206
  local entry = getLoginEntry(self, loginId)
  if entry ~= nil then
    return entry
207
  end
208
  InvalidLogins{loginIds={loginId}}
209 210
end

211
local function newLoginRegistryWrapper(LoginRegistry, buskey)
212
  local self = Wrapper{
213
    __object = LoginRegistry,
214
    cache = LRUCache(),
215
    buskey = buskey,
216 217 218
    getLoginEntry = getLoginEntry,
    getLoginInfo = getLoginInfo,
  }
219
  return self
220 221
end

222
local function unmarshalChain(self, signed)
223 224 225
  local context = self.context
  local decoder = context.orb:newdecoder(signed.encoded)
  return decoder:get(context.types.CallChain)
226 227
end

228
local function getDispatcherFor(self, request, credential)
229 230 231 232
  local callback = self.onCallDispatch
  if callback ~= nil then
    local params = request.parameters
    return callback(self,
233
      credential.bus,
234
      credential.login,
235 236 237 238 239 240
      request.object_key,
      request.operation_name,
      unpack(params, 1, params.n))
  end
end

241
local pcallWithin do
242 243
  local function continuation(context, backup, ...)
    context:setCurrentConnection(backup)
244 245 246
    return ...
  end
  function pcallWithin(self, obj, op, ...)
247 248 249
    local context = self.context
    local backup = context:setCurrentConnection(self)
    return continuation(context, backup, pcall(obj[op], obj, ...))
250 251 252 253 254 255 256 257 258 259 260 261 262
  end
end

local callWithin do
  local function continuation(ok, errmsg, ...)
    if not ok then error(errmsg, 3) end
    return errmsg, ...
  end
  function callWithin(...)
    return continuation(pcallWithin(...))
  end
end

Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
263 264 265 266 267 268 269
local function newRenewer(self, lease)
  local thread
  thread = newthread(function()
    local login = self.login
    local access = self.AccessControl
    while self.login == login do
      self.renewer = thread
270
      delay(lease)
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
271 272 273 274 275 276 277 278 279 280
      self.renewer = nil
      log:action(msg.RenewLogin:tag{id=login.id,entity=login.entity})
      local ok, result = pcall(access.renew, access)
      if ok then
        lease = result
      else
        log:exception(msg.FailedToRenewLogin:tag{error = result})
      end
    end
  end)
281
  self.context.connectionOf[thread] = self
282
  log.viewer.labels[thread] = "Login "..self.login.id.." Renewer"
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
283 284 285
  resume(thread)
end

286 287
local function getCoreFacet(self, name, iface)
  return self.context.orb:narrow(self.bus:getFacetByName(name), iface)
288 289 290
end

local function intiateLogin(self)
291
  local AccessControl = getCoreFacet(self, "AccessControl", AccessControlRepId)
292 293 294 295 296
  local certificate, errmsg = decodecertificate(AccessControl:_get_certificate())
  if certificate == nil then
    ServiceFailure{message=msg.InvalidBusCertificate:tag{message=errmsg}}
  end
  local buskey, errmsg = certificate:getpubkey()
297
  if buskey == nil then
298
    ServiceFailure{message=msg.UnableToObtainBusKey:tag{message=errmsg}}
299 300 301 302 303
  end
  return AccessControl, buskey
end

local function encryptLogin(self, buskey, pubkey, data)
304 305
  local context = self.context
  local encoder = context.orb:newencoder()
306
  encoder:put({data=data,hash=sha256(pubkey)},
307
    context.types.LoginAuthenticationInfo)
308 309 310
  return buskey:encrypt(encoder:getdata())
end

311
local function localLogin(self, AccessControl, busid, buskey, login, lease)
312 313
  local LoginRegistry = getCoreFacet(self, "LoginRegistry", LoginRegistryRepId)
  if self.login ~= nil then AlreadyLoggedIn() end
314 315 316 317
  self.invalidLogin = nil
  self.busid = busid
  self.buskey = buskey
  self.AccessControl = AccessControl
318
  self.LoginRegistry = newLoginRegistryWrapper(LoginRegistry, buskey)
319 320
  self.login = login
  newRenewer(self, lease)
321 322 323
  if self.legacy then
    local legacy = getCoreFacet(self, "LegacySupport", IComponentRepId)
    if legacy ~= nil then
324 325 326 327 328 329 330 331 332
      local access = legacy:getFacetByName("AccessControl")
      if access ~= nil then
        local converter = legacy:getFacetByName("LegacyConverter")
        legacy = self.context.orb:narrow(access, LegacyAccessControlRepId)
        if converter ~= nil then
          legacy.converter = self.context.orb:narrow(converter, LegacyConverterRepId)
        else
          log:exception(msg.LegacyDataExportDisableDueToMissingConverter)
        end
333
      else
334 335
        log:exception(msg.LegacyDisableDueToMissingAccessControl)
        legacy = nil
336 337 338 339 340 341
      end
    else
      log:exception(msg.LegacySupportNotAvailable)
    end
    self.legacy = legacy
  end
342 343
end

344
local function localLogout(self)
345 346 347 348
  self.busid = nil
  self.buskey = nil
  self.AccessControl = nil
  self.LoginRegistry = nil
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363
  self.login = nil
  self:resetCaches()
  local renewer = self.renewer
  if renewer ~= nil then
    self.renewer = nil
    unschedule(renewer)
  end
end

local function getLogin(self)
  local login = self.login
  if login == nil then
    -- try to recover from an invalid login
    local invlogin = self.invalidLogin
    while invlogin ~= nil do
364 365 366 367
      local ok, ex = pcall(self.onInvalidLogin, self, invlogin)
      if not ok then
        log:exception(msg.OnInvalidLoginCallbackError:tag{error=ex})
      end
368 369 370 371 372 373 374 375 376 377 378 379 380
      local current = self.invalidLogin
      if current == invlogin then
        self.invalidLogin = nil
        invlogin = nil
      else
        invlogin = current
      end
    end
    login = self.login
  end
  return login
end

381 382
local MaxEncryptionSize = EncryptedBlockSize-11
local MaxEncryptionData = repeatstring("\255", MaxEncryptionSize)
383 384 385 386 387

local function busaddress2component(orb, host, port, key)
  local ref = "corbaloc::"..host..":"..port.."/"..key
  local ok, result = pcall(orb.newproxy, orb, ref, nil, "scs::core::IComponent")
  if not ok then
388
    InvalidBusAddress{host=host,port=port,message=tostring(result)}
389 390 391 392 393 394
  end
  return result
end



395 396 397 398 399 400 401 402 403 404 405 406 407
local SharedAuthSecret = class()

function SharedAuthSecret:__init()
  self.busid = self.bus
end

function SharedAuthSecret:cancel()
  local attempt = self.attempt
  return pcall(attempt.cancel, attempt)
end



408
local Connection = class({}, BaseInterceptor)
409

410
function Connection:resetCaches()
411
  BaseInterceptor.resetCaches(self)
412 413 414
  self.signedChainOf = memoize(function(chain) return LRUCache() end, "k")
end

415
local NullChain = {}
416
function Connection:signChainFor(target, chain)
417
  if target == BusEntity then return chain, chain.islegacy end
418
  local access = self.AccessControl
419
  local cache = self.signedChainOf[chain or NullChain]
420
  local joined = cache:get(target)
421
  while joined == nil do
422
    joined = access:signChainFor(target)
423 424
    local login = getLogin(self)
    if login == nil then
425
      NO_PERMISSION{
426
        completed = "COMPLETED_NO",
427
        minor = NoLoginCode,
428 429
      }
    end
430
    cache = self.signedChainOf[chain or NullChain]
431
    if unmarshalChain(self, joined).caller.id == login.id then
432
      cache:put(target, joined)
433 434
      break
    end
435
    joined = cache:get(target)
436
  end
437
  return joined
438 439
end

440

441
function Connection:sendrequest(request)
442
  local login = getLogin(self)
443
  if login ~= nil then
444
    sendBusRequest(self, request)
445
    request.login = login
446
  else
447
    setNoPermSysEx(request, NoLoginCode)
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
448
    log:exception(msg.AttemptToCallWhileNotLoggedIn:tag{
449
      operation = request.operation_name,
450 451
    })
  end
452 453
end

454
local NoInvalidLoginHandling = {}
455

456
function Connection:receivereply(request)
457
  receiveBusReply(self, request)
458
  local thread = running()
459
  if request.success == false and NoInvalidLoginHandling[thread] == nil then
460
    local except = request.results[1]
461
    if is_NO_PERMISSION(except, InvalidLoginCode) then
462
      local invlogin = request.login
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
463
      log:exception(msg.GotInvalidLoginException:tag{
464
        operation = request.operation_name,
465 466
        login = invlogin.id,
        entity = invlogin.entity,
467
      })
468
      local ok, result
469
      local logins = self.LoginRegistry
470 471 472 473 474 475 476
      if logins ~= nil then
        NoInvalidLoginHandling[thread] = true
        ok, result = pcall(logins.getLoginValidity, logins, invlogin.id)
        NoInvalidLoginHandling[thread] = nil
      else -- we aren't logged
        ok, result = true, 0
      end
477 478 479 480 481 482 483 484 485 486
      if ok and result > 0 then
        log:exception(msg.GotFalseInvalidLogin:tag{
          invlogin = invlogin.id,
        })
        except.minor = InvalidRemoteCode
      elseif ok or is_NO_PERMISSION(result, InvalidLoginCode) then
        if self.login == invlogin then
          localLogout(self)
          self.invalidLogin = invlogin
        end
487
        request.success = nil -- reissue request to the same reference
488
        log:action(msg.ReissueCallAfterInvalidLogin:tag{
489
          operation = request.operation_name,
490
        })
491
      else
492 493 494 495 496
        log:exception(msg.UnableToVerifyOwnLoginValidity:tag{
          error = result,
          invlogin = invlogin.id,
        })
        except.minor = UnavailableBusCode
497 498 499
      end
    end
  end
500 501
end

502
function Connection:receiverequest(request, ...)
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
503
  if self.login ~= nil then
504 505 506
    local ok, ex = pcall(receiveBusRequest, self, request, ...)
    if not ok then
      if is_NO_PERMISSION(ex, NoLoginCode) then
507
        log:exception(msg.LostLoginWhilePrepatingDispatch:tag{
508 509
          operation = request.operation.name,
        })
510 511 512 513 514 515 516 517
        setNoPermSysEx(request, UnknownBusCode)
      elseif is_TRANSIENT(ex) or is_COMM_FAILURE(ex) then
        log:exception(msg.UnableToVerifyLoginDueToCoreServicesUnaccessible:tag{
          operation = request.operation.name,
        })
        setNoPermSysEx(request, UnverifiedLoginCode)
      else
        error(ex)
518
      end
519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544
    elseif request.success == nil then
      local legacy = self.legacy
      if legacy ~= nil then
        local converter = legacy.converter
        if converter ~= nil then
          local context = self.context
          local chain = context:getCallerChain()
          if not chain.islegacy then
            context:joinChain(chain)
            local ok, result = pcall(converter.convertSignedChain, converter)
            context:exitChain()
            if ok then
              chain.legacy = result
            else
              -- TODO: is there a better minor code for this?
              --       It seems the problem is the name of this minor code and
              --       not lack of a special name. A proper name would be
              --       UnavailableBusRemotelyCode
              log:exception(msg.UnableToConvertSignedChain:tag{
                errmsg = result,
              })
              setNoPermSysEx(request, UnverifiedLoginCode)
            end
          end
        end
      end
545 546
    end
  else
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
547
    log:exception(msg.GotCallWhileNotLoggedIn:tag{
548
      operation = request.operation_name,
549
    })
550
    setNoPermSysEx(request, UnknownBusCode)
551
  end
552 553 554
end


555
function Connection:loginByPassword(entity, password, domain)
556
  if self.login ~= nil then AlreadyLoggedIn() end
557
  local AccessControl, buskey = intiateLogin(self)
558
  local pubkey = self.prvkey:encode("public")
559
  local encrypted, errmsg = encryptLogin(self, buskey, pubkey, password)
560
  if encrypted == nil then
561
    AccessDenied{message=msg.UnableToEncryptPassword:tag{message=errmsg}}
562
  end
563 564
  local login, lease = AccessControl:loginByPassword(entity, domain, pubkey,
                                                     encrypted)
565 566
  local busid = AccessControl:_get_busid()
  localLogin(self, AccessControl, busid, buskey, login, lease)
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
567
  log:request(msg.LoginByPassword:tag{
568 569 570
    login = login.id,
    entity = login.entity,
  })
571 572
end

573
function Connection:loginByCertificate(entity, privatekey)
574
  if self.login ~= nil then AlreadyLoggedIn() end
575 576
  local AccessControl, buskey = intiateLogin(self)
  local attempt, challenge = AccessControl:startLoginByCertificate(entity)
577 578
  local secret, errmsg = privatekey:decrypt(challenge)
  if secret == nil then
579
    pcall(attempt.cancel, attempt)
580
    AccessDenied{message=msg.UnableToDecryptChallenge:tag{message=errmsg}}
581 582
  end
  local pubkey = self.prvkey:encode("public")
583
  local encrypted, errmsg = encryptLogin(self, buskey, pubkey, secret)
584
  if encrypted == nil then
585
    pcall(attempt.cancel, attempt)
586
    ServiceFailure{message=msg.UnableToEncryptSecret:tag{message=errmsg}}
587 588 589
  end
  local ok, login, lease = pcall(attempt.login, attempt, pubkey, encrypted)
  if not ok then
590
    if is_OBJECT_NOT_EXIST(login) then
591 592 593
      ServiceFailure{message=msg.UnableToCompleteLoginByCertificateInTime}
    end
    error(login)
594
  end
595 596
  local busid = AccessControl:_get_busid()
  localLogin(self, AccessControl, busid, buskey, login, lease)
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
597
  log:request(msg.LoginByCertificate:tag{
598 599 600
    login = login.id,
    entity = login.entity,
  })
601 602
end

603
function Connection:startSharedAuth()
604 605
  local AccessControl = self.AccessControl
  if AccessControl == nil then
606
    NO_PERMISSION{
607
      completed = "COMPLETED_NO",
608
      minor = NoLoginCode,
609 610 611
    }
  end
  local attempt, challenge = callWithin(self, AccessControl,
612
                                        "startLoginBySharedAuth")
613 614
  local secret, errmsg = self.prvkey:decrypt(challenge)
  if secret == nil then
615
    pcall(attempt.cancel, attempt)
616
    ServiceFailure{message=msg.UnableToDecryptChallenge:tag{message=errmsg}}
617
  end
618 619 620 621 622
  local legacy = self.legacy
  if legacy ~= nil then
    legacy = legacy.converter
    if legacy ~= nil then
      local ok
623
      ok, legacy = pcallWithin(self, legacy, "convertSharedAuth", attempt)
624 625 626 627 628 629
      if not ok then
        log:exception(msg.UnableToConvertSharedAuth:tag{ errmsg = legacy })
        legacy = nil
      end
    end
  end
630 631 632
  return SharedAuthSecret{
    bus = self.busid,
    attempt = attempt,
633
    legacy = legacy,
634 635
    secret = secret,
  }
636 637
end

638
function Connection:loginBySharedAuth(sharedauth)
639
  if self.login ~= nil then AlreadyLoggedIn() end
640
  local AccessControl, buskey = intiateLogin(self)
641 642 643 644
  local busid = AccessControl:_get_busid()
  if busid ~= sharedauth.bus then WrongBus() end
  local attempt = sharedauth.attempt
  local secret = sharedauth.secret
645
  local pubkey = self.prvkey:encode("public")
646
  local encrypted, errmsg = encryptLogin(self, buskey, pubkey, secret)
647
  if encrypted == nil then
648
    pcall(attempt.cancel, attempt)
649
    AccessDenied{message=msg.UnableToEncryptSecret:tag{message=errmsg}}
650 651 652
  end
  local ok, login, lease = pcall(attempt.login, attempt, pubkey, encrypted)
  if not ok then
653 654
    if is_OBJECT_NOT_EXIST(login) then
      InvalidLoginProcess()
655 656
    end
    error(login)
657
  end
658
  localLogin(self, AccessControl, busid, buskey, login, lease)
659
  log:request(msg.LoginBySharedAuth:tag{
660 661 662
    login = login.id,
    entity = login.entity,
  })
663 664
end

665
function Connection:logout()
666
  local result, except = true
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
667 668 669 670 671 672
  local login = self.login
  if login ~= nil then
    log:request(msg.PerformLogout:tag{
      login = login.id,
      entity = login.entity,
    })
673
    local thread = running()
674
    NoInvalidLoginHandling[thread] = true
675
    result, except = pcallWithin(self, self.AccessControl, "logout")
676
    NoInvalidLoginHandling[thread] = nil
677
    localLogout(self)
678 679 680
    if not result and is_NO_PERMISSION(except, InvalidLoginCode) then
      result, except = true, nil
    end
681
  end
682
  self.invalidLogin = nil
683
  return result, except
684 685
end

686
function Connection:onInvalidLogin()
687
  -- does nothing by default
688 689
end

690 691


692
local NoBusInterception = {}
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
693 694 695

local WeakKeys = { __mode="k" }

696
local Context = class({}, BaseContext)
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
697

698
function Context:__init()
699
  if self.prvkey == nil then self.prvkey = newkey(EncryptedBlockSize) end
700
  self.connectionOf = setmetatable({}, WeakKeys) -- [thread]=connection
701
  self.types.LoginAuthenticationInfo =
702
    self.orb.types:lookup_id(LoginAuthenticationInfoRepId)
703
  self.types.VersionedDataSeq =
704
    self.orb.types:lookup_id(VersionedDataSeqRepId)
705 706 707 708
  self.types.ExportedCallChain =
    self.orb.types:lookup_id(ExportedCallChainRepId)
  self.types.ExportedSharedAuth =
    self.orb.types:lookup_id(ExportedSharedAuthRepId)
709 710
  self.types.LegacyExportedCallChain =
    self.orb.types:lookup_id(LegacyExportedCallChainRepId)
711 712
  self.types.LegacyExportedSharedAuth =
    self.orb.types:lookup_id(LegacyExportedSharedAuthRepId)
713 714 715 716
  -- following are necessary to execute 'BaseInterceptor.unmarshalCredential(self)'
  self.legacy = true
  -- following is necessary to execute 'BaseInterceptor.unmarshalSignedChain(self)'
  self.context = self 
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
717 718
end

719
function Context:sendrequest(request)
720
  if NoBusInterception[running()] == nil then
721
    local conn = self:getCurrentConnection()
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
722
    if conn ~= nil then
723
      request[self] = conn
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
724 725 726 727 728
      conn:sendrequest(request)
    else
      log:exception(msg.AttemptToCallWithoutConnection:tag{
        operation = request.operation_name,
      })
729
      setNoPermSysEx(request, NoLoginCode)
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
730 731 732 733 734
    end
  else
    log:access(self, msg.PerformIgnoredCall:tag{
      operation = request.operation_name,
    })
735
  end
736 737
end

738
function Context:receivereply(request)
739
  local conn = request[self]
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
740 741 742
  if conn ~= nil then
    conn:receivereply(request)
    if request.success ~= nil then
743
      request[self] = nil
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
744 745 746
    end
  end
end
747

748
function Context:receiverequest(request)
749
  local credential = unmarshalCredential(self, request.service_context)
750 751 752 753 754 755 756 757 758 759 760 761 762
  if credential ~= nil then
    local conn = getDispatcherFor(self, request, credential)
              or self.defaultConnection
    if conn ~= nil then
      request[self] = conn
      self:setCurrentConnection(conn)
      conn:receiverequest(request, credential)
    else
      log:exception(msg.GotCallWhileDisconnected:tag{
        operation = request.operation_name,
      })
      setNoPermSysEx(request, UnknownBusCode)
    end
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
763
  else
764 765
    log:exception(msg.DeniedOrdinaryCall:tag{
      operation = request.operation.name,
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
766
    })
767
    setNoPermSysEx(request, NoCredentialCode)
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
768 769
  end
end
770

771
function Context:sendreply(request)
772
  local conn = request[self]
773
  if conn ~= nil then
774
    request[self] = nil
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
775 776
    conn:sendreply(request)
  end
777 778
end

779
function Context:connectByReference(bus, props)
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
780
  if props == nil then props = {} end
781 782 783 784 785
  -- validate access key if provided
  local prvkey = props.accesskey
  if prvkey ~= nil then
    local result, errmsg = decodepubkey(prvkey:encode("public"))
    if result == nil then
786
      InvalidPropertyValue{
787 788 789 790
        property = "accesskey",
        value = msg.UnableToObtainThePublicKey:tag{error=errmsg},
      }
    end
791
    result, errmsg = result:encrypt(MaxEncryptionData)
792
    if result == nil then
793
      InvalidPropertyValue{
794 795 796 797 798 799
        property = "accesskey",
        value = msg.UnableToEncodeDataUsingPublicKey:tag{error=errmsg},
      }
    end
    result, errmsg = prvkey:decrypt(result)
    if result == nil then
800
      InvalidPropertyValue{
801 802 803 804 805
        property = "accesskey",
        value = msg.UnableToDecodeDataUsingTheKey:tag{error=errmsg},
      }
    end
  end
806
  return Connection{
807
    context = self,
808
    orb = self.orb,
809
    bus = bus,
810
    prvkey = prvkey or self.prvkey,
811
    legacy = not props.nolegacy,
812
  }
813 814
end

815 816
function Context:connectByAddress(host, port, props)
  if props == nil then props = {} end
817 818
  local ref = busaddress2component(self.orb, host, port, BusObjectKey)
  return self:connectByReference(ref, props)
819 820 821 822
end

Context.createConnection = Context.connectByAddress

823 824
function Context:setDefaultConnection(conn)
  local old = self.defaultConnection
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
825
  self.defaultConnection = conn
826
  return old
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
827 828
end

829
function Context:getDefaultConnection()
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
830 831 832
  return self.defaultConnection
end

833 834 835 836 837 838
function Context:setCurrentConnection(conn)
  local connectionOf = self.connectionOf
  local thread = running()
  local old = connectionOf[thread]
  connectionOf[thread] = conn
  return old
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
839 840
end

841
function Context:getCurrentConnection()
842 843
  return self.connectionOf[running()]
      or self.defaultConnection
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
844 845
end

846 847
function Context:getLoginRegistry()
  local conn = self:getCurrentConnection()
848
  if conn == nil or conn.login == nil then
849
    NO_PERMISSION{
850
      completed = "COMPLETED_NO",
851
      minor = NoLoginCode,
852 853 854 855 856
    }
  end
  return conn.LoginRegistry
end

857 858 859
function Context:getOfferRegistry()
  local conn = self:getCurrentConnection()
  if conn == nil or conn.login == nil then
860 861 862 863 864 865 866 867
    NO_PERMISSION{
      completed = "COMPLETED_NO",
      minor = NoLoginCode,
    }
  end
  return getCoreFacet(conn, "OfferRegistry", OfferRegistryRepId)
end

868
function Context:makeChainFor(target)
869 870 871
  local conn = self:getCurrentConnection()
  if conn == nil or conn.login == nil then
    NO_PERMISSION{
872
      completed = "COMPLETED_NO",
873
      minor = NoLoginCode,
874
    }
875
  end
876 877 878 879 880 881 882 883 884 885 886 887
  local signed = conn:signChainFor(target, self:getJoinedChain())
  return unmarshalJustSignedChain(self, conn, signed)
end

function Context:importChain(token, domain)
  local conn = self:getCurrentConnection()
  local buskey, AccessControl = conn.buskey, conn.AccessControl
  if buskey == nil then
    NO_PERMISSION{
      completed = "COMPLETED_NO",
      minor = NoLoginCode,
    }
888
  end
889 890

  local encrypted = assert(blockencrypt(buskey, "encrypt", MaxEncryptionSize, token))
891 892
  local signed = AccessControl:signChainByToken(encrypted, domain)
  return unmarshalJustSignedChain(self, conn, signed)
893 894
end

895 896 897
local EncodingValues = {
  Chain = {
    magictag = exportconst.MagicTag_CallChain,
898
    versions = {
899 900 901
      -- version encoding order
      ExportVersion, LegacyExportVersion,
      -- version encoding info
902 903
      [ExportVersion] = {
        typename = "ExportedCallChain",
904 905 906 907 908
        pack = function (self, chain)
          if not chain.islegacy then
            return chain
          end
        end,
909
        unpack = function (self, decoded)
910 911
          return unmarshalSignedChain(self, decoded,
                                      self.types.CallChain)
912 913 914 915
        end,
      },
      [LegacyExportVersion] = {
        typename = "LegacyExportedCallChain",
916 917 918 919 920 921 922 923 924 925 926 927 928
        pack = function (self, chain)
          if chain.islegacy then
            return {
              bus = chain.busid,
              signedChain = chain,
            }
          elseif chain.legacy ~= nil then
            return {
              bus = chain.busid,
              signedChain = chain.legacy,
            }
          end
        end,
929
        unpack = function (self, decoded)
930 931 932
          local chain = unmarshalSignedChain(self, decoded.signedChain,
                                             self.types.LegacyCallChain)
          chain.busid = decoded.bus
933
          chain.islegacy = true
934
          return chain
935 936 937
        end,
      },
    },
938
  },
939 940
  SharedAuth = {
    magictag = exportconst.MagicTag_SharedAuth,
941
    versions = {
942 943 944
      -- version encoding order
      ExportVersion, LegacyExportVersion,
      -- version encoding info
945 946
      [ExportVersion] = {
        typename = "ExportedSharedAuth",
947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968
        pack = function (self, secret)
          if not secret.islegacy then
            return secret
          end
        end,
        unpack = function (self, decoded)
          return SharedAuthSecret(decoded)
        end,
      },
      [LegacyExportVersion] = {
        typename = "LegacyExportedSharedAuth",
        pack = function (self, secret)
          if secret.islegacy then
            return secret
          elseif secret.legacy ~= nil then
            return {
              bus = secret.bus,
              attempt = secret.legacy,
              secret = secret.secret,
            }
          end
        end,
969
        unpack = function (self, decoded)
970
          decoded.islegacy = true
971 972 973 974
          return SharedAuthSecret(decoded)
        end,
      },
    },
975 976 977 978 979 980 981
  },  
}

for name, info in pairs(EncodingValues) do
  Context["encode"..name] = function (self, value)
    local types = self.types
    local orb = self.orb
982 983 984 985 986 987 988 989 990 991 992 993
    local exported = {}
    local versions = info.versions
    for _, versiontag in ipairs(versions) do
      local version = versions[versiontag]
      local result = version.pack(self, value)
      if result ~= nil then
        local encoder = orb:newencoder()
        encoder:put(result, types[version.typename])
        exported[#exported+1] = {
          version = versiontag,
          encoded = encoder:getdata()
        }
994
      end
995
    end
996
    local encoder = orb:newencoder()
997
    encoder:put(exported, types.VersionedDataSeq)
998
    return info.magictag..encoder:getdata()
999 1000
  end

1001 1002 1003 1004 1005 1006
  Context["decode"..name] = function (self, stream)
    local magictag = info.magictag
    if findstring(stream, magictag, 1, "no regex") == 1 then
      local types = self.types