! Authority server for new-style filestores/file access, GDMR, July 1988

%option "-NoCheck-NoTrace-NoDiag-NoLine"

! The processes in this module are responsible for maintaining the password
! database, for issuing user tokens, and for identifying the users associated
! with given tokens.

%ownstring(31) database name = "$Authority"
%constinteger user token limit = 32
%constinteger processes = 3
%constinteger priority = 5
%constinteger size = 15360

%include "Moose:Mouse.Inc"
%include "GDMR_H:FSysAcc.Inc"
%include "GDMR_H:Dir.Inc"
%include "GDMR_H:Tree.Inc"
%include "GDMR_H:DateTime.Inc"

%include "GDMR_H:Auth.Inc"

%externalroutinespec FS insert(%string(31) name, %integer value)
%externalpredicatespec FS lookup(%string(31) name, %integername value)

%systemroutinespec phex(%integer i)
%systemintegerfnspec free store

%constinteger mask = 16_7FFFFFFF
%constinteger priv = 16_80000000

%routine pdate
   %string(31) d, t
      unpack date(get datestamp, d, t)
      printstring(d);  space;  printstring(t);  spaces(2)
%end

%predicate contains colons(%string(*)%name s)
   %integer i
      %false %if s = ""
      %for i = 1, 1, length(s) %cycle
         %true %if charno(s, i) = ':'
      %repeat
      %false
%end


! Token format, hidden from the outside (**TEMPORARY** version)

%recordformat user token fm(%integer tag, flags,
                            %string(31) user, domain,
                            %integer local value,
                            %integer EUCSD UID, EUCSD GID,
                            %integer LFCS UID, LFCS GID)
! Nasty hack, which is OK on 68000 and 68010: because only the bottom
! 24 bits are used in addresses we can use the top 8 bits to encode
! a sequence number.....

%ownrecord(user token fm)%array user tokens(1 : user token limit) = 0(*)
%ownrecord(semaphore fm) user token semaphore = 0

%record(user token fm)%map allocate token(%string(31) user, domain)
   %owninteger token sequence = 0
   %record(user token fm)%name t
   %integer i, ch, colon
      %result == nil %if user = "" %or domain = ""
      colon = 0
      %for i = length(user), -1, 1 %cycle
         colon = i %and %exit %if charno(user, i) = ':'
      %repeat
      %result == nil %if colon = length(user)
      semaphore wait(user token semaphore)
      %for i = 1, 1, user token limit %cycle
         t == user tokens(i)
         %if t_tag = 0 %start
            token sequence <- token sequence + 16_01000000
            t_tag = addr(t) ! token sequence
            t_flags = 0
            t_user = sub string(user, colon + 1, length(user))
            t_domain = domain
            signal semaphore(user token semaphore)
            !! printstring("Allocated token ");  phex(t_tag)
            !! printstring(" for ");  printstring(t_user)
            !! printstring(" at ");  printstring(domain);  newline
            %result == t
         %finish
      %repeat
      signal semaphore(user token semaphore)
      %result == nil
%end


! Password encryption, based on ECMA CRC from Gordon.

%integerfn encrypt(%string(255) user, password)
   %integer rem, p

   %routine include(%byte b)
      %integer i, flag
         b = b - 'a' + 'A' %if 'a' <= b <= 'z'
         %for i = 0, 1, 7 %cycle
            flag = rem
            rem = rem<<1 ! b&1
            rem = rem !! 2_00000100110000010001110110110111 %if flag < 0
            b = b>>1
         %repeat
   %end

      !! printstring("Encrypt: """);  zprintstring(user)
      !! printstring(""" """);  zprintstring(password)
      !! print symbol('"');  newline
      rem = 16_FFFFFFFF
      include(charno(user, p)) %for p = 1, 1, length(user)
      %if password # "" %start
         include(charno(password, p)) %for p = 1, 1, length(password)
      %finish
      !! printstring("Encrypted to ");  phex(rem);  newline
      %result = rem & mask
%end


! Case-independent username comparison

%predicate same(%string(*)%name s, t)
   %integer i, a, b
      %false %unless length(s) = length(t)
      %for i = 1, 1, length(s) %cycle
         a = charno(s, i);  a = a - 'a' + 'A' %if 'a' <= a <= 'z'
         b = charno(t, i);  b = b - 'a' + 'A' %if 'a' <= b <= 'z'
         %false %unless a = b
      %repeat
      %true
%end


! Token -> username conversion.

%owninteger default token = 0
%ownintegername default == nil

%predicate set default
   %integer x
      %if FS lookup("AUTHORITY_DEFAULT_TOKEN", x) %start
         default == integer(x)
         !! printstring("Default token at ");  phex(x);  newline
         %true
      %else
         %false
      %finish
%end

%externalpredicate authority identify user(%integer token, %string(*)%name user)
   %record(user token fm)%name t
      %on 0 %start;  %false;  %finish
      %false %unless token & 1 = 0;  ! Must be aligned
      %if token = 0 %start
         %if default == nil %start
            %false %unless set default
         %finish
         token = default
      %finish
      t == record(token & 16_00FFFFFF)
      %false %if t_tag # token
      !! printstring("Identify user: token ");  phex(token)
      !! printstring(", user ");  printstring(t_user)
      !! printstring(" at ");  printstring(t_domain)
      !! printstring(", local value ");  phex(t_local value);  newline
      user = t_user
      %true
%end

%externalpredicate authority local identify user(%integer token,
                                                 %integername value)
   %record(user token fm)%name t
      %on 0 %start;  %false;  %finish
      %false %unless token & 1 = 0;  ! Must be aligned
      %if token = 0 %start
         %if default == nil %start
            %false %unless set default
         %finish
         token = default
      %finish
      t == record(token & 16_00FFFFFF)
      %if t ## nil %start
         !! printstring("Local identify user: token ");  phex(token)
         !! printstring(", tag ");  phex(t_tag)
         !! printstring(", user ");  printstring(t_user)
         !! printstring(" at ");  printstring(t_domain)
         !! printstring(", local value ");  phex(t_local value);  newline
         %false %if t_tag # token
         value = t_local value
         %true
      %finish
      !! printstring("Local identify: null token");  newline
      %false
%end

%externalpredicate authority local set user(%integer token, value)
   %record(user token fm)%name t
      %on 0 %start;  %false;  %finish
      %false %unless token & 1 = 0;  ! Must be aligned
      %if token = 0 %start
         %if default == nil %start
            %false %unless set default
         %finish
         token = default
      %finish
      t == record(token & 16_00FFFFFF)
      %if t ## nil %start
         !! printstring("Local set user: token ");  phex(token)
         !! printstring(", tag ");  phex(t_tag)
         !! printstring(", user ");  printstring(t_user)
         !! printstring(" at ");  printstring(t_domain)
         !! printstring(", local value ");  phex(value);  newline
         %false %if t_tag # token
         t_local value = value
         %true
      %finish
      %false
%end


! YP access

%recordformat YP request fm(%record(message fm) system part,
                            %integer flags,
                            %string(15) user, domain,
                            %string(*)%name data)
! "" data implies failure
%ownrecord(mailbox fm)%name YP box == nil

%externalpredicatespec check BSD password(%string(31) pass, target)

%integerfn YP validate user by domain(%string(31) user, pass, domain)
   %record(semaphore fm) sem = 0
   %record(mailbox fm) box = 0
   %record(YP request fm) req = 0
   %record(YP request fm)%name rep
   %string(255) data = "", encr
   %integer x, pos
   %bytename ch
      !! printstring("YP validate ");  printstring(user)
      !! printstring(" at ");  printstring(domain);  newline
      %if YP box == nil %start
         %if FS lookup("YP_PASSWD_REQUESTS", x) %start
            !! printstring("YP mailbox at ");  phex(x);  newline
            YP box == record(x)
         %else
            !! printstring("No YP mailbox??");  newline
            %result = -1
         %finish
      %finish
      setup semaphore(sem)
      setup mailbox(box, sem)
      setup message(req, size of(req))
      req_user = user;  req_domain = domain
      req_data == data
      req_flags = 1;  ! Refresh cache
      send message(req, YP box, box)
      rep == receive message(box)
      !! printstring("YP reply:");  newline
      !! print symbol('"');  printstring(data);  print symbol('"')
      !! newline
      %result = -1 %if data = ""
      ! Extract the encrypted password
      %for x = 1, 1, length(data) %cycle
         %if charno(data, x) = ':' %start
            !! printstring("Found first ':' at ");  write(x, 0);  newline
            pos = x + 1
            %exit
         %finish
      %repeat
      encr = ""
      %for x = pos, 1, length(data) %cycle
         ch == charno(data, x)
         %exit %if ch = ':'
         encr = encr . to string(ch)
      %repeat
      !! printstring("Encrypyted pass: ");  printstring(encr);  newline
      %if encr = "" %or check BSD password(pass, encr) %start
         pdate
         printstring("Authority validate: ");  printstring(user)
         printstring(" at ");  printstring(domain)
         printstring(" OK");  newline
         %result = 0
      %else
         pdate
         printstring("Authority validate: password wrong for ")
         printstring(user);  printstring(" at ");  printstring(domain)
         newline
         %result = -1
      %finish
%end

%integerfn YP validate user(%record(authority request fm)%name r,
                            %string(*)%name domain)
   %string(127) user, xdomain
   %integer x
      domain == xdomain %if domain == nil
      %if r_user -> domain . (":") . user %start
         to upper(domain)
         domain = "+cs.ed.ac.uk" %if domain = "CS"
         %result = YP validate user by domain(user, r_pass, domain)
      %else
         x = YP validate user by domain(r_user, r_pass, "EUCSD")
         domain = "EUCSD" %and %result = 0 %if x = 0
         x = YP validate user by domain(r_user, r_pass, "LFCS")
         domain = "LFCS" %and %result = 0 %if x = 0
         %result = x
      %finish
%end


! Token -> UID/GID conversion

%externalroutine authority identify UID(%integer token, %string(15) domain,
                                        %integername xUID, xGID)
   %record(semaphore fm) sem = 0
   %record(mailbox fm) box = 0
   %record(YP request fm) req = 0
   %record(YP request fm)%name rep
   %string(255) data = ""
   %record(user token fm)%name t
   %integername UID, GID
   %integer x, pos
      %on 0 %start;  -> xdud;  %finish
      -> xdud %unless token & 1 = 0;  ! Must be aligned
      %if token = 0 %start
         %if default == nil %start
            -> xdud %unless set default
         %finish
         token = default
      %finish
      t == record(token & 16_00FFFFFF)
      -> xdud %if t_tag # token
      !! printstring("Identify UID: token ");  phex(token)
      !! printstring(", user ");  printstring(t_user)
      !! printstring(" at ");  printstring(t_domain)
      !! printstring(" in domain ");  printstring(domain);  newline
      %if domain = "+cs.ed.ac.uk" %start
         UID == t_EUCSD UID;  GID == t_EUCSD GID
      !%else %if domain = "LFCS"
      !   UID == t_LFCS UID;  GID == t_LFCS GID
      %else
         !! printstring("Unimplemented domain");  newline
         -> xdud
      %finish
      %if UID # 0 %start
         ! Already set
         xUID = UID;  xGID = GID
         %return
      %finish
      ! Not yet known
      %if YP box == nil %start
         %if FS lookup("YP_PASSWD_REQUESTS", x) %start
            !! printstring("YP mailbox at ");  phex(x);  newline
            YP box == record(x)
         %else
            !! printstring("No YP mailbox??");  newline
            -> xdud
         %finish
      %finish
      setup semaphore(sem)
      setup mailbox(box, sem)
      setup message(req, size of(req))
      req_user = t_user;  req_domain = domain
      req_data == data
      send message(req, YP box, box)
      rep == receive message(box)
      !! printstring("YP reply:");  newline
      !! print symbol('"');  printstring(data);  print symbol('"')
      !! newline
      %if data = "" %start
         ! Unknown
         UID = -2;  GID = -2
         xUID = -2;  xGID = -2
         %return
      %finish
      ! Skip to UID
      %for x = 1, 1, length(data) %cycle
         pos = x + 1 %and %exit %if charno(data, x) = ':'
      %repeat
      -> dud %if pos >= length(data)
      %for x = pos, 1, length(data) %cycle
         pos = x + 1 %and %exit %if charno(data, x) = ':'
      %repeat
      -> dud %if pos >= length(data)
      ! Extract the UID
      !! printstring("Extracting UID at ");  write(pos, 0);  newline
      UID = 0
      %for x = pos, 1, length(data) %cycle
         pos = x + 1 %and %exit %if charno(data, x) = ':'
         UID = 10 * UID + charno(data, x) - '0'
      %repeat
      -> dud %if pos >= length(data)
      ! Extract the GID
      !! printstring("Extracting GID at ");  write(pos, 0);  newline
      GID = 0
      %for x = pos, 1, length(data) %cycle
         pos = x + 1 %and %exit %if charno(data, x) = ':'
         GID = 10 * GID + charno(data, x) - '0'
      %repeat
      -> dud %if pos >= length(data)
      xUID = UID;  xGID = GID
      %return

dud:  UID = -2;  GID = -2
xdud: xUID = -2;  xGID = -2
%end


! Database access procedures

%owninteger database ID = 0
%owninteger database token = 0
%ownrecord(semaphore fm) database semaphore = 0

%routine lookup database
   %string(255) textual translation
   %integer status
      !! printstring("Auth: lookup database");  newline
      status = directory lookup one(nil, 0, 0, database name, 0,
                                    database ID, textual translation)
      %if status = 0 %start
         ! Success, so nothing more to do.
         !! printstring("Lookup: ID ");  phex(database ID);  newline
         %return
      %finish
      printstring("Failed to find authority database: ");  write(status, 0)
      printstring("; creating a new one");  newline
      status = B tree create(nil, 0, database name, 1, 0, database ID)
      %if status # 0 %start
         printstring("Create failed: ");  write(status, 0);  newline
         %return
      %finish
      status = directory insert ID(nil, 0, 16_41010002, database name, database ID)
      %if status # 0 %start
         printstring("Failed to insert entry for authority database: ")
         write(status, 0);  newline
      %finish
%end

%constinteger read = 0
%constinteger modify = 1

%integerfn open database(%integer mode)
   %integer status, flags
      semaphore wait(database semaphore)
      status = B tree open by ID(nil, 0, database ID, 1, database token, flags)
      signal semaphore(database semaphore) %if status # 0
      %result = status
%end

%constinteger OK = 0
%constinteger abort = -1

%routine close database(%integer how)
   %integer status
      status = B tree close(database token, how)
      signal semaphore(database semaphore)
%end


! Action routines, one for each request code.

%integerfn do authority add encrypted(%record(authority request fm)%name r)
   %string(127) caller
   %integer status, d pass
      status = open database(modify)
      %result = status %if status # 0
      %if B tree empty(database token) %start
         ! First user added must be privileged
         r_encrypted pass = r_encrypted pass ! priv
      %else
         %unless authority identify user(r_token, caller) %start
            ! Dud token
            close database(abort)
            %result = -4
         %finish
         !! printstring("Caller is ");  printstring(caller);  newline
         status = B tree find entry(database token, caller, d pass)
         %if status # 0 %start
            ! Unknown user in database??
            close database(abort)
            %result = -3
         %finish
         !! printstring("Encrypted password is ");  phex(d pass);  newline
         %if d pass & priv = 0 %start
            ! Must be privileged to add users
            close database(abort)
            %result = -2
         %finish
      %finish
      status = B tree add entry(database token, r_user, r_encrypted pass)
      close database(status)
      %result = status
%end

%integerfn do authority add user(%record(authority request fm)%name r)
   r_encrypted pass = encrypt(r_user, r_pass)
   %result = do authority add encrypted(r)
%end

%integerfn do authority delete user(%record(authority request fm)%name r)
   %result = -4
%end

%integerfn do authority set password(%record(authority request fm)%name r)
   %string(127) caller
   %integer status, u pass, d pass
      %result = -1 %unless authority identify user(r_token, caller)
      status = open database(modify)
      %result = status %if status # 0
      status = B tree find entry(database token, caller, d pass)
      %if status # 0 %start
         close database(status)
         %result = status
      %finish
      %if (%not same(caller, r_user)) %and d pass & priv = 0 %start
         ! Need privilege to change someone else's password
         close database(abort)
         %result = -2
      %finish
      u pass = encrypt(caller, r_pass2)
      %if u pass # d pass & mask %start
         ! Caller's supplied pass was wrong
         close database(abort)
         %result = -1
      %finish
      status = B tree find entry(database token, r_user, d pass)
      %if status # 0 %start
         ! Victim not in database
         close database(abort)
         %result = -1
      %finish
      u pass = encrypt(r_user, r_pass)
      u pass = u pass ! (d pass & priv);  ! Preserve privilege
      status = B tree modify entry(database token, r_user, u pass)
      close database(status)
      %result = status
%end

%integerfn do authority validate user(%record(authority request fm)%name r,
                                      %string(*)%name domain)
   %integer status, u pass, d pass
      %result = YP validate user(r, domain) %if contains colons(r_user)
      status = open database(read)
      %result = status %if status # 0
      status = B tree find entry(database token, r_user, d pass)
      close database(OK)
      %if status # 0 %start
         !pdate
         !printstring("Authority validate: unknown user ")
         !printstring(r_user);  newline
         !%result = status
         %result = YP validate user(r, domain)
      %finish
      !! printstring(r_user);  printstring("'s database pass is ")
      !! phex(d pass);  newline
      u pass = encrypt(r_user, r_pass)
      !! printstring("Supplied pass is ");  phex(u pass);  newline
      %if u pass # d pass & mask %start
         pdate
         printstring("Authority validate: password wrong for ")
         printstring(r_user);  newline
         %result = -1
      %finish
      pdate
      printstring("Authority validate: ");  printstring(r_user)
      printstring(" OK");  newline
      domain = "local" %if domain ## nil
      %result = 0
%end

%integerfn do authority issue token(%record(authority request fm)%name r)
   %record(user token fm)%name t
   %string(31) domain
   %integer status
      status = do authority validate user(r, domain)
      %result = status %if status # 0
      t == allocate token(r_user, domain)
      %if t == nil %start
         pdate
         printstring("Authority issue token: no free tokens")
         newline
         %result = -3
      %finish
      r_token = t_tag
      r_domain = domain %if r_domain ## nil
      %result = 0
%end

%integerfn do authority logon default(%record(authority request fm)%name r)
   %integer status
      status = do authority issue token(r)
      %result = status %if status # 0
      default token = r_token
      %result = 0
%end

%integerfn do authority void token(%record(authority request fm)%name r)
   %record(user token fm)%name t
      t == record(r_token & 16_00FFFFFF)
      %if t ## nil %start
         %result = -3 %if t_tag # r_token
         t = 0
      %finish
      %result = 0
%end

%integerfn do authority logoff default(%record(authority request fm)%name r)
   %integer status
      r_token = default token
      status = do authority void token(r)
      default token = 0
      %result = status
%end

%integerfn do authority set privilege(%record(authority request fm)%name r,
                                      %integer onoff)
   %string(127) caller
   %integer status, u pass, d pass
      %result = -1 %unless authority identify user(r_token, caller)
      %result = -3 %if same(caller, r_user);  ! Can't change self
      status = open database(modify)
      %result = status %if status # 0
      status = B tree find entry(database token, caller, d pass)
      %if status # 0 %start
         ! Caller not in database
         close database(status)
         %result = status
      %finish
      u pass = encrypt(caller, r_pass2)
      %if u pass # d pass & mask %start
         ! Caller's supplied pass was wrong
         close database(abort)
         %result = -1
      %finish
      %if d pass & priv = 0 %start
         ! Need privilege to change someone else's
         close database(abort)
         %result = -2
      %finish
      status = B tree find entry(database token, r_user, d pass)
      %if status # 0 %start
         ! Target not in database
         close database(status)
         %result = status
      %finish
      printstring(caller);  printstring(": ");  printstring("un") %if onoff = 0
      printstring("set authority privilege for ");  printstring(r_user);  newline
      %if onoff = 0 %then d pass = d pass & mask %c
                    %else d pass = d pass ! priv
      status = B tree modify entry(database token, r_user, d pass)
      close database(status)
      %result = status
%end

%ownrecord(semaphore fm) request semaphore = 0
%ownrecord(mailbox fm) request mailbox = 0

%routine authority process
   %record(authority request fm)%name r
   %switch action(authority add user : authority logoff default)
      open input(2, ":N");  select input(2)
      open output(2, ":T");  select output(2)
      %cycle
         r == receive message(request mailbox)
         !! printstring("Auth: received request type ")
         !! write(r_code, 0);  newline
         -> action(r_code) %c
            %if authority add user <= r_code <= authority logoff default
action(*):
         r_status = -1
         -> reply

action(authority add user):
         r_status = do authority add user(r)
         -> reply

action(authority add encrypted):
         r_status = do authority add encrypted(r)
         -> reply

action(authority delete user):
         r_status = do authority delete user(r)
         -> reply

action(authority set password):
         r_status = do authority set password(r)
         -> reply

action(authority validate user):
         r_status = do authority validate user(r, r_domain)
         -> reply

action(authority issue token):
         r_status = do authority issue token(r)
         -> reply

action(authority void token):
         r_status = do authority void token(r)
         -> reply

!action(authority identify user):
!         r_status = do authority identify user(r)
!         -> reply

action(authority set privilege):
         r_status = do authority set privilege(r, 1)
         -> reply

action(authority unset privilege):
         r_status = do authority set privilege(r, 0)
         -> reply

action(authority logon default):
         r_status = do authority logon default(r)
         -> reply

action(authority logoff default):
         r_status = do authority logoff default(r)
         -> reply

reply:   send message(r, r_system part_reply, nil) %if r_system part_reply ## nil
      %repeat
%end

%begin
   %record(process fm)%name p
   %integer i
   %label x
      open input(3, ":N");  select input(3)
      open output(3, ":T");  select output(3)
      setup semaphore(request semaphore)
      setup mailbox(request mailbox, request semaphore)
      FS insert(authority mailbox name, addr(request mailbox))
      FS insert("AUTHORITY_DEFAULT_TOKEN", addr(default TOKEN))
      setup semaphore(user token semaphore)
      signal semaphore(user token semaphore)
      setup semaphore(database semaphore)
      signal semaphore(database semaphore)
      lookup database
      p == create process(size, addr(x), priority, nil) %for i = 1, 1, processes - 1
      p == nil;  ! Avoid junk diagnostics
      set priority(nil, priority)
      {} printstring("Auth: ");  write(free store, 0)
      {} printstring(" free");  newline
x:    authority process
      ! Never returns...
%end %of %program
