home *** CD-ROM | disk | FTP | other *** search
/ Simtel MSDOS - Coast to Coast / simteldosarchivecoasttocoast2.iso / citadel / k2ne603b.zip / CTDL_MAN.ZIP / HACK3.MAN < prev    next >
Text File  |  1985-04-27  |  22KB  |  448 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. (Uncoincidentally) the format used to transmit messages for networking
  171. purposes is precisely that used on disk, except that lines are marked
  172. with ASCII CR characters in stead of ASCII LF characters.  The current
  173. distribution includes CTDLNET, which is basically a database integrator;
  174. please see CTDLNET.DOC on its operation and functionality (if any).
  175.  
  176. /************************************************************************/
  177. /*            portability problems                */
  178. /************************************************************************/
  179.  
  180. Check all I/O, modem, console, and file stuff, and especially the
  181. dPrintf and mPrintf functions.
  182.  
  183. /************************************************************************/
  184. /*            "Room" records (ctdlRoom.sys)            */
  185. /************************************************************************/
  186. The rooms are basically indices into ctdlMsg.sys, the message file.
  187. As noted in the overview, each is essentially an array of pointers into
  188. the message file.  The pointers consist of a 16-bit message ID number
  189. (we will wrap around at 64K for these purposes) together with a 16-bit
  190. psuedo-sector offset within ctdlMsg.sys telling us where the message begins.
  191.  
  192. Since messages are number sequentially and written circularly, the
  193. set of messages existing in ctdlMsg.sys will always form a continuous
  194. sequence at any given time.  Thus, by remembering the ID numbers of the
  195. oldest and newest messages in the message file, we can check to see
  196. if a message exists >before< going to disk, saving ourselves (and the
  197. disk drive) the pain of futile seeks in search of the nonexistent.
  198. This information is recorded in oldest and newest, 32 bit numbers.
  199. You'll be seeing more of these...
  200.  
  201. The newest is simply incremented each time we enter a
  202. new message in the message files.  Oldest is incremented
  203. each time we overwrite an FF (start-of-message) byte in the course
  204. of writing a new message into the files.  This corresponds to dead
  205. reckoning -- current code never checks to see that the message number
  206. of the message we are overwriting is what we think it is.  In a garbaged
  207. file with extra FF bytes around, this could cause oldest to
  208. count too rapidly, eventually perhaps overtaking newest,
  209. at which time the system will look completely empty.  If you suspect
  210. something like this is going on, just use configur.exe to rebuild
  211. accurate numbers.
  212.  
  213. That should be enough background to tackle a full-scale room.  From
  214. ctdl.h :
  215.  
  216. struct {
  217.     unsigned char rbgen;    /* generation number of room        */
  218.     struct rflags rbflags;    /* same bits as flags above        */
  219.     char    rbname[NAMESIZE];    /* name of this room            */
  220.     char    rbdisk;        /* disk this room's files are in    */
  221.     char    rbdirname[9];    /* user directory for room's files    */
  222.     struct {
  223.     ulong     rbmsgNo;    /* every message gets unique#        */
  224.     ulong     rbmsgLoc;    /* sector message starts in        */
  225.     } msg[MSGSPERRM];
  226. } roomBuf;
  227.  
  228. [Note that all components start with "rb" for roomBuf, to make sure we
  229.  don't accidentally use an offset in the wrong structure. Be very careful
  230.  also to get a meaningful sequence of components --
  231.  C86 provides no checking on this sort of stuff either.]
  232.  
  233. Rbgen handles the problem of rooms which have died and been reborn
  234. under another name.  This will be clearer when we get to the log file.
  235. For now, just note that each room has a generation number which is
  236. bumped by one each time it is recycled.
  237.  
  238. Rbflags is just a bag of bits recording the status of the room.  The
  239. defined bits are:
  240. Bit 0:    INUSE.      1 if the room is valid, 0 if it is free for re-assignment.
  241. Bit 1:    PUBLIC.   1 if the room is visible by default, else 0.
  242. Bit 2:    MSDOSDIR. 1 if the room is a window onto some disk/userspace, else 0.
  243. Bit 3:    PERMROOM. 1 if the room should not be recycled even if empty, else 0.
  244.     (Lobby>, Mail> and all CPMDIRs are automatically permanent.)
  245.  
  246. Rbname is just an ASCII string (null-terminated, like all strings)
  247. giving the name of the room.
  248.  
  249. Rbdisk and rbdirname are meaningful only in MSDOSDIR rooms, in which case
  250. they give the disk (0 == A:, 1==B: ... 15==P:) and directory name (sysop
  251. selected) to window.
  252.  
  253. Finally, msg is the array of pointers into the message file.  RbmsgNo
  254. is the ID number of the message, and RbmsgLoc is the sector it begins
  255. in.  (For NIL, we stick the value -1 in RbmsgLoc.)
  256.  
  257.  
  258. RoomTab is just a little index into ctdlRoom.sys, to keep us from
  259. constantly pounding around on the disk looking for things.  It basically
  260. records the name and status of each room.  It is 100% redundant with
  261. the file itself... as all our indices are.  (As all indices should be!)
  262. Note that RoomTab is a significant consumer of RAM all by itself.  It
  263. is RAM well spent, but if you have to shave Citadel a few K to make
  264. it fit your system, cutting the number of rooms a bit is one try.
  265.  
  266. The only field new to us in roomTab is rtlastMessage, recording the
  267. most recent message in the room.  When we are searching for rooms with
  268. messages a given caller hasn't seen, we can check this number in RAM
  269. and avoid unprofitable disk accesses.
  270.  
  271. /************************************************************************/
  272. /*            log records (ctdlLog.sys)            */
  273. /************************************************************************/
  274. This is the fun one.  Get some fresh air and plug in your thinking cap
  275. first.    (Time, space and complexity are the eternal software rivals.
  276. We've got 256 log entries x about 500 messages spread over up to 128
  277. rooms to worry about, and with floppies disk access time is important...
  278. so perforce, we opt for lots of complexity to keep time and space in bounds.)
  279.  
  280. To understand what is happening in the log code takes a little persistence.
  281. You also have to disentangle the different activities going on and
  282. tackle them one by one.
  283.  
  284.  o    We want to remember some random things such as terminal screen
  285.     size, and automatically set them up for each caller at login.
  286.  
  287.  o    We want to be able to locate all new messages, and only new
  288.     messages, efficiently.    Messages should stay new even if it
  289.     takes a caller a couple of calls to get around to them.
  290.  
  291.  o    We want to remember which private rooms a given caller knows
  292.     about, and treat them as normal rooms.    This means mostly
  293.     automatically seeking out those with new messages.  (Obviously,
  294.     we >don't< want to do this for unknown private rooms!)    This
  295.     has to be secure against the periodic recycling of rooms
  296.     between calls.
  297.  
  298.  o    We want to support private mail to a caller.
  299.  
  300.  o    We want to provide some protection of this information (via
  301.     passwords at login) and some assurance that messages are from
  302.     who they purport to be from (within the system -- one shouldn't
  303.     be able to forge messages from established users).
  304.  
  305. Lifting another page from ctdl.h gives us:
  306.  
  307. struct logBuffer {
  308.     unsigned char lbnulls;        /* # nulls to print after newline    */
  309.     struct lflags lbflags;        /* UCMASK, LFMASK, EXPERT        */
  310.     unsigned char lbwidth;        /* terminal width            */
  311.     char    lbname[NAMESIZE];   /* caller's name            */
  312.     char    lbpw[NAMESIZE];     /* caller's password        */
  313.     int     lbgen[MAXROOMS];    /* 5 bits gen, 3 bits lastVisit    */
  314.     ulong    lbvisit[MAXVISIT];  /* newestLo on last few calls    */
  315.     ulong    lbslot[MAILSLOTS];  /* for private mail         */
  316.     ulong    lbId[MAILSLOTS];    /* for private mail         */
  317. }
  318.  
  319. Looks simple enough, doesn't it?  One topic at a time:
  320.  
  321.         RANDOM CONFIGURATION PARAMETERS
  322.  
  323. These are in the first three fields in the record.  Lbnulls gives the
  324. number of nulls to print after a newline, for folks who like
  325. simultaneous hardcopy.    Or any remaining ASR33 aficionados calling up...
  326. Lbwidth is the caller's screen width.  We format all messages to this
  327. width, as best we can.    Lbflags is another bit-bag, recording
  328. uppercase-only folks, people who need a linefeed after a carraige-return,
  329. people who want to suppress the little automatic hints all through
  330. the system, and people who like to see the time a message was created.
  331.  
  332.  
  333.              FINDING NEW MESSAGES
  334.  
  335. This is the most important.  Thus, it winds up being the most
  336. elaborate.  Conceptually, what we would like to do is mark each
  337. message with a bit after our caller has read it, so we can avoid
  338. printing it out again next call.  Unfortunately, with 256 log
  339. entries this would require adding two sectors to each message... and
  340. we'd wind up reading off disk lots of messages which would never
  341. get printed.  So we resort to an arcane mixture of approximation
  342. and low animal cunning.
  343.  
  344. The approximation comes in doing things at the granularity of
  345. rooms rather than messages.  Messages in a given room are "new"
  346. until we visit it, and "old" after we leave the room... whether
  347. we read any of them or not.  This can actually be defended: anyone
  348. who passes through a room without reading the contents probably just
  349. isn't interested in the topic, and would just as soon not be dragged
  350. back every visit and forced to read them.  Given that messages are
  351. numbered sequentially, we can simply record the most recent message ID#
  352. of each room as of the last time we visited it.  With 128 rooms, this
  353. would give us (for each user) an array of 128 integers, or 256 bytes.
  354.  
  355. This is still too much -- I'd like the complete log record for a user
  356. to be 256 bytes or less, and we have other stuff to do yet.
  357.  
  358. So, we complicate a little more.  We record in lbvisit[MAXVISIT] the
  359. most recent message ID# in the system on each of the last six calls
  360. or so.    Now, for each room, we can just indicate which call we last
  361. visited the room on.  This takes 3 bits per room, which we stash in
  362. the low three bits of lbgen[MAXROOMS].    Now we're down to
  363. 128 rooms x 3 bits (plus a few bytes in lbvisit[], of course),
  364. which is quite reasonable.
  365.  
  366. Putting it all together, we can now compute whether a given room
  367. has new messages for our current caller without going to disk at all:
  368.  > We get the lbgen[] entry for the room in question
  369.  > We mask off the lower 3 bits
  370.  > We use the result as an index into lbvisit[], getting the ID number
  371.    of the most recent message in the system as of the last time we
  372.    visited the room.
  373.  > We compare this with roomTab[].rtlastMessage, which tells us
  374.    what the most recent message in the room is currently.
  375.  
  376.  
  377.          REMEMBERING WHICH PRIVATE ROOMS TO VISIT
  378.  
  379. This looks trivial at first glance -- just record one bit per room per
  380. caller in the log records.  The problem is that rooms get recycled
  381. periodically, and we'd rather not run through 256 log entries each
  382. time we do it.    So we adopt a kludge which should work 99% of the time.
  383.  
  384. As previously noted, each room has a generation number, which is bumped
  385. by one each time it is recycled.  As not noted, this generation number
  386. runs from 0 -> 31 (and then wraps around and starts over).  Thus, these
  387. numbers take 5 bits to represent.  By a miraculous coincidence, we have
  388. exactly 5 bits left in the lbgen[] entries in the log records.    [Anyone
  389. familiar with "capability" pointers will be encountering deja vu here...]
  390.  
  391. When someone visits a room, we set the generation number in lbgen[]
  392. equal to that of the room.  This flags the room as being available.
  393. If the room gets recycled, on our next visit the two generation numbers
  394. will no longer match, and the room will no longer be available -- just
  395. the result we're looking for.  (Naturally, if a room is marked as PUBLIC,
  396. all this stuff is irrelevant.)
  397.  
  398. This leaves only the problem of an accidental matchup between the two
  399. numbers giving someone access to a Forbidden Room.  We can't eliminate
  400. this danger completely, but it can be reduced to insignificance for
  401. most purposes.    (Just don't bet megabucks on the security of this system!)
  402. Each time someone logs in, we set all "wrong" generation numbers to be
  403. one less than the actual generation of the room. This means that the
  404. room must be recycled thirty-one times before an accidental matchup
  405. can be achieved.  (We do this for all rooms, INUSE or dead, public
  406. or private, since any of them may be reincarnated as a Forbidden Room.)
  407.  
  408. Thus, for someone to accidentally be lead to a Forbidden Room, they
  409. must establish an account on the system, then not call until some room
  410. has been recycle thirty-one to thirty-two times, which room must be
  411. reincarnated as a Forbidden Room, which someone must now call back
  412. (having not scrolled off the userlog in the mean time) and read new
  413. messages.  The last clause is about the only probable one in the sequence.
  414. The danger of this is much less than the danger that someone will
  415. simply guess the name of the room outright...
  416.  
  417.  
  418.              SUPPORTING PRIVATE MAIL
  419.  
  420. Can one have an elegant kludge?  This must come pretty close.
  421.  
  422. Private mail is sent and recieved in the Mail> room, which otherwise
  423. behaves pretty much as any other room.    To make this work, we store
  424. the actual message pointers in lbslot[] and lbId[] in the caller's
  425. log record, and then copy them into the Mail> room array whenever we
  426. enter the room.  This requires a little fiddling to get things just
  427. right.    We have to update roomTab[MAILROOM].rtlastMessage at login
  428. to reflect the presence or absence of new messages, for example.  And
  429. MakeMessage() has to be kludged to ask for the name of the recipient
  430. of the message whenever a message is entered in Mail>.    But basically
  431. it works pretty well, keeping the code and user interface simple and
  432. regular.
  433.  
  434.  
  435.            PASSWORDS AND NAME VALIDATION
  436.  
  437. LogTab[] indexes ctdlLog.sys, giving us a quick way of finding people.
  438. It is basically a chronologically sorted hash table.  We keep a two-byte
  439. hash of the name and password of each caller in RAM.  When someone tries
  440. to log in, we just whip through the table in order looking for matches
  441. on the password hash and loading the matching logfile entry in.  Bogus
  442. hits are eliminated by the simple expedient of refusing to acknowledge
  443. a new user who's name or password hashes on top of an existing user.
  444. Computer chauvinism at it's best...
  445. This makes it difficult to forge messages from an existing user.  (Fine
  446. point: nonprinting characters are converted to printing characters, and
  447. leading, trailing, and double blanks are deleted.)
  448.