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