!                     Mailer Executive



const  string  (3) version= "6a.2"

!  Requires privileges 4,8,9,10,12,18,20,24,25,26 + ACR=9

const  integer  yes= 1
const  integer  no= 0
const  integer  kent= yes
const  integer  name entry size= 36
const  integer  ad entry size= 128
const  integer  default max rnames= 3000
const  integer  abs max rnames= 10000
const  integer  max components= 30
const  integer  last component= 19
const  integer  max recipients= 512
const  integer  max messages= 1000
const  integer  max msg file size= 1024; ! Kb
const  integer  msg total kb= 2048; ! Kb
const  integer  message entry size= 176
const  integer  station entry size= 512
const  integer  name file header= 160
const  integer  hash length=1023; !must be 2**n -1
const  integer  max fsys= 99
const  integer  max server map top= 63; !max servers for one message
const  integer  ad file version no= 1
const  string  (6) mailer= "MAILER"


!  mailer flags

const  integer  bad params= 501
const  integer  duplicate component= 502
const  integer  unknown component= 503
const  integer  invalid command= 504
const  integer  no valid recipients= 505
!%constinteger too many recipients = 506
const  integer  addr table full= 507
!%constinteger name table full = 508
const  integer  illegal name= 509
const  integer  mail service closed= 510
!%constinteger recipient offline = 511
const  integer  message too long= 512
const  integer  error report= 513
const  integer  missing component= 514
const  integer  no free message descriptors= 515
!%constinteger invalid component = 516
const  integer  total message kb exceeded= 517
const  integer  cannot return report file= 518
!%constinteger message stored = 519
!%constinteger forbidden component = 520
const  integer  create file fails= 521
const  integer  user not accredited= 522
!%constinteger invalid password = 523
const  integer  rname not accredited= 524
const  integer  rname already accredited= 525
const  integer  rname belongs to another user= 526
const  integer  uncollected mail for rname= 527
const  integer  invalid after specification= 528
const  integer  not available to students= 529
const  integer  invalid rname option= 530

const  string  (15) array  err mess(507:509)= c 
   "Addr table full", "Name table full", "Illegal name"


!  mailer activities

const  integer  oper req= 20
const  integer  open fsys= 21; !from direct
const  integer  user mess= 22
const  integer  spoolr reply= 23
const  integer  return file ack= 24
const  integer  file from spoolr= 25
const  integer  alarm call= 26
const  integer  close fsys= 27
const  integer  take dbfile= 28
const  integer  take config file= 29

!  message states

const  integer  sending= 1; !record of a message file
const  integer  received= 2; !record for each outstanding recipient or remote server
!%constinteger archived = 4
const  integer  spooling= 8; !awaiting immediate ack from spoolr
const  integer  spooled= 16; !awaiting ftp complete from spoolr
const  integer  waiting= 32; !after dt delivery, no recip descriptors
const  integer  unused= 0
const  integer  outbound=received!spooling!spooled

const  string  (8) addrfile= "ADDRFILE"
const  string  (8) snamefile= "NAMEFIL"
const  string  (10) addrbackup= "ADDRBACKUP"
const  string  (6) dbfile= "DBFILE"
const  string  (5) newdbfile= "DBOUT"
const  string  (10) conf backup= "CONFBACKUP"
const  string  (1) snl= "
"

!  message components

const  string  (13) array  uc comp name(1:last component)= c 
"",             "SUBJECT",      "FROM",         "DATE",         "MESSAGEID",
 "TO",           "CC",           "BCC",          "SENDER",       "AFTER",
 "INREPLYTO",    "REPLYTO",      "KEYWORDS",     "FOLDER",       "REFERENCES",
 "COMMENTS",     "ACKNOWLEDGETO","",             "VIA"

const  string  (16) array  lc comp name(1:max components)= "
",              "Subject: ",    "From: ",       "Date: ",      "Message-ID: ",
 "To: ",         "cc: ",         "bcc: ",        "Sender: ",    "After: ",
 "In-reply-to: ","Reply-to: ",   "Keywords: ",   "Folder: ",    "References: ",
 "Comments: ",   "Acknowledge-to: ","Via: ",     "Via: ",       "Via: ",
 "Via: ",        "Via: ",        "Via: ",        "Source-key: ", "",
 "",             "",             "",             "",             ""

!   Order of details in message header

const  integer  body= 1, subject = 2, from = 3, cdate = 4, messid = 5
const  integer  to= 6, cc = 7, bcc = 8, sender = 9, after = 10
const  integer  in reply to= 11, replyto = 12, keywords = 13, folder = 14
const  integer  references= 15, comments = 16, ack to = 17, via = 18
const  integer  via2= 19, via3 = 20, via4 = 21, via5 = 22, via6 = 23
const  integer  source key= 24, user1 = 25, user2 = 26, user3 = 27, user4 = 28,
 user5 = 29, user6 = 30

!   Order of components in a message

const  integer  array  order(1:max components)= c 
source key, via,via2, via3, via4, via5, via6, cdate,from, subject,  sender,
 to, cc,
 bcc,reply to,comments, mess id, in reply to,  references,  keywords,
 folder, ack to, after, user1, user2, user3, user4, user5, user6, body

const  integer  acomf= x'80000000'+48<<18; !address of communications record
const  integer  not assigned= x'80808080'; !internal unassigned pattern
const  integer  max via fields= 5
const  integer  max user fields= 6
const  integer  already exists= 16; !director flag
const  integer  does not exist= 32; !director flag
const  integer  fsys not available= 23; !director flag
const  integer  user not known= 37; !director flag
const  integer  process na= 61; !director flag
const  integer  file header size= 32; !ss standard file header size
const  integer  r= b'00000001'; !read permission
const  integer  w= b'00000010'; !write permission
const  integer  shared= b'00001000'; !allow others access
const  integer  zerod= b'00000100'; !zero file on creation
const  integer  tempfi= b'00000001'; !temp file on creation
const  integer  cherish= 8; !on creation
const  integer  noarch= 17; !dfstatus arch inhibit entry
const  integer  set cherish= 1; !dfstatus entry
const  integer  get index list= 8; !dperm entry
const  integer  get sfi surname= 18; !dsfi entry
const  integer  get last logon= 6; !dsfi entry
const  integer  get config name= 2; !dsfi entry
const  integer  msg indicator= 43; !dsfi entry
const  integer  ss char type= 3; !subsystem char filetype
const  integer  secsin24hrs= 86400; ! secs in day
const  integer  days70= 25567; ! days from jan1 1900 to jan1 1970
const  long  integer  secs70=x'0000000083AA7E80'; ! secs ditto
const  integer  secs in year= x'1E13380'
const  integer  after limit= secs in year; !use for 'after' messages
const  integer  default return period= secs in 24hrs*100; !return to sender in 3 months
const  integer  junk return period= secs in 24hrs*7; !one week for junk mail
const  integer  max outstanding= 100; !before forcing user to accept
const  integer  apf= x'19A'; !w at 9, r at 10
const  integer  open= 1
const  integer  closed= 0
const  integer  tell= 1
const  integer  dont tell= 0
const  integer  spoolr requeue= 1
const  integer  spoolr delete= 0
const  integer  report=yes
const  integer  no report=no
const  integer  ok= 0
const  integer  not found= 0
const  integer  max reply index= 127; !for activity of reply from spoolr
const  integer  spool log reply=(max reply index+1)<<8!spoolr reply
const  integer  elapsed int= x'000A0002'; !elapsed interval timer service
const  integer  local= 1; !origin of message
const  integer  remote= 2; !origin of message
const  integer  sfioption= 1; !set if addr table entry = sfi surname
const  integer  alias option= 2; !set if entry is an alias for user
const  integer  dlist option= 3; !set if entry is a distribution list
const  integer  bboard option= 4; !set if entry is a bulletin board
const  integer  max dlists= 255
!%constinteger open dlist option = b'00001000'
!%constinteger member dlist option = b'00010000'
const  integer  any vias= 1<<via!1<<via2!1<<via3!1<<via4!1<<via5!1<<via6
const  integer  reqd local comp=1<<comments!1<<body
const  integer  valid local comp=~(1<<c date!1<<source key!1<<mess id!1<<sender c 
!any vias)
const  integer  originator comp= 1<<from!1<<sender!1<<reply to

const  integer  add viastring= 255; !qualifiers for 'process recipients'
const  integer  jnt header= 254
const  integer  ignore route= 253
const  integer  flag bcc= 252

const  integer  mailer ack= 1; !types of msg mailer sends
const  integer  mailer report= 2
const  integer  mailer returned msg= 3
const  integer  mailer dead letter= 4
const  integer  mailer ftp failure= 5

const  integer  update flag=1; !items in the station_flags field
const  integer  update copy flag=2
const  integer  route flag=4
const  integer  this auth flag=8
const  integer  this host flag=16
const  integer  local host flag= 32
const  integer  mail service offered=2; !in station_services

const  string  (6) this ukac= "UK.AC."
const  string  (3) array  month(1:12)= c 
   "Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"

! monitoring:
!     2**0 = poffs + cpu/pts
!     2**1 = viastrings (for reports)
!     2**2 = calls on 'process recipients'
!     2**3 = log descriptors (in 'check descriptors')

!  communications record format - extant from chopsupe 20a onwards
record  format  comf(byte  integer  b1, b2, b3, ocp type, integer  ipldev, sblks, sepgs, ndiscs, ddtaddr,
    gpctabsize, gpca, sfctabsize, sfca, sfck, dirsite, dcodeda, suplvn, wasklokcorrect, date0, date1, date2, time0,
    time1, time2, epagesize, users, cattad, dqaddr, byte  integer  nsacs, resv1, sacport1, sacport0, nocps,
    systype, ocpport1, ocpport0, integer  itint, contypea, gpcconfa, fpcconfa, sfcconfa, blkaddr, dptaddr, smacs,
    trans, long  integer  kmon, integer  ditaddr, smacpos, supvsn, pstva, secsfrmn, secstocd, sync1dest, sync2dest,
    asyncdest, maxprocs, inspersec, elaphead, commsreca, storeaad, procaad, sfcctad, drumtad, tslice, sp0, sp1,
    sp2, sp3, sp4, sp5, sp6, sp7, sp8, lstl, lstb, pstl, pstb, hkeys, hoot, sim, clkx, clky, clkz, hbit, slaveoff,
    inhssr, sdr1, sdr2, sdr3, sdr4, sesr, hoffbit, s2, s3, s4, end)
!systype 0 = p series    -     #0 = s series

record  format  finff(integer  nkb, rup, eep, apf, use, arch, fsys, conseg, cct, codes, codes2, ssbyte,
    string  (6) offer)

record  format  archf(string  (11) name, integer  kbytes, string  (8) date, string  (6) tape, integer  chapter,
    flags)

record  format  c tab f(integer  beg, len)

record  format  cf(integer  dest, srce, string  (23) s)

record  format  pe(integer  dest, srce, p1, p2, p3, p4, p5, p6)

record  format  pf(integer  dest, srce, string  (7) user, integer  p3, p4, p5, p6)

record  format  fhf(integer  end, start, size, type, free hole, datetime, sp0, version, sp1, bitcomp,
    record  (c tab f) array  cp(1:max components))

record  format  ad file f(integer  end, start, size, type, free hole, datetime, entries, version, anon link)

record  format  msg descriptor f(string  (31) rname, string  (15) managr, server, string  (47) mess id,
    integer  dt sent, dt told, dt received, dt spooled, dt delivered, dt deleted, dt after, status, ident,
    recip link, r type, rname link, sp1, sp2, sp3, sp4)

record  format  pointers f(integer  link list displ, ftp table displ, queues, queue entry size, queue displ,
    queue name displ, streams, stream entry size, stream displ, remotes, remote entry size, remote displ,
    remote name displ, stations, station entry size, station displ, station name displ, station addresses displ,
    guest no, byte  integer  array  discs(0:max fsys), string  (63) dead letters, this full host,
    integer  hash len, integer  array  hash t(0:hash length))

record  format  ftp station f((byte  integer  max lines or  c 
   byte  integer  max ftp lines), byte  integer  status, (byte  integer  service or  c 
   byte  integer  ftp service), byte  integer  connect retry ptr, fep, address type, services,
    (byte  integer  q lines or  byte  integer  ftp q lines), (integer  limit or  c 
   integer  ftp limit), integer  last call, last response, system loaded, connect attempts, connect retry time,
    integer  array  ispare(0:4), integer  seconds, bytes, integer  last q response by us, p transfers, q transfers,
    p kb, q kb, p mail, q mail, integer  name, shortest name, integer  array  address(1:4), integer  pss entry,
    integer  mail, integer  ftp, integer  description, (integer  queue or  c 
   integer  route), integer  flags,
    byte  integer  array  string space(0:375) {decrement this if more fields added, keep to 512 total})

record  format  server map f(integer  servno, half  integer  rtable pointer, byte  integer  flags, bcc,
    string  (127) name, string  (15) short name)

record  format  recip table f(string  (127) rname, string  (15) managr, integer  type, entry, fsys, link)

record  format  addr entry f(string  (31) rname, department, string  (7) password, string  (15) server, managr,
    integer  timestamp, options, dt last told, fsys, dlist index, link)

record  format  name entry f(string  (31) rname, integer  soundex)

record  format  name file f(integer  end, start, size, type, free, datetime, sp0, version,
    integer  array  startchar('A':'['), integer  extrastart, extraend, sp1, sp2, sp3)

record  format  usf(string  (6) user, byte  integer  nkb, integer  indno)

record  format  i prms f(string  (6) user, byte  integer  uprm)

record  format  permf(integer  bytes, ownp, eep, spare, record  (i prms f) array  i prms(0:15))

record  format  reqf(integer  dest, srce, flag, string  (6) user, file, integer  p6)

record  format  repf(integer  dest, srce, byte  integer  flag, string  (6) file, string  (15) ftp source)

record  format  dtablef(string  (31) dname, integer  offset, length)

record  format  dbf(integer  end, start, size, filetype, sp0, dt, format, sp1, string  (6) bowner,
    integer  n boards, n dlists, board offset, sp2, string  (255) jobtext,
    record  (dtablef) array  dtab(1:max dlists))


system  routine  spec  move(integer  length, from, to)
system  routine  spec  fill(integer  length, from, filler)
external  string  fn  spec  derrs(integer  flag)
external  integer  fn  spec  dexecmess(string  (6) user, integer  sact, len, adr)
external  integer  fn  spec  dsfi(string  (6) user, integer  fsys, type, set, address)
external  integer  fn  spec  change context
external  integer  fn  spec  dpon2(string  (6) user, record  (pe) name  p, integer  msgtype, outno)
external  routine  spec  dpoff(record  (pe) name  p)
external  integer  fn  spec  dchsize(string  (6) user, string  (11) file, integer  fsys, newsize)
external  routine  spec  dprintstring(string  (255) s)
external  routine  spec  get av fsys(integer  name  n, integer  array  name  a)
external  integer  fn  spec  get usnames2(record  (usf) array  name  nn, integer  name  n, integer  fsys)
external  integer  fn  spec  dfsys(string  (6) user, integer  name  fsys)
external  integer  fn  spec  dpermission(string  (6) owner, user, string  (8) date, string  (11) file,
    integer  fsys, type, adrprm)
external  integer  fn  spec  ddestroy(string  (6) user, string  (11) file, string  (8) date, integer  fsys, type)
external  integer  fn  spec  ddisconnect(string  (6) user, string  (11) file, integer  fsys, destroy)
external  integer  fn  spec  drename(string  (6) user, string  (11) oldname, newname, integer  fsys)
external  integer  fn  spec  dfstatus(string  (6) user, string  (11) file, integer  fsys, act, value)
external  integer  fn  spec  dfilenames(string  (6) user, record  (archf) array  name  inf, integer  name  filenum,
    maxrec, nfiles, integer  fsys, type)
external  integer  fn  spec  dfinfo(string  (6) user, string  (11) file, integer  fsys, address)
external  integer  fn  spec  dcreate(string  (6) user, string  (11) file, integer  fsys, nkb, type)
external  integer  fn  spec  dconnect(string  (6) user, string  (11) file, integer  fsys, mode, apf,
    integer  name  seg, gap)
external  integer  fn  spec  dmessage(string  (6) user, integer  name  l, integer  act, fsys, adr)
external  integer  fn  spec  dnewgen(string  (6) user, string  (11) file, newgen of file, integer  fsys)
external  integer  fn  spec  dsubmit(record  (pe) name  p, integer  len, ad, sact, string  (6) user)
external  integer  fn  spec  dtransfer(string  (6) user1, user2, string  (11) file, newname, integer  fsys1, fsys2,
    type)



external  routine  spec  dump(integer  start, finish, conad)
external  string  fn  spec  h to s(integer  value, places)
external  string  fn  spec  i to s(integer  value)
external  integer  fn  spec  s to i(string  name  s)
external  routine  spec  log print(string  (255) s)
external  routine  spec  pt rec(record  (pe) name  p)
external  routine  spec  send and define(integer  strm, size, string  (15) q)


!  e x t e r n a l  v a r i a b l e s
!  - - - - - - - -  - - - - - - - - -
extrinsic  integer  com36; !address of restart registers
!%extrinsicinteger oper no
!current oper output console
extrinsic  integer  my fsys; !mailer file system
extrinsic  integer  my service number; !mailer service number
extrinsic  string  (6) my name; !mailer username





external  routine  control(integer  rtable conad)


   integer  temp, bitcomp, monitoring, e page size, db conad, last fsys
   integer  rtable top, mailer state, report conad, after linkhead
   integer  message count, secs now, report fsys, bad bitcomp
   integer  name file conad, addr file conad, max rnames
   integer  report full, top spoolr reply, ad ltou trans, discarded entry
   integer  server map top, config conad, station offset, stat space offset
   string  (255) viastring
   string  (31) current configfile
   string  (11) user reportfile
   string  (8) name  date, time
   string  (31) this host, this lc host; !used in originator fields
   string  (15) this short host; !used in ADDR FILE and MSG_SERVER
   string  (31) this institution, this lc institution; !the name of the institution directory
   string  (3) root filename
   string  (3) time zone

   record  (ad file f) name  ad file
   record  (comf) name  com
   record  (name entry f) array  format  name table arf(1:abs max rnames)
   record  (name entry f) array  name  n table
   record  (name file f) name  name file
   record  (recip table f) array  format  rtable arf(1:max recipients)
   integer  array  name  startchar
   integer  array  f systems(0:max fsys)
   record  (recip table f) array  name  rtable
   record  (fhf) name  report file header
   record  (pointers f) name  pointers
   record  (ftp station f) name  station
   integer  array  name  hasht
   record  (dbf) name  db
   integer  array  spool reply index(0:max reply index)
   integer  array  msg cbeg(1:max components); !for start addrs of components in a msg
   integer  array  msg clen(1:max components); !for component lengths
   record  (server map f) array  server map(0:max server map top)

   routine  spec  switchgear
   routine  spec  initialise
   routine  spec  append to report(integer  conad, len)
   routine  spec  compose remote message(integer  ident, posn, nkb, bits, integer  name  flag)
   integer  fn  spec  connect config file(string  (31) filename)
   routine  spec  connect or create(string  (6) user, string  (11) file, integer  fsys, size, flags,
       integer  name  caddr)
   routine  spec  connect tables
   routine  spec  create maillist(integer  fsys)
   routine  spec  detachjob for accept(string  (6) user, integer  fsys, string  (255) text)
   routine  spec  discredit entry(integer  entry no)
   integer  fn  spec  accredit name(string  (31) rname, department, string  (15) server, managr, integer  options,
       fsys, secs)
   routine  spec  dispatch to remote(integer  ident, string  (127) remote, string  (6) user, integer  name  flag)
   routine  spec  close file system(integer  fsys)
   routine  spec  deliver after message
   routine  spec  put component(integer  comp no, string  name  place)
   integer  fn  spec  lookup name(string  (255) rname)
   integer  fn  spec  message addr(integer  ident)
   string  fn  spec  ident to s(integer  ident)
   string  fn  spec  compress(string  (255) rname)
   routine  spec  update tables
   routine  spec  check descriptors(integer  fsys spec)
   routine  spec  unlink msg(record  (msg descriptor f) name  s msg, integer  ident, next recip)
   routine  spec  compose message(integer  ident, bits, nkb, integer  name  flag)
   routine  spec  connect dbfile(integer  name  flag)
   integer  fn  spec  current dt in secs
   routine  spec  staticise message(integer  beg, end, origin, integer  name  flag)
   routine  spec  decode spoolr reply(integer  reply, flag)
   routine  spec  delete junk(integer  fsys spec)
   routine  spec  distribute mail(record  (msg descriptor f) name  msg, string  (6) user, integer  sending ident,
       fsys, bitcomp, size, integer  name  flag)
   routine  spec  delete message(integer  ident, integer  name  flag)
   routine  spec  link recipient(record  (msg descriptor f) name  sent msg, r msg, integer  ident, ad entry, fsys,
       tell)
   routine  spec  give messages(string  (6) user, integer  fsys, anon, integer  name  linkhead, flag)
   routine  spec  killfsys(integer  fsys)
   routine  spec  create and connect(string  (11) file, integer  fsys, nbytes, createmode, connectmode,
       integer  name  caddr)
   routine  spec  add to report(string  (255) s)
   string  fn  spec  lcstring(string  (255) s)
   integer  fn  spec  locate dlist entry(string  (255) rname)
   integer  fn  spec  lookup host(string  (127) name)
   routine  spec  mail queue(record  (reqf) name  p)
   routine  spec  mailer sends message(integer  fsys, conad, len, type, integer  name  flag)
   integer  fn  spec  msg for mailer(string  (15) srce, string  name  msg id)
   integer  fn  spec  newgen or rename(string  (11) newfile, oldfile, integer  name  conad)
   routine  spec  movefsys(integer  from fsys, to fsys)
   integer  fn  spec  next fsys
   routine  spec  open file system(integer  fsys)
   routine  spec  place alarm call
   string  fn  spec  printable(string  (255) s)
   routine  spec  process recipients(integer  from, to, type, report)
   routine  spec  process name command(string  (255) s, string  (6) user, integer  name  flag)
   routine  spec  process remote file(integer  caddr, string  name  file, string  (6) user, integer  name  flag,
       string  (127) ftp source)
   routine  spec  process user req(record  (pf) name  p)
   routine  spec  process oper req(record  (cf) name  p)
   routine  spec  process mail command(string  (255) s, string  (6) user, integer  name  ident, flag)
   string  fn  spec  dt
   routine  spec  table sort(integer  n)
   routine  spec  relink new rnames
   routine  spec  reset message count
   routine  spec  retell recipients
   routine  spec  return report(string  (6) user, string  (11) reportfile, integer  fsys, integer  name  flag)
   routine  spec  return old messages(string  (31) managr, integer  return period)
   routine  spec  return to sender(record  (msg descriptor f) name  msg, integer  name  flag, integer  ident)
   string  fn  spec  check filename(string  (31) s)
   routine  spec  link after(record  (msg descriptor f) name  msg, integer  ident)
   string  fn  spec  secs to dt(integer  secs)
   routine  spec  send acknowledgement(record  (msg descriptor f) name  r msg)
   string  fn  spec  short form(string  (15) server)
   integer  fn  spec  soundex(string  (31) name)
   string  fn  spec  statstring(integer  ad)
   integer  fn  spec  s to ident(string  (255) s)
   integer  fn  spec  get next descriptor(integer  fsys)
   integer  fn  spec  student user(string  (6) user)
   routine  spec  take component(integer  component, string  name  s)
   routine  spec  tidy archive(integer  fsys spec)
   string  fn  spec  toupper(string  (255) s)
   routine  spec  update remote servers(string  (15) action, integer  loopstart, loopend)




!initial entry here
   *stln_temp; !to allow ndiags to exit from control
   com36 = temp
   temp = change context
   print string("Mailer ".version." Started".snl)
   !tell operator console we have started
   initialise; !set up tables and lists

   cycle 
      switch gear; !if we exit go round again
      send and define(1, 64, "LP"); !must have entered diags, print log
   repeat 



   routine  switch gear

!   Accepts incoming messages to MAILER and switches to the
!   appropriate routine. If any errors occur in a subsequently called
!   routine the stack is collapsed to the level of this routine and a
!   return is made from this routine.

      integer  temp, dact, pt, ms, pt1, ms1
      switch  sw(0:31); ! 1 for each activity
      record  (pe) p

      *stln_temp; !store lnb for ndiags to exit
      com36 = temp
      dact = 0; !hold last activity
      pt1 = 0
      ms1 = 0

! main loop of the mailer executive
wait:
!sit here waiting for something to do
      if  monitoring=yes start ; !is poff to be monitored
         selectoutput(1); !select log stream
         temp = dsfi(myname, myfsys, 24, 0, addr(pt))
         temp = dsfi(myname, myfsys, 28, 0, addr(ms))
         printstring(dt."Sleeping, last activity cost:  ".itos(pt-pt1)."pt, ".itos(ms-ms1)."ms".snl)
         pt1 = pt; ms1 = ms
         dpoff(p); !suspend if no params
         print string(dt."POFF ")
         pt rec(p)
         selectoutput(0); !back to oper
      finish  else  dpoff(p)
      if  dact#p_dest&31 start ; !same as previous activity?
         dact = p_dest&31
         temp = change context
      finish 
      ->sw(dact); !go do some thing

sw(oper req):
      process oper req(p)
      ->wait
sw(open fsys):
      unless  0<=p_p1<=max fsys then  printstring("Bad fsys no".snl) and  ->wait
      open file system(p_p1)
      check descriptors(p_p1) unless  mailer state=closed
      ->wait
sw(user mess):
      process user req(p)
      ->wait
sw(spoolr reply):
      log print(dt."FTRANS REPLY - Sident = ".ident to s(p_p2).", flag = ".itos(p_p1).snl)
      decode spoolr reply((p_dest>>8)&x'FF', p_p1)
      ->wait
sw(return file ack):                     !spoolr ack
sw(file from spoolr):                    !input file
      mail queue(p)
      ->wait
sw(alarm call):
      deliver after message
      ->wait
sw(close fsys):
      close file system(p_p1)
      ->wait
sw(take dbfile):                         !new dlist/bboard file
      connect dbfile(p_p1)
      p_dest = p_srce
      p_srce = my service number!take db file
      temp = dpon2("", p, 0, 6)
      ->wait
sw(take config file):
      temp = connect config file("")
      ->wait

sw(*):
      print string("BAD DACT "); pt rec(p)
      ->wait

! end of mailer executive main loop
   end ; !of routine switch gear



   string  (23) fn  dt
!      Returns the date and time in a fixed format
      result  = "DT: ".date." ".time." "
   end ; !of stringfn dt

   integer  fn  kday(integer  d, m, y)
!      returns days since 1900 given day month &year(<=99)
      if  m>2 then  m = m-3 else  m = m+9 and  y = y-1
      result  = 1461*y//4+(153*m+2)//5+d+58
   end 

   routine  kdate(integer  name  d, m, y, integer  k)
!     k is days since 1st jan 1900
!     returns d, m, y   2 digit y only
!      %integer W
!      K=K+693902
      ! DAYS SINCE CEASARS BDAY
!      W=4*K-1
!      Y=W//146097
!      K=W-146097*Y
!      D=K//4
!      K=(4*D+3)//1461
!      D=4*D+3-1461*K
!      D=(D+4)//4
!      M=(5*D-3)//153
!      D=5*D-3-153*M
!      D=(D+5)//5
!      Y=K
      *lss_k; *iad_693902
      *imy_4; *isb_1; *imdv_146097
      *lss_ tos ; *idv_4; *imy_4; *iad_3
      *imdv_1461; *st_(y)
      *lss_ tos ; *iad_4; *idv_4
      *imy_5; *isb_3; *imdv_153
      *st_(m); *lss_ tos 
      *iad_5; *idv_5; *st_(d)
      if  m<10 then  m = m+3 else  m = m-9 and  y = y+1
   end ; ! of kdate


   integer  fn  current dt in secs
!     gives current dt in new packed form
      const  long  integer  mill=1000000
      *rrtc_0; *ush_-1
      *shs_1; *ush_1
      *imdv_mill
      *isb_secs70; *stuh_ b 
!*OR_X'80000000'
      *exit_-64
   end 


   routine  decwrite2(integer  value, ad)
!     writes value as two decimal iso digits into ad and ad+1
      *lss_value; *imdv_10
      *ush_8; *iad_ tos ; *iad_x'3030'
      *lda_ad; *ldtb_x'58000002'
      *st_(dr  )
   end ; ! of decwrite2

   integer  fn  i2(integer  ad)
!   ad points to the first of a pair of decimal characters. the result
!   is the numeric value of the chas
      result  = 10*(byteinteger(ad)&x'F')+(byteinteger(ad+1)&x'F')
   end ; !of i2

   string  (19) fn  secs to dt(integer  p)
!      Converts secs to a date/time string.
      integer  h, m, at, d, mo, y, ad, secs
      string  (9) dat
      string  (8) tim
      at = addr(tim)
      tim = "00:00:00"
      *lss_p; *ush_1; *ush_-1
      *imdv_60; *imdv_60; *imdv_24
      *lss_ tos ; *st_h
      *lss_ tos ; *st_m
      *lss_ tos ; *st_secs
      decwrite2(h, at+1)
      decwrite2(m, at+4)
      decwrite2(secs, at+7)
      ad = addr(dat)
      dat = "00 XXX 00"
      p = (p&x'7FFFFFFF')//secs in 24 hrs
      kdate(d, mo, y, p+days70)
      string(ad+2) = " ".month(mo)
      decwrite2(d, ad+1)
      decwrite2(y, ad+8)
      result  = dat."  ".tim." ".time zone
   end ; !of secs to dt



   integer  fn  check and convert dt(string  (255) s)

!   Checks that a string contains a date and time and converts to secs.

      string  (255) t
      integer  i, ad, j
      s = s.t while  s->s.(" ").t
      s = toupper(s)
      if  s->s.("GMT") or  s->s.("BST") start 
      finish 
      unless  length(s)=15 then  result  = -1
      move(3, addr(s)+3, addr(t)+1)
      length(t) = 3
      t = compress(t)
      cycle  i = 12, -1, 1
         if  t=compress(month(i)) start 
            ad = addr(s)
            j = kday(i2(ad+1), i, i2(ad+6))-days70
            j = j*secsin 24 hrs; !X'80000000'
            result  = j+3600*i2(ad+8)+60*i2(ad+11)+i2(ad+14)
         finish 
      repeat 
      result  = -1
   end ; !of check and convert dt



   integer  fn  get fourth sunday(integer  month, year)
      integer  day, weekday
      day = kday(1, month, year)-days70
      weekday = (day-3)-((day-3)//7)*7
      if  weekday=0 then  day = day+21 else  day = day+28-weekday
      result  = day*secs in 24 hrs
   end ; !of get fourth sunday



   string  fn  check filename(string  (255) s)
      integer  i, j
      if  length(s)>11 or  s="" then  result  = ""
      cycle  i = length(s), -1, 1
         j = charno(s, i)
         unless  'A'<=j<='Z' or  (i#1 and  ('0'<=j<='9' or  j='#')) then  result  = ""
      repeat 
      result  = s
   end ; !of check filename



   routine  process user req(record  (pf) name  p)

!   This routine receives messages from users via DMAIL, either
!   requesting MAILSERVER or NAMESERVER operations

      record  (pe) name  pp
      string  (255) s, t
      string  (7) user
      integer  len, flag, ident, f
      p_user = "VOLUMS" if  p_user="DIRECT"
      ident = 0
      report conad = 0
      bad bitcomp = 0
      message count = 0
      secs now = current dt in secs
top:  len = 255; !max size of message prepared to accept
      flag = dmessage("", len, 0, my fsys, addr(s)+1)
      !give me next message
      if  flag=0 start 
         if  len>0 start ; !check there was a message
            length(s) = len; !set length of string
            if  s->t.("**").user.(" ").s and  s->t.(": ").s start 
               !remove info not required
               length(s) = length(s)-1 while  length(s)>0 and  charno(s, length(s))=nl
               !strip newlines
               length(user) = 6; !remove bell char from username
               log print(dt."FROM ".user." ".s.snl)
               user = "VOLUMS" if  user="DIRECT"
               if  user#p_user start 
                  log print(dt." ABOVE MESSAGE DISCARDED POFFED MESSAGE FROM ".p_user.snl)
                  ->top
               finish 
               if  s->("MAILSERVER ").s then  process mail command(s, user, ident, flag) else  start 
                  if  s->("NAMESERVER ").s then  process name command(s, user, flag) else  flag = bad params
               finish 
            finish  else  flag = bad params; !start of message invalid
         finish  else  flag = bad params; !length invalid
      finish ; !bad flag from director
      if  flag#0 start 
         log print(dt."USER MESSAGE REPLY TO ".p_user." FLAG ".i to s(flag).snl)
      finish 
      if  report conad#0 start 
         if  report conad<0 then  flag = cannot return report file else  start 
            return report(user, userreportfile, report fsys, f)
            if  f=0 then  flag = error report else  flag = f
         finish 
      finish 
      pp == p
      pp_dest = pp_srce
      pp_srce = my service number!user mess
      pp_p1 = flag
      pp_p2 = message count
      pp_p3 = secs now
      pp_p4 = ident
      pp_p5 = bad bitcomp
      flag = dpon2("", pp, 0, 6); !reply to user message received
   end ; !of  process user req



   routine  process mail command(string  (255) s, string  (6) user, integer  name  ident, flag)

!     Acts on one of the following user requests:

!     ACCEPT <root filename>,<recip names>
!     POST <msg file>,<offset>,<length>,<error filename>
!     REVOKE <mess id>
!     ENQUIRE <mess id>,<report filename>

      integer  i, seg, gap, fsys, f, entry, offset, len, j, dt after
      record  (finff) finf
      string  (31) sfi surname, date time now
      string  (63) sender name, fromname
      string  (255) param1, param2, param3, param4
      record  (addr entry f) name  addr entry
      record  (msg descriptor f) name  msg
      const  integer  max mail commands= 4
      switch  comm(1:max mail commands)
      const  string  (7) array  mail command(1:max mail commands)= c 
         "ACCEPT", "POST", "REVOKE", "ENQUIRE"

      if  mailer state=closed then  flag = mail service closed and  return 
      fsys = -1
      flag = dfsys(user, fsys)
      report fsys = fsys
      if  flag=0 start 
         if  s->s.(" ").param1 start 
            unless  param1->param1.(",").param2 then  param2 = ""
            unless  param2->param2.(",").param3 then  param3 = ""
            unless  param3->param3.(",").param4 then  param4 = ""
            cycle  i = max mail commands, -1, 1
               if  s=mail command(i) then  ->comm(i)
            repeat 
         finish  else  param1 = ""
         flag = invalid command
      finish 
      return 
comm(1):                                 !user wants his mail
      rootfilename <- param1
      if  length(rootfilename)>2 then  flag = invalid command and  return 
      until  param2="" cycle 
         if  flag#0 then  exit 
         unless  param2->param4.("+").param2 then  param4 = param2 and  param2 = ""
         entry = lookup name(param4)
         if  entry#0 start 
            addr entry == record(addr file conad+entry*ad entry size)
            if  addr entry_managr=user and  addr entry_server=this short host start 
               give messages(user, fsys, no, addr entry_link, flag)
               continue 
            finish 
         finish 
         flag = dsfi(user, fsys, getsfisurname, 0, addr(sfisurname))
         if  compress(param4)#compress(sfisurname) start 
            if  entry=0 then  flag = rname not accredited else  flag = rname belongs to another user
         finish  else  give messages(user, fsys, yes, ad file_anon link, flag)
      repeat 
      if  message count>0 start 
         i = dsfi(user, fsys, msg indicator, 0, addr(j))
         if  i=0 start 
            j = j-message count
            if  j<0 then  j = 0
            i = dsfi(user, fsys, msg indicator, 1, addr(j))
         finish 
      finish 
      return 
comm(2):                                 !user posting mail
      offset = s to i(param2)
      len = s to i(param3)
      user reportfile = checkfilename(param4)
      flag = dfinfo(user, param1, fsys, addr(finf))
      if  flag#0 then  return 
      if  user reportfile="" or  offset<0 or  len<=0 or  offset+len>finf_nkb<<10 then  c 
         flag = invalid command and  return 
      if  finf_nkb>max msg file size then  flag = message too long and  return 
      seg = 0; gap = 0
      flag = dconnect(user, param1, fsys, r, 0, seg, gap)
      if  flag=0 start 
         staticise message(seg<<18+offset, seg<<18+offset+len-1, local, flag)
         if  flag=0 and  bitcomp&reqd local comp=0 start 
            bad bitcomp = 1<<body
            flag = missing component
         finish 
         bitcomp = bitcomp&valid local comp
         cycle  i = reply to, 5, ack to
            if  bitcomp&(1<<i)#0 start ; !check syntax
               server map top = 0
               process recipients(msg cbeg(i), msg clen(i), i, report)
               if  report conad#0 or  server map top=0 start 
                  if  i=reply to then  bitcomp = bitcomp&(~(1<<reply to)) and  report conad = 0 else  c 
                     flag = error report and  add to report("    invalid Acknowledge-to address")
               finish 
            finish 
         repeat 
         if  flag=0 start 
            flag = dsfi(user, fsys, getsfisurname, 0, addr(sfisurname))
            fromname = sfisurname
            entry = lookup name(sfisurname)
            addr entry == record(addr file conad+entry*ad entry size)
            if  entry#0 and  (addr entry_managr#user or  addr entry_server#this short host) then  entry = 0
            if  bitcomp&(1<<from)=0 start ; !add "from" component
               if  entry#0 then  sender name = sfisurname." @ ".this lc institution else  c 
                  sender name = sfisurname." <".user."@".this lc host.">"
               put component(from, sender name)
            else ; !check "from" component
               take component(from, param3)
               if  param3->param3.(",").param4 start 
               finish 
               if  param3->param3.("<").param4 start 
               finish 
               param3 = printable(param3)
               if  param3#"" then  fromname = param3
               server map top = 0
               process recipients(msg cbeg(from), msg clen(from), from, report)
               if  report conad#0 or  server map top=0 start 
                  if  bitcomp&(1<<reply to)=0 then  i = 1<<reply to else  i = 1<<sender
                  report conad = 0
               else 
                  if  rtable top>1 or  rtable(server map(1)_rtable pointer)_managr#user or  c 
                     server map(1)_name#this host then  i = 1<<sender else  i = 0
               finish 
               if  i#0 start 
                  if  entry=0 then  sender name = sfisurname." <".user."@".this lc host.">" else  c 
                     sender name = sfisurname." @ ".this lc institution
                  put component(sender, sendername)
                  if  i&(1<<reply to)#0 then  put component(reply to, sendername)
               finish 
            finish 
         finish 
         if  flag=0 start 
            server map top = 0
            cycle  i = to, 1, bcc
               process recipients(msg cbeg(i), msg clen(i), i, report) unless  bitcomp&(1<<i)=0
            repeat 
            if  server map top=0 then  flag = no valid recipients else  start 
               if  rtable top*finf_nkb>msg total kb start 
                  if  report conad>0 then  add to report("Total message size exceeded") else  c 
                     flag = total message kb exceeded
               finish 
            finish 
            if  flag=0 start 
               dt after = 0
               take component(after, param2)
               if  param2#"" start 
                  dt after = check and convert dt(param2)
                  unless  0<dt after<secs now+after limit then  flag = invalid after specification
               finish 
            finish 
            if  flag=0 and  report conad#0 then  flag = error report
            if  flag=0 start 
               if  flag=0 start 
                  ident = get next descriptor(fsys)
                  if  ident#0 start 
                     msg == record(message addr(ident))
                     msg = 0
                     msg_rname <- fromname
                     msg_managr = user
                     msg_server = this short host
                     date time now = secs to dt(secs now)
                     msg_mess id = "<".date time now."  ".ident to s(ident)."@".this short host.">"
                     put component(mess id, msg_mess id)
                     msg_dt sent = secs now
                     msg_dt received = secs now
                     put component(c date, date time now)
                     if  dt after<=secs now start 
                        msg_status = sending
                        log print(dt."Message ".msg_mess id." sent by ".msg_managr.snl)
                        distribute mail(msg, user, ident, fsys, bitcomp, finf_nkb, flag)
                        if  msg_recip link=0 then  msg_status = unused
                     else ; !after specified
                        log print(dt."Message ".msg_mess id." sent by ".msg_managr." deliver: ".param2.snl)
                        msg_dt after = dt after
                        compose remote message(ident, -1, finf_nkb, bitcomp, flag)
                        if  flag=0 start 
                           msg_status = waiting
                           link after(msg, ident)
                           if  after linkhead=ident then  place alarm call
                        finish 
                     finish 
                  finish  else  flag = no free message descriptors
               finish 
            finish 
         finish 
         f = ddisconnect(user, param1, fsys, 0)
         if  f#0 start 
            log print(dt."Disconnect ".user.".".param1." fails ".derrs(flag).snl)
         finish 
      finish  else  log print(dt."Connect ".user.".".param1." fails ".derrs(flag).snl)
      if  message count>0 then  dprintstring("USER: ".user." MSGKB: ".i to s((len+1023)>>10)." MSGRECIPS: ".itos c 
         (message count).snl)
      return 
comm(3):                                 !revoke mail
comm(4):                                 !inquire
   end ; !of process mail command



   routine  staticise message(integer  beg, end, origin, integer  name  flag)

!   Analyses a sequence of bytes, resolving into a series of components.
!   Each component has its address and length recorded.
!   Variable BITCOMP indicates the components found.

      integer  pt, char, j, com, n vias, n user fields
      string  (63) c
      msg cbeg(body) = beg; !in case no components
      msg clen(body) = end-beg+1
      bitcomp = 0
      n vias = 0
      n user fields = 0
      pt = beg; c = ""
      while  pt<end cycle 
         char = byteinteger(pt)
         if  char#':' start 
            if  char=nl and  c#"" start ; !no ':' found before the nl
               if  bitcomp#0 start ; !some fields already found
                  msg cbeg(body) = beg
                  msg clen(body) = end-beg+1
               finish 
               bitcomp = bitcomp!(1<<body)
               return 
            finish 
            if  32<char<127 and  length(c)<63 then  c = c.tostring(char)
         else 
            c = compress(c); j = 0
            cycle  com = 2, 1, last component
               if  c=uc comp name(com) then  j = 1<<com and  exit 
            repeat 
            if  j=0 or  j&bitcomp#0 start 
               if  origin=local start 
                  if  j=0 then  flag = unknown component else  flag = duplicate component and  bad bitcomp = 1<<j
                  return 
               finish 
               if  j=1<<via2 and  n vias<max via fields start 
                  n vias = n vias+1
                  com = via2+nvias
               else 
                  if  n user fields<max user fields start 
                     com = user1+n user fields
                     n user fields = n user fields+1
                     pt = beg-1; !include field name
                  else 
                     bitcomp = bitcomp!(1<<body)
                     msg cbeg(body) = beg
                     msg clen(body) = end-beg+1
                     return 
                  finish 
               finish 
               j = 1<<com
            finish 
            if  byteinteger(pt+1)=' ' then  beg = pt+2 else  beg = pt+1
            pt = pt+1 until  pt>=end or  (byteinteger(pt)=nl and  pt<end and  byteinteger(pt+1)#' ')
            msg cbeg(com) = beg
            if  byteinteger(pt)=nl start 
               msg clen(com) = pt-beg
               if  pt+1<end and  byteinteger(pt+1)=nl start 
                  msg cbeg(body) = pt+2
                  msg clen(body) = end-pt-1
                  bitcomp = bitcomp!(1<<body)
                  pt = end
               finish 
            finish  else  msg clen(com) = pt-beg+1
            if  msg clen(com)>0 then  bitcomp = bitcomp!j
            beg = pt+1
            c = ""
         finish 
         pt = pt+1
      repeat 
   end ; !of staticise message



   routine  put component(integer  comp no, string  name  place)

!   Note the addition of a component to a message

      msg cbeg(comp no) = addr(place)+1
      msg clen(comp no) = length(place)
      bitcomp = bitcomp!1<<comp no
   end ; !of put component



   integer  fn  student user(string  (6) user)
      if  kent=no start 
         if  charno(user, 4)='U' then  result  = not available to students
      else 
         if  charno(user, 3)='U' or  charno(user, 3)='T' or  charno(user, 3)='L' then  c 
            result  = not available to students
      finish 
      result  = 0
   end ; !of student user



   routine  process recipients(integer  beg, len, type, report)

!      Resolves a list of recipient names and checks the validity of each name.
!      Links together recipients with the same server.
!      Expands distribution list rnames and adds each dlist name to a dummy
!      server for loop elimination.

      record  (addr entry f) name  addr entry
      string  (255) rname, lcrname, server
      string  (15) managr
      integer  pt, error, comments, tokenstate
      integer  i, entry, end, fsys, dentry, option, flag, serv flag, save report
      integer  serv no, posn, host count
      string  (127) s1, s2
      string  (202) str
      string  (31) sfisurname
      const  byte  integer  percent marker= 255
      const  integer  none allocated= -1
      const  integer  dlist map posn= 0; !in server map
      const  integer  duplicate=1
      const  integer  not duplicate=0
      const  integer  domain literal ref=0
      routine  spec  make report(string  (255) lcrname, s)


      integer  fn  next token
         integer  cl, act
         switch  sw(0:12)
         const  byte  integer  array  next state(0:10, 0:4)= c 
         0,   2,   0,   1,   0,   0,   3,   0,   3,   4,   0,
         1,   1,   1,   0,   1,   1,   1,   0,   1,   1,   1,
         2,   2,   2,   2,   2,   2,   2,   0,   2,   2,   2,
         0,   0,   0,   0,   0,   0,   3,   0,   3,   0,   0,
         4,   4,   4,   4,   4,   4,   4,   0,   4,   0,   0
!  special    (    )    "  comma  sp  rest  end  ¬    [    ]

         const  byte  integer  array  next action(0:10, 0:4)= c 
         3,   4,   8,   1,   6,   0,   1,   6,  10,   1,   8,
         1,   1,   1,   7,   1,   1,   1,   9,  10,   1,   1,
         0,   4,   5,   0,   0,   0,   0,   8,  11,   0,   0,
         2,   2,   8,   2,   2,   2,   1,   2,  10,   2,   8,
         1,   1,   1,   1,   1,   1,   1,   8,  10,   8,  12

!  States:
!             0 - scanning for char
!             1 - building quoted string
!             2 - building comment
!             3 - building string
!             4 - building domain literal

         const  byte  integer  array  adjust(0:7)= 2,255,2,255,255,1,3,4
         const  byte  integer  array  class(32:126)= c 
            5,6,3,6(2),0,6(2),1,2,6(2),4,6(13),0,4,0,6,0,6,0,6(26),9,8,10,6(33)
         str = ""
         cycle 
            pt = pt+1
            if  pt>end then  cl = 7 else  start 
               if  32<=byteinteger(pt)<=126 then  cl = class(byteinteger(pt)) else  cl = 5
            finish 
            act = next action(cl, tokenstate)
            tokenstate = nextstate(cl, tokenstate)
            ->sw(act)
sw(0):                                   !do nothing
            continue 
sw(1):                                   !add to string
            str <- str.tostring(byteinteger(pt))
            continue 
sw(2):                                   !return string
            pt = pt-1; !rescan char
            if  length(str)=2 and  to upper(str)="AT" then  result  = 2
            result  = 0; !string result
sw(3):                                   !return special
            result  = adjust((byteinteger(pt)>>1)&7)
sw(4):                                   !inc comments
            comments = comments+1
            continue 
sw(5):                                   !dec comments
            if  comments=0 then  error = 1 else  comments = comments-1
            if  comments=0 then  tokenstate = 0
            continue 
sw(6):                                   !end
            result  = 5
sw(7):                                   !add to and return
            str <- str.""""
            result  = 0
sw(8):                                   !badly nested brackets
sw(9):                                   !incomplete quoted string
            error = act-7
            continue 
sw(10):                                  !add quoted char to string
            if  pt=end then  continue 
            str <- str."¬".tostring(byteinteger(pt+1))
sw(11):                                  !skip quoted char
            pt = pt+1
            continue 
sw(12):                                  !add ']' to, and return
            str <- str."]"
            result  = 6; !domain literal
         repeat 
      end ; !of next token


      routine  get next rname
         integer  relays, state, token, act, beg
         switch  sw(0:5)
         const  byte  integer  array  pstate(0:6, 0:8)= c 
         1,    99,    99,     6,    99,     0,     99,
         1,     2,     4,     6,    99,     0,     99,
         8,    99,    99,     6,    99,     0,     99,
         3,    99,     4,    99,    99,     0,      3,
         3,    99,    99,    99,    99,    99,      3,
         5,    99,     6,    99,     7,    99,      5,
         5,    99,    99,    99,    99,    99,      5,
        99,    99,    99,    99,    99,     0,     99,
         8,    99,     4,     6,    99,     0,     99
!      string   :      @      <      >    end   dom-lit

         const  byte  integer  array  action(0:6, 0:8)= c 
        1,     5,     5,     3,     5,     4,     5,
        1,     3,     2,     3,     5,     4,     5,
        1,     5,     5,     3,     5,     4,     5,
        1,     5,     2,     5,     5,     4,     1,
        1,     5,     5,     5,     5,     5,     1,
        1,     5,     2,     5,     0,     5,     1,
        1,     5,     5,     5,     5,     5,     1,
        5,     5,     5,     5,     5,     4,     5,
        1,     5,     2,     3,     5,     4,     5

!   States:
!              0 - starting
!              1 - scanning after string
!              2 - reading group (label:)
!              3 - reading address
!              4 - string must follow (@ received)
!              5 - reading address (<> type)
!              6 - string must follow (<> type)
!              7 - reading for end (after <>)
!              8 - reading after label

         beg = pt+1
         state = 0; error = 0
         rname = ""; relays = 0
         tokenstate = 0; comments = 0
         cycle 
            token = next token
            act = action(token, state)
            state = pstate(token, state)
            ->sw(act)
sw(0):                                   !do nothing
            continue 
sw(1):                                   !add to string
            if  length(rname)+length(str)>200 then  ->sw(5)
            if  rname#"" and  relays=0 then  rname = rname." ".str else  rname = rname.str
            continue 
sw(2):                                   !note '@'
            if  relays=0 and  rname->rname.(" ").str then  rname = """".rname." ".str.""""
            rname = rname."%"
            relays = 1
            continue 
sw(3):                                   !begin again after label
            rname = ""
            continue 
sw(4):                                   !end of address
            if  error=0 then  exit 
sw(5):                                   !error found
            token = next token while  token#5; !get end
            rname = ""
            cycle  beg = beg, 1, pt
               if  byteinteger(beg)=nl then  continue 
               rname = rname.tostring(byteinteger(beg))
               if  length(rname)=65 then  rname = rname." etc." and  exit 
            repeat 
            make report("", "'".rname."'   invalid address format") if  report=yes
            error = 1
            exit 
         repeat 
      end ; !of get next rname


      string  fn  rightmost host
         integer  fin, i
         host count = host count+1; !postion within address
         fin = addr(rname)+length(rname)
         for  i = fin, -1, addr(rname)+1 cycle 
            if  byteinteger(i)#'%' then  continue 
            byteinteger(i) = fin-i
            length(rname) = length(rname)-(fin-i)-1
            result  = string(i)
         repeat 
         result  = ""
      end ; !of rightmost host


      routine  make report(string  (255) lcrname, s)
         if  report conad=0 and  type=jnt header then  add to report("Cannot deliver to -".snl)
         if  lcrname#"" start ; !change last '%'
            rname = lcrname
            server = rightmost host
            if  server#"" then  lcrname = rname." @ ".server
            lcrname = "'".lcrname."'"."   "
         finish 
         add to report("    ".lcrname.s)
      end ; !of make report


      string  fn  type to s(integer  type)
         const  string  (12) array  process type(flag bcc:add viastring)= c 
         "flag bcc: ", "originator: ", "jnt header: ", "add vias: "
         if  0<type<max components then  result  = lc comp name(type)
         if  flag bcc<=type<=add viastring then  result  = process type(type)
         result  = ": "
      end ; !of type to s


      integer  fn  lookup locally
         integer  order, f
         switch  sw(0:2)
         if  serv flag&this host flag#0 then  order = 2 else  order = 1
         cycle 
            ->sw(order)
sw(*):                                   !lookup directory
            entry = lookup name(rname)
            if  entry>0 start 
               addr entry == record(addr file conad+entry*ad entry size)
               managr = addr entry_managr
               server = addr entry_server
               rname = addr entry_rname
               fsys = addr entry_fsys
               option = addr entry_options
               result  = lookup host(server)
            finish 
            if  order#1 then  exit 
sw(2):                                   !lookup name-number tables
            rname = compress(rname); !possible change to check dsfi for perm to receive mail, rather than . .
            if  length(rname)=6 and  mailer#rname#"VOLUMS" and  "SPOOLR"#rname#"JOBBER" and  c 
               "VIEWER"#rname#"LIBRAR" and  rname#"DIRECT" then  f = dfsys(rname, fsys) else  f = user not accredited
            if  f=0 then  f = dsfi(rname, -1, getsfisurname, 0, addr(sfisurname))
            if  f=0 start 
               server = this host
               entry = lookup name(sfisurname)
               if  entry#0 start 
                  addr entry == record(addr file conad+entry*ad entry size)
                  if  rname=addr entry_managr and  addr entry_server=this short host start 
                     rname = sfisurname
                     managr = addr entry_managr
                     server = this host
                     fsys = addr entry_fsys
                  finish  else  entry = 0
               finish 
               result  = lookup host(server)
            finish 
            if  order=1 then  exit ; !we have tried both dir and nnt
            order = 0; !go back and try directory
         repeat 
         make report(lcrname, "recipient name not known") if  report=yes
         server = ""
         result  = -1
      end ; !of lookup locally

      integer  fn  check for duplicate(integer  pos)
         string  (127) crname
         integer  link
         posn = pos; !remember where
         crname = compress(rname)
         link = server map(pos)_rtable pointer
         while  link#0 cycle 
            if  managr=rtable(link)_managr and  crname=compress(rtable(link)_rname) start 
               if  type=flag bcc then  rtable(link)_type = bcc
               result  = duplicate
            finish 
            link = rtable(link)_link
         repeat 
         result  = not duplicate
      end ; !of check duplicate



      if  server map top=0 start ; !initialise
         rtable top = 0
         server map(dlist map posn)_rtable pointer = 0
      finish 
      end = beg+len-1
      pt = beg-1
      while  pt<end cycle ; !main loop of the routine
         get next rname
         if  error#0 or  rname="" then  continue 
         if  rtable top=max recipients start 
            add to report("Too many recipients - message not delivered") if  report=yes
            return 
         finish 
         if  type=add viastring start 
            if  length(rname)+length(viastring)<=255 then  rname = rname.viastring
         finish 
         serv flag = 0; !extract from station entry
         host count = 0; !1 = rightmost hostname in address
         entry = 0; !set if dir entry found
         option = 0; !type of dir entry
         fsys = -1; !fsys of user
         managr = ""; !set to userno if dir entry
         lcrname = rname; !save a lc copy
         {common host deletion possible here}
         if  monitoring&4#0 then  logprint(dt."    *".type to s(type)." ".rname)

         cycle 
            server = rightmost host
            if  server="" start ; !local part reached
               serv no = lookup locally; !translate directory entry or check userno
               if  servno<=0 then  exit ; !a bad directory entry!
            else ; !non-empty rightmost host
               serv no = lookup host(server); !if valid, is it me?
               if  serv no>0 and  station_flags&(this host flag!this auth flag)#0 start 
                  serv flag = station_flags; !remember this for use in 'lookup locally'
                  continue ; !go lookup next part
               finish 
            finish 
            if  servno>0 start 
               if  station_flags&route flag#0 and  type#ignore route start 
                  if  statstring(station_route)->s1.("*").s2 start 
                     unless  server->str.(".").server then  exit 
                     server = s1.server.s2
                  finish  else  server = statstring(station_route)
                  rname = rname."%".server
                  continue ; !repeat the process
               finish ; !not a route, so we are finished
            else ; !server not recognised
               if  host count=1 and  type=jnt header start 
                  !not so strict here
                  if  servno=domain literal ref then  continue 
                  !discard it, it means us
                  { host not known but may mean here! - try the next part on spec }
                  str = rname.tostring(percent marker).server
                  !save it
                  server = rightmost host; !after the one which brought us here
                  save report = report; report = no
                  if  server="" then  flag = lookup locally else  flag = lookup host(server)
                  report = save report
                  str -> rname.(tostring(percent marker)).server
                  !put it back
                  if  flag#-1 then  continue ; !it worked, so now do it properly
               finish 
            finish 
            exit 
         repeat 

         if  monitoring&4#0 then  logprint("  ->  ".rname." + ".server." (".itos(servno).")".snl)
         if  servno<0 start ; !invalid host
            if  length(server)=0 then  continue ; !have already reported
            length(server) = 100 if  length(server)>100
            make report(lcrname, "host name not known") if  report=yes
            continue ; !to next address
         finish 
         unless  length(rname)<=127 start 
            length(rname) = 200 if  length(rname)>200
            make report(lcrname, "recipient name too long") if  report=yes
            continue 
         finish 
         if  servno=domain literal ref start 
            if  length(server)>127 then  make report(lcrname, "host name too long") and  continue 
         else 
            if  station_services&mail service offered=0 start 
               make report(lcrname, "host does not offer a mail service") if  report=yes
               continue 
            finish 
         finish 
!  chain together rnames for the same server - also chain all dlists to check for loops
         posn = none allocated
         if  server map top=0 start 
            if  option=dlist option then  posn = dlist map posn
            !chain dlists from 0
         else 
            if  option=dlist option then  flag = check for duplicate(dlist map posn) else  start 
               flag = not duplicate
               if  servno#domain literal ref start ; !dont chain these together
                  for  i = 1, 1, server map top cycle 
                     if  servno=server map(i)_servno then  flag = check for duplicate(i) and  exit 
                  repeat 
               finish 
            finish 
            if  flag=duplicate then  continue ; !omit it
         finish 
         if  type=flag bcc then  continue ; !dont add to table
         rtable top = rtable top+1
         rtable(rtable top)_entry = entry
         rtable(rtable top)_rname = rname
         rtable(rtable top)_managr = managr
         rtable(rtable top)_fsys = fsys
         if  option=bboard option then  rtable(rtable top)_type = bboard option else  rtable(rtable top)_type = type
         if  posn=none allocated start ; !first use of this host
            if  server map top=max server map top start 
               add to report("Too many hosts addressed - message not delivered") if  report=yes
               rtable top = max recipients
               return 
            finish 
            server map top = server map top+1; !get next slot
            posn = server map top
            server map(posn)_servno = servno
            server map(posn)_bcc = 0
            if  servno=domain literal ref start 
               server map(posn)_name = server
               server map(posn)_short name = ""
               server map(posn)_flags = 0
            else 
               server map(posn)_name = statstring(station_name)
               server map(posn)_short name <- statstring(station_shortest name)
               server map(posn)_flags = station_flags
            finish 
            rtable(rtable top)_link = 0
         finish  else  rtable(rtable top)_link = server map(posn)_rtable pointer
         server map(posn)_rtable pointer = rtable top
         if  option=dlist option start 
            dentry = locate dlist entry(rname)
            if  dentry#0 start 
               process recipients(db_dtab(dentry)_offset+db conad, db_dtab(dentry)_length, type, no report)
            finish  else  printstring("Missing dlist: ".rname.snl)
         finish 
         if  type=bcc then  server map(posn)_bcc = yes
      repeat 
   end ; !of process recipients



   integer  fn  locate dlist entry(string  (255) rname)
      integer  i
      if  dbconad=0 or  db_n dlists=0 then  result  = 0
      rname = compress(rname)
      cycle  i = 1, 1, db_n dlists
         if  rname=db_dtab(i)_dname then  result  = i
      repeat 
      result  = 0
   end ; !of locate dlist entry



   string  fn  printable(string  (255) s)
      string  (255) t
      integer  i
      if  s="" then  result  = ""
      t = ""
      cycle  i = 1, 1, length(s)
         if  33<=charno(s, i)<=126 then  t = t.tostring(charno(s, i))
      repeat 
      result  = t
   end ; !of printable



   routine  distribute mail(record  (msg descriptor f) name  msg, string  (6) user, integer  sending ident, fsys,
       bitcomp, size, integer  name  flag)

!      Gets a descriptor for each local recipient of a message and for each
!      remote server, and links the descriptors by message and by recipient.

      record  (msg descriptor f) name  r msg
      integer  ident, next, bits, fl, m count, file exists, posn
      string  (127) remote
      string  (15) short remote
      flag = 0; file exists = 0
      for  posn = 1, 1, server map top cycle 
         next = server map(posn)_rtable pointer
         while  next#0 cycle 
            ident = get next descriptor(fsys)
            if  ident=0 then  flag = no free message descriptors and  return 
            r msg == record(message addr(ident))
            r msg = 0
            r msg_mess id = msg_mess id
            r msg_dt sent = msg_dt sent
            r msg_dt received = msg_dt received
            r msg_ident = sending ident
            if  server map(posn)_flags&this host flag#0 start 
               if  file exists=0 start 
                  compose message(sending ident, bitcomp&(~(1<<bcc)), size, flag)
                  if  flag#0 then  return 
                  file exists = 1
               finish 
               if  rtable(next)_managr="" start ; !not in directory
                  r msg_rname = ""
                  r msg_managr <- rtable(next)_rname
               else 
                  r msg_rname <- rtable(next)_rname
                  r msg_managr <- rtable(next)_managr
               finish 
               r msg_server = this short host
               r msg_status = received
               r msg_r type = rtable(next)_type
               fl = dsfi(r msg_managr, rtable(next)_fsys, msg indicator, 0, addr(m count))
               if  fl=0 then  m count = m count+1 and  c 
                  fl = dsfi(r msg_managr, rtable(next)_fsys, msg indicator, 1, addr(m count))
               if  fl#0 start 
                  log print(dt."DSFI43 fails ".derrs(fl).snl)
               finish 
               link recipient(msg, r msg, ident, rtable(next)_entry, rtable(next)_fsys, tell)
               log print(dt."Message ".msg_mess id." received for ".r msg_managr." at ".ident to s(ident).snl)
               if  rtable(next)_type=bboard option then  c 
                  detach job for accept(r msg_managr, rtable(next)_fsys, r msg_rname)
            else ; !send to remote server
               remote = server map(posn)_name
               short remote = server map(posn)_short name
               if  short remote="" then  short remote <- remote
               r msg_rname <- myname."@".short remote
               r msg_managr = myname
               r msg_server <- short remote
               bits = bitcomp&(~((1-server map(posn)_bcc)<<bcc))
               compose remote message(ident, posn, size, bits, flag)
               if  flag=0 start 
                  dispatch to remote(ident, remote, user, flag)
                  if  flag=0 start 
                     r msg_recip link = msg_recip link
                     msg_recip link = ident
                     if  file exists=0 start 
                        compose message(sending ident, bits, size, flag)
                        if  flag#0 then  return 
                        file exists = 1
                     finish 
                  finish  else  delete message(ident, bits)
               finish 
               if  flag=0 start 
                  log print(dt."Message ".msg_mess id." spooling to ".short remote." at ".ident to s(ident).snl)
                  r msg_status = spooling
                  message count = message count+1
               else 
                  log print(dt."Message ".msg_mess id." failure to spool to ".short remote.snl)
                  flag = create file fails
                  add to report("Failed to transmit to ".remote)
               finish 
               exit 
            finish 
            next = rtable(next)_link
         repeat 
      repeat 
   end ; !of distribute mail



   routine  link recipient(record  (msg descriptor f) name  sent msg, r msg, integer  ident, ad entry, fsys, tell)

!      Links a received message descriptor to others for the same recipient,
!      and to others for the same message.

      record  (addr entry f) name  addr entry
      string  (63) s, t
      integer  flag, l, link
      s = ""
      if  ad entry#0 start 
         addr entry == record(addr file conad+ad entry*ad entry size)
         if  addr entry_options#sfioption start ; !to an alias
            if  addr entry_link=0 and  addr entry_options=alias option then  s = "for ".addr entry_rname else  c 
               tell = no
         finish  else  s = "from ".sent msg_rname
         link = addr entry_link
         r msg_rname link = link
         addr entry_link = ident
      else 
         link = ad file_anon link
         r msg_rname link = link
         ad file_anon link = ident
         s = "from ".sent msg_rname
      finish 
!      %if tell = yes %start
!         %while link # 0 %cycle
!            msg to == record(message addr(link))
!            %if msg to_status = received %start
!               %if ad entry # 0 %or msg to_managr = r msg_managr %start
!                  msg from == record(message addr(msg to_ident))
!                  %if msg from_rname = sent msg_rname %then tell = no %and %exit
!               %finish
!            %finish %else %exit
!            link = msg to_rname link
!         %repeat
!      %finish
      r msg_recip link = sent msg_recip link
      sent msg_recip link = ident
      message count = message count+1
      if  tell=yes and  s#"" and  r msg_server=this short host start 
         s = "Message ".s
         s = s." ".t while  s->s.("  ").t
         if  ad entry#0 then  addr entry_dt last told = secs now
         l = length(s)
         flag = dmessage(r msg_managr, l, 1, fsys, addr(s)+1)
         if  0#flag#process na start 
            log print(dt."Dmessage ".r msg_managr." fails ".derrs(flag).snl)
         finish 
      finish 
   end ; !of link recipient



   routine  unlink msg(record  (msg descriptor f) name  s msg, integer  ident, next recip)
      record  (msg descriptor f) name  next msg
      integer  name  link
      link == s msg_recip link
      while  link#0 cycle 
         if  link=ident then  link = next recip and  return 
         next msg == record(message addr(link))
         link == next msg_recip link
      repeat 
      log print(dt."Failed to unlink ".ident to s(ident)." from ".s msg_mess id.snl)
   end ; !of unlink msg



   routine  compose message(integer  ident, bits, nkb, integer  name  flag)

!      Creates a message file from a series of components.

      integer  caddr, c, i, pos, next, last
      record  (fhf) name  file header
      string  (11) filename
      filename = ident to s(ident)
      create and connect(filename, ident>>24, nkb<<10, zerod!cherish, r!w, caddr)
      if  caddr=0 then  flag = create file fails and  return 
      file header == record(caddr)
      cycle  last = 1, 1, max components
         if  bits>>last=0 then  exit 
      repeat 
      file header_start = 40+last*8
      file header_end = file header_start
      cycle  next = 1, 1, maxcomponents
         c = order(next)
         if  bits>>c&1=1 start 
            i = file header_end+length(lc comp name(c))+2+msg clen(c)
            if  i>=file header_size start 
               i = ((i+epage size-1)&(-epage size))>>10
               flag = dchsize(myname, filename, ident>>24, i)
               if  flag#0 start 
                  printstring("Chsize ".myname.".".filename." fails ".derrs(flag).snl)
                  flag = ddestroy(myname, filename, "", ident>>24, 0)
                  printstring(dt."Destroy ".myname.".".filename." flag = ".derrs(flag).snl)
                  flag = create file fails
                  return 
               finish 
               file header_size = i<<10
            finish 
            pos = file header_end+caddr
            string(pos) = lc comp name(c)
            i = pos+byteinteger(pos)+1
            byteinteger(pos) = nl
            move(msg clen(c), msg cbeg(c), i)
            file header_cp(c)_beg = i-caddr
            file header_cp(c)_len = msg clen(c)
            i = i+msg clen(c)
            file header_end = i-caddr
         finish 
      repeat 
      file header_bitcomp = bits
      flag = ddisconnect(myname, filename, ident>>24, 0)
      if  flag#0 start 
         printstring("Disconnect ".myname.".".filename." fails ".derrs(flag).snl)
         flag = create file fails
      finish 
   end ; !of compose message



   routine  give messages(string  (6) user, integer  fsys, anon, integer  name  linkhead, flag)
      record  (msg descriptor f) name  msg
      record  (msg descriptor f) name  s msg
      integer  name  link
      record  (fhf) name  s header
      string  (31) bcc name
      integer  sconad, rconad, seg, gap, i, f, nkb
      link == linkhead
      while  link#0 cycle 
         msg == record(message addr(link))
         if  msg_status=received and  shortform(msg_server)=this short host and  (msg_managr=user or  anon=no) start 
            s msg == record(message addr(msg_ident))
            seg = 0; gap = 0
            flag = dconnect(myname, ident to s(msg_ident), msg_ident>>24, r, 0, seg, gap)
            if  flag=0 start 
               sconad = seg<<18
               s header == record(sconad)
               if  msg_rtype=bcc or  (s header_bitcomp>>ack to)&1=1 start 
                  fill(max components*8, addr(msg cbeg(1)), 0)
                  bitcomp = s header_bitcomp
                  cycle  i = max components, -1, 1
                     if  (bitcomp>>i)&1=1 start 
                        msg cbeg(i) = s header_cp(i)_beg+s conad
                        msg clen(i) = s header_cp(i)_len
                     finish 
                  repeat 
               finish 
               if  msg_rtype#bcc start ; !just make a copy
                  create and connect(ident to s(fsys<<24), fsys, integer(sconad), zerod!tempfi, r!w, rconad)
                  if  rconad>0 start 
                     move(integer(sconad), sconad, rconad)
                     flag = ddisconnect(myname, ident to s(fsys<<24), fsys, 0)
                  finish  else  flag = create file fails
               else 
                  if  msg_rname#"" then  bcc name = msg_rname else  bcc name = user."@".this short host
                  put component(bcc, bcc name)
                  nkb = ((integer(sconad)+epage size-1)&(-epage size))>>10
                  compose message(fsys<<24, bitcomp, nkb, flag)
               finish 
               if  flag=0 start 
                  if  s msg_dt after#0 then  i = s msg_dt after else  i = s msg_dt sent
                  cycle  i = i, 1, i+9
                     flag = dtransfer(myname, user, ident to s(fsys<<24), rootfilename.htos(i, 8), fsys, fsys, 1)
                     if  flag#already exists then  exit 
                  repeat 
                  if  flag=0 then  start 
                     message count = message count+1
                     log print(dt."Message ".s msg_mess id." delivered to ".msg_rname." at ".user.snl)
                     msg_dt delivered = secs now
                     msg_status = unused
                     unlink msg(s msg, link, msg_recip link)
                     link = msg_rname link
                     if  (s header_bitcomp>>ack to)&1=1 then  send acknowledgement(msg)
                  else 
                     log print(dt."Transfer ".s msg_mess id." to ".user." fails ".derrs(flag).snl)
                  finish 
               finish 
               if  flag#0 then  f = ddisconnect(myname, ident to s(fsys<<24), fsys, 1)
               !+destroy, ignore flag
               f = ddisconnect(myname, ident to s(msg_ident), msg_ident>>24, 0)
               if  s msg_recip link=0 then  delete message(msg_ident, f)
            else 
               printstring("!!Connect ".msg_mess id." fails ".derrs(flag).snl)
               msg_status = unused
               unlink msg(s msg, link, msg_recip link)
               link = msg_rname link
            finish 
         else 
            if  anon=no start 
               printstring(dt."Bad link for ".user." in MAILLIST fsys ".itos(fsys).snl)
               return 
            finish  else  link == msg_rname link
         finish 
         if  flag#0 then  return 
      repeat 
   end ; !of give messages



   integer  fn  get next descriptor(integer  fsys)

!      Gets the next free message descriptor from the MAILLIST file.
!      Free pointer cycles round file looking hopefully for the oldest
!      free descriptors so as not to over write recently used ones to
!      preserve a history of what has gone on.

      record  (fhf) name  file header
      integer  ms
      record  (msg descriptor f) array  name  messages
      record  (msg descriptor f) array  format  msg af(1:max messages)
      if  f systems(fsys)#0 start ; !check that a mail file is there
         file header == record(f systems(fsys))
         messages == array(f systems(fsys)+message entry size, msg af)
         ms = file header_free hole; !find next free hole
         until  ms=file header_free hole cycle 
            !stop when we come round again
            if  messages(ms)_status=unused start 
               !is descriptor unused
               file header_free hole = ms+1
               file header_free hole = 1 if  file header_free hole>max messages
               !wrap round
               result  = fsys<<24!ms
            finish 
            ms = ms+1
            ms = 1 if  ms>max messages
         repeat 
         printstring("No free message descriptors fsys ".i to s(fsys).snl)
      finish  else  printstring("Invalid fsys for descriptor = ".i to s(fsys).snl)
      result  = 0
   end ; !of integerfn get next descriptor



   string  fn  short form(string  (15) server)

!     Temp fn to check the equivalence of host names

      integer  i
      i = lookup host(server)
      if  i<=0 then  result  = ""
      result  = statstring(station_shortest name)
   end ; !of short form


   string  fn  ident to s(integer  ident)

!      Turns a message identifier into a string of fixed format
!
      string  (2) fsys
      string  (4) rest
      fsys = i to s(ident>>24)
      fsys = "0".fsys if  length(fsys)=1
      rest = i to s(ident&x'FFFFFF')
      rest = "0".rest while  length(rest)<4
      result  = fsys.rest
   end ; !of stringfn ident to s



   integer  fn  message addr(integer  ident)

!      Returns the address of the message descriptor "IDENT"
!      Returns zero if IDENT is not valid

      integer  fsys, ms
      fsys = ident>>24; ms = ident&x'FFFFFF'
      result  = 0 unless  f systems(fsys)#0 and  1<=ms<=max messages
      result  = f systems(fsys)+ms*message entry size
   end ; !of integerfn message addr



   routine  return to sender(record  (msg descriptor f) name  msg, integer  name  flag, integer  ident)

!      Returns a message to its originator.  The message either failed
!      to spool, or no indication was later received from spoolr about
!      its progress.

      integer  seg, gap, s conad, type, i
      record  (msg descriptor f) name  s msg
      record  (fhf) name  s header
      string  (8) s
      report fsys = myfsys
      report conad = 0
      type = mailer dead letter
      s conad = 0
      add to report("Message ".msg_mess id)
      add to report("failed to transmit to ".msg_server)
      s msg == record(message addr(msg_ident))
      seg = 0; gap = 0
      flag = dconnect(myname, ident to s(msg_ident), msg_ident>>24, r, 0, seg, gap)
      if  flag=0 start 
         s conad = seg<<18
         s header == record(s conad)
         fill(max components*8, addr(msg cbeg(1)), 0)
         bitcomp = s header_bitcomp
         cycle  i = max components, -1, 1
            if  (bitcomp>>i)&1=1 start 
               msg cbeg(i) = s header_cp(i)_beg+s conad
               msg clen(i) = s header_cp(i)_len
            finish 
         repeat 
         type = mailer returned msg
      finish  else  printstring("Connect ".ident to s(msg_ident)." fails: ".derrs(flag))
      mailer sends message(msg_ident>>24, sconad, -1, type, flag)
      delete message(ident, i)
      if  sconad>0 then  i = ddisconnect(myname, ident to s(msg_ident), msg_ident>>24, 0)
      if  flag=0 then  s = "succeeds" else  s = "fails"
      log print(dt."Returning msg ".msg_mess id." at ".ident to s(msg_ident)." ".s.snl)
   end ; !of return to sender



   routine  delete message(integer  ident, integer  name  flag)

!      Routine to delete a message and its descriptor.

      record  (msg descriptorf) name  message
      string  (11) file
      string  (8) plus
      integer  fsys, ma
      file = ident to s(ident)
      fsys = ident>>24
      ma = message addr(ident)
      if  ma=0 start 
         printstring("Invalid desc ".file." in delete msg".snl)
         return 
      finish 
      message == record(ma)
      flag = ddestroy(my name, file, "", fsys, 0)
      if  flag#0 start 
         if  message_status&outbound=0 then  printstring("Destroy ".my name.".".file." fails ".derrs(flag).snl)
         plus = " (descr)"
      finish  else  plus = " (file)"
      log print(dt."Message ".message_mess id." deleted, ident=".ident to s(ident).plus.snl)
      message_dt deleted = current dt in secs
      message_status = unused
   end ; !of routine delete message



   string  fn  statstring(integer  ad)
      if  ad=0 then  result  = ""
      result  = string(stat space offset+ad)
   end ; !of statstring


   integer  fn  lookup hasht(string  (127) name)
      record  format  hname f(integer  link, host entry, string  (255) name)
      record  (hname f) name  hname entry
      integer  i, pt, n, h
      byte  integer  array  x(0:15)
      const  byte  integer  array  prime(1:7)= 23, 19, 11, 7, 5, 13, 17

      pt = (addr(x(7))>>3)<<3
      longinteger(pt) = 0
      n = addr(name)
      byteinteger(pt+i&7) = byteinteger(pt+i&7)!!byteinteger(n+i) for  i = 0, 1, length(name)
      h = length(name)*29
      h = h+prime(i)*byteinteger(pt+i) for  i = 1, 1, 7
      h = h&hash length
      if  pointers_hasht(h)#-1 start 
         hname entry == record(config conad+pointers_hasht(h))
         cycle 
            if  name=hname entry_name start ; !found it
               station == record(station offset+hname entry_host entry*statio                     n entry size)
               stat space offset = addr(station_string space(0))
               !for use in 'statstring'
               result  = hname entry_host entry
            finish 
            exit  if  hname entry_link=-1
            hname entry == record(config conad+hname entry_link)
         repeat 
      finish 
      result  = 0
   end ; !of lookup hasht


   integer  fn  lookup host(string  (127) name)

!     Determines if a host name is valid - may be a domain literal, or found in the network config file

      integer  res
      string  (127) s1, s2
      if  (length(name)>2 and  charno(name, 1)='[') or  name->s1.("FTP").s2 then  result  = 0
      !domain literal
      name = toupper(printable(name))
      res = lookup hasht(name)
      if  res#0 then  result  = res
      unless  name->(this ukac).s1 start 
         res = lookup hasht(this ukac.name); !prefix uk.ac
         if  res#0 then  result  = res
         if  name->name.(".").s1 start 
            res = lookup hasht(name); !for arpa.anything etc
            if  res#0 and  station_flags&route flag#0 then  result  = res
         finish 
      finish 
      result  = -1
   end ; !of lookup host


   integer  fn  lookup name(string  (255) rname)

!      Searches the name table for a given rname and returns
!      the entry no of the corresponding addr table entry.

      integer  lower, upper, entry
      string  (255) sur
      sur = compress(rname)
      unless  0<length(sur)<32 and  'A'<=charno(sur, 1)<='Z' then  result  = not found
      lower = startchar(charno(sur, 1))
      upper = startchar(charno(sur, 1)+1)
      if  lower<upper start 
         cycle 
            entry = (lower+upper)>>1
            if  n table(entry)_rname=sur then  ->give result
            if  n table(entry)_rname<sur start 
               if  entry=lower then  exit 
               lower = entry
            else 
               if  entry=upper then  exit 
               upper = entry
            finish 
         repeat 
      finish 
      if  name file_extrastart<=name file_extraend start 
         cycle  entry = name file_extrastart, 1, name file_extraend
            if  n table(entry)_rname=sur then  ->give result
         repeat 
      finish 
      result  = not found
give result:

      if  n table(entry)_soundex=0 then  discarded entry = entry and  result  = not found
      result  = entry
   end ; !of lookup name



   string  fn  compress(string  (255) s)

!     Translates a string to uppercase alphanumeric characters.

      const  byte  integer  array  tab(0:255)= c 
          0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
          0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,48,49,50,51,52,53,54,55,56,57,0,0,
 0,0,0,0,
          0,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,
 86,87,88,89,90,0,0,0,0,0,
          0,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,
 86,87,88,89,90,0,0,0,0,0,
          0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
          0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
          0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
          0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
      integer  dr0, dr1, accdr0, accdr1, i, j
      string  (255) t
      if  length(s)=0 then  result  = ""
      dr0 = x'58000000'!length(s)
      dr1 = addr(s)+1
      accdr0 = x'18000100'
      accdr1 = addr(tab(0))
      *ld_dr0
      *lsd_accdr0
      *ttr_ l  = dr 
      i = addr(s)
      j = addr(t)+1
      cycle  i = i+1, 1, i+byteinteger(i)
         if  byteinteger(i)#0 then  byteinteger(j) = byteinteger(i) and  j = j+1
      repeat 
      length(t) = j-addr(t)-1
      length(t) = 31 if  length(t)>31
      result  = t
   end ; !of compress



   string  fn  to upper(string  (255) s)
      integer  dr0, dr1, accdr0, accdr1
      dr0 = x'58000000'!length(s)
      dr1 = addr(s)+1
      accdr0 = x'18000100'
      accdr1 = ad ltou trans
      *ld_dr0
      *lsd_accdr0
      *ttr_ l  = dr 
      result  = s
   end ; !of to upper



   routine  append to report(integer  conad, len)
      integer  size, flag, i
      if  len>16000 start 
         add to report(snl."The text of your message begins as follows -".snl)
         len = 16000
         for  i = conad+len-1, -1, conad+15000 cycle 
            if  byteinteger(i)=nl then  len = i-conad+1 and  exit 
         repeat 
      finish  else  add to report(snl."The text of your message follows -".snl)
      if  byteinteger(conad)#nl then  add to report("")
      size = (report file header_end+len+epage size)&(~epage size)
      if  size>report file header_size start 
         flag = dchsize(myname, "REPORTFILE", report fsys, size>>10)
         if  flag#0 start 
            printstring("Dchsize reportfile fails: ".derrs(flag).snl)
            len = report file header_size-report file header_end
         finish  else  report file header_size = size
      finish 
      move(len, conad, report conad+report file header_end)
      report file header_end = report file header_end+len
   end ; !of append to report


   routine  add to report(string  (255) s)

!     Creates a report file and adds text to it.

      integer  flag, size
      if  report conad=0 start 
         create and connect("REPORTFILE", report fsys, epage size, zerod!tempfi, r!w, report conad)
         if  report conad=0 then  report conad = -1 else  start 
            report file header == record(report conad)
            report file header_type = ss char type
         finish 
      finish 
      if  report conad<0 then  return 
      size = (report file header_end+length(s)+1+epage size)&(-epage size)
      if  size>report file header_size start 
         flag = dchsize(myname, "REPORTFILE", report fsys, size>>10)
         if  flag#0 start 
            log print(dt."Chsize reportfile fails ".derrs(flag).snl)
            string(report conad+report file header_size-11) = "** FULL **"
            report full = 1
            report file header_end = report file header_size-1
            return 
         finish 
         report file header_size = size
      finish 
      string(report conad+report file header_end) = s
      byteinteger(report conad+report file header_end) = nl
      report file header_end = report file header_end+length(s)+1
   end ; !of add to report



   routine  return report(string  (6) user, string  (11) report file, integer  fsys, integer  name  flag)

!      Transfers a report file to a user

      integer  i
      flag = ddisconnect(myname, "REPORTFILE", fsys, 0)
      if  flag#0 start 
         printstring("Disconnect ".myname.".REPORTFILE fails ".derrs(flag).snl)
         flag = cannot return report file
         return 
      finish 
      flag = dtransfer(myname, user, "REPORTFILE", reportfile, fsys, fsys, 1)
      if  flag=0 then  return 
      printstring("Transfer REPORTFILE to ".user." fails ".derrs(flag).snl)
      i = ddestroy(myname, "REPORTFILE", "", fsys, 0)
      flag = cannot return report file
   end ; !of return report



   routine  send acknowledgement(record  (msg descriptor f) name  r msg)

!     On acceptance of a message, sends an acknowledgement to the sender

      string  (63) s
      integer  flag, save
      save = message count
      report conad = 0
      add to report("Message ".r msg_mess id)
      if  r msg_rname="" then  s = r msg_managr."@".this host else  s = r msg_rname." @ ".this institution
      add to report("accepted by ".s." at ".time." on ".date.snl)
      mailer sends message(r msg_ident>>24, 0, 0, mailer ack, flag)
      if  flag=0 then  s = "succeeds" else  s = "fails"
      log print(dt."Sending acknowledgement to ".r msg_mess id." ".s.snl)
      report conad = 0
      message count = save
   end ; !of send acknowledgement



   routine  compose remote message(integer  ident, posn, nkb, bits, integer  name  flag)

!     Packages up a message for transmission to a remote mail server.

      integer  i, c, dispose, caddr, link, mode, low server, high server
      record  (fhf) name  file header
      string  (255) s
      string  (6) filename

      routine  put(integer  len, beg, string  (255) extra)
         integer  i, flag
         i = file header_end+len+length(extra)
         if  i>file header_size start 
            i = ((i+epage size-1)&(-epage size))>>10
            flag = dchsize(myname, filename, ident>>24, i)
            if  flag#0 start 
               printstring("Dchsize ".myname." fails ".derrs(flag).snl)
               dispose = yes
               file header_end = file header_start
               return 
            finish 
            file header_size = i<<10
         finish 
         if  len>0 start 
            move(len, beg, caddr+file header_end)
            file header_end = file header_end+len
         finish 
         if  extra#"" start 
            move(length(extra), addr(extra)+1, caddr+file header_end)
            file header_end = file header_end+length(extra)
         finish 
      end ; !of put

      dispose = no; !dont destroy
      if  posn=-1 start ; != 'after' delivery
         low server = 1; high server = server map top
         mode = cherish
      else 
         low server = posn; high server = posn
         mode = tempfi
      finish 
      filename = ident to s(ident)
      create and connect(filename, ident>>24, nkb<<10, zerod!mode, r!w, caddr)
      if  caddr=0 then  flag = create file fails and  return 
      file header == record(caddr)
      s = ""
      cycle  i = low server, 1, high server
         link = server map(i)_rtable pointer
         while  link#0 cycle ; !build jnt mail header
            if  s#"" then  put(0, 0, ",".snl)
            if  rtable(link)_managr="" then  s = rtable(link)_rname."@".server map(i)_name else  start 
               if  server map(i)_flags&update flag=0 then  s = rtable(link)_managr."@".server map(i)_name else  c 
                  s = rtable(link)_rname."@".this institution
            finish 
            log print(dt."JNT header = ".s.snl) if  monitoring&2>0
            put(0, 0, s)
            link = rtable(link)_link
         repeat 
      repeat 
      put(0, 0, snl.snl)
      cycle  i = 1, 1, max components
         c = order(i)
         if  bits>>c&1=1 start 
            put(length(lc comp name(c)), addr(lc comp name(c))+1, "")
            if  byteinteger(msg cbeg(c)+msg clen(c)-1)#nl then  s = snl else  s = ""
            put(msg clen(c), msg cbeg(c), s)
         finish 
      repeat 
      flag = ddisconnect(myname, filename, ident>>24, dispose)
      if  flag#0 start 
         printstring("Ddisconnect ".myname." .".filename." fails".derrs(flag).snl)
         flag = create file fails
      finish 
      if  dispose=yes then  flag = create file fails
   end ; !of compose remote message



   routine  dispatch to remote(integer  ident, string  (127) remote, string  (6) user, integer  name  flag)

!      Submits a message to SPOOLR for transmission to a remote server.

      integer  index
      string  (11) srce
      record  format  document descriptorf(string  (7) header, byte  integer  state, string  (6) user,
          string  (15) dest, integer  date and time received, date and time started, date and time output,
          date and time deleted, start after date and time, priority, data start, data length, time, output limit,
          half  integer  mode of access, byte  integer  priority requested, forms, mode, copies, order, rerun,
          decks, drives, fails, outdev, srce, output, delivery, name, byte  integer  array  vol label(1:8),
          byte  integer  external user, external password, external name, ftp alias, storage codename, device type,
          device qualifier, data type, text storage, sp1, sp2, sp3, sp4, sp5, sp6, sp7, byte  integer  dap blocks,
          try emas to emas, ftp retry level, byte  integer  string ptr, string  (147) string space)
      record  (document descriptorf) document


      routine  to doc string(record  (document descriptor f) name  document, byte  integer  name  field,
          string  name  value)
         field = 0 and  return  if  value=""
         field = x'FF' and  return  if  document_string ptr+length(value)>147
         field = document_string ptr
         string(addr(document_string space)+document_string ptr-1) = value
         document_string ptr = document_string ptr+length(value)+1
      end 

      index = top spoolr reply
      until  index=top spoolr reply cycle 
         if  spool reply index(index)=0 start 
            srce = ident to s(ident)
            document = 0
            document_string ptr = 1
            document_dest = "FTP"
            document_priority = 3; !STD just now
            to docstring(document, document_ftp alias, remote)
            document_mode of access = x'0001'
            to docstring(document, document_name, srce)
            to docstring(document, document_srce, srce)
            document_header = "BINDOC:"
            document_user = user
            flag = dexecmess("FTRANS", index<<8!spoolr reply, 264, addr(document))
            log print(dt."Dexecmess ".myname.".".ident to s(ident)." to ".remote." flag = ".derrs(flag).snl)
            if  flag=0 then  spool reply index(index) = ident and  top spoolr reply = (index+1)&max reply index
            return 
         finish 
         index = (index+1)&max reply index
      repeat 
      printstring("No replies from FTRANS -  Mailer closed".snl)
      mailer state = closed
      flag = create file fails
   end ; !of dispatch to remote



   routine  link after(record  (msg descriptor f) name  msg, integer  ident)

!      Inserts a message descriptor into a linked list of 'after'
!      messages in chronological order.

      integer  name  link
      record  (msg descriptor f) name  l msg
      link == after linkhead
      msg_ident = 0
      while  link#0 cycle 
         l msg == record(message addr(link))
         unless  l msg_status=waiting then  return 
         if  l msg_dt after>msg_dt after then  msg_ident = link and  exit 
         link == l msg_ident
      repeat 
      link = ident
      message count = message count+1
   end ; !of link after



   routine  place alarm call

!     Call the elapsed interval timer for a time-determined delivery message

      integer  i, secs, j, k
      record  (msg descriptor f) name  msg
      record  (pe) p
      i = message addr(after linkhead)
      if  i#0 start 
         msg == record(i)
         secs = msg_dt after-current dt in secs
         p = 0
         if  secs<=0 then  p_dest = my service number!alarm call else  start 
            p_dest = elapsed int; !get pon in n secs
            p_p1 = my service number!alarm call
            if  secs>x'7FFF' then  p_p2 = x'7FFF' else  p_p2 = secs
         finish 
         i = dpon2("", p, 0, 6)
      finish 
      k = (charno(date, 7)-'0')*10+charno(date, 8)-'0'
      i = get fourth sunday(3, k); !in march
      j = get fourth sunday(10, k); !in october
      if  i<=current dt in secs<j then  time zone = "bst" else  time zone = "gmt"
   end ; !of place alarm call



   routine  process name command(string  (255) s, string  (6) user, integer  name  flag)

!      Acts on one of the following user requests:
!        ACCREDIT <Rname>,(ALIAS ! DLIST ! BBOARD),,<dept>
!        DISCREDIT <Rname>
!        INQUIRE <Rname>

      integer  fsys, i, entry, option
      string  (255) rname, param1, param2, param3
      record  (addr entry f) name  addr entry
      const  integer  max name commands= 4
      switch  comm(1:max name commands)
      const  string  (9) array  name command(1:max name commands)= c 
         "ACCREDIT","DISCREDIT","INQUIRE","MAILHERE"
      const  string  (6) array  dir options(2:4)= "ALIAS", "DLIST", "BBOARD"

      if  mailer state=closed then  flag = mail service closed and  return 
      fsys = -1
      secs now = current dt in secs
      flag = dfsys(user, fsys)
      report conad = 0
      if  flag=0 start 
         if  s->s.(" ").rname start 
            unless  rname->rname.(",").param1 then  param1 = ""
            unless  0<length(rname)<=31 then  flag = illegal name and  return 
            unless  param1->param1.(",").param2 then  param2 = ""
            unless  param2->param2.(",").param3 then  param3 = ""
            cycle  i = max name commands, -1, 1
               if  s=name command(i) then  ->comm(i)
            repeat 
         finish 
         flag = invalid command
      finish 
      return 

comm(1):                                 !accredit
      flag = student user(user)
      if  flag#0 then  return 
      if  length(param3)>31 then  flag = bad params and  return 
      option = 0
      cycle  i = 2, 1, 4
         if  param1=dir options(i) then  option = i and  exit 
      repeat 
      if  option=0 then  flag = bad params and  return 
      if  option>2 and  (dbconad=0 or  db_bowner#user) then  flag = invalid rname option and  return 
      entry = lookup name(rname)
      if  entry#0 start 
         addr entry == record(addr file conad+entry*ad entry size)
         if  addr entry_managr#user or  addr entry_server#this short host then  c 
            flag = rname already accredited and  return 
         addr entry_department = param3
         return 
      finish 
      if  user="MANAGR" start 
         if  param3->param1.("@").param2 start 
            unless  0<length(param1)<16 and  0<length(param2)<16 then  flag = bad params and  return 
            param2 = toupper(param2)
            flag = accredit name(rname, "", param2, param1, option, -1, secs now)
            return 
         finish 
         if  length(param3)=6 start 
            user = toupper(param3)
            param3 = ""; fsys = -1
            i = dfsys(user, fsys)
            if  i#0 then  flag = user not accredited and  return 
         finish 
      finish  else  if  kent = yes then  start 
         if  user # "BBOARD" then  start 
            flag = bad params
            return 
         finish 
      finish 
      flag = accredit name(rname, param3, this short host, user, option, fsys, secs now)
      if  flag=0 start 
         entry = lookup name(rname)
         if  entry#0 then  update remote servers("ACCREDIT", entry, entry)
      finish 
      return 


comm(2):                                 !discredit
      entry = lookup name(rname)
      if  entry=0 then  flag = user not accredited and  return 
      addr entry == record(addr file conad+entry*ad entry size)
      if  user#"MANAGR" start 
         if  kent = yes then  flag = bad params and  return 
         if  addr entry_managr#user or  addr entry_server#this short host then  c 
            flag = rname belongs to another user and  return 
         if  addr entry_link#0 then  flag = uncollected mail for rname and  return 
      finish 
      update remote servers("DISCREDIT", entry, entry)
      discredit entry(entry)
      return 


comm(3):                                 !inquire
      flag = lookup name(rname)
      return 

comm(4):                                 !mailhere
      entry = lookup name(rname)
      if  entry = 0 then  flag = user not accredited and  return 
      addr entry == record(addr file conad+entry*ad entry size)
      if  addr entry_department # "__sysid__".user then  start 
         flag = rname belongs to another user
         return 
      finish 
      addr entry_server = this short host
      addr entry_managr = user
      addr entry_timestamp = addr entry_timestamp - 86400
      addr entry_department = ""
      update remote servers("ACCREDIT",entry,entry)
      flag = 0
      return 
   end ; !of process name command



   routine  discredit entry(integer  entry no)

!     Removes an entry from the name and address tables

      record  (addr entry f) name  addr entry
      addr entry == record(addr file conad+entry no*ad entry size)
      log print(dt."Discrediting ".addr entry_rname." <".addr entry_managr."@".addr entry_server."> ". c 
         addr entry_department.snl)
      n table(entry no)_soundex = 0
      addr entry = 0
   end ; !of discredit entry



   integer  fn  accredit name(string  (31) rname, department, string  (15) server, managr, integer  options, fsys,
       secs)

!      Adds an entry to the name and address tables.

      record  (addr entry f) name  addr entry
      string  (31) sur
      integer  entry no, i
      sur = compress(rname)
      unless  length(sur)>0 and  'A'<=charno(sur, 1)<='Z' then  result  = illegal name
      if  server=this short host and  length(sur)=6 start 
         i = -1
         i = dfsys(sur, i); !make sure its not anothers usernumber
         if  i#user not known and  sur#managr then  result  = illegal name
      finish 
      if  ad file_entries>=max rnames start 
         printstring("Addr table full!!".snl)
         result  = addr table full
      finish 
      discarded entry = 0
      entry no = lookup name(sur)
      if  discarded entry#0 then  entry no = discarded entry else  entry no = name file_extraend+1
      addr entry == record(addr file conad+entry no*ad entry size)
      unless  1<=entry no<=max rnames and  addr entry_rname="" start 
         printstring("Addr file corrupt!!".snl)
         ad file_entries = max rnames
         result  = addr table full
      finish 
      addr entry = 0
      addr entry_fsys = fsys
      addr entry_options = options
      addr entry_timestamp = secs
      addr entry_managr = managr
      addr entry_server = server
      addr entry_department = department
      addr entry_rname = rname
      n table(entry no)_rname = compress(rname)
      n table(entry no)_soundex = soundex(rname)
      if  discarded entry=0 then  name file_extraend = entry no and  ad file_entries = ad file_entries+1
      log print(dt."Accrediting ".rname." <".managr."@".server."> ".department.snl)
      result  = ok
   end ; !of accredit name



   routine  create and connect(string  (11) file, integer  fsys, nbytes, createmode, connectmode,
       integer  name  caddr)
      integer  seg, gap, again, size, f, flag
      record  (fhf) name  file header
      string  (11) rt
      caddr = 0
      size = (nbytes+epage size-1)&(-epage size)
      cycle  again = 2, -1, 1
         flag = dcreate(myname, file, fsys, size>>10, createmode)
         if  flag=0 start 
            if  createmode&cherish#0 then  flag = dfstatus(myname, file, fsys, noarch, 0)
            seg = 0; gap = 0
            flag = dconnect(myname, file, fsys, connectmode, 0, seg, gap)
            if  flag=0 start 
               caddr = seg<<18
               file header == record(caddr)
               file header_end = file header size
               file header_start = file header size
               file header_size = size
               file header_datetime = current dt in secs!x'80000000'
               return 
            finish  else  rt = "Connect"
            exit 
         finish 
         rt = "Create"
         f = ddestroy(myname, file, "", fsys, 0)
         f = ddisconnect(myname, file, fsys, 1); !speculative
      repeat 
      printstring(rt." ".myname.".".file." fails ".derrs(flag).snl)
   end ; !of create and connect



   routine  connect or create(string  (6) user, string  (11) file, integer  fsys, size, flags, integer  name  caddr)

!   Connect or create a file. Setting CADDR with the connect address or
!   zero if unsuccesful.

      record  (fhf) name  file header
      record  (finff) file info
      integer  flag, seg, gap, nkb
      string  (31) filename
      caddr = 0; !set return connect address to zero initially
      nkb = ((size+e page size-1)&(-e page size))>>10
      flag = dfinfo(user, file, fsys, addr(file info))
      if  flag=0 start 
         if  nkb#file info_nkb start 
            flag = dchsize(user, file, fsys, nkb)
            if  flag#0 then  printstring("Chsize ".user.".".file." fails ".derrs(flag).snl) else  c 
               printstring(user.".".file." SIZE CHANGED ".i to s(nkb-file info_nkb)." KBYTES".snl)
         finish 
      finish 
      seg = 0; !any segment will do
      gap = 0; !any gap will do
      unless  flag=does not exist then  flag = dconnect(user, file, fsys, r!w!shared, 0, seg, gap)
      unless  flag=ok start ; !successfully connected?
         filename = user.".".file
         unless  flag=does not exist start ; !no? then did it exist
            printstring("Connect ".filename." fails ".derrs(flag).snl)
            !yes then failure message
            flag = ddestroy(user, file, "", fsys, 0)
            !try to destroy it
         finish  else  flag = ok
         if  flag=ok start ; !success or does not exist
            flag = dcreate(user, file, fsys, nkb, flags)
            !create file
            if  flag=ok start ; !created ok?
               if  flags&cherish#0 then  flag = dfstatus(user, file, fsys, noarch, 0)
               seg = 0; gap = 0
               flag = dconnect(user, file, fsys, r!w!shared, 0, seg, gap)
               if  flag=ok start ; !connected ok?
                  caddr = seg<<18; !set connect address
                  file header == record(caddr)
                  !set up a file headder
                  file header_end = file header size
                  file header_start = file header size
                  file header_size = (size+e page size-1)&(-e page size)
                  file header_datetime = current dt in secs
               finish  else  printstring("Connect ".filename." fails ".derrs(flag).snl)
            finish  else  printstring("Create ".filename." fails ".derrs(flag).snl)
         finish  else  printstring("Destroy ".filename." fails ".derrs(flag).snl)
      finish  else  caddr = seg<<18; !already existed so return connect address
   end ; !of routine connect or create



   routine  mail queue(record  (reqf) name  p)

!      Processes files held in SPOOLR's mail queue

      const  integer  reqact= x'FFFF003C'; !request a file from spoolr
      const  integer  return act= x'FFFF003D'; ! return a file to spoolr
      record  (repf) name  pp
      string  (127) ftp source
      integer  flag, fsys, seg, gap
      own  integer  dispose
      switch  act(return file ack:file from spoolr)

      pp == p
      ->act(p_dest&x'1F')

act(return file ack):
      if  pp_flag#0 start 
         printstring("SPOOL return file fails = ".itos(pp_flag).snl)
         return 
      finish 
      if  dispose=spoolr requeue then  dispose = spoolr delete and  return 
      if  mailer state=closed then  return 
      p = 0
      p_dest = reqact; !now request another file
      p_srce = file from spoolr
      p_flag = 0; !delay reply till there is a file
      p_user = myname
      flag = dpon2("SPOOLR", p, 1, 6)
      return 

act(file from spoolr):
      if  pp_flag#0 start 
         printstring("SPOOL request file fails = ".itos(pp_flag).snl)
         return 
      finish 
      if  mailer state=closed then  dispose = spoolr requeue else  start 
         fsys = (charno(pp_file, 1)-'0')*10+charno(pp_file, 2)-'0'
         seg = 0
         gap = 0
         dispose = spoolr delete; !delete when returned
         flag = dconnect("SPOOLR", pp_file, fsys, r, 0, seg, gap)
         if  flag=0 start 
            ftp source <- string(seg<<18+16); !sneaky, spoolr sticks source in file
            if  ftp source="" then  ftp source = pp_ftp source
            !for compatibility
            process remote file(seg<<18, pp_file, "RELAY", dispose, ftp source)
            flag = ddisconnect("SPOOLR", pp_file, fsys, 0)
         finish  else  printstring("Dconnect SPOOLR.".pp_file." fails ".derrs(flag).snl)
      finish 
      if  dispose=spoolr requeue then  printstring("MAILER requeues SPOOLR.".pp_file.snl)
      p_dest = return act
      p_srce = return file ack
      p_file = pp_file
      p_user = myname
      p_flag = dispose
      p_p6 = 0
      flag = dpon2("SPOOLR", p, 1, 6)
   end ; !of mail queue



   routine  process remote file(integer  caddr, string  name  file, string  (6) user, integer  name  dispose,
       string  (127) ftp source)

!   Takes a file from SPOOLR and distributes it to its recipients.
!   Also processes a time-determined delivery message whose time has come.

      integer  flag, top, mend, mbeg, i, j, msg type, nkb, ident, dt sent, fsys, len
      string  (255) s, msg id, fromname, rname, via string, server
      string  (31) date time now, managr, srce
      record  (msg descriptor f) name  msg
      record  (fhf) name  file header
      const  byte  integer  array  preferred sender comp(1:3)= reply to, from, sender

      if  dispose<0 then  srce = myname else  srce = "SPOOLR"
      log print(dt.srce.".".file." received".snl)
      message count = 0
      report conad = 0
      report fsys = myfsys
      bitcomp = 0
      msg type = mailer report; !in case of failure
      secs now = current dt in secs
      date time now = secs to dt(secs now)
      fsys = myfsys

!   Partition the file into JNT header and message proper

      file header == record(caddr)
      top = caddr+file header_start
      m beg = top; !find start of message within file
      m end = caddr+file header_end-1
      while  m beg<m end cycle 
         if  byteinteger(m beg)#nl then  m beg = m beg+1 and  continue 
         mbeg = mbeg+1 until  9#byteinteger(m beg)#' ' or  m beg=m end
         m beg = m beg+1
         if  byteinteger(mbeg-1)=nl then  exit 
      repeat 

!   If file partitioned ok, analyse message components

      if  top+2<m beg<m end start 
         staticise message(m beg, m end, remote, i); !get message components
         if  bitcomp=0 then  bitcomp = 1<<body

!   Find where message is from and add Via field

         srce = ""
         if  ""#ftp source#"LP" start 
            if  lookup host(ftp source)>0 then  ftp source = lcstring(statstring(station_name))
            via string = ftp source."  ; (to ".this lc host.") ".date time now
            put component(via, viastring)
            srce = ftp source
         finish 

!   Find who the message is from

         cycle  j = 1, 1, 3
            server map top = 0
            i = preferred sender comp(j)
            if  bitcomp&(1<<i)=0 then  continue 
            process recipients(msg cbeg(i), msg clen(i), ignore route, no report)
            if  server map top#0 then  exit 
         repeat 
         if  server map top#0 start ; !know who its from
            rname = rtable(server map(1)_rtable pointer)_rname
            managr = rtable(server map(1)_rtable pointer)_managr
            server = server map(1)_short name
            if  server="" then  server = server map(1)_name
            server map top = 0; !reset to zero
         else 
            if  srce#"" then  server = srce else  server = "?"
            rname = "?"
            managr = ""
         finish 
         take component(mess id, msgid)
         take component(cdate, s)
         dt sent = check and convert dt(s)
         unless  0<dt sent<secs now then  dt sent = secs now-1
         if  msg id="" start 
            if  s->msgid.(",").s start ; !remove day from date
            finish 
            if  0<length(s)<200 then  msg id = "sent ".s else  msg id = "received ".date time now
            msg id = "<".msg id." via ".server.">"
            put component(mess id, msg id)
         finish 
         take component(from, fromname)
         if  fromname->fromname.(",").s start 
         finish 
         if  server=this short host or  srce="" then  fsys = next fsys
         if  srce="" and  toupper(fromname)="FTPMAN" then  srce = "SPOOLR"
         unless  fromname->fromname.("<").s or  length(rname)=1 then  fromname = rname
         if  compress(fromname)="" then  fromname = rname

!   Analyse recipients (JNT mail header addresses)

         i = m beg-top-2
         process recipients(top, i, jnt header, report)
         if  server map top=0 start ; !no recipients
            move(31, top, addr(s)+1)
            length(s) = 31
            s = compress(s)
            length(s) = length(myname)
            if  s=myname start 
               if  (bitcomp&originator comp)=1<<from and  (compress(rname)=myname or  srce="SPOOLR") start 
                  report conad = 0
                  flag = msg for mailer(server, msg id)
                  if  flag=0 then  return 
               finish 
               msg type = mailer dead letter
               log print(dt."Srce = ".srce.", server = ".server.snl)
            finish 
            add to report("Message contains no valid recipients - not delivered") if  report conad=0
         finish 

!   If there are valid recipients, distribute the message to them

         if  server map top>0 and  rtable top<max recipients start 
            if  bitcomp>>bcc&1=1 start ; !determine the bcc recipients
               i = rtable top
               process recipients(msg cbeg(bcc), msg clen(bcc), flag bcc, no report)
               rtable top = i
            finish 
            ident = get next descriptor(fsys)
            if  ident#0 start 
               msg == record(message addr(ident))
               msg = 0
               msg_rname <- fromname
               msg_managr <- managr
               msg_server <- server
               msg_mess id <- msg id
               msg_dt sent = dt sent
               msg_dt received = secs now
               msg_status = sending
               nkb = (m end-m beg+1023)>>10
               log print(dt."Remote message ".msg_mess id." from ".srce." received at ".ident to s(ident).snl)
               distribute mail(msg, user, ident, fsys, bitcomp, nkb, flag)
               if  msg_recip link=0 then  msg_status = unused
               if  flag#0 start 
                  printstring("Increase MAXKB or index size for MAILER fsys ".itos(fsys).snl)
                  dispose = spoolr requeue
                  return 
               finish 
            finish  else  dispose = spoolr requeue and  return 
         finish 
      finish  else  add to report("Invalid msg format") and  msg type = mailer dead letter

!   Return a report to the sender or deal with a file with the wrong format

      if  report conad#0 start 
         log print(dt."Non-delivery report generated".snl)
         if  msg type=mailer report start 
            caddr = mbeg; !omit JNT header
            len = mend-mbeg+1
         finish  else  len = -1; !use the file header
         mailer sends message(fsys, caddr, len, msg type, flag)
      finish 
   end ; !of process remote file



   routine  mailer sends message(integer  fsys, conad, len, type, integer  name  flag)

!     Creates a message from MAILER to a user.
!     The message may be an acknowledgement, a non-delivery report, a returned message,
!     a dead letter or an FTP failure report

      integer  i, j, l, ident, final type, rtype
      string  (31) me
      string  (255) sub, in reply, dts, s
      string  (5) account user
      record  (msg descriptor f) name  msg
      const  integer  no mtypes= 5
      const  string  (20) array  mtype(1:no mtypes)= c 
        "Acknowledgement", "Non-delivery report","Returned message",
 "Dead letter/report", "Transmission failure"
      const  byte  integer  array  best sender(1:4)= ack to, sender, from, reply to

      routine  output(integer  from, to)
         integer  i
         cycle  i = from, 1, to
            printsymbol(byteinteger(i))
         repeat 
      end ; !of output

      if  len=-1 start ; !get len from the file header
         len = integer(conad)-integer(conad+4)
         conad = conad+integer(conad+4)
      finish 
      server map top = 0
      final type = type
      if  type#mailer dead letter start ; !if dead, deliver to local postmaster
         rtype = 0
         if  bitcomp&any vias#0 start 
            viastring = ""
            cycle  i = via6, -1, via
               if  (bitcomp>>i)&1=0 then  continue 
               take component(i, dts)
               if  dts->dts.(";").sub start ; !comment?
               finish 
               dts = printable(dts)
               if  length(dts)=0 then  continue 
               if  dts="TEST" or  dts="GUEST" then  continue 
               if  length(dts)+length(viastring)>254 then  viastring = "" and  exit 
               viastring = viastring."%".dts
            repeat 
            if  viastring#"" then  rtype = add viastring
            log print(dt."Viastring=".viastring.snl) if  monitoring&2=2
         finish 
         if  type=1 then  l = 1 else  l = 2; !use ack to?
         cycle  i = l, 1, 4; !where to send msg?
            j = best sender(i)
            if  rtype#add viastring then  rtype = j
            if  bitcomp&(1<<j)=0 then  continue 
            process recipients(msg cbeg(j), msg clen(j), rtype, no report)
            if  server map top>0 start 
               msg cbeg(to) = msg cbeg(j); !note new 'to' field
               msg clen(to) = msg clen(j)
               if  compress(rtable(1)_rname)->s.(myname).dts then  server map top = 0
               exit 
            finish 
         repeat 
      finish 
      if  server map top=0 start ; !no good originator field
         log print(dt."Cannot send ".mtype(type)." to sender".snl)
         j = addr(pointers_dead letters)+1
         l = length(pointers_dead letters)
         process recipients(j, l, 0, no report); !try dead letter box
         if  server map top>0 then  msg cbeg(to) = j and  msg clen(to) = l
         final type = mailer dead letter
      finish 

      ident = get next descriptor(fsys)
      if  ident=0 or  server map top=0 start 
         send and define(2, 4, "LPONLY")
         selectoutput(2)
         printstring(dt."Dumping non-deliverable ".mtype(type).snl)
         output(conad, conad+len-1) if  conad>0
         if  report conad>0 start 
            printstring(snl.dt."Contents of report file:".snl)
            output(report conad+integer(report conad+4), report conad+integer(report conad)-1)
            flag = ddisconnect(myname, "REPORTFILE", reportfsys, 1)
            !+destroy
            report conad = 0
         finish 
         selectoutput(0)
         send and define(2, 0, "LPONLY"); !stream2, 0=dont redefine
         selectoutput(1)
         printstring(dt."Non-deliverable ".mtype(type)."  sent to LP".snl)
         selectoutput(0)
         flag = 1
         return 
      finish 
      secs now = current dt in secs
      dts = secs to dt(secs now)
      msg == record(message addr(ident))
      msg = 0
      msg_rname = myname
      msg_server = this short host
      msg_mess id = "<".dts."  ".ident to s(ident)."@".this short host.">"
      msg_dt sent = secs now
      msg_dt received = secs now
      msg_status = sending
      me = myname."@".this host
      put component(from, me)
      put component(c date, dts)
      take component(subject, sub)
      if  length(sub)>200 then  length(sub) = 200
      if  sub="" then  sub = mtype(type) else  sub = mtype(type)." (".sub.")"
      if  final type#type then  sub = mtype(final type)." - ".sub
      put component(subject, sub)
      if  bitcomp&(1<<mess id)#0 start ; !set 'in reply to'
         take component(mess id, in reply)
         in reply = "Your message ".in reply
         put component(in reply to, in reply)
      else 
         bitcomp = bitcomp&(~(1<<in reply to))
      finish 
      put component(mess id, msg_mess id)
      bitcomp = bitcomp!1<<to!1<<body
      bitcomp = bitcomp&(1<<from!1<<to!1<<c date!1<<mess id!1<<in reply to!1<<subject!1<<body)
      if  final type>=mailer report and  conad#0 then  append to report(conad, len)
      msg cbeg(body) = report conad+report file header_start+1
      msg clen(body) = report file header_end-report file header_start-1
      i = (msg clen(body)+4095)>>10
      account user <- compress(mtype(type))
      distribute mail(msg, account user, ident, fsys, bitcomp, i, flag)
      if  msg_recip link=0 then  msg_status = 0
      i = ddisconnect(myname, "REPORTFILE", report fsys, 1); !+ destroy
      report conad = 0
   end ; !of mailer sends message



   integer  fn  next fsys
      integer  fsys
      fsys = last fsys
      until  fsys=last fsys cycle 
         if  pointers_discs(fsys)&3=2 and  f systems(fsys)#0 start 
            last fsys = fsys+1
            last fsys = 0 if  last fsys>max fsys
            result  = fsys
         finish 
         fsys = fsys+1
         fsys = 0 if  fsys>max fsys
      repeat 
      result  = myfsys
   end ; !of next fsys


   routine  take component(integer  component, string  name  s)

!      Copies a message component to a string skipping leading spaces

      integer  l, i
      if  bitcomp&(1<<component)=0 then  s = "" and  return 
      i = msg cbeg(component)
      l = i+msg clen(component)-1
      i = i+1 while  i<=l and  byteinteger(i)=' '
      l = l-i+1
      if  l>255 then  l = 255
      move(l, i, addr(s)+1)
      length(s) = l
   end ; !of take component



   routine  process oper req(record  (cf) name  p)

!      Processes mainframe OPER commands

      record  (finff) info
      record  (msg descriptor f) name  msg
      record  (addr entry f) name  addr entry
      integer  i, oper no, infoad, size, j, del
      string  (255) param, reply, s1, s2
      const  integer  max commands= 22
      switch  sw(1:max commands)
      const  string  (8) array  command(1:max commands)= c 
         "MON","CONFIG","STOP","PRINT","CLOSE","OPEN","OPENFSYS","UPDATE",
 "DUMP","TEST","CREATE","DISPLAY",
         "DELETE","UPDATERS","KICK","SHORTUPD","RETURN","MAILLIST",
 "KILLFSYS","MOVEFSYS","TIDYARCH","TAKECONF"

      if  p_s="" then  return 
      del = no
      oper no = p_srce>>8&7; !where message came from
      log print(dt."From OPER".itos(oper no)." ".p_s.snl)
      p_s = param.reply while  p_s->param.(" ").reply
      cycle  i = max commands, -1, 1
         if  p_s->s1.(command(i)).param and  s1="" then  ->sw(i)
      repeat 
      reply = "Invalid command ".p_s." ?"
      ->error
sw(1):                                   !monitoring on/off
      i = stoi(param)
      if  i>=0 then  monitoring = i and  return 
      if  "ON"#param#"OFF" then  ->bad parameter
      if  param="ON" then  monitoring = yes else  monitoring = no
      return 
sw(2):                                   !config
      if  param->s1.(".").s2 and  length(s1)=6 and  1<=length(s2)<=11 start 
         i = dsfi(myname, myfsys, get config name, 1, addr(param))
         if  i#0 then  printstring("DSFI set config fails ".derrs(i).snl)
      else 
         if  param#"?" then  ->bad parameter
         i = dsfi(myname, myfsys, get config name, 0, addr(param))
         if  i=0 then  printstring("Config ".param.snl) else  printstring("DSFI get config fails ".derrs(i).snl)
      finish 
      return 
sw(3):                                   !stop
      stop 
sw(4):                                   !print
      send and define(1, 64, "LP")
      return 
sw(5):                                   !close
      if  mailer state=closed then  reply = "Already closed" and  ->error
      mailer state = closed
      i = ddisconnect(myname, addrfile, myfsys, 0)
      if  i#0 then  printstring("Disconnect ADDRFILE fails:".derrs(i).snl)
      addr file conad = 0
      return 
sw(7):                                   !open fsys
      if  mailer state=closed then  printstring("Openfsys fails - mailer closed".snl) and  return 
      i = stoi(param)
      unless  0<=i<=max fsys then  ->bad parameter
      if  f systems(i)#0 then  printstring("Already open fsys ".param.snl) and  return 
      open file system(i)
      check descriptors(i)
      return 
sw(6):                                   !open
      if  mailer state=open then  reply = "Already opened" and  ->error
      connect tables
      if  addr file conad>0 start 
         i = connect config file("")
         if  i#0 then  i = connect config file(confbackup)
         if  i=0 start 
            mailer state = open
            if  name file conad=0 then  update tables
         finish 
         if  mailer state=open then  check descriptors(-1)
      finish  else  printstring("Cannot open".snl)
      return 
sw(8):                                   !update tables
      if  mailer state=closed then  printstring("Update fails - mailer closed".snl) and  return 
      update tables
      if  mailer state=closed then  return 
      delete junk(-1)
      i = monitoring
      monitoring = 8; !log descriptors
      check descriptors(-1)
      monitoring = i
      relink new rnames
      if  message count#0 then  check descriptors(-1)
      reset message count
      retell recipients
      update remote servers("UPDATE", 1, ad file_entries)
      update remote servers("UPDATEALL", 1, ad file_entries)
      printstring("Update complete".snl)
      return 
sw(9):                                   !dump
      unless  param->param.(",").reply.(",").s1 then  ->bad parameter
      if  param->s2.("M").param and  s2="" then  i = f systems(s to i(param)) else  start 
         i = addr file conad
      finish 
      if  i<=0 then  ->bad parameter
      selectoutput(1)
      dump(stoi(reply)+i, stoi(s1), i)
      selectoutput(0)
      return 
sw(10):                                  !test
      unless  param->s1.(",").s2 then  ->bad parameter
      cycle  i = 1, 1, ad file_entries
         addr entry == record(addr file conad+i*ad entry size)
         if  addr entry_server=s1 then  addr entry_server = s2
      repeat 
      printstring("Done".snl)
      return 
sw(11):                                  !create
      infoad = addr(info)
      i = dfinfo(myname, addrfile, myfsys, infoad)
      if  i#does not exist start 
         if  i=0 then  printstring("Already exists".snl) else  printstring("Dfinfo ADDRFILE fails:".derrs(i).snl)
         return 
      finish 
      i = dfinfo(myname, addrbackup, myfsys, infoad)
      if  i#does not exist start 
         i = newgen or rename(addrbackup, addrfile, addr file conad)
         if  i=0 then  printstring("ADDRFILE recreated from private backup".snl)
         return 
      finish 
      size = ad entry size*(default max rnames+1)
      create and connect(addrfile, myfsys, size, zerod!cherish, r!w!shared, i)
      if  i=0 then  return 
      ad file == record(i)
      ad file_start = ad entry size
      ad file_end = size
      ad file_size = (size+epage size-1)&(-epage size)
      ad file_datetime = current dt in secs!x'80000000'
      ad file_version = ad file version no
      i = dpermission(myname, "", "", addrfile, myfsys, 1, r)
      printstring("ADDRFILE created".snl)
      return 
sw(13):                                  !delete
      del = yes
sw(12):                                  !display
      i = s to ident(param)
      j = message addr(i)
      if  j=0 then  ->bad parameter
      msg == record(j)
      printstring("Message ident = ".ident to s(i).snl)
      printstring("Rname = ".msg_rname."  Managr = ".msg_managr."  Server = ".msg_server.snl)
      printstring("Mess ID = ".msg_mess id.snl)
      if  msg_dt sent#0 then  printstring("DT sent = ".secs to dt(msg_dt sent).snl)
      if  msg_dt received#0 then  printstring("DT received = ".secs to dt(msg_dt received).snl)
      if  msg_dt spooled#0 then  printstring("DT spooled = ".secs to dt(msg_dt spooled).snl)
      if  msg_dt after#0 then  printstring("DT after = ".secs to dt(msg_dt after).snl)
      if  msg_dt delivered#0 then  printstring("DT delivered = ".secs to dt(msg_dt delivered).snl)
      if  msg_dt deleted#0 then  printstring("DT deleted = ".secs to dt(msg_dt deleted).snl)
      printstring("Status = ".itos(msg_status)."  Ident = ".ident to s(msg_ident)."  Recip link = ".ident to s c 
         (msg_recip link).snl)
      printstring("Rtype = ".itos(msg_rtype)."  Rname link = ".ident to s(msg_rname link).snl)
      if  del=yes then  delete message(i, j)
      return 
sw(14):                                  !update rs
      if  mailer state=closed then  printstring("Update rs fails - mailer closed".snl) and  return 
      update remote servers("UPDATE", 1, ad file_entries)
      return 
sw(15):                                  !kick mailer stream
      p = 0
      p_dest = return file ack
      mail queue(p)
      return 
sw(16):                                  !shortupdate
      update tables
      if  mailer state=closed then  return 
      reset message count; !zeroed by update tables
      check descriptors(-1)
      printstring("Shortupd complete".snl)
      return 
sw(17):                                  !return old messages
      if  param#"" start 
         i = stoi(param)
         if  i>0 then  param = "" and  i = i*secs in 24hrs else  i = 0
      finish  else  i = default return period
      return old messages(param, i)
      return 
sw(18):                                  !create maillist
      i = stoi(param)
      unless  0<=i<=max fsys then  ->bad parameter
      create maillist(i)
      return 
sw(19):                                  !killfsys
      i = stoi(param)
      unless  0<=i<=max fsys then  ->bad parameter
      killfsys(i)
      return 
sw(20):                                  !movefsys
      if  param->s1.("TO").s2 start 
         i = stoi(s1)
         j = stoi(s2)
         if  0<=i<=max fsys and  0<=j<=max fsys start 
            movefsys(i, j)
            return 
         finish 
      finish 
      ->bad parameter
sw(21):                                  !tidyarch
      if  param="" then  i = myfsys else  i = stoi(param)
      unless  0<=i<=max fsys or  i=-1 then  ->bad parameter
      tidy archive(i)
      return 
sw(22):                                  !takeconf
      i = connect config file(param)
      if  i#0 then  i = connect configfile(myname.".".confbackup)
      if  i#0 start 
         mailer state = closed
         printstring("Mailer closed!!".tostring(17).snl)
      finish  else  printstring("Done".snl)
      return 
bad parameter:

      reply = "Invalid parameter ".param." ?"
error:
      printstring(reply.snl)
   end ; !of process oper req



   routine  deliver after message

!      Have received an alarm call - now deliver a time-determined delivery message

      record  (msg descriptor f) name  msg
      integer  ident, seg, gap, flag, dispose
      string  (11) file
      if  after linkhead=0 then  return 
      ident = after linkhead
      msg == record(message addr(ident))
      if  msg_dt after<=current dt in secs start 
         after linkhead = msg_ident
         if  msg_status=waiting start 
            file = ident to s(ident)
            seg = 0; gap = 0
            flag = dconnect(myname, file, ident>>24, r!w, 0, seg, gap)
            if  flag=0 start 
               dispose = -1; !its not really a remote file
               process remote file(seg<<18, file, msg_managr, dispose, "")
               flag = ddisconnect(myname, file, ident>>24, 0)
               if  flag#0 start 
                  log print(dt."Ddisconnect ".myname.".".file." fails: ".derrs(flag).snl)
               finish 
            else 
               log print(dt."Dconnect ".myname.".".file." fails: ".derrs(flag).snl)
            finish 
            delete message(ident, flag) unless  dispose=spoolr requeue
         finish 
      finish 
      place alarm call
   end ; !of deliver after message



   routine  decode spoolr reply(integer  reply, flag)

!      Interprets SPOOLR's reply to a request to transmit a file to
!      a remote server.

      integer  ident, i
      record  (msg descriptor f) name  msg

      if  reply=max reply index+1 then  return ; !mailer logfile
      if  0<=reply<=max reply index start 
         ident = spool reply index(reply)
         i = message addr(ident)
         if  i#0 start 
            msg == record(i)
            if  msg_status=spooling start 
               if  flag=0 start 
                  msg_dt spooled = current dt in secs
                  msg_status = spooled
               else 
                  printstring("Failed to spool ".ident to s(ident)." !!".snl)
                  !!!!SHOULD TRY AGAIN??
                  return to sender(msg, flag, ident)
               finish 
               spool reply index(reply) = 0
               return 
            finish 
         finish 
      finish  else  ident = 0
      log print(dt."Bad reply from FTRANS - index ".itos(reply).", ident ".ident to s(ident).snl)
   end ; !of decode spoolr reply



   integer  fn  msg for mailer(string  (15) srce, string  name  msg id)

!      This function handles messages from other mail servers which
!      contain amendments to the name/address directory

      integer  i, entry, pt, fin, its, iopt, records, fn, j
      integer  res, seg, gap, s conad, ident, flag
      string  (255) s, rname, dept, managr, t
      string  (6) file
      record  (msg descriptor f) name  t msg
      record  (msg descriptor f) name  msg
      record  (fhf) name  s header
      record  (addr entry f) name  addr entry
      const  integer  max commands= 4
      const  string  (9) array  coms(1:max commands)= c 
         "ACCREDIT","DISCREDIT","UPDATE", "FTP"
      switch  c(1:max commands)



      integer  fn  check record(integer  from, len)
         string  (255) ts, opt
         if  0<len<255 start 
            move(len, from, addr(s)+1)
            length(s) = len
            if  s->rname.(",").dept.(",").managr.(",").ts.(",").opt start 
               if  opt->opt.(snl) start 
               finish 
               ts = compress(ts)
               its = stoi(ts)
               iopt = stoi(opt)
               if  its#not assigned#iopt and  length(rname)<=31 and  length(dept)<=31 and  length(managr)<=15 start 
                  entry = lookup name(rname)
                  if  entry#0 then  addr entry == record(addr file conad+entry*ad entry size)
                  managr = printable(managr)
                  if  its>secs now then  its = secs now
                  result  = 0
               finish 
            finish 
         finish 
         s = "Invalid ".coms(fn).", record ".itos(records)." in file"
         log print(dt.s.snl)
         add to report(s)
         result  = 1
      end ; !of check record



      integer  fn  try to accredit
         if  entry=0 then  result  = accredit name(rname, dept, srce, managr, iopt, -1, its)
         if  addr entry_server#srce or  addr entry_managr#managr start 
            !conflict
            if  addr entry_timestamp<=its start 
               log print(dt."Rejecting ACCREDIT for ".rname." <".managr."@".srce."> ".dept.snl)
               if  addr entry_timestamp=its then  discredit entry(entry)
               !remove both
            else 
               discredit entry(entry); !supplants existing entry
               result  = accredit name(rname, dept, srce, managr, iopt, -1, its)
            finish 
         else ; !same entry, may be amendments
            addr entry_department = dept
            addr entry_options = iopt
            addr entry_timestamp = its
            addr entry_rname = rname
            addr entry_link = 0
         finish 
         result  = 0
      end ; !of try to accredit

      if  srce#"" start 
         take component(comments, s)
         s = compress(s)
         if  length(s)+length(msgid)>200 then  s = "?"
         dept <- dt."Remote msg ".msg id." from ".srce.", ".s." advice".snl
         log print(dept)
         records = 0
         cycle  fn = max commands, -1, 1
            if  s=coms(fn) then  ->c(fn)
         repeat 
         s = "bad function name"
      finish  else  s = "no source key"
      log print(dt."Invalid msg for MAILER - ".s.snl)
      add to report("Fails - ".s)
      result  = 1

c(1):                                    !accredit
      if  check record(msg cbeg(body), msg clen(body))#0 then  result  = 1
      i = try to accredit
      if  i#0 then  add to report("Accredit fails - ".err mess(i))
      result  = i

c(2):                                    !discredit
      if  check record(msg cbeg(body), msg clen(body))#0 then  result  = 1
      if  entry=0 then  s = " Rname not accredited" else  s = " not Rname owner"
      if  entry=0 or  managr#addr entry_managr or  srce#addr entry_server start 
         log print(dt."Rejecting DISCREDIT ".rname." <".managr."@".srce."> ".s.snl)
         add to report("Discredit fails - ".s)
         result  = 1
      finish  else  discredit entry(entry)
      result  = 0

c(3):                                    !update
      cycle  entry = 1, 1, ad file_entries
         addr entry == record(addr file conad+entry*ad entry size)
         if  addr entry_server=srce then  addr entry_link = -1
      repeat 
      pt = msg cbeg(body)
      fin = pt+msg clen(body)-1
      until  pt>=fin cycle 
         j = fin+1
         cycle  i = pt, 1, fin
            if  byteinteger(i)=nl then  j = i and  exit 
         repeat 
         if  check record(pt, j-pt)#0 then  result  = 1
         pt = j+1
         if  try to accredit#0 then  result  = 1
         records = records+1
      repeat 
      log print(dt.itos(records)." UPDATE records processed".snl)
      cycle  entry = 1, 1, ad file_entries
         addr entry == record(addr file conad+entry*ad entry size)
         if  addr entry_server=srce and  addr entry_link=-1 then  discredit entry(entry)
      repeat 
      result  = 0

c(4):                                    !ftp report
      take component(references, file); !get msg ident
      ident = s to ident(file)
      i = message addr(ident)
      res = 1
      if  i#0 start 
         t msg == record(i)
         if  t msg_status=spooled or  t msg_status=spooling start 
            take component(keywords, s); !gives result 0 or 1
            if  s="0" then  fn = 0 else  fn = 1
            s = "Message ".t msg_mess id." FTP ".file." to ".t msg_server
            msg == record(message addr(t msg_ident))
            if  fn=0 then  s = s." success" and  res = 0 else  start 
               s = s." fails"
               seg = 0; gap = 0
               flag = dconnect(myname, ident to s(t msg_ident), t msg_ident>>24, r, 0, seg, gap)
               if  flag=0 start 
                  take component(body, t); !spoolr's report on the failure
                  sconad = seg<<18
                  s header == record(s conad)
                  bitcomp = s header_bitcomp
                  cycle  i = 1, 1, max components
                     if  (bitcomp>>i)&1=1 start 
                        msg cbeg(i) = s header_cp(i)_beg+s conad
                        msg clen(i) = s header_cp(i)_len
                     finish 
                  repeat 
                  report conad = 0
                  add to report("The file transfer of your message to host ".t msg_server)
                  add to report("was not successful.  The transaction is given below -".snl)
                  add to report(t)
                  mailer sends message(my fsys, sconad, -1, mailer ftp failure, flag)
                  if  flag=0 then  res = 0 else  s = s." - report fails"
                  i = ddisconnect(myname, ident to s(t msg_ident), t msg_ident>>24, 0)
               finish  else  s = s.snl.dt."Connect ".ident to s(t msg_ident)." fails ".derrs(flag)
            finish 
            t msg_status = unused
            t msg_dt deleted = current dt in secs
            unlink msg(msg, ident, t msg_recip link)
            if  msg_recip link=0 then  delete message(t msg_ident, i)
         finish  else  s = "FTP report - message status wrong for ".file
      finish  else  s = "FTP report - invalid ident = ".file
      log print(dt.s.snl)
      if  res#0 then  add to report(s)
      result  = res
   end ; !of msg for mailer



   integer  fn  s to ident(string  (255) s)
      integer  i, j
      if  length(s)#6 then  result  = 0
      cycle  i = 1, 1, 6
         unless  '0'<=charno(s, i)<='9' then  result  = 0
      repeat 
      length(s) = 2
      i = stoi(s)
      length(string(addr(s)+2)) = 4
      j = stoi(string(addr(s)+2))
      result  = i<<24!j
   end ; !of s to ident



   routine  return old messages(string  (31) managr, integer  return period)

!      Searches for old undelivered messages and returns them to sender

      integer  return date, fsys, msg no, seg, gap, flag, old ident, conad, i
      record  (msg descriptor f) array  name  messages
      record  (msg descriptor f) array  format  msg af(1:max messages)
      record  (fhf) name  m file
      record  (msg descriptor f) name  r msg
      record  (msg descriptor f) name  old msg
      string  (10) s
      integer  name  link

      return date = current dt in secs
      if  return date<ad file_datetime then  printstring("Invalid DT set".snl) and  return 
      if  return period=0 then  return date = 0 else  return date = return date-return period
      cycle  fsys = 0, 1, max fsys
         if  f systems(fsys)#0 start 
            messages == array(f systems(fsys)+message entry size, msg af)
            cycle  msg no = 1, 1, max messages
               if  messages(msg no)_status=sending and  (messages(msg no)_dt sent<return date or  c 
                  ""#managr=messages(msg no)_managr) start 
                  old ident = fsys<<24!msg no
                  old msg == messages(msg no)
                  seg = 0; gap = 0
                  flag = dconnect(myname, ident to s(old ident), fsys, r, 0, seg, gap)
                  if  flag#0 start 
                     printstring("Connect MAILER.".ident to s(old ident)." fails ".derrs(flag).snl)
                     old msg_status = unused
                     continue 
                  finish 
                  report conad = 0
                  add to report("The message listed below was uncollected by the".snl. c 
                     "following recipient(s) and has been deleted:")
                  link == old msg_recip link
                  while  link#0 cycle 
                     r msg == record(message addr(link))
                     link == r msg_recip link
                     if  r msg_mess id#old msg_mess id then  printstring("Bad link!!".snl) and  return 
                     add to report("    ".r msg_rname." <".r msg_managr."@".r msg_server.">")
                     r msg_status = unused
                  repeat 
                  conad = seg<<18
                  m file == record(conad)
                  bitcomp = m file_bitcomp
                  i = lookup host(old msg_server)
                  if  (i>0 and  station_flags&local host flag#0) or  bitcomp&(1<<ack to)#0 start 
                     cycle  i = max components, -1, 1
                        if  (bitcomp>>i)&1=1 start 
                           msg cbeg(i) = m file_cp(i)_beg+conad
                           msg clen(i) = m file_cp(i)_len
                        finish 
                     repeat 
                     mailer sends message(fsys, conad, -1, mailer returned msg, flag)
                     if  flag=0 then  s = "succeeds" else  s = "fails"
                  finish  else  s = "suppressed"
                  log print(dt."Returning msg ".old msg_mess id." at ".ident to s(old ident)." to ".old msg_server. c 
                     " ".s.snl)
                  i = ddisconnect(myname, ident to s(old ident), fsys, 0)
                  if  flag=0 then  delete message(old ident, flag)
               finish 
            repeat 
         finish 
      repeat 
   end ; !of return old messages


   routine  update remote servers(string  (15) action, integer  loopstart, loopend)

!      Generates a file containing a one line entry extracted from
!      the address table for each locally accredited R-name and transmits
!      it to remote mail servers.

      record  (fhf) name  file header
      record  (msg descriptor f) name  msg
      record  (addr entry f) name  addr entry
      integer  serv no, entry, ident, nkb, flag, x, update type
      string  (255) s, s1, s2
      string  (31) dts
      string  (47) msg id

      if  action="UPDATEALL" then  update type = update copy flag else  update type = update flag
      message count = 0
      cycle  x = 1, 1, pointers_stations
         station == record(station offset+x*station entry size)
         if  station_flags&update type=update type start 
            report conad = 0
            report fsys = myfsys
            report full = 0
            cycle  entry = loopstart, 1, loopend
               addr entry == record(addr file conad+entry*ad entry size)
               if  update type&update copy flag=update copy flag or  addr entry_server=this short host start 
                  s = addr entry_rname
                  s = s.s1 while  s->s.(",").s1
                  s1 = addr entry_department
                  s1 = s1.s2 while  s1->s1.(",").s2
                  if  update type&update copy flag=update copy flag then  s2 = addr entry_server else  c 
                     s2 = "X".htos(addr entry_timestamp, 8)
                  s = s.",".s1.",".addr entry_managr.",".s2.",".itos(addr entry_options)
                  s = s.s1 while  s->s.(snl).s1
                  add to report(s)
               finish 
            repeat 
            if  report conad>0 then  add to report("")
            if  report conad<=0 or  report full=1 start 
               printstring("Create remote server update fails".snl)
               return 
            finish 
            bitcomp = 1<<body
            file header == record(report conad)
            msg cbeg(body) = report conad+file header_start+1
            msg clen(body) = file header_end-file header_start-1
            s = myname."@".this host
            put component(from, s)
            secs now = current dt in secs
            dts = secs to dt(secs now)
            put component(c date, dts)
            put component(comments, action)
            ident = get next descriptor(myfsys)
            if  ident=0 then  return 
            msgid = "<".dts."  ".ident to s(ident)."@".this short host.">"
            put component(messid, msgid)
            msg == record(message addr(ident))
            msg = 0
            msg_mess id = msgid
            msg_dt sent = secs now
            msg_managr = myname
            msg_server = this short host
            msg_mess id = msg id
            server map top = 0
            nkb = (msg clen(body)+1124)>>10
            cycle  serv no = 1, 1, pointers_stations
               station == record(station offset+servno*station entry size)
               if  station_flags&update type=update type start 
                  stat space offset = addr(station_string space(0))
                  s1 = "mailer@".statstring(station_name)
                  process recipients(addr(s1)+1, length(s1), to, no report)
               finish 
            repeat 
            distribute mail(msg, "UPDAT", ident, myfsys, bitcomp, nkb, flag)
            if  msg_recip link#0 then  msg_status = sending
            exit 
         finish 
      repeat 
      flag = ddisconnect(myname, "REPORTFILE", myfsys, 1); !+destroy
      report conad = 0
   end ; !of update remote servers



   routine  check descriptors(integer  fsys spec)

!      Searches the MAILLIST file on the specified fsys and checks
!      the consistency of all SENDING and RECEIVING descriptors.

      integer  entry, fsys, msg no, i, flag, ident, send ident, res, log
      integer  previous after linkhead
      string  (8) state
      string  (31) s
      record  (addr entry f) name  addr entry
      record  (msg descriptor f) array  name  messages
      record  (msg descriptor f) array  format  msg af(1:max messages)
      record  (msg descriptor f) name  sent msg
      byte  integer  array  use(1:max messages)
      byte  integer  array  visit(1:max messages)


      integer  fn  set(byte  integer  array  name  a, integer  no)
         if  a(no)=1 then  result  = 1
         a(no) = 1
         result  = 0
      end ; !of set

      if  fsys spec=-1 start 
         cycle  entry = 1, 1, ad file_entries
            addr entry == record(addr file conad+entry*ad entry size)
            if  addr entry_rname#"" then  addr entry_link = 0
         repeat 
         ad file_anon link = 0
         after linkhead = 0
      finish 
      if  monitoring&8#0 then  log = yes else  log = no
      log print(dt."Check descriptors".snl)
      message count = 0
      previous afterlinkhead = after linkhead
      cycle  fsys = 0, 1, max fsys
         if  (fsys spec=-1 or  fsys spec=fsys) and  f systems(fsys)#0 start 
            messages == array(f systems(fsys)+message entry size, msg af)
            fill(max messages, addr(use(1)), 0)
            fill(max messages, addr(visit(1)), 0)
            cycle  msg no = 1, 1, max messages
               ident = fsys<<24!msg no
               if  messages(msg no)_status&sending#0 start 
                  if  set(visit, msg no)=0 then  messages(msg no)_recip link = 0
               else 
                  flag = set(use, msg no); !set use
                  if  waiting#messages(msg no)_status#unused start 
                     send ident = messages(msg no)_ident
                     i = message addr(send ident)
                     sent msg == record(i)
                     cycle  res = 1, 1, 10
                        if  i#0 and  sent msg_status=sending and  messages(msg no)_mess id=sent msg_mess id then  c 
                           res = 0 and  exit 
                        *lss_(3); *ush_-26
                        *and_3; *st_flag
                        printstring("Mailer descr, OCP=".itos(flag).snl)
                     repeat 
                     if  res=0 start 
                        if  set(visit, send ident&x'FFFFFF')=0 then  sent msg_recip link = 0
                        flag = set(use, send ident&x'FFFFFF')
                        !set use
                        if  messages(msg no)_status&(spooling!spooled)#0 start 
                           messages(msg no)_recip link = sent msg_recip link
                           sent msg_recip link = ident
                           if  messages(msg no)_status=spooled then  state = "spooled" else  state = "spooling"
                        else 
                           if  shortform(messages(msg no)_server)=this short host then  c 
                              entry = lookup name(messages(msg no)_rname) else  entry = 0
                           link recipient(sent msg, messages(msg no), ident, entry, -1, dont tell)
                           state = "received"
                        finish 
                        if  log=yes start 
                           if  messages(msg no)_rname#"" then  s = messages(msg no)_rname else  c 
                              s = "[".messages(msg no)_managr."]"
                           log print(dt."ID=".messages(msg no)_mess id.", ".state." for ".s." at ".ident to s c 
                              (ident).snl)
                        finish 
                     else 
                        printstring("Inconsistent descriptor, ident=".ident to s(ident).", ID=".messages(msg no) c 
                           _mess id.", s ident=X".h to s(send ident, 8).snl)
                        messages(msg no)_status = unused
                     finish 
                  else 
                     if  messages(msg no)_status=waiting start 
                        link after(messages(msg no), ident)
                        log print(dt."ID=".messages(msg no)_mess id." from ".messages(msg no)_rname.", waiting ". c 
                           secs to dt(messages(msg no)_dt after).snl) if  log=yes
                     finish 
                  finish 
               finish 
            repeat 
            cycle  msg no = 1, 1, max messages
               if  set(use, msg no)=0 start 
                  printstring("Message ".messages(msg no)_mess id." has zero recipients".snl)
                  delete message(fsys<<24!msg no, flag)
               finish 
            repeat 
         finish 
      repeat 
      if  previous after linkhead#after linkhead then  place alarm call
      log print(dt.itos(message count)." messages outstanding for Fsys ".itos(fsys spec).snl)
   end ; !of check descriptors



   routine  reset message count

!      Resets the DSFI record showing message count for a process

      integer  array  count(1:ad file_entries)
      integer  i, j, last, flag, c, link
      integer  u, top user, fsys
      const  integer  max users= 1000
      string  (6) array  user(1:max users)
      record  (addr entry f) name  addr entry
      record  (addr entry f) name  addr entry2
      record  (msg descriptor f) name  msg

      log print(dt."Reset message count".snl)
      cycle  i = 1, 1, ad file_entries
         addr entry == record(addr file conad+i*ad entry size)
         if  addr entry_server=this short host start 
            c = 0
            last = i
            link = addr entry_link
            while  link#0 cycle 
               c = c+1
               msg == record(message addr(link))
               link = msg_rname link
            repeat 
            count(i) = c
            j = 1
            while  j<i cycle 
               addr entry2 == record(addr file conad+j*ad entry size)
               if  addr entry2_managr=addr entry_managr and  addr entry2_server=this short host and  c 
                  count(j)>=0 start 
                  count(i) = count(i)+count(j)
                  count(j) = -1
                  exit 
               finish 
               j = j+1
            repeat 
         finish  else  count(i) = -1
      repeat 
      cycle  i = 1, 1, last
         if  count(i)>=0 start 
            addr entry == record(addr file conad+i*ad entry size)
            flag = dsfi(addr entry_managr, addr entry_fsys, msg indicator, 1, addr(count(i)))
            if  flag#0 start 
               log print(dt."DSFI43 fails for ".addr entry_managr.derrs(flag).snl)
            else 
!               %if count(i) > max outstanding %then detachjob for accept(addr entry_managr,                             %c
!                  addr entry_fsys,addr entry_rname)
               if  count(i)>maxoutstanding start 
                  log print("DETACHING FOR ".addr entry_managr.snl)
               finish 
            finish 
         finish 
      repeat 
      top user = 0
      link = ad file_anon link
      while  link#0 cycle 
         msg == record(message addr(link))
         u = 1
         while  u<=top user cycle 
            if  user(u)=msg_managr then  count(u) = count(u)+1 and  exit 
            u = u+1
         repeat 
         if  u>top user start 
            if  u>max users then  exit ; !cant handle any more
            top user = top user+1
            user(top user) = msg_managr; !add to table
            count(top user) = 1
         finish 
         link = msg_rname link
      repeat 
      u = 1
      while  u<=top user cycle 
         fsys = -1
         flag = dfsys(user(u), fsys)
         if  flag=0 start 
            flag = dsfi(user(u), fsys, msg indicator, 1, addr(count(u)))
!           %if COUNT(U)>MAX OUTSTANDING %then DETACHJOB FOR ACCEPT(USER(U),FSYS,"")
         else 
            log print(dt."Dfsys ".user(u)." fails : ".derrs(flag).snl)
         finish 
         u = u+1
      repeat 
   end ; !of reset message count



   routine  relink new rnames

!     Checks each anon-link message. If any recipients now have a valid
!     R-name then amend the message descriptor accordingly

      integer  link, flag, entry
      string  (31) sfisurname
      record  (msg descriptor f) name  msg
      record  (addr entry f) name  addr entry

      log print(dt."Relink new rnames".snl)
      link = ad file_anon link
      message count = 0
      while  link#0 cycle 
         msg == record(message addr(link))
         if  msg_status&outbound=0 start 
            printstring("Bad anonlink for ident ".ident to s(link).snl)
            return 
         finish 
         if  shortform(msg_server)=this short host start 
            flag = dsfi(msg_managr, -1, getsfisurname, 0, addr(sfisurname))
            if  flag=0 start 
               entry = lookup name(sfisurname)
               if  entry#0 start 
                  addr entry == record(addr file conad+entry*ad entry size)
                  if  msg_managr=addr entry_managr and  this short host=addr entry_server start 
                     msg_rname = addr entry_rname
                     message count = 1
                     log print(dt."Message ".msg_mess id." recipient ".msg_rname." relinked to ".addr entry_rname. c 
                        snl)
                  finish 
               finish 
            else 
               log print(dt."DSFI fails for user ".msg_managr.", message ".msg_mess id.derrs(flag).snl)
               entry = 0
            finish 
         finish 
         link = msg_rname link
      repeat 
   end ; !of relink new rnames



   routine  retell recipients

!     Checks if any users with uncollected mail have logged on since
!     the last TELL message was issued - if so issue another!
!     Also corrects any msg descriptors where the recipient has emigrated
!     to another host.


      integer  fn  adjust(integer  logon)
         integer  days
         if  logon<0 then  result  = logon&x'7FFFFFFF'
         days = kday(logon>>17&x'1F', logon>>22&x'F', (logon>>26)+70)-days70
         result  = days*secs in 24 hrs+(logon>>12&x'1F')*3600+(logon>>6&x'3F')*60+(logon&x'3F')
      end ; !of adjust


      integer  entry, flag, logon, logon2, l, link, last 24 hours
      string  (63) s
      record  (msg descriptor f) name  msg, msg2
      record  (addr entry f) name  addr entry
      log print(dt."Retell recipients".snl)
      message count = 0
      secs now = current dt in secs
      cycle  entry = 1, 1, ad file_entries
         addr entry == record(addr file conad+entry*ad entry size)
         if  addr entry_link#0 start 
            if  addr entry_server=this short host start 
               flag = dsfi(addr entry_managr, addr entry_fsys, get last logon, 0, addr(logon))
               if  flag=0 start 
                  logon = adjust(logon)
                  if  addr entry_dt last told<logon<secs now start 
                     message count = message count+1
                     s = "Outstanding message(s)"
                     if  addr entry_options=alias option then  s = s." for ".addr entry_rname
                     l = length(s)
                     flag = dmessage(addr entry_managr, l, 1, addr entry_fsys, addr(s)+1)
                     addr entry_dt last told = secs now
                     if  0#flag#process na then  c 
                        printstring(dt."Dmessage ".addr entry_managr." fails ".derrs(flag).snl) else  c 
                        printstring(dt."Retell ".addr entry_managr.snl)
                  finish 
               else 
                  log print(dt."DSFI logon fails for ".addr entry_managr." ".derrs(flag).snl)
               finish 
            else ; !recipient has moved
               link = addr entry_link
               until  link=0 cycle 
                  msg == record(message addr(link))
                  msg_rname = ""; !so its not linked to addr file
                  log print(dt."Message ".msg_mess id." at ".ident to s(link)." relinked".snl)
                  link = msg_rname link
               repeat 
            finish 
         finish 
      repeat 

!  Now check messages not linked via ADDR file

      last 24 hours = secs now-secs in 24 hrs
      s = "Outstanding message"
      l = length(s)
      link = ad file_anon link
      while  link#0 cycle 
         msg == record(message addr(link))
         if  msg_status#received start 
            printstring("Bad anon link for ident ".ident to s(link).snl)
            return 
         finish 
         if  msg_dt told=last 24 hours then  link = msg_rname link and  continue 
         flag = dsfi(msg_managr, -1, get last logon, 0, addr(logon))
         if  flag=0 start 
            logon = adjust(logon)
            if  last 24 hours<logon<secs now and  msg_dt received<logon start 
               message count = message count+1
               flag = dmessage(msg_managr, l, 1, -1, addr(s)+1)
               if  0#flag#process na then  log print(dt."Dmessage ".msg_managr." fails ".derrs(flag).snl) else  c 
                  printstring(dt."Retell ".msg_managr.snl)
            finish 
            msg2 == msg
            while  msg2_rname link#0 cycle 
               msg2 == record(message addr(msg2_rname link))
               if  msg2_managr=msg_managr then  msg2_dt told = last 24 hours
            repeat 
         else 
            log print(dt."DSFI logon fails for ".msg_managr." ".derrs(flag).snl)
         finish 
         link = msg_rname link
      repeat 
      log print(dt."Total retells = ".itos(message count).snl)
   end ; !of retell recipients



   integer  fn  soundex(string  (31) name)
      const  string  (1) array  stable('A':'Z')= c 
         "",  "1", "2", "3", "",  "1",
         "2", "",  "",  "2", "2", "4",
         "5", "5", "",  "1", "2", "6",
         "2", "3", "",  "1", "",  "2",
         "",  "2"
      string  (31) work, s
      integer  ptr, last, this

      length(name) = length(name)-1 while  c 
         length(name)>0 and  (charno(name, length(name))='.' or  charno(name, length(name))=' ')
      cycle 
         if  name->s.(".").name then  continue 
         if  name->s.(" ").name then  continue 
         exit 
      repeat 
      name = compress(name)
      if  name="" then  result  = 1
      if  (name->work.("MAC").name and  work="") or  (name->work.("MC").name and  work="") start 
         work = "@"
         last = '@'
         ptr = 1
      else 
         last = charno(name, 1)
         work = tostring(last)
         ptr = 2
      finish 
      while  ptr<=length(name) cycle 
         this = charno(name, ptr)
         if  'A'<=this<='Z' and  this#last start 
            last = this
            work = work.stable(this)
         finish 
         ptr = ptr+1
      repeat 
      work <- work."0000"
      move(4, addr(work)+1, addr(ptr))
      result  = ptr
   end ; !of soundex



   routine  detachjob for accept(string  (6) user, integer  fsys, string  (255) rname)
      integer  caddr, flag, suffix
      string  (11) jfilename
      string  (255) s, text
      record  (pe) p
      if  db conad=0 or  db_jobtext="" start 
         log print(dt."No jobtext for user ".user." rname ".rname.snl)
      else 
         create and connect("JOBFILE", fsys, 4095, zerod, r!w, caddr)
         if  caddr#0 start 
            text = db_jobtext.rname.snl
            string(caddr+31) = text
            integer(caddr) = integer(caddr)+length(text)
            flag = ddisconnect(myname, "JOBFILE", fsys, 0)
            suffix = current dt in secs
            suffix = suffix-(suffix//100)*100
            jfilename = "M#JOB".itos(suffix)
            flag = dtransfer(myname, user, "JOBFILE", jfilename, fsys, fsys, 1)
            if  flag=0 start 
               s = "DOCUMENT DEST=BATCH,SRCE=".jfilename.",START=32,NAME=MAILJOB,LENGTH=".itos(length(text)). c 
                  ",OUT=FILE,OUTNAME=.NULL"
               p = 0
               string(addr(p_p1)) = "SPOOLR"
               flag = dsubmit(p, length(s), addr(s)+1, spool log reply, user)
            finish 
            s = derrs(flag)
         finish  else  s = "create file fails"
         log print(dt."Dsubmit/transfer job for ".user.", flag = ".s.snl)
      finish 
   end ; !of detachjob for accept



   string  fn  lcstring(string  (255) s)
      integer  i
      for  i = addr(s)+1, 1, addr(s)+length(s) cycle 
         if  'A'<=byteinteger(i)<='Z' then  byteinteger(i) = byteinteger(i)+32
      repeat 
      result  = s
   end ; !of lcstring


   routine  connect dbfile(integer  name  flag)

!     Connect the distribution list/bulletin board file for use.  Also takes a new dbfile in service.

      integer  seg, gap
      if  db conad=0 start 
         seg = 0; gap = 0
         flag = dconnect(myname, dbfile, myfsys, r!w!shared, apf, seg, gap)
         if  flag=0 start 
            db conad = seg<<18
            db == record(db conad)
            return 
         finish 
         printstring("Connect DBFILE fails: ".derrs(flag).snl)
      finish 
      flag = newgen or rename(newdbfile, dbfile, db conad)
      printstring("Take new DBFILE, flag = ".derrs(flag).snl)
      if  db conad#0 start 
         db == record(db conad)
         flag = dpermission(myname, db_bowner, "", dbfile, myfsys, 2, r!w)
         !w access to owner
      finish 
   end ; !of connect dbfile



   integer  fn  connect config file(string  (31) filename)

!     Connects the network configuration file.  Normally shared access to FTRANS.CFILE
!     Can take a new file during service.

      integer  seg, gap, flag, i, conad
      string  (31) user, file, u, f
      record  (fhf) name  file header

      if  filename="" start 
         flag = dsfi(my name, my fsys, 2, 0, addr(filename))
      finish  else  flag = 0
      if  flag=0 start 
         if  filename->user.(".").file and  length(user)=6 and  length(file)<=11 start 
            if  config conad#0 start ; !disconnect the current file
               if  current configfile->u.(".").f start 
                  flag = ddisconnect(u, f, myfsys, 0)
                  if  flag#0 then  flag = ddisconnect(u, f, -1, 0)
                  if  flag#0 start 
                     printstring("Ddisconnect ".current configfile." fails: ".derrs(flag).snl)
                  finish  else  config conad = 0
               finish 
            finish 
            seg = 0; gap = 0
            flag = dconnect(user, file, myfsys, r!shared, 0, seg, gap)
            if  flag#0 then  flag = dconnect(user, file, -1, r!shared, 0, seg, gap)
            if  seg#0 start 
               current configfile = filename; !remember filename
               config conad = seg<<18
               file header == record(config conad)
               pointers == record(config conad+file header_start)
               station offset = config conad+pointers_station displ-station e                     ntry size
               hasht == pointers_hasht
               this host = ""; this short host = ""
               this institution = ""
               flag = 1
               for  i = 1, 1, pointers_stations cycle ; !look for this host & institution
                  station == record(station offset+i*station entry size)
                  stat space offset = addr(station_string space(0))
                  if  station_flags&this host flag#0 start 
                     this host = statstring(station_name)
                     this short host = statstring(station_shortest name)
                     this lc host = lcstring(this host)
                  else 
                     if  station_flags&this auth flag#0 start 
                        this institution = statstring(station_name)
                        this lc institution = lcstring(this institution)
                     finish 
                  finish 
                  if  this host#"" and  this institution#"" then  flag = 0 and  exit 
               repeat 
               if  length(this short host)>15 start 
                  printstring("Shortname for thishost too long!: ".this short host.snl)
                  flag = 1
               finish 
               if  flag=0 start 
                  create and connect("X".confbackup, myfsys, file header_end, zerod!cherish, r!w, conad)
                  if  conad>0 start 
                     move(file header_end, config conad, conad)
                     i = ddisconnect(myname, "X".confbackup, myfsys, 0)
                     if  i=0 start ; !replace the old backup
                        i = dnewgen(myname, "X".confbackup, confbackup, myfsys)
                        if  i#0 then  i = drename(myname, "X".confbackup, confbackup, myfsys)
                        if  i#0 then  printstring("Dnewgen/rename X".confbackup." fails: ".derrs(i).snl)
                     finish  else  printstring("Ddisconnect X".confbackup." fails: ".derrs(i).snl)
                  finish 
                  result  = 0
               finish 
               printstring("Invalid config file!!!".snl)
            finish  else  printstring("Dconnect ".filename." fails :".derrs(flag).snl)
         finish  else  printstring("Invalid configfile name: ".filename.snl)
      finish  else  printstring("Config from index fails: ".derrs(flag).snl)
      result  = 1
   end ; !of connect config file


   routine  update tables

!    Checks every entry in the address table to make sure that a user
!    has not been discredited or has not set zero permission to MAILER.
!    An entry stays if its fsys is currently off-line.  Also checks
!    whether the SFI surname has changed - adjusts table accordingly.
!    Initialises name table, resets count, and adds names to new name table.
!    Then checks all users on all file systems and accredits new
!    users (except those with perm=zero to MAILER)
!    Reports users with conflicting names.
!    Finally sorts the name table alphabetically if it has been changed.

!   CHANGE??: disc on-line but not in pointers, causes discredit entry

      integer  i, j, permad, fsys, entry, n, f, flag, size, last, zero
      integer  char, nf conad, new conad, next slot, new max rnames
      integer  array  a(0:max fsys)
      record  (addr entry f) name  addr entry
      record  (addr entry f) name  addr entry2
      record  (ad file f) name  new ad file
      record  (perm f) perm
      record  (usf) array  nn(0:511)
      string  (31) sfisurname


      integer  fn  perm to mailer
         integer  i, j
         i = (perm_bytes-16)//8
         j = 0
         while  j<i cycle 
            if  perm_i prms(j)_user=mailer start 
               if  perm_i prms(j)_uprm=no then  result  = no
            finish 
            j = j+1
         repeat 
         result  = yes
      end ; !of perm to mailer

!  Create new name table

      if  ad file_entries+200>max rnames then  new max rnames = max rnames+200 else  new max rnames = max rnames
      if  new max rnames>abs max rnames then  c 
         printstring("MAILER - too many names!!".snl) and  new max rnames = abs max rnames
      after linkhead = 0
      if  name file conad#0 then  name file_datetime = 0; !tells users 'bad medicine'
      mailer state = closed
      name file conad = 0
      flag = ddisconnect(myname, snamefile, myfsys, 0)
      if  flag#0 then  printstring("Disconnect NAMEFILE fails: ".derrs(flag).snl)
      size = name file header+new max rnames*name entry size
      create and connect("N".snamefile, myfsys, size, zerod!tempfi, r!w, nf conad)
      if  nf conad=0 start 
         printstring("MAILER closed".snl)
         return 
      finish 
      name file == record(nf conad)
      n table == array(nf conad+name file header, name table arf)
      startchar == name file_startchar
      permad = addr(perm)
      secs now = current dt in secs
      last = 0

!  Check existing entries in the address table

      cycle  entry = max rnames, -1, 1
         addr entry == record(addr file conad+entry*ad entry size)
         addr entry_link = 0
         if  addr entry_server=this short host start 
again:
            flag = dpermission(addr entry_managr, "", "", "", addr entry_fsys, get index list, permad)
            if  flag#ok start 
               fsys = -1
               i = dfsys(addr entry_managr, fsys)
               if  i=ok and  fsys#addr entry_fsys start ; !found on another fsys
                  log print(" - moving ".addr entry_rname." to fsys ".itos(fsys).snl)
                  addr entry_fsys = fsys
                  ->again
               finish 
               if  flag=fsys not available start 
                  !users fsys currently off-line
                  log print(" - ".addr entry_rname." at ".addr entry_managr." off-line".snl)
               else 
                  if  flag=user not known start 
                     !user discredited
                     log print(" - removing ".addr entry_rname." at ".addr entry_managr." ".derrs(flag).snl)
                     addr entry = 0
                  else ; !some failure in dperm
                     log print(" - DPERM ".addr entry_rname." at ".addr entry_managr." fails:".derrs(flag).snl)
                  finish 
               finish 
            else ; !got perm
               if  perm to mailer=no and  addr entry_options=sfioption start 
                  log print(" - Removing ".addr entry_rname." at ".addr entry_managr." no permission".snl)
                  addr entry = 0
               else 
                  flag = dsfi(addr entry_managr, addr entry_fsys, get sfi surname, 0, addr(sfisurname))
                  if  flag=0 and  sfisurname#addr entry_rname and  addr entry_options=sfioption start 
                     log print(" - removing ".addr entry_rname." at ".addr entry_managr." new sname".snl)
                     addr entry = 0
                  finish 
               finish 
            finish 
         finish ; !not this server, cant check
         if  addr entry_rname#"" start 
            if  last=0 then  last = entry
         else 
            if  last#0 start 
               addr entry2 == record(addr file conad+last*ad entry size)
               addr entry = addr entry2
               addr entry2 = 0
               last = last-1
            finish 
         finish 
      repeat 

!  Next collect names into new name table.

      if  last>0 start 
         cycle  entry = 1, 1, last
            addr entry == record(addr file conad+entry*ad entry size)
            n table(entry)_rname = compress(addr entry_rname)
            n table(entry)_soundex = entry
         repeat 
      finish 
      cycle  i = '[', -1, 'A'
         startchar(i) = 0
      repeat 
      name file_extrastart = 1
      name file_extraend = last
      ad file_entries = last

!  Now check for newly accredited users

      get av fsys(f, a)
      zero = 0
      cycle  f = 0, 1, f-1
         if  pointers_discs(a(f))&2=0 start 
            printstring("Not updating fsys ".itos(a(f))." - not in config file".snl)
            continue 
         else 
            log print(dt."Updating fsys ".itos(a(f)).snl)
         finish 
         flag = get usnames2(nn, n, a(f))
         if  flag=0 start 
            j = 0
            while  j<n cycle 
               flag = dsfi(nn(j)_user, a(f), msg indicator, 1, addr(zero))
               if  flag#0 start 
                  log print("DSFI43 fails for ".nn(j)_user.derrs(flag).snl)
               finish 
               flag = dsfi(nn(j)_user, a(f), get sfi surname, 0, addr(sfisurname))
               if  nn(j)_user=mailer or  nn(j)_user="SPOOLR" or  nn(j)_user="VOLUMS" or  nn(j)_user="JOBBER" then  c 
                  sfisurname = ""
               if  flag=0 start 
                  if  sfisurname#"" start 
                     entry = lookup name(sfisurname)
                     if  entry=not found start 
                        flag = dpermission(nn(j)_user, "", "", "", a(f), get index list, permad)
                        if  flag=0 start 
                           if  perm to mailer=yes start 
                              flag = accredit name(sfisurname, "", this short host, nn(j)_user, sfioption, a(f),
                                  secs now)
                              if  flag#ok start 
                                   log print(" - accredit ".nn(j)_user." fails: ".err mess(flag).snl)
                              finish 
                           else 
                              log print(" - cant accredit ".nn(j)_user." no permission".snl)
                           finish 
                        finish  else  printstring("Update - DPERM fails for ".nn(j)_user." ".derrs(flag).snl)
                     else ; !same user?
                        addr entry == record(addr file conad+entry*ad entry s                              ize)
                        if  compress(addr entry_rname)=compress(sfisurname) and  nn(j)_user=addr entry_managr and  c 
                           addr entry_fsys=a(f) and  addr entry_server=this short host start 
                           if  addr entry_options=alias option then  addr entry_options = sfi option
                        else 
                           log print(" - cant accredit ".nn(j)_user." sname='".sfisurname."' same as ". c 
                              addr entry_managr."@".addr entry_server.snl)
                        finish 
                     finish 
                  else 
                     log print(" - cant accredit ".nn(j)_user." - null sname".snl)
                  finish 
               finish  else  printstring("Update - DSFI fails for ".nn(j)_user." ".derrs(flag).snl)
               j = j+1
            repeat 
         finish  else  printstring("Update - GETUS fails, fsys ".itos(a(f))." ".derrs(flag).snl)
      repeat 

!  Now sort the name table.

      cycle  i = 1, 1, name file_extraend
         n table(i)_soundex = i
      repeat 
      table sort(name file_extraend)

!  Set pointers (A -> Z+1) to partition the name table alphabetically

      i = 1
      cycle  char = 'A', 1, '['
         i = i+1 while  i<=name file_extraend and  char>charno(n table(i)_rname, 1)
         startchar(char) = i
      repeat 
      name file_extrastart = i
      name file_datetime = current dt in secs

!   Create new addrfile and move sorted entries into it.
!   Then newgen both files.

      size = ad entry size*(new max rnames+1)
      create and connect("N".addrfile, myfsys, size, zerod!tempfi, r!w, new conad)
      if  new conad#0 start 
         new ad file == record(new conad)
         new ad file_start = ad entry size
         new ad file_end = size
         new ad file_size = (size+epagesize-1)&(-epagesize)
         next slot = newconad+ad entry size
         flag = 0
         cycle  i = 1, 1, ad file_entries
            unless  0<n table(i)_soundex<=ad file_entries then  flag = 1 and  exit 
            move(ad entry size, addr file conad+n table(i)_soundex*ad entry size, next slot)
            sfisurname <- string(next slot)
            n table(i)_soundex = soundex(sfisurname)
            next slot = next slot+ad entry size
         repeat 
         new ad file_entries = ad file_entries
         new ad file_datetime = name file_datetime
      finish  else  flag = 1
      if  flag=0 then  flag = newgen or rename("N".snamefile, snamefile, name file conad)
      if  flag=0 then  flag = newgen or rename("N".addrfile, addrfile, addr file conad)
      if  flag#0 start 
         mailer state = closed
         printstring("Mailer closed".snl)
         return 
      finish 
      ad file == record(addr file conad)
      name file == record(name file conad)
      n table == array(name file conad+name file header, name table arf)
      startchar == name file_startchar
      connect or create(myname, addrbackup, myfsys, ad file_end, cherish!zerod, new conad)
      if  new conad>0 start ; !make a backup copy
         move(ad file_end, addr file conad, newconad)
         flag = ddisconnect(myname, addrbackup, myfsys, 0)
      finish 
      printstring("Tables updated".snl)
      log print(dt."R-names accredited = ".itos(name file_extraend).snl)
      mailer state = open
   end ; !of update tables



   integer  fn  newgen or rename(string  (11) newfile, file, integer  name  conad)
      integer  seg, gap, flag, f
      conad = 0
      flag = ddisconnect(myname, newfile, myfsys, 0)
      flag = ddisconnect(myname, file, myfsys, 0)
      flag = dnewgen(myname, file, newfile, myfsys)
      if  flag#0 start 
         flag = drename(myname, newfile, file, myfsys)
         if  flag#0 start 
            printstring("Newgen/rename ".file." fails ".derrs(flag).snl)
            result  = flag
         finish 
      finish 
      seg = 0; gap = 0
      flag = dconnect(myname, file, myfsys, r!w!shared, apf, seg, gap)
      if  flag#0 then  printstring("Connect ".file." fail ".derrs(flag).snl) else  conad = seg<<18
      f = dfstatus(myname, file, myfsys, setcherish, 0)
      f = dfstatus(myname, file, myfsys, noarch, 0)
      f = dpermission(myname, "", "", file, myfsys, 1, r)
      result  = flag
   end ; !of newgen or rename



   routine  table sort(integer  n)
      integer  i, j, ordered, save, an
      byte  integer  array  x(1:name entry size)
      save = addr(x(1))
      an = addr(n table(n))
      cycle  i = addr(n table(1)), name entry size, an-name entry size
         move(name entry size, an, save)
         ordered = yes
         cycle  j = an, -name entry size, i+name entry size
            if  string(j-name entry size)<string(save) start 
               move(name entry size, save, j)
               move(name entry size, j-name entry size, save)
            else 
               move(name entry size, j-name entry size, j)
               ordered = no
            finish 
         repeat 
         move(name entry size, save, i)
         if  ordered=yes then  exit 
      repeat 
      i = i-addr(ntable(1))
      i = i//nameentrysize
      write(i, 4); newline
   end ; !of table sort



   routine  connect tables

!     Connects the ADDR and NAME files.
!     If not found on the IPL disc, searches all other file systems
!     for them and transfers them across.

      integer  seg, gap, fsys, flag, infoad, i
      record  (finff) info
      seg = 0; gap = 0
      addr file conad = 0
      name file conad = 0
      flag = dconnect(myname, addrfile, myfsys, r!w!shared, apf, seg, gap)
      if  seg#0 then  addr file conad = seg<<18 else  start 
         printstring("Connect ADDRFILE on IPL disc fails:".derrs(flag).snl)
         if  flag#does not exist then  return 
         if  pointers_discs(myfsys)&1=0 then  c 
            printstring("Addrfile not transferable to non-service disc".snl) and  return 
         infoad = addr(info)
         cycle  fsys = 0, 1, max fsys
            if  f systems(fsys)#0 start 
               flag = dfinfo(myname, addrfile, fsys, infoad)
               if  flag=0 start 
                  flag = dtransfer(myname, myname, addrfile, addrfile, fsys, myfsys, 1)
                  if  flag=0 start 
                     printstring("ADDRFILE transferred from fsys ".itos(fsys).snl)
                     seg = 0; gap = 0
                     flag = dconnect(myname, addrfile, myfsys, r!w!shared, apf, seg, gap)
                     if  flag=0 start 
                        addr file conad = seg<<18
                        flag = dtransfer(myname, myname, snamefile, snamefile, fsys, myfsys, 1)
                        if  flag=0 then  printstring("NAMEFILE transferred from fsys ".itos(fsys).snl)
                        exit 
                     else 
                        printstring("Connect ADDRFILE fails:".derrs(flag).snl)
                        return 
                     finish 
                  finish  else  printstring("Dtransfer ADDRFILE fsys ".itos(fsys)." fails:".derrs(flag).snl)
               finish 
            finish 
         repeat 
      finish 
      if  addr file conad=0 then  return 
      ad file == record(addr file conad)
      i = (ad file_end//ad entry size)-1
      if  i>max rnames then  max rnames = i
      seg = 0; gap = 0
      flag = dconnect(myname, snamefile, myfsys, r!w!shared, apf, seg, gap)
      if  seg#0 start 
         name file conad = seg<<18
         name file == record(name file conad)
         if  name file_datetime#ad file_datetime start 
            printstring("Out of date NAMEFILE".snl)
            name file_datetime = 0
            name file conad = 0
         else 
            n table == array(name file conad+name file header, name table arf)
            startchar == name file_startchar
         finish 
      finish  else  printstring("Connect NAMEFILE fails:".derrs(flag).snl)
   end ; !of connect tables



   routine  delete junk(integer  fsys spec)

!     Deletes junk mail without notice to user or sender if older that
!     "junk return period" (currently 7 days).  So far only TRANSFER ok
!     messages are junked but this may be extended!!
!     In addition, returns SPOOLING and SPOOLED messages to sender

      integer  fsys, msg no, return date, flag
      record  (msg descriptor f) array  name  messages
      record  (msg descriptor f) array  format  msg af(1:max messages)
      const  string  (15) junkname= "TRANSFER [ ok ]"

      log print(dt."Delete junk, fsys ".itos(fsys spec).snl)
      return date = current dt in secs
      if  return date<ad file_datetime then  printstring("Invalid DT set".snl) and  return 
      return date = return date-junk return period
      cycle  fsys = 0, 1, max fsys
         if  (fsys spec=-1 or  fsys spec=fsys) and  f systems(fsys)#0 start 
            messages == array(f systems(fsys)+message entry size, msg af)
            cycle  msg no = 1, 1, max messages
               if  messages(msg no)_dt sent<return date start 
                  if  messages(msg no)_status=sending and  messages(msg no)_rname=junkname start 
                     delete message(fsys<<24!msg no, flag)
                     delete message(messages(msg no)_recip link, flag)
                  finish 
                  if  messages(msg no)_status=spooled or  messages(msg no)_status=spooling start 
                     log print(dt."Message ".messages(msg no)_mess id." overdue spooling".snl)
                     return to sender(messages(msg no), flag, fsys<<24!msg no)
                  finish 
               finish 
            repeat 
         finish 
      repeat 
   end ; !of delete junk



   routine  move fsys(integer  from fsys, to fsys)

!    Moves message files and message descriptors from one disc to another

      integer  msg no, old ident, new ident, flag, next old, id
      string  (6) old file, new file
      record  (msg descriptor f) array  name  messages
      record  (msg descriptor f) array  format  msg af(1:max messages)
      record  (msg descriptor f) name  new msg

      if  fsystems(from fsys)=0 or  fsystems(to fsys)=0 start 
         printstring("Fails - fsys off-line!!".snl)
         return 
      finish 
      messages == array(f systems(from fsys)+message entry size, msg af)
      cycle  msg no = 1, 1, max messages
         old ident = from fsys<<24!msg no
         old file = ident to s(old ident)
         if  sending#messages(msg no)_status#waiting then  continue 
         new ident = get next descriptor(to fsys)
         if  new ident=0 then  return 
         new file = ident to s(new ident)
         new msg == record(message addr(new ident))
         flag = dtransfer(myname, myname, old file, new file, from fsys, to fsys, 1)
         if  flag#0 start 
            printstring("Transfer ".old file." to ".new file." fails : ".derrs(flag).snl)
            return 
         finish 
         new msg = messages(msg no); !copy contents
         if  messages(msg no)_status=sending start 
            next old = messages(msg no)_recip link&x'FFFFFF'
            while  next old#0 cycle 
               if  messages(next old)_status=spooled then  printstring("Cant move spooled msg ".old file.snl) else  c 
                  start 
                  id = get next descriptor(to fsys)
                  if  id=0 then  return 
                  new msg == record(message addr(id))
                  new msg = messages(next old); !copy contents
                  new msg_ident = new ident
               finish 
               messages(next old)_status = unused
               next old = messages(next old)_recip link&x'FFFFFF'
            repeat 
         finish 
         messages(msg no)_status = unused; !fully moved
         messages(msg no)_dt deleted = current dt in secs
         log print(dt."Message ".messages(msg no)_mess id." moved from ".old file." to ".new file.snl)
      repeat 
      printstring("Movefsys complete".snl)
   end ; !of move fsys



   routine  tidy archive(integer  fsys spec)

!        Keeps the archive index under control by deleting old duplicates

      record  format  archf(string  (11) name, integer  kbytes, string  (8) date, string  (6) tape,
          integer  chapter, flags)
      const  integer  max= 1000
      record  (arch f) array  arch(0:max-1)
      integer  filenum, maxrec, nfiles, fsys, i, j, flag
      cycle  fsys = 0, 1, maxfsys
         if  fsys spec=-1 or  fsys=fsys spec start 
            if  f systems(fsys)=0 then  continue 
            filenum = 0; maxrec = max
            flag = dfilenames(myname, arch, filenum, maxrec, nfiles, fsys, 2)
            if  flag=0 start 
               if  maxrec>1 start 
                  cycle  i = 0, 1, maxrec-2
                     if  arch(i)_name="" then  continue 
                     cycle  j = i+1, 1, maxrec-1
                        if  arch(i)_name=arch(j)_name start 
                           flag = ddestroy(myname, arch(j)_name, arch(j)_date, fsys, 2)
                           log print(dt."Destroy BACK ".arch(j)_name." flag =".derrs(flag).snl)
                           arch(j)_name = ""
                        finish 
                     repeat 
                  repeat 
               finish 
            finish  else  printstring("Dfilenames fails: ".derrs(flag).snl)
         finish 
      repeat 
   end ; !of tidy archive



   routine  killfsys(integer  fsys)

!     Deletes all messages sent to users on a given fsys and
!     removes directory entries for the users.

      record  (addr entry f) name  addr entry
      record  (msg descriptor f) name  msg
      record  (msg descriptor f) array  name  messages
      record  (msg descriptor f) array  format  msg af(1:max messages)
      integer  i, link, msg no
      if  pointers_discs(fsys)&2#0 start 
         printstring("Killfsys refused - fsys ".itos(fsys)." is".snl."still present in config file".snl)
         return 
      finish 
      cycle  i = 1, 1, ad file_entries
         addr entry == record(addr file conad+i*ad entry size)
         if  addr entry_fsys=fsys and  addr entry_server=this short host start 
            link = addr entry_link
            while  link>0 cycle 
               msg == record(message addr(link))
               msg_dt deleted = current dt in secs
               msg_status = unused
               log print(dt."Message ".msg_mess id." for ".addr entry_rname." removed at ".ident to s(link).snl)
               link = msg_rname link
            repeat 
            discredit entry(i)
         finish 
      repeat 
      if  fsystems(fsys)#0 start 
         messages == array(f systems(fsys)+message entry size, msg af)
         cycle  msg no = 1, 1, max messages
            if  messages(msg no)_status&sending#0 then  delete message(fsys<<24!msg no, i)
         repeat 
      finish  else  printstring("Fsys ".itos(fsys)." off-line for kill!!".snl)
   end ; !of killfsys



   routine  create maillist(integer  fsys)
      record  (fhf) name  file header
      integer  caddr, file size, flag
      string  (11) file
      string  (2) sfsys
      sfsys = i to s(fsys)
      if  f systems(fsys)=0 start ; !check if already open
         file = "MAILLIST".sfsys
         file size = message entry size*(max messages+1)
         connect or create(my name, file, fsys, file size, zerod!cherish, caddr)
         !connect or create
         f systems(fsys) = caddr; !store connect address
         unless  caddr=0 start 
            file header == record(caddr)
            if  file header_end=file header_start start 
               !new file?
               file header_end = file size
               file header_free hole = 1
               printstring("NEW MAIL LIST FSYS ".sfsys.snl)
               flag = dpermission(myname, "DIRECT", "", "", fsys, 6, r)
               printstring("Set index permission for DIRECT fails ".derrs(flag).snl) if  flag#0
            finish 
         finish  else  printstring("No MAIL LIST fsys ".sfsys.snl)
      finish  else  printstring("Already open fsys ".sfsys.snl)
   end ; !of routine create maillist



   routine  open file system(integer  fsys)

!      MAILER maintains a file index on each file system and this routine
!      opens the files on the specified file system for use.
!      When a file system is open the connect address of its MAILLIST file is
!      placed in the array F SYSTEMS.

      integer  caddr, flag, seg, gap
      string  (11) file
      string  (2) sfsys
      sfsys = i to s(fsys)
      if  f systems(fsys)=0 start ; !check if already open
         file = "MAILLIST".sfsys
         seg = 0; gap = 0
         flag = dconnect(myname, file, fsys, r!w!shared, 0, seg, gap)
         caddr = seg<<18
         if  caddr>0 then  f systems(fsys) = caddr else  c 
            printstring("Connect MAILLIST fsys ".sfsys." fails ".derrs(flag).snl)
      finish  else  printstring("Already open fsys ".sfsys.snl)
   end ; !of routine open file system



   routine  close file system(integer  fsys)

!      Notified by Director that a file system is closing, this
!      routine disconnects the MAILLIST files on all closing fsys
!      then relinks the message descriptors.

      integer  f, k, n, flag
      string  (11) file
      integer  array  a(0:max fsys)

      if  f systems(fsys)=0 then  return ; !already closed
      get av fsys(n, a)
      n = n-1
      cycle  f = 0, 1, maxfsys
         if  f systems(f)=0 then  continue 
         cycle  k = 0, 1, n
            if  a(k)=f then  exit 
            if  k=n start ; !not available
               file = "MAILLIST".itos(f)
               flag = ddisconnect(myname, file, f, 0)
               f systems(f) = 0
               log print(dt."Ddisconnect ".myname.".".file." flag = ".derrs(flag).snl)
            finish 
         repeat 
      repeat 
      if  mailer state=open then  check descriptors(-1)
   end ; !of close file system



   routine  initialise

!       Sets up global variables, tables and lists
!       and connects files used by MAILER on the on-line file systems.

      integer  i, j, k
      integer  array  a(0:max fsys); !used to store fsys nos suplied by director
      record  (pe) p
      com == record(acomf)
      date == string(addr(com_date0)+3)
      time == string(addr(com_time0)+3)
      e page size = com_e page size<<10; !extended page size in bytes
      monitoring = 6
      max rnames = default max rnames
      after linkhead = 0
      report fsys = my fsys
      last fsys = 0
      ad l to u trans = com_trans+512
      cycle  i = 0, 1, max reply index
         spool reply index(i) = 0
      repeat 
      top spoolr reply = 0
      cycle  i = 0, 1, max fsys
         f systems(i) = 0; !mark all files as not connected
      repeat 
      get av fsys(j, a); !get list of available f systems
      i = 0
      while  i<j cycle 
         open file system(a(i)); !open currently on line file systems
         k = change context
         i = i+1
      repeat 
      rtable == array(rtable conad, rtable arf)
      k = (charno(date, 7)-'0')*10+charno(date, 8)-'0'
      i = get fourth sunday(3, k); !in march
      j = get fourth sunday(10, k); !in october
      if  i<=current dt in secs<j then  time zone = "bst" else  time zone = "gmt"
      dbconad = 0
      connect dbfile(i)
      connect tables
      if  addr file conad=0 start 
         mailer state = closed
         printstring("MAILER closed".tostring(17).snl)
         return 
      finish 
      config conad = 0; current configfile = ""
      i = connect config file("")
      if  i#0 then  i = connect configfile(myname.".".confbackup)
      if  i#0 start 
         mailer state = closed
         printstring("Mailer closed!!".tostring(17).snl)
      else 
         mailer state = open
         if  name file conad=0 then  update tables
      finish 
      if  mailer state=open start 
         check descriptors(-1)
         p = 0; !kick spoolr for my stream
         p_dest = return file ack
         mail queue(p)
      finish 
   end ; !of routine initialise
end 
end  of  file