home *** CD-ROM | disk | FTP | other *** search
/ Crawly Crypt Collection 1 / crawlyvol1.bin / bbs / citadel2 / hack.doc < prev    next >
Text File  |  1989-12-01  |  22KB  |  443 lines

  1. /************************************************************************/
  2. /*                hack.doc                */
  3. /************************************************************************/
  4.  
  5. /************************************************************************/
  6. /*                History                 */
  7. /*                                    */
  8. /* 85Apr27 HAW    Partially updated for Citadel-86.            */
  9. /* 82Dec07 CrT    Completed.                        */
  10. /* 82Nov23 CrT    Created.                        */
  11. /************************************************************************/
  12.  
  13. /************************************************************************/
  14. /*                Audience                */
  15. /*                                    */
  16. /* Folks trying to read or modify the Citadel code.            */
  17. /************************************************************************/
  18.  
  19. /************************************************************************/
  20. /*                Purpose                 */
  21. /*                                    */
  22. /* Explain the basic data structures and algorithms.            */
  23. /************************************************************************/
  24.  
  25. /************************************************************************/
  26. /*                Overview                */
  27. /************************************************************************/
  28.  
  29.     The fundamental structure of the system is very simple.  CtdlMsg.sys
  30. is a circular file.  New messages are simply written around the buffer
  31. in an endless circle, overwriting old messages in their way.  There is
  32. no other way of deleting messages, and text is never shuffled on disk.
  33. Messages are numbered consecutively and start with an FF (hex)
  34. byte.  Except for this FF start-of-message byte, all bytes in the message
  35. file have the high bit set to 0.  This means that in principle it is
  36. trivial to scan through the message file and locate message N if it
  37. exists, or return error.  (Complexities, as usual, crop up when we
  38. try for efficiency...)
  39.     Each room is basically just a list of message numbers.  Each time
  40. we enter a new message in a room, we slide all the old message-numbers
  41. down a slot, and probably the oldest one falls off the bottom.    Reading
  42. a room is just a matter looking up the messages one by one and printing
  43. them out.  If the message has been overwritten already, we just ignore it.
  44.     Implementing the "new message" function is also trivial in principle:
  45. we just keep track, for each caller in the userlog, of the highest-numbered
  46. message which existed on the >last< call.  (Remember, message numbers are
  47. simply assigned sequentially each time a message is created.  This
  48. sequence is global to the entire system, not local within a room.)  If
  49. we ignore all message-numbers in the room less than this, only new messages
  50. will be printed.  Voila!  Code up the system described thus far, and
  51. you'll have a good approximation to Version 1.    Better stop and reread
  52. everything to here, so you can pick out the fundamental mechanisms among
  53. all of Version 2's bells and whistles.
  54.  
  55. /************************************************************************/
  56. /*        message format on disk    (ctdlMsg.sys)            */
  57. /************************************************************************/
  58.  
  59. Message format has changed relative to V1, in the direction of using
  60. more disk space and providing greater flexibility.
  61.  
  62. A message now consists of a sequence of character strings.  Each string
  63. begins with a type byte indicating the meaning of the string and is
  64. ended with a null.  All strings are printable ASCII: in particular,
  65. all numbers are in ASCII rather than binary.  This is for simplicity,
  66. both in implementing the system and in implementing other code to
  67. work with the system.  For instance, a database driven off Citadel archives
  68. can do wildcard matching without worrying about unpacking binary data such
  69. as dates first.  To provide later downward compatability,
  70. all software should be written to IGNORE fields not currently defined.
  71.  
  72.  
  73. /************************************************************************/
  74. /*          The type bytes currently defined are:         */
  75. /************************************************************************/
  76.  
  77. BYTE    Mnemonic    Comments
  78.  
  79. 0xFF            Start-of-message indicator.  Followed by local
  80.             message ID# as ASCII string, null-terminated as
  81.             always.  This byte is the >only< byte which has
  82.             the high bit set in a Citadel message.buf file.
  83.             This field must be present in every message.
  84. A    Author        Name of originator of message.
  85. D    Date        Date message was created.
  86. C    Time        Time message was created.
  87. M    Message     Text of message.  Is last field in a message, by
  88.             definition.  Following data will be ignored.
  89.             This field must be present in every message.
  90. N    Name        Human name for node originated on.  Used on
  91.             title line of foreign messages.  Ex:
  92.             ODD-DATA
  93.             will produce a title message something like
  94.             82Nov23 from Cynbe ru Taren @ODD-DATA
  95. O    Origin        ID of node message originated on: Country code plus
  96.             phone number of system.  (Not stored for locally
  97.             originated messages.)  Ex:
  98.             US 206 633 3282
  99. R    Room        Room of origin.  Topic.
  100. S    Source ID#    Message ID # on system message was created on.
  101.             Two 16-bit integers (high and low halves of
  102.             full 32-bit ID#) separated by a blank.    Ex:
  103.             0 13654
  104. T    To        Addressee.  Used only for private messages in Mail>,
  105.             in version 2.00 .
  106.             EXAMPLE
  107.  
  108. Let <FF> be a 0xFF byte, and <0> be a null (0x00) byte.  Then a message
  109. which prints as
  110.  
  111. LOGLAN> read new
  112.  
  113. 82Nov04 From James Cooke Brown
  114. Loi uiue i Ti logla
  115.  
  116. LOGLAN>
  117.  
  118. might be stored as
  119.  
  120. <FF>0 3583<0>D82Nov04<0>AJames Cooke Brown<0>RLOGLAN<0>MLoi uiue i Ti logla<0>
  121. |--Local ID--|---Date---|-----Author---------|--Room---|-------Message--------|
  122.  
  123. The date, room and author fields could be in any order. Not all fields
  124. are printed by default.  The local ID# and Room field are suppressed here.
  125. An isolated system will not normally have use for fields beyond those
  126. used in this example.
  127.  
  128. Lines are marked with C NewLine (ASCII LF) characters, within the message
  129. text proper.
  130.  
  131. /************************************************************************/
  132. /*                Networking                    */
  133. /************************************************************************/
  134.  
  135. Citadel nodes network by sharing one or more rooms.  Any Citadel node
  136. can choose to keep an image of any public room on any other Citadel node
  137. (subject to willingness to foot the phone bills, of course!).  The
  138. procedure in essence simply involves calling the imaged node up periodically
  139. and copying any new messages in the imaged room into the local image.
  140.  
  141. There is no necessary reciprocity or pre-arrangement, although convenience
  142. and politeness respectively suggest both.  The node which gets the
  143. information foots the phone bill for the transaction.  This promotes
  144. simple and harmonious relations between the nodes.
  145.  
  146. Complexities arise primarily from the possibility of densely connected
  147. networks: one does not wish to accumulate multiple copies of a given
  148. message, which can easily happen.  Nor does one want to see old messages
  149. percolating indefinitely through the system.
  150.  
  151. This problem is handled by a simple brute-force mechanism: each node
  152. keeps a list of all messages it has seen recently, recording origin
  153. system, creation date, and original ID#.  When downloading, messages
  154. which have already been seen, or which are too old to be remembered,
  155. are skipped.  Messages can percolate outward through a large network
  156. with no global routing or control, but do not reproduce wildly or
  157. cycle indefinitely.
  158.  
  159.  
  160. The above discussion should make the function of the new
  161. fields reasonably clear:
  162.  
  163.  o  Every message needs a local ID#, so the system can determine if
  164.     a given caller has seen it before.
  165.  o  Travelling messages need to carry system of origin, date of
  166.     origin, and original ID# with them, to keep reproduction and
  167.     cycling under control.
  168.  
  169.  
  170. /************************************************************************/
  171. /*            portability problems                */
  172. /************************************************************************/
  173.  
  174. Check all I/O, modem, console, and file stuff, and especially the
  175. dPrintf and mPrintf functions.
  176.  
  177. /************************************************************************/
  178. /*            "Room" records (ctdlRoom.sys)            */
  179. /************************************************************************/
  180. The rooms are basically indices into ctdlMsg.sys, the message file.
  181. As noted in the overview, each is essentially an array of pointers into
  182. the message file.  The pointers consist of a 16-bit message ID number
  183. (we will wrap around at 64K for these purposes) together with a 16-bit
  184. psuedo-sector offset within ctdlMsg.sys telling us where the message begins.
  185.  
  186. Since messages are number sequentially and written circularly, the
  187. set of messages existing in ctdlMsg.sys will always form a continuous
  188. sequence at any given time.  Thus, by remembering the ID numbers of the
  189. oldest and newest messages in the message file, we can check to see
  190. if a message exists >before< going to disk, saving ourselves (and the
  191. disk drive) the pain of futile seeks in search of the nonexistent.
  192. This information is recorded in oldest and newest, 32 bit numbers.
  193. You'll be seeing more of these...
  194.  
  195. The newest is simply incremented each time we enter a
  196. new message in the message files.  Oldest is incremented
  197. each time we overwrite an FF (start-of-message) byte in the course
  198. of writing a new message into the files.  This corresponds to dead
  199. reckoning -- current code never checks to see that the message number
  200. of the message we are overwriting is what we think it is.  In a garbaged
  201. file with extra FF bytes around, this could cause oldest to
  202. count too rapidly, eventually perhaps overtaking newest,
  203. at which time the system will look completely empty.  If you suspect
  204. something like this is going on, just use configur.exe to rebuild
  205. accurate numbers.
  206.  
  207. That should be enough background to tackle a full-scale room.  From
  208. ctdl.h :
  209.  
  210. struct {
  211.     unsigned char rbgen;    /* generation number of room        */
  212.     struct rflags rbflags;    /* same bits as flags above        */
  213.     char    rbname[NAMESIZE];    /* name of this room            */
  214.     char    rbdisk;        /* disk this room's files are in    */
  215.     char    rbdirname[9];    /* user directory for room's files    */
  216.     struct {
  217.     ulong     rbmsgNo;    /* every message gets unique#        */
  218.     ulong     rbmsgLoc;    /* sector message starts in        */
  219.     } msg[MSGSPERRM];
  220. } roomBuf;
  221.  
  222. [Note that all components start with "rb" for roomBuf, to make sure we
  223.  don't accidentally use an offset in the wrong structure. Be very careful
  224.  also to get a meaningful sequence of components --
  225.  C86 provides no checking on this sort of stuff either.]
  226.  
  227. Rbgen handles the problem of rooms which have died and been reborn
  228. under another name.  This will be clearer when we get to the log file.
  229. For now, just note that each room has a generation number which is
  230. bumped by one each time it is recycled.
  231.  
  232. Rbflags is just a bag of flags recording the status of the room.  The
  233. defined flags are:
  234.     INUSE.    TRUE if the room is valid, FALSE if it is free for
  235.         re-assignment.
  236.     PUBLIC.    TRUE if the room is visible by default.
  237.     ISDIR.    TRUE if the room is a window onto some disk/userspace.
  238.     PERMROOM.    TRUE if the room should not be recycled even if empty.
  239.     (Lobby>, Mail> and all ISDIRs are automatically permanent.)
  240.  
  241. Rbname is just an ASCII string (null-terminated, like all strings)
  242. giving the name of the room.
  243.  
  244. Rbdisk and rbdirname are meaningful only in ISDIR rooms, in which case
  245. they give the disk (0 == A:, 1==B: ... 15==P:) and directory name (sysop
  246. selected) to window.
  247.  
  248. Finally, msg is the array of pointers into the message file.  RbmsgNo
  249. is the ID number of the message, and RbmsgLoc is the sector it begins
  250. in.  (For NIL, we stick the value -1 in RbmsgLoc.)
  251.  
  252.  
  253. RoomTab is just a little index into ctdlRoom.sys, to keep us from
  254. constantly pounding around on the disk looking for things.  It basically
  255. records the name and status of each room.  It is 100% redundant with
  256. the file itself... as all our indices are.  (As all indices should be!)
  257. Note that RoomTab is a significant consumer of RAM all by itself.  It
  258. is RAM well spent, but if you have to shave Citadel a few K to make
  259. it fit your system, cutting the number of rooms a bit is one try.
  260.  
  261. The only field new to us in roomTab is rtlastMessage, recording the
  262. most recent message in the room.  When we are searching for rooms with
  263. messages a given caller hasn't seen, we can check this number in RAM
  264. and avoid unprofitable disk accesses.
  265.  
  266. /************************************************************************/
  267. /*            log records (ctdlLog.sys)            */
  268. /************************************************************************/
  269. This is the fun one.  Get some fresh air and plug in your thinking cap
  270. first.    (Time, space and complexity are the eternal software rivals.
  271. We've got 256 log entries x about 500 messages spread over up to 128
  272. rooms to worry about, and with floppies disk access time is important...
  273. so perforce, we opt for lots of complexity to keep time and space in bounds.)
  274.  
  275. To understand what is happening in the log code takes a little persistence.
  276. You also have to disentangle the different activities going on and
  277. tackle them one by one.
  278.  
  279.  o    We want to remember some random things such as terminal screen
  280.     size, and automatically set them up for each caller at login.
  281.  
  282.  o    We want to be able to locate all new messages, and only new
  283.     messages, efficiently.    Messages should stay new even if it
  284.     takes a caller a couple of calls to get around to them.
  285.  
  286.  o    We want to remember which private rooms a given caller knows
  287.     about, and treat them as normal rooms.    This means mostly
  288.     automatically seeking out those with new messages.  (Obviously,
  289.     we >don't< want to do this for unknown private rooms!)    This
  290.     has to be secure against the periodic recycling of rooms
  291.     between calls.
  292.  
  293.  o    We want to support private mail to a caller.
  294.  
  295.  o    We want to provide some protection of this information (via
  296.     passwords at login) and some assurance that messages are from
  297.     who they purport to be from (within the system -- one shouldn't
  298.     be able to forge messages from established users).
  299.  
  300. Lifting another page from ctdl.h gives us:
  301.  
  302. struct logBuffer {
  303.     unsigned char lbnulls;        /* # nulls to print after newline    */
  304.     struct lflags lbflags;        /* UCMASK, LFMASK, EXPERT        */
  305.     unsigned char lbwidth;        /* terminal width            */
  306.     char    lbname[NAMESIZE];   /* caller's name            */
  307.     char    lbpw[NAMESIZE];     /* caller's password        */
  308.     int     lbgen[MAXROOMS];    /* 5 bits gen, 3 bits lastVisit    */
  309.     ulong    lbvisit[MAXVISIT];  /* newestLo on last few calls    */
  310.     ulong    lbslot[MAILSLOTS];  /* for private mail         */
  311.     ulong    lbId[MAILSLOTS];    /* for private mail         */
  312. }
  313.  
  314. Looks simple enough, doesn't it?  One topic at a time:
  315.  
  316.         RANDOM CONFIGURATION PARAMETERS
  317.  
  318. These are in the first three fields in the record.  Lbnulls gives the
  319. number of nulls to print after a newline, for folks who like
  320. simultaneous hardcopy.    Or any remaining ASR33 aficionados calling up...
  321. Lbwidth is the caller's screen width.  We format all messages to this
  322. width, as best we can.    Lbflags is another bit-bag, recording
  323. uppercase-only folks, people who need a linefeed after a carraige-return,
  324. people who want to suppress the little automatic hints all through
  325. the system, and people who like to see the time a message was created.
  326.  
  327.  
  328.              FINDING NEW MESSAGES
  329.  
  330. This is the most important.  Thus, it winds up being the most
  331. elaborate.  Conceptually, what we would like to do is mark each
  332. message with a bit after our caller has read it, so we can avoid
  333. printing it out again next call.  Unfortunately, with 256 log
  334. entries this would require adding two sectors to each message... and
  335. we'd wind up reading off disk lots of messages which would never
  336. get printed.  So we resort to an arcane mixture of approximation
  337. and low animal cunning.
  338.  
  339. The approximation comes in doing things at the granularity of
  340. rooms rather than messages.  Messages in a given room are "new"
  341. until we visit it, and "old" after we leave the room... whether
  342. we read any of them or not.  This can actually be defended: anyone
  343. who passes through a room without reading the contents probably just
  344. isn't interested in the topic, and would just as soon not be dragged
  345. back every visit and forced to read them.  Given that messages are
  346. numbered sequentially, we can simply record the most recent message ID#
  347. of each room as of the last time we visited it.  With 128 rooms, this
  348. would give us (for each user) an array of 128 integers, or 256 bytes.
  349.  
  350. This is still too much -- I'd like the complete log record for a user
  351. to be 256 bytes or less, and we have other stuff to do yet.
  352.  
  353. So, we complicate a little more.  We record in lbvisit[MAXVISIT] the
  354. most recent message ID# in the system on each of the last six calls
  355. or so.    Now, for each room, we can just indicate which call we last
  356. visited the room on.  This takes 3 bits per room, which we stash in
  357. the low three bits of lbgen[MAXROOMS].    Now we're down to
  358. 128 rooms x 3 bits (plus a few bytes in lbvisit[], of course),
  359. which is quite reasonable.
  360.  
  361. Putting it all together, we can now compute whether a given room
  362. has new messages for our current caller without going to disk at all:
  363.  > We get the lbgen[] entry for the room in question
  364.  > We mask off the lower 3 bits
  365.  > We use the result as an index into lbvisit[], getting the ID number
  366.    of the most recent message in the system as of the last time we
  367.    visited the room.
  368.  > We compare this with roomTab[].rtlastMessage, which tells us
  369.    what the most recent message in the room is currently.
  370.  
  371.  
  372.          REMEMBERING WHICH PRIVATE ROOMS TO VISIT
  373.  
  374. This looks trivial at first glance -- just record one bit per room per
  375. caller in the log records.  The problem is that rooms get recycled
  376. periodically, and we'd rather not run through 256 log entries each
  377. time we do it.    So we adopt a kludge which should work 99% of the time.
  378.  
  379. As previously noted, each room has a generation number, which is bumped
  380. by one each time it is recycled.  As not noted, this generation number
  381. runs from 0 -> 31 (and then wraps around and starts over).  Thus, these
  382. numbers take 5 bits to represent.  By a miraculous coincidence, we have
  383. exactly 5 bits left in the lbgen[] entries in the log records.    [Anyone
  384. familiar with "capability" pointers will be encountering deja vu here...]
  385.  
  386. When someone visits a room, we set the generation number in lbgen[]
  387. equal to that of the room.  This flags the room as being available.
  388. If the room gets recycled, on our next visit the two generation numbers
  389. will no longer match, and the room will no longer be available -- just
  390. the result we're looking for.  (Naturally, if a room is marked as PUBLIC,
  391. all this stuff is irrelevant.)
  392.  
  393. This leaves only the problem of an accidental matchup between the two
  394. numbers giving someone access to a Forbidden Room.  We can't eliminate
  395. this danger completely, but it can be reduced to insignificance for
  396. most purposes.    (Just don't bet megabucks on the security of this system!)
  397. Each time someone logs in, we set all "wrong" generation numbers to be
  398. one less than the actual generation of the room. This means that the
  399. room must be recycled thirty-one times before an accidental matchup
  400. can be achieved.  (We do this for all rooms, INUSE or dead, public
  401. or private, since any of them may be reincarnated as a Forbidden Room.)
  402.  
  403. Thus, for someone to accidentally be lead to a Forbidden Room, they
  404. must establish an account on the system, then not call until some room
  405. has been recycle thirty-one to thirty-two times, which room must be
  406. reincarnated as a Forbidden Room, which someone must now call back
  407. (having not scrolled off the userlog in the mean time) and read new
  408. messages.  The last clause is about the only probable one in the sequence.
  409. The danger of this is much less than the danger that someone will
  410. simply guess the name of the room outright...
  411.  
  412.  
  413.              SUPPORTING PRIVATE MAIL
  414.  
  415. Can one have an elegant kludge?  This must come pretty close.
  416.  
  417. Private mail is sent and recieved in the Mail> room, which otherwise
  418. behaves pretty much as any other room.    To make this work, we store
  419. the actual message pointers in lbslot[] and lbId[] in the caller's
  420. log record, and then copy them into the Mail> room array whenever we
  421. enter the room.  This requires a little fiddling to get things just
  422. right.    We have to update roomTab[MAILROOM].rtlastMessage at login
  423. to reflect the presence or absence of new messages, for example.  And
  424. MakeMessage() has to be kludged to ask for the name of the recipient
  425. of the message whenever a message is entered in Mail>.    But basically
  426. it works pretty well, keeping the code and user interface simple and
  427. regular.
  428.  
  429.  
  430.            PASSWORDS AND NAME VALIDATION
  431.  
  432. LogTab[] indexes ctdlLog.sys, giving us a quick way of finding people.
  433. It is basically a chronologically sorted hash table.  We keep a two-byte
  434. hash of the name and password of each caller in RAM.  When someone tries
  435. to log in, we just whip through the table in order looking for matches
  436. on the password hash and loading the matching logfile entry in.  Bogus
  437. hits are eliminated by the simple expedient of refusing to acknowledge
  438. a new user who's name or password hashes on top of an existing user.
  439. Computer chauvinism at it's best...
  440. This makes it difficult to forge messages from an existing user.  (Fine
  441. point: nonprinting characters are converted to printing characters, and
  442. leading, trailing, and double blanks are deleted.)
  443.