// IfsLogConn.bcpl - authentication (Login and Connect)
// Copyright Xerox Corporation 1979, 1981, 1982
// Last modified April 11, 1982  3:01 PM by Taft

get "Ifs.decl"
get "IfsFiles.decl"
get "IfsDirs.decl"
get "Grapevine.decl"

external
[
// outgoing procedures
CreateUserInfo; DestroyUserInfo; Login; Connect; FindDIF

// incoming procedures
GetDIF; UpdateCachedDIF; CheckAccess; MakeQualifiedRName; WheelCall
IFSOpenFile; FilePos; ChangeFileAttributes; StreamsFD; PutTemplate; Closes
MakeKey; Authenticate
StringCompare; ExtractSubstring; RNamesEqual
Password; ReadCalendar; DoubleSubtract; DoubleUsc
SysAllocateZero; SysFree; Zero; MoveBlock; FreePointer; DefaultArgs

// outgoing statics
enablePasswordLog

// incoming statics
enableGrapevineAuth; enableGrapevineGroup
]

static
[
enablePasswordLog = false
]

// User name conventions:
// If Grapevine authentication is not enabled, user and connect names
// must exactly match the names of the DIFs to which access is being gained.
// If Grapevine authentication is enabled, user names not qualified by
// registry are assumed to belong to the default registry, except in the
// case where the qualified name does not exist in Grapevine but does exist
// (qualified or unqualified) as a local DIF.
// UserInfo.userName is always fully-qualified except in the latter case.
// With respect to presence or absence of qualification, UserInfo.connName
// always matches the name of the local DIF, and it is not possible to
// connect to a directory for which no DIF exists.

//---------------------------------------------------------------------------
let CreateUserInfo() = SysAllocateZero(lenUserInfo)
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
and DestroyUserInfo(ui) be
//---------------------------------------------------------------------------
[
FreePointer(lv ui>>UserInfo.userName, lv ui>>UserInfo.connName,
 lv ui>>UserInfo.defaultDir, lv ui)
]

//---------------------------------------------------------------------------
and Login(name, password, ui, force; numargs na) = valof
//---------------------------------------------------------------------------
// Name may be qualified or unqualified; if unqualified, defaultRegistry
// is assumed.  If force then suppress files-only and password check.
// Returns: 0 if ok, an error code otherwise.
// Updates UserInfo structure iff successful (changes only userName,
// connName, capabilities, and userGroups).
[
if na le 3 then force = false
if name eq 0 % name>>String.length eq 0 resultis ecNamePasswordRequired
let res = 0

if ui>>UserInfo.userName eq 0 %  // currently not logged in?
 not RNamesEqual(name, ui>>UserInfo.userName) then  // different name?
   [
   let difName = 0
   let dif = GetDIF(name, false, lv difName)

   res = valof
      [
      if dif eq 0 then
         [
	 // no local DIF found.  We can now succeed only if Grapevine
	 // authenticates the name/password; and login cannot be forced.
	 if not enableGrapevineAuth % force resultis ecUserName
	 
	 // set up a blank DIF that perhaps will be validated by Grapevine.
	 dif = SysAllocateZero(lenDIF)
         dif>>DIF.nonexistentDIF = true
	 difName = MakeQualifiedRName(name)
	 ]

      unless force do
         [
         if dif>>DIF.filesOnly resultis ecFilesOnly
         let ec = ValidateDIF(dif, difName, password)
	 if ec ne 0 resultis ec
         ]

      // successful login.  Update names in UserInfo block.
      // userName is fully qualified except in the case of a name for
      // which a local DIF exists but which Grapevine never heard of.
      // connName always matches the local DIF (if there is one).
      FreePointer(lv ui>>UserInfo.userName, lv ui>>UserInfo.connName,
       lv ui>>UserInfo.defaultDir)
      ui>>UserInfo.userName = dif>>DIF.validGrapevineRName?
       MakeQualifiedRName(difName), ExtractSubstring(difName)
      ui>>UserInfo.connName = difName
      difName = 0

      if enablePasswordLog & not force then
         [  // Password logging hack
         let s = WheelCall(IFSOpenFile, "<System>Ifs.test", 0, modeAppend)
         if s ne 0 then
            [
            if FilePos(s) eq 0 then
	       WheelCall(ChangeFileAttributes, StreamsFD(s), ZeroAttr)
            PutTemplate(s, "$S $S*n", ui>>UserInfo.userName, password)
            Closes(s)
            ]
         ]

      ui>>UserInfo.capabilities = dif>>DIF.capabilities
      MoveBlock(lv ui>>UserInfo.userGroups, lv dif>>DIF.userGroups,
       lenProtection)
      resultis 0
      ]

   FreePointer(lv difName, lv dif)
   ]

resultis res
]

//---------------------------------------------------------------------------
and Connect(name, password, ui) = valof
//---------------------------------------------------------------------------
// name = 0 or name.length = 0 means connect to the user's login directory.
// Returns: 0 if ok, an error code otherwise.
// Updates connName in UserInfo structure iff successful.
// Checks password only if something has changed.
[
if name eq 0 % name>>String.length eq 0 then name = ui>>UserInfo.userName
let res = 0

unless RNamesEqual(name, ui>>UserInfo.connName) do
   [
   let difName = 0
   let dif = GetDIF(name, false, lv difName)

   res = valof
      [
      if dif eq 0 % dif>>DIF.nonexistentDIF then
         // permit connecting to nonexistent DIF only if Grapevine
	 // authentication is enabled and the connect name matches
	 // the login name.
         unless RNamesEqual(difName, ui>>UserInfo.userName) do
            resultis ecConnectName

      // Permit connect to succeed if:
      // (1) connecting to login directory;
      // (2) user is owner of directory;
      // (3) user is member of connect groups of directory; or
      // (4) password is the correct one for the directory.
      unless RNamesEqual(difName, ui>>UserInfo.userName) %  // (1)
       RNamesEqual(ui>>UserInfo.userName, lv dif>>DIF.owner) %  // (2)
       CheckAccess(lv dif>>DIF.connectProt, false) %  // (3)
       ValidateDIF(dif, difName, password) eq 0 do  // (4)
         resultis ecConnectPassword

      // success.  Install actual DIF name (if it exists) as connect name.
      SysFree(ui>>UserInfo.connName)
      ui>>UserInfo.connName = difName
      difName = 0
      resultis 0
      ]

   FreePointer(lv dif, lv difName)
   ]
  
resultis res
]

//---------------------------------------------------------------------------
and ZeroAttr(fd, ld, nil) = valof
//---------------------------------------------------------------------------
[
Zero(lv ld>>ILD.fileProt, lenFileProt)
ld>>ILD.noBackup = true
resultis true
]

//---------------------------------------------------------------------------
and ValidateDIF(dif, name, password) = valof
//---------------------------------------------------------------------------
// Interacts with Grapevine, if appropriate, to verify that name and
// password are (still) authentic.  If necessary, updates the DIF.
// To call ValidateDIF for a nonexistent local directory, the caller must
// construct a blank DIF -- a block of length lenDIF whose contents are all
// zero.   A nonexistent DIF is recognizable by its nonexistentDIF bit
// being true.  In this case, if validation is successful, the password is
// filled in and the DIF is inserted into the cache.
// Returns zero if successful, or an error code describing the outcome:
//   ecUserName		DIF is nonexistent and Grapevine never heard of name.
//   ecUnknownUserName	DIF is nonexistent and Grapevine is unresponsive.
//   ecUserPassword	Either Grapevine rejected the password, or Grapevine
//			is unresponsive and the password does not match DIF.
[
if password eq 0 % password>>String.length eq 0 resultis ecUserPassword
let difPassword = lv dif>>DIF.password
let localPwdOk = difPassword!0 ne 0 & Password(password, difPassword, false)

if enableGrapevineAuth then
   [
   let time = vec 1; ReadCalendar(time)
   DoubleSubtract(time, table [ 0; 124300B ])  // 12 hours
   unless localPwdOk & DoubleUsc(lv dif>>DIF.timeLastValid, time) gr 0 do
      [ // password wrong or DIF is stale -- time to ask Grapevine again
      let key = vec lenPassword
      MakeKey(password, key)
      let rName = MakeQualifiedRName(name)
      let ec = Authenticate(rName, key)
      SysFree(rName)
      switchon ec into
         [
         case ecIndividual:
	    // successfully authenticated -- update the DIF
            Password(password, difPassword, true)
	    ReadCalendar(lv dif>>DIF.timeLastValid)
	    dif>>DIF.validGrapevineRName = true
	    if enableGrapevineGroup then
	       // forget user group membership -- require it to be
	       // determined afresh from Grapevine when needed.
	       Zero(lv dif>>DIF.userGroups, lenProtection)
	    UpdateCachedDIF(name, dif)
	    resultis 0

         case ecBadPassword:
            resultis ecUserPassword

         case ecBadRName:
	    if localPwdOk then
	       [
	       // Grapevine doesn't know about this name, but we do and
	       // the password matches the local DIF.
	       ReadCalendar(lv dif>>DIF.timeLastValid)
	       dif>>DIF.validGrapevineRName = false
	       UpdateCachedDIF(name, dif)
	       ]
	    endcase

	 // case ecAllDown:
	 default:
	    // depend on the information in the local DIF, but do not
	    // update its timeLastValid.
	    if difPassword!0 eq 0 resultis ecUnknownUserName
         ]
      ]
   ]
   
resultis localPwdOk? 0, difPassword!0 eq 0? ecUserName, ecUserPassword
]