1 /* OPX packet structures, in C 2 Reverse engineered by William McBrine <wmcbrine@gmail.com> 3 Placed in the Public Domain 4 5 Version 1.2 of this document, Dec. 28, 2000 6 -- Separated Fido structure from MAIL.DAT header 7 -- Updated email address, revised commentary 8 Version 1.1 of this document (unreleased), Feb. 14, 2000 9 -- Modified description of line endings 10 -- DUSRCFG.DAT and EXTAREAS.DAT, contributed by Armando Ramos 11 -- MAIL.FDX info 12 -- A few additional flags; realignment of brdRec 13 -- More details on <BBSID>.ID 14 -- Changed string definition 15 Version 1.0 of this document, Oct. 27, 1999 16 -- First public documentation of the OPX format 17 18 This is still not a complete specification. Although adequate for a 19 reader, it should not be used as the basis for a door. 20 21 BRDINFO.DAT contains the list of areas, along with things like sysop 22 and BBS names; MAIL.DAT holds the actual messages. (These correspond 23 roughly to CONTROL.DAT and MESSAGES.DAT, respectively, in QWK.) Each 24 packet also has two index files, MAIL.FDX and MAIL.IDX. The .IDX file 25 appears redundant, and is not documented here. The .FDX file is needed 26 to ensure correct handling of long messages (> 64K), and is also used 27 to store read markers. The new files list is NEWFILES.TXT, and a 28 variety of bulletin files may be present. Offline config is handled 29 through the optional files DUSRCFG.DAT (from the door) and RUSRCFG.DAT 30 (from the reader). 31 32 The structures were originally designed for Borland Pascal. Here's a 33 macro to help define the strings in C terms: 34 */ 35 36 #define pstring(y,x) unsigned char y[x + 1] 37 38 /* Note that in version 1.0, this was instead defined as: 39 40 #define pstring(y,x) struct {unsigned char len; char data[x];} y 41 42 But I found that on some systems, the structs were being padded for 43 alignment. 44 45 Here are macros for little-endian shorts (16-bit) and longs (32-bit) -- 46 similar to the tWORD and tDWORD defintions in the Blue Wave specs, 47 except that I don't draw a distinction between signed and unsigned, and 48 I use only the portable (byte-by-byte) definition: 49 */ 50 51 typedef unsigned char pshort[2]; 52 typedef unsigned char plong[4]; 53 typedef unsigned char pbyte; 54 55 /* ### Fido Message Header ### */ 56 57 /* This is used both in MAIL.DAT, and in replies. It replaces the 58 "repHead" struct used in earlier versions of this document, and changes 59 the "msgHead" struct. 60 61 Based on Fido packet specifications, this struct uses null-terminated 62 (C-style) strings instead of the BP strings used elsewhere. 63 */ 64 65 #define FIDO_HEAD_SIZE 190 66 67 typedef struct { 68 char from[36]; /* From: (null-terminated string) */ 69 char to[36]; /* To: */ 70 char subject[72]; /* Subject: */ 71 char date[20]; /* Date in ASCII (not authoritative) */ 72 pshort dest_zone; /* Fido zone number of destination -- in 73 replies, set to 0 for non-netmail */ 74 pshort dest_node; /* Node of dest. */ 75 pshort orig_node; /* Node number of originating system */ 76 pshort orig_zone; /* Zone of orig. */ 77 pshort orig_net; /* Net of orig. */ 78 pshort dest_net; /* Net of dest. */ 79 plong date_written; /* Date in packed MS-DOS format */ 80 plong date_arrived; /* Date the message arrived on the BBS, in packed 81 MS-DOS format (meaningless in replies) */ 82 pshort reply; /* Number of message that this replies to, 83 if applicable */ 84 pshort attr; /* Attributes */ 85 pshort up; /* Number of message that replies to this one, 86 if applicable (meaningless in replies) */ 87 } fidoHead; 88 89 /* "attr" is a bitfield with the following values: */ 90 91 #define OPX_PRIVATE 1 /* Private message */ 92 #define OPX_CRASH 2 /* Fido crashmail */ 93 #define OPX_RECEIVED 4 /* Read by addressee */ 94 #define OPX_SENT 8 95 #define OPX_FATTACH 16 96 #define OPX_ORPHAN 64 97 #define OPX_KILL 128 98 #define OPX_LOCAL 256 /* Some readers set this on every reply */ 99 #define OPX_HOLD 512 100 #define OPX_FREQ 2048 101 #define OPX_RREQ 4096 102 #define OPX_RECEIPT 8192 103 #define OPX_FUREQ 32768 104 105 /* "Packed MS-DOS format" dates are the format used by MS-DOS in some of 106 its time/date routines, and in the FAT file system. They can cover 107 dates from 1980 through 2107 -- better than a signed 32-bit time_t, but 108 still limited. The ASCII date field is deprecated. 109 110 bits 00-04 = day of month 111 bits 05-08 = month 112 bits 09-15 = year - 1980 113 114 bits 16-20 = second / 2 115 bits 21-26 = minute 116 bits 27-31 = hour 117 */ 118 119 /* ### BRDINFO.DAT structures ### */ 120 121 /* The Header */ 122 123 /* To ensure correct operation where alignment padding is used, when 124 reading from or writing to disk, use the _SIZE defines given here 125 rather than "sizeof": 126 */ 127 #define BRD_HEAD_SIZE 743 128 129 typedef struct { 130 char unknown1[15]; 131 pstring(doorid,20); /* ID of the door that made this */ 132 pstring(bbsid,8); /* Like BBSID in QWK; used for replies */ 133 pstring(bbsname,60); /* BBS name */ 134 pstring(sysopname,50); /* Sysop's name */ 135 char unknown2[81]; 136 pstring(zone,6); /* Fidonet zone number of BBS, in ASCII */ 137 pstring(net,6); /* Net number */ 138 pstring(node,6); /* Node number */ 139 char unknown3[252]; 140 pstring(doorver,10); /* Version number of door */ 141 char unknown4[2]; 142 pstring(phoneno,26); /* Phone number of BBS */ 143 char unknown5[4]; 144 pstring(bbstype,123); /* BBS software name and version -- not 145 the right length, I'm sure */ 146 pstring(username,35); /* User's name */ 147 char unknown6[21]; 148 pshort numofareas; /* Number of conferences */ 149 char unknown7[4]; 150 pbyte readerfiles; /* Number of readerfiles */ 151 } brdHeader; 152 153 /* This is followed by a 13-byte record (a Pascal string[12]) for each 154 readerfile, as specified in brdHeader.readerfiles. Then there's a 155 single byte before the board records begin, which is an obsolete area 156 counter. Note that in version 1.0 of this document, I chose to ignore 157 this and assume that the board record started one byte earlier. 158 */ 159 160 /* Each Area */ 161 162 #define BRD_REC_SIZE 86 163 164 typedef struct { 165 pshort acclevel; /* Access level */ 166 pbyte conflow; /* Low byte of conf. number (obsolete) */ 167 pstring(name,70); /* Name of conference on BBS */ 168 pshort confnum; /* Number of conference on BBS */ 169 char unknown2[3]; 170 pshort attrib; /* Area attributes (bitflags) */ 171 char unknown3[2]; 172 pbyte attrib2; /* More area attributes */ 173 pbyte scanned; /* Subscribed flag -- not reliable */ 174 pbyte oldattrib; /* Low byte of attrib (obsolete) */ 175 } brdRec; 176 177 /* Some of the flags in attrib appear to be: */ 178 179 #define OPX_NETMAIL 1 180 #define OPX_PRIVONLY 4 181 #define OPX_PUBONLY 8 182 183 /* And in attrib2: */ 184 185 #define OPX_INTERNET 64 186 #define OPX_USENET 128 187 188 /* After all the areas comes some extra data which I'm ignoring for now. 189 It appears to be a list of the message number, conference number, and 190 attribute (?) for each message in the packet. 191 */ 192 193 /* ### EXTAREAS.DAT ### */ 194 195 /* Some packets have extra area data in the file EXTAREAS.DAT. This file 196 consists entirely of brdRec records, as in BRDINFO.DAT. The procedure 197 for handing area data when this file is present should be as follows: 198 199 X = brdHeader.numofareas 200 Y = Length of file "EXTAREAS.DAT" / Structure length of brdRec (86) 201 Z = X - Y 202 Read Z amount of area info from "BRDINFO.DAT" 203 Read Y amount of area info from "EXTAREAS.DAT" 204 */ 205 206 /* ### MAIL.DAT structures ### */ 207 208 /* Each message consists of the header, in a fixed format shown below, 209 followed by the message text, whose length is specified in the header. 210 Messages for all areas are concatenated. 211 212 The first 14 bytes of the header are specific to OPX; the remainder is 213 based on Fidonet packet structures. The length field specifies the 214 length of the entire message, including the classic Fido header, but 215 NOT including the OPX-specific part of the header (those first 14 bytes). 216 217 Since the header size is fixed (AFAIK), a more useful interpretation of 218 the length field might be the length of the text plus 0xBE bytes. Also, 219 because the field is only a 16-bit integer, it will be invalid if a 220 message longer than 64k is packed. 221 */ 222 223 /* OPX Message Header */ 224 225 #define MSG_HEAD_SIZE 204 226 227 typedef struct { 228 pshort msgnum; /* Message number on BBS */ 229 pshort confnum; /* Conference number */ 230 pshort length; /* Length of text + Fido header (0xBE) */ 231 char unknown1; 232 char msgtype; /* 'D' = Direct (personal), 233 'K' = Keyword, or ' ' */ 234 char unknown2[6]; 235 236 fidoHead f; /* Classic Fido header */ 237 } msgHead; 238 239 /* The message text consists of lines delimited by LF, CRLF, CR, or even a 240 misused "soft CR" character (0x8D). There's no consistency, and I'm not 241 sure whether this covers all the possible forms. Various Fido-style 242 "hidden lines" may be present in the text, including "INETORIG 243 <address>" on Internet email. 244 */ 245 246 /* ### MAIL.FDX structures ### */ 247 248 /* Basically one header, and then one record per message; but it's a 249 little trickier than that. 250 */ 251 252 /* The Header */ 253 254 #define FDX_HEAD_SIZE 25 255 256 typedef struct { 257 pshort RowsInPage; /* Normally the total number of messages */ 258 pshort ColsInPage; /* Always 1, in MAIL.FDX */ 259 pshort PagesDown; 260 pshort PagesAcross; 261 pshort ElSize; /* Size of element; i.e., sizeof(fdxRec) */ 262 pshort PageSize; /* RowsInPage * ColsInPage * ElSize */ 263 pshort PageCount; /* Normally 1, but see below */ 264 plong NextAvail; /* Next "page" = Total file size, here */ 265 char ID[7]; /* Always "\006VARRAY" */ 266 } fdxHeader; 267 268 /* This is followed by a table of pointers to pages; each is a plong. 269 You're supposed to use this by looping from 1 to PageCount, reading 270 each entry from the pointer table, then seeking to that position and 271 reading RowsInPage records for each one. 272 273 In practice, PageCount seems always to be 1, while RowsInPage is 274 equivalent to the total number of messages in the packet. But I can't 275 guarantee this. If more than one page were used, the RowsInPage value 276 would become tricky, since only one value is used for all pages; if 277 fdxRec records were split over multiple pages, the same number would 278 have to be assigned to each page, as there seems to be no provision for 279 having one page shorter than another. On the other hand, a second page 280 would seem to be required for any packet with more than 5957 messages. 281 (Perhaps such a large packet is simply not allowed?) 282 283 PagesDown and PagesAcross also seem always to be 1. 284 */ 285 286 /* Each Message */ 287 288 #define FDX_REC_SIZE 11 289 290 typedef struct { 291 pshort confnum; /* Area number */ 292 pshort msgnum; /* Message number */ 293 char msgtype; /* 'D' = Direct (personal), 294 'K' = Keyword, or ' ' */ 295 pbyte flags; /* Read, replied, etc. */ 296 pbyte marks; /* Marked, etc. */ 297 plong offset; /* Start of message in MAIL.DAT */ 298 } fdxRec; 299 300 /* Although these records contain the conference and message numbers 301 themselves, they should be stored in the same order as the messages in 302 MAIL.DAT. Message lengths can be calculated by subtracting the offset 303 field of an fdxRec from that of the next one. 304 */ 305 306 /* "flags" is a bitfield with the following values: */ 307 308 #define FDX_READ 0x01 /* Set when read by reader */ 309 #define FDX_REPLIED 0x02 /* Set if a reply exists for this message */ 310 #define FDX_SEARCH 0x04 /* Set if message is a search hit */ 311 312 /* "marks" is a bitfield with the following values: */ 313 314 #define FDX_KILL 0x01 315 #define FDX_FILE 0x02 316 #define FDX_PRINT 0x04 317 #define FDX_READ2 0x08 318 #define FDX_URGENT 0x10 319 #define FDX_TAGGED 0x20 320 #define FDX_DOS 0x40 321 322 /* ### DUSRCFG.DAT structures ### */ 323 324 /* Only some packets have this. It indicates which areas are subscribed 325 to, and enables offline config. There's one header record, followed by 326 one record for each area. 327 */ 328 329 /* The Header */ 330 331 #define OCFG_HEAD_SIZE 120 332 333 typedef struct { 334 plong unknown1; 335 pbyte flags1, flags2, flags3, flags4; /* Bit-mapped options */ 336 pshort numofareas; /* Total number of areas */ 337 pbyte helplevel; /* Door Menu Help Level: 338 0 = NOVICE, 1 = EXPERT, 2 = GXPRESS */ 339 pbyte flags5; 340 char unknown2[108]; 341 } ocfgHeader; 342 343 /* Values for the flags are as follows: */ 344 345 /* Bit-masks for flags1 */ 346 #define OCFG_GRAPHICS 1 /* Use Door Ansi Graphics */ 347 #define OCFG_HOTKEYS 2 /* Use Door Menu Hot Keys */ 348 #define OCFG_GROUPMAIL 4 /* Accept Group Mail */ 349 #define OCFG_MY_MAIL 8 /* Scan Your Own Mail */ 350 351 /* Bit-masks for flags2 */ 352 #define OCFG_NEWFILES 4 /* Scan for New Files */ 353 #define OCFG_MAIL_ONLY 64 /* Show Areas with Mail Only */ 354 355 /* Bit-masks for flags3 */ 356 #define OCFG_VACATION 4 /* Use Vacation Saver Mail */ 357 #define OCFG_BULLETIN 8 /* Scan For News Bulletins */ 358 #define OCFG_IBM_CHAR 16 /* Use IBM Characters */ 359 #define OCFG_REP_RECPT 32 /* Send REPLY Receipt */ 360 #define OCFG_SEND_QWK_NDX 64 /* QWK: Send NDX index files */ 361 #define OCFG_STRIP_QWK_KLUDGE 128 /* QWK: Strip Kludges Lines */ 362 363 /* Bit-masks for flags4 */ 364 #define OCFG_AUTO_XPRESS 1 /* Use Auto Xpress Starter */ 365 #define OCFG_SEL_AREAS_ONLY 8 /* Send Selected Areas Only */ 366 #define OCFG_PACKET_EXT 64 /* Use Packet Extension */ 367 #define OCFG_USE_FLEX 128 /* Use Flex Assistant */ 368 369 /* Bit-masks for flags5 */ 370 #define OCFG_QWK_WRAP 1 /* QWK: Perform Word Wrapping */ 371 #define OCFG_SKIP_RIP 2 /* Skip RIP Graphics */ 372 #define OCFG_SEARCH_MSG 4 /* Search Message Body */ 373 #define OCFG_COLORED_NEW_FILES 8 /* Colorized New Files List */ 374 375 /* Each Area */ 376 377 #define OCFG_REC_SIZE 3 378 379 typedef struct { 380 pshort confnum; 381 pbyte scanned; /* 1 = Scan, 0 = Don't scan */ 382 } ocfgRec; 383 384 /* After all the specified area records, some extra records sometimes 385 appear at the end of the file; I don't know their purpose, if any. 386 */ 387 388 /* ### REPLIES ### */ 389 390 /* Reply packets are named in the form <BBSID>.REP. Inside each packet is 391 one file per message, in a form very close to that used in MAIL.DAT, 392 with a header followed by the text; along with a text file named 393 <BBSID>.ID. Where offline config is supported, RUSRCFG.DAT may also be 394 included. 395 396 The header is just the classic Fido header ("fidoHead"). This omits one 397 crucial piece of information: the conference number! Instead, it's 398 encoded in the filename of the message. The names come in two forms. 399 Messages which are not replies to existing messages are named as: 400 401 !N<x>.<yyy> 402 403 where <x> is the serial number of the message (this is ignored, AFAIK), 404 in decimal, with no leading zeroes; and <yyy> is the destination area, 405 in _base 36_, with leading zeroes if needed to pad it out to three 406 characters. Messages which are replies are named: 407 408 !R<x>.<yyy> 409 410 In this case, <x> is the number of the message to which this is a 411 reply. (This is redundant with the reply field in the header.) <x> is 412 again a decimal number with no leading zeroes, and <yyy> is the same in 413 both forms. Note that this system implies there can only be one reply 414 to a given message in a single reply packet; some readers enforce this. 415 (The limit can be circumvented by not using the R form for subsequent 416 replies.) 417 418 After the header comes the message text. The length is determined by 419 the file length, since there's no length field in the header. The text 420 is CRLF-delimited paragraphs. I'm not sure whether the lines should be 421 forced to wrap (like QWK) or not (like Blue Wave); when I tested this, 422 I got inconsistent results. 423 424 Several Fido-style "hidden lines" (beginning with ctrl-A) may be 425 present; one reader typically includes a PID and MSGID, and adds an 426 INTL for netmail. Internet email is indicated by an "INETDEST" kludge 427 line (ctrl-A, "INETDEST", a space (no colon), and then the address). 428 429 Some readers end the text with a zero byte, but this doesn't appear to 430 be necessary. 431 */ 432 433 /* <BBSID>.ID */ 434 435 /* A CRLF-delimited plain ASCII text file with seven lines. It seems to be 436 intended for security/validation purposes, mainly indicating the 437 registration status of the reader. 438 439 Line 1: A textual representation of a boolean, either "TRUE" or 440 "FALSE". It's "TRUE" only if the packet comes from a registered 441 reader AND the user name matches the registered user. 442 Line 2: Blank, in replies where line 1 is "FALSE"; otherwise, a 443 hexadecimal number of unknown significance. 444 Line 3: Version number of the reader, in decimal. 445 Line 4: Another boolean; always "TRUE" in my limited experience. I 446 don't yet know its purpose. 447 Line 5: The date the packet was created, in packed MS-DOS format, 448 treated as a signed number and written out as a decimal number. 449 Line 6: The "reader code" in decimal; 0 for unregistered readers. This 450 shows up even when the names don't match. 451 Line 7: Another number; it seems to be always "-598939720" when line 1 452 is "FALSE", but varies otherwise. 453 454 Also, the file is given a weird time stamp, although that doesn't seem 455 to be required by the doors I've tested it with. 456 */ 457 458 /* RUSRCFG.DAT */ 459 460 /* This file is the same format as DUSRCFG.DAT, but is generated by the 461 reader. It is only present when the configuration is changed, and may 462 only be generated if a DUSRCFG.DAT file exists in the original packet. 463 Unchanged information from DUSRCFG.DAT is carried over to RUSRCFG.DAT. 464 */ 465