mmail-0.52/000755 000765 000024 00000000000 13450550726 013517 5ustar00wmcbrinestaff000000 000000 mmail-0.52/Makefile.bcc000644 000765 000024 00000002014 13433645047 015704 0ustar00wmcbrinestaff000000 000000 #----------------------------------------------- # MultiMail Makefile (top) for Borland/Turbo C++ #----------------------------------------------- !ifndef CURS_DIR CURS_DIR = \pdcurses !endif !if $(SYS) == DOS LIBS = spawnl.lib $(CURS_DIR)\dos\pdcurses.lib !else LIBS = $(CURS_DIR)\wincon\pdcurses.lib CC = bcc32c -q -D__WIN32__ !endif LIST = tclist COMPILER = $(CC) -c -P O = obj OPTS = -Immail -Iinterfac .SUFFIXES: .cc .autodepend {mmail}.cc.obj: $(COMPILER) $(OPTS) $< {interfac}.cc.obj: $(COMPILER) $(OPTS) -I$(CURS_DIR) $< MOBJS = misc.$(O) resource.$(O) mmail.$(O) driverl.$(O) filelist.$(O) \ area.$(O) letter.$(O) read.$(O) compress.$(O) pktbase.$(O) bw.$(O) \ qwk.$(O) omen.$(O) soup.$(O) opx.$(O) IOBJS = mmcolor.$(O) mysystem.$(O) isoconv.$(O) basic.$(O) interfac.$(O) \ packet.$(O) arealist.$(O) letterl.$(O) letterw.$(O) lettpost.$(O) \ ansiview.$(O) addrbook.$(O) tagline.$(O) help.$(O) main.$(O) all: mm mm: $(MOBJS) $(IOBJS) $(CC) -emm @$(LIST) $(LIBS) clean: del *.obj del mm.exe del mm.tds mmail-0.52/mm.1000644 000765 000024 00000104505 13431372256 014216 0ustar00wmcbrinestaff000000 000000 .TH MultiMail 1 "February 14, 2019" .SH NAME mm - offline mail reader for Blue Wave, QWK, OMEN, SOUP and OPX packets .SH SYNOPSIS .B mm [-option1 value] [-option2 value] [...] [filename1] [filename2] [...] .br .SH DESCRIPTION MultiMail is an offline mail packet reader, supporting the Blue Wave, QWK, OMEN, SOUP and OPX formats. It uses a simple curses-based interface. SOUP is used for Internet email and Usenet. The other formats are primarily used with dialup (or telnet) BBSes, to save connect time and to provide a better interface to the message base. .SH USAGE On most screens, a summary of the available keystroke commands is displayed in the lower part of the screen. (You can disable this, and reclaim some screen real estate, by turning on "ExpertMode".) Note that for lack of space, not all commands are listed on every screen where they're available. For example, the search functions, which are available everywhere, are summarized only in the packet list and address book. The principle, albeit not one that's consistently implemented, is that the summary need appear only on the first screen where the commands are available. When in doubt, try one and see if it works. :-) In the letter window or ANSI viewer, pressing F1 or '?' will bring up a window listing the available commands. The basic navigation keys, available throughout the program, consist of the standard cursor and keypad keys, with to select. For terminals without full support for these keys, aliases are available for some of them: ESC = Q .br PgDn = B .br PgUp = F .br Right = + .br Left = - .br (Although shown in capitals, these may be entered unshifted.) With "Lynx-style navigation", activated by the "UseLynxNav" option, the Left arrow key backs out from any screen, while the Right arrow key selects. The plus and minus keys are no longer aliases for Right and Left, but perform the same functions as in the traditional navigation system. Of special note is the space bar. In most screens, it functions as an alias for PgDn; but in the letter window, it works as a combination PgDn/Enter key, allowing you to page through an area with one key. In the area list, the default view (selectable in the .mmailrc) is of Subscribed areas only, or of Active areas (i.e., those with messages) if the Subscribed areas are unknown. By pressing L, you can toggle between Active, All, and Subscribed views. (Some formats, like plain QWK, don't have any way to indicate subscribed areas. In other cases, you may have received an abbreviated area list, so that the Subscribed and All views are the same.) In all modes, areas with replies always appear, flagged with an 'R' in the leftmost column. In the letter list, only unread messages are displayed, by default; but you can toggle this by pressing L. If there are any marked messages, L first switches to a marked-only mode, then to all messages, then back to unread-only. Also, the default mode -- unread or all -- can be set in the .mmailrc. Multiple sort modes are available in the packet and letter lists; you can cycle through them by pressing '$'. The default sort modes are set in the .mmailrc. Options can be specified on the command line as well as in the .mmailrc. Option names are the same as those which appear there, though they must be prefaced by one or two dashes, and should not be followed by a colon. There must be a space between the option name and the value; values which include spaces must be quoted. All options must be specified before any packet names or directories on the line. Finally, options which take a filename or path should always include the full path. (This is not, however, necessary for packet names.) Packet names may be specified on the command line, bypassing the packet menu. If multiple packets are named, they'll be opened sequentially. If a directory is specified instead of a file, the packet window will by opened on that directory, and no further items will be read from the command line. 'T' in the packet menu may need clarification: it stamps the highlighted file with the current date and time. You can abort the program immediately from any screen with CTRL-X. You won't be prompted to confirm the exit, but you will still be prompted to save replies and pointers (unless autosaving is set). Note that if you've specified multiple packets on the command line, this is the only way to terminate the sequence prematurely. You can obtain a temporary command shell anywhere by pressing CTRL-Z. In the DOSish ports (MS-DOS, OS/2, Windows), it spawns a command shell, and you return to MultiMail via the "exit" command. In Unix, it relies on the shell to put MultiMail in the background; you return with "fg". (This has always been available in the Unix versions; however, it won't work if MultiMail wasn't launched from an interactive shell, or if the shell doesn't support it.) .SH MOUSING MultiMail is mousable on many platforms: X, SDL, the Linux console (with gpm), Windows, DOS and OS/2. (You can still use selection with X and gpm, too; to select or paste, hold down the shift key.) In each list window, button 1 highlights a line, or selects it (the same as pressing Enter) if it's already highlighted. Double-click to select it immediately. Click on the scrollbar to page up or down, or on the line just above or below it to scroll a line at a time. In the packet, area, and letter lists, click on the appropriate part of the window title to change the sort or list type. In the letter window, page up by clicking in the top half of the message text, or down (and on to the next message) by clicking in the bottom half (equivalent to the space bar). Scroll the message a single line up or down by clicking on the status bars at top and bottom. The status flags "Read" and "Marked" can be toggled by clicking on them; clicking on "Save" saves, clicking on "Repl" starts a reply (followup; i.e., the same as 'R'), and "Pvt" starts a private reply (email or netmail; i.e., same as 'N'). In text-entry windows, button 1 works the same as the Enter key; and the dialog boxes work in the obvious way. Button 3 backs out of any screen, equivalent to ESC. .SH SEARCHING A case-insensitive search function is available on all screens. Press '/' to specify the text to look for, or '>' or '.' to repeat the last search. New searches (specified with '/') always start at the beginning of the list or message. Repeat searches (with '>' or '.') start with the line below the current one. You can take advantage of this to manually adjust the starting point for the next search. Searches started in the letter, area or packet lists allow the searches to extend below the current list. "Full text" searches all the way through the text of each message; "Headers" searches only the message headers (the letter list), "Areas" only the area list, and "Pkt list" only the packet list. So, a "Full text" search started from the packet list will search every message in every packet (but only in the current directory). When scanning "Full text", the automatic setting of the "Read" marker is disabled. However, if you find a search string in the header of a message and then select it manually, the marker will be set. But if you start scanning from the packet list, and exit the packet via a repeat search, the last-read markers won't be saved. Scans of "Headers" or "Full text" that start from the area list or packet list will automatically expand the letter lists they descend into. Similarly, scans that start at the packet list will expand the area lists. Otherwise, if you're viewing the short list, that's all that will be searched. I hope the above makes some sense. :-) The searching functions are difficult to explain, but easy to use. .SH FILTERING A new twist on searching, as of version 0.43, is filtering. This is available in all of the list windows, but not the letter or ANSI viewer. Unlike searching, it always applies only to the current list. Press '|' to bring up the filter prompt, and specify the text to filter on. To clear a filter, press '|', and then press return at a blank filter prompt. (A string that's not found in the list will have the same effect.) Press ESC to leave the filter as it was. The list will now be limited to those items that contain the text you entered, and that text will appear at the end of the window's title as a reminder. The filter will be retained through lower levels, but will be cleared by exiting to a higher level. Note that a search in, e.g., the letter list will search only the message headers (and only those which are visible in the list), and not the bodies. When the filter is active in the letter list, the "All" option in the Save menu will save only the items that match the filter. This can be used as a quick alternative to marking and saving. You can also combine filtering and marking. Changing modes and sort types will not clear the filter. A search in a filtered list will search only the items that match the filter. .SH OFFLINE CONFIGURATION Offline config is limited to subscribe (add) and unsubscribe (drop) functions. The Blue Wave, OPX, OMEN, QWKE, and QWK Add/Drop (with DOOR.ID) methods are supported. (The QMAIL "CONFIG" method is not supported.) Offline config is not available in SOUP mode. In the area list, press 'U' or 'Del' to unsubscribe from the highlighted area. To subscribe to a new conference, first expand the list ('L'), then highlight the appropriate area and press 'S' or 'Ins'. Dropped areas are marked with a minus sign ('-') in the first column; added areas with a plus ('+'). In the expanded area list, already-subscribed areas are marked with an asterisk ('*'). (This and also applies to the little area list. With plain QWK packets, the asterisk should not be relied upon; other areas may also be subscribed.) Added or dropped areas are highlighted in the "Area_Reply" color. Yeah, I'll have to change that name now. ;-) Pressing 'S' on an area marked with '-', or 'U' on an area marked '+' turns the flag off again. In Blue Wave, OPX, OMEN or QWKE mode, the list of added and dropped areas is read back in when the reply packet is reopened. If the reply packet has already been uploaded, and you're reading a packet with the altered area list, this is benign. If it's an older packet, you can alter the list before uploading, as with reply messages. In QWK Add/Drop mode, the changed area flags are converted to reply messages when the reply packet is saved. Note: Adding or dropping areas sets the "unsaved replies" flag, like entering a reply message, but does not invoke automatic reply packet saving until you exit the packet. Unfortunately, the OMEN mode has not actually been tested; but I believe it conforms to the specs. Reports welcome. .SH HIDDEN LINES AND ROT13 In the letter window, you can toggle viewing of Fidonet "hidden" lines (marked with a ^A in the first position) by pressing 'x'. The lines are shown as part of the text, but in a different color. In Internet email and Usenet areas, the full headers of the messages are available in the same way (if provided in the packet -- generally, full headers are available in SOUP, and partial extra headers in Blue Wave). Pressing 'd' toggles rot13 encoding, the crude "encryption" method used for spoiler warnings and such, primarily on Usenet. .SH ANSI VIEWER If a message contains ANSI color codes, you may be able to view it as originally intended by activating the ANSI viewer. Press 'v' to start it. Press 'q' to leave the ANSI viewer; the navigation keys are the same as in the mail-reading window. The ANSI viewer includes support for animation. While within the ANSI viewer, press 'v' again to animate the picture. Press any key to abort the animation. The ANSI viewer is also used to display the new files list and bulletins, if any are present. New in version 0.43 is support for the '@' color codes used by PCBoard and Wildcat. This is on by default in the ANSI viewer, but it can be toggled to strip the codes, or pass them through untranslated, by pressing '@'. As of version 0.46, the ANSI viewer also includes limited support for AVATAR (level 0) and BSAVE (text only) screens. These can be toggled via ^V and ^B, respectively. .SH CHARACTER SETS MultiMail supports automatic translation between two character sets: the IBM PC set (Code Page 437), and Latin-1 (ISO 8859-1). Messages can be in either character set; the set is determined by the area attributes -- Internet and Usenet areas default to Latin-1, while all others default to IBM -- and by a CHRS or CHARSET kludge, if one is present. OMEN packets indicate their character set in the INFOxy.BBS file. MultiMail translates when displaying messages and creating replies. The Unix versions of MultiMail assume that the console uses Latin-1, while the DOSish versions (DOS, OS/2, and Windows) assume the IBM PC set. You can override this via the .mmailrc option "Charset", or on a temporary basis by pressing 'c'. You can also use a different character set by disabling the conversion in MultiMail, and letting your terminal handle it. For SOUP packets, and for Internet or Usenet areas in other packets, everything will be passed through unchanged if you set MultiMail to "Latin-1". For most other packet types, setting MultiMail to "CP437" will have the same effect. Beginning with version 0.33, a new character set variable is available: "outCharset". This is a string which MultiMail puts into the MIME identifier lines in SOUP replies if the text includes 8-bit characters. It's also used for the pseudo-QP headers which are generated under the same conditions; and when displaying such headers, MultiMail only converts text back to 8-bit if the character set matches. The default is "iso-8859-1". By default, if a header line in a SOUP reply contains 8-bit characters, MultiMail now writes it out with RFC 2047 (pseudo-QP) encoding. You can disable this for mail and/or news replies via the "UseQPMailHead" and "UseQPNewsHead" options, though I don't recommend it. The bodies can also be encoded in quoted-printable; this is now on by default for mail, and off for news. The options "UseQPMail" and "UseQPNews" toggle QP encoding. (The headers and bodies of received messages will still be converted to 8-bit.) QP decoding is temporarily disabled when you toggle the display of hidden lines ('X') in the letter window, so that you can see the raw text of the message. .SH ADDRESS BOOK The address book in MultiMail is intended primarily for use with Fido-style Netmail or Internet email areas, in those packet types which support these. When entering a message (other than a reply) into such an area, the address book comes up automatically. It's also possible to use the name portion of an address from the address book even when Fido/Internet addressing isn't available, by starting a new message via CTRL-E instead of 'E'. You can pull up the address book from most screens by pressing 'A', which allows you to browse or edit the list. While reading in the letter window, you can grab the current "From:" address by invoking the address book and pressing 'L'. .SH TAGLINE WINDOW From most screens, you can pull up the tagline window to browse or edit the list by pressing CTRL-T. As of version 0.43, you can toggle sorting of the taglines by pressing '$' or 'S'. .SH REPLY SPLITTING Replies may be split, either automatically, or manually via CTRL-B in the reply area. For automatic splitting, the default maximum number of lines per part is set in the .mmailrc. The split occurs whenever the reply packet is saved. This allows you to defer the split and still re-edit the whole reply as one. However, with autosave on, the split will occur immediately after entering a reply (because the save does, too). Setting MaxLines in the .mmailrc to 0 disables automatic splitting; manual splitting is still allowed. Attempts to split at less than 20 lines are assumed to be mistakes and are ignored. .SH ENVIRONMENT MultiMail uses the HOME or MMAIL environment variable to find its configuration file, .mmailrc; and EDITOR for the default editor. MMAIL takes precedence over HOME if it's defined. If neither is defined, the startup directory is used. The use of EDITOR can be overridden in .mmailrc; however, environment variables can't be used within .mmailrc. You should also make sure that your time zone is set correctly. On many systems, that means setting the TZ environment variable. A typical value for this variable is of the form "EST5EDT" (that one's for the east coast of the U.S.A.). .SH FILES The only hardwired file is the configuration file: .B .mmailrc (mmail.rc in DOS, OS/2 or Windows). It's used to specify the pathnames to MultiMail's other files, and the command lines for external programs (the editor and the archivers). By default, the other files are placed in the MultiMail home directory ($HOME/mmail or $MMAIL). Directories specified in the .mmailrc are created automatically; the default Unix values are shown here: .TP .B ~/mmail To store the tagline file, netmail addressbook, etc. .TP .B taglines A plain text file, one tagline per line. .TP .B addressbook (address.bk in DOS, OS/2 or Windows) A list of names and corresponding Fido netmail or Internet email addresses. Note that Internet addresses are prefaced with an 'I'. .TP .B colors Specifies the colors to use. (See COLORS.md.) .TP .B ~/mmail/down To store the packets as they came from the bbs. .TP .B ~/mmail/up To store the reply packet(s) which you have to upload to the bbs. .TP .B ~/mmail/save The default directory for saving messages. .SH CONFIG FILE The config file (see above) is a plain text file with a series of values, one per line, in the form "KeyWord: Value". The case of the keywords is not signifigant. Additional, comment lines may be present, starting with \&'#'; you can remove these or add your own. (But note that the comments are replaced by the defaults when you upgrade to a new version.) If any of the keywords are missing, default values will be used. As of version 0.41, any of these keywords except "Version" may also be specified on the command line. Command-line options take precedence over those in the config file, but their effect is not guaranteed -- some internal pathnames are initialized before the command line is read, for example. Here are the keywords and their functions: .TP .B Version Specifies the version of MultiMail which last updated the file. This is used to check whether the file should be updated and the "new version" prompt displayed. Note that old values are preserved when the file is updated; the update merely adds any keywords that are new. This keyword is also used in the colors file. .TP .B UserName Your name in plain text, e.g., "UserName: William McBrine". This is used together with InetAddr to create a default "From:" line for SOUP replies; and by itself in OMEN for display purposes (the actual From name is set on upload), and for matching personal messages. .TP .B InetAddr Your Internet email address, e.g., "InetAddr: wmcbrine@gmail.com". This is combined with the UserName in the form "UserName " ("William McBrine ") to create a default "From:" line for SOUP replies. Note that if neither value is specified, and nothing is typed manually into the From: field when creating a message, no From: line will be generated -- which is perfectly acceptable to at least some SOUP programs, like UQWK. .TP .B QuoteHead, InetQuote These strings are placed at the beginning of the quoted text when replying in normal or Internet/Usenet areas, respectively. (The distinction is made because the quoting conventions for BBSes and the Internet are different.) Replaceable parameters are indicated with a '%' character, as follows: %f = "From" in original message .br %t = To .br %d = Date (of original message) .br %s = Subject .br %a = Area .br %n = newline (for multi-line headers) .br %% = insert an actual percent character .br Note that you can't put white space at the start of one of these strings (it will be eaten by the config parser), but you can get around that by putting a newline first. .TP .B mmHomeDir MultiMail's home directory. .TP .B TempDir This is the directory where MultiMail puts its temporary files -- by default, as of 0.45, the same as mmHomeDir. The files are actually created within a subdirectory of this directory; the subdirectory is named "workNNNN", where NNNN is a random number (checked against any existing files or directories before being created). .TP .B signature Path to optional signature file, which should be a simple text file. If specified, it will be appended to every message you write. You should give the full path, not just the name. .TP .B editor The editor MultiMail uses for replies, along with any command-line options. This may also be a good place to insert spell-checkers, etc., by specifying a batch file here. Note that the default value is just the editor that's (almost) guaranteed to be available, for a given OS (although the Unix "EDITOR" environment variable is checked first), and is in no way a preferred editor; you can and should change it. .TP .B PacketDir Default packet directory. .TP .B ReplyDir Default reply packet directory. .TP .B SaveDir Default directory for saved messages. .TP .B AddressBook Path and filename of the address book. (You might change this to share it with another installation, but basically this keyword isn't too useful.) .TP .B TaglineFile Path and filename of the tagline file. This could be altered from a batch file to swap between different sets of taglines. (But note that this value is only read at startup.) You could also share taglines with another program, but be careful with that; MultiMail truncates the lines at 76 characters. .TP .B ColorFile Path and filename of the colors file. See COLORS.md. .TP .B UseColors Yes/No. This governs whether color is used, or monochrome. When colors are disabled, the terminal's default foreground and background colors are used. It's also a crude way to implement transparency (the only way, if you're not using ncurses or PDCurses/SDL) -- the entire background will be transparent when using an appropriate terminal. .TP .B Transparency Yes/No. Only available in ncurses or PDCurses for SDL. (The option will appear, but not work, in non-ncurses, non-PDCurses platforms.) When this is set to Yes, all areas where the background color is the same as the background color set in the "Main_Back" line, in the colors file, are instead set to the default background color, and thus become transparent areas in those terminal programs, like Eterm and Gnome Terminal, that support this. .TP .B BackFill Yes/No. Normally the background area is filled with a checkerboard pattern (ACS_BOARD characters, in curses terms). You can disable that here, leaving those areas as flat background color. This option is intended mostly to make transparency more effective, but it might help with any color scheme. .TP .B *UncompressCommand, *CompressCommand Command lines (program name, options, and optionally the path) for the archivers to compress and uncompress packets and reply packets. ZIP, ARJ, RAR, LHA and tar/gzip are recognized. The "unknown" values are a catch- all, attempted for anything that's not recognized as one of the other four types; if you have to deal with ARC or ZOO files, you might define the archiver for them here. .TP .B PacketSort The packet list can be sorted either in inverse order of packet date and time (the newest at the top), or in alphabetical order by filename. "Time" specifies the former, and "Name" the latter. (Actually only the first letter is checked, and case is not signifigant. This applies to the other keywords of this type (the kind that have a fixed set of values to choose from) as well.) The sort type specified here is only the default, and can be toggled from the packet window by pressing '$'. .TP .B AreaMode The default mode for the area list: "All", "Subscribed", or "Active". This is the mode that will be used on first opening a packet, but it can be changed by pressing L while in the area list or little area list. For a description of the modes, see USAGE. .TP .B LetterSort The sort used by default in the letter list. Can be "Subject" (subjects sorted alphabetically, with a case-insensitive compare), "Number" (sorted by message number), "From" or "To". (This can be overridden, as in the packet list.) .TP .B LetterMode The default mode for the letter list: "All" or "Unread". This is the mode used on first opening an area; it can be toggled by pressing L. (The Marked view is also available in the letter list, but cannot be set as the default here.) .TP .B ClockMode The display mode for the clock in the upper right corner of the letter window: "Time" (of day), "Elapsed" (since MultiMail started running), or "Off". .TP .B Charset The character set that the console is assumed to use. Either "CP437" (code page 437, the U.S. standard for the IBM PC and clones) or "Latin-1" (aka ISO-8859-1, the standard for most other systems). Note that the character set of messages is determined separately (q.v.). .TP .B UseTaglines Yes/No. If no, the tagline window is not displayed at all when composing a message. .TP .B AutoSaveReplies Yes/No. If yes, the reply packet is saved automatically -- the equivalent of pressing F2, but without a confirmation prompt -- whenever the contents of the reply area are changed. This can be convenient, and even a safety feature if your power supply is irregular, but it provides less opportunity to take back a change (like deleting a message). If no, you're prompted whether to save the changes on exiting the packet. Note that if you say no to that prompt, nothing that you wrote during that session will be saved (unless you saved it manually with F2). .TP .B StripSoftCR Yes/No. Some messages on Fido-type networks contain spurious instances of character 141, which appears as an accented 'i' in code page 437. These are really so-called "soft returns", where the message was wrapped when composing it, but not indicating a paragraph break. Unfortunately, the character can also appear legitimately as that accented 'i', so this option defaults to no. It can be toggled temporarily via the 'I' key in the letter window, and it doesn't apply to messages in the Latin-1 character set. This is now applied only in Blue Wave mode. .TP .B BeepOnPers Yes/No. If yes, MultiMail beeps when you open a message addressed to or from yourself in the letter window. (These are the same messages which are highlighted in the letter list.) .TP .B UseLynxNav Yes/No. See the description under USAGE. .TP .B ReOnReplies Yes/No. By popular demand. :-) Setting this to "No" will disable the automatic prefixing of "Re: " to the Subject when replying -- except in areas flagged as Internet email or Usenet, where this is the standard, and is still upheld. .TP .B QuoteWrapCols Numeric. The right margin for quoted material in replies (including the quote indicator). .TP .B MaxLines Numeric. See the description under REPLY SPLITTING. .TP .B outCharset String. See the description under CHARACTER SETS. .TP .B UseQPMailHead Yes/No. Controls the use of RFC 2047 encoding in outgoing mail headers. .TP .B UseQPNewsHead Yes/No. Controls the use of RFC 2047 encoding in outgoing news headers. .TP .B UseQPMail Yes/No. Controls the use of quoted-printable encoding in outgoing mail. .TP .B UseQPNews Yes/No. Controls the use of quoted-printable encoding in outgoing news. .TP .B ExpertMode Yes/No. If set to No, the onscreen help menus are not shown; instead, the space is used to extend the size of info windows by a few lines. .TP .B IgnoreNDX Yes/No. This option applies only to QWK packets. If set to yes, the *.NDX files are always ignored, in favor of the "new" indexing method that depends only on MESSAGES.DAT. This method is slightly slower than the *.NDX-based indexing method (though the delay is dwarfed by packet decompression time), but the most common problem with QWK packets is corrupt *.NDX files. MultiMail now recognizes some cases where the *.NDX files are corrupt and switches automatically, but it doesn't catch them all. .SH UPGRADING The basic upgrade procedure is to simply copy the new executable over the old one. No other files are needed. When you run a new version of MultiMail (0.19 or later) for the first time, it automatically updates your .mmailrc and ColorFile with any new keywords. (Old keywords, and the values you've set for them, are preserved. However, comments are lost.) Some notes on specific upgrades: Version 0.48 adds the .mmailrc option "Mouse", which allows you to enable or disable mouse input (for instance, if you don't want to see the mouse cursor). Version 0.45 adds "TempDir". Note that temporary files are handled differently in this version, and the TEMP and TMP environment variables are ignored. "homeDir" has been removed. Version 0.43 adds "ClockMode", and makes "UseColors" available in all ports. Also note that CPU usage while idle may be higher in some configurations. Version 0.41 adds the option "IgnoreNDX". Version 0.39 changes the function of the "Transparency" option slightly. It now operates on the color set in "Main_Back", rather than Black. Also, if you're accustomed to using the mouse to cut and paste under X or gpm, note that you now have to hold down the shift key while doing this. Version 0.38 adds "ExpertMode", "Transparency", "UseColors", and "BackFill", while removing the options "BuildPersArea", "UseScrollBars", "MakeOldFlags", and "AutoSaveRead". Version 0.37 adds "tarUncompressCommand" and "tarCompressCommand". Version 0.36 adds "LetterMode" and "AreaMode". Version 0.33 adds "ReOnReplies", "outCharset", "UseQPMailHead", "UseQPNewsHead", "UseQPMail" and "UseQPNews"; changes some default values. Version 0.32 adds "BuildPersArea" and "MakeOldFlags". Version 0.30 adds "UserName", "InetAddr", "QuoteHead", "InetQuote", and "QuoteWrapCols". Version 0.29 adds "UseScrollBars" and "UseLynxNav". Version 0.28 adds "MaxLines", "StripSoftCR", and "BeepOnPers". Version 0.26 adds "AutoSaveReplies", "AutoSaveRead", and "UseTaglines". Version 0.25 adds "Charset", "PacketSort", and "LetterSort". The default packet sort is now by time instead of name. If you're upgrading from 0.19 to 0.20 or later, and you have a customized ColorFile, be sure to note the new options. The ColorFile is new in 0.19. Check it out (~/mmail/colors, by default). As of 0.16, the HOME environment variable can be overridden with MMAIL, or omitted altogether. If you're upgrading from a version before 0.9, and you have existing reply packets (.rep or .new) whose names are partly or wholly in uppercase, you must rename them to lowercase before version 0.9 or higher will recognize them. (Downloaded packets are not at issue.) If you're upgrading from a version below 0.8, you may want to manually delete the /tmp/$LOGNAME directory created by previous versions. (0.8 and higher clean out their own temp directories, and use different names for each session.) If you're upgrading from a version prior to 0.7, please note the changes in the default directories; previously they were "~/mmail/bwdown", etc. .SH NOTES Unlike the other archive types, tar/gzip recompresses the entire packet when updating the .red flags, so it can be a bit slow. Also, the supplied command lines assume GNU tar, which has gzip built-in. Separated gunzip/tar and tar/gzip command lines are possible, but would require a (simple) external script. MultiMail only checks for the gzip signature, and does not actually verify that the gzipped file is a tar file. OPX reply packets are always created with a .rep extension, which differs from the behavior of some other readers. If you switch from QWK packets to OPX packets on the same board, MultiMail will _not_ open an old QWK .rep in OPX mode, nor vice versa. (It will try, and will terminate with "Error opening reply packet".) SOUP reply packets are created with the name "basename.rep", where basename is the part of the original packet name before the first period. (Unlike other formats, there's no actual standard for this in SOUP, but this seems to be the most common form among the SOUP readers I surveyed.) Also, not that I expect anyone to try this, but currently MultiMail is only able to read reply packets generated by other SOUP readers if the replies are in 'b' or 'B' mode, and are one to a file within the packet. Most readers meet the first criterion, but some of them batch all mail and news replies into a single file for each type. When re-editing a reply, it gets pushed to end of the list of replies. The R)ename function in the packet window can also be used to move files between directories; however, the destination filename must still be specified along with the path. If you're using the XCurses (PDCurses) version, and your editor isn't an X app, it will work better if you set MultiMail's "editor" keyword to "xterm \-e filename" (instead of just "filename"). I decided not to do this automatically because someone might actually use it with an X editor. Editing and deletion of old replies are available through the REPLY area, which always appears at the top of the area list. This differs from Blue Wave and some other readers. The Escape key works to back out from most screens, but after you press it, you'll have to wait a bit for it to be sensed (with ncurses; not true with PDCurses). Only Blue Wave style taglines (beginning with "...") are recognized by the tagline stealer. The tagline must be visible on the screen to be taken. Netmail only works in Blue Wave, OMEN and OPX modes, and is still slightly limited. Netmail from points includes the point address. Internet email is available in Blue Wave and OPX modes, for those doors that support it, and in SOUP mode, using the same interface as Fido netmail. .SH AUTHORS MultiMail was originally developed under Linux by Kolossvary Tamas and Toth Istvan. John Zero was the maintainer for versions 0.2 through 0.6; since version 0.7, the maintainer is William McBrine . Additional code has been contributed by Peter Krefting, Mark D. Rejhon, Ingo Brueckl, Robert Vukovic and Mark Crispin. .SH BUGS AND KNOWN PROBLEMS Red Hat Linux 6.0 (and possibly 6.x) comes with a defective installation of ncurses. When linked to this, MultiMail mostly works, but odd effects appear when scrolling. (Users describe it as double-spaced.) The problem can be fixed by reinstalling ncurses from the source -- not the source RPM that comes with Red Hat, but the original source from the ncurses site (see INSTALL). SOUP area type 'M' is not recognized. I have yet to find a program that can generate one. :-) The ANSI viewer eats a lot less memory than it used to, but it can still be a problem. (Each character/attribute pair takes up four bytes in memory. But lines which have the same attribute throughout are stored as plain text.) The new file list and bulletin viewer is a hack. If you find any bugs, please write to me. mmail-0.52/config.h000644 000765 000024 00000010072 13450520640 015125 0ustar00wmcbrinestaff000000 000000 #define STRingize(x) #x #define STR(x) STRingize(x) #define MM_MAJOR 0 #define MM_MINOR 52 #define MM_NAME "MultiMail" #define MM_SNAME "MMail" #define MM_VERNUM STR(MM_MAJOR) "." STR(MM_MINOR) #define MM_TOPHEADER MM_NAME "/%.16s v" MM_VERNUM #define USE_SHADOWS // "Shadowed" windows #define VANITY_PLATE // Author info -- undefine for longer packet list /* With the default tar/gz compress command, leave this on. It causes the entire file to be rearchived when using that format, instead of just the .red file. If you replace the tar/gz command with something smarter (e.g., via a script), you can disable the kludge. */ #define TAR_KLUDGE /* ----- Some supported compilers ----- */ #if defined(__TURBOC__) && defined(__MSDOS__) # define TURBO16 #endif #if defined(__TURBOC__) && defined(__WIN32__) # define BORLAND32 #endif #if defined(__WATCOMC__) && defined(__I86__) # define WATCOM16 #endif #if defined(TURBO16) || defined(WATCOM16) # define SIXTEENBIT #endif /* ----- Platform-specific defines ----- */ /* HAS_UNAME determines whether the "System" part of the tearline is taken from the uname() function, or is hardwired. Turbo/Borland C++ and Watcom don't have it. */ #if !defined(__TURBOC__) && !defined(__MINGW32__) && !defined(__WATCOMC__) && !defined(_MSC_VER) # define HAS_UNAME #endif /* MS-DOS and DOS-like systems. */ #if defined(__MSDOS__) || defined(__WIN32__) || defined(__OS2__) # define DOSCHARS # define DOSNAMES # define USE_SHELL # define EXTRAPATH /* Assume the use of PDCurses on these platforms (can't check it explicitly until curses.h is included), and so: Some characters are unprintable on standard Unix terminals, even with A_ALTCHARSET set. But PDCurses will handle them. */ # define ALLCHARSOK #else /* Not a DOS-like system -- enable home directory elision. */ # define HAS_HOME #endif /* Also, see the NCURSES_SIGWINCH definition in interfac/interfac.h -- it should be fine as is, but may need manual adjustment in some cases. */ /* I use strcasecmp() and strncasecmp() throughout, but some systems call these functions stricmp() and strincmp(). */ #if defined(__TURBOC__) || defined(__WATCOMC__) || defined(_MSC_VER) # define USE_STRICMP #endif /* unistd.h is the POSIX header file. Borland/Turbo C doesn't have it. */ #if !defined(__TURBOC__) && !defined(__MINGW32__) && !defined(__WATCOMC__) && !defined(_MSC_VER) # define HAS_UNISTD #endif /* Limit allocation sizes for 16-bit systems */ #ifdef SIXTEENBIT # define LIMIT_MEM # define MAXBLOCK 0x0FFE0L #else # define MAXBLOCK 0x07FFFFFE0L #endif /* For the 16-bit MS-DOS version compiled with Turbo C++ 3.0, I've added the SPAWNO library by Ralf Brown to get more memory when shelling. */ #ifdef TURBO16 # define USE_SPAWNO #endif /* Watcom (all DOSish platforms) and Turbo C++ need an extra call to get the drive letter changed, when changing directories. */ #if defined(__WATCOMC__) || defined(TURBO16) # define USE_SETDISK #endif /* In Borland/Turbo C++, using findfirst()/findnext() is faster than using readdir()/stat(). */ #ifdef __TURBOC__ # define USE_DIRH # define USE_FINDFIRST #endif /* Another variation, _findfirst()/_findnext(), for Windows. */ #ifdef __WIN32__ # ifndef USE_FINDFIRST # define USE_FINDFIRST # endif # define USE_IOH #endif /* Borland C++ 5.5 barfs on time_t = 0, which appears as the timestamp of the top-level directory. Also, utime(), though implemented, doesn't work right. */ #ifdef BORLAND32 # define TIMEKLUDGE # define USE_SETFTIME #endif /* Turbo C++ 3.0 lacks the "bool" and "off_t" types.*/ #ifndef TURBO16 # define HAS_BOOL # define HAS_OFFT #endif /* Some lines in the code serve no purpose but to suppress the GCC warning "might be used uninitialized in this function". Borland C++ 5.5, on the other hand, complains "is assigned a value that is never used" if I leave these lines _in_. */ #ifndef __TURBOC__ # define BOGUS_WARNING #endif /* ----- End of platform-specific defines ----- */ #ifndef HAS_BOOL typedef unsigned char bool; # define true 1 # define false 0 #endif #ifndef HAS_OFFT typedef long off_t; #endif mmail-0.52/Makefile.vc000644 000765 000024 00000001574 13433645145 015576 0ustar00wmcbrinestaff000000 000000 #-------------------------------------------------- # MultiMail Makefile (top) for Microsoft Visual C++ #-------------------------------------------------- CURS_DIR = \pdcurses LIBS = $(CURS_DIR)\wincon\pdcurses.lib user32.lib advapi32.lib COMPILER = $(CC) -nologo -c -O2 -D__WIN32__ all: mm O = obj .SUFFIXES: .cc {mmail}.cc.obj:: $(COMPILER) $< {interfac}.cc.obj:: $(COMPILER) -I$(CURS_DIR) $< MOBJS = misc.$(O) resource.$(O) mmail.$(O) driverl.$(O) filelist.$(O) \ area.$(O) letter.$(O) read.$(O) compress.$(O) pktbase.$(O) bw.$(O) \ qwk.$(O) omen.$(O) soup.$(O) opx.$(O) IOBJS = mmcolor.$(O) mysystem.$(O) isoconv.$(O) basic.$(O) interfac.$(O) \ packet.$(O) arealist.$(O) letterl.$(O) letterw.$(O) lettpost.$(O) \ ansiview.$(O) addrbook.$(O) tagline.$(O) help.$(O) main.$(O) mm: $(MOBJS) $(IOBJS) link -out:mm.exe *.obj $(LIBS) clean: del *.obj del mm.exe !include depend mmail-0.52/mmail/000755 000765 000024 00000000000 13446322300 014604 5ustar00wmcbrinestaff000000 000000 mmail-0.52/Makefile000644 000765 000024 00000005323 13433644713 015163 0ustar00wmcbrinestaff000000 000000 #-------------------------- # MultiMail Makefile (top) #-------------------------- msrc = mmail isrc = interfac # General options (passed to mmail/Makefile and interfac/Makefile). POST # is for any post-processing that needs doing. ifeq ($(DEBUG),Y) OPTS = -g -Wall -pedantic else OPTS = -O2 -Wall -pedantic POST = strip mm$(E) endif # PREFIX is the base directory under which to install the binary and man # page; generally either /usr/local or /usr (or perhaps /opt...). PREFIX = /usr/local #-------------------------------------------------------------- # Defaults are for the standard curses setup: # CURS_DIR specifies the directory with curses.h, if it's not in the # include path. LIBS lists any "extra" libraries that need to be linked # in, including the curses library. RM is the Delete command ("rm" or # "del", as appropriate), and SEP is the separator for multi-statement # lines... some systems require ";", while others need "&&". ifeq ($(OS),Windows_NT) CURS_DIR = /pdcurses LIBS = $(CURS_DIR)/wincon/pdcurses.a RM = del SEP = && E = .exe else CURS_DIR = . LIBS = -lcurses RM = rm -f SEP = ; E = endif #-------------------------------------------------------------- # With PDCurses for X11: ifeq ($(SYS),X11) CURS_DIR = $(shell xcurses-config --cflags | cut -c 3-) LIBS = $(shell xcurses-config --libs) endif #-------------------------------------------------------------- # With PDCurses for SDL: ifeq ($(SYS),SDL) CURS_DIR = /Users/wmcbrine/pdsrc/PDCurses LIBS = $(CURS_DIR)/sdl2/pdcurses.a $(shell sdl2-config --libs) endif #-------------------------------------------------------------- # For DJGPP: ifeq ($(SYS),DOS) CURS_DIR = /pdcurses LIBS = $(CURS_DIR)/dos/pdcurses.a RM = del SEP = ; E = .exe POST = strip mm.exe; exe2coff mm.exe; copy /b \ c:\djgpp\bin\cwsdstub.exe+mm mm.exe; del mm endif HELPDIR = $(PREFIX)/man/man1 O = o .SUFFIXES: .cc .PHONY: clean dep install all: mm$(E) MOBJS = misc.o resource.o mmail.o driverl.o filelist.o area.o letter.o \ read.o compress.o pktbase.o bw.o qwk.o omen.o soup.o opx.o IOBJS = mmcolor.o mysystem.o isoconv.o basic.o interfac.o packet.o \ arealist.o letterl.o letterw.o lettpost.o ansiview.o addrbook.o \ tagline.o help.o main.o $(MOBJS) : %.o: $(msrc)/%.cc $(CXX) $(OPTS) -c $< $(IOBJS) : %.o: $(isrc)/%.cc $(CXX) $(OPTS) -I$(CURS_DIR) -c $< mm$(E): $(MOBJS) $(IOBJS) $(CXX) -o mm$(E) $(MOBJS) $(IOBJS) $(LIBS) $(POST) dep: $(CXX) -MM $(msrc)/*.cc | sed s/"\.o"/"\.\$$(O)"/ > depend $(CXX) -I$(CURS_DIR) -MM $(isrc)/*.cc | sed s/"\.o"/"\.\$$(O)"/ >> depend clean: $(RM) *.o $(RM) mm$(E) install:: install -c -s mm $(PREFIX)/bin install -c -m 644 mm.1 $(HELPDIR) $(RM) $(HELPDIR)/mmail.1 ln $(HELPDIR)/mm.1 $(HELPDIR)/mmail.1 include depend mmail-0.52/HISTORY.md000644 000765 000024 00000353647 13450521061 015212 0ustar00wmcbrinestaff000000 000000 Revision History ================ ## 0.52 - 2019/04/01: - Support extended length (71 character) To: and From: fields in QWKE replies. Reported by "g00r00" et al. - For system names between 6 and 10 characters, use "MMail/name" instead of "MultiMail" for the Blue Wave tear line. - Fixes for various warnings when building with current compilers; less cluttered build output. - Use CURS_DIR to specify the default library location on the command line when building; removed references to CURS_LIB and CURS_INC. Partly due to Rob Swindell. - Use "SYS=DOS" with Makefile.bcc, as with others, instead of "DOS=Y". - Makefiles renamed PDCurses-style, for a simpler build process. - Binaries built against PDCurses current git (post-3.8); Makefiles modified accordingly. - Cleaned up documentation; also, the man page and README no longer include the version number, which is now only specified in config.h (and here). See the git log for more details. ## 0.51 - 2018/04/01: It is risen. No fooling. - Enable blinking in PDCurses for the ANSI viewer, wherever possible. (Binaries of this version were built with PDCurses 3.6, which adds software blinking for most platforms.) - The Windows build now allows resizing under Windows 10, or with ConEmu in some earlier versions. - Workaround for corrupt messages.dat when using the fallback (non-.ndx) QWK scan -- keep looking for next valid header. Based on bug report by "XBessa". - Blue Wave issue: macros string fields are different lengths in INF_HEADER vs. PDQ_HEADER. Reported by Frederic Cambus. - A 64-bit version built with MSVC would crash on startup (other 64-bit builds not affected) -- fixed. - NetBSD fixes by Frederic Cambus. - The Mac build now reports itself as "MultiMail/Mac" instead of "MultiMail/Darwin"; also, "/DOS" vs. "/MS-DOS", "/OS2" vs. "/OS/2", "/Win" vs. "/Win32". - Makefiles reorganized and reduced -- elminiated the per-directory build process; all objects are built in the top-level directory now. DJGPP and MinGW are merged into the main Makefile; DEBUG, SDL and X11 are command-line options. MSVC does an all-at-once build (similar to PDCurses). Borland for Windows is restored, and building with old Turbo C++ is combined into Makefile.bcc via a command-line option. And Watcom now allows building for any platform via an option (instead of editing the Makefile). - Removed separate "modules" file (now builds all) and "version" file (now defined in config.h). - Dropped the mmail.spec file -- I think this is better left to distro maintainers. - Dropped support for EMX; cleaned up some lingering references to RSX/NT. - Various compile warnings and errors with more modern compilers, undone. - New source code formatting -- four-space indentation, no tabs. - Documentation "converted" to Markdown format (mainly just by renaming files); removed trailing spaces. - Most references to "Win32" changed to "Windows", per Microsoft recommendations. - Spelling and other doc fixes by Robert James Clay. - Updated web and email addresses. - The Artist Formerly Known as Peter Karlsson is now "Peter Krefting". ## 0.50 - N/A There was never a formal 0.50 release, but over the years, people have distributed versions of "0.50", based on the source code repository. So, in an attempt to reduce confusion, I'm skipping "0.50" for the next release. ## 0.49 - 2007/07/19: - Built against PDCurses 3.3 (and 3.1 is now the minimum version that will work); removed XCURSES references, for the sake of PDCurses 3.2+. This is faster, and allows me to finally chuck the resize_term() kludge introduced in 0.42 (for the problem where the window scrolled up on return from shelling out in Win32). - You can now use the "Transparency" keyword with PDCurses -- with the SDL port, you can achieve the same effects as in, e.g., Gnome Terminal with ncurses. - I'm distributing the Win32 binary with pdcurses.dll dynamically linked, purely so that you can swap it out with the SDL version if you like (see http://pdcurses.sf.net/). It's fun to play with. :-) - In "animate" mode, the ANSI viewer now pauses after each character. This feature was omitted for a long time because the animation was slow enough on older machines, and the shortest possible delay was still too long. But animated mode is useless nowadays without it. - If the config files can't be written (updated), MultiMail no longer aborts with an error; instead, a message is displayed, with a pause, and it continues. - Updated to GPL version 3. ## 0.48 - 2007/04/04: - This version now builds with PDCurses 3.0, and if using PDCurses, will no longer build with an earlier version. This gives us mouse support for DOS and OS/2, and better idling in the XT version, among other improvements. - New keyword: "Mouse". Setting it to "No" turns off support for mouse input. Mainly this is to allow disabling of the mouse cursor; it also means you can select without holding shift in X11. - Use setlocale(), to give a correct display in environments such as the console under Ubuntu. - The FAQ and TODO files were outdated, and not very useful. - Removed the makefiles for EMX and Borland C++ for Windows. These compilers are basically dead. I also removed Makefile.wid -- full- fledged wide-character support will be coming in the future, but for now, this file was just more clutter. - Changed "install" to a double-colon rule in Makefile to avoid conflict with INSTALL on non-case-sensitive filesystems, like Mac OS X's HFS+. ## 0.47 - 2005/12/31: ### New features: - Added support for Microsoft Visual C++, MinGW, and Open Watcom. I highly recommend Watcom, and I'm now using it for the OS/2 port. I'd use it for the Win32 port, but sadly the directory scan (used in the packet list) is using the slow POSIX-emulation method rather the fast native method, and I haven't got that sorted out yet. MinGW is also very nice, and I'd use that, except that it needs MSVCRT.DLL, which doesn't come with old Windows 95. So -- rather than continuing with the 5-year-old Borland 5.5 (although it still works) -- I'm delivering an MSVC-built version. This version doesn't support set_new_handler(), but no one is likely to notice (famous last words). Also, I've finally dropped the pretense of supporting RSX/NT. - The starting directory and home directory are now added to the PATH searched for archivers and editors, so you can include them in the same directory with mm.exe. I'm taking advantage of this to bundle InfoZip's zip and unzip, hugely bloating the Windows zip, but hopefully stopping the complaints I get from people who can't figure out how to set up archiving themselves. - Support for the "GreekQWK" variant, which uses 0x0C instead of 0xE3 as the line delimiter. Reported by Dimitris Mandalidis. Note that there's still no particular support for the Greek _character set_, but you can probably make it work by disabling character set conversion. - A mouse click in the lower half of the screen, in the ANSI viewer, now counts as if the spacebar had been pressed, instead of PgDn. Also, when the screen is at the start, even clicking in the top half counts as a space. This makes for easier mouse-only navigation with the new welcome screen display. - Included Makefile.wid for wide-character ncurses. This doesn't really do anything yet, except give you the proper background character instead of the octothorpe, and work semi-decently in a UTF-8 Linux VT. For now, I don't recommend using it, nor distributing binaries based on it. - Various documentation updates. ### Bug fixes and such: - Revisions for newest version of PDCurses -- fewer ifdefs needed. Note that MultiMail now must be compiled with PDCurses 2.7 or later. - Changed some compiler options to work better with recent gcc. Unfortunately it bloats the executable. - Removed the distinction between Bulletins and New file lists, so there's only one prompt to bother you with now. - Minor code cleanup. ## 0.46 - 2003/12/31: ### New features: - First, an apology: This version probably should've come out in July, when most of these changes were done... though there was a bug in one of the new features that didn't show up until Halloween. - MultiMail now displays the appropriate screens (when it can determine them) on opening and closing a packet, instead of just adding them to the bulletin list. This is more like the way most other OLRs work (though not, e.g., Blue Wave). I hope you like it. :-) I had to adjust to it myself, but now I wouldn't go back. - The ANSI viewer now includes partial support for AVATAR (level 0) and BSAVE (.BSV; text-only). As with the parsing of AT codes, these can be toggled, via ^V and ^B respectively. - The lower portion of the area list display has been redesigned. It now includes the name of the Door and BBS that created the packet, if available, and omits the duplicate description. The field formerly labelled "BBS:" is now "Name:"; "BBS:" is used for the software type instead. In the upper portion, there's a little more room for the descriptions. Door type in OPX mode is not entirely satisfactory; some SX doors seem to use the field for BBS version instead. Neither of the new fields is available in Blue Wave mode. ### Bug fixes and such: - With this version, I've added MIPS and AMD64 to my list of tested systems (no mods needed). However, I'm not including those binaries, because a) the MIPS binary I built is only for a Tivo, and doesn't display onscreen anyway, so you'd have to telnet in or use a serial console; and b) there's a small but annoying bug in the (beta) version of ncurses used on SF.net's AMD64 compile farm machine, such that the window borders all show up with the wrong color. (The problem is not actually 64-bit related. Also, please note that MultiMail itself has been 64-bit ready for a long time, since being ported to the Alpha processor.) - Fix for some incorrect CONTROL.DAT files -- a failed read (because a file was too short) was returning a dupe of the last line; now returns a blank. - Make reply endings in OPX conform better to what SX does -- CR/LF, and a trailing zero. (I noticed that the tearline was being eaten by the Wildcat SX door on Doc's Place, and there was _no_ final CR/LF... I don't think it used to do that, but anyway, this fixes it.) - ANSI viewer: Allowed right arrow to exit even in lynxNav mode -- makes more sense with new Hello/Goodbye behavior; fewer false positives when interpreting AT codes; better handling of chars 0-31 and some others, under ncurses; no more overlapping of screens when a screen clear command is rendered. - Some code simplification. ## 0.45 - 2003/06/08: ### New features: - New temporary directory and file system: Instead of using tmpnam() -- which frankly never worked properly outside of Unix, and is sometimes regarded as a security risk there -- MultiMail now generates temp directories of the form "workNNNN", where NNNN is a random number, under the directory specified by the new .mmailrc keyword "TempDir". By default, TempDir is set to the same value as mmHomeDir. NOTE THAT IF YOU'VE BEEN USING ENVIRONMENT VARAIBLES TO SPECIFY A TEMP DIR, IT WILL NO LONGER WORK. They aren't even used to set the default (as with EDITOR). In the XT port, TempDir also now specifies the location of the swap file, if that feature is enabled. And temporary files are now created as "tmpNNNNN.txt" under the workNNNN directory, where NNNNN is a serial number; so they no longer clutter the base temp directory, they have the ".txt" extension some editors depend on, and there's no longer a problem with editor-created backup files being cleared out. - Big improvements in the ANSI viewer under Unix: For those terminals (like the Linux console) that support the "smpch" terminfo capability, the IBM PC characters are passed through directly. For other terminals (like xterm), the use of the widely unimplemented ACS_BLOCK and ACS_BOARD has been replaced with an inverse space and ACS_CKBOARD, respectively. (In ncurses' curses.h, ACS_BLOCK and ACS_BOARD are identified as "Teletype 5410v1 symbols", while ACS_CKBOARD is in the "VT100 symbols" group. Everything supports VT100, but few terminals support the other class.) Note that the use of the inverse space does present a problem when characters have the A_BOLD attribute, as it's interpreted differently by different terminals, so that some will make the cell "bright", and some won't; but this seems to look best on the greatest number of terminals. Also note that "xterm -fn vga -tn linux" is still the best way to view ANSI under X. ;-) - A new method for setting mmHomeDir: If it's based on the environment variable "HOME", add "mmail" to the end, as before; but if it's based on "MMAIL" or the start directory, don't. Practically speaking, this should make it apply "mmail" under Unix and not under DOS-ish systems, in most cases. That means that if you create a directory like "C:\MMAIL" and run mm.exe from there, it will no longer create "C:\MMAIL\MMAIL\DOWN", etc., but simply "C:\MMAIL\DOWN". Note that this (unlike the new temp system) applies only to new installations; upgrades will not alter existing MMAIL.RC pathnames. - The name shown at the top of the screen is now "MultiMail/Sysname vX.X" -- i.e., the same as would appear in a QWK reply tearline -- instead of "MultiMail offline reader vX.X". Shorter, yet more informative. :-) ### Bug fixes and such: - Doing a ^X during a bulletin would cause a segfault. - A malformed 'H' command (e.g., using "0" as a parameter) in ANSI could cause a segfault. - The window title is explicitly set to "MultiMail" in the Win32 and XCurses ports. This eliminates the problem where "UNZIP" would remain in the title after unarchiving a packet under Win 9x. - Revived cursor mode save/restore routines for PDCurses (except for Win32, where it doesn't work); moved cursor restoration after endwin() call in mysystem() (this is why it didn't do the job before). - Updated Makefiles for PDCurses 2.6, and GCC 3.2.1 (in the case of EMX). I strongly recommend upgrading to PDCurses 2.6, in most cases, as it already incorporates my patch for halfdelay(); however, there's a fatal bug in the OS/2 version. (Write me for a patch.) I also recommend ncurses 5.3 over 5.2 -- for one thing, it fixes the problem with the bool declaration that broke searching and animation in some installations -- but I've left it as 5.2 in the Makefile, for now, because I have too many systems to recompile it for. :-) - Remaining compilation warnings eliminated for most platforms: Got rid of tmpnam() (see above), moved "%y" strftime() strings outside the calls (copied this from Ingo Brueckl), and added "-Wno-deprecated" to the Makefile options for the latest versions of gcc. - Useless .mmailrc keyword "homeDir" removed. (It specified the base value for mmHomeDir, but in most cases that was already specified explicitly on the very next line.) - Stronger attempts to use backslash characters instead of slash where appropriate in MMAIL.RC (depending on the platform). But again, this is cosmetic. - Optimizations, comments, etc. ## 0.44 - 2003/05/07: ### New features: - Added the pipe character, '|', as an alternative to '^' for invoking the filter function. It turns out that '^' is a poor choice for many non-U.S. keyboards; sorry. The pipe character is also somewhat more consistent with other programs with similar functions. Problem reported by Peter Krefting. ### Bug fixes and such: - Changed the way that the length of QWK replies is calculated. This is a speculative fix for a reported problem with corrupt replies that I couldn't reproduce; but from what was described, this _should_ do it. At any rate, the new code is simpler and cleaner. Reported by Jimmy Day et al. - Pressing the "END" key in string input fields now works correctly, taking the cursor to the end of the field instead of ending the edit. Reported by Touko Rajala. - Bogus/unintentional '@' color code sequences could cause segfaults in the ANSI viewer. Reported by Neall Mercado. - In the Win32 version, under NT (though not 9x), the temporary directories were not being deleted. - The filter was not working correctly in the LittleAreaListWindow. - Junk could appear in the LetterList on a resort if some messages were read. - Narrowed the width of some windows as a workaround for display bugs in xterm. - Added "-inul" to the default command lines for RAR to suppress the output. - Minor internal changes to allow compilation and remove warnings under Sun's SUNWspro C++ compiler. This also entailed the elimination of the sillier of the "set_Letter_Params()" functions, which was long overdue. :-) - Miscellaneous small changes for efficiency. ## 0.43 - 2002/03/09: ### New features: - A big one: Filter feature in all list windows, invoked by pressing '^' (shift-6). See the "FILTERING" section of the man page for details. This was inspired by similar features in the Pan and BNR newsreaders. - Clock in the upper right corner of the letter window. (I would've added it to the others, but they lack an obvious place to put it. I figured that one spends most of one's time in the letter window anyway.) It can show time of day, time since startup, or be disabled, as controlled by the new keyword "ClockMode". Note that this required changes to the keyboard routines (now using halfdelay() instead of a blocking call), which in turn affected the ability of MultiMail to give up CPU time when idling. Most ports still use little CPU, but the XT version pegs it high, except under DOSEmu. (Old versions of DOSEmu, ironically, instead show high CPU usage with the new DOS (DPMI) version, which doesn't have that problem under Windows or OS/2.) Also, I had to fix PDCurses to make this work -- write to me if you want the patch. - "AT code" parser for the ANSI viewer, for PCBoard and Wildcat '@' color codes. (I've only seen these used in the sample packets that came with a few readers -- do they exist in the wild?) Also, "ANSI music", though still not played, is now filtered out, so at least it doesn't clutter the image. - The "UseColors" keyword is now available in the PDCurses (DOSish) ports. That means a monochrome mode, which should help on some displays. - The packet name is now displayed when in the letter list, letter window or ANSI viewer. No more having to back out to check. :-) The placement of the packet name in the area list window has been changed for consistency. - The tagline window can now be sorted, optionally. - An "Opening..." notice is now displayed when entering an area, as when opening a packet. (This operation can be slow, so it's good to have a hint that MultiMail got the message.) ### Bug fixes and such: - The type 'm' (Unix mailbox) parser in the SOUP module was missing some valid "From " separators, so I incorporated code based on the VALID macro from the C-Client library by Mark Crispin. I've read conflicting claims about whether its "free-fork" license is GPL-compatible, but since I don't see a better way to do this, I've chosen to assume that it is, at least for now. Anyway, this may in fact be the _only_ valid (no pun intended) way to parse a Unix mailbox without false positives or negatives. - The ANSI viewer was not displaying the headers of SOUP messages when animating them, or thereafter. This was really a problem with multiblock messages (otherwise not noticeable except in the XT version, and then only with a message over 64K). Also, the ANSI viewer now acknowledges "expert" mode (i.e., it omits the "F1 or ? - Help" message from the lower right). - The address book and tagline windows now show the number of items, like the other lists. - In an xterm, when the tagline window was active, bits of the scroll bar and the screen border were being replaced with blanks. I've fixed this in an ad hoc way, but I'm not quite sure what caused it. - An empty file list in the packet window caused problems. Normally you wouldn't see this, because there's always at least a ".." entry for the parent directory. But the filesystem on a Windows CE device apparently lacks them, as I found when running MultiMail/XT under PocketDOS. - Setting the "UseColors" keyword to "No" now forces the use of the default colors (or rather, the monochrome version of them), as the .mmailrc comment had already claimed it did, instead of the monochrome version of the colors specified by "ColorFile". - The "Reverse" attribute will no longer be stripped from a color file that's rewritten by a PDCurses (DOSish) port. - If the tagline file existed, but was completely empty, a segfault would occur on selecting the (nonexistent) tagline when composing a message. Reported by Touko Rajala. - Some really basic stuff added to the man page -- it always should've been there. Further revision is needed. - Minor speedups. ## 0.42 - 2001/12/08: ### New features: - CTRL-E instead of 'E' now forces invocation of the addressbook when entering a message in a non-netmail, non-email area. This only gets you the real name portion. I don't really see the use of this myself -- the entire point of the addressbook is to store netmail and email addresses, not simple names -- but it was requested, and easy to do, so here it is. It works wherever 'E' works (area list, letter list, letter window), although due to space considerations and laziness, it only appears in the letter list's help menu. Note that you can still only use the 'L' function of the addressbook on messages where an address is present. (Changing that would be more involved.) Suggested by Greg Sears. ### Bug fixes and such: - In the Win32 version, under Windows XP and perhaps 2000 (?), the window was scrolled up on returning from a shell (unarchiving, etc.), unless the scrollback buffer was disabled. Although cosmetic, this was enough of a nuisance that I'm releasing 0.42 now. - Added strings.h to the includes in mmail.h to get it to compile under QNX. (If you're trying this, edit the Makefile to say "-lncurses" rather than "-lcurses".) Note that the background characters (ACS_BOARD and ACS_CKBOARD) aren't what they should be in QNX; otherwise, it works well. - New error message for the case when no files are found in the work directory after uncompression, but no error code was returned by the unarchiver. (Previously this resulted in the misleading message "Packet type not recognized"; that message is now reserved for times when there are actually some files in the work directory.) - Since version 0.38, when I moved development to a new computer, the documentation file "mm.txt", included with the DOSish ports, had not been converted correctly by the script that builds those archives -- it was left as raw troff code instead of a rendered man page, and the line endings were left as LF (Unix style). In other words, it was basically illegible. The discouraging part of this is that no one reported it, which makes me think that no one's reading the documentation. :-( - Other, minor updates to the documentation. ## 0.41 - 2001/10/16: ### New features: - Options can now be specified on the command line. Any .mmailrc option may be used, though not all will actually work (some of them are used in initialization before this point). Options are preceded by '-' or "--", and must finish before a packet is specified. See the man page for more details. - New keyword: "IgnoreNDX". This makes MultiMail ignore the *.NDX files in QWK packets, in favor of the new (slower, but safer) indexing method that uses only MESSAGES.DAT. Turn this on if you get bogus *.NDX files from your BBS (as seems all too common). ### Bug fixes and such: - Big bug: Reply splitting in QWK on DOSish systems (DOS, Win32, OS/2) generated a corrupt reply packet (always). It's been this way a long time; possibly since the message split feature was introduced. I never noticed it because a) I rarely split messages, b) I seldom use QWK anymore -- all SOUP and BW now, c) I never use the DOSish ports, except for testing, and d) the split replies _look_ OK, until you close and reopen the packet. I had a lot of bug reports which, in retrospect, seem to be about this, but I never got enough detail to make sense of the problem, until Ken Hrynchuk reported on it. Many thanks to him. - Bogus values in the *.NDX files of a QWK packet are now checked for and often handled without having to abort. In some cases, MultiMail is able to revert to the alternate indexing method automatically; in others, the message "ERROR READING MESSAGES.DAT" appears in the letter list, instead of the gibberish that resulted when following a bogus pointer. For those cases which still cause problems, see the new "IgnoreNDX" keyword. - Problems with the ncurses configuration on Mandrake 8 (well, on my system, anyway) caused MultiMail to hang on searches or ANSI animation. There's now a partial workaround for this (searching works OK, ANSI anim still hangs) in MultiMail. But the proper fix is this: Download the ncurses source; configure; manually edit the line in curses.h that defines bool to say "typedef unsigned char bool" instead of "typedef unsigned bool"; compile; and install. I still have to figure out why the typedef comes out that way; the same version of ncurses (with the same configure script) works correctly on my old Slackware system. You might see this problem on other systems as well. - Blue Wave messages with Internet kludge lines (as produced by version 4 doors) were causing segfaults. This is what happens when a feature doesn't get exercised for a while. :-( - Segfault on a message with a MSGID kludge line, but no other body text. - In the Win32 port, packet time stamping now works under Win 9x, though it still doesn't work on network shares under NT. - Some unprintable characters weren't being filtered out when running in an xterm, resulting in a garbled display. - In the 16-bit port, in QWK packets with no *.NDX files, MultiMail was failing if the MESSAGES.DAT file was larger than 32K. - The END key now works under screen (for example) with ncurses. - The message-splitting function now uses parentheses instead of brackets, and the part number is padded with leading zeroes. I did this to match standard practice in the alt.binaries.* newsgroups. - Added the undocumented option "-#" to the default command lines for PKZip and PKUnzip. This suppresses output, equivalent to the "-q" option used in the default InfoZip command lines. Suggested by K.H. (Note that if you're upgrading, you'll have to put it in manually, if you want it; as always, MultiMail won't alter values that are already specified in its config file.) - Shelled commands (zip, unzip, edit) can now handle paths with spaces in them. - On MultiMail's first run, on DOSish systems, the MMAIL.RC ended up with paths that had a mix of slash and backslash characters. Although this didn't interfere with operation, it was a cosmetic defect. - The defaults in the top-level Makefile have changed to "" for CURS_INC, and "-lcurses" for LIBS. Most/all distros of Linux, as well as FreeBSD, have replaced the older curses with ncurses by now, and some (e.g., Debian) lack the old-style compatibility links, so this now seems to be the most widely compatible configuration. (The old defaults are now in the first commented-out example section.) - I figured out the problem that led to the hasty release of version 0.40 -- it was a missing pair of parentheses. Or to look at it another way, a misunderstanding of operator precedence. ## 0.40 - 2001/04/15: ### New features: - Nothing new this time. ### Bug fixes and such: - Undid one part of the BSOC, which (perversely enough, since this is exactly what it was intended to cut down on) was causing crashes in the Win32, XT, and OS/2 ports. I still haven't nailed down WHY it was doing this, but I isolated the source of the trouble by brute-force analysis. :-( I promise better testing next time. - Useless mouse code now omitted in DOS, XT and OS/2 ports. (Yes, I hope to actually support the mouse on these platforms eventually. First, mouse support has to be added to their respective ports of PDCurses.) ## 0.39 - 2001/04/13: ### New features: - Mouse functionality. Currently, this is only supported under X (with either ncurses or PDCurses), in the Linux console with gpm, and in the Win32 port. See the man page for a description of how to use this -- not well-described there, but it should be fairly intuitive. - A new flag to join Marked, Replied, Read and Private: Saved. It's set automatically when a message is saved (natch). It appears in the letter list as a lowercase 's', and as a new flag in the group displayed in the letter window. (Note that if the message is also Marked, only the 'M' will appear in the letter list -- I was short of columns.) Because of this, the names displayed in the letter window have been shortened. Saving a message no longer marks it as Read, since that seems redundant now (though it still does clear the Marked flag). The new flag is mapped appropriately in .XTI files, but not in MAIL.FDX, so it's not preserved in OPX packets. - New transparency method: The transparent background color is the one specified by "Main_Back" in the colors file, rather than always black. This allows the use of transparency with light backgrounds, etc. (See the screenshots web page.) ### Bug fixes and such: - A longstanding bug: It was impossible to rename packets after opening them. Finally fixed, as part of a big string-operation cleanup. - Another longstanding one, also part of the BSOC: On some platforms, when specifying a packet name on the command line, names of certain lengths wouldn't work. After I finally sorted this out, the surprising thing was that it had ever been working for names of any length. - Many potential buffer overflows and bogus limits fixed in the BSOC. Others remain. :-( - Since time immemorial: When pulled from the address book into a message, the "To:" field was being restricted to 29 characters, regardless of packet type. Also, the length of names in the address book had been limited to 44 characters. - A fresh one: Reply packets were being stamped with a bogus date. - The packet list, if sorted by time, is no longer rearranged when a packet is Touched. - Replies are now preserved in the event of a crash during the write-out process. Inspired by Matt Munson. - The highlight bar wasn't showing up in XCurses -- probably since about version 0.18, which shows how often I try that port. (I also had some other problems with XCurses, as yet unsolved; it sometimes locked up on starting.) - Stupid bug in SOUP: segfault on message areas with 8-character filenames. - The line showing "BBS:" and "Sysop name:" in the area list is now omitted if neither is defined (as in SOUP packets), leaving an extra line for the areas themselves. - The minimum screen size is now 60x17. I'm shooting for 40 columns. ;-) ## 0.38 - 2001/02/03: ### New features: - First new version in over seven months! See, it's not dead after all. - Transparency: There are three new keywords in the .mmailrc which are mainly for making the background transparent, for use in programs like Eterm which can put an image in the background: "UseColors" (default: yes), "Transparency" (default: no), and "BackFill". "Transparency" only works in ncurses; "BackFill" is the only one available on all platforms. See the man page for more on these. - New date/timestamp handling: When updating packets with the .red markers, MultiMail now preserves the original timestamp of the file. This means that date sorting will reflect the order in which packets arrived, rather than the order in which they were last read. (Note, however, that this feature doesn't quite work correctly in the Win32 port (see README.win). One way around this would be to use PKZIP with the "-k" option; or just use the DOS port.) Inspired by Ingo Brueckl. At first I had this toggled by a keyword, but then... - The complement to the new timestamp handling is the 'T' function in the packet window, which "Touches" a file, setting its timestamp to the current time. I needed this because I actually depend on the timestamps being updated sometimes. - New "ExpertMode" option suppresses the help text, for those who are familiar with the commands and would rather use more of the screen for text. Four lines are gained in the packet, area and letter lists, among other gains. - In a related note, you now can gain seven lines in the packet list by undefining VANITY_PLATE in config.h, but this is strictly a compile-time option. :-) - OPX secretly restored. ;-) ### Bug fixes and such: - Removed several older keywords to simplify the .mmailrc: BuildPersArea (even on my XT, it didn't really take much longer), MakeOldFlags, UseScrollBars, and AutoSaveRead (all these are now always on). - The default editor for the Win32 port is now "start /w notepad". I'm not crazy about this, but under NT (in VMware), EDIT was completely locking up. Unfortunately MS provides no text-mode, native Win32 editor. (BTW, an interesting aside: Under NT, "notepad" is actually sufficient (the calling program waits for it to return); the "start /w" bit is for the benefit of 9x, which otherwise launches notepad and returns immediately.) - The Win32 port had trouble with the most recent Win32 version of PDCurses, because it now returns shift, alt and control immediately as keystrokes. (XCurses presumably had the same problem, but I didn't test this.) This is worked around now. - SOUP (and BW v4): Newsgroup lines which exceed the width of the display now end in "..." to indicate a continuation. (You can see the entire "Newsgroups:" line by pressing 'X' for full headers.) - SOUP (and BW v4?): Broken "References:" lines which contained no proper message ID's were causing segfaults. Reported by Peter Krefting. BTW, would the perpetrators of these broken messages please fix them? - SOUP: In 'm' mode, checking for "From "-line dividers is a little more rigorous; the line must now also contain an '@'. This eliminated all the false positives I was seeing on Pine-generated mail folders. - In packets where the list of subscribed areas is not known (e.g., all plain QWK and SOUP packets), the "Subscribed" mode in the area list is now skipped automatically. - The default color scheme is now a little less day-glo, :-) and shows up better on some terminals. The old one is still available in tradit.col. Let me know what you think. - Ncurses' built-in SIGWINCH handler is on by default in 5.0+; this was conflicting with MultiMail's handler. It should now work OK, but see NCURSES_SIGWINCH in interfac.h if you're not using ncurses' defaults. - When going backwards through messages via '-' and backing out of an area, the display was not updated correctly. I've also changed the behavior of '-' in this instance to go back to the last non-empty group instead of the last group (more consistent with '+'). ## 0.37 - 2000/06/28: ### New features: - Moved web site and email from ClarkNet to SourceForge. Among other benefits, this means that old versions of the MultiMail source code are now archived online, going back to 0.1. Binaries (for certain platforms) from 0.36 on will also be archived. - New compression type: tar/gz. (New .mmailrc keywords: "tarCompressCommand" and "tarUncompressCommand".) Unlike the other types, this recompresses the entire packet when updating the .red flags, so it can be a bit slow. Also, the supplied command lines assume GNU tar, which has gzip built-in. Separated gunzip/tar and tar/gzip command lines would require a (simple) external script. MultiMail only checks for the gzip signature. Requested by Peter Krefting for compatibility with GNUS. ### Bug fixes and such: - The packet list could crash on directory names longer than 32 characters. Reported by Tuukka Lehtinen. - Zero-length messages caused a segfault. Reported by T.L. - In SOUP packets with index files, the .idx file would be opened instead of the corresponding .msg file. Reported (indirectly) by Peter Krefting. - GNUS apparently uses a bogus identifier 'n' in the first position of the attribute fields of the AREA files in the SOUP packets it generates, equivalent to the standard type 'u'. MultiMail now recognizes this flag. Reported by P.K. - In MakeChain(), rejoin is now always applied to non-quoted lines. This looks better in most cases; in particular, where space padding on the end of a line resulted in a line of exactly 80 columns. (Previous versions of MultiMail would print an extra blank line then.) - relist() is now always called from MakeActive() in the area list and little area list. This means that the list is updated properly in Active mode, among other things. - In the packet list search function, the wrong packet was highlighted. - On some platforms (including the NetBSD m68k binary distributed on the web site), forced word-alignment of structs broke the "old-style" (.PDQ-based) offline config in Blue Wave mode. (Yes, I should've anticipated this after the similar bug with OPX mode in 0.32.) - The signature-adding routine will now skip CR characters, so (in principle) a .sig could be shared between Unix and DOSish readers. Other text files should still be kept separate. Inspired by Martin Prieto. - The backslashes that had briefly appeared in the DOSish versions' packet windows had reverted to forward slashes again in 0.36. :-) ## 0.36 - 2000/04/28: ### New features: - The area list has a new mode, "Active", to accompany the existing "All" and "Subscribed" views. The Active mode lists only those areas that contain messages. The L key now cycles among all three modes. (Note that in some cases, two modes may be equivalent; e.g., in plain QWK packets, there's no way to distinguish Active from Subscribed.) - There's also a new "Marked" view in the letter list. If any messages in the area are marked, the L key cycles between all, unread, and marked views. (Otherwise, it toggles all/unread, as before.) Marked messages are no longer automatically included in the unread view. - In the packet and letter lists, the current sort modes are now displayed. And in the area and letter lists, the current mode is displayed. The number of files is shown in the packet list, but only if the total number of items (files + directories) exceeds the window length. The area and letter lists now work similarly, showing the number of items only when they exceed the window length. - By popular demand, the default modes for the area list (all, subscribed, or active) and the letter list (all or unread) can now be set in .mmailrc. See the "AreaMode" and "LetterMode" keywords. - Old-style Blue Wave offline config, using .PDQ files, is now supported. (.UPI/.NET replies still aren't, yet.) Requested by Russell Tiedt. - Specifying a directory on the command line now opens the packet window on that directory. No more items are read from the command line afterwards. - In Unix, the "~" character is now recognized as a synonym for the contents of the "HOME" environment variable in input, and is also used that way in the packet list display. (However, "~username" is not supported.) - The Tab key works as an alternative to Enter in input windows. - The BCC port is now the "standard" Win32 port. (The RSX/NT port is still available, renamed mmrsxNNN.zip.) - New BeOS binary. ### Bug fixes and such: - Makefile.tcc and Makefile.bcc are now sensible -- no more renaming or moving kludges. :-) Also, the "UNUSED_PARAMS" nonsense is gone, and MultiMail compiles without warnings under BCC. - The message and ansi viewers can now handle > 64k in the 16-bit version, depending on available memory. - In myreaddir(), the DJGPP, Turbo and Borland ports (mmdos, mmxt, and mmwin) now use findfirst()/findnext() instead of readdir()/stat(). This means that the packet window opens significantly faster on large directories. (stat() is fine in Unix, but slow in MS-DOS, where it's implemented via findfirst().) Also, the list is now built in a single pass, instead of one pass for files and another for directories, resulting in a speedup on all platforms. - Test of read-only status now works in Borland/Turbo ports. - Grabbing of Fidonet addresses was broken in some situations, causing segfaults. Reported by Greg Paski. - When lines ended with spaces, they could be quoted incorrectly. Reported by Jim Hanoian. - Tab characters in messages could cause segfaults in some cases. - Soft CR stripping wasn't working. - Looking through my (very) old packets, I found some in which another reader (?) had added its own files to a packet with the names "a0000.ndx" and "a0001.ndx", which made MultiMail generate a bogus area 0 for those packets. Fixed. - The keypad mostly didn't work in the BCC (Win32) port of MultiMail. Reported by Jeff Foy. - Using the Goto function in the packet list now puts the highlight bar on the first file, as when a directory is selected by pressing Enter. In Lynx mode, going left (back) in the packet list now actually goes to the previous directory, instead of the parent directory, and the previous highlight position is restored. - When cycling through sort modes in the letter list, sort by "To:" is now skipped for areas which have no such field (i.e., Usenet areas). - Wrapped header lines in SOUP are now displayed in the correct color. - When writing out offline config (add/drop), the area list was scanned, but the current area number was not preserved. This sometimes led to minor cosmetic problems in the reply area. Reported by J.H. - Finally replaced the ugly #define list in mmcolor.h with an enum. - Some malformed Internet "From:" lines appear without a space between the name and address. In such cases, MultiMail was stripping the last letter of the real name. - Several new error messages. - The color files are now archived separately within the binary archives, to reduce clutter. Now if I can just figure out how to combine some of the documentation files in a sensible way... ## 0.35 - 2000/03/29: ### New features: - None. ### Bug fixes and such: - Oops! Due to the changes in the way LetterWindow::MakeChain() works, quote wrapping was broken. (Lines after wrapped lines would not be wrapped upwards, nor marked with quote marks.) A five-second fix, but alas, I didn't notice the bug until now. ## 0.34 - 2000/03/29: ### New features: - Two new ports officially supported: An alternate Win32 port, via Borland C++ 5.5 (available free at the Borland web site); and, more interestingly, a 16-bit MS-DOS port, via Turbo C++ 3.0 (an ancestor of Borland C++ 5.5). I released an experimental version of the latter a couple weeks ago, but the official 0.34 version is much better, mainly in reducing memory usage and in not (always) giving up when memory runs out. :-) - Areas that have replies are now always included in the short list, which prevents the replies being misdirected on re-edit. Such areas are also now flagged with an 'R' in the main area list. ### Bug fixes and such: - The "References:" line disappeared from SOUP replies if they were re-edited before posting. Reported by J.B. Moreno. - Massive reductions in memory usage for the LetterWindow and AnsiWindow classes. The plain message text takes up about half as much space as before; the ANSI viewer savings vary, but can be even greater. - The environmental variable "TEMP" now works as an alternative to "TMP" with the DJGPP port. If neither of those is defined, nor "DJGPP", it will now use the startup directory instead of "C:\". - TEMP or TMP values ending in a slash or backslash now work correctly in the Win32, OS/2 and 16-bit MS/DOS ports. Also, all these ports now use the startup directory rather than the MMAIL directory as the default location for the temporary directories and files, in the absence of a TEMP or TMP environment variable. - SOUP replies sometimes had the wrong length in the length field (one byte too long), resulting in spurious characters at the ends of messages. - Skip trying to add to read-only files. (But this check doesn't work yet in the Borland/Turbo ports.) - References to "interface" in the code changed to "ui". (Apparently, "interface" is a reserved word in Borland C++.) - Soft CR stripping moved to the Blue Wave class. Let me know if this is a problem. (I haven't seen them in QWK or OMEN packets, and they'll never appear in SOUP.) - Various code cleanup. Now 16-bit clean, among other things. :-) ## 0.33 - 2000/02/21: ### New features: - Support for encoding and decoding Quoted-Printable headers and bodies, primarily in SOUP packets. Decoding is always performed; encoding is performed subject to the new .mmailrc keywords "outCharSet", "UseQPHead", "UseQPMailHead", "UseQPNewsHead", "UseQPMail" and "UseQPNews". Thanks to Peter Krefting for some of the code. - Support for the OPX format has been removed. I hope this will be only temporary. The reason is not technical. If you're affected by this, and you'd like to continue using OPX, please email me privately. - Support for individual packet types may now be disabled when compiling, in order to save a little space in the executable. The packet modules to include are selected in the "modules" file. - The Rename function in the packet list now brings up a Blue Wave-style default filename, with a numeric extension based on the last one found plus one, unless the packet already has a numeric extension. Also, rename failures are reported, and you can no longer rename to the name of an existing file. - Some extra pop-up notices like "Opening...", mainly for the benefit of slower systems. :-) There are also additional non-fatal error messages in several places, as when a Save fails. - .XTI support is now fully segregated into the Blue Wave module. This is cleaner, but it means that .XTI files are only recognized in Blue Wave packets, and not in other types. The default for the "MakeOldFlags" option in .mmailrc has been changed to "Yes". - The automatic prefixing of "Re: " on the subjects of replies can now be disabled, for non-Internet areas only, via the "ReOnReplies:" .mmailrc keyword. By popular demand. :-) - New color scheme contributed by Tuukka Lehtinen. - Before the removal of the OPX module, these features were added: - In OPX mode, MAIL.FDX is now handled in the same way as .XTI in Blue Wave mode. It's written instead of .red when "MakeOldFlags" is set. - Offline config in OPX mode. Thanks to Armando Ramos for the format. ### Bug fixes and such: - "To:" and "From:" kludge lines in text are now checked only in QWKE packets, instead of all QWK packets. This should help with some systems that require "To:" lines for Internet addressing. - Blue Wave anonymous areas are now recognized as alias areas, as in QWKE. - Areas flagged as read-only will no longer accept replies, and will pop up an error box instead. This works in Blue Wave, QWKE, and OMEN. Requested by Jaakko Lintula. - The MS-DOS version sometimes segfaulted due to bugs in DJGPP's qsort(). Fixed by upgrading to a newer DJGPP library. Reported by Laird Kelly. - When reading the .mmailrc file, unrecognized keywords are now reported. May help to catch typos. :-) - In OMEN mode, when the UserName was not set in .mmailrc, all messages were being flagged as personal. - Defaults for "AutoSaveReplies" and "AutoSaveRead" changed to "Yes". - Compiling on an Alpha revealed several bugs that didn't show up on systems where pointers and ints were the same size (i.e., all the 32-bit systems); mainly inappropriate usage of resource::get() vs. ::getInt(). MultiMail is now 64-bit ready. :-) Thanks to Compaq for the free account. - In Blue Wave mode, the echotag field is now used as the area description if the description field was left blank. Problem reported by Greg Mayman. - Internet address parsing modified to deal with badly-formed addresses like "(Name)
". Problem reported by Dane Beko. - There were intermittent segfaults in the letter window with screens wider than 80 columns, due to an off-by-one error in memory allocation. - The following fixes were made to the OPX module prior to its removal: - Some versions of the Silver Xpress door eat the last character of the tear line. MultiMail now pads it out with a sacrificial space in OPX mode. Reported by Jim Hanoian. - Some OPX packets split their areas into BRDINFO.DAT and EXTAREAS.DAT; MultiMail should now be able to handle this (untested). Reported by Armando Ramos. - On some platforms (e.g., the NetBSD version distributed on my site), OPX support was not working due to structure alignment padding. Fixed. - New line ending recognized: plain CR. (This is in addition to the LF, CRLF, and 0x8D endings recognized in 0.32.) If you got messages with blank bodies, or immediate segfaults when opening messages, this will probably fix it. Reported by Laird Kelly. - Certain packets (with messages over 64k?) reportedly had incorrect values in the length fields of some messages, causing everything after that point to become unreadable. This is fixable by using MAIL.FDX to index the messages, which MultiMail now does. Reported by Armando Ramos. ## 0.32 - 1999/10/30: ### New features: - Support for the OPX packet format. This was reverse-engineered, and has really only been tested with the WINS door. See mmail/opxstrct.h in the source code archive for the specs. - A "Personal" area (messages addressed to you) is now created automatically for all packet types except SOUP. This can be turned on or off (default is on) via the "BuildPersArea" option in the .mmailrc -- except in QWK packets with .NDX files, where the presence or absence of a "personal.ndx" file determines whether the area is generated (the same as in older versions of MultiMail). - With Blue Wave packets (and _only_ with Blue Wave packets), MultiMail now has the option (off by default) of storing its last-read pointers in an ".xti" file, as used by the Blue Wave reader and some compatible programs (like BWSave), instead of in MultiMail's native ".red" format. This is toggled by the "MakeOldFlags" option in the .mmailrc. (I made the name generic in anticipation of support for other such formats, like OPX's "mail.fdx".) In reading, .red no longer takes precedence over .xti; if both files are present, the one with the later date is used. ### Bug fixes and such: - Character set translation now works correctly on Linux text consoles that use Unicode-mapped fonts. Problem reported by Peter Krefting. Also, OMEN replies to Latin-1 packets were not recognized as being Latin-1 on rereading; initial printing of translated fields in the header editor was incorrect; and the ANSI viewer always assumed CP437. - Forwarded messages now add only the changed header lines to the body of the text, instead of using the same format as saved messages. - Date in SOUP replies was being localized, which is undesirable. Reported by P.K. - The Personal area now always shows the correct number of unread messages in the area list. - Blank lines are now added automatically before signatures, and the sigdash ("-- ") in Internet and Usenet areas is correctly followed by a newline before a sig. (Previously, it worked right with taglines, but not sigs.) - Areas with a very long description could cause a segfault when opening the letter list. (They were truncated, but not quite enough since the addition of the message count.) Also, the header of the letter list now takes advantage of the full width of larger screens. - Can now read net-status QWK packets correctly, even without .NDX files. (End users shouldn't really be reading net-status QWK packets; but judging by a packet I was sent, some are.) - Empty subject lines are now forced to the end of the letter list when sorting by subject, instead of messing up the sorting as they were doing. - Precautionary fix for hypothetical corrupted Y2K date fields in Blue Wave packets. Hopefully not needed. :-) - The User-Agent line in SOUP mode now includes "SOUP" in the comment. :-) ## 0.31 - 1999/10/07: ### New features: - When a new version is run for the first time, instead of prompting whether to continue or quit, it prompts whether to edit the .mmailrc file or just continue. Should save a step. ;-) ### Bug fixes and such: - Text files (e.g., MMAIL.RC, or the DOOR.ID or CONTROL.DAT from a QWK packet) which did not end in a line feed character would cause MultiMail to lock up. This was due to a new line-reading function introduced in version 0.30, which fixed some other problems, but stupidly added this one. Due to the seriousness of this bug, I'm releasing 0.31 early. Reported by David Pratt et al. - Searching did not work in the little area list. (This dates to 0.29, though I just noticed it.) - In SOUP packets, extra spaces between header keywords and values are now ignored. ## 0.30 - 1999/09/30: ### New features: - Support for SOUP packets. Note that type 'M' areas and index files are not supported yet; only 'm', 'b' and 'B'. - Support for OMEN packets. - The new keywords "UserName" and "InetAddr" are used to build a default "From:" line for SOUP replies, of the form "UserName ". If you leave them blank, no "From:" line will be generated. (Conforming SOUP packers, like UQWK, will generate their own "From:" lines in that case.) The UserName is also used in OMEN mode for display purposes (the actual From name is set on upload), and for matching personal messages. - Replies in Usenet areas may now be cross-posted; the newsgroups list can be edited. - Quote headers can now be set in the config file (separately for Internet/ Usenet areas, and others), via the "QuoteHead" and "InetQuote" keywords. The maximum width for quoted material can also be set, via "QuoteWrapCols" (suggested by Holger Granholm). See the man page for details. - The "Lines:" indicator now includes a percentage. ### Bug fixes and such: - Long lines in text files would sometimes cause the remainder of the file to be read incorrectly. Mainly, this could show up with the tagline file. Reported by Dane Beko. Also, blank lines in the tagline file are now ignored. - Area 0, if present, was not being sorted in the letter list. - In the MS-DOS version, the first shell-out would trash the old prompt, so second and subsequent shells had a bogus prompt. Reported by Jerel Arbaugh. - When re-editing a reply and shortening it, the remaining text could end up off the screen when returning from the editor. Reported by Jim Hanoian. Also, a similar effect could be briefly seen when editing a reply from the original, if the reply was shorter. - Text entry fields did not work correctly when backspacing if the text filled the field. Reported by Tom Rutherford. - Changed default zip commands -- removed "-o" (for both PKZIP and InfoZip) and added "-k" for InfoZip. The latter option forces stored pathnames to an MS-DOS compatible 8.3 unicase format, which ensures maximal compatibility with doors and such. You may want to add this yourself if you're upgrading; but note that it could result in two ".red" files being stored into packets that had been read without this option. - Saved messages can now have longer header lines, and the headers are more appropriate (closer to what you see in the letter window) for Internet/ Usenet areas. - Signatures and/or tearlines in Internet/Usenet replies are now prefaced with "-- ", and "... " is omitted. Signatures are recognized in text in these areas, are colored with the tagline color (I'll make this a separate color later), and are not quoted when replying. - Long subject lines and other long lines can now be fully edited, regardless of screen width, and will be fully displayed if the screen is wide enough. - In Internet/Usenet areas in Blue Wave packets, the subject, date and newsgroups are now extracted from the kludge lines in the text, as with references and message-id. (Most of the changes in this version of MultiMail are for enhanced Internet/Usenet support; although this was primarily for the benefit of SOUP, it carries over into Blue Wave.) - TAB characters are now decoded into spaces in MakeChain(). This prevents them from pushing lines off the screen. But to save a message with its TAB characters intact, you have to save it from the ANSI viewer. - Strip any number of leading "Re:"'s, not just the first. - Filenames within reply packets are now created in upper case, even if you don't use the "-k" option (see above). Mainly this was for compatibility with the MBSE BBS, though there may be others with the same problem. (The format specs generally show the filenames in upper case, so this is arguably the correct behavior for a case-sensitive filesystem; but most were designed with the FAT filesystem in mind, and only SOUP explicitly addresses the issue of case.) - "Re: " will not be prepended if it would make the subject exceed the maximum width for the format. Also, non-QWKE QWK packets now have their subjects limited to 25 characters (again); the "Subject:" kludge will not be added except in QWKE packets. (I'll add it back for some later, when I can identify the particular door's capabilities better.) - ':' and '|' are now recognized as quote indicators, but only when appearing in the first column. (But if ':' is the start of a smiley, it's ignored.) - When quoting in Internet/Usenet areas, blank lines now have a quote marker prepended, as this seems to be the standard. - Intitial check for 80x20 instead of 60x20, error if screen too small. - I made a (temporary) patch to PDCurses for the MS-DOS version, so that MultiMail will work correctly in non-standard video modes. Problem reported by J.A. - Overdue documentation of the .mmailrc file in the man page. ## 0.29 - 1999/08/29: ### New features: - List windows now have a "scroll bar" when the number of items in the list exceeds the number that will fit onscreen. This feature is selectable via the "UseScrollBars" keyword in .mmailrc (on by default). Note that it's not (yet) mouseable. - The new "UseLynxNav" option (off by default) changes the function of the left and right cursor keys: left backs out, and right selects. (It's named after the Lynx web browser. The idea comes from Pine, which has a similar option.) It also makes the left arrow equivalent to selecting ".." in the packet list, except on the top level (where it's equivalent to Quit). - Fido "SEEN-BY:" lines are now marked as hidden if they weren't already. (This allows you to turn on the "extended info" option in Blue Wave doors below ver. 4 with impunity.) - The Delete key will work as an alias for 'K' in all cases. ### Bug fixes and such: - Major bug: In certain cases, the little area list would not set the correct area, and the reply would be misdirected. Reported by Jerel D. Arbaugh. - Outside of the packet/area/letter list, the search routine could abort in some cases (i.e., '/' would simply not work). Reported by Jim Hanoian. - If a message was saved (from the letter list) before any messages had been viewed, it could be saved rot13'd! - The "soft CR" filter was not working if the console character set was Latin-1. - Re-editing of replies from the original area could move the letter list selection inappropriately (to the corresponding reply number). - If the last-opened packet was deleted and replaced by another packet of the same name, the old packet would be "reopened" even though it was gone. Reported by Raymond Cool. - Lines which contained a '^A' character, but _not_ at the beginning of the line, were erroneously being marked as hidden. - "Re:" is now left visible in the letter window, though still stripped from the letter list. (In this, I emulate Tin.) - During a search, the beep-on-personal would beep on each personal message scanned, even when the corresponding message was not actually displayed. - When shelling out, the prompt string could be truncated if it was excessively long. Not really a bug -- I just hadn't believed that anyone would actually have a prompt string that long. :-) Reported by J.D.A. - Due to its placement at the start of a line, an instance of the word ".mmailrc" was interpreted as a control code and disappeared from the formatted man page. Reported by Geoffrey Wilson. ## 0.28 - 1999/07/28: ### New features: - Offline config! (Add/Drop only, at the moment.) For Blue Wave, QWKE, and QWK (with DOOR.ID indicating Add/Drop support). See the man page for details. - The area list window now has additional status flags: '+' and '-' for added/dropped, and '*' in the full list for subscribed areas. The little area list has '*' also, and is now wider. - A more advanced search routine. It will now (optionally) descend levels, allowing even a full text search over multiple packets. - The address book now allows direct entry, as well as editing of existing addresses. The tagline window allows editing of existing taglines. (The old function of the 'E' key in the tagline window has been moved to the 'A' key.) - Reply splitting, both manual and (optionally) automatic. - Quick exit from any screen via ^X. - Command shell (for DOSish ports) via ^Z. - Packets can now be specified directly on the command line, bypassing the packet menu. (If multiple packets are listed, they will be opened in sequence.) - Better quote wrapping -- paragraphs are rewrapped, and second-level quotes are not requoted (except in Internet/Usenet areas, which use a reduced quote for second-level quotes). - Optional stripping of "soft carriage returns" (those annoying accented i's that sometimes appear). - Optional beep in letter window when opening personal messages. ### Bug fixes and such: - The "Yes/No" prompt to keep old reply packets has been replaced by "Save/Kill", to reduce the chances of deleting a packet by mistake (as can happen when one is in the habit of hitting 'N' in response to the new files or bulletins prompts). Suggested by several users. - In all string input windows (those that use ShadowedWin::getstring()), a backspace as the first character will now preserve the default text (if any). I'm not sure if it's better this way or not, though. - The tagline window was displayed improperly on a screen wider than 80 columns when scrolled up or down a line at a time. - I've changed the message MultiMail shows the first time a new version is run, to something I hope is clearer. Please note that MultiMail will never wipe out the values from your old config file, only update the file with new keywords (if any). - When changing to a new directory (or on startup), the first file rather than the first directory is highlighted. - When composing a new message and not quoting, MultiMail no longer creates a 0-byte temp file before launching the editor. (You probably won't notice the difference, but I think it may address one bug report I got a while back. For me, it makes pico say "New file" instead of "Read 0 line".) - Some more egcs warnings fixed. (Odd; they should have appeared earlier.) - Internal reorganization: Most non-ANSI, non-curses code is now in mysystem.cc. I hope this may simplify porting to non-POSIX systems. Also, part of interfac.cc was split off into basic.cc; and some stupid #define and const int lists were replaced with enums. ## 0.27 - 1999/06/27: ### New features: - The little area list now has short-list capability, like the main area list. Press 'L' to toggle it. ### Bug fixes and such: - In QWK reply packets, the binary conference number was not being included. This made them incompatible with some doors (at least some versions of Galacticomm's software, and probably EzyBBS). The problem goes back to MultiMail's beginnings. I didn't notice it before now because most QWK doors use the ASCII conference number instead. (There are redundant fields in a REP packet.) Thanks to SparkAm@peyam.net for tracing the problem. - The precompiled MS-DOS binary of 0.26 wouldn't work on machines without FPUs, due to the FPU code linked in with the difftime() function in the packet list. I've replaced this with an integral subtraction, which is adequate for my purposes. Reported by Greg Mayman et al. - curs_set() is now called from Win::cursor_on() and Win::cursor_off(), regardless of the curses implementation. (Formerly, these lines were ifdef'ed.) This is for compatibility with ncurses 5.0-beta1, which no longer calls it from leaveok(). I'm told that Solaris curses doesn't, either. - A netmail address on a message would be retained if the message were forwarded to a non-netmail area, and it would not be editable. Reported by Jim Hanoian. - The packet list window length is now variable for lists shorter than the max, as with the letter list. The "info" window in the area list has been merged into the main window. (The visible effect of this is that the lower window no longer has a title.) - Some internal reorganization. ## 0.26 - 1999/05/26: ### New features: - Automatic saving of replies is now available via the "AutoSaveReplies" keyword in .mmailrc. When this is enabled, MultiMail automatically rewrites the reply packet after a reply is created, edited or killed. It's similar to pressing "F2" or "!" after each event, but without the warning window. I'm considering making this the default behavior for future versions -- any comments? - The "AutoSaveRead" keyword turns on automatic saving of the last read markers when exiting a packet, bypassing the "Save lastread" prompt. (This is another thing I might make the default.) - Packets may now be renamed from the packet list window by pressing 'R'. (This function can also be used to move them to different directories, though you must specify the filename as well as the pathname of the destination.) - Directories are now shown in the packet list, and are navigable; or you can specify a directory to jump to via the 'G' command. (On the DOSish ports, the 'G' command is the only way to change drive letters.) The current directory appears as the title of the packet list window. Note that this change means that the old behavior of exiting when no packets were found will effectively never occur. - By popular request, multiple replies to the same message (in the same reply packet) are now allowed. (This is a partial reversal of the change in 0.24.) When re-replying, you're prompted to re-edit; if you say no, a new reply is generated. Only the first of the existing replies will be recognized for re-editing. - When opening a packet, you're now prompted whether or not to keep any existing replies. - The tagline window can now be completely disabled via the "UseTaglines" keyword in .mmailrc. Requested by Jack Pfisterer. ### Bug fixes and such: - Re-editing replies from the reply area no longer forces a return to the letter list. Instead, the re-edited letter is displayed. - Cosmetic problems with the wrong area being displayed on the status line (and the header window) during the reply process have been cleared up. - The "This will overwrite any existing reply packet" warning from the F2 function is now suppressed if there is no packet. - MultiMail will now display a warning if the reply packet is not created, or if the last read markers could not be saved. Also, it will pause for two seconds after getting an error code from a program it shelled to (archiver or editor) or attempted to shell to, so that any console error messages from the system may (hopefully) be read. - Speculative fix for reported problems with replies disappearing (which I couldn't reproduce): calls to clearDirectory() now pass the name of the working directory explicitly, rather than ".". - Added "-e" to the default add command for ARJ to suppress pathname inclusion. Suggested by Ken Whiton. - The separate colors for the Search window have been eliminated, as the Save dialog is now used for both (as well as for the new prompts in the packet list). - The "F2, !: Make reply packet" function is now available in the letter list and letter window, as well as the area window. - Shorter date format in the packet list; more space for long filenames. - On Blue Wave messages with no taglines, a blank line is now inserted before the origin line. - Fixed errors (and some warnings besides) that made MultiMail 0.25 incompatible with egcs. Thanks to Tony Summerfelt for reporting the problems and testing (and re-testing) the solutions. - If there was a tagline in the message on screen, and you chose not to use a tagline in the reply, the tagline from the original would be used instead. Reported by David Toutant and J.F. (With the reorganized tagline code, I've also reverted to having the tagline grabber pick the last tagline in a message, instead of the first.) - In Blue Wave mode, when the packet filename differed from the "base name" (BBS id), the former was used for replies, when it should be the latter. Reported by Jim Hanoian. - After animating ANSI, MultiMail would loop on a non-blocking keyboard check, sucking up CPU. - As usual, lots of internal reorganization. ## 0.25 - 1999/03/19: ### New features: - Support for Internet and Usenet messages in Blue Wave mode. (Note: AFAIK, this is only supported by the Blue Wave version 4 door for PCBoard.) Also, Internet addresses can now be taken into the address book. - Character sets (either code page 437 or Latin-1) are now selected for each area or message. The default character set is CP437 (as before), except for Internet or Usenet areas (as marked by Blue Wave or QWKE flags), which are Latin-1. The character set in individual Fidonet messages can be overridden by the "CHRS:" or "CHARSET:" kludges. Replies are set according to the default for the destination area. - The console character set and the default sort types for the packet and letter lists can now be set in the config file. The default packet sort is now by time (latest first) instead of by name. - The "To" field is now omitted from the letter list in Usenet areas and the QWK Personal area. The "From" field is omitted in the REPLY area. This leaves more room for the "Subject" lines by eliminating redundant info. - Direct support for RAR archives. Note: there's a problem with this if you read the same packets in, e.g., both Linux and DOS: RAR will save the last read marker file twice (once in upper case, once in lower). - More ANSI navigation: The ENTER key now works the same in the ANSI viewer as in the letter window (advance to next message). - The packet list can now be rescanned (to pick up new packets) by pressing 'U'. - The message count now appears in the title bar of the letter list (like the area count in the area list). - New color scheme by Gary Gilmore. ### Bug fixes and such: - The alternate QWK private message flag, '+', was not being recognized. Reported by Tom Rutherford. - Changed the message that prompts to save replies to something I hope is clearer. Suggested by Max Chamberlain. - If the reply packet had a different archive type than the main packet, MultiMail would attempt to use *that* archiver (the reply packet's) to add the last-read markers to the main packet. The correct behavior is to use the main packet's archive type when remaking the reply packet! - When quoted material was wrapped, the wrapped lines would have trailing spaces. These are now stripped. - In the area and tagline lists, a completely blank line would sometimes appear at the top of the list. (A long-standing bug, finally fixed.) - Strip blank lines after hidden lines. (Blue Wave puts blanks after the hidden headers in Usenet messages.) - Fewer keys to hit in the header editor. :-) The cursor is now placed on either the Subject or To line, as appropriate (you can still arrow up to edit the other lines); and the "Make message private?" prompt is not presented when sending to areas that are either all-public or all-private (as indicated by Blue Wave or QWKE flags). - Bizarre alignment bug in the EMX-based ports (OS/2 and Win32) caused crashes on some QWK packets. Reported by Jim Hanoian. - Source code reorganized somewhat... no more .a files. - Blue Wave packets could have identical areanums in multiple areas. Although this is an error, the Blue Wave reader handled it; now MultiMail does as well. This is also a little faster. However, it assumes that the mixRecords and INF area headers are sorted the same way, which I would've preferred not to assume. Problem reported by Scott Jones. - In 0.24, the new packet kill routine failed to exit after killing the last packet, leaving garbage on screen. Reported by Gary Gilmore. - A filename without an extension would mess up the display in the packet list. A long-standing bug that I never really cared about, but which J.H. mentioned. - All directories are now omitted from the packet list. - One more attempted cursor fix for the Win32 version. - Minor display bug with messages 100,000 lines or longer. (Now OK up to 1,000,000.) - In the EMX ports, in the letter sort by subject, the secondary sort by message number was still broken. Reported by J.H. ## 0.24 - 1999/02/12: ### New features: - "Save" function in ANSI mode. This differs from the letter window save in that no wrapping is performed, and no header is prepended. This is also the only way to save bulletins and new file lists. - New navigation in ANSI mode: the space bar and left and right arrow keys now work in the same way as in the letter window. This also means the interface for the bulletin viewer is slightly different: 'Q' exits from the entire list, instead of just the current bulletin, and you can move back and forth between bulletins as in the letter window. - Multiple sort modes in the packet list: by date (latest first), and by name. By name is still the default. "$" toggles between them. - Multiple sort modes in the letter list: by subject (still the default), message number (i.e., no sort), from, or to. "$" cycles through them. - The help window at the bottom of the packet, area and letter lists can now show multiple pages of options (similar to Pine's "Other commands" menu function). - When character set translation is on in the ANSI viewer, some IBM characters are now mapped to curses equivalents, giving a better result on non-PC terminals (e.g., xterm, vt100). This works best with the box- drawing characters. The letter window still uses a plain ASCII mapping for these characters. - Pop-up help is now available in the ANSI viewer, as in the letter window. - Messages can be forwarded from any area, with the original headers preserved in the text. - Support for the Win32 console via RSX/NT. See README.rsx if you want to compile for this platform. - Fidonet MSGID/REPLY kludge lines are now supported, in Blue Wave mode. - Additional QWKE support -- in QWKE packets, the short list now shows "subscribed-to" conferences, as in Blue Wave mode, instead of just the non-empty ones; and alias areas are supported. The "QWKE" type appears in the Info window of the area list. - When Replying to a message that's already marked "Replied", the previous reply (if available) will be re-edited instead of a new reply being created. - Some new color schemes. ### Bug fixes and such: - The help window now survives a screen resize, instead of being closed. - Killing a packet no longer forces a reread of the directory. - The addressbook will no longer grab a new entry when no Fido address is available. Also, the addresses are now sorted alphabetically on startup. (Anything added during the session will not be alphabetized; however, the list will be sorted again on the next startup.) - The full length of Blue Wave "To" and "From" fields is now available in the header editor. (Previously, they were restricted to 25 characters.) - The subject sort in the letter list was not correctly performing the secondary sort, by message number, in runs of the same subject. - Time/date printout code changed in packet list and in Blue Wave reply headers. I think this looks a bit better. - The experimental Win32 console version had a problem with slow screen output. This seems to be a problem with the console API (?), but it's worked around in this version. (Normally, PDCurses checks for a keypress after printing each line. In Win32, this call takes a noticeable amount of time to return, even though it's supposed to be non-blocking. So, in MultiMail, the check is disabled for the Win32 version. This also disables typeahead, but I doubt anyone will even notice that.) - A last attempt at fixing the remaining cursor problems in the PDCurses versions. (The cursor size is now checked at startup, and that value is explictly restored instead of using curs_set(1).) Scott Jones reported the persiting problem in the OS/2 version. - The index of the Personal area is now updated just by entering the letter list, or anytime a message is read in that area. Better than previous versions, though still not ideal. - Support for the keypad plus, minus, and slash keys under PDCurses. (This completes the keypad "Enter" fix in the previous version.) - The experimental Win32 version supported only "\" in the pathnames specified in MMAIL.RC, where the DOS and OS/2 versions allowed either "\" or "/". Now both work in Win32, and both will be converted to "\" when MMAIL.RC is updated. Reported by Rafael Cresci. - One more character allowed for unames in Blue Wave reply packets (six instead of five). Specifically, that means "MultiMail/MS-DOS" and "MultiMail/NetBSD" will now appear in Blue Wave tearlines, instead of the abbreviated form "MultiMail". - Hidden lines at the end of a message would cause the line count to be over by one. Reported by Jim Hanoian. - In the Save dialog, user-entered names were getting spaces converted to underscores, though only the second time through (when the name was pulled up as the default). Reported by J.H. ## 0.23 - 1999/01/22: ### New features: - QWKE and PCBoard-style "Subject:", "To:", and "From:" line kludges are now supported in QWK mode, along with "Title:", which is used on WWIV. The QWKE "Subject:" line is generated automatically for replies, if needed. Note that in regular messages (though not in replies), these lines aren't parsed until the message body is accessed by entering the letter window, so the fields as shown in the letter list will at first be the short forms. - Reply forwarding -- press \-'F' while in the reply area (either the letter list or letter window) to forward a reply. (Forwarding of regular messages is not available yet.) - In the REPLY area, 'R' now works as an alias for 'E'. Added at the insistence of Jim Hanoian. :-) - When (re-)editing or forwarding a reply, the area can now be changed. - REPLY and PERSONAL areas now show the original area (as well as the collection area) in the letter window. Suggested by J.H. ### Bug fixes and such: - New behavior in the ANSI viewer: Instead of resetting everything when a screen-clearing code ([2J) is encountered, it sets the last-drawn line as the new baseline, and continues from there. With the old system, information was sometimes lost (though it could still be viewed in the animation mode); now, screens are simply chained together, and everything is visible and scrollable. - Characters 8 (backspace) and 12 (form feed) are now handled in the ANSI viewer, and skipped in the letter window. Ctrl-Z is stripped in the ANSI viewer. - The BBS name and Sysop name fields in the info window of the area list were being unnecessarily truncated. - In the PDCurses versions, the cursor remained disabled when shelling to an editor. Reported by Dane Beko. (Due to the way I implemented this fix, PDCurses 2.2 is no longer acceptable for MultiMail; you must use 2.3.) - When creating a .REP, a zero-length message body would cause a segfault. - Blank space at the end of the little area list is now omitted (as in the letter list). - An attempt to send Netmail when no Netmail area is defined (as is always the case in QWK mode) now pops up a nonFatalError window. - The "Save lastread pointers?" message now comes up less often; the "any read" flag is set less aggressively. (If you re-read a message that's already marked as read, the any-read flag will not be set, as it was in previous versions.) - When entering areas which are fully read, the highlight bar is no longer moved to the last message. Similarly, if the entire packet is marked as read, the active area is not set to the last non-empty area, but the first. Suggested by J.H. - Hopefully, a better subject-line sort in the letter list: If two subjects have the same content for the entire length of the shorter of the two, the shorter one is no longer automatically placed first; instead, they're put in message number order. - The ENTER key on the numeric keypad did not work in the PDCurses versions. Reported by Russell Tiedt. - Added "/m" to the parameters passed to LHA in the MSDOS version. Without this, it gave an "Extension is not .lha. Continue? [Y/N]" prompt when archiving replies. ## 0.22 - 1999/01/01: ### New features: - The long-awaited support for new file lists and bulletins! They're displayed automatically on opening the packet. Currently, this is a bit of a kludge -- although I'm starting to like it. Memory usage is excessive. (See the man page for more info.) - Support for XCurses (the X port of PDCurses). See README.xc for details. - Added a line counter to the ANSI window, as in the letter window. ### Bug fixes and such: - If a tagline was displayed on screen, it would replace the tagline selected for a reply via the tagline editor. This bug dates to 0.20. - TAB characters that advanced past the end of a line were causing problems in the ANSI viewer. - Aborting a netmail reply left the netmail address set when a regular reply was attempted afterwards. - The translation toggle ('c') now works in all screens. (In particular, the ANSI viewer.) - Proper printing of character 127 (DEL) under ncurses. - The SIGWINCH (resize) handler now works acceptably even when a SIGWINCH occurs while in a WarningWindow() or getstring() call. ## 0.21 - 1998/12/15: ### New features: - Not new, but newly documented: When run under Windows 95, the MSDOS version supports long filenames! Apparently this is a feature of the DJGPP standard library, though I didn't realize it before. (I don't know about Win 98 or NT -- anybody else?) ### Bug fixes and such: - The code to generate the default tagline file was broken in 0.20, which meant that all first-time users got a segfault. Argh! Also, since I was messing with the taglines anyway, I changed the default list. - In the PDCurses versions, the "Could not uncompress packet" and "Packet type not recognized" messages would pop up without first restoring the packet list screen. Also, some extra parentheses have been added around color names for the benefit of PDCurses/Win32 (still an incomplete port). - Updated the README.{DOS,OS2} files to reflect the new style of default MMAIL.RC in use since 0.19. - "/" and "." added to pop-up help window. ## 0.20 - 1998/12/06: ### New features: - Search function. Case-insensitve searching is available everywhere. It's still a bit limited, in that it only works within the current list (or letter); i.e., you can't yet search multiple letters at once. Hit '/' to bring up the text entry window and start the search, or '.' to repeat the last search. (Yeah, I know -- it needs better keys.) Searching is from the current line on down. - Fido "hidden" message lines (marked with a ^A) can be displayed; 'X' toggles this function. - New features in the tagline editor and address book: New entries in the address book are checked for dupes; also, tagline dupes, which were already checked, are now reported. The address book and the tagline editor allow you to Kill entries. And the tagline editor can be brought up directly, via ^T, in the same way as the address book. Old features: The reLoad function has been removed from the tagline editor. - Rot13 function in letter window -- toggle with 'D'. - If a new letter is not edited (as measured by the time stamp), you'll be asked if you want to cancel it on returning to MultiMail. Partly inspired by Gregory Paksi. - Redundant colors can be omitted from the ColorFile. See colors/README.col for details. Also, there are some new colors, and some old ones are gone; be sure to check your ColorFile if you've customized it. - Messages to or from you are highlighted in a different color in the letter list. After Ingo Brueckl (though this implementation is a little different from his). - Read-only support for Blue Wave's .XTI files (last-read markers). This means that any old packets you read with Blue Wave will be marked in the same way when first opened in MultiMail; however, any changes made in MultiMail will not be seen by Blue Wave. Partly inspired by I.B., but this is a different (and more correct) implementation. - "Home" and "End" keys are now available in text entry fields (i.e., ShadowedWin::getstring()). After I.B. ### Bug fixes and such: - The FMPT kludge line is now supported, which means that in Blue Wave mode, the netmail addresses of points will be recognized. - Aborting a header edit from the letter list could cause a segfault. (The fix in 0.19 was incomplete.) - Some bogus ANSI codes (mainly, "ESC[?7h") are now semi-interpreted, so they no longer clutter the screen. Also, the ANSI background color is now hardwired to white on black, instead of being set by Main_Back. - The highlight method for the active line was no good on a light background with ncurses 4.2 (was ok with 4.1). Some other changes for light backgrounds, too. - High-bit, "low-bit" (below 32) and certain special characters are now handled better by MultiMail, being actually printed instead of interpreted as control codes. :-) Particularly useful for ANSI. - TAB characters are now rendered as the appropriate number of spaces in the letter and ANSI windows. - In text entry fields (getstring()), high-bit characters were showing up with weird attributes (due to a sign error). Reported (indirectly) by I.B., this bug has been present since about 0.10! I'm suprised no one from Fido zone 2 mentioned it to me before. - Packets can now be renamed without MultiMail losing track of its last read markers. (This will, however, still result in a second .red file being created.) - When entering a tagline manually via "E" in the tagline editor, the entry field is now placed correctly, instead of appearing to overwrite the last tagline. Also, bogus taglines are no longer generated from message lines which are just "...". - In the Reply area, the help menu for the letter list was showing a 'K' where it should've been an 'S'. Also, capitalization in help menus has been standardized (only the keys that activate commands are in caps in the descriptive text), and the pop-up letter help window is now in two columns. - Killing an item (in any menu) now moves down to the next entry, regardless of the position in the list (unless at the end). - The 'L' command now remains available in the address book after a screen resize (SIGWINCH). Also, taking an address via 'L' automatically sets the active line to the last position (where the new entry is). - Netmail addresses are now included in Saved letters. - The resize (SIGWINCH) handler was causing a segfault in some circumstances. Although I just noticed it, this bug dates to 0.18. Curiously, it occurred for me only when running under "screen". The fix was to change Win::inkey() to remove the loop. - Changed the .mmailrc separators from " = " back to ": ", which makes them compatible with 0.18 and earlier. I'll probably change them again. (I was trying to achieve a more "classic" style of .rc file, but in fact, it still wasn't quite right.) - The cursor was not being disabled in PDCurses versions. - "Read" indicator in the letter window was not updated after Saving. - Changed interface/Makefile and mmail/Makefile to allow "make clean" to work in DOS. Suggested by I.B. - And of course, various internal changes. ## 0.19 - 1998/10/25: ### New features: - The .mmailrc has been redesigned. New versions of MultiMail will update it automatically; comments will be lost, but old data will be preseved. Note: the new form is not readable by 0.18 or earlier, because of the use of '=' as a separator. Partly inspired by Ingo Brueckl. - Non-fatal errors. Currently, this is used only for the previously fatal "Could not uncompress packet" and "Packet type not recognized". - You can change the colors MultiMail uses without recompiling. See the automatically generated "~/colors" for details; also, some sample color schemes are included in the 0.19 archive. This feature is due largely to Ingo Brueckl. - "Marked" messages are kept in the active letter list, whether read or unread. In combination with the "Save" changes (see below), this means that marking is now actually useful. :-) - Extensive changes to the "Save" dialog: When you Save from the letter list, you're given a choice of "All", "This one", or "Quit"; if any messages are marked, you also get the option "Marked". Saving "All" now saves all messages in the active list, rather than all in the area. (To get the old behavior, just toggle the full list on first.) Saving a message sets the "Read" flag, and turns off "Marked". The last-entered filename is retained (separately) for "All", "This One", and "Marked". If no name is entered manually, the automatic name is regenerated with each use; otherwise, the manual name is kept until you exit MultiMail. ### Bug fixes and such: - Segfaults in saving All, in some cases. - Certain ANSI pics, with codes in the form "[;27H" (i.e., with an empty parameter before a semicolon) were causing segfaults. - After the cursor was turned on, e.g. by the Save window or an aborted message header edit, it was left in the middle of the screen on some terminals. It is now forced down to the corner. - The header editor was being left on screen after exiting via ESC. - Removed the text "Fatal Error" from fatal errors. :-) It was sometimes redundant, and other times wrong. - When the last packet was deleted from the packet list, a segfault could occur. Reported by Alan Ianson. - Functions which redrew the letter window (e.g., toggling character set translation) were setting the Read marker each time. - The tagline window now looks better on screens other than 80 columns. - Various internal changes, and added comments. ## 0.18 - 1998/08/14: ### New features: - MultiMail can now read QWK packets without .NDX files. Partly inspired by Simon Callan. Note: It's still preferable to include them, since it takes about three times longer to open the packet without them. (But that's not counting the unpacking time, which is much longer still.) - The letter list now allows toggling between showing all messages, and showing only those which are unread; when entering an area, it defaults to unread (unless all the messages are marked read). I've found that this makes a BIG difference in the feel of the program -- it's more like a newsreader now. ;-) Use the 'L' key to toggle between the long and short lists (as in the area list). ### Bug fixes and such: - Removed a lot of redundant code from the interface section, and reorganized it... There are many new features that need implementing, but so far I've been concentrating on cleaning up and simplifying the existing code, so as to have a solid base to build on. I think I'm almost there. ;-) - Found a couple small memory leaks, in AnsiWindow (the statbar was not being deleted) and main_read_class. Also, the tagline file was being left open after reading it. - Now gives a fatal error (instead of segfaulting) if the screen is smaller than 60x20. - The address book and tagline window now respond correctly to SIGWINCH. - In the tagline window, the key for rereading the file has been changed from 'F' to 'L'. This allows 'F' to be used as an alias for PgDn, as on other screens. (The real purpose was to merge the code into the rest of the keyboard-handling code.) - If there was only one area (i.e., REPLY), and the right arrow was pressed, an infinite loop would occur. Reported by Tamminen Eero. - The 'N' key, for Netmail, is now disabled when no Netmail area is available. Partly inspired by Francois Thunus. - Sample filenames in the default .mmailrc now conform to the OS (i.e., 8.3 (FAT) format for DOS and OS/2), and the unused "ReadDir" line has been removed. Inspired by F.T. - Stripping and adding of blank lines at the end of messages works a little better now, but still needs work. - The addressbook was crashing, when empty (0 items). Reported by Marc D. Williams. ## 0.17 - 1998/07/06: ### New features: - Netmail addresses, when present in Blue Wave packets (or reply packets), are displayed in the From: or To: fields in the letter window, as appropriate; and when entering a netmail message, the address can now be edited. (Note: Netmail is largely untested yet.) - In the header editor (From, To, etc.), you can now use the up and down arrows to move between fields, or press ESC in any field to abort the edit. Press ENTER on the last field to exit normally. - Reading a message in the QWK Personal area marks it as read in its original area. - A full area list is now available in both QWK and Blue Wave modes; it can be toggled by pressing 'L' in the area list. (The compile-time "shortlist" definition has been removed.) One benefit is that you can enter a message in any area, even when only the short list is being displayed. - When re-editing a message, you can now edit the header as well as the text. - ANSI animation. While in the ANSI viewer, press one of the ANSI activation keys again (or 'A') to see an animated view. Press any key to abort the animation. - The ANSI viewer supports the screen-clearing code. - The cursor is turned off, wherever possible. Less visual clutter. :-) - True scrolling instead of redrawing; minimized the redrawing done for letter window, ANSI viewer, and all ListWindows. It's now MUCH faster on slow machines and slow terminals. ### Bug fixes and such: - MASSIVE rewrite... Almost everything that was crufty in previous versions is now done the right way. :-) General fixes include: plugging memory leaks (there were a lot of these, I'm sorry to say), elimination of one-based arrays, adding more limit checks, and removal of all kinds of kludges and redundant code. The interface is more fully separate from the specific drivers; adding new packet types now requires changing only mmail/driverl.cc. - The netmail area is found by attribute rather than name, and (owing to the full area list now available in Blue Wave mode) can always be found, instead of only when you have received netmail. :-) - Character set translation is applied to the area list, and to area descriptions in the letter list and letter window. - The number of replies shown in the REPLY area is updated immediately when it changes. - The method of adding blanks before taglines and tearlines is changed; it should no longer be necessary to type an extra CR at the end of replies. - QWK replies are automatically word-wrapped at 80 columns, if they're not wrapped already. (In Blue Wave, this is not desirable. QWK doors seem to prefer individual lines; Blue Wave, paragraphs.) - MultiMail now ensures that QWK replies end with a line-ending character, instead of letting the last line run off into the padding area. Without this, certain QWK doors would strip off the tearline. - The path is no longer displayed in the Save window, and the suggested filename is conformed to FAT (8.3) standards. Note that you can still enter any pathname, and any filename that will be accepted by your system. - Specifying pathnames that end in a slash or backslash (e.g., in .mmailrc) should now work correctly. - The extraneous "bw" prefix found on many of the .mmailrc variable names is no longer needed, though it will still be recognized for backwards compatibility. - Space bar now works in the little area list. - The QWK "Personal" area no longer appears in the little area list, nor can messages be entered in it from the area list. - Unrecognized packets no longer cause a segfault (just an exit). - Added trivial SIGWINCH handlers for AddressBook and TaglineWindow (they just exit that menu). - Fatal errors are now reported correctly in the OS/2 version, even when they occur before curses initialization. - In the ANSI viewer, there are no more segfaults in OS/2, and no more stray characters on the status line in either PDCurses version. - ANSI Reverse attribute now works with PDCurses. - ANSI viewing works in reply area. - A SIGWINCH can no longer result in the active (highlighted) line being displaced off the screen. - New default LHA parameters for MSDOS version (the Unix ones didn't work for it). - The currently selected address is preserved between calls to the AddressBook. ## 0.16 - 1998/05/28: ### New features: - This version compiles for OS/2, with EMX. This entailed substantial changes to the Makefile (q.v.), as well as some code. - The environment variable MMAIL can now be used instead of HOME, to specify the directory of the .mmailrc (or mmail.rc) file. If neither variable is defined, MultiMail will use the current directory (this is also new). - The DOS-to-Latin 1 character table has been improved, for translation of graphics characters, by stealing most of the table from DOSEmu. - Truly automatic packet recognition, by packet contents instead of name. Patch by Robert Vukovic. - Internal ANSI viewer. Works on all platforms (except that it sometimes segfaults when scrolling, under OS/2). Much better than the "less" viewer, and it will form the basis of new file list and bulletin viewers. ### Bug fixes and such: - Another big bug in the DOS version (argh!): Last read markers were not being saved. This bug was the result of an error in the bug fix in 0.15. :-/ I'm surprised no one has reported this. - In the previous DOS versions, under some arrangements of directories, the temporary directories would not be removed. - The man page has been renamed to "mm.1", and the title changed, so that "man" and "apropos" will work more appropriately. The install will make a link to the old name (mmail.1). - The "Replied" flag is now set by O- and N-type replies, as well as R. - Some code reorganization. ## 0.15 - 1998/05/12: ### New feature: - If you back out from a packet, to the packet list, you can now reselect the same packet without it being decompressed again. Thanks to Robert Vukovic for convincing me of the need for something like this. ### Bug fix: - In DOS, the default editor (edit.com) didn't actually work, because it didn't understand paths with slashes instead of backslashes. I missed this because it worked with Qedit. :-) The slashes are now flipped on the pathnames passed to external editors and archivers (in DOS only). Thanks to kifox@geocities.com for reporting the problem. ## 0.14 - 1998/04/25: ### Bug fix: - A more subtle line-ending bug... under MSDOS, extra characters were sometimes added to the end of a reply. (Unix versions were not affected.) ## 0.13 - 1998/04/24: ### Bug fix: - While making changes to accomodate MSDOS line endings, I made a small oversight that caused replies to be truncated at one character if they were re-edited. ## 0.12 - 1998/04/23: ### New features: - Compatible with MSDOS. All source code files have been changed to single-case, 8.3 form, and #ifdef's are used on filenames internally where necessary. (Most other issues are taken care of by DJGPP, though some Makefile changes were necessary, and the starting directory is now restored on termination -- needed in DOS, but not in Unix.) - Compatible with PDCurses (as distributed with DJGPP), and SysV curses (specifically, Solaris). Note that the shadows on windows are opaque if you compile with SysV curses. (I also added "#define USE_SHADOWS", which you can comment out or remove to get rid of the shadows altogether.) ### Bug fixes and such: - After the reorganization of resource.C in version 0.11 (if not before), any change to the value of mmHomeDir in .mmailrc would leave the bwPacketDir, bwReplyDir, etc. variables unaffacted. - memError() now works correctly, even before initialization of the interface. Fatal errors in general will be reported better in this version. - Some additonal bounds checking on sprintf() calls. - tmpnam() checked. In principle, as few as 26 temporary filenames might be available from it (which should still be enough, unless you write a whole lot of replies). ## 0.11 - 1998/03/21: ### New features: - Area and system descriptions added to saved messages. - I removed the last remaining command-line option, "-m", on the grounds that it was useless. (This is a new UNfeature.) After testing it on my 9600 bps terminal, it doesn't appear to improve the speed; and the alternate color scheme, which it was originally designed to select, was never implemented. (Instead, I've added inversing to the top and bottom bars in the letter window, and made a few other changes to make it more monochrome-friendly.) ### Bug fixes and such: - resource.C and resource.h rewritten and simplified to allow compilation with gcc 2.8.1. (It's not yet clear to me why the old version didn't work, but this is better anyway.) There are still a few warnings, but it works OK. - When entering the From:, To:, and Subject: on replies, the full width of the fields had not been available. Under some circumstances, this could even cause a segfault. These lengths still need to be checked more stringently. - Automatic "Re:" adding now takes place before subject-line editing, instead of after. This allows users to see that it will be added, and to override it if desired. (It also saves a few bytes of code!) I don't know why I did it the other way before. - In the event of a "Fatal Error:" exit, MultiMail will now clean up after itself, as with normal exits. Also, failed memory allocation is now explicitly checked. - Default paths for "zip" and "unzip" removed. (These would only show up if the paths were not defined in the .mmailrc -- as they are with the default .mmailrc.) Thanks to Carey Bloodworth for pointing this out. - If the message numbers exceeded 5 digits (i.e., 100000+), they would mess up the display in the letter list. Now, it takes 6. :-) - Much internal reorganization. - Fixed a bug with my website -- the new-style URL (/~wmcbrine/) messed up some relative pathnames. :-) ## 0.10 - 1998/03/07: ### New features: - Shadowed windows! Tell me if you like them. - SIGWINCH support. You can now resize the terminal (e.g., maximize the xterm) while MultiMail is running, and it will adapt to the new size. ### Bug fixes and such: - Under Solaris, the help menu area was not being cleared when changing from one menu to another. - One more space available in letter list (now consistent with the other lists). - On some systems (such as Linux w/ glibc, and NetBSD), if you attempted to read a packet that didn't already have a "bbsid.red" file (mm's read markers) in it, MultiMail v0.9 would segfault right after unzipping a packet, due to a very stupid attempt to call fclose() with a NULL pointer. Under Linux w/ libc.5, this actually worked OK; and I didn't notice it on my NetBSD test system because the packets there already had .red files in them. Thanks again to Cesar Cardoso for reporting the problem. Due to the seriousness of this bug, I'm releasing v0.10 early. ## 0.9 - 1998/02/26: ### New features: - Added alias, "!", for F2. Added Tab as an alias for right arrow (next unread) in letter list. - Better handling of screen widths other than 80; better use of available screen space even in 80 columns. Still to do: handle SIGWINCH. - QWK now has the option (on by default) to show only those areas which have messages in them, instead of the full area list. I set this on by default to match the behavior of the Blue Wave side; you can change this in the top-level Makefile. Currently, it's only a compile-time option. (In the future, I'll add the ability to do a full list in Blue Wave mode, and to toggle the mode at runtime.) - .REP and .NEW filenames are now forced to lowercase. Should be easier to type. :-) But if you have any uppercase-named reply packets from previous versions, you'll have to manually rename them before 0.9 will recognize them. Sorry. - Blue Wave mode now works on big-endian systems! The full functionality of MultiMail is now available on all platforms where it compiles. And "-fpack-struct" is no longer needed in the Makefile -- which I hope may mean increased portability. - system() calls to rm and sed have been eliminated. ### Bug fixes and such: - Strip spaces from Blue Wave subject lines; fixes sorting in some cases. - In Blue Wave mode, MultiMail was using LF as a paragraph delimiter. The correct behavior is to use CR as the delimiter and ignore any LFs. Thanks to Marc D. Williams for submitting a packet that required this fix. - Messages in Blue Wave packets are supposed to have a leading space, but packets produced by the ReneWave door lacked them. They also contained nulls in messages (a no-no). MultiMail now deals with these problems without flaking out. Thanks again to M.D.W. - With Blue Wave packets, MultiMail now scans for "*.inf", instead of assuming that the packet name minus the extension is the basename. Normally, that is the case, but the "welcome!.000" packet that comes with the Blue Wave reader -- which uses "welcome" internally -- is a counterexample. MultiMail can now read this packet. :-) - Startup for QWK packets is much faster, especially on slow systems. Previous versions would attempt to open the .ndx file for each area defined in the control.dat (and would do so several times for each area), whether it existed or not; 0.9 scans to see which .ndx files actually exist first. - Changed ANSI viewer to work better with certain messages. Really, I should make it user-definable, instead of being hard-wired to "less"; but I'm probably going to make it into an internal function anyway. - Added "#include " to mmail/mmail.h. Usually I wouldn't mention a change of this type, but in this case, it was done to get MultiMail to compile with glibc (libc 6). Thanks to Cesar Cardoso for reporting the problem and testing the solution. - Saved messages are now wrapped at 80 columns, regardless of screen width at the time of saving; and the date is now added to the saved header. - Replying to a message no longer messes up the right margin of the original when viewing it immediately after replying. - Paths are no longer stored with the .red file when using LHA. In previous versions, this could cause the storing of multiple .red files into a packet. - Next/previous unread in letter list now work correctly, even with messages that have manually been marked Unread. - Miscellaneous minor internal fixes and optimizations. ## 0.8 - 1998/02/10: ### New features: - "Re: " is stripped from subject lines, for sorting and display purposes, and added automatically on replies (unless doing so would truncate the subject). Subject sorting is now case-insensitive. - Letter window now displays "bbsnum (x of y)", for consistency with the letter list and to provide more information. - Slightly expanded the area description length to make better use of the available space. Also, in the letter list, more of the subject is shown. - Restored the "line/lines" counter found in early versions of MultiMail. This shows the length of the message in lines, and the number of the top line on screen. - Temporary files are now cleared at the end of a session. Also, since MultiMail now generates a unique temporary directory for each session, a single user can run multiple concurrent sessions. (Just don't try to read the same packet in each one!) - System uname added to Blue Wave tearline (if it will fit). - Character set translation can now be toggled at runtime, by pressing 'c', instead of at compilation time. - Packet type is now recognized automatically, based on the filename's extension. (You can still force the other type.) Consequently, the "-b" and "-q" options have been removed. - The Blue Wave area list now uses the description instead of the echotag. This is much more useful, and conforms to the Blue Wave reader. - The number of the original message is now passed to the door for reply linking. - In the letter window, the top and bottom bars are now inversed when in monochrome. In the various lists, the highlight bar is now drawn with stdout(), for greater contrast. - Many changes to the top-level Makefile; it's now more portable, and commented. You can now set the location of the ncurses header file here, instead of editing the source. - The packet list is now sorted, and shows file sizes as well as dates. - The backquote character (`) is converted to an ESC when using the ANSI viewer. (Some systems recode ESC characters this way.) - You can now Kill packets from the packet list. ### Bug fixes and such: - If all preexisting replies were deleted, attempting to create a new reply during the same session could cause a segfault. - Terminal newlines stripped from replies. Conforms to the Blue Wave reader; suppresses gaps after the tagline with some doors. - QWK subject fields had been truncated at 24 characters. (The field is 25 chars.) - The tagline file can now be hand-edited without introducing blank lines. - In the letter window, PgDn, End, and the down arrow now stop at the actual end of the text. - No more extra junk in Blue Wave reply packets. - Blue Wave replies can now be safely reedited. (Previously, the line endings could be messed up -- left in Unix format.) Stray characters no longer appear at the end of Blue Wave replies while viewing them. - Blue Wave mode, like QWK mode, now shows the BBS message numbers in the letter list, instead of the messages' position in the packet. - Trailing ", Sysop" stripped from sysop name in QWK mode. - Opening reply packets that were generated by the Blue Wave reader caused a segfault, due to case mismatch; it now works correctly. - Version number "encryption" for Blue Wave replies fixed (so the number appears correctly in tearlines). (IMO, the Blue Wave specs are in error in describing this feature -- bluewave.h refers to addition when it should say subtraction.) - Left and right arrow keys in the area list now work much faster when skipping empty areas, and can take you to the first and last areas, instead of second and next-to-last. - Killed the stupid repeating-REPLY-area bug in Blue Wave mode. - When creating Blue Wave replies, MultiMail now makes the proper choice between "real name" and "alias", depending on the area flags. ## 0.7 - 1997/12/07: ### New features: - Right and left arrow keys now select next/previous unread message in an area, similarly to the way they function in the area list (jumping to non-empty areas). - ANSI viewer. Hit ^A while reading a message to view it in color, if it has ANSI codes embedded. This is still rudimentary; it uses "less" as the viewer, and it depends on the terminal to interpret the codes. - Private flag support. Complete, except for Blue Wave area flag checks. - Character set translation is essentially complete. The only further changes I envision are the ability to turn translation on or off at run time, instead of at compile time; some possible changes to the translation table; and maybe additional character sets. - Stolen taglines are checked for dupes, included only once. - With some minor changes, I got it to work under SunOS (Solaris). No longer just a Linux program! :-) So, the tearline is now derived from the uname. Blue Wave mode still requires a little-endian system. - QWK "Personal" conference. - Many key aliases added, mainly for use on terminals that don't support keys like PgDn and F1. Also, Space Bar now functions as a combination PgDn/Enter in the letter window, to allow paging through a conference. - Marking and read/unread toggle now work from the letter list, simplifying bulk marking. In the REPLY area, Kill now works from the letter list. - Automatic creation of .mmailrc and the mmail directories; no more "make install_dirs". - Changed bluewave.h to version 3. (No related feature changes yet.) I now use it in unmodified form; "-fpack-struct" is sufficient. ### Bug fixes and such: - Suppression of "hidden" text lines added for Blue Wave (already present for QWK). - Append saved files instead of overwrite. - If saving without a path specified, save in the "save" directory. - Better adaptation to nonstandard screen sizes. - Really random taglines (previously, there was no call to srand). - "Save lastread pointers?" and "Reply area has changed..." messages now come up only when appropriate. - "Personal" column in area list removed in QWK mode. Maybe not a bug, but it was unused. - Fall back to login name if no alias name defined. (It was using only the alias name, sometimes leading to blank From: lines in Blue Wave mode. This feature needs more work -- the alias should not always be the default.) - Various date fixes (QWK and Blue Wave). - Print the right area type for QWK Replies. - Area names padded out with spaces when necessary to correct a cosmetic defect in the REPLIES/PERSONAL letter lists' "Area" fields. - Version number references made consistent. - Remove extra bytes from ends of messages (QWK and Blue Wave). - A big one: Kill the *correct* reply messages with 'K'. (!) - A BIG one: QWK reply area numbers fixed. It was using the internal area number, rather than the QWK number; so replies would go to the wrong areas! (Under specific conditions -- a packet with conferences that were numbered serially, starting from zero -- this would actually work right, which is doubtless how the bug snuck in to begin with.) - Stop truncating replies. (This bug complimented the extra byte bug, preventing segfaults in the old version.) - I changed the default directories from "bwdown", etc., to "down", etc. This wasn't a bug, but it was too Blue Wave-centric for a dual-function reader. (I believe the original intent of K.T. & T.I. was that the QWK implementation would have its own directories, e.g., "qwkdown"; but as implemented by J.Z., a common directory was used. I may revert to the putative original design at some point. Internally, the "bw" prefix is still used on many shared structures.) - Make .REP packets from the BBSID, not the base packetname. - "Unread" now counts all messages marked unread, not just those which are also unmarked and unreplied. - Letter sort fixed (now sorts by number within each subject), and faster. - Eliminated the defaulting to "reply" when an otherwise undefined key was pressed. - Calls to todos replaced with internal code. - QWK .ndx parser replaced with faster, non-endian-dependent version. - Many ncurses changes. Most importantly, shells and (normal) exits now restore the screen mode. (Error exits still need fixing.) - Makefile changes (including centralizing options in top-level Makefile). Could do with a bit more changing, I think. - A big one (since it prevented me from even using 0.6, as it was): Fixed segfaults on opening packets with uppercase names within (i.e., those from most or all DOS-based BBSes). - Many minor changes to suppress warning messages during compilation. - Many, many more. :-) These are just the user-visible ones. ### Bugs unfixed: - The lockup-on-exit bug, described below as having appeared in 0.3 and disappeared in 0.4, has resurfaced. I too am unable to trace it. It can come or go after almost any random change in the code. -- William McBrine ## 0.6a - 1997/03/25: - Oops! The signature was in the wrong place. Right order is: signature, tagline, tearline ;-) - Tagline adoption is supported! (Doesn't check dupe tags yet.) - QWK-reply packs contained '\n' as a line terminator. It's incorrect! We have to use softCR (char#227). - Character conversion is somewhat in! For ISO 8859-1 <--> CP437 (DOS) translation, #define ISOCONVERT in /interface/interface.h - ISO conversion doesn't do the from/to/subj yet ## 0.5a - 1997/03/22: - Fixed another date bug (when reloading QWK replies, the date would be mangled). - You can now use a signature file after each letter (specify its filename in .mmailrc) - Says error, if ~/.mmailrc isn't found. (The program needs this file!) - File open errors printed, inside a BW/QWK packet, it tries four variations of the filename (eg. mybbs.dat, MYBBS.dat, MYBBS.DAT, mybbs.DAT). - The annoying bug, which occurred when quitting, has disappeared! ;-o ## 0.4a - 1997/03/12: - Fixed the date handling with QWK packets. Should do it well. That part wasn't even implemented before. - Colors should be fine now, everywhere. - One small, but ANNOYING bug introduced: the program doesn't exit properly, sometimes you have to kill it! Sorry, couldn't trace this bug yet. Maybe you can help. ## 0.3alpha - 1997/02/16: - Took out many more bugs. This version now is almost usable ;) - You can now (re)Edit your reply! - Major design and color changes... Still not done, but doing good! - Added message Marking, Read/Unread toggle. ## 0.2alpha - 1997/02/12: - Debugged the program, fixed lots'a bugs, added QWK support. Only a preview! Don't use it, that's my advice! ;) -- John Zero ## 0.1c.staticbin - 1996/??/??: - statically linked elf executable of 0.1c. Problems were reported with gcc 2.7.2 and libc 5.2.?. ## 0.1c - 1996/03/19: - bugfix, now uses "todos", instead of "/usr/bin/todos", only elf binary included ## 0.1b - 1996/02/05: - Compiles on RedHat 2.1 + dynamic elf binary included ## 0.1a - 1996/02/03: - INSTALL file created (cut from readme) ## 0.1 - 1996/01/27: - initial release -- Kolossvary Tamas and Toth Istvan mmail-0.52/Makefile.wcc000644 000765 000024 00000003212 13254747371 015736 0ustar00wmcbrinestaff000000 000000 #----------------------------------------------- # MultiMail Makefile (top) for Open Watcom C++ #----------------------------------------------- # For Windows: CURS_DIR = /pdcurses LIBS = $(CURS_DIR)/wincon/pdcurses.lib COMPILER = wpp386 -zq -bt=nt -D__WIN32__ -DWIN32 LINKER = wlink system nt #-------------------------------------------------------------- # For 32-bit OS/2: !ifeq SYS OS2 LIBS = $(CURS_DIR)/os2/pdcurses.lib COMPILER = wpp386 -zq -bt=os2v2 -D__OS2__ LINKER = wlink system os2v2 !endif #-------------------------------------------------------------- # For 32-bit DOS: !ifeq SYS DOS32 LIBS = $(CURS_DIR)/dos/pdcurses.lib COMPILER = wpp386 -zq -bt=dos4g -mf -D__MSDOS__ LINKER = wlink system dos4g !endif #-------------------------------------------------------------- # For 16-bit DOS: !ifeq SYS DOS16 LIBS = $(CURS_DIR)/dos/pdcurses.lib COMPILER = wpp -zq -bt=dos -ml -D__MSDOS__ LINKER = wlink system dos !endif !ifdef __LOADDLL__ ! loaddll wlink wlinkd ! loaddll wlib wlibd ! loaddll wpp wppdi86 ! loaddll wpp386 wppd386 !endif O = obj CPPFLAGS = -I$(CURS_DIR) .cc: mmail;interfac .cc.obj: .autodepend $(COMPILER) $(CPPFLAGS) $< MOBJS = misc.$(O) resource.$(O) mmail.$(O) driverl.$(O) filelist.$(O) & area.$(O) letter.$(O) read.$(O) compress.$(O) pktbase.$(O) bw.$(O) & qwk.$(O) omen.$(O) soup.$(O) opx.$(O) IOBJS = mmcolor.$(O) mysystem.$(O) isoconv.$(O) basic.$(O) interfac.$(O) & packet.$(O) arealist.$(O) letterl.$(O) letterw.$(O) lettpost.$(O) & ansiview.$(O) addrbook.$(O) tagline.$(O) help.$(O) main.$(O) all: mm.exe mm.exe: $(MOBJS) $(IOBJS) $(LINKER) name mm.exe file *.obj libfile $(LIBS) clean del *.obj del mm.exe mmail-0.52/INSTALL.md000644 000765 000024 00000006414 13433132267 015151 0ustar00wmcbrinestaff000000 000000 MultiMail compilation and installation procedure ================================================ These instructions assume that you're compiling MultiMail from source. For precompiled binaries, see the README files that accompany them instead. 1. Make sure any needed packages are installed -- In addition to the MultiMail package itself, you'll also need InfoZip or PKZIP (and/or LHA, ARJ, etc.) to uncompress the packets and compress the replies. InfoZip is available from: http://infozip.sf.net/ (PKZIP is the default for DOS; InfoZip is the default for other platforms.) The programs should be installed somewhere in the PATH; otherwise, the full path must be specified in ~/.mmailrc. To compile MultiMail, you'll need curses -- either ncurses, SysV curses (e.g., Solaris curses), or PDCurses. You can get ncurses from: http://invisible-island.net/ncurses/ PDCurses is available at: http://pdcurses.org/ (If you're using Linux, you probably already have ncurses and InfoZip.) If using PDCurses, MultiMail now requires version 3.6 or later. The 16-bit MS-DOS Turbo C++ port also uses Ralf Brown's SPAWNO library: http://www.cs.cmu.edu/afs/cs.cmu.edu/user/ralf/pub/WWW/files.html 2. Configure it (for compilation) -- Check the options and paths in the Makefile. If curses.h isn't in the include path, change CURS_DIR as appropriate. You may also need to change LIBS. These can be set on the command line, e.g. "make CURS_DIR=/pdcurses". 3. Compile MultiMail -- At the base directory, type: `make` 4. Run it -- Type: `./mm` (For DOS, OS/2 or Windows, set the MMAIL or HOME variable, then run mm.) 5. (Optional:) Configure it (for end user) -- Edit the ~/.mmailrc file. (For DOS, OS/2 or Windows, mmail.rc.) 6. (Optional:) Install it system-wide -- Type: `make install` to install the manual and binary under /usr/local (requires root access). (This doesn't work in DOS, OS/2 or Windows.) See the man page (mmail.1) and README for more information. This package includes some example color schemes, in the "colors" directory. To select one, use the "ColorFile" keyword in .mmailrc . Support for XCurses (PDCurses) ------------------------------ When MultiMail is compiled with XCurses, you can use the X resource database to set certain startup options. Here are some example resources: XCurses*normalFont: 9x15 XCurses*boldFont: 9x15bold XCurses*lines: 30 XCurses*cols: 80 For details, see the PDCurses documentation. If you're using a non-X text editor with an XCurses version of MultiMail, it will work better if you set MultiMail's editor variable to "xterm -e $EDITOR" instead of just "$EDITOR" (the default). Compile notes: Windows, MS-DOS, and OS/2 ---------------------------------------- In the MultiMail source, separate makefiles are provided for these ports. Makefile - GCC (including DJGPP and MinGW) Makefile.bcc - Borland C++ (Windows, MS-DOS) Makefile.vc - Microsoft Visual C++ (Windows) Makefile.wcc - Watcom (All platforms -- Windows by default) Point to your installation of PDCurses and compile with, e.g.: make -f Makefile.bcc CURS_DIR=/pdcurs38 SYS=DOS (Use "wmake" instead of "make" for Watcom; "nmake" for MSVC.) mmail-0.52/README.md000644 000765 000024 00000002001 13431371443 014763 0ustar00wmcbrinestaff000000 000000 MultiMail Offline Reader ======================== MultiMail is an offline mail packet reader for Unix / Linux, MS-DOS, OS/2, Windows, macOS, and other systems, using a curses-based interface. It supports the Blue Wave, QWK, OMEN, SOUP and OPX formats. MultiMail is free, open source software, distributed under the GNU General Public License. You can get the latest version at: http://wmcbrine.com/mmail/ See the [History] file for changes. See [Install] for the installation procedure, and the [man page] for information on usage. Credits ------- MultiMail was originally developed under Linux by Kolossvary Tamas and Toth Istvan. John Zero was the maintainer for versions 0.2 through 0.6; since version 0.7, the maintainer is William McBrine . Additional code has been contributed by Peter Krefting, Mark D. Rejhon, Ingo Brueckl, Robert Vukovic, and Frederic Cambus. Bug reports and suggestions are noted in the [History] file. [History]: HISTORY.md [Install]: INSTALL.md [man page]: mm.1 mmail-0.52/colors/000755 000765 000024 00000000000 13257130623 015013 5ustar00wmcbrinestaff000000 000000 mmail-0.52/COPYING.md000644 000765 000024 00000110501 13065145251 015142 0ustar00wmcbrinestaff000000 000000 This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. The file bluewave.h is under a different license: --- begin extract The Blue Wave Offline Mail System File Structures (hereafter known as "structures" or "the structures") were created by George Hatchew, and are the copyrighted property of George Hatchew and Cutting Edge Computing. Permission is granted for third parties to use these structures in their own programs, without any royalties or licenses required. Cutting Edge Computing reserves the right to make any changes to these structures, at any time. As such, third parties are requested not to make any unauthorized changes to these structures, as Cutting Edge Computing is not bound to follow these changes. Any proposed changes should be brought to the attention of Cutting Edge Computing, where they may be included in future revisions of the structures. Authors that use these structures are allowed to claim that their programs are "Blue Wave compatible", provided that such programs can process mail and reply packets that can be handled without problems or difficulties by The Blue Wave Offline Mail Doors and Readers from Cutting Edge Computing. (Think of it as the "litmus test" for Blue Wave compatibility.) Finally, "Blue Wave" is a trademarked term of Cutting Edge Computing, and cannot be used by authors in the titles of their applications. This does not, however, restrict the ability to describe the application as a "Blue Wave compatible" offline mail application (i.e. "FooBar: The Blue Wave-Compatible Offline Mail Door for Widget BBS"). --- end extract The GPL, below, is copyrighted by the Free Software Foundation, but the instance of code that it refers to (the MultiMail packet reader) is copyrighted by me and the others who wrote it. - William McBrine ---------------------------------------- ### GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. ### Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. ### TERMS AND CONDITIONS #### 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. #### 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. #### 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. #### 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. #### 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. #### 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: - a) The work must carry prominent notices stating that you modified it, and giving a relevant date. - b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". - c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. - d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. #### 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: - a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. - b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. - c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. - d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. - e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. #### 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: - a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or - b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or - c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or - d) Limiting the use for publicity purposes of names of licensors or authors of the material; or - e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or - f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. #### 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. #### 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. #### 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. #### 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. #### 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. #### 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. #### 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. #### 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. #### 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. #### 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS ### How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands \`show w' and \`show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . mmail-0.52/tclist000644 000765 000024 00000000526 13246535430 014745 0ustar00wmcbrinestaff000000 000000 mysystem.obj mmcolor.obj isoconv.obj basic.obj interfac.obj packet.obj arealist.obj letterl.obj letterw.obj -Yo lettpost.obj ansiview.obj addrbook.obj tagline.obj -Yo- help.obj main.obj misc.obj resource.obj mmail.obj filelist.obj area.obj letter.obj read.obj compress.obj driverl.obj pktbase.obj -Yo bw.obj qwk.obj omen.obj soup.obj opx.obj mmail-0.52/depend000644 000765 000024 00000015446 13247051571 014711 0ustar00wmcbrinestaff000000 000000 area.$(O): mmail/area.cc mmail/mmail.h mmail/../config.h \ mmail/../mmail/misc.h mmail/../mmail/resource.h \ mmail/../interfac/mysystem.h bw.$(O): mmail/bw.cc mmail/bw.h mmail/pktbase.h mmail/mmail.h \ mmail/../config.h mmail/../mmail/misc.h mmail/../mmail/resource.h \ mmail/../interfac/mysystem.h mmail/bluewave.h mmail/compress.h compress.$(O): mmail/compress.cc mmail/compress.h mmail/mmail.h \ mmail/../config.h mmail/../mmail/misc.h mmail/../mmail/resource.h \ mmail/../interfac/mysystem.h driverl.$(O): mmail/driverl.cc mmail/bw.h mmail/pktbase.h mmail/mmail.h \ mmail/../config.h mmail/../mmail/misc.h mmail/../mmail/resource.h \ mmail/../interfac/mysystem.h mmail/bluewave.h mmail/qwk.h mmail/omen.h \ mmail/soup.h mmail/opx.h mmail/opxstrct.h filelist.$(O): mmail/filelist.cc mmail/mmail.h mmail/../config.h \ mmail/../mmail/misc.h mmail/../mmail/resource.h \ mmail/../interfac/mysystem.h letter.$(O): mmail/letter.cc mmail/mmail.h mmail/../config.h \ mmail/../mmail/misc.h mmail/../mmail/resource.h \ mmail/../interfac/mysystem.h misc.$(O): mmail/misc.cc mmail/mmail.h mmail/../config.h \ mmail/../mmail/misc.h mmail/../mmail/resource.h \ mmail/../interfac/mysystem.h mmail.$(O): mmail/mmail.cc mmail/mmail.h mmail/../config.h \ mmail/../mmail/misc.h mmail/../mmail/resource.h \ mmail/../interfac/mysystem.h mmail/compress.h \ mmail/../interfac/error.h omen.$(O): mmail/omen.cc mmail/omen.h mmail/pktbase.h mmail/mmail.h \ mmail/../config.h mmail/../mmail/misc.h mmail/../mmail/resource.h \ mmail/../interfac/mysystem.h mmail/compress.h opx.$(O): mmail/opx.cc mmail/opx.h mmail/pktbase.h mmail/mmail.h \ mmail/../config.h mmail/../mmail/misc.h mmail/../mmail/resource.h \ mmail/../interfac/mysystem.h mmail/opxstrct.h mmail/compress.h pktbase.$(O): mmail/pktbase.cc mmail/compress.h mmail/mmail.h \ mmail/../config.h mmail/../mmail/misc.h mmail/../mmail/resource.h \ mmail/../interfac/mysystem.h mmail/pktbase.h qwk.$(O): mmail/qwk.cc mmail/qwk.h mmail/pktbase.h mmail/mmail.h \ mmail/../config.h mmail/../mmail/misc.h mmail/../mmail/resource.h \ mmail/../interfac/mysystem.h mmail/compress.h read.$(O): mmail/read.cc mmail/mmail.h mmail/../config.h \ mmail/../mmail/misc.h mmail/../mmail/resource.h \ mmail/../interfac/mysystem.h mmail/compress.h resource.$(O): mmail/resource.cc mmail/mmail.h mmail/../config.h \ mmail/../mmail/misc.h mmail/../mmail/resource.h \ mmail/../interfac/mysystem.h mmail/../interfac/error.h soup.$(O): mmail/soup.cc mmail/soup.h mmail/pktbase.h mmail/mmail.h \ mmail/../config.h mmail/../mmail/misc.h mmail/../mmail/resource.h \ mmail/../interfac/mysystem.h mmail/compress.h addrbook.$(O): interfac/addrbook.cc interfac/interfac.h \ interfac/../mmail/mmail.h interfac/../mmail/../config.h \ interfac/../mmail/../mmail/misc.h \ interfac/../mmail/../mmail/resource.h \ interfac/../mmail/../interfac/mysystem.h interfac/mmcolor.h \ interfac/isoconv.h ansiview.$(O): interfac/ansiview.cc interfac/interfac.h \ interfac/../mmail/mmail.h interfac/../mmail/../config.h \ interfac/../mmail/../mmail/misc.h \ interfac/../mmail/../mmail/resource.h \ interfac/../mmail/../interfac/mysystem.h interfac/mmcolor.h \ interfac/isoconv.h arealist.$(O): interfac/arealist.cc interfac/interfac.h \ interfac/../mmail/mmail.h interfac/../mmail/../config.h \ interfac/../mmail/../mmail/misc.h \ interfac/../mmail/../mmail/resource.h \ interfac/../mmail/../interfac/mysystem.h interfac/mmcolor.h \ interfac/isoconv.h basic.$(O): interfac/basic.cc interfac/interfac.h interfac/../mmail/mmail.h \ interfac/../mmail/../config.h interfac/../mmail/../mmail/misc.h \ interfac/../mmail/../mmail/resource.h \ interfac/../mmail/../interfac/mysystem.h interfac/mmcolor.h \ interfac/isoconv.h help.$(O): interfac/help.cc interfac/interfac.h interfac/../mmail/mmail.h \ interfac/../mmail/../config.h interfac/../mmail/../mmail/misc.h \ interfac/../mmail/../mmail/resource.h \ interfac/../mmail/../interfac/mysystem.h interfac/mmcolor.h \ interfac/isoconv.h interfac.$(O): interfac/interfac.cc interfac/error.h interfac/interfac.h \ interfac/../mmail/mmail.h interfac/../mmail/../config.h \ interfac/../mmail/../mmail/misc.h \ interfac/../mmail/../mmail/resource.h \ interfac/../mmail/../interfac/mysystem.h interfac/mmcolor.h \ interfac/isoconv.h isoconv.$(O): interfac/isoconv.cc interfac/interfac.h \ interfac/../mmail/mmail.h interfac/../mmail/../config.h \ interfac/../mmail/../mmail/misc.h \ interfac/../mmail/../mmail/resource.h \ interfac/../mmail/../interfac/mysystem.h interfac/mmcolor.h \ interfac/isoconv.h letterl.$(O): interfac/letterl.cc interfac/interfac.h \ interfac/../mmail/mmail.h interfac/../mmail/../config.h \ interfac/../mmail/../mmail/misc.h \ interfac/../mmail/../mmail/resource.h \ interfac/../mmail/../interfac/mysystem.h interfac/mmcolor.h \ interfac/isoconv.h letterw.$(O): interfac/letterw.cc interfac/interfac.h \ interfac/../mmail/mmail.h interfac/../mmail/../config.h \ interfac/../mmail/../mmail/misc.h \ interfac/../mmail/../mmail/resource.h \ interfac/../mmail/../interfac/mysystem.h interfac/mmcolor.h \ interfac/isoconv.h lettpost.$(O): interfac/lettpost.cc interfac/interfac.h \ interfac/../mmail/mmail.h interfac/../mmail/../config.h \ interfac/../mmail/../mmail/misc.h \ interfac/../mmail/../mmail/resource.h \ interfac/../mmail/../interfac/mysystem.h interfac/mmcolor.h \ interfac/isoconv.h main.$(O): interfac/main.cc interfac/error.h interfac/interfac.h \ interfac/../mmail/mmail.h interfac/../mmail/../config.h \ interfac/../mmail/../mmail/misc.h \ interfac/../mmail/../mmail/resource.h \ interfac/../mmail/../interfac/mysystem.h interfac/mmcolor.h \ interfac/isoconv.h mmcolor.$(O): interfac/mmcolor.cc interfac/interfac.h \ interfac/../mmail/mmail.h interfac/../mmail/../config.h \ interfac/../mmail/../mmail/misc.h \ interfac/../mmail/../mmail/resource.h \ interfac/../mmail/../interfac/mysystem.h interfac/mmcolor.h \ interfac/isoconv.h mysystem.$(O): interfac/mysystem.cc interfac/interfac.h \ interfac/../mmail/mmail.h interfac/../mmail/../config.h \ interfac/../mmail/../mmail/misc.h \ interfac/../mmail/../mmail/resource.h \ interfac/../mmail/../interfac/mysystem.h interfac/mmcolor.h \ interfac/isoconv.h interfac/error.h packet.$(O): interfac/packet.cc interfac/interfac.h \ interfac/../mmail/mmail.h interfac/../mmail/../config.h \ interfac/../mmail/../mmail/misc.h \ interfac/../mmail/../mmail/resource.h \ interfac/../mmail/../interfac/mysystem.h interfac/mmcolor.h \ interfac/isoconv.h tagline.$(O): interfac/tagline.cc interfac/interfac.h \ interfac/../mmail/mmail.h interfac/../mmail/../config.h \ interfac/../mmail/../mmail/misc.h \ interfac/../mmail/../mmail/resource.h \ interfac/../mmail/../interfac/mysystem.h interfac/mmcolor.h \ interfac/isoconv.h interfac/tagline.h mmail-0.52/interfac/000755 000765 000024 00000000000 13446322253 015307 5ustar00wmcbrinestaff000000 000000 mmail-0.52/interfac/mmcolor.cc000644 000765 000024 00000022624 13065427221 017272 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * color handling, and default colors Copyright 1998-2017 William McBrine , Ingo Brueckl Distributed under the GNU General Public License, version 3 or later. */ #include "interfac.h" chtype emph(coltype chA) { chtype ch = ColorArray[chA]; if (has_colors()) switch (PAIR_NUMBER(ch) & 7) { case COLOR_BLACK: case COLOR_RED: case COLOR_BLUE: ch |= A_BOLD; } else ch |= A_BOLD; return ch; } chtype noemph(coltype chA) { chtype ch = ColorArray[chA]; if (has_colors()) switch (PAIR_NUMBER(ch) & 7) { case COLOR_WHITE: case COLOR_YELLOW: case COLOR_GREEN: case COLOR_CYAN: case COLOR_MAGENTA: ch |= A_BOLD; } return ch; } chtype ColorClass::allcolors[numColors] = { COL(COLOR_WHITE, COLOR_BLACK), //Start screen/backgnd COL(COLOR_BLUE, COLOR_BLACK) | A_BOLD, //Start/bdr COL(COLOR_MAGENTA, COLOR_BLACK), //Start screen/bottom COL(COLOR_WHITE, COLOR_BLACK) | A_BOLD, //Help desc. COL(COLOR_YELLOW, COLOR_BLACK) | A_BOLD, //Help keys COL(COLOR_WHITE, COLOR_BLUE) | A_BOLD, //Help 2 bdr COL(COLOR_YELLOW, COLOR_BLUE) | A_BOLD, //Help 2 text COL(COLOR_YELLOW, COLOR_BLUE) | A_BOLD, //Welcome bdr COL(COLOR_YELLOW, COLOR_BLUE) | A_BOLD, //Welcome prog name COL(COLOR_CYAN, COLOR_BLUE) | A_BOLD, //Welcome auth names COL(COLOR_YELLOW, COLOR_BLUE) | A_BOLD, //Add. backgnd COL(COLOR_GREEN, COLOR_BLUE) | A_BOLD, //Add. headers COL(COLOR_CYAN, COLOR_BLUE) | A_BOLD, //Address book/text COL(COLOR_WHITE, COLOR_RED) | A_BOLD, //Warn/text COL(COLOR_YELLOW, COLOR_RED) | A_BOLD, //Warn/hilight COL(COLOR_WHITE, COLOR_BLUE) | A_BOLD, //Letter/text COL(COLOR_CYAN, COLOR_BLUE), //Letter/quoted text COL(COLOR_CYAN, COLOR_BLUE), //Letter/tagline COL(COLOR_GREEN, COLOR_BLUE) | A_BOLD, //Letter/tear COL(COLOR_GREEN, COLOR_BLUE), //Letter/hidden COL(COLOR_CYAN, COLOR_BLUE), //Letter/origin COL(COLOR_MAGENTA, COLOR_WHITE) | A_REVERSE, //Letter/bottom statline COL(COLOR_BLUE, COLOR_CYAN), //Letter/header text COL(COLOR_BLACK, COLOR_CYAN), //msgnum COL(COLOR_BLACK, COLOR_CYAN), //from COL(COLOR_BLACK, COLOR_CYAN), //to COL(COLOR_BLACK, COLOR_CYAN), //subject COL(COLOR_BLACK, COLOR_CYAN), //date COL(COLOR_CYAN, COLOR_BLACK) | A_REVERSE, //flags high COL(COLOR_WHITE, COLOR_CYAN), //flags COL(COLOR_YELLOW, COLOR_BLUE) | A_BOLD, //Packet/header COL(COLOR_GREEN, COLOR_BLUE) | A_BOLD, //line text COL(COLOR_CYAN, COLOR_BLUE) | A_BOLD, //Packet/lines COL(COLOR_WHITE, COLOR_BLUE) | A_BOLD, //Little area COL(COLOR_WHITE, COLOR_BLUE) | A_BOLD, //line text COL(COLOR_GREEN, COLOR_BLUE), //Area list/reply area COL(COLOR_CYAN, COLOR_BLUE), //Area list/normal COL(COLOR_YELLOW, COLOR_BLUE) | A_BOLD, //info win COL(COLOR_WHITE, COLOR_BLUE) | A_BOLD, //filled text COL(COLOR_GREEN, COLOR_BLUE) | A_BOLD, //border text COL(COLOR_YELLOW, COLOR_BLUE) | A_BOLD, //border COL(COLOR_GREEN, COLOR_BLUE) | A_BOLD, //header text COL(COLOR_WHITE, COLOR_BLUE) | A_BOLD, //Letter text COL(COLOR_CYAN, COLOR_BLUE), //Letter/enter get1 COL(COLOR_GREEN, COLOR_BLUE) | A_BOLD, //get2 COL(COLOR_WHITE, COLOR_RED) | A_BOLD, //Letter/save border COL(COLOR_WHITE, COLOR_RED) | A_BOLD, //Letter/save COL(COLOR_YELLOW, COLOR_RED) | A_BOLD, //get COL(COLOR_WHITE, COLOR_BLUE), //Letter list/top text1 COL(COLOR_GREEN, COLOR_BLUE), //Letter list/personal COL(COLOR_YELLOW, COLOR_BLUE) | A_BOLD, //Letter list COL(COLOR_WHITE, COLOR_BLUE) | A_BOLD, //top text1 COL(COLOR_GREEN, COLOR_BLUE) | A_BOLD, //areaname COL(COLOR_YELLOW, COLOR_BLUE) | A_BOLD, //headers COL(COLOR_YELLOW, COLOR_BLUE) | A_BOLD, //Tagline COL(COLOR_GREEN, COLOR_BLUE) | A_BOLD, //Tagline/text COL(COLOR_GREEN, COLOR_BLUE) | A_BOLD, //key select COL(COLOR_GREEN, COLOR_BLUE) | A_BOLD, //Tagline/enter COL(COLOR_CYAN, COLOR_BLUE) | A_BOLD, //enter get COL(COLOR_CYAN, COLOR_BLUE) | A_BOLD, //lines COL(COLOR_WHITE, COLOR_WHITE) | A_BOLD //All black! }; const char *ColorClass::col_names[numColors] = { "Main_Back", "Main_Border", "Main_BottSeparator", "BottHelp_Descrip", "BottHelp_Keys", "Help_Border", "Help_Text", "Welcome_Border", "Welcome_Header", "Welcome_Text", "Address_Border", "Address_Descrip", "Address_List", "Warn_Text", "Warn_Keys", "Letter_Text", "Letter_Quoted", "Letter_Tagline", "Letter_Tearline", "Letter_Hidden", "Letter_Origin", "Letter_Border", "LH_Text", "LH_Msgnum", "LH_From", "LH_To", "LH_Subject", "LH_Date", "LH_FlagsHigh", "LH_Flags", "Packet_Border", "Packet_Header", "Packet_List", "LittleArea_Header", "LittleArea_List", "Area_Reply", "Area_List", "Area_InfoDescrip", "Area_InfoText", "Area_TopText", "Area_Border", "Area_Header", "HeadEdit_Text", "HeadEdit_Input1", "HeadEdit_Input2", "Save_Border", "Save_Header", "Save_Input", "LettList_Text", "LettList_Personal", "LettList_Border", "LettList_TopText", "LettList_Area", "LettList_Header", "Tag_Border", "Tag_Text", "Tag_Keys", "Tag_Input1", "Tag_Input2", "Tag_List", "Shadow" }; const char *ColorClass::col_intro[] = { "---------------", "Color selection", "---------------", "", "The format is \"ItemName: , , \"", "Colors are Black, Blue, Green, Cyan, Red, Magenta, Yellow, and White.", "Attributes are Bold or Reverse.", "", "If no color for ItemName is defined, the default will be used. (Defaults", "are shown below.) Lines beginning with '#' are commented out.", 0 }; const char *ColorClass::col_comments[numColors] = { "Background colors", 0, 0, "Bottom help window", 0, "Pop-up help", 0, "Welcome window (vanity plate)", 0, 0, "Address book", 0, 0, "Warning window", 0, "Letter window", 0, 0, 0, 0, 0, 0, "Letter header", 0, 0, 0, 0, 0, 0, 0, "Packet list", 0, 0, "Little area list", 0, "Area list", 0, 0, 0, 0, 0, 0, "Header editor", 0, 0, "Save filename input", 0, 0, "Letter list", 0, 0, 0, 0, 0, "Taglines", 0, 0, 0, 0, 0, "Shadows" }; const chtype ColorClass::mapped[] = {COLOR_BLACK, COLOR_BLUE, COLOR_GREEN, COLOR_CYAN, COLOR_RED, COLOR_MAGENTA, COLOR_YELLOW, COLOR_WHITE}; // analyze a ,, color string chtype ColorClass::colorparse(const char *colorstring) { static const char *const cnames[] = {"bla", "blu", "gre", "cya", "red", "mag", "yel", "whi", "bol", "rev"}; static chtype c[] = {COLOR_WHITE, COLOR_BLACK}; chtype att = A_NORMAL; char *pos = (char *) colorstring; for (int i = 0; (i < 3) && *pos; i++) { while (*pos == ' ' || *pos == '\t') pos++; for (int j = 0; j < 10; j++) if (!strncasecmp(pos, cnames[j], 3)) { switch (j) { case 8: att = A_BOLD; break; case 9: att = A_REVERSE; break; default: c[i] = mapped[j]; } break; } while (*pos && *pos != ',') pos++; if (*pos == ',') pos++; } // Swap white-on-white for black-on-black: if ((c[0] == COLOR_BLACK) && (c[1] == COLOR_BLACK)) return COL(COLOR_WHITE, COLOR_WHITE) | att; else return COL(c[0], c[1]) | att; } void ColorClass::processOne(int c, const char *resValue) { allcolors[c] = colorparse(resValue); } const char *ColorClass::configLineOut(int x) { return decompose(allcolors[x]); } const char *ColorClass::findcol(chtype ch) { static const char *const cnames[] = {"Black", "Blue", "Green", "Cyan", "Red", "Magenta", "Yellow", "White", ""}; int x; for (x = 0; x < 8; x++) if (ch == mapped[x]) break; return cnames[x]; } const char *ColorClass::decompose(chtype ch) { static char compost[26]; chtype fg, bg, bold, rev; fg = PAIR_NUMBER(ch) >> 3; bg = PAIR_NUMBER(ch) & 7; bold = ch & A_BOLD; rev = ch & A_REVERSE; // Swap black-on-black for white-on-white: if ((fg == (COLOR_WHITE)) && (bg == (COLOR_WHITE))) fg = bg = COLOR_BLACK; char *p = compost; p += sprintf(p, "%s, %s", findcol(fg), findcol(bg)); if (bold) sprintf(p, ", Bold"); else if (rev) sprintf(p, ", Reverse"); return compost; } void ColorClass::Init() { const char *configname = mm.resourceObject->get(ColorFile); bool usecol = mm.resourceObject->getInt(UseColors); names = col_names; intro = col_intro; comments = col_comments; configItemNum = numColors; if (usecol) if (parseConfig(configname)) newConfig(configname); ColorArray = allcolors; } mmail-0.52/interfac/main.cc000644 000765 000024 00000003724 13232375272 016552 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * main, error Copyright 1996-1997 Kolossvary Tamas Copyright 1997-2017 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #include "error.h" #include "interfac.h" #include Interface *ui = 0; const chtype *ColorArray = 0; time_t starttime; ErrorType error; mmail mm; #ifdef USE_MOUSE MEVENT mm_mouse_event; #endif ErrorType::ErrorType() { starttime = time(0); srand((unsigned) starttime); origdir = mygetcwd(); } ErrorType::~ErrorType() { mychdir(origdir); delete[] origdir; } const char *ErrorType::getOrigDir() { return origdir; } #if defined(SIGWINCH) && !defined(PDCURSES) && !defined(NCURSES_SIGWINCH) extern "C" void sigwinchHandler(int sig) { if (sig == SIGWINCH) ungetch(KEY_RESIZE); signal(SIGWINCH, sigwinchHandler); } #endif void fatalError(const char *description) { delete ui; fprintf(stderr, "\n\n%s\n\n", description); exit(EXIT_FAILURE); } void pauseError(const char *description) { fprintf(stderr, "\n\n%s\n\n", description); napms(2000); } #ifdef USE_MOUSE void mm_mouse_get() { # ifdef NCURSES_MOUSE_VERSION getmouse(&mm_mouse_event); # else nc_getmouse(&mm_mouse_event); # endif } #endif int main(int argc, char **argv) { char **ARGV = argv; int ARGC = argc; setlocale(LC_ALL, ""); while ((ARGC > 2) && ('-' == ARGV[1][0])) { char *resName = ARGV[1] + 1; char *resValue = ARGV[2]; if ('-' == *resName) resName++; mm.resourceObject->processOneByName(resName, resValue); ARGV += 2; ARGC -= 2; } ui = new Interface(); ui->init(); if (ARGC > 1) for (int i = 1; (i < ARGC) && ui->fromCommandLine(ARGV[i]); i++); else ui->main(); delete ui; ui = 0; // some destructors, executed after this, may check for this return EXIT_SUCCESS; } mmail-0.52/interfac/error.h000644 000765 000024 00000000516 13245266073 016617 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * error-reporting class Copyright 1998-2018 William McBrine Distributed under the GNU General Public License, version 3 or later. */ class ErrorType { char *origdir; public: ErrorType(); ~ErrorType(); const char *getOrigDir(); }; extern ErrorType error; mmail-0.52/interfac/mysystem.h000644 000765 000024 00000003204 13255077227 017357 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * protos for mysystem.cc Copyright 1997-2017 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #ifndef MYSYSTEM_H #define MYSYSTEM_H extern "C" { #include } class mystat; char *myfgets(char *, int, FILE *); int mysystem(const char *); int mysystem2(const char *, const char *); char *mytmpdir(const char *); char *mytmpnam(); void edit(const char *); int mychdir(const char *); int mymkdir(const char *); void myrmdir(const char *); char *mygetcwd(); const char *sysname(); bool myopendir(const char *); const char *myreaddir(mystat &); void clearDirectory(const char *); time_t touchFile(const char *); #ifdef LIMIT_MEM long maxfreemem(); long limitmem(long); #else # define limitmem(x) x #endif char *canonize(char *); #ifdef HAS_HOME const char *homify(const char *); #else # define homify(x) x #endif #ifdef USE_SHELL class Shell { char *prompt; public: Shell(); ~Shell(); void out(); }; #endif #ifdef EXTRAPATH class ExtraPath { char *newpath; public: ExtraPath(); ~ExtraPath(); }; #endif class mystat { int mode; off_t size; time_t date; public: mystat(const char *); mystat(); bool init(const char *); #ifdef USE_FINDFIRST # ifdef USE_IOH void init(long, time_t, unsigned); # else void init(long, long, char); # endif #endif void init(); bool isdir(); bool readable(); bool writeable(); off_t fsize(); time_t fdate(); void reset_date(const char *); }; #ifdef USE_STRICMP # define strcasecmp stricmp # define strncasecmp strnicmp #endif #endif mmail-0.52/interfac/interfac.h000644 000765 000024 00000041566 13261372020 017257 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * most class definitions for the interface Copyright 1996 Kolossvary Tamas Copyright 1997 John Zero Copyright 1997-2018 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #ifndef INTERFACE_H #define INTERFACE_H #include "../mmail/mmail.h" extern "C" { #include #include } #if defined(PDCURSES) && (PDC_BUILD < 3100) # error Please upgrade to PDCurses 3.1 or later #endif #if defined(NCURSES_MOUSE_VERSION) || defined(PDCURSES) # define USE_MOUSE #endif /* The following assumes that Ncurses' internal SIGWINCH handler is enabled if, and only if, the Ncurses version is 5.0 or higher. That's the default; if your setup is different, you should manually define or undefine NCURSES_SIGWINCH as appropriate. */ #ifdef SIGWINCH # if defined(NCURSES_VERSION_MAJOR) && (NCURSES_VERSION_MAJOR > 4) # define NCURSES_SIGWINCH # endif # ifndef KEY_RESIZE # define KEY_RESIZE 0777 # endif #endif #include "mmcolor.h" #include "isoconv.h" #define MINWIDTH 60 #ifdef VANITY_PLATE # define MINHINORM 19 # define MINHIEXPERT 17 #else # define MINHINORM 17 # define MINHIEXPERT 17 #endif enum statetype {nostate, packetlist, arealist, letterlist, letter, letter_help, littlearealist, address, tagwin, ansiwin, ansi_help}; enum searchret {False, True, Abort}; enum lineattr {Hidden, Origin, Tearline, Tagline, Sigline, Quoted, Normal}; enum {s_fulltext = 1, s_headers, s_arealist, s_pktlist}; #if defined(SIGWINCH) && !defined(PDCURSES) && !defined(NCURSES_SIGWINCH) extern "C" void sigwinchHandler(int); #endif #define TAGLINE_LENGTH 76 /* Include Keypad keys for PDCurses */ #ifdef PDCURSES # define MM_PLUS '+': case PADPLUS # define MM_MINUS '-': case PADMINUS # define MM_ENTER '\r': case '\n': case PADENTER # define MM_SLASH '/': case PADSLASH # define MM_UP KEY_UP: case KEY_A2 # define MM_DOWN KEY_DOWN: case KEY_C2 # define MM_LEFT KEY_LEFT: case KEY_B1 # define MM_RIGHT KEY_RIGHT: case KEY_B3 # define MM_HOME KEY_HOME: case KEY_A1 # define MM_END KEY_END: case KEY_LL: case KEY_C1 # define MM_PPAGE KEY_PPAGE: case KEY_A3 # define MM_NPAGE KEY_NPAGE: case KEY_C3 # define MM_INS KEY_IC: case PAD0 # define MM_DEL KEY_DC: case PADSTOP #else # define MM_PLUS '+' # define MM_MINUS '-' # define MM_ENTER '\r': case '\n' # define MM_SLASH '/' # define MM_UP KEY_UP # define MM_DOWN KEY_DOWN # define MM_LEFT KEY_LEFT # define MM_RIGHT KEY_RIGHT # define MM_HOME KEY_HOME # define MM_END KEY_END: case KEY_LL # define MM_PPAGE KEY_PPAGE # define MM_NPAGE KEY_NPAGE # define MM_INS KEY_IC # define MM_DEL KEY_DC #endif #define MM_BACKSP KEY_BACKSPACE: case 8 #define MM_ESC 27 #define MM_F1 KEY_F(1) #define MM_F2 KEY_F(2) #ifdef USE_MOUSE # define MM_MOUSE KEY_MOUSE #endif #ifdef MM_WIDE # define MM_BOARD ((wchar_t) 0x2591) #else # define MM_BOARD (ACS_BOARD) #endif class ColorClass : public baseconfig { static chtype allcolors[]; static const char *col_names[], *col_intro[], *col_comments[]; static const chtype mapped[]; chtype colorparse(const char *); void processOne(int, const char *); const char *configLineOut(int); const char *findcol(chtype); const char *decompose(chtype); public: void Init(); }; class Win { protected: WINDOW *win; chtype *buffer, curratt; public: Win(int, int, int, chtype); Win(int, int, int, coltype); ~Win(); void init(int, int, int); void Clear(chtype); void Clear(coltype); void put(int, int, chtype); void put(int, int, char); #ifdef MM_WIDE void put(int, int, wchar_t); #endif void put(int, int, const chtype *, int = 0); int put(int, int, const char *, int = -1); int attrib(chtype); int attrib(coltype); void horizline(int); void update(); void delay_update(); void wtouch(); void wscroll(int); void cursor_on(); void cursor_off(); int keypressed(); int inkey(); void boxtitle(coltype, const char *, chtype); void clreol(int, int); #ifdef USE_MOUSE int xstart(); int ystart(); #endif }; class ShadowedWin : public Win { #ifdef USE_SHADOWS WINDOW *shadow; #endif public: ShadowedWin(int, int, int, coltype, const char * = 0, coltype = C_SBACK); ~ShadowedWin(); void touch(); int getstring(int, int, char *, int, coltype, coltype); }; class InfoWin : public ShadowedWin { Win *info; public: char *lineBuf; InfoWin(int, int, int, coltype, const char * = 0, coltype = C_SBACK, int = 3, int = 2); ~InfoWin(); void irefresh(); void touch(); void oneline(int, chtype); void iscrl(int); #ifdef USE_MOUSE int xstartinfo(); int ystartinfo(); #endif }; class ListWindow { private: bool lynxNav; //use Lynx-like navigation? int oldPos; //position at last Draw() int oldActive; //active at last Draw() int oldHigh; //location of highlight bar at last Draw() void checkPos(int); chtype setHighlight(chtype); protected: InfoWin *list; int list_max_y, list_max_x, top_offset; int position; //the first element in the window int active; //this is the highlited coltype borderCol; void Draw(); //refreshes the window void DrawOne(int, chtype); void DrawOne(int, coltype); void DrawAll(); virtual int NumOfItems() = 0; virtual void oneLine(int) = 0; virtual searchret oneSearch(int, const char *, int) = 0; virtual bool extrakeys(int) = 0; virtual void setFilter(const char *) = 0; public: ListWindow(); virtual ~ListWindow(); void Move(int); //scrolloz void setActive(int); int getActive(); searchret search(const char *, int); bool KeyHandle(int); virtual void Delete() = 0; virtual void Prev(); virtual void Next(); }; #ifdef VANITY_PLATE class Welcome { ShadowedWin *window; public: void MakeActive(); void Delete(); }; #endif class Person { public: Person *next; char *name; net_address netmail_addr; bool killed; Person(const char * = 0, const char * = 0); Person(const char *, net_address &); ~Person(); void setname(const char *); void dump(FILE *); }; class AddressBook : public ListWindow { Person head, *curr, *highlighted, **people, **living; const char *addfname; char *filter; int NumOfPersons, NumOfActive; bool NoEnter, inletter; int NumOfItems(); void oneLine(int); searchret oneSearch(int, const char *, int); bool extrakeys(int); void setFilter(const char *); void Add(const char *, net_address &); void GetAddress(); int HeaderLine(ShadowedWin &, char *, int, int, coltype); int Edit(Person &); void NewAddress(); void ChangeAddress(); void ReadFile(); void DestroyChain(); void MakeChain(); void ReChain(); void SetLetterThings(); void WriteFile(); void kill(); public: AddressBook(); ~AddressBook(); void MakeActive(bool); void Delete(); void Init(); }; class tagline { public: tagline(const char * = 0); tagline *next; char text[TAGLINE_LENGTH + 1]; bool killed; }; class TaglineWindow : public ListWindow { tagline head, *curr, *highlighted, **taglist, **tagactive; const char *tagname; char *filter; int NumOfTaglines, NumOfActive; bool nodraw, sorted; void oneLine(int); searchret oneSearch(int, const char *, int); bool extrakeys(int); void setFilter(const char *); void kill(); bool ReadFile(); void WriteFile(bool); void DestroyChain(); void MakeChain(); void RandomTagline(); public: TaglineWindow(); ~TaglineWindow(); void MakeActive(); void Delete(); void EnterTagline(const char * = 0); void EditTagline(); void Init(); int NumOfItems(); const char *getCurrent(); }; class LittleAreaListWindow : public ListWindow { int disp, areanum; int NumOfItems(); void oneLine(int); searchret oneSearch(int, const char *, int); bool extrakeys(int); void setFilter(const char *); void Select(); public: void init(); void MakeActive(); void Delete(); int getArea(); }; class PacketListWindow : public ListWindow { class oneDir { public: oneDir *parent; char *name; int position, active; oneDir(const char *, oneDir *); ~oneDir(); }; #ifdef VANITY_PLATE Welcome welcome; #endif file_list *packetList; oneDir *currDir, *origDir; #ifdef HAS_HOME char *home; #endif time_t currTime; int noDirs, noFiles; bool sorttype; int NumOfItems(); void oneLine(int); searchret oneSearch(int, const char *, int); bool extrakeys(int); void setFilter(const char *); void newList(); bool newDir(const char *); void gotoDir(); void renamePacket(); void killPacket(); void MakeActiveCore(); public: PacketListWindow(); ~PacketListWindow(); void init(); void MakeActive(); void Delete(); void Select(); pktstatus OpenPacket(); bool back(); }; class AreaListWindow : public ListWindow { char format[40]; bool hasPers, hasSys; int NumOfItems(); void oneLine(int); searchret oneSearch(int, const char *, int); bool extrakeys(int); void setFilter(const char *); public: void ResetActive(); void MakeActive(); void Delete(); void Select(); void ReDraw(); void FirstUnread(); void Prev(); void Next(); }; class LetterListWindow : public ListWindow { char format[50], *topline; int NumOfItems(); void oneLine(int); searchret oneSearch(int, const char *, int); bool extrakeys(int); void setFilter(const char *); void listSave(); void setFormat(); void MakeActiveCore(); public: LetterListWindow(); void ResetActive(); void MakeActive(); void Delete(); void Select(); void FirstUnread(); void Prev(); void Next(); }; class LetterWindow { class Line { public: Line *next; const char *text; unsigned length; lineattr attr; Line(); void out(FILE *); }; Win *headbar, *header, *text, *statbar; Line **linelist; char tagline1[TAGLINE_LENGTH + 1], *To; int letter_in_chain; //-1 = no letter in chain int position; //which row is the first in the text window int NumOfLines; int y; //height of the window, set by MakeActive int beepPers; bool rot13, hidden, lynxNav; net_address NM; time_t lasttime; void lineCount(); void oneLine(int); void Move(int); char *netAdd(char *); int HeaderLine(ShadowedWin &, char *, int, int, coltype); int EnterHeader(char *, char *, char *, bool &); void QuoteText(FILE *); void DestroyChain(); void setToFrom(char, char *, char *); void forward_header(FILE *, const char *, const char *, const char *, int, bool); void EditLetter(bool); bool SplitLetter(int = 0); long reconvert(const char *); void write_header_to_file(FILE *); void write_to_file(FILE *); void GetTagline(); bool Previous(); void NextDown(); void MakeChain(int, bool = false); void MakeChainFixPos(); void DrawFlags(); void UpdateHeader(); void DrawHeader(); void DrawBody(); void DrawStat(); bool EditOriginal(); public: LetterWindow(); ~LetterWindow(); void MakeActive(bool); void TimeUpdate(); void Delete(); bool Next(); void Draw(bool = false); void ReDraw(); bool Save(int); void EnterLetter(int, char); void StatToggle(int); net_address &PickNetAddr(); void set_Letter_Params(net_address &, const char *); void setPos(int); int getPos(); searchret search(const char *); void SplitAll(int); void KeyHandle(int); }; class HelpWindow { Win *menu; int midpos, endpos, base, items; void newHelpMenu(const char **, const char **, int); void h_packetlist(); void h_arealist(); void h_letterlist(); void h_letter(bool); public: HelpWindow(); void MakeActive(); void Delete(); void baseNext(); void baseReset(); }; class StringFile { FILE *afile; file_header *fromFile; letter_body *msgBody, *msgBodyFirst; const unsigned char *srcStr, *curpos; public: void init(file_header *); void init(letter_body *); void close(); unsigned char nextchar(); void backup(int); void reset(); bool anyleft(); }; class AnsiWindow { class AnsiLine { private: AnsiLine *prev, *next; union { chtype *text; unsigned char *atext; }; int length; chtype att; bool isasc; public: AnsiLine *getprev(); AnsiLine *getnext(bool = false); AnsiLine(AnsiLine * = 0); ~AnsiLine(); int unpack(chtype *); void pack(chtype *, size_t); void show(Win *, int); void unpacktext(char *); void remapzero(chtype newatt); }; static const int ansi_colortable[], pc_colortable[]; bool colorsused[64]; char escparm[256]; //temp copy of ESC sequence parameters const char *title; StringFile source; Win *header, *text, *statbar, *animtext; AnsiLine *head, *curr, **linelist; int position; //which row is the first in the window int NumOfLines; int x, y; //dimensions of the window int cpx, cpy, lpy; //ANSI positions int spx, spy; //stored ANSI positions int tlen; //maximum X reached int ccf, ccb, cfl, cbr, crv; //colors and attributes int oldcolorx, oldcolory; int baseline; //base for positions in non-anim mode bool anim; //animate mode? bool ansiAbort; #ifdef NCURSES_VERSION bool useAltCharset; #endif chtype *chtmp, attrib; //current attribute bool isLatin, avtparse, bsvparse, isbsv; int atparse; void oneLine(int); void lineCount(); void DrawBody(); int getparm(); void cls(); void colreset(); chtype colorcore(); void colorset(); void pc_colorset(unsigned char); void athandle(); void cpylow(); void cpyhigh(); void cpxhigh(); void cpxlow(); void escfig(); void avatar(); void posreset(); void checkpos(); void update(unsigned char); void ResetChain(); void MakeChain(); void DestroyChain(); void animate(); void statupdate(const char * = 0); void Save(); public: void Init(); void set(letter_body *, const char *, bool); void set(file_header *, const char *, bool); void MakeActive(); void Delete(); void setPos(int); int getPos(); searchret search(const char *); void KeyHandle(int); }; class Interface { #ifdef USE_SHELL Shell shell; #endif #ifdef EXTRAPATH ExtraPath extrapath; #endif PacketListWindow packets; AddressBook addresses; HelpWindow helpwindow; LittleAreaListWindow littleareas; Win *screen; ListWindow *currList; statetype state, prevstate, searchstate; const char *searchItem, *cmdpktname; file_header *goodbye; int Key, searchmode, s_oldpos, width_min, height_min; bool unsaved_reply, any_read, addrparm, commandline, abortNow, dontSetAsRead, lynxNav; #ifdef KEY_RESIZE bool resized; void sigwinch(); #endif void init_colors(); void oldstate(statetype); void helpreset(statetype); void newstate(statetype); void alive(); void screen_init(); const char *pkterrmsg(pktstatus); void newpacket(); void create_reply_packet(); void save_read(); int ansiCommon(); void searchNext(); void searchSet(); void KeyHandle(); public: ColorClass colorlist; AreaListWindow areas; LetterListWindow letters; LetterWindow letterwindow; TaglineWindow taglines; AnsiWindow ansiwindow; Interface(); ~Interface(); void init(); void main(); int WarningWindow(const char *, const char ** = 0, int = 2); int savePrompt(const char *, char *); void nonFatalError(const char *); void ReportWindow(const char *); void changestate(statetype); void redraw(); bool select(); bool back(); //returns true if we have to quit statetype active(); statetype prevactive(); void addressbook(bool = false); bool Tagwin(); int ansiLoop(letter_body *, const char *, bool); int ansiFile(file_header *, const char *, bool); void ansiList(file_header **, bool); int areaMenu(); void kill_letter(); void setUnsaved(); void setUnsavedNoAuto(); void setAnyRead(); void setKey(int); bool dontRead(); bool fromCommandLine(const char *); }; extern mmail mm; extern Interface *ui; extern time_t starttime; #ifdef USE_MOUSE extern MEVENT mm_mouse_event; void mm_mouse_get(); #endif #endif mmail-0.52/interfac/help.cc000644 000765 000024 00000015330 13065426650 016553 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * help windows Copyright 1996-1997 Kolossvary Tamas Copyright 1997-2017 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #include "interfac.h" HelpWindow::HelpWindow() { baseReset(); } void HelpWindow::newHelpMenu(const char **keys, const char **func, int it) { if (mm.resourceObject->getInt(ExpertMode)) menu = 0; else { int x, y, z, end; items = it; menu = new Win(3, COLS - 2, LINES - 4, C_HELP2); midpos = (COLS / 2) - 6; endpos = COLS - 21; if (items < 10) end = items; else { end = base + 8; if (end > items) end = items; } for (z = base; z < end; z++) { if (keys[z]) { x = (z - base) / 3; switch ((z - base) % 3) { case 0: y = 2; break; case 1: y = midpos; break; default: y = endpos; } menu->attrib(C_HELP1); menu->put(x, y, ": "); menu->put(x, y + 2, func[z]); y -= strlen(keys[z]); menu->attrib(C_HELP2); menu->put(x, y, keys[z]); } } if (items > 9) { menu->put(2, endpos - 1, "O"); menu->attrib(C_HELP1); menu->put(2, endpos, ": Other functions"); } menu->delay_update(); } } void HelpWindow::h_packetlist() { static const char *keys[] = { "Q", "Enter", "S, $", "K", "/, .", "G", "U", "R", "A", "^T", "^Z", "B", "Space, F", "^X", "T", "|, ^" }, *func[] = { "Quit", "select packet", "change Sort type", "Kill packet", "search / next", "Go to directory", "Update list", "Rename packet", "Addressbook", "Tagline editor", "command shell", "alias for PgUp", "aliases for PgDn", "eXit now", "Touch file", "filter list" }; newHelpMenu(keys, func, 16); } void HelpWindow::h_arealist() { static const char *keys[] = { "Q", "Enter", "F2, !", "E", "S, Ins", "U, Del", "L", "-", "+" }, *func[] = { "back to packet list", "select area", "Make reply packet", "Enter letter in area", "Subscribe", "Unsubscribe", "all/subscribed/active", "prev non-empty", "next non-empty" }; newHelpMenu(keys, func, 9); } void HelpWindow::h_letterlist() { static const char *keys[] = { "L", "Enter", "$", "E", "^F", "S", "U", "M", "A", "^T", "F2, !", "^E", "-", "+" }, *func[] = { "List all/unread/marked", "read letter", "change sort type", "Enter letter in area", "Forward letter", "Save (all/marked)", "Unread/read toggle", "Mark/unmark", "Addressbook", "Tagline editor", "make reply packet", "Enter from addressbook", "previous unread", "next unread" }, *repkeys[] = { "K", "Enter", "$", "E", "^F", "S", 0, "^B", "A", "^T", "F2, !" }, *repfunc[] = { "Kill letter", "read letter", "change sort type", "Edit letter", "Forward letter", "Save (all/marked)", 0, "Break into parts", "Addressbook", "Tagline editor", "Make reply packet" }; if (!mm.areaList->isReplyArea()) newHelpMenu(keys, func, 14); else newHelpMenu(repkeys, repfunc, 11); } void HelpWindow::h_letter(bool isAnsi) { enum {width = 60, citems = 18, regitems = 7, repitems = 3, ansitems = 12}; static const char *common[citems] = { "S - Save letter", "A - Addressbook", "C - toggle Character set", "D - Decrypt (rot13) toggle", "X - eXtra (hidden) lines", "I - Ignore soft CRs toggle", "^T - Tagline editor", "^F - Forward letter", "F2, ! - Make reply packet", "V, ^V, ^A - ANSI viewer", "/ - start a search", ". - repeat last search", "- - previous letter", "+, Enter - next letter", "Space - page through area", "^Z - command shell", "Q - back to letter list", "^X - eXit " MM_NAME " now" }, *regular[] = { "E - Enter new letter (post)", "R - Reply (followup)", "O - reply to Original sender", "N - Netmail/Email reply", "T - Take tagline", "M - Mark/unmark letter", "U - Unread/read toggle" }, *reply[] = { "K - Kill letter", "E, R - [Re-]Edit letter", "^B - Break reply into parts" }, *ansi[] = { "S - Save to file", "C - toggle Character set", "V, A, ^A - Animate", "/ - start a search", ". - repeat last search", "Space - page down/next", "- - previous", "+ - next", "@ - toggle at-code parsing", "^V - toggle AVATAR parsing", "^B - toggle BSAVE parsing", "Q - Quit ANSI viewer" }; int extras = isAnsi ? ansitems : mm.areaList->isReplyArea() ? repitems : regitems; int usecommon = isAnsi ? 0 : citems; int height = ((extras + usecommon + 1) >> 1) + 4; menu = new ShadowedWin(height, width, (LINES - height) >> 1, C_HELP3); menu->attrib(C_HELP4); const char **extchar = isAnsi ? ansi : ((extras == 7) ? regular : reply); int x, line = 0; for (x = 0; x < extras; x++) { if (!(x & 1)) line++; menu->put(line, (x & 1) ? (width >> 1) + 2 : 2, extchar[x]); } for (x = extras; x < usecommon + extras; x++) { if (!(x & 1)) line++; menu->put(line, (x & 1) ? (width >> 1) + 2 : 2, common[x - extras]); } menu->put(line + 2, 2, "Plus the standard direction keys"); menu->wtouch(); } void HelpWindow::MakeActive() { switch (ui->active()) { case ansi_help: h_letter(true); break; case letter_help: h_letter(false); break; case letterlist: h_letterlist(); break; case arealist: h_arealist(); break; case packetlist: h_packetlist(); default:; } } void HelpWindow::Delete() { switch (ui->active()) { case ansi_help: case letter_help: delete (ShadowedWin *) menu; break; case letterlist: case arealist: case packetlist: delete menu; default:; } } void HelpWindow::baseNext() { if (items > 9) { base += 8; if (base > (items - 1)) base = 0; } Delete(); MakeActive(); } void HelpWindow::baseReset() { base = 0; } mmail-0.52/interfac/arealist.cc000644 000765 000024 00000026041 13255076614 017432 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * area list Copyright 1996-1997 Kolossvary Tamas Copyright 1997-2018 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #include "interfac.h" // ***** LittleAreaListWindow ***** void LittleAreaListWindow::MakeActive() { position = 0; areanum = -1; mm.areaList->setMode(mm.areaList->getMode() - 1); mm.areaList->relist(); list_max_y = (NumOfItems() < LINES - 10) ? NumOfItems() : LINES - 10; list_max_x = 52; top_offset = 3; borderCol = C_LALBTEXT; list = new InfoWin(list_max_y + 4, list_max_x + 2, 3, borderCol, 0, C_SBACK, 4, top_offset); list->put(1, 2, "Reply goes to area:"); DrawAll(); } void LittleAreaListWindow::init() { int oldarea = mm.areaList->getAreaNo(); disp = 0; do mm.areaList->gotoActive(disp++); while (mm.areaList->isCollection()); disp--; mm.areaList->gotoArea(mm.letterList->getAreaID()); active = mm.areaList->getActive() - disp; // restore old area (for collection areas): mm.areaList->gotoArea(oldarea); } void LittleAreaListWindow::Select() { mm.areaList->gotoActive(active + disp); } void LittleAreaListWindow::Delete() { delete list; } int LittleAreaListWindow::getArea() { return areanum; } int LittleAreaListWindow::NumOfItems() { return mm.areaList->noOfActive() - disp; } void LittleAreaListWindow::oneLine(int i) { mm.areaList->gotoActive(position + i + disp); sprintf(list->lineBuf, "%c%-50.50s ", (!mm.areaList->isShortlist() && (mm.areaList->getType() & ACTIVE)) ? '*' : ' ', mm.areaList->getDescription()); areaconv_in(list->lineBuf); DrawOne(i, C_LALLINES); } searchret LittleAreaListWindow::oneSearch(int x, const char *item, int) { mm.areaList->gotoActive(x + disp); return mm.areaList->filterCheck(item) ? True : False; } bool LittleAreaListWindow::extrakeys(int key) { switch (key) { case MM_ENTER: Select(); if (mm.areaList->getType() & (COLLECTION | READONLY)) { areanum = -1; ui->nonFatalError("Cannot reply there"); } else areanum = mm.areaList->getAreaNo(); break; case 'L': Select(); { int x = mm.areaList->getAreaNo(); ui->areas.Select(); mm.areaList->relist(); ui->areas.ResetActive(); mm.areaList->gotoArea(x); active = mm.areaList->getActive() - disp; } ui->redraw(); } return false; } void LittleAreaListWindow::setFilter(const char *item) { mm.areaList->setFilter(item); init(); } // ***** AreaListWindow ***** void AreaListWindow::FirstUnread() { int i; mm.areaList->updatePers(); position = active = 0; for (i = 0; i < NumOfItems(); i++) { mm.areaList->gotoActive(i); if (!mm.areaList->getNoOfUnread()) Move(KEY_DOWN); else break; } if (i == NumOfItems()) { position = active = 0; for (i = 0; i < NumOfItems(); i++) { mm.areaList->gotoActive(i); if (!mm.areaList->getNoOfLetters()) Move(KEY_DOWN); else break; } } } int AreaListWindow::NumOfItems() { return mm.areaList->noOfActive(); } void AreaListWindow::oneLine(int i) { char *p = list->lineBuf; mm.areaList->gotoActive(position + i); unsigned long attrib = mm.areaList->getType(); if (position + i == active) { p += sprintf(p, "%.20s", mm.areaList->getAreaType()); if (mm.areaList->isNetmail()) p += sprintf(p, ", Netmail"); else if (mm.areaList->isInternet()) p += sprintf(p, ", Email"); else if (mm.areaList->isUsenet()) p += sprintf(p, ", Usenet"); else if (attrib & ECHOAREA) p += sprintf(p, ", Echo"); if (attrib & PERSONLY) p += sprintf(p, ", Pers"); else if (attrib & PERSALL) p += sprintf(p, ", Pers+All"); int q = ((list_max_x >> 1) - 8) - (p - list->lineBuf); while (--q > 0) sprintf(p++, " "); p = list->lineBuf; list->attrib(C_ALINFOTEXT2); list->put(list_max_y + 3 + hasSys, 8, p); list->delay_update(); } p += sprintf(p, format, ((attrib & ADDED) ? '+' : ((attrib & DROPPED) ? '-' : ((attrib & ACTIVE) && !mm.areaList->isShortlist()) ? '*' : ((attrib & HASREPLY) ? 'R' : ' '))), mm.areaList->getShortName(), mm.areaList->getDescription()); if (mm.areaList->getNoOfLetters()) p += sprintf(p, " %6d ", mm.areaList->getNoOfLetters()); else p += sprintf(p, " . "); if (mm.areaList->getNoOfUnread()) p += sprintf(p, "%6d ", mm.areaList->getNoOfUnread()); else p += sprintf(p, " . "); if (hasPers) { if (mm.areaList->getNoOfPersonal()) sprintf(p, "%6d ", mm.areaList->getNoOfPersonal()); else sprintf(p, " . "); } coltype ch = ((attrib & (REPLYAREA | ADDED | DROPPED)) || ((attrib & HASREPLY) && !(attrib & ACTIVE))) ? C_ALREPLINE : C_ALPACKETLINE; areaconv_in(list->lineBuf); DrawOne(i, mm.areaList->getNoOfUnread() ? emph(ch) : noemph(ch)); } searchret AreaListWindow::oneSearch(int x, const char *item, int mode) { searchret retval; mm.areaList->gotoActive(x); retval = mm.areaList->filterCheck(item) ? True : False; if (!retval && (mode < s_arealist) && mm.areaList->getNoOfLetters()) { int oldactive = active; ResetActive(); mm.areaList->getLetterList(); mm.letterList->setMode(-1); mm.letterList->relist(); ui->changestate(letterlist); ui->letters.setActive(-1); retval = ui->letters.search(item, mode); if (retval != True) { active = oldactive; ui->changestate(arealist); delete mm.letterList; } } return retval; } void AreaListWindow::Select() { mm.areaList->gotoActive(active); } void AreaListWindow::ResetActive() { active = mm.areaList->getActive(); } void AreaListWindow::MakeActive() { static const char *almodes[] = {"All", "Subscribed", "Active"}; unsigned int padding, middle; char tmp[80], tpad[7]; hasPers = mm.packet->hasPersonal(); mm.areaList->updatePers(); mm.areaList->setMode(mm.areaList->getMode() - 1); mm.areaList->relist(); const char *bb = mm.packet->getBBSName(); const char *sy = mm.packet->getSysOpName(); const char *bp = mm.packet->getBBSProg(); const char *dp = mm.packet->getDoorProg(); hasSys = bb && *bb; bool hasProg = (bp && *bp) || (dp && *dp); list_max_y = LINES - (mm.resourceObject->getInt(ExpertMode) ? 11 : 15) + !hasSys + !hasProg; list_max_x = COLS - 6; top_offset = 2; const char *filter = mm.areaList->getFilter(); char *p = tmp + sprintf(tmp, "%.20s | %s Areas", mm.resourceObject->get(PacketName), almodes[mm.areaList->getMode()]); if (NumOfItems() > list_max_y) p += sprintf(p, " (%d)", NumOfItems()); if (filter) sprintf(p, " | %.20s", filter); charconv_in(tmp); borderCol = C_ALBORDER; list = new InfoWin(list_max_y + 5 + hasSys + hasProg, list_max_x + 2, 2, borderCol, tmp, C_ALBTEXT, 5 + hasSys + hasProg); list->attrib(C_ALHEADTEXT); list->put(1, 3, "Area# Description"); int newloc = list_max_x - 14; if (hasPers) newloc -= 8; list->put(1, newloc, "Total Unread"); if (hasPers) list->put(1, list_max_x - 5, "Pers"); list->horizline(list_max_y + 2); padding = list_max_x - 26; if (hasPers) padding -= 8; sprintf(format, "%%c%%6s %%-%d.%ds", padding, padding); middle = (list_max_x - 2) >> 1; padding = list_max_x - 8; list->attrib(C_ALINFOTEXT); if (hasSys) list->put(list_max_y + 3, 2, "Name:"); if (sy && *sy) list->put(list_max_y + 3 + hasSys, middle, " Sysop:"); list->put(list_max_y + 3 + hasSys, 2, "Type:"); if (dp && *dp) list->put(list_max_y + 4 + hasSys, 2, "Door:"); if (bp && *bp) list->put(list_max_y + 4 + hasSys, middle, " BBS:"); sprintf(tpad, "%%.%ds", (middle < 87) ? middle - 8 : 79); middle += 8; list->attrib(C_ALINFOTEXT2); if (hasSys) { p = list->lineBuf; sprintf(p, "%-*.*s", padding, padding, bb); charconv_in(p); list->put(list_max_y + 3, 8, p); } if (sy && *sy) { sprintf(tmp, tpad, sy); charconv_in(tmp); list->put(list_max_y + 3 + hasSys, middle, tmp); } if (dp && *dp) { sprintf(tmp, tpad, dp); charconv_in(tmp); list->put(list_max_y + 4 + hasSys, 8, tmp); } if (bp && *bp) { sprintf(tmp, tpad, bp); charconv_in(tmp); list->put(list_max_y + 4 + hasSys, middle, tmp); } DrawAll(); Select(); } void AreaListWindow::Delete() { delete list; } void AreaListWindow::Prev() { do { Move(KEY_UP); Select(); } while (!mm.areaList->getNoOfLetters() && (mm.areaList->getActive() > 0)); } void AreaListWindow::Next() { do { Move(KEY_DOWN); Select(); } while (!mm.areaList->getNoOfLetters() && (mm.areaList->getActive() < mm.areaList->noOfActive() - 1)); } bool AreaListWindow::extrakeys(int key) { bool end = false; Select(); switch (key) { case 5: case 'E': if (!(mm.areaList->getType() & (COLLECTION | READONLY))) { if ((5 == key) || mm.areaList->isEmail()) { ui->addressbook(); Select(); } ui->letterwindow.EnterLetter(mm.areaList->getAreaNo(), 'E'); } else ui->nonFatalError("Cannot reply there"); break; #ifdef USE_MOUSE case MM_MOUSE: { int begx = list->xstart(), begy = list->ystart(); if ((mm_mouse_event.y != begy) || ((mm_mouse_event.x < (begx + 13)) || (mm_mouse_event.x > (begx + 40)))) break; } #endif case 'L': mm.areaList->relist(); ResetActive(); ui->redraw(); break; case 'S': case MM_INS: case 'U': case MM_DEL: if (mm.areaList->hasOffConfig()) { switch (key) { case 'S': case MM_INS: mm.areaList->Add(); break; default: mm.areaList->Drop(); } ui->setUnsavedNoAuto(); Move(KEY_DOWN); Draw(); } else ui->nonFatalError("Offline config is unavailable"); } return end; } void AreaListWindow::setFilter(const char *item) { mm.areaList->setFilter(item); } mmail-0.52/interfac/letterl.cc000644 000765 000024 00000016631 13255077061 017302 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * message list Copyright 1996 Kolossvary Tamas Copyright 1997 John Zero Copyright 1997-2018 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #include "interfac.h" LetterListWindow::LetterListWindow() { lsorttype = mm.resourceObject->getInt(LetterSort); } void LetterListWindow::listSave() { static const char *saveopts[] = { "Marked", "All", "This one", "Quit" }; int marked = !(!mm.areaList->getNoOfMarked()); int status = ui->WarningWindow("Save which?", saveopts + !marked, 3 + marked); if (status) { bool saveok = ui->letterwindow.Save(status); if ((status == 1) && saveok) Move(KEY_DOWN); } } void LetterListWindow::Next() { do { Move(KEY_DOWN); mm.letterList->gotoActive(active); } while (mm.letterList->getRead() && ((active + 1) < NumOfItems())); } void LetterListWindow::FirstUnread() { position = 0; active = 0; } void LetterListWindow::Prev() { do { Move(KEY_UP); mm.letterList->gotoActive(active); } while (mm.letterList->getRead() && (active > 0)); } int LetterListWindow::NumOfItems() { return mm.letterList->noOfActive(); } void LetterListWindow::oneLine(int i) { mm.letterList->gotoActive(position + i); int st = mm.letterList->getStatus(); char *p = list->lineBuf; p += sprintf(p, format, (st & MS_MARKED) ? 'M' : (st & MS_SAVED) ? 's' : ' ', (st & MS_REPLIED) ? '~' : ' ', (st & MS_READ) ? '*' : ' ', mm.letterList->getMsgNum(), mm.letterList->getFrom(), mm.letterList->getTo(), stripre(mm.letterList->getSubject())); if (mm.areaList->isCollection()) { const char *origArea = mm.letterList->getNewsgrps(); if (!origArea) origArea = mm.areaList->getDescription(mm.letterList->getAreaID()); if (origArea) sprintf(p - 15, " %-13.13s ", origArea); } coltype linecol = mm.letterList->isPersonal() ? C_LLPERSONAL : C_LISTWIN; letterconv_in(list->lineBuf); DrawOne(i, (st & MS_READ) ? noemph(linecol) : emph(linecol)); } searchret LetterListWindow::oneSearch(int x, const char *item, int mode) { searchret retval; mm.letterList->gotoActive(x); retval = mm.letterList->filterCheck(item) ? True : False; if (!retval && (mode == s_fulltext)) { ui->changestate(letter); ui->letterwindow.setPos(-1); retval = ui->letterwindow.search(item); if (retval != True) ui->changestate(letterlist); } return retval; } void LetterListWindow::setFormat() { char topformat[50]; unsigned char tot, maxFromLen, maxToLen, maxSubjLen; tot = COLS - 19; maxSubjLen = tot / 2; tot -= maxSubjLen; maxToLen = tot / 2; maxFromLen = tot - maxToLen; if (!mm.areaList->hasTo() || (mm.areaList->isCollection() && !mm.areaList->isReplyArea())) { maxSubjLen += maxToLen; maxToLen = 0; } if (mm.areaList->isReplyArea()) { maxSubjLen += maxFromLen; maxFromLen = 0; } sprintf(format, "%%c%%c%%c%%6ld %%-%d.%ds %%-%d.%ds %%-%d.%ds", maxFromLen, maxFromLen, maxToLen, maxToLen, maxSubjLen, maxSubjLen); sprintf(topformat, " Msg#%s", format + 10); sprintf(topline, topformat, "From", "To", "Subject"); } void LetterListWindow::MakeActiveCore() { static const char *llmodes[] = {"All", "Unread", "Marked"}, *llsorts[] = {"subject", "number", "from", "to"}; int maxbott = LINES - (mm.resourceObject->getInt(ExpertMode) ? 7 : 11); list_max_y = (NumOfItems() < maxbott) ? NumOfItems() : maxbott; bool too_many = (NumOfItems() > list_max_y); const char *modestr = llmodes[mm.letterList->getMode()]; const char *sortstr = llsorts[lsorttype]; const char *pn = mm.resourceObject->get(PacketName); const char *filter = mm.letterList->getFilter(); int pnlen = strlen(pn); if (pnlen > 20) pnlen = 20; int offset = strlen(modestr) + pnlen + 3; int flen = filter ? strlen(filter) + 3 : 0; if (flen > 20) flen = 20; int nwidth = COLS - (too_many ? 27 : 19) - offset - flen - strlen(sortstr); char *title = new char[COLS + 1]; char *end = title + sprintf(title, "%.*s | %s in %.*s", pnlen, pn, modestr, nwidth, mm.areaList->getDescription()); char *newend = end + sprintf(end, ", by %s", sortstr); if (too_many) newend += sprintf(newend, " (%d)", NumOfItems()); if (flen) sprintf(newend, " | %.*s", flen - 3, filter); areaconv_in(title); borderCol = C_LLBBORD; list = new InfoWin(list_max_y + 3, list_max_x + 2, 2, borderCol, title, C_LLTOPTEXT1); list->attrib(C_LLTOPTEXT2); *end = '\0'; offset += 3; list->put(0, offset + 3, title + offset); delete[] title; list->attrib(C_LLHEAD); list->put(1, 3, topline); if (mm.areaList->isCollection()) list->put(1, COLS - 19, "Area"); list->touch(); DrawAll(); Select(); } void LetterListWindow::MakeActive() { top_offset = 2; list_max_x = COLS - 6; topline = new char[COLS + 1]; setFormat(); ui->areas.Select(); MakeActiveCore(); } void LetterListWindow::Select() { mm.letterList->gotoActive(active); } void LetterListWindow::ResetActive() { active = mm.letterList->getActive(); } void LetterListWindow::Delete() { delete list; delete[] topline; } bool LetterListWindow::extrakeys(int key) { Select(); switch (key) { #ifdef USE_MOUSE case MM_MOUSE: { int begx = list->xstart(), begy = list->ystart(); if (mm_mouse_event.y == begy) { if ((mm_mouse_event.x > (begx + 12)) && (mm_mouse_event.x < (begx + 27))) extrakeys('L'); else if ((mm_mouse_event.x > (begx + 27)) && (mm_mouse_event.x < (begx + list_max_x))) extrakeys('$'); } } break; #endif case 'U': case 'M': // Toggle read/unread and marked from letterlist mm.letterList->setStatus(mm.letterList->getStatus() ^ ((key == 'U') ? MS_READ : MS_MARKED)); ui->setAnyRead(); Move(KEY_DOWN); Draw(); break; case 5: case 'E': if (mm.areaList->isReplyArea()) ui->letterwindow.KeyHandle('E'); else if (!(mm.areaList->getType() & (COLLECTION | READONLY))) { if ((5 == key) || mm.areaList->isEmail()) ui->addressbook(); ui->letterwindow.EnterLetter(mm.areaList->getAreaNo(), 'E'); } else ui->nonFatalError("Cannot reply there"); break; case 2: case 6: case MM_DEL: case 'K': if (mm.areaList->isReplyArea()) ui->letterwindow.KeyHandle(key); break; case 'L': mm.letterList->relist(); ResetActive(); ui->redraw(); break; case '$': mm.letterList->resort(); ui->redraw(); break; case 'S': listSave(); ui->redraw(); } return false; } void LetterListWindow::setFilter(const char *item) { mm.letterList->setFilter(item); } mmail-0.52/interfac/interfac.cc000644 000765 000024 00000060202 13250315724 017407 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * Interface, ShadowedWin, ListWindow Copyright 1996 Kolossvary Tamas Copyright 1997 John Zero Copyright 1997-2017 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #include "error.h" #include "interfac.h" Interface::Interface() { isoConsole = mm.resourceObject->getInt(Charset); lynxNav = mm.resourceObject->getInt(UseLynxNav); searchItem = 0; goodbye = 0; #ifdef KEY_RESIZE resized = false; # if defined(SIGWINCH) && !defined(PDCURSES) && !defined(NCURSES_SIGWINCH) signal(SIGWINCH, sigwinchHandler); # endif #endif commandline = abortNow = dontSetAsRead = false; unsaved_reply = any_read = false; state = nostate; width_min = MINWIDTH; height_min = mm.resourceObject->getInt(ExpertMode) ? MINHIEXPERT : MINHINORM; } void Interface::init() { colorlist.Init(); taglines.Init(); addresses.Init(); alive(); screen_init(); ansiwindow.Init(); } void Interface::main() { if (commandline) { pktstatus pktret = mm.selectPacket(cmdpktname); if (pktret == PKT_OK) newpacket(); else fatalError(pkterrmsg(pktret)); } else { packets.init(); newstate(packetlist); } KeyHandle(); } Interface::~Interface() { delete screen; touchwin(stdscr); refresh(); leaveok(stdscr, FALSE); echo(); endwin(); } void Interface::init_colors() { #if defined(NCURSES_VERSION) || defined(PDCURSES) bool trans = mm.resourceObject->getInt(Transparency); int bkcol = PAIR_NUMBER(ColorArray[C_SBACK]) & 7; #endif for (int back = COLOR_BLACK; back <= (COLOR_WHITE); back++) for (int fore = COLOR_BLACK; fore <= (COLOR_WHITE); fore++) init_pair((fore << 3) + back, fore, #if defined(NCURSES_VERSION) || defined(PDCURSES) (trans && (back == bkcol)) ? -1 : #endif back); // Color pair 0 cannot be used (argh!), so swap: init_pair(((COLOR_WHITE) << 3) + (COLOR_WHITE), COLOR_BLACK, COLOR_BLACK); // Of course, now I can't do white-on-white (grey). :-( } void Interface::alive() { initscr(); refresh(); if (mm.resourceObject->getInt(UseColors)) start_color(); #if defined(NCURSES_VERSION) || defined(PDCURSES) use_default_colors(); #endif init_colors(); cbreak(); noecho(); nonl(); #ifdef USE_MOUSE if (mm.resourceObject->getInt(Mouse)) mousemask(BUTTON1_CLICKED | BUTTON1_DOUBLE_CLICKED | BUTTON3_CLICKED, 0); #endif } void Interface::screen_init() { char tmp[80]; // Make a new background window, fill it with MM_BOARD characters: screen = new Win(LINES, COLS, 0, C_SBACK); if (mm.resourceObject->getInt(BackFill)) { for (int y = 1; y < (LINES - 1); y++) for (int x = 1; x < (COLS - 1); x++) screen->put(y, x, MM_BOARD); } if ((COLS < width_min) || (LINES < height_min)) { sprintf(tmp, "A screen at least %dx%d is required", width_min, height_min); fatalError(tmp); } // Border and title: #if defined(PDCURSES) PDC_set_title(MM_NAME); #endif sprintf(tmp, MM_TOPHEADER, sysname()); screen->boxtitle(C_SBORDER, tmp, emph(C_SBACK)); // Help window area: if (!mm.resourceObject->getInt(ExpertMode)) { screen->attrib(C_SSEPBOTT); screen->horizline(LINES - 5); } screen->delay_update(); } const char *Interface::pkterrmsg(pktstatus pktret) { return (pktret == PKT_UNFOUND) ? "Could not open packet" : (pktret == PKT_NOFILES) ? "No files uncompresed - check archiver config" : (pktret == UNCOMP_FAIL) ? "Could not uncompress packet" : "Packet type not recognized"; } int Interface::WarningWindow(const char *warning, const char **selectors, int items) { static const char *yesno[] = { "Yes", "No" }; const char **p; int x, z, width, itemlen, c, curitem, def_val = 0; bool result = false; if (!selectors) selectors = yesno; // default options // Calculate the window width and maximum item width: for (p = selectors, itemlen = 0, curitem = 0; curitem < items; curitem++, p++) { z = strlen(*p); if (z > itemlen) itemlen = z; } itemlen += 2; x = itemlen * items + 3; z = strlen(warning); if ((z + 4) > x) { x = z + 4; itemlen = z / items; } width = x; ShadowedWin warn(7, width, (LINES >> 1) - 4, C_WTEXT); warn.put(2, (x - z) >> 1, warning); // Display each item: for (p = selectors, curitem = 0; curitem < items; curitem++) { x = curitem * itemlen + ((itemlen - strlen(*p) + 5) >> 1); warn.attrib(C_WTEXT); warn.put(4, x + 1, *p + 1); warn.attrib(C_WTEXTHI); warn.put(4, x, **p++); } // Main loop -- highlight selected item and process keystrokes: while (!result) { for (p = selectors, curitem = 0; curitem < items; curitem++, p++) { z = strlen(*p); x = curitem * itemlen + ((itemlen - z + 5) >> 1); warn.put(4, x - 1, (def_val == curitem) ? '[' : ' '); warn.put(4, x + z, (def_val == curitem) ? ']' : ' '); } warn.cursor_on(); // For SIGWINCH warn.update(); warn.cursor_off(); do c = warn.inkey(); while (ERR == c); for (p = selectors, curitem = 0; (curitem < items) && !result; curitem++, p++) if ((c == tolower(**p)) || (c == toupper(**p))) { def_val = curitem; result = true; } if (!result) switch (c) { #ifdef USE_MOUSE case MM_MOUSE: mm_mouse_get(); if ((LINES >> 1) == mm_mouse_event.y) for (p = selectors, curitem = 0; (curitem < items) && !result; curitem++, p++) { z = strlen(*p); x = curitem * itemlen + ((itemlen - z + 5) >> 1) + ((COLS - width) / 2); if ((x <= mm_mouse_event.x) && (mm_mouse_event.x <= (x + z))) { def_val = curitem; result = true; } } break; #endif case MM_LEFT: if (!def_val) def_val = items; def_val--; break; case MM_RIGHT: case '\t': if (++def_val == items) def_val = 0; break; case MM_ENTER: result = true; } } return (items - 1) - def_val; // the order is inverted } int Interface::savePrompt(const char *prompt1, char *filename) { ShadowedWin question(5, 60, (LINES >> 1) - 3, C_LLSAVEBORD); question.attrib(C_LLSAVETEXT); question.put(1, 2, prompt1); question.put(2, 2, "<"); question.put(2, 57, ">"); question.put(3, 38, " to cancel"); return question.getstring(2, 3, filename, 54, C_LLSAVETEXT, C_LLSAVEGET); } void Interface::nonFatalError(const char *warn) { static const char *ok[] = { "OK" }; WarningWindow(warn, ok, 1); redraw(); } void Interface::ReportWindow(const char *message) { ShadowedWin rwin(3, strlen(message) + 4, (LINES >> 1) - 2, C_WTEXT); rwin.put(1, 2, message); rwin.update(); } statetype Interface::active() { return state; } statetype Interface::prevactive() { return prevstate; } void Interface::oldstate(statetype n) { if (n != nostate) helpwindow.Delete(); switch (n) { case packetlist: packets.Delete(); break; case arealist: areas.Delete(); break; case letterlist: letters.Delete(); break; case littlearealist: areas.Select(); case address: case tagwin: currList->Delete(); oldstate(prevstate); break; case letter_help: case letter: letterwindow.Delete(); break; case ansi_help: case ansiwin: ansiwindow.Delete(); default:; } } void Interface::helpreset(statetype oldst) { screen->wtouch(); if ((oldst != state) && (prevstate != state)) helpwindow.baseReset(); } void Interface::newstate(statetype n) { statetype oldst = state; state = n; switch (n) { case packetlist: helpreset(oldst); currList = &packets; packets.MakeActive(); break; case arealist: helpreset(oldst); currList = &areas; areas.MakeActive(); break; case letterlist: helpreset(oldst); currList = &letters; letters.MakeActive(); break; case letter: letterwindow.MakeActive(oldst == letterlist); break; case letter_help: letterwindow.MakeActive(false); break; case littlearealist: newstate(prevstate); state = littlearealist; currList = &littleareas; littleareas.MakeActive(); break; case address: newstate(prevstate); state = address; currList = &addresses; addresses.MakeActive(addrparm); break; case tagwin: newstate(prevstate); state = tagwin; currList = &taglines; taglines.MakeActive(); break; case ansi_help: case ansiwin: ansiwindow.MakeActive(); default:; } helpwindow.MakeActive(); } void Interface::changestate(statetype n) { oldstate(state); newstate(n); } void Interface::redraw() { changestate(state); } void Interface::newpacket() { file_header *hello, **bulletins; static const char *keepers[] = {"Save", "Kill"}; unsaved_reply = any_read = false; if (mm.checkForReplies()) { if (!WarningWindow("Existing replies found:", keepers)) mm.deleteReplies(); else { redraw(); ReportWindow("Opening replies..."); } } mm.openReply(); mm.areaList = new area_list(&mm); int noOfReplies = mm.areaList->getRepList(); mm.driverList->initRead(); if (mm.getOffConfig() || noOfReplies) { mm.areaList->setMode(0); mm.areaList->relist(); } bool latin = mm.packet->isLatin(); hello = mm.packet->getHello(); goodbye = mm.packet->getGoodbye(); bulletins = mm.packet->getBulletins(); if (hello) ansiFile(hello, hello->getName(), latin); if (!abortNow) { areas.FirstUnread(); changestate(arealist); } if (!abortNow && bulletins) { if (WarningWindow("View bulletins / new files?")) ansiList(bulletins, latin); else redraw(); } } bool Interface::select() { pktstatus pktret; switch (state) { case packetlist: ReportWindow("Opening..."); pktret = packets.OpenPacket(); if (pktret == PKT_OK) newpacket(); else if (pktret != NEW_DIR) nonFatalError(pkterrmsg(pktret)); break; case arealist: areas.Select(); if (mm.areaList->getNoOfLetters() > 0) { ReportWindow("Opening..."); mm.areaList->getLetterList(); letters.FirstUnread(); changestate(letterlist); } break; case letterlist: letters.Select(); changestate(letter); break; case letter: letterwindow.Next(); break; case littlearealist: case address: currList->KeyHandle('\n'); case tagwin: Key = '\n'; case ansiwin: case ansi_help: case letter_help: return back(); default:; } return false; } bool Interface::back() { switch (state) { case packetlist: switch (Key) { #ifdef USE_MOUSE case MM_MOUSE: #endif case MM_LEFT: if (!packets.back()) return false; } if (abortNow || WarningWindow("Do you really want to quit?")) { oldstate(state); return true; } else redraw(); break; case arealist: if (any_read) save_read(); if (unsaved_reply) if (mm.resourceObject->getInt(AutoSaveReplies) || WarningWindow("The REPLY area has changed. Save changes?")) { redraw(); create_reply_packet(); } if (!abortNow && goodbye) { ansiFile(goodbye, goodbye->getName(), mm.packet->isLatin()); goodbye = 0; } mm.Delete(); if (abortNow || commandline) { oldstate(state); return true; } else changestate(packetlist); break; case letterlist: delete mm.letterList; case letter: case letter_help: case ansi_help: changestate((statetype)(state - 1)); if (abortNow) return back(); break; case littlearealist: case address: case tagwin: case ansiwin: changestate(prevstate); return (abortNow && (state != arealist)) ? back() : true; default:; } return false; } #ifdef KEY_RESIZE void Interface::sigwinch() { oldstate(state); delete screen; # ifdef PDCURSES resize_term(0, 0); # else # if defined(SIGWINCH) && !defined(NCURSES_SIGWINCH) endwin(); initscr(); refresh(); # endif # endif screen_init(); newstate(state); resized = false; } #endif void Interface::kill_letter() { if (WarningWindow("Are you sure you want to delete this letter?")) { redraw(); mm.areaList->killLetter(mm.letterList->getAreaID(), mm.letterList->getMsgNum()); setUnsaved(); changestate(letterlist); if (!mm.areaList->getNoOfLetters()) back(); } else redraw(); } void Interface::create_reply_packet() { static int lines = mm.resourceObject->getInt(MaxLines); if (lines) letterwindow.SplitAll(lines); ReportWindow("Saving replies..."); bool result = mm.makeReply(); if (result) unsaved_reply = false; else nonFatalError("Warning: Unable to create reply packet!"); } void Interface::save_read() { ReportWindow("Saving last read..."); if (mm.saveRead()) any_read = false; else { redraw(); nonFatalError("Could not save last read"); } } void Interface::addressbook(bool NoEnter) { prevstate = state; addrparm = NoEnter; changestate(address); KeyHandle(); } bool Interface::Tagwin() { prevstate = state; changestate(tagwin); KeyHandle(); switch (Key) { case MM_ENTER: return true; } return false; } int Interface::ansiLoop(letter_body *source, const char *title, bool latin) { ansiwindow.set(source, title, latin); return ansiCommon(); } int Interface::ansiFile(file_header *f, const char *title, bool latin) { ansiwindow.set(f, title, latin); return ansiCommon(); } void Interface::ansiList(file_header **base, bool latin) { file_header **a = base; while (a && *a) { switch (ansiFile(*a, (*a)->getName(), latin)) { case 1: a++; break; case -1: if (a != base) a--; break; default: a = 0; } } } int Interface::ansiCommon() { prevstate = state; changestate(ansiwin); KeyHandle(); switch (Key) { case MM_LEFT: if (lynxNav) return 0; case MM_MINUS: return -1; case MM_RIGHT: case MM_ENTER: case MM_PLUS: return 1; } return 0; } int Interface::areaMenu() { prevstate = state; littleareas.init(); changestate(littlearealist); KeyHandle(); return littleareas.getArea(); } void Interface::setUnsaved() { unsaved_reply = true; if (mm.resourceObject->getInt(AutoSaveReplies)) create_reply_packet(); } void Interface::setUnsavedNoAuto() { unsaved_reply = true; } void Interface::setAnyRead() { any_read = true; } bool Interface::dontRead() { return dontSetAsRead; } void Interface::searchNext() { if (searchItem) { // We should only continue if the search was started in an // appropriate state with respect to the current state. bool stateok; switch (state) { case letter: case letterlist: case arealist: stateok = ((int) state >= (int) searchstate); break; default: stateok = (state == searchstate); } if (stateok) { searchret result; dontSetAsRead = true; bool restorepos = (s_oldpos == -1); ReportWindow("Searching (ESC to abort)..."); switch (state) { case letter: if (restorepos) { s_oldpos = letterwindow.getPos(); letterwindow.setPos(-1); } result = letterwindow.search(searchItem); if ((result != True) && restorepos) letterwindow.setPos(s_oldpos); break; case ansiwin: if (restorepos) { s_oldpos = ansiwindow.getPos(); ansiwindow.setPos(-1); } result = ansiwindow.search(searchItem); if ((result != True) && restorepos) ansiwindow.setPos(s_oldpos); break; default: if (restorepos) { s_oldpos = currList->getActive(); currList->setActive(-1); } result = currList->search(searchItem, searchmode); if ((result != True) && restorepos) currList->setActive(s_oldpos); } dontSetAsRead = false; if (result == False) if (state == searchstate) { redraw(); nonFatalError("No more matches"); } else { if (state == letterlist) delete mm.letterList; else if (state == arealist) mm.Delete(); changestate((statetype)((int) state - 1)); searchNext(); } else if (result == Abort) { if (state == searchstate) redraw(); else while (state != searchstate) changestate((statetype)((int) state - 1)); nonFatalError("Search aborted"); } else redraw(); } } } void Interface::searchSet() { static const char *searchopts[] = { "Pkt list", "Area list", "Headers", "Full text", "Quit" }; static char item[80]; bool inPackets = (state == packetlist); bool hasAreas = (state == arealist) || inPackets; if (hasAreas || (state == letterlist)) { searchmode = WarningWindow("Search which?", searchopts + !hasAreas + !inPackets, 3 + hasAreas + inPackets); if (!searchmode) { searchItem = 0; return; } } if (!savePrompt("Search for:", item)) *item = '\0'; searchItem = *item ? item : 0; searchstate = state; s_oldpos = -1; } void Interface::setKey(int newkey) { ungetch(newkey); } bool Interface::fromCommandLine(const char *pktname) { mychdir(error.getOrigDir()); commandline = mychdir(pktname); if (!commandline) { char *cdir = mygetcwd(); mm.resourceObject->set_noalloc(PacketDir, cdir); main(); abortNow = true; } else { char *s, *t = strpbrk((char *) pktname, "/\\"); if (!t) { s = new char[strlen(pktname) + 3]; sprintf(s, "./%s", pktname); } else s = strdupplus(pktname); cmdpktname = s; main(); delete[] s; state = nostate; } return commandline; } void Interface::KeyHandle() // Main loop { bool end = false; while (!(end || abortNow)) { #ifdef KEY_RESIZE if (resized) sigwinch(); #endif doupdate(); Key = screen->inkey(); #ifdef KEY_RESIZE resized = (KEY_RESIZE == Key); #endif if (((state == letter_help) || (state == ansi_help)) #ifdef KEY_RESIZE && !resized #endif ) { if (ERR != Key) { abortNow = (Key == 24); back(); } } else { if ((Key >= 'a') && (Key <= 'z')) Key = toupper(Key); switch (Key) { case 24: // ^X abortNow = true; case 'Q': case MM_ESC: case MM_BACKSP: end = back(); break; case MM_ENTER: end = select(); break; #ifdef USE_SHELL case 26: // ^Z shell.out(); break; #endif case MM_SLASH: searchSet(); redraw(); case '.': case '>': searchNext(); break; case 'A': switch (state) { case packetlist: case arealist: case letterlist: case letter: addressbook(true); break; case address: case tagwin: currList->KeyHandle(Key); break; case ansiwin: ansiwindow.KeyHandle(Key); default:; } break; case 20: // ^T switch (state) { case packetlist: case arealist: case letterlist: case letter: Tagwin(); default:; } break; case 'C': isoConsole = !isoConsole; redraw(); break; case 'O': switch (state) { case packetlist: case arealist: case letterlist: helpwindow.baseNext(); break; case letter: letterwindow.KeyHandle(Key); default:; } break; case '!': case MM_F2: switch (state) { case arealist: case letterlist: case letter: if (!mm.checkForReplies() || WarningWindow( "This will overwrite the existing reply packet. Continue?")) { redraw(); create_reply_packet(); } redraw(); default:; } break; #ifdef USE_MOUSE case MM_MOUSE: mm_mouse_get(); if (mm_mouse_event.bstate & BUTTON3_CLICKED) { end = back(); break; } #endif default: switch (state) { case letter: letterwindow.KeyHandle(Key); break; case ansiwin: switch (Key) { case MM_RIGHT: case MM_LEFT: case MM_PLUS: case MM_MINUS: end = back(); break; default: ansiwindow.KeyHandle(Key); } break; default: end = currList->KeyHandle(Key); } } } } } mmail-0.52/interfac/ansiview.cc000644 000765 000024 00000072244 13432012334 017443 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * ANSI image/text viewer Copyright 1998-2019 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #include "interfac.h" //------------------ // AnsiLine methods //------------------ AnsiWindow::AnsiLine::AnsiLine(AnsiLine *parent) : prev(parent) { next = 0; text = 0; length = 0; isasc = false; } AnsiWindow::AnsiLine::~AnsiLine() { if (isasc) delete[] atext; else delete[] text; } AnsiWindow::AnsiLine *AnsiWindow::AnsiLine::getnext(bool isnew) { if (!next) if (isnew) next = new AnsiLine(this); return next; } AnsiWindow::AnsiLine *AnsiWindow::AnsiLine::getprev() { return prev; } int AnsiWindow::AnsiLine::unpack(chtype *tmp) { int i; if (isasc) for (i = 0; i < length; i++) tmp[i] = att | atext[i]; else if (length) memcpy(tmp, text, length * sizeof(chtype)); for (i = length; i < COLS; i++) tmp[i] = ' ' | (C_ANSIBACK); return length; } void AnsiWindow::AnsiLine::pack(chtype *tmp, size_t newlen) { size_t i; if (isasc) delete[] atext; else delete[] text; isasc = true; att = newlen ? (*tmp & ~(A_CHARTEXT)) : C_ANSIBACK; for (i = 0; (i < newlen) && isasc; i++) if ((tmp[i] & ~(A_CHARTEXT)) != att) isasc = false; if (isasc) { atext = newlen ? new unsigned char[newlen] : 0; for (i = 0; i < newlen; i++) atext[i] = tmp[i] & (A_CHARTEXT); } else { text = newlen ? new chtype[newlen] : 0; if (newlen) memcpy(text, tmp, newlen * sizeof(chtype)); } length = newlen; } void AnsiWindow::AnsiLine::show(Win *win, int i) { if (length) { if (isasc) { win->attrib(att); win->put(i, 0, (char *) atext, length); } else { win->attrib(0); win->put(i, 0, text, length); } } win->attrib(C_ANSIBACK); win->clreol(i, length); } void AnsiWindow::AnsiLine::unpacktext(char *tmp) { if (isasc) { if (length) memcpy((unsigned char *) tmp, atext, length); tmp += length; } else for (int i = 0; i < length; i++) *tmp++ = (text[i] & (A_CHARTEXT)); *tmp = '\0'; } void AnsiWindow::AnsiLine::remapzero(chtype newatt) { if (isasc) { if (!PAIR_NUMBER(att & (A_COLOR))) { att &= ~(A_COLOR); att |= newatt; } } else for (int i = 0; i < length; i++) if (!PAIR_NUMBER(text[i] & (A_COLOR))) { text[i] &= ~(A_COLOR); text[i] |= newatt; } } //-------------------- // StringFile methods //-------------------- void StringFile::init(file_header *fromFileA) { fromFile = fromFileA; srcStr = 0; afile = 0; reset(); } void StringFile::init(letter_body *msgBodyA) { fromFile = 0; msgBody = msgBodyFirst = msgBodyA; reset(); } void StringFile::close() { if (fromFile) { fclose(afile); afile = 0; } } unsigned char StringFile::nextchar() { int result; if (fromFile) { result = fgetc(afile); if (EOF == result) result = 0; } else { result = *curpos++; if (!result && msgBody) { msgBody = msgBody->next; if (msgBody) { srcStr = (const unsigned char *) msgBody->getText(); curpos = srcStr; result = *curpos++; } } } return result; } void StringFile::backup(int c) { if (fromFile) ungetc(c, afile); else if (curpos > srcStr) curpos--; } void StringFile::reset() { if (fromFile) { if (!afile) { afile = mm.workList->ftryopen(fromFile->getName()); if (!afile) fatalError("Error opening ANSI file"); } fseek(afile, 0, SEEK_SET); } else { if (msgBodyFirst) { msgBody = msgBodyFirst; srcStr = (const unsigned char *) msgBody->getText(); } curpos = srcStr; } } bool StringFile::anyleft() { return !(fromFile ? feof(afile) : (!*curpos && (!msgBody || !msgBody->next))); } //-------------------- // AnsiWindow methods //-------------------- const int AnsiWindow::ansi_colortable[8] = {COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_YELLOW, COLOR_BLUE, COLOR_MAGENTA, COLOR_CYAN, COLOR_WHITE}; const int AnsiWindow::pc_colortable[8] = {COLOR_BLACK, COLOR_BLUE, COLOR_GREEN, COLOR_CYAN, COLOR_RED, COLOR_MAGENTA, COLOR_YELLOW, COLOR_WHITE}; void AnsiWindow::Init() { #ifdef NCURSES_VERSION // Is the "PC" character set available? char smpch[] = "smpch"; char *result = tigetstr(smpch); useAltCharset = (result && (result != ((char *) -1))); #endif atparse = 1; avtparse = true; bsvparse = true; } void AnsiWindow::DestroyChain() { while (NumOfLines--) delete linelist[NumOfLines]; delete[] linelist; } int AnsiWindow::getparm() { char *parm; int value; if (escparm[0]) { for (parm = escparm; (*parm != ';') && *parm; parm++); if (*parm == ';') { // more params after *parm++ = '\0'; value = (parm == escparm + 1) ? 1 : atoi(escparm); strcpy(escparm, parm); } else { // last parameter value = atoi(escparm); escparm[0] = '\0'; } } else // empty last param value = 1; return value; } void AnsiWindow::cls() { if (anim) animtext->Clear(C_ANSIBACK); else { cpy = NumOfLines - baseline - !cpy; checkpos(); if (baseline < (NumOfLines - 1)) baseline = NumOfLines - 1; } posreset(); } void AnsiWindow::colreset() { cfl = crv = cbr = 0; ccf = COLOR_WHITE; ccb = COLOR_BLACK; } chtype AnsiWindow::colorcore() { // Attribute set // Check bold and blinking: chtype tmpattrib = (cbr ? A_BOLD : 0) | (cfl ? A_BLINK : 0) | // If animating, check for color pair 0 (assumes COLOR_BLACK == 0), // and for remapped black-on-black: ((!anim || ((ccb | ccf) || !(oldcolorx | oldcolory))) ? (crv ? REVERSE(ccf, ccb) : COL(ccf, ccb)) : (crv ? REVERSE(oldcolorx, oldcolory) : COL(oldcolorx, oldcolory))); // If not animating, mark color pair as used: if (!anim) colorsused[(ccf << 3) + ccb] = true; return tmpattrib; } void AnsiWindow::colorset() { int tmp; while(escparm[0]) { tmp = getparm(); switch (tmp) { case 0: // reset colors colreset(); break; case 1: // bright cbr = 1; break; case 5: // flashing cfl = 1; break; case 7: // reverse crv = 1; break; default: if ((tmp > 29) && (tmp < 38)) // foreground ccf = ansi_colortable[tmp - 30]; else if ((tmp > 39) && (tmp < 48)) // background ccb = ansi_colortable[tmp - 40]; } } attrib = colorcore(); } void AnsiWindow::pc_colorset(unsigned char c) { cfl = !(!(c & 0x80)); // or should it be cleared for AVATAR? cbr = !(!(c & 8)); crv = 0; ccf = pc_colortable[c & 7]; ccb = pc_colortable[(c & 0x70) >> 4]; attrib = colorcore(); } void AnsiWindow::athandle() { static int oldccf = -1, oldccb, oldcbr, oldcfl; unsigned fg = 0, bg = 0; int result; unsigned char c[2], bgc[2], fgc[2]; bool xmode = false; c[0] = source.nextchar(); c[1] = '\0'; switch (c[0]) { case 'X': xmode = true; c[0] = source.nextchar(); case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': bgc[0] = c[0]; bgc[1] = '\0'; result = sscanf((const char *)bgc, "%x", &bg); if (1 == result) { fgc[0] = source.nextchar(); fgc[1] = '\0'; result = sscanf((const char *)fgc, "%x", &fg); if (1 == result) { if (!xmode) { c[0] = source.nextchar(); result = ('@' == c[0]); } if (1 == result) { if (1 == atparse) { if (!fg && !bg) { oldccf = ccf; oldccb = ccb; oldcbr = cbr; oldcfl = cfl; } else { if ((15 == fg) && (15 == bg)) { if (-1 != oldccf) { ccf = oldccf; ccb = oldccb; cbr = oldcbr; cfl = oldcfl; attrib = colorcore(); } } else { cbr = (fg > 7); cfl = (bg > 7); ccf = pc_colortable[fg & 7]; ccb = pc_colortable[bg & 7]; attrib = colorcore(); } } } } else { update('@'); update(bgc[0]); update(fgc[0]); source.backup(c[0]); } } else { update('@'); if (xmode) update('X'); update(bgc[0]); source.backup(fgc[0]); } } else { update('@'); if (xmode) update('X'); source.backup(c[0]); } break; case 'C': c[0] = source.nextchar(); c[1] = source.nextchar(); if (('L' == c[0]) && ('S' == c[1])) { cls(); c[0] = source.nextchar(); } else { update('@'); update('C'); update(c[0]); update(c[1]); } break; default: source.backup(c[0]); update('@'); } } void AnsiWindow::cpylow() { if (cpy < 0) cpy = 0; } void AnsiWindow::cpyhigh() { if (anim && (cpy > (LINES - 2))) cpy = LINES - 2; } void AnsiWindow::cpxhigh() { if (cpx > (COLS - 1)) cpx = COLS - 1; } void AnsiWindow::cpxlow() { if (cpx < 0) cpx = 0; } void AnsiWindow::escfig() { unsigned char a[2]; a[0] = source.nextchar(); a[1] = '\0'; switch (a[0]) { case 'A': // cursor up cpy -= getparm(); cpylow(); break; case 'B': // cursor down cpy += getparm(); cpyhigh(); break; case 'C': // cursor right cpx += getparm(); cpxhigh(); break; case 'D': // cursor left cpx -= getparm(); cpxlow(); break; case 'J': // clear screen if (getparm() == 2) cls(); break; case 'H': // set cursor position case 'f': cpy = getparm() - 1; cpylow(); cpyhigh(); cpx = getparm() - 1; cpxlow(); cpxhigh(); break; case 's': // save position spx = cpx; spy = cpy; break; case 'u': // restore position cpx = spx; cpy = spy; break; case 'm': // attribute set (colors, etc.) colorset(); break; case 'M': // eat the music (Kate Bush) do *a = source.nextchar(); while ((14 != *a) && ('\n' != *a)); break; // I know this is strange, but I think it beats the alternatives: default: if ((a[0] >= '0') && (a[0] <= '9')) case '=': // for some weird mutant codes case '?': case ';': // parameter separator { strcat(escparm, (const char *)a); escfig(); } } } void AnsiWindow::avatar() { unsigned char c = source.nextchar(); switch (c) { case 1: // attribute set pc_colorset(source.nextchar()); break; case 2: // blink on cfl = 1; break; case 3: // cursor up cpy--; cpylow(); break; case 4: // cursor down cpy++; cpyhigh(); break; case 5: // cursor left cpx--; cpxlow(); break; case 6: // cursor right cpx++; cpxhigh(); break; case 7: // clrtoeol() TODO break; case 8: // set cursor position cpy = source.nextchar(); cpyhigh(); cpx = source.nextchar(); cpxhigh(); break; default: update(22); source.backup(c); } } void AnsiWindow::posreset() { cpx = cpy = lpy = spx = spy = 0; } void AnsiWindow::checkpos() { if (cpy != lpy) { curr->pack(chtmp, tlen); if (cpy > lpy) { for (; lpy != cpy; lpy++) // move down and curr = curr->getnext(true); // allocate space } else if (cpy < lpy) { for (; lpy != cpy; lpy--) // move up curr = curr->getprev(); } if ((cpy + 1) > (NumOfLines - baseline)) NumOfLines = cpy + baseline + 1; tlen = curr->unpack(chtmp); } } void AnsiWindow::update(unsigned char c) { // Characters 0 - 31 static const chtype lowtrans[] = { ' ', '0', '*', 'V', ACS_DIAMOND, ACS_PLUS, ACS_PLMINUS, ACS_DEGREE, ACS_BLOCK, 'O', ACS_BLOCK, 'Q', 't', 'd', 'd', '*', 187, 171, ACS_VLINE, '!', 182, 167, ACS_BULLET, ACS_PLMINUS, ACS_UARROW, ACS_DARROW, ACS_LARROW, ACS_RARROW, ACS_LLCORNER, ACS_HLINE, ACS_UARROW, ACS_DARROW }; // Characters 176 - 223 static const chtype acstrans[] = { ACS_CKBOARD, ACS_CKBOARD, ACS_CKBOARD, ACS_VLINE, ACS_RTEE, ACS_RTEE, ACS_RTEE, ACS_URCORNER, ACS_URCORNER, ACS_RTEE, ACS_VLINE, ACS_URCORNER, ACS_LRCORNER, ACS_LRCORNER, ACS_LRCORNER, ACS_URCORNER, ACS_LLCORNER, ACS_BTEE, ACS_TTEE, ACS_LTEE, ACS_HLINE, ACS_PLUS, ACS_LTEE, ACS_LTEE, ACS_LLCORNER, ACS_ULCORNER, ACS_BTEE, ACS_TTEE, ACS_LTEE, ACS_HLINE, ACS_PLUS, ACS_BTEE, ACS_BTEE, ACS_TTEE, ACS_TTEE, ACS_LLCORNER, ACS_LLCORNER, ACS_ULCORNER, ACS_ULCORNER, ACS_PLUS, ACS_PLUS, ACS_LRCORNER, ACS_ULCORNER, ACS_BLOCK, // There are no good equivalents for these four: '_', '[', ']', '~' }; if (!ansiAbort) { chtype ouch, localattrib = attrib; #ifndef ALLCHARSOK // unprintable control codes switch (c) { // double musical note case 14: c = 19; break; case 15: // much like an asterisk c = '*'; break; case 155: // ESC + high-bit = slash-o, c = 'o'; // except in CP 437 } #endif if (isoConsole && !isLatin) { #ifdef NCURSES_VERSION if (useAltCharset) { ouch = c; if (c > 159) ouch |= A_ALTCHARSET; } else #endif { if ((c > 175) && (c < 224)) { ouch = acstrans[c - 176]; // IBM character 219 should map to ACS_BLOCK, but // since that's widely unimplemented, we map it to a // reverse space instead. Note that some terminals // (like PuTTY) would also like cfl and cbr swapped, // but XFree86's xterm prefers otherwise. if (c == 219) { ouch = ' '; crv = !crv; localattrib = colorcore(); crv = !crv; } // suppress or'ing of A_ALTCHARSET: c = ' '; } else { if (c < 32) { ouch = lowtrans[c]; c = ' '; } else { if (c == 127) c = 'A'; else if (c & 0x80) c = (unsigned char) dos2isotab[c & 0x7f]; ouch = c; } } } } else { if (!isoConsole && isLatin) if (c & 0x80) c = (unsigned char) iso2dostab[c & 0x7f]; ouch = c; } if ((c < ' ') || ((c > 126) && (c < 160))) ouch |= A_ALTCHARSET; ouch |= localattrib; int limit = LINES - 2; if (anim) { if (cpy > limit) { animtext->wscroll(cpy - limit); for (int i = cpy - limit; i; i--) animtext->clreol(limit - i + 1, 0); cpy = limit; } animtext->attrib(0); animtext->put(cpy, cpx++, ouch); animtext->attrib(C_ANSIBACK); animtext->update(); napms(12); ansiAbort |= (animtext->keypressed() != ERR); } else { checkpos(); chtmp[cpx++] = ouch; if (cpx > tlen) tlen = cpx; } if (cpx == COLS) { cpx = 0; cpy++; } } } void AnsiWindow::ResetChain() { head = new AnsiLine(); curr = head; posreset(); curr = curr->getnext(true); tlen = 0; NumOfLines = 1; baseline = 0; } void AnsiWindow::MakeChain() { unsigned char c; unsigned int blen = 1; ansiAbort = false; if (!anim) { ResetChain(); chtmp = new chtype[COLS]; curr->unpack(chtmp); } attrib = C_ANSIBACK; colreset(); source.reset(); if (bsvparse && isbsv) { c = source.nextchar(); if (0xfd != c) { source.backup(c); isbsv = false; } for (blen = 0; blen < 5; blen++) c = source.nextchar(); blen = source.nextchar(); blen <<= 8; blen += c; } do { c = source.nextchar(); #ifdef LIMIT_MEM if (maxfreemem() < ((long) NumOfLines * sizeof(AnsiLine *) + 0x200)) { c = 0; blen = 2; } #endif if (bsvparse && isbsv) { pc_colorset(source.nextchar()); if (!c) c = ' '; update(c); blen -= 2; } else switch (c) { case 1: // hidden lines (in pos 0) if (!cpx) { do c = source.nextchar(); while (source.anyleft() && (c != '\n')); } case 0: break; case 8: // backspace if (cpx) cpx--; break; case 10: cpy++; case 13: cpx = 0; case 7: // ^G: beep case 26: // ^Z: EOF for DOS break; case 12: // form feed cls(); break; case 22: // Main Avatar code if (avtparse) avatar(); else update(c); break; case 25: // Avatar RLE code if (avtparse) { unsigned char x; c = source.nextchar(); x = source.nextchar(); while (x--) update(c); } else update(c); break; case '`': case 27: // ESC c = source.nextchar(); if ('[' == c) { escparm[0] = '\0'; escfig(); } else { source.backup(c); update('`'); } break; case '\t': // TAB cpx = ((cpx / 8) + 1) * 8; while (cpx >= COLS) { cpx -= COLS; cpy++; } break; case '@': if (atparse) { athandle(); break; } default: update(c); } } while (c && !ansiAbort && blen); if (!anim) { curr->pack(chtmp, tlen); delete[] chtmp; if (!ansiAbort) { linelist = new AnsiLine *[NumOfLines]; curr = head->getnext(); int i = 0; while (curr) { linelist[i++] = curr; curr = curr->getnext(); } } delete head; } } void AnsiWindow::statupdate(const char *intro) { static const char *helpmsg = "F1 or ? - Help ", *mainstat = " ANSI View"; char *tmp = new char[COLS + 1]; const char *pn = mm.resourceObject->get(PacketName); bool expert = mm.resourceObject->getInt(ExpertMode); int pnlen = strlen(pn); if (pnlen > 20) pnlen = 20; int maxw = COLS - pnlen - (expert ? 16 : 31); sprintf(tmp, " %.*s |%s: %-*.*s %s", pnlen, pn, (intro ? intro : mainstat), maxw, maxw, title, expert ? "" : helpmsg); statbar->cursor_on(); statbar->put(0, 0, tmp); statbar->update(); statbar->cursor_off(); delete[] tmp; } void AnsiWindow::animate() { animtext = new Win(LINES - 1, COLS, 0, C_ANSIBACK); animtext->cursor_on(); posreset(); colreset(); anim = true; statupdate(" Animating"); MakeChain(); if (!ansiAbort) statupdate(" Done"); anim = false; while (!ansiAbort && (animtext->inkey() == ERR)); animtext->cursor_off(); delete animtext; header->wtouch(); text->wtouch(); statupdate(); } void AnsiWindow::oneLine(int i) { linelist[position + i]->show(text, i); } void AnsiWindow::lineCount() { char tmp[44]; int percent = ((long) position + y) * 100 / NumOfLines; if (percent > 100) percent = 100; sprintf(tmp, "Lines: %6d/%-6d %3d%%", position + 1, NumOfLines, percent); header->put(0, COLS - 26, tmp); header->delay_update(); } void AnsiWindow::DrawBody() { lineCount(); for (int i = 0; i < y; i++) if ((position + i) < NumOfLines) oneLine(i); else text->clreol(i, 0); } void AnsiWindow::set(letter_body *ansiSource, const char *winTitle, bool latin) { source.init(ansiSource); position = 0; isLatin = latin; title = winTitle; isbsv = false; } void AnsiWindow::set(file_header *f, const char *winTitle, bool latin) { source.init(f); position = 0; isLatin = latin; title = winTitle; const char *fname = f->getName(); size_t i = strlen(fname); isbsv = (i > 4) && !strcasecmp(fname + i - 4, ".bsv"); } void AnsiWindow::MakeActive() { int i; bool end, oldAbort; header = new Win(1, COLS, 0, C_LBOTTSTAT); text = new Win(LINES - 2, COLS, 1, C_ANSIBACK); statbar = new Win(1, COLS, LINES - 1, C_LBOTTSTAT); anim = false; oldAbort = ansiAbort; char *tmp = new char[COLS + 1]; i = sprintf(tmp, " " MM_TOPHEADER, sysname()); for (; i < COLS; i++) tmp[i] = ' '; tmp[i] = '\0'; header->put(0, 0, tmp); header->delay_update(); delete[] tmp; y = LINES - 2; x = COLS; for (i = 0; i < 64; i++) colorsused[i] = false; colorsused[PAIR_NUMBER(C_LBOTTSTAT)] = 1; //don't remap stat bars oldcolorx = oldcolory = 0; init_pair(((COLOR_WHITE) << 3) + (COLOR_WHITE), COLOR_WHITE, COLOR_WHITE); MakeChain(); // This deals with the unavailability of color pair 0: if (colorsused[0]) { // assumes COLOR_BLACK == 0 // Find an unused color pair for black-on-black: for (end = false, i = 1; i < 64 && !end; i++) if (!colorsused[i]) { end = true; oldcolorx = i >> 3; oldcolory = i & 7; init_pair(i, COLOR_BLACK, COLOR_BLACK); } // Remap all instances of color pair 0 to the new pair: if (end) for (i = 0; i < NumOfLines; i++) linelist[i]->remapzero(COL(oldcolorx, oldcolory)); } #ifdef PDCURSES PDC_set_blink(TRUE); #endif DrawBody(); text->delay_update(); statupdate(); ansiAbort = oldAbort; } void AnsiWindow::Delete() { ansiAbort = true; #ifdef PDCURSES PDC_set_blink(FALSE); #endif // Restore remapped color pairs: if (oldcolorx + oldcolory) init_pair((oldcolorx << 3) + oldcolory, oldcolorx, oldcolory); init_pair(((COLOR_WHITE) << 3) + (COLOR_WHITE), COLOR_BLACK, COLOR_BLACK); DestroyChain(); delete header; delete text; delete statbar; source.close(); } void AnsiWindow::setPos(int n) { position = n; } int AnsiWindow::getPos() { return position; } searchret AnsiWindow::search(const char *item) { searchret found = False; char *buffer = new char[COLS + 1]; for (int n = position + 1; (n < NumOfLines) && (found == False); n++) { if (text->keypressed() == 27) { found = Abort; break; } linelist[n]->unpacktext(buffer); found = searchstr(buffer, item) ? True : False; if (found == True) { position = n; DrawBody(); text->delay_update(); } } delete[] buffer; return found; } void AnsiWindow::Save() { FILE *fd; static char keepname[128]; char filename[128], oldfname[128]; if (keepname[0]) strcpy(filename, keepname); else { sprintf(filename, "%.8s.ans", title); unspace(filename); } strcpy(oldfname, filename); if (ui->savePrompt("Save to file:", filename)) { mychdir(mm.resourceObject->get(SaveDir)); fd = fopen(homify(filename), "at"); if (fd) { unsigned char c; source.reset(); do { c = source.nextchar(); if (c) fputc(c, fd); } while (c); fclose(fd); } else { char tmp[142]; sprintf(tmp, "%s: Save failed", filename); ui->nonFatalError(tmp); } if (strcmp(filename, oldfname)) strcpy(keepname, filename); } else ui->nonFatalError("Save aborted"); } void AnsiWindow::KeyHandle(int key) { switch (key) { #ifdef USE_MOUSE case MM_MOUSE: if (0 == mm_mouse_event.y) KeyHandle(KEY_UP); else if ((LINES - 1) == mm_mouse_event.y) KeyHandle(KEY_DOWN); else if ((mm_mouse_event.y > (LINES >> 1)) || (0 == position)) KeyHandle(' '); else KeyHandle(KEY_PPAGE); break; #endif case MM_UP: if (position > 0) { position--; text->wscroll(-1); oneLine(0); lineCount(); } break; case MM_DOWN: if (position < NumOfLines - y) { position++; text->wscroll(1); oneLine(y - 1); lineCount(); } break; case MM_HOME: position = 0; DrawBody(); break; case MM_END: if (NumOfLines > y) { position = NumOfLines - y; DrawBody(); } break; case 'B': case MM_PPAGE: position -= ((y < position) ? y : position); DrawBody(); break; case ' ': position += y; if (position < NumOfLines) DrawBody(); else ui->setKey('\n'); break; case 'F': case MM_NPAGE: if (position < NumOfLines - y) { position += y; if (position > NumOfLines - y) position = NumOfLines - y; DrawBody(); } break; case 'V': case 'A': case 1: animate(); break; case 'S': Save(); DrawBody(); break; case '@': atparse++; if (3 == atparse) atparse = 0; ui->redraw(); break; case 22: avtparse = !avtparse; ui->redraw(); break; case 2: bsvparse = !bsvparse; ui->redraw(); break; case MM_F1: case '?': ui->changestate(ansi_help); } text->delay_update(); } mmail-0.52/interfac/basic.cc000644 000765 000024 00000042675 13432114122 016702 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * Interface, ShadowedWin, ListWindow Copyright 1996 Kolossvary Tamas Copyright 1997 John Zero Copyright 1997-2019 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #include "interfac.h" Win::Win(int height, int width, int topline, chtype backg) { init(height, width, topline); Clear(backg); } Win::Win(int height, int width, int topline, coltype backg) { init(height, width, topline); Clear(backg); } Win::~Win() { delete[] buffer; delwin(win); } void Win::init(int height, int width, int topline) { // All windows are centered horizontally. win = newwin(height, width, topline, (COLS - width) / 2); buffer = new chtype[width + 1]; keypad(win, TRUE); cursor_off(); } void Win::Clear(chtype backg) { wbkgdset(win, backg); werase(win); wbkgdset(win, ' '); attrib(backg); } void Win::Clear(coltype backg) { wbkgdset(win, ColorArray[backg] | ' '); werase(win); wbkgdset(win, ' '); attrib(ColorArray[backg]); } void Win::put(int y, int x, chtype z) { mvwaddch(win, y, x, z); } void Win::put(int y, int x, char z) { mvwaddch(win, y, x, (unsigned char) z); } #ifdef MM_WIDE void Win::put(int y, int x, wchar_t z) { wchar_t tmp[2]; tmp[0] = z; tmp[1] = 0; mvwaddwstr(win, y, x, tmp); } #endif void Win::put(int y, int x, const chtype *z, int len) { // The cast is to suppress warnings with certain implementations // of curses that omit "const" from the prototype. if (!len) len = getmaxx(win) - x; mvwaddchnstr(win, y, x, (chtype *) z, len); } int Win::put(int y, int x, const char *z, int len) { // Formerly just mvwaddstr() -- this ensures printing of (most) // characters outside the ASCII range, instead of control codes. const int tabwidth = 8; chtype z2; int counter = 0, limit = getmaxx(win) - x; if ((-1 == len) || (len > limit)) len = limit; for (; *z && (counter < len); z++) { z2 = ((unsigned char) *z); switch (z2) { #ifndef ALLCHARSOK // unprintable control codes case 14: // double musical note z2 = 19; break; case 15: // much like an asterisk z2 = '*'; break; case 155: // ESC + high bit = slash-o, z2 = 'o'; // except in CP 437 break; case 8: // backspace case 12: // form feed z2 = '#'; break; #endif case 27: // ESC z2 = '`'; break; case '\t': // TAB z2 = ' '; int i = (tabwidth - 1) - (counter % tabwidth); len += i; if (len > limit) { i = limit - counter - 1; len = limit; } while (i--) buffer[counter++] = z2 | curratt; } if ((z2 < ' ') || ((z2 > 126) && (z2 < 160))) { if (isoConsole) z2 = '?'; else z2 |= A_ALTCHARSET; } buffer[counter++] = z2 | curratt; } mvwaddchnstr(win, y, x, buffer, counter); return counter; } int Win::attrib(chtype z) { curratt = z; return wattrset(win, z); } int Win::attrib(coltype z) { return attrib(ColorArray[z]); } void Win::horizline(int y) { wmove(win, y, 1); whline(win, ACS_HLINE, getmaxx(win) - 2); } void Win::update() { wrefresh(win); } void Win::delay_update() { wnoutrefresh(win); } void Win::wtouch() { touchwin(win); wnoutrefresh(win); } void Win::wscroll(int i) { scrollok(win, TRUE); wscrl(win, i); scrollok(win, FALSE); } void Win::cursor_on() { leaveok(win, FALSE); curs_set(1); } void Win::cursor_off() { leaveok(win, TRUE); curs_set(0); } int Win::keypressed() { // Return key status immediately (non-blocking) nocbreak(); // Clear any halfdelay() set cbreak(); // Back to normal -- is there a better way? nodelay(win, TRUE); return wgetch(win); } int Win::inkey() { // Wait until key pressed, SIGWINCH received, or 5 seconds #ifdef PDCURSES nodelay(win, TRUE); #else # ifndef NCURSES_VERSION nocbreak(); // Solaris curses needs this, otherwise it's cbreak(); // stuck in nodelay mode after keypressed() # endif #endif halfdelay(50); return wgetch(win); } void Win::boxtitle(coltype backg, const char *title, chtype titleAttrib) { attrib(backg); box(win, 0, 0); if (title) { put(0, 2, ACS_RTEE); put(0, 3 + strlen(title), ACS_LTEE); attrib(titleAttrib); put(0, 3, title); attrib(backg); } } void Win::clreol(int y, int x) { for (int i = x; i < COLS; i++) put(y, i, (chtype) (' ' | curratt)); } #ifdef USE_MOUSE int Win::xstart() { return getbegx(win); } int Win::ystart() { return getbegy(win); } #endif ShadowedWin::ShadowedWin(int height, int width, int topline, coltype backg, const char *title, coltype titleAttrib) : Win(height, width, topline, backg) { // The text to be in "shadow" is taken from different windows, // depending on the curses implementation. Note that the default, // "stdscr", just makes the shadowed area solid black; only with // ncurses and PDCurses does it draw proper shadows. #ifdef USE_SHADOWS int i, j; chtype *right, *lower; # ifndef NCURSES_VERSION # ifdef PDCURSES WINDOW *&newscr = curscr; # else WINDOW *&newscr = stdscr; # endif # endif int xlimit = 2 + (ui->active() != letter); int firstcol = (COLS - width) / 2; while ((width + firstcol) > (COLS - xlimit)) width--; while ((height + topline) > (LINES - 1)) height--; right = new chtype[(height - 1) << 1]; lower = new chtype[width + 1]; // Gather the old text and attribute info: for (i = 0; i < (height - 1); i++) for (j = 0; j < 2; j++) right[(i << 1) + j] = (mvwinch(newscr, (topline + i + 1), (firstcol + width + j)) & (A_CHARTEXT | A_ALTCHARSET)); mvwinchnstr(newscr, (topline + height), (firstcol + 2), lower, width); // Redraw it in darkened form: shadow = newwin(height, width, topline + 1, firstcol + 2); leaveok(shadow, TRUE); for (i = 0; i < (height - 1); i++) for (j = 0; j < 2; j++) mvwaddch(shadow, i, width - 2 + j, (right[(i << 1) + j] | ColorArray[C_SHADOW])); for (i = 0; i < width; i++) mvwaddch(shadow, height - 1, i, (lower[i] & (A_CHARTEXT | A_ALTCHARSET)) | ColorArray[C_SHADOW]); wnoutrefresh(shadow); #endif boxtitle(backg, title, ColorArray[titleAttrib]); #ifdef USE_SHADOWS delete[] lower; delete[] right; #endif } ShadowedWin::~ShadowedWin() { #ifdef USE_SHADOWS delwin(shadow); #endif } void ShadowedWin::touch() { #ifdef USE_SHADOWS touchwin(shadow); wnoutrefresh(shadow); #endif Win::wtouch(); } int ShadowedWin::getstring(int y, int x, char *string, int maxlen, coltype bg_color, coltype fg_color) { int i, j, offset, end, c; char *tmp = new char[maxlen + 1]; int width = getmaxx(win) - x - 1; int dwidth = (maxlen > width) ? width : maxlen; cropesp(string); attrib(fg_color); for (i = 0; i < maxlen; i++) { if (i < dwidth) put(y, x + i, ACS_BOARD); tmp[i] = '\0'; } tmp[maxlen] = '\0'; j = strlen(string); offset = (j > dwidth) ? j - dwidth : 0; for (i = offset; i < j; i++) put(y, x + i - offset, string[i]); if (!string[0]) wmove(win, y, x); cursor_on(); update(); i = end = 0; bool first_key = true; while (!end) { do c = inkey(); while (ERR == c); // these keys make the string "accepted" and editable: if (first_key) { first_key = false; switch (c) { case MM_LEFT: case MM_RIGHT: case MM_BACKSP: case MM_HOME: case MM_END: #ifdef USE_MOUSE case MM_MOUSE: #endif strncpy(tmp, string, maxlen); i = strlen(tmp); } } switch (c) { case MM_DOWN: end++; case MM_UP: end++; case '\t': case MM_ENTER: end++; case MM_ESC: end++; break; case MM_LEFT: if (i > 0) i--; break; case MM_RIGHT: if ((i < maxlen) && tmp[i]) i++; break; case MM_BACKSP: if (!i) break; i--; case 127: case MM_DEL: // Delete key for (j = i; j < maxlen; j++) tmp[j] = tmp[j + 1]; tmp[maxlen] = '\0'; break; case MM_HOME: i = 0; break; case MM_END: i = strlen(tmp); break; #ifdef USE_MOUSE case MM_MOUSE: mm_mouse_get(); if (mm_mouse_event.bstate & BUTTON3_CLICKED) end = 1; else end = 2; break; #endif case ERR: break; default: for (j = (maxlen - 1); j > i; j--) tmp[j] = tmp[j - 1]; if (i < maxlen) { tmp[i] = c; i++; } } while (i < offset) offset--; while ((i - offset) > dwidth) offset++; for (j = offset; j < dwidth + offset; j++) put(y, x + j - offset, (chtype) (tmp[j] ? (unsigned char) tmp[j] : ACS_BOARD)); wmove(win, y, x + i - offset); update(); } if (tmp[0]) strcpy(string, tmp); attrib(bg_color); j = strlen(string); for (i = 0; i < dwidth; i++) put(y, x + i, ((i < j) ? string[i] : ' ')); update(); cursor_off(); delete[] tmp; return end - 1; } InfoWin::InfoWin(int height, int width, int topline, coltype backg, const char *title, coltype titleAttrib, int bottx, int topx) : ShadowedWin(height, width, topline, backg, title, titleAttrib) { lineBuf = new char[COLS]; info = new Win(height - bottx, width - 2, topline + topx, backg); } InfoWin::~InfoWin() { delete info; delete[] lineBuf; } void InfoWin::irefresh() { info->delay_update(); } void InfoWin::touch() { ShadowedWin::touch(); info->wtouch(); } void InfoWin::oneline(int i, chtype ch) { info->attrib(ch); info->put(i, 0, lineBuf); } void InfoWin::iscrl(int i) { info->wscroll(i); } #ifdef USE_MOUSE int InfoWin::xstartinfo() { return info->xstart(); } int InfoWin::ystartinfo() { return info->ystart(); } #endif ListWindow::ListWindow() { position = active = 0; oldPos = oldActive = oldHigh = -100; lynxNav = mm.resourceObject->getInt(UseLynxNav); } ListWindow::~ListWindow() { } void ListWindow::checkPos(int limit) { if (active >= limit) active = limit - 1; if (active < 0) active = 0; if (active < position) position = active; else if (active - position >= list_max_y) position = active - list_max_y + 1; if (limit > list_max_y) { if (position < 0) position = 0; else if (position > limit - list_max_y) position = limit - list_max_y; } else position = 0; } chtype ListWindow::setHighlight(chtype ch) { chtype backg = PAIR_NUMBER(ch) & 7; switch (backg) { case COLOR_BLACK: case COLOR_RED: case COLOR_BLUE: ch = REVERSE(COLOR_WHITE, COLOR_BLACK); break; default: ch = REVERSE(COLOR_BLACK, COLOR_WHITE); } return ch; } void ListWindow::Draw() { const chtype current = ACS_DIAMOND; const chtype old = ACS_CKBOARD; int i, j, limit = NumOfItems(); checkPos(limit); // Highlight bar: if (limit > list_max_y) { if (oldHigh == -100) { list->attrib(borderCol); for (i = top_offset; i < (top_offset + list_max_y); i++) list->put(i, list_max_x + 1, old); } i = active * list_max_y / limit; if (i != oldHigh) { list->attrib(borderCol); list->put(top_offset + oldHigh, list_max_x + 1, old); list->put(top_offset + i, list_max_x + 1, current); list->delay_update(); oldHigh = i; } } // Scroll or redraw: i = position - oldPos; switch (i) { case -1: case 1: list->iscrl(i); case 0: if (active != oldActive) { j = oldActive - position; if ((j >= 0) && (j < list_max_y)) oneLine(j); } oneLine(active - position); list->irefresh(); break; default: for (j = 0; j < list_max_y; j++) { oneLine(j); if (position + j == limit - 1) j = list_max_y; } list->touch(); } oldPos = position; oldActive = active; } void ListWindow::DrawOne(int i, chtype ch) { // Highlight, if drawing the active bar if (position + i == active) ch = setHighlight(ch); list->oneline(i, ch); } void ListWindow::DrawOne(int i, coltype ch) { DrawOne(i, ColorArray[ch]); } void ListWindow::DrawAll() { oldPos = oldActive = oldHigh = -100; Draw(); } void ListWindow::Move(int dir) { int limit = NumOfItems(); switch (dir) { case KEY_UP: active--; break; case KEY_DOWN: active++; break; case KEY_PPAGE: position -= list_max_y; active -= list_max_y; break; case KEY_NPAGE: position += list_max_y; active += list_max_y; break; case KEY_HOME: active = 0; break; case KEY_END: active = limit - 1; break; } checkPos(limit); } void ListWindow::setActive(int x) { active = x; } int ListWindow::getActive() { return active; } searchret ListWindow::search(const char *item, int mode) { int limit = NumOfItems(); searchret found = False; statetype orgstate = ui->active(); for (int x = active + 1; (x < limit) && (found == False); x++) { if (list->keypressed() == 27) { found = Abort; break; } found = oneSearch(x, item, mode); if (found == True) { active = x; if (ui->active() == orgstate) Draw(); } } checkPos(limit); return found; } void ListWindow::Prev() { Move(KEY_UP); } void ListWindow::Next() { Move(KEY_DOWN); } bool ListWindow::KeyHandle(int key) { bool draw = true, end = false; switch (key) { #ifdef USE_MOUSE case MM_MOUSE: { int begx = list->xstartinfo(), begy = list->ystartinfo(); if ( ((mm_mouse_event.x < begx) || (mm_mouse_event.x > (begx + list_max_x))) || ((mm_mouse_event.y < (begy - 1)) || (mm_mouse_event.y > (begy + list_max_y))) ) { draw = false; end = extrakeys(key); } else { mm_mouse_event.y -= begy; if (-1 == mm_mouse_event.y) Move(KEY_UP); else if (list_max_y == mm_mouse_event.y) Move(KEY_DOWN); else if ((begx + list_max_x) == mm_mouse_event.x) { if (mm_mouse_event.y > oldHigh) Move(KEY_NPAGE); else if (mm_mouse_event.y < oldHigh) Move(KEY_PPAGE); } else { bool select = (mm_mouse_event.bstate & BUTTON1_DOUBLE_CLICKED) || (active == (position + mm_mouse_event.y)); active = position + mm_mouse_event.y; if (select) { draw = false; end = ui->select(); } } } } break; #endif case MM_LEFT: if (lynxNav) { draw = false; end = ui->back(); break; } case MM_MINUS: Prev(); break; case MM_RIGHT: if (lynxNav) { draw = false; end = ui->select(); break; } case '\t': case MM_PLUS: Next(); break; case MM_DOWN: Move(KEY_DOWN); break; case MM_UP: Move(KEY_UP); break; case MM_HOME: Move(KEY_HOME); break; case MM_END: Move(KEY_END); break; case 'B': case MM_PPAGE: Move(KEY_PPAGE); break; case ' ': case 'F': case MM_NPAGE: Move(KEY_NPAGE); break; case '|': case '^': draw = false; { char item[80]; *item = '\0'; if (ui->savePrompt("Filter on:", item)) setFilter(item); } ui->redraw(); break; default: draw = false; end = extrakeys(key); } if (draw) Draw(); return end; } mmail-0.52/interfac/tagline.cc000644 000765 000024 00000020610 13432132231 017226 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * tagline selection, editing Copyright 1996-1997 Kolossvary Tamas Copyright 1997-2018 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #include "interfac.h" extern "C" int tnamecmp(const void *a, const void *b) { int d; const char *p = (*((tagline **) a))->text; const char *q = (*((tagline **) b))->text; d = strcasecmp(p, q); if (!d) d = strcmp(q, p); return d; } tagline::tagline(const char *tag) { if (tag) { strncpy(text, tag, TAGLINE_LENGTH); text[TAGLINE_LENGTH] = '\0'; } killed = false; next = 0; } TaglineWindow::TaglineWindow() { nodraw = true; sorted = false; NumOfTaglines = NumOfActive = 0; taglist = tagactive = 0; filter = 0; } TaglineWindow::~TaglineWindow() { DestroyChain(); } void TaglineWindow::MakeActive() { int expmode = mm.resourceObject->getInt(ExpertMode); nodraw = false; list_max_y = LINES - (expmode ? 12 : 15); int xwidth = COLS - 4; if (xwidth > (TAGLINE_LENGTH + 2)) xwidth = TAGLINE_LENGTH + 2; list_max_x = xwidth - 2; top_offset = 1; borderCol = C_TBBACK; char tmp[60]; char *p = tmp + sprintf(tmp, "Taglines, %s", sorted ? "sorted" : "unsorted"); if (NumOfActive > list_max_y) p += sprintf(p, " (%d)", NumOfActive); if (filter) sprintf(p, " | %.20s", filter); list = new InfoWin(LINES - 10, xwidth, 5, borderCol, tmp, C_TTEXT, expmode ? 2 : 5, top_offset); if (!expmode) { int x = xwidth / 3 + 1; int y = list_max_y + 1; list->attrib(C_TKEYSTEXT); list->horizline(y); list->put(++y, 2, "Q"); list->put(y, x, "R"); list->put(y, x * 2, "Enter"); list->put(++y, 2, "K"); list->put(y, x, "A"); //list->put(y, x * 2, " /, ."); list->put(y, x * 2 + 4, "E"); list->attrib(C_TTEXT); list->put(y, 3, ": Kill current tagline"); list->put(y, x + 1, ": Add new tagline"); //list->put(y, x * 2 + 5, ": search / next"); list->put(y, x * 2 + 5, ": Edit tagline"); list->put(--y, 3, ": don't apply tagline"); list->put(y, x + 1, ": Random select tagline"); list->put(y, x * 2 + 5, ": apply tagline"); } DrawAll(); } void TaglineWindow::Delete() { delete list; nodraw = true; } bool TaglineWindow::extrakeys(int key) { switch (key) { case 'A': EnterTagline(); break; case 'E': EditTagline(); break; case 'R': RandomTagline(); break; case MM_DEL: case 'K': if (highlighted) kill(); break; case 'S': case '$': sorted = !sorted; MakeChain(); Delete(); MakeActive(); } return false; } void TaglineWindow::setFilter(const char *item) { delete[] filter; filter = strdupplus(item); MakeChain(); if (!NumOfActive) { delete[] filter; filter = 0; MakeChain(); } } void TaglineWindow::RandomTagline() { int i = rand() / (RAND_MAX / NumOfActive); Move(KEY_HOME); for (int j = 1; j <= i; j++) Move(KEY_DOWN); DrawAll(); } void TaglineWindow::EnterTagline(const char *tag) { FILE *fd; char newtagline[TAGLINE_LENGTH + 1]; int y; Move(KEY_END); if (NumOfActive >= list_max_y) { y = list_max_y; position++; } else y = NumOfActive + 1; active++; if (!nodraw) { NumOfActive++; Draw(); NumOfActive--; } else { int xwidth = COLS - 4; if (xwidth > (TAGLINE_LENGTH + 2)) xwidth = TAGLINE_LENGTH + 2; list = new InfoWin(5, xwidth, (LINES - 5) >> 1, C_TBBACK); list->attrib(C_TTEXT); list->put(1, 1, "Enter new tagline:"); list->update(); } strcpy(newtagline, tag ? tag : ""); if (list->getstring(nodraw ? 2 : y, 1, newtagline, TAGLINE_LENGTH, C_TENTER, C_TENTERGET)) { cropesp(newtagline); if (newtagline[0]) { //check dupes; also move curr to end of list: bool found = false; curr = &head; while (curr->next && !found) { curr = curr->next; found = !strcmp(newtagline, curr->text); } if (!found) { curr->next = new tagline(newtagline); fd = fopen(tagname, "at"); if (fd) { fputs(newtagline, fd); fputc('\n', fd); fclose(fd); } NumOfTaglines++; MakeChain(); } else ui->nonFatalError("Already in file"); } } Move(KEY_END); if (!nodraw) { DrawAll(); doupdate(); } else list->update(); } void TaglineWindow::EditTagline() { char newtagline[TAGLINE_LENGTH + 1]; strcpy(newtagline, getCurrent()); if (list->getstring(active - position + 1, 1, newtagline, TAGLINE_LENGTH, C_TENTER, C_TENTERGET)) { cropesp(newtagline); if (newtagline[0]) strcpy(tagactive[active]->text, newtagline); } WriteFile(false); Draw(); } void TaglineWindow::kill() { if (ui->WarningWindow("Remove this tagline?")) { if (position) position--; highlighted->killed = true; NumOfTaglines--; MakeChain(); WriteFile(false); } Delete(); MakeActive(); } bool TaglineWindow::ReadFile() { FILE *fd; char newtag[TAGLINE_LENGTH + 1]; bool flag; fd = fopen(tagname, "rt"); flag = !(!fd); if (flag) { char *end; curr = &head; do { end = myfgets(newtag, sizeof newtag, fd); if (end && (newtag[0] != '\n')) { if (*end == '\n') *end = '\0'; curr->next = new tagline(newtag); curr = curr->next; NumOfTaglines++; } } while (end); fclose(fd); } return flag; } void TaglineWindow::WriteFile(bool message) { FILE *tagx; if (message) printf("Creating %s...\n", tagname); tagx = fopen(tagname, "wt"); if (tagx) { for (int x = 0; x < NumOfTaglines; x++) { fputs(taglist[x]->text, tagx); fputc('\n', tagx); } fclose(tagx); } } void TaglineWindow::MakeChain() { delete[] taglist; taglist = new tagline *[NumOfTaglines + 1]; delete[] tagactive; tagactive = new tagline *[NumOfTaglines + 1]; NumOfActive = 0; if (NumOfTaglines) { curr = head.next; int c = 0; while (curr) { if (!curr->killed) { taglist[c++] = curr; if (!filter || searchstr(curr->text, filter)) tagactive[NumOfActive++] = curr; } curr = curr->next; } if (sorted && (NumOfActive > 1)) qsort(tagactive, NumOfActive, sizeof(tagline *), tnamecmp); } tagactive[NumOfTaglines] = 0; // hack for EnterTagline } void TaglineWindow::DestroyChain() { while (NumOfTaglines) delete taglist[--NumOfTaglines]; delete[] taglist; delete[] tagactive; } void TaglineWindow::oneLine(int i) { int z = position + i; curr = (z < NumOfActive) ? tagactive[z] : 0; if (z == active) highlighted = curr; sprintf(list->lineBuf, "%-*.*s", list_max_x, list_max_x, curr ? curr->text : " "); DrawOne(i, C_TLINES); } searchret TaglineWindow::oneSearch(int x, const char *item, int) { return searchstr(tagactive[x]->text, item) ? True : False; } int TaglineWindow::NumOfItems() { return NumOfActive; } // Create tagline file if it doesn't exist. void TaglineWindow::Init() { // Default taglines: #include "tagline.h" tagname = mm.resourceObject->get(TaglineFile); bool useDefault = !ReadFile(); if (useDefault) { curr = &head; for (const char **p = defaultTags; *p; p++) { curr->next = new tagline(*p); curr = curr->next; NumOfTaglines++; } } MakeChain(); if (useDefault) WriteFile(true); } const char *TaglineWindow::getCurrent() { return tagactive[active]->text; } mmail-0.52/interfac/packet.cc000644 000765 000024 00000024626 13255077256 017107 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * packet list window, vanity plate Copyright 1996 Kolossvary Tamas Copyright 1997 John Zero Copyright 1997-2018 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #include "interfac.h" #ifdef VANITY_PLATE void Welcome::MakeActive() { window = new ShadowedWin(6, 50, 2, C_WBORDER); window->attrib(C_WELCOME1); window->put(1, 7, "Welcome to " MM_NAME " Offline Reader!"); window->attrib(C_WELCOME2); window->put(3, 2, "Copyright (c) "); window->put(3, 16, __DATE__ + 7); window->put(3, 21, "William McBrine, Kolossvary"); window->put(4, 7, "Tamas, Toth Istvan, John Zero, et al."); window->touch(); } void Welcome::Delete() { delete window; } #endif PacketListWindow::oneDir::oneDir(const char *nameA, oneDir *parentA) : parent(parentA) { name = fixPath(nameA); position = active = 0; } PacketListWindow::oneDir::~oneDir() { delete[] name; } PacketListWindow::PacketListWindow() { #ifdef HAS_HOME home = getenv("HOME"); #endif origDir = 0; packetList = 0; } void PacketListWindow::init() { mychdir(mm.resourceObject->get(PacketDir)); char *tmp = mygetcwd(); origDir = new oneDir(tmp, 0); delete[] tmp; currDir = origDir; sorttype = mm.resourceObject->getInt(PacketSort); newList(); if (noFiles) // If there are any files, active = noDirs; // set active to the first one. } PacketListWindow::~PacketListWindow() { delete origDir; delete packetList; } void PacketListWindow::newList() { delete packetList; mm.resourceObject->set(oldPacketName, (char *) 0); const char *target = currDir->name; mm.resourceObject->set(PacketDir, target); time(&currTime); packetList = new file_list(target, sorttype, true); noDirs = packetList->getNoOfDirs(); noFiles = packetList->getNoOfFiles(); } void PacketListWindow::MakeActiveCore() { const int stline = #ifdef VANITY_PLATE 9; #else 2; #endif list_max_y = LINES - (stline + (mm.resourceObject->getInt(ExpertMode) ? 5 : 9)); bool usenum = false; int items = NumOfItems(); if (list_max_y > items) list_max_y = items ? items : 1; else usenum = true; char tmp[46], *dest = tmp, *src = currDir->name; int end, maxlen = 36, len = strlen(src); if (usenum) maxlen -= sprintf(tmp, " (%d)", noFiles); #ifdef HAS_HOME int hlen = home ? strlen(home) : 0; bool inhome = hlen && (hlen <= len) && !strncmp(home, currDir->name, hlen); if (inhome && ((len - hlen) < maxlen)) { tmp[0] = '~'; dest++; src += hlen; end = len - hlen + 1; } else #endif if (len > maxlen) { strcpy(tmp, "..."); dest += 3; src += (len - maxlen) + 3; end = maxlen; } else end = len; strcpy(dest, src); canonize(dest); int newend = end + sprintf(tmp + end, ", by %s", sorttype ? "time" : "name"); if (usenum) newend += sprintf(tmp + newend, " (%d)", noFiles); const char *filter = packetList->getFilter(); if (filter) { int flen = strlen(filter); int fmax = list_max_x - 5 - newend; if (flen > fmax) flen = fmax; sprintf(tmp + newend, " | %.*s", flen, filter); } list = new InfoWin(list_max_y + 3, list_max_x + 2, stline, borderCol, tmp, C_PHEADTEXT); list->attrib(C_PLINES); tmp[end] = '\0'; list->put(0, 3, tmp); list->attrib(C_PHEADTEXT); list->put(1, 3, "Packet Size Date"); list->touch(); DrawAll(); } void PacketListWindow::MakeActive() { list_max_x = 48; top_offset = 2; borderCol = C_PBBACK; #ifdef VANITY_PLATE welcome.MakeActive(); #endif MakeActiveCore(); } int PacketListWindow::NumOfItems() { return noDirs + noFiles; } void PacketListWindow::Delete() { delete list; #ifdef VANITY_PLATE welcome.Delete(); #endif } void PacketListWindow::oneLine(int i) { char *tmp = list->lineBuf; int absPos = position + i; time_t tmpt; packetList->gotoFile(absPos); if (absPos < noDirs) { absPos = sprintf(tmp, " <%.28s", packetList->getName()); char *tmp2 = tmp + absPos; *tmp2++ = '>'; absPos = 32 - absPos; while (--absPos > 0) *tmp2++ = ' '; } else { const char *tmp2 = packetList->getName(); strcpy(tmp, " "); if (*tmp2 == '.') sprintf(&tmp[2], "%-20.20s", tmp2); else { for (int j = 2; *tmp2 && (*tmp2 != '.') && (j < 10); j++) tmp[j] = *tmp2++; sprintf(&tmp[10], "%-10.10s", tmp2); } sprintf(&tmp[20], "%12lu", (unsigned long) packetList->getSize()); } tmpt = packetList->getDate(); #ifdef TIMEKLUDGE if (!tmpt) tmpt = currTime; #endif long dtime = currTime - tmpt; // 15000000 secs = approx six months (use year if older): strftime(&tmp[32], 17, ((dtime < 0 || dtime > 15000000L) ? " %b %d %Y " : " %b %d %H:%M "), localtime(&tmpt)); DrawOne(i, C_PLINES); } searchret PacketListWindow::oneSearch(int x, const char *item, int mode) { const char *s; searchret retval; packetList->gotoFile(x); s = packetList->getName(); retval = searchstr(s, item) ? True : False; if ((retval == False) && (x >= noDirs) && (mode < s_pktlist)) { int oldactive = active; active = x; if (OpenPacket() == PKT_OK) { mm.checkForReplies(); mm.openReply(); ui->redraw(); ui->ReportWindow("Searching (ESC to abort)..."); mm.areaList = new area_list(&mm); mm.areaList->getRepList(); mm.driverList->initRead(); mm.areaList->setMode(-1); mm.areaList->relist(); ui->changestate(arealist); ui->areas.setActive(-1); retval = ui->areas.search(item, mode); if (retval != True) { active = oldactive; mm.Delete(); ui->changestate(packetlist); } } else active = oldactive; } return retval; } void PacketListWindow::Select() { packetList->gotoFile(active); } bool PacketListWindow::back() { bool end = false; if (currDir != origDir) { oneDir *oldDir = currDir->parent; delete currDir; currDir = oldDir; newList(); position = currDir->position; active = currDir->active; ui->redraw(); } else end = true; return end; } bool PacketListWindow::extrakeys(int key) { bool end = false; switch (key) { #ifdef USE_MOUSE case MM_MOUSE: { int begx = list->xstart(), begy = list->ystart(); if ( (mm_mouse_event.y != begy) || ((mm_mouse_event.x < (begx + 3)) || (mm_mouse_event.x > (begx + list_max_x))) ) break; } #endif case 'S': case '$': packetList->resort(); sorttype = !sorttype; delete list; MakeActiveCore(); break; case 'G': gotoDir(); break; case 'R': renamePacket(); break; case MM_DEL: case 'K': killPacket(); break; case 'T': Select(); packetList->setDate(); time(&currTime); delete list; MakeActiveCore(); break; case 'U': newList(); ui->redraw(); } return end; } void PacketListWindow::setFilter(const char *item) { packetList->setFilter(item); noDirs = packetList->getNoOfDirs(); noFiles = packetList->getNoOfFiles(); } bool PacketListWindow::newDir(const char *dname) { char *result = packetList->changeDir(homify(dname)); if (result) { currDir->position = position; currDir->active = active; oneDir *nd = new oneDir(result, currDir); currDir = nd; newList(); position = 0; active = noFiles ? noDirs : 0; delete[] result; return true; } return false; } void PacketListWindow::gotoDir() { char pathname[70]; pathname[0] = '\0'; if (ui->savePrompt("New directory:", pathname) && pathname[0]) { if (newDir(pathname)) ui->redraw(); else ui->nonFatalError("Could not change to directory"); } else ui->nonFatalError("Change cancelled"); } void PacketListWindow::renamePacket() { if (active >= noDirs) { Select(); const char *fname = packetList->getName(); char question[60], answer[60]; sprintf(question, "New filename for %.39s:", fname); if (getNumExt(fname) != -1) sprintf(answer, "%.59s", fname); else { const char *base = findBaseName(fname); int ext = packetList->nextNumExt(base); sprintf(answer, "%.55s.%03d", base, ext); } if (ui->savePrompt(question, answer) && answer[0] && strcmp(fname, answer)) { const char *expanswer = homify(answer); bool changeit = !(packetList->exists(expanswer)); if (changeit) { Select(); changeit = !(packetList->changeName(expanswer)); if (changeit) { newList(); ui->redraw(); } else ui->nonFatalError("Rename failed"); } else ui->nonFatalError("Name already used"); } else ui->nonFatalError("Rename cancelled"); } } void PacketListWindow::killPacket() { if (active >= noDirs) { Select(); char tmp[128]; sprintf(tmp, "Do you really want to delete %.90s?", packetList->getName()); if (ui->WarningWindow(tmp)) { packetList->kill(); noFiles = packetList->getNoOfFiles(); } ui->redraw(); } } pktstatus PacketListWindow::OpenPacket() { Select(); if (active < noDirs) { if (newDir(0)) ui->redraw(); else ui->nonFatalError("Could not change to directory"); return NEW_DIR; } else return (active < NumOfItems()) ? mm.selectPacket(packetList->getName()) : PKT_UNFOUND; } mmail-0.52/interfac/tagline.h000644 000765 000024 00000002227 12542072425 017105 0ustar00wmcbrinestaff000000 000000 /* Perhaps these don't deserve to be enshrined in the source code, but I wanted to simplify installation by having the tagline file auto-generated for new users. You can replace this list with whatever taglines you like (just make sure the last item is a zero). - WJM3 */ #ifndef TAGLINES_H #define TAGLINES_H static const char *defaultTags[] = { "MultiMail, the new multi-platform, multi-format offline reader!", "\"42? 7 and a half million years and all you can come up with is 42?!\"", "2 + 2 = 5 for extremely large values of 2.", "Computer Hacker wanted. Must have own axe.", "DalekDOS v(overflow): (I)Obey (V)ision impaired (E)xterminate", "Direct from the Ministry of Silly Walks", "Gone crazy, be back later, please leave message.", "Got my tie caught in the fax... Suddenly I was in L.A.", "He does the work of 3 Men...Moe, Larry & Curly", "Heisenberg may have slept here.", "Internal Error: The system has been taken over by sheep at line 19960", "So easy, a child could do it. Child sold separately.", "The number you have dailed...Nine-one-one...has been changed.", "What is mind? No matter! What is matter? Never mind! - Homer S.", 0 }; #endif mmail-0.52/interfac/mmcolor.h000644 000765 000024 00000005151 13250315724 017130 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * color pairs #define'd here Copyright 1996-1997 John Zero Copyright 1997-2017 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #ifndef MMCOLOR_H #define MMCOLOR_H #define COL(f, b) COLOR_PAIR(((f) << 3) + (b)) #define REVERSE(f, b) ((COL((f), (b))) | (A_REVERSE)) #define C_ANSIBACK COL(COLOR_WHITE, COLOR_BLACK) enum coltype { C_SBACK, //Start screen/backgnd C_SBORDER, //Start/bdr C_SSEPBOTT, //Start screen/bottom C_HELP1, //Help desc. C_HELP2, //Help keys C_HELP3, //Help 2 bdr C_HELP4, //Help 2 text C_WBORDER, //Welcome border C_WELCOME1, //Welcome prog name C_WELCOME2, //Welcome auth names C_ADDR1, //Add. backgnd C_ADDR2, //Add. headers C_ADDR3, //Address book/text C_WTEXT, //Warn/text C_WTEXTHI, //Warn/hilight C_LTEXT, //Letter/text C_LQTEXT, //Letter/quoted text C_LTAGLINE, //Letter/tagline C_LTEAR, //Letter/tear C_LHIDDEN, //Letter/hidden C_LORIGIN, //Letter/origin C_LBOTTSTAT, //Letter/bottom statline C_LHEADTEXT, //Letter/header text C_LHMSGNUM, //msgnum C_LHFROM, //from C_LHTO, //to C_LHSUBJ, //subject C_LHDATE, //date C_LHFLAGSHI, //flags high C_LHFLAGS, //flags C_PBBACK, //Packet/header C_PHEADTEXT, //line text C_PLINES, //Packet/lines C_LALBTEXT, //Little area C_LALLINES, //line text C_ALREPLINE, //Area list/reply area C_ALPACKETLINE, //Area list/normal C_ALINFOTEXT, //info win C_ALINFOTEXT2, //filled text C_ALBTEXT, //border text C_ALBORDER, //border C_ALHEADTEXT, //header text C_LETEXT, //Letter text C_LEGET1, //Letter/enter get1 C_LEGET2, //get2 C_LLSAVEBORD, //Letter/save border C_LLSAVETEXT, //Letter/save C_LLSAVEGET, //get C_LISTWIN, //Letter list/top text1 C_LLPERSONAL, //Letter list/personal C_LLBBORD, //Letter list C_LLTOPTEXT1, //top text1 C_LLTOPTEXT2, //areaname C_LLHEAD, //headers C_TBBACK, //Tagline C_TTEXT, //Tagline/text C_TKEYSTEXT, //key select C_TENTER, //Tagline/enter C_TENTERGET, //enter get C_TLINES, //lines C_SHADOW, //All black! numColors }; chtype emph(coltype); chtype noemph(coltype); extern const chtype *ColorArray; #endif mmail-0.52/interfac/mysystem.cc000644 000765 000024 00000027330 13261410055 017506 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * some low-level routines common to both sides Copyright 1997-2018 William McBrine Distributed under the GNU General Public License, version 3 or later. */ /* Most non-ANSI, non-curses stuff is here. */ #include "interfac.h" #include "error.h" extern "C" { #if defined(__WATCOMC__) || defined(_MSC_VER) # include #else # ifndef USE_FINDFIRST # include # endif #endif #ifdef USE_SPAWNO # include #endif #ifdef LIMIT_MEM # ifdef __WATCOMC__ # include # else # include # endif #endif #ifdef HAS_UNISTD # include #endif #ifdef USE_DIRH # include #endif #include #ifdef HAS_UNAME # include #endif #ifdef USE_SETFTIME # include #else # if defined(__WATCOMC__) || defined(_MSC_VER) # include # else # include # endif #endif #if defined(USE_IOH) || defined(USE_SETFTIME) # include #endif #if defined(__WATCOMC__) || defined(TURBO16) # include #endif #if defined(USE_FINDFIRST) && defined(USE_IOH) # include #endif } #ifndef S_IREAD # define S_IREAD S_IRUSR #endif #ifndef S_IWRITE # define S_IWRITE S_IWUSR #endif #ifndef USE_FINDFIRST static DIR *Dir; #endif void fatalError(const char *description); char *myfgets(char *s, int size, FILE *stream) { char *end = 0; if (!feof(stream) && fgets(s, size, stream)) { end = s + strlen(s) - 1; // Skip any leftovers: if (*end != '\n') while (!feof(stream) && (fgetc(stream) != '\n')); } return end; } int mysystem(const char *cmd) { if (ui && !isendwin()) endwin(); #ifdef USE_SPAWNO int result = mm.resourceObject->getInt(swapOut) ? systemo(mm.resourceObject->get(BaseDir), cmd) : -1; if (-1 == result) result = system(cmd); #else int result = system(cmd); #endif // Non-zero result = error; pause so it can (maybe) be read if (result) napms(2000); if (ui) { #ifdef PDCURSES PDC_set_title(MM_NAME); #endif keypad(stdscr, TRUE); } return result; } int mysystem2(const char *cmd, const char *args) { size_t lencmd = strlen(cmd); char *qargs = canonize(quotespace(args)); char *cmdline = new char[lencmd + strlen(qargs) + 2]; sprintf(cmdline, "%s %s", cmd, qargs); int result = mysystem(cmdline); delete[] cmdline; delete[] qargs; return result; } char *mytmpdir(const char *home) { mystat st; char name[9]; if (mychdir(home)) fatalError("Could not change to temp dir"); do sprintf(name, "work%04x", (rand() & 0xffff)); while (st.init(name)); return canonize(fullpath(home, name)); } char *mytmpnam() { static long tcount = 1; char name[13]; if (tcount > 99999L) fatalError("Out of temporary filenames"); sprintf(name, "tmp%05ld.txt", tcount++); return canonize(fullpath(mm.resourceObject->get(BaseDir), name)); } void edit(const char *reply_filename) { mysystem2(mm.resourceObject->get(editor), reply_filename); } #ifdef __WATCOMC__ void setdisk(int drive) { unsigned total; /* We don't care, but we have to feed it. */ _dos_setdrive((unsigned) ++drive, &total); } #endif int mychdir(const char *pathname) { #ifdef USE_SETDISK if (':' == pathname[1]) setdisk(toupper(pathname[0]) - 'A'); #endif return chdir(pathname); } int mymkdir(const char *pathname) { #ifdef HAS_UNISTD return mkdir(pathname, S_IRWXU); #else return mkdir(pathname); #endif } void myrmdir(const char *pathname) { rmdir(pathname); } char *mygetcwd() { char pathname[256], *result; result = getcwd(pathname, 255); return strdupplus(result ? pathname : "."); } // system name -- results of uname() const char *sysname() { #if defined(__WIN32__) return "Win"; #elif defined(__APPLE__) return "Mac"; #elif defined(__MSDOS__) # ifdef SIXTEENBIT return "XT"; # else return "DOS"; # endif #elif defined(__OS2__) return "OS2"; #elif defined(HAS_UNAME) static struct utsname buf; if (!buf.sysname[0]) uname(&buf); return buf.sysname; #else return "?"; #endif } bool myopendir(const char *dirname) { #ifdef USE_FINDFIRST return !mychdir(dirname); #else return ((Dir = opendir((char *) dirname)) != 0) ? !mychdir(dirname) : false; #endif } const char *myreaddir(mystat &st) { #ifdef USE_FINDFIRST # ifdef USE_IOH // Windows static intptr_t handle; static bool first = true; static struct _finddata_t blk; int result; if (first) { char wildcard[] = "*"; handle = _findfirst(wildcard, &blk); result = (handle == -1) ? -1 : 0; first = false; } else result = _findnext(handle, &blk); if (-1 == result) { if (-1 != handle) _findclose(handle); first = true; return 0; } else { st.init((long) blk.size, blk.time_write, blk.attrib); return blk.name; } # else // DOS(ish) static struct ffblk blk; static bool first = true; int result; if (first) { result = findfirst("*.*", &blk, FA_DIREC); first = false; } else result = findnext(&blk); if (result) { # ifndef __MSDOS__ findclose(&blk); # endif first = true; return 0; } else { st.init(blk.ff_fsize, ((long) blk.ff_ftime << 16) + (long) blk.ff_fdate, blk.ff_attrib); return blk.ff_name; } # endif #else // POSIX static dirent *entry; const char *result = 0; entry = readdir(Dir); if (entry) result = entry->d_name; else closedir(Dir); if (result) st.init(result); return result; #endif } void clearDirectory(const char *DirName) { mystat st; const char *fname; if (myopendir(DirName)) { while ((fname = myreaddir(st)) != 0) if (!st.isdir()) remove(fname); } else { char tmp[512]; sprintf(tmp, "Could not change to %.491s", DirName); fatalError(tmp); } } #ifdef USE_SETFTIME void myutime(const char *fname, time_t now) { struct ftime ut; struct tm tmnow = *localtime(&now); ut.ft_tsec = tmnow.tm_sec >> 1; ut.ft_min = tmnow.tm_min; ut.ft_hour = tmnow.tm_hour; ut.ft_day = tmnow.tm_mday; ut.ft_month = tmnow.tm_mon + 1; ut.ft_year = tmnow.tm_year - 80; int f = open(fname, O_RDWR | O_BINARY); if (f != -1) { setftime(f, &ut); close(f); } } #endif time_t touchFile(const char *fname) { time_t now = time(0); #ifdef USE_SETFTIME myutime(fname, now); #else struct utimbuf ut; ut.actime = ut.modtime = now; utime((char *) fname, &ut); #endif return now; } #ifdef LIMIT_MEM /* Constrain memory allocation according to maximum block size and free memory remaining. Currently used only in the 16-bit MS-DOS port. */ long maxfreemem() { return # ifdef __WATCOMC__ (long) _memmax(); # else // Turbo C++ (long) coreleft(); # endif } long limitmem(long wanted) { long maxavail = maxfreemem(); // Give it a 25% margin maxavail -= (wanted >> 2); if (wanted > maxavail) wanted = maxavail; return wanted; } #endif /* Convert pathnames to "canonical" form (change slashes to backslashes). The "nospace" stuff leaves any parameters unconverted. */ char *canonize(char *sinner) { #ifdef DOSNAMES int i; bool nospace = true; bool inquotes = false; for (i = 0; sinner[i] && nospace; i++) { if ('\"' == sinner[i]) inquotes = !inquotes; else if ('/' == sinner[i]) sinner[i] = '\\'; else if ((' ' == sinner[i]) && !inquotes) nospace = false; } #endif return sinner; } #ifdef HAS_HOME /* Recognize '~' as a substitute for the home directory path, on Unix-like systems. */ const char *homify(const char *raw) { static const char *home = getenv("HOME"); if (home && raw && (raw[0] == '~') && ((raw[1] == '/') || (raw[1] == '\0'))) { static char expanded[512]; sprintf(expanded, "%.255s/%.255s", home, raw + 1); return expanded; } else return raw; } #endif #ifdef USE_SHELL /* Command shell routine -- currently only used in the DOSish ports */ Shell::Shell() { const char *oldprompt = getenv("PROMPT"); if (!oldprompt) oldprompt = "$p$g"; size_t len = strlen(oldprompt) + 13; prompt = new char[len]; sprintf(prompt, "PROMPT=%s[MM] ", oldprompt); putenv(prompt); } Shell::~Shell() { delete[] prompt; } void Shell::out() { mychdir(error.getOrigDir()); touchwin(stdscr); refresh(); mysystem(getenv("COMSPEC")); ui->redraw(); } #endif #ifdef EXTRAPATH /* Add the starting directory and the MMAIL directory to the PATH, mainly for the benefit of Windows, where InfoZip is not standard. (But currently this is enabled for all the DOSish ports.) */ ExtraPath::ExtraPath() { const char *oldpath = getenv("PATH"); if (!oldpath) fatalError("No PATH defined!"); const char *orig = error.getOrigDir(); const char *home = mm.resourceObject->get(homeDir); size_t len = strlen(oldpath) + strlen(orig) + strlen(home) + 8; newpath = new char[len]; sprintf(newpath, "PATH=%s;%s;%s", oldpath, orig, home); putenv(newpath); } ExtraPath::~ExtraPath() { delete[] newpath; } #endif mystat::mystat(const char *fname) { init(fname); } mystat::mystat() { init(); } bool mystat::init(const char *fname) { #ifdef USE_FINDFIRST # ifdef USE_IOH // Windows struct _finddata_t blk; long result = _findfirst((char *) fname, &blk); bool retval = (-1 != result); if (retval) { init((long) blk.size, blk.time_write, blk.attrib); _findclose(result); } else init(); # else // DOS(ish) struct ffblk blk; bool retval = !findfirst(fname, &blk, FA_DIREC); if (retval) init(blk.ff_fsize, ((long) blk.ff_ftime << 16) + (long) blk.ff_fdate, blk.ff_attrib); else init(); # ifndef __MSDOS__ findclose(&blk); # endif # endif #else // POSIX struct stat fileStat; bool retval = !stat((char *) fname, &fileStat); if (retval) { size = fileStat.st_size; date = fileStat.st_mtime; mode = fileStat.st_mode; } else init(); #endif return retval; } #ifdef USE_FINDFIRST # ifdef USE_IOH void mystat::init(long sizeA, time_t dateA, unsigned attrib) { size = sizeA; date = dateA; mode = S_IREAD | ((attrib & _A_RDONLY) ? 0 : S_IWRITE) | ((attrib & _A_SUBDIR) ? S_IFDIR : 0); } # else void mystat::init(long sizeA, long dateA, char ff_attrib) { size = sizeA; date = mktime(getdostime(dateA)); mode = S_IREAD | ((ff_attrib & FA_RDONLY) ? 0 : S_IWRITE) | ((ff_attrib & FA_DIREC) ? S_IFDIR : 0); } # endif #endif void mystat::init() { size = -1; date = (time_t) -1; mode = 0; } bool mystat::isdir() { return !(!(mode & S_IFDIR)); } bool mystat::readable() { return !(!(mode & S_IREAD)); } bool mystat::writeable() { return !(!(mode & S_IWRITE)); } off_t mystat::fsize() { return size; } time_t mystat::fdate() { return date; } void mystat::reset_date(const char *fname) { if (date != (time_t) -1) #ifdef USE_SETFTIME myutime(fname, date); #else { struct utimbuf ut; ut.actime = date; // Should be current time ut.modtime = date; utime((char *) fname, &ut); } #endif } mmail-0.52/interfac/isoconv.cc000644 000765 000024 00000004622 13076204200 017270 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * conversion tables ISO 8859-1 <-> IBM codepage 437 Copyright 1997 Peter Krefting , Toth Istvan Copyright 1998-2017 William McBrine Distributed under the GNU General Public License, version 3 or later. */ /* Original tables by Peter Krefting, modified by William McBrine after DOSEmu's video/terminal.h, by Mark D. Rejhon. */ #include "interfac.h" enum cdirtype {CC_ISOTO437, CC_437TOISO}; bool isoConsole; const char *dos2isotab = "\307\374\351\342\344\340\345\347\352\353\350\357\356\354\304\305" "\311\346\306\364\366\362\373\371\377\326\334\242\243\245\120\146" "\341\355\363\372\361\321\252\272\277\055\254\275\274\241\253\273" ":%&|{{{..{I.'''.``+}-+}}`.**}=**+*+``..**'.#_][~" "a\337\254\266{\363\265t\330\364\326\363o\370En" "=\261><()\367=\260\267\267%\140\262= "; const char *iso2dostab = "\200\201\202\203\204\205\206\207\210\211\212\213\214\215\216\217" "\220\221\222\223\224\225\226\227\230\231\232\233\234\235\236\237 " "\255\233\234$\235|\025\"c\246\256\252-r-\370\361\3753\'\346\024\371,1" "\370\257\254\253/\250AAAA\216\217\222\200E\220EEIIIID\245OOOO\231x" "\231UUU\232Y \341\205\240\203a\204\206\221\207\212\202\210\211\215\241" "\214\213 \244\225\242\223o\224\366\224\227\243\226\201y \230"; char *charconv(char *buf, cdirtype cdir) { const char *ct = (cdir == CC_ISOTO437) ? iso2dostab : dos2isotab; for (char *p = buf; *p; p++) { unsigned char c = *p; if (c & 0x80) *p = ct[c & 0x7f]; } return buf; } char *charconv_in(char *buf) { return (isoConsole ? charconv(buf, CC_437TOISO) : buf); } char *charconv_out(char *buf) { return (isoConsole ? charconv(buf, CC_ISOTO437) : buf); } char *letterconv_in(char *buf) { return (mm.letterList->isLatin() ^ isoConsole) ? charconv(buf, isoConsole ? CC_437TOISO : CC_ISOTO437) : buf; } char *letterconv_out(char *buf) { return (mm.letterList->isLatin() ^ isoConsole) ? charconv(buf, isoConsole ? CC_ISOTO437 : CC_437TOISO) : buf; } char *areaconv_in(char *buf) { return (mm.areaList->isLatin() ^ isoConsole) ? charconv(buf, isoConsole ? CC_437TOISO : CC_ISOTO437) : buf; } char *areaconv_out(char *buf) { return (mm.areaList->isLatin() ^ isoConsole) ? charconv(buf, isoConsole ? CC_ISOTO437 : CC_437TOISO) : buf; } mmail-0.52/interfac/letterw.cc000644 000765 000024 00000073505 13432113576 017317 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * message display Copyright 1996 Kolossvary Tamas Copyright 1997 John Zero Copyright 1997-2019 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #include "interfac.h" LetterWindow::Line::Line() { next = 0; text = 0; attr = Normal; } void LetterWindow::Line::out(FILE *fd) { for (unsigned i = 0; i < length; i++) fputc(text[i], fd); fputc('\n', fd); } LetterWindow::LetterWindow() { linelist = 0; NM.isSet = hidden = rot13 = false; NumOfLines = 0; tagline1[0] = '\0'; To = 0; beepPers = mm.resourceObject->getInt(BeepOnPers); lynxNav = mm.resourceObject->getInt(UseLynxNav); } LetterWindow::~LetterWindow() { delete[] To; } net_address &LetterWindow::PickNetAddr() { Line *line; static net_address result; int i; for (i = 0; i < NumOfLines; i++) if (Origin == linelist[i]->attr) break; if (i != NumOfLines) { line = linelist[i]; i = line->length; char *end = (char *) (line->text) + i; while (((line->text[i - 1]) != '(') && i > 0) i--; // we have the opening bracket while ((i < (int) line->length) && ((line->text[i] < '0') || (line->text[i] > '9'))) i++; // we have the begining of the address char c = *end; *end = '\0'; result = &line->text[i]; *end = c; } else result = mm.letterList->getNetAddr(); return result; } void LetterWindow::ReDraw() { headbar->wtouch(); header->wtouch(); text->wtouch(); statbar->cursor_on(); // to get it out of the way statbar->wtouch(); statbar->cursor_off(); } void LetterWindow::DestroyChain() { if (linelist) { while (NumOfLines) delete linelist[--NumOfLines]; delete[] linelist; linelist = 0; } letter_in_chain = -1; } /* Take a message as returned by getBody() -- the whole thing as one C string, with paragraphs separated by '\n' -- and turn it into a linked list with an array index, wrapping long paragraphs if needed. Also flags hidden and quoted lines. */ void LetterWindow::MakeChain(int columns, bool rejoin) { static const char *seenby = "SEEN-BY:"; const int tabwidth = 8; letter_body *msgBody; Line head, *curr; int x, orgarea = -1; if (mm.areaList->isCollection() && !mm.areaList->isReplyArea()) { orgarea = mm.areaList->getAreaNo(); mm.areaList->gotoArea(mm.letterList->getAreaID()); } bool inet = !(!(mm.areaList->getType() & INTERNET)); DestroyChain(); letter_in_chain = mm.letterList->getCurrent(); curr = &head; bool qpenc = mm.letterList->isQP(); if (hidden) mm.letterList->setQP(false); msgBody = mm.letterList->getBody(); if (hidden) mm.letterList->setQP(qpenc); bool insig = false, skipSeenBy = false; while (msgBody) { unsigned char each; #ifdef BOGUS_WARNING char *src = 0, #else char *src, #endif *dest, *begin; int len, maxcol; lineattr tmpattr; bool wrapped = false; bool end = false, allHidden = msgBody->isHidden(); //#define XFACE #ifdef XFACE // Quick X-Face hack if (allHidden) { src = msgBody->getText(); const char *c = searchstr(src, "x-face: "); if (c) { char tmp[255]; char *xname = mytmpnam(); FILE *f = fopen(xname, "w"); c += 8; bool end = false; while (!end) { fputc(*c++, f); if (!(*c) || (('\n' == *c) && !((' ' == c[1]) || ('\t' == c[1])))) end = true; } fputc('\n', f); fclose(f); sprintf(tmp, "(uncompface -X %s | xv -; rm %s) &", xname, xname); mysystem(tmp); delete[] xname; } } #endif if (!hidden && allHidden) end = true; else { src = msgBody->getText(); letterconv_in(src); } while (!end) { if (!hidden) // skip ^A lines while ((*src == 1) || (skipSeenBy && !strncmp(src, seenby, 8))) { do src++; while (*src && (*src != '\n')); while (*src == '\n') src++; } if (*src) { curr->next = new Line; curr = curr->next; tmpattr = allHidden ? Hidden : Normal; NumOfLines++; if (*src == 1) { // ^A is hidden line marker src++; tmpattr = Hidden; } else if (skipSeenBy && !strncmp(src, seenby, 8)) tmpattr = Hidden; begin = 0; curr->text = dest = src; len = 0; maxcol = columns; while (!end && ((len < maxcol) || (rejoin && (Quoted == tmpattr) && (len < 80)))) { each = *src; if (each == '\n') { if (wrapped) { wrapped = false; // Strip any trailing spaces (1): while (len && (dest[-1] == ' ')) { len--; dest--; } // If the next character indicates a new // paragraph, quote or hidden line, this // is the end of the line; otherwise make // it a space: each = src[1]; if (!each || (each == '\n') || (each == ' ') || (each == '\t') || (each == '>') || (each == 1) || skipSeenBy) { src++; break; } else if (!len) src++; else each = ' '; } else { // if wrapped is false, EOL src++; break; } } switch (each) { case '\t': maxcol -= (tabwidth - (len % tabwidth)); case ' ': // begin == start of last word (for wrapping): begin = 0; if (Normal == tmpattr) if (len >= (maxcol - 1)) wrapped = true; break; case '>': // Quoted line if ((len < 5) && (Normal == tmpattr)) tmpattr = Quoted; break; case ':': if ((src[1] == '-') || (src[1] == ')') || (src[1] == '(')) break; case '|': if ((len == 0) && (Normal == tmpattr)) tmpattr = Quoted; break; default: if (rot13) { if (each >= 'A' && each <= 'Z') each = (each - 'A' + 13) % 26 + 'A'; else if (each >= 'a' && each <= 'z') each = (each - 'a' + 13) % 26 + 'a'; } } if ((each != ' ') && (each != '\t') && !begin) begin = src; if (each) { *dest++ = each; src++; len++; } end = !each; // a 0-terminated string } // Start a new line on a word boundary (if needed): if ((len >= maxcol) && begin && ((src - begin) < maxcol) && !(rejoin && (Quoted == tmpattr))) { x = src - begin; len -= x; while (x--) *--src = *--dest; wrapped = (Hidden != tmpattr); } // Check for sigs: const char *ct = curr->text; if (inet && !insig && (ct[0] == '-') && (ct[1] == '-') && (((ct[2] == ' ') && (len == 3)) || (len == 2))) insig = true; if (insig) tmpattr = Sigline; // Strip any trailing spaces (2): while (len && ((ct[len - 1] == ' ') || (ct[len - 1] == '\t'))) len--; curr->length = len; // Check for taglines, tearlines, and origin lines: if (!inet && (Normal == tmpattr)) { each = *ct; if ((ct[1] == each) && (ct[2] == each) && ((ct[3] == ' ') || (len == 3))) switch (each) { case '.': if (len > 4) tmpattr = Tagline; break; case '-': case '~': tmpattr = Tearline; } else if (!strncmp(ct, " * Origin:", 10)) { tmpattr = Origin; // SEEN-BY: should only appear after Origin: skipSeenBy = true; } } curr->attr = tmpattr; } else end = true; } msgBody = msgBody->next; } linelist = new Line *[NumOfLines]; curr = head.next; x = 0; while (curr) { linelist[x++] = curr; curr = curr->next; } if (orgarea != -1) mm.areaList->gotoArea(orgarea); } void LetterWindow::StatToggle(int mask) { int stat = mm.letterList->getStatus(); stat ^= mask; mm.letterList->setStatus(stat); ui->setAnyRead(); DrawFlags(); } void LetterWindow::MakeChainFixPos() { MakeChain(COLS); if (position >= NumOfLines) position = (NumOfLines > y) ? NumOfLines - y : 0; } void LetterWindow::Draw(bool redo) { if (redo) { rot13 = false; position = 0; tagline1[0] = '\0'; if (!ui->dontRead()) { if (!mm.letterList->getRead()) { mm.letterList->setRead(); // nem ide kene? de. ui->setAnyRead(); } if (beepPers && mm.letterList->isPersonal()) beep(); } } if (letter_in_chain != mm.letterList->getCurrent()) MakeChainFixPos(); DrawHeader(); DrawBody(); DrawStat(); } char *LetterWindow::netAdd(char *tmp) { net_address &na = mm.letterList->getNetAddr(); if (na.isSet) { const char *p = na; if (*p) tmp += sprintf(tmp, (na.isInternet ? " <%s>" : " @ %s"), p); } return tmp; } #define flagattr(x) header->attrib((x) ? C_LHFLAGSHI : C_LHFLAGS) void LetterWindow::DrawFlags() { int stat = mm.letterList->getStatus(); flagattr(mm.letterList->getPrivate()); header->put(2, COLS - 27, "Pvt "); flagattr(stat & MS_READ); header->put(2, COLS - 23, "Read "); flagattr(stat & MS_REPLIED); header->put(2, COLS - 18, "Repl "); flagattr(stat & MS_MARKED); header->put(2, COLS - 13, "Mark "); flagattr(stat & MS_SAVED); header->put(2, COLS - 8, "Save "); header->delay_update(); } void LetterWindow::lineCount() { char tmp[40]; int percent = ((position + y) > NumOfLines) ? 100 : (((long) position + y) * 100 / NumOfLines); header->attrib(C_LHMSGNUM); sprintf(tmp, "%9d/%-10d%3d%% ", position + 1, NumOfLines, percent); header->put(1, COLS - 28, tmp); header->delay_update(); } void LetterWindow::UpdateHeader() { char tmp[256], *p; int orgarea = -1; if (mm.areaList->isCollection() && !mm.areaList->isReplyArea()) { orgarea = mm.areaList->getAreaNo(); mm.areaList->gotoArea(mm.letterList->getAreaID()); } int maxToFromWidth = COLS - 42; if (maxToFromWidth > 255) maxToFromWidth = 255; int maxSubjWidth = COLS - 9; if (maxSubjWidth > 255) maxSubjWidth = 255; if (orgarea != -1) mm.areaList->gotoArea(orgarea); header->attrib(C_LHMSGNUM); sprintf(tmp, "%ld (%d of %d)", mm.letterList->getMsgNum(), mm.letterList->getCurrent() + 1, mm.areaList->getNoOfLetters()); header->put(0, 8, tmp); if (orgarea != -1) mm.areaList->gotoArea(mm.letterList->getAreaID()); header->attrib(C_LHFROM); p = tmp + sprintf(tmp, "%.*s", maxToFromWidth, mm.letterList->getFrom()); if (mm.areaList->isEmail()) netAdd(p); letterconv_in(tmp); header->put(1, 8, tmp); header->attrib(C_LHTO); if (mm.areaList->hasTo()) { p = (char *) mm.letterList->getTo(); if (*p) { p = tmp + sprintf(tmp, "%.*s", maxToFromWidth, p); if (mm.areaList->isReplyArea()) if (p != tmp) netAdd(p); } } else sprintf(tmp, "%.*s", maxToFromWidth, (const char *) mm.letterList->getNetAddr()); letterconv_in(tmp); header->put(2, 8, tmp); header->attrib(C_LHSUBJ); int i = sprintf(tmp, "%.*s", maxSubjWidth, mm.letterList->getSubject()); letterconv_in(tmp); header->put(3, 8, tmp); header->clreol(3, i + 8); header->attrib(C_LHDATE); sprintf(tmp, "%.30s", mm.letterList->getDate()); // Truncate the date to fit, if needed: p = tmp; while (p && (strlen(tmp) > 26)) { p = strrchr(tmp, ' '); if (p) *p = '\0'; } letterconv_in(tmp); header->put(0, COLS - 27, tmp); if (orgarea != -1) mm.areaList->gotoArea(orgarea); DrawFlags(); } void LetterWindow::DrawHeader() { header->Clear(C_LHEADTEXT); header->put(0, 2, "Msg#:"); header->put(0, COLS - 33, "Date:"); header->put(1, 2, "From:"); header->put(1, COLS - 33, "Line:"); if (mm.areaList->hasTo()) header->put(2, 2, " To:"); else header->put(2, 2, "Addr:"); header->put(2, COLS - 33, "Stat: "); header->put(3, 2, "Subj:"); UpdateHeader(); } void LetterWindow::oneLine(int i) { static const coltype useatt[] = { C_LHIDDEN, C_LORIGIN, C_LTEAR, C_LTAGLINE, C_LTAGLINE, C_LQTEXT, C_LTEXT }; int length, z = position + i; if (z < NumOfLines) { Line *curr = linelist[z]; text->attrib(useatt[curr->attr]); if (Tagline == curr->attr) { length = curr->length - 4; if (length > TAGLINE_LENGTH) length = TAGLINE_LENGTH; strncpy(tagline1, &curr->text[4], TAGLINE_LENGTH); tagline1[length] = '\0'; } length = text->put(i, 0, curr->text, curr->length); } else { text->attrib(C_LTEXT); length = 0; } text->clreol(i, length); } void LetterWindow::DrawBody() { lineCount(); for (int i = 0; i < y; i++) oneLine(i); text->delay_update(); } void LetterWindow::DrawStat() { static const char *helpmsg = " F1 or ? - Help "; char format[40], *tmp = new char[COLS + 1]; bool expert = mm.resourceObject->getInt(ExpertMode); const char *pn = mm.resourceObject->get(PacketName); int pnlen = strlen(pn); if (pnlen > 20) pnlen = 20; int maxw = COLS - ((expert ? 4 : 20) + pnlen); bool collflag = false; if (mm.areaList->isCollection()) { if (mm.areaList->isReplyArea()) { maxw -= 10; sprintf(format, " %%.*s | REPLY in: %%-*.*s%%s"); } else { maxw -= 13; sprintf(format, " %%.*s | PERSONAL in: %%-*.*s%%s"); } mm.areaList->gotoArea(mm.letterList->getAreaID()); collflag = true; } else sprintf(format, " %%.*s | %%-*.*s%%s"); const char *s = mm.letterList->getNewsgrps(); sprintf(tmp, format, pnlen, pn, maxw, maxw, s ? s : mm.areaList->getDescription(), expert ? "" : helpmsg); areaconv_in(tmp); if (s && ((int) strlen(s) > maxw)) memcpy(tmp + maxw + pnlen + 1, "...", 3); if (collflag) ui->areas.Select(); statbar->cursor_on(); statbar->put(0, 0, tmp); statbar->delay_update(); statbar->cursor_off(); delete[] tmp; } void LetterWindow::TimeUpdate() { static int mode = mm.resourceObject->getInt(ClockMode); char tmp[6]; time_t now = time(0); if (mode && ((now - lasttime) > 59)) { if (1 == mode) strftime(tmp, 6, "%H:%M", localtime(&now)); else { unsigned long elapsed = (now - starttime) / 60; sprintf(tmp, "%02ld:%02ld", (elapsed / 60) % 100, elapsed % 60); } headbar->put(0, COLS - 6, tmp); headbar->delay_update(); lasttime = now - (now % 60) + ((2 == mode) ? (starttime % 60) : 0); } } void LetterWindow::MakeActive(bool redo) { DestroyChain(); y = LINES - 7; headbar = new Win(1, COLS, 0, C_LBOTTSTAT); header = new Win(5, COLS, 1, C_LHEADTEXT); text = new Win(y, COLS, 6, C_LTEXT); statbar = new Win(1, COLS, LINES - 1, C_LBOTTSTAT); char *tmp = new char[COLS + 1]; int i = sprintf(tmp, " " MM_TOPHEADER, sysname()); tmp[i] = '\0'; headbar->put(0, 0, tmp); headbar->clreol(0, i); headbar->delay_update(); delete[] tmp; lasttime = 0; TimeUpdate(); Draw(redo); } bool LetterWindow::Next() { if (mm.letterList->getActive() < (mm.letterList->noOfActive() - 1)) { ui->letters.Move(KEY_DOWN); mm.letterList->gotoActive(mm.letterList->getActive() + 1); Draw(true); return true; } else { ui->back(); doupdate(); ui->back(); doupdate(); ui->areas.KeyHandle('+'); } return false; } bool LetterWindow::Previous() { if (mm.letterList->getActive() > 0) { ui->letters.Move(KEY_UP); mm.letterList->gotoActive(mm.letterList->getActive() - 1); Draw(true); return true; } else { ui->back(); doupdate(); ui->back(); doupdate(); ui->areas.KeyHandle('-'); } return false; } void LetterWindow::Move(int dir) { switch (dir) { case MM_UP: if (position > 0) { position--; text->wscroll(-1); oneLine(0); text->delay_update(); lineCount(); } break; case MM_DOWN: if (position < NumOfLines - y) { position++; lineCount(); text->wscroll(1); oneLine(y - 1); text->delay_update(); } break; case MM_HOME: position = 0; DrawBody(); break; case MM_END: if (NumOfLines > y) { position = NumOfLines - y; DrawBody(); } break; case 'B': case MM_PPAGE: position -= ((y < position) ? y : position); DrawBody(); break; case 'F': case MM_NPAGE: if (position < NumOfLines - y) { position += y; if (position > NumOfLines - y) position = NumOfLines - y; DrawBody(); } break; } } void LetterWindow::NextDown() { position += y; if (position < NumOfLines) DrawBody(); else Next(); } void LetterWindow::Delete() { DestroyChain(); delete headbar; delete header; delete text; delete statbar; headbar = header = text = statbar = 0; } bool LetterWindow::Save(int stype) { FILE *fd; static const char *ntemplate[] = { "%.8s.%.03d", "%.8s.all", "%.8s.mkd" }; static char keepname[3][128]; char filename[128], oldfname[128]; stype--; if (keepname[stype][0]) strcpy(filename, keepname[stype]); else { switch (stype) { case 2: // Marked case 1: // All sprintf(filename, ntemplate[stype], mm.areaList->getName()); break; case 0: // This one sprintf(filename, ntemplate[0], mm.areaList->getName(), mm.letterList->getCurrent()); } unspace(filename); } strcpy(oldfname, filename); if (ui->savePrompt("Save to file:", filename)) { mychdir(mm.resourceObject->get(SaveDir)); fd = fopen(homify(filename), "at"); if (fd) { int num = mm.letterList->noOfActive(); switch (stype) { case 2: case 1: for (int i = 0; i < num; i++) { mm.letterList->gotoActive(i); if ((stype == 1) || (mm.letterList->getStatus() & MS_MARKED)) write_to_file(fd); } break; case 0: write_to_file(fd); } fclose(fd); if (!stype) MakeChain(COLS); ui->setAnyRead(); } else { char tmp[142]; sprintf(tmp, "%s: Save failed", filename); ui->nonFatalError(tmp); return false; } if (strcmp(filename, oldfname)) strcpy(keepname[stype], filename); return true; } else { ui->nonFatalError("Save aborted"); return false; } } void LetterWindow::write_header_to_file(FILE *fd) { enum {system, area, newsg, date, from, to, addr, subj, items}; static const char *names[items] = {"System", " Area", "Newsgr", " Date", " From", " To", " Addr", " Subj"}; char Header[512], *p; int j; for (j = 0; j < 72; j++) fputc('=', fd); fputc('\n', fd); mm.areaList->gotoArea(mm.letterList->getAreaID()); const char *head[items] = { mm.packet->getBBSName(), mm.areaList->getDescription(), mm.letterList->getNewsgrps(), mm.letterList->getDate(), mm.letterList->getFrom(), mm.letterList->getTo(), mm.letterList->getNetAddr(), mm.letterList->getSubject() }; ui->areas.Select(); head[to + mm.areaList->hasTo()] = 0; if (head[newsg]) head[area] = 0; for (j = 0; j < items; j++) if (head[j]) { p = Header + sprintf(Header, "%.511s", head[j]); if (((j == from) && mm.areaList->isEmail()) || ((j == to) && mm.areaList->isReplyArea())) netAdd(p); letterconv_in(Header); fprintf(fd, " %s: %s\n", names[j], Header); } for (j = 0; j < 72; j++) fputc('-', fd); fputc('\n', fd); } void LetterWindow::write_to_file(FILE *fd) { write_header_to_file(fd); // write chain to file MakeChain(80); for (int i = 0; i < NumOfLines; i++) linelist[i]->out(fd); fputc('\n', fd); // set saved, unmarked -- not part of writing to a file, but anyway int stat = mm.letterList->getStatus(); int oldstat = stat; stat |= MS_SAVED; stat &= ~MS_MARKED; mm.letterList->setStatus(stat); if (stat != oldstat) ui->setAnyRead(); } // For searches (may want to start at position == -1): void LetterWindow::setPos(int x) { position = x; } int LetterWindow::getPos() { return position; } searchret LetterWindow::search(const char *item) { searchret found = False; for (int x = position + 1; (x < NumOfLines) && (found == False); x++) { if (text->keypressed() == 27) { found = Abort; break; } found = searchstr(linelist[x]->text, item, linelist[x]->length) ? True : False; if (found == True) { position = x; if (ui->active() == letter) DrawBody(); } } return found; } void LetterWindow::KeyHandle(int key) { int t_area; switch (key) { case ERR: // no key pressed if (ui->active() == letter) TimeUpdate(); break; #ifdef USE_MOUSE case MM_MOUSE: if (0 == mm_mouse_event.y) Move(KEY_UP); else if ((LINES - 1) == mm_mouse_event.y) Move(KEY_DOWN); else if (mm_mouse_event.y > 5) { if (mm_mouse_event.y > (LINES >> 1)) NextDown(); else Move(KEY_PPAGE); } else if (3 == mm_mouse_event.y) { if (mm_mouse_event.x >= (COLS - 8)) KeyHandle('S'); else if (mm_mouse_event.x >= (COLS - 13)) KeyHandle('M'); else if (mm_mouse_event.x >= (COLS - 18)) KeyHandle('R'); else if (mm_mouse_event.x >= (COLS - 23)) KeyHandle('U'); else if (mm_mouse_event.x >= (COLS - 27)) KeyHandle('N'); } break; #endif case 'D': rot13 = !rot13; MakeChainFixPos(); DrawBody(); break; case 'X': hidden = !hidden; MakeChainFixPos(); DrawBody(); break; case 'I': { bool stripCR = mm.resourceObject->getInt(StripSoftCR); mm.resourceObject->set(StripSoftCR, !stripCR); } MakeChainFixPos(); DrawBody(); break; case 'S': Save(1); DrawFlags(); ReDraw(); break; case '?': case MM_F1: ui->changestate(letter_help); break; case 'V': case 1: // Ctrl-A case 11: // Ctrl-V { int nextAns; bool cont = false; do { Delete(); nextAns = ui->ansiLoop(mm.letterList->getBody(), mm.letterList->getSubject(), mm.letterList->isLatin()); if (nextAns == 1) cont = Next(); else if (nextAns == -1) cont = Previous(); } while (nextAns && cont); } break; case MM_RIGHT: if (lynxNav) break; case MM_PLUS: Next(); break; case MM_LEFT: if (lynxNav) { ui->back(); break; } case MM_MINUS: Previous(); break; case ' ': NextDown(); break; case 6: // Ctrl-F EditLetter(true); ui->redraw(); break; default: if (mm.areaList->isReplyArea()) { switch(key) { case 'R': case 'E': EditLetter(false); ui->redraw(); break; case MM_DEL: case 'K': ui->kill_letter(); break; case 2: // Ctrl-B SplitLetter(); ui->redraw(); break; default: Move(key); } } else { switch (key) { case '\t': ui->letters.Next(); Draw(true); break; case 'M': case 'U': StatToggle((key == 'M') ? MS_MARKED : MS_READ); break; case 'R': // Allow re-editing from here: case 'O': if (mm.letterList->getStatus() & MS_REPLIED) if (EditOriginal()) { ui->redraw(); break; } case 5: case 'E': t_area = ui->areaMenu(); if (t_area != -1) { mm.areaList->gotoArea(t_area); if ((5 == key) || mm.areaList->isEmail()) { if ((5 == key) || ('E' == key)) ui->addressbook(); else { net_address nm = PickNetAddr(); set_Letter_Params(nm, 0); } } EnterLetter(t_area, (5 == key) ? 'E' : key); } break; case 'N': t_area = (mm.areaList->getType() & INTERNET) ? mm.areaList->findInternet() : mm.areaList->findNetmail(); if (t_area != -1) { net_address nm = PickNetAddr(); if (nm.isSet) { set_Letter_Params(nm, 0); EnterLetter(t_area, 'N'); } else ui->nonFatalError("No reply address"); } else ui->nonFatalError("Netmail is not available"); break; case 'T': GetTagline(); break; default: Move(key); } } } } mmail-0.52/interfac/addrbook.cc000644 000765 000024 00000022714 13255076414 017414 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * address book Copyright 1996 Kolossvary Tamas Copyright 1997-2018 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #include "interfac.h" extern "C" int perscomp(const void *a, const void *b) { return strcasecmp((*((Person **) a))->name, (*((Person **) b))->name); } Person::Person(const char *sname, const char *saddr) { if (saddr && (*saddr == 'I')) saddr++; netmail_addr = saddr; setname(sname); killed = false; next = 0; } Person::Person(const char *sname, net_address &naddr) { netmail_addr = naddr; setname(sname); killed = false; next = 0; } Person::~Person() { delete[] name; } void Person::setname(const char *sname) { name = strdupplus(sname); } void Person::dump(FILE *fd) { fprintf(fd, (netmail_addr.isInternet ? "%s\nI%s\n\n" : "%s\n%s\n\n"), name, (const char *) netmail_addr); } AddressBook::AddressBook() { NumOfPersons = NumOfActive = 0; people = living = 0; filter = 0; } AddressBook::~AddressBook() { DestroyChain(); } void AddressBook::MakeActive(bool NoEnterA) { int expmode = mm.resourceObject->getInt(ExpertMode); statetype s = ui->prevactive(); if (s != address) inletter = ((s == letter) || (s == littlearealist)) && !mm.areaList->isReplyArea(); NoEnter = NoEnterA; list_max_y = LINES - (expmode ? 9 : 12); list_max_x = COLS - 6; top_offset = 2; borderCol = C_ADDR1; char tmp[60]; char *p = tmp + sprintf(tmp, "Addresses"); if (NumOfActive > list_max_y) p += sprintf(p, " (%d)", NumOfActive); if (filter) sprintf(p, " | %.20s", filter); int xwidth = list_max_x + 2; list = new InfoWin(LINES - 6, xwidth, 2, borderCol, tmp, C_ADDR2, expmode ? 3 : 6); list->attrib(C_ADDR2); list->put(1, 2, "Name Netmail address"); if (!expmode) { int x = xwidth / 3 + 1; int y = list_max_y + 2; list->horizline(y); list->put(++y, 3, ": Quit addressbook"); list->put(y, x + 3, ": Add new address"); list->put(y, x * 2 + 1, ": search / next"); list->put(++y, 3, ": Kill current address"); list->put(y, x + 3, ": Edit address"); if (inletter) list->put(y, x * 2 + 1, ": address from Letter"); list->attrib(C_ADDR1); list->put(y, 2, "K"); list->put(y, x + 2, "E"); if (inletter) list->put(y, x * 2, "L"); list->put(--y, 2, "Q"); list->put(y, x + 2, "A"); list->put(y, x * 2 - 3, "/, ."); } DrawAll(); } void AddressBook::Delete() { delete list; } bool AddressBook::extrakeys(int key) { switch (key) { case MM_ENTER: SetLetterThings(); break; case 'A': NewAddress(); break; case 'E': ChangeAddress(); break; case MM_DEL: case 'K': kill(); break; case 'L': GetAddress(); } return false; } void AddressBook::setFilter(const char *item) { delete[] filter; filter = strdupplus(item); MakeChain(); if (!NumOfActive) { delete[] filter; filter = 0; MakeChain(); } } void AddressBook::WriteFile() { FILE *fd = fopen(addfname, "wt"); for (int x = 0; x < NumOfPersons; x++) people[x]->dump(fd); fclose(fd); } void AddressBook::kill() { if (highlighted) { if (ui->WarningWindow("Remove this address?")) { highlighted->killed = true; if (position) position--; NumOfPersons--; MakeChain(); WriteFile(); } Delete(); MakeActive(NoEnter); } } void AddressBook::SetLetterThings() { if (!NoEnter && highlighted) ui->letterwindow.set_Letter_Params(highlighted->netmail_addr, highlighted->name); } void AddressBook::Add(const char *Name, net_address &Address) { if (Address.isSet) { bool found = false; // Dupe check; also positions curr at end of list: curr = &head; while (curr->next && !found) { curr = curr->next; found = (curr->netmail_addr == Address) && !strcasecmp(curr->name, Name); } if (!found) { curr->next = new Person(Name, Address); FILE *fd = fopen(addfname, "at"); curr->next->dump(fd); fclose(fd); NumOfPersons++; MakeChain(); active = NumOfPersons; Draw(); } else ui->nonFatalError("Already in addressbook"); } else ui->nonFatalError("No address found"); } void AddressBook::GetAddress() { if (inletter) Add(mm.letterList->getFrom(), ui->letterwindow.PickNetAddr()); } int AddressBook::HeaderLine(ShadowedWin &win, char *buf, int limit, int pos, coltype color) { int getit = win.getstring(pos, 8, buf, limit, color, color); return getit; } int AddressBook::Edit(Person &p) { char NAME[100], NETADD[100]; const int maxitems = 2; int result, current = 0; bool end = false; if (p.netmail_addr.isSet) { sprintf(NAME, "%.99s", p.name); sprintf(NETADD, "%.99s", (const char *) p.netmail_addr); } else NAME[0] = NETADD[0] = '\0'; ShadowedWin add_edit(4, COLS - 4, (LINES >> 1) - 2, C_LETEXT); add_edit.put(1, 2, "Name:"); add_edit.put(2, 2, "Addr:"); add_edit.attrib(C_LEGET1); add_edit.put(1, 8, NAME); add_edit.attrib(C_LEGET2); add_edit.put(2, 8, NETADD); add_edit.update(); do { result = HeaderLine(add_edit, current ? NETADD : NAME, 99, current + 1, current ? C_LEGET2 : C_LEGET1); switch (result) { case 0: end = true; break; case 1: current++; if (current == maxitems) end = true; break; case 2: if (current > 0) current--; break; case 3: if (current < maxitems - 1) current++; } } while (!end); if (result && NAME[0] && NETADD[0]) { p.setname(NAME); p.netmail_addr = NETADD; if (!p.netmail_addr.isSet) result = 0; } else result = 0; return result; } void AddressBook::NewAddress() { Person p; p.netmail_addr.isSet = false; if (Edit(p)) Add(p.name, p.netmail_addr); ui->redraw(); } void AddressBook::ChangeAddress() { if (highlighted) if (Edit(*highlighted)) WriteFile(); ui->redraw(); } void AddressBook::oneLine(int i) { int z = position + i; curr = (z < NumOfActive) ? living[z] : 0; if (z == active) highlighted = curr; char *tmp = list->lineBuf; int x = curr ? sprintf(tmp, " %-31s %s", curr->name, (const char *) curr->netmail_addr) : 0; for (; x < list_max_x; x++) tmp[x] = ' '; tmp[x] = '\0'; DrawOne(i, C_ADDR3); } searchret AddressBook::oneSearch(int x, const char *item, int) { const char *s; s = searchstr(living[x]->name, item); if (!s) s = searchstr(living[x]->netmail_addr, item); return s ? True : False; } int AddressBook::NumOfItems() { return NumOfActive; } void AddressBook::ReadFile() { FILE *fd; char name[256], nmaddr[256], other[256]; bool end = false; fd = fopen(addfname, "rt"); if (fd) { curr = &head; while (!end) { do end = !myfgets(name, sizeof name, fd); while (name[0] == '\n' && !end); end = !myfgets(nmaddr, sizeof nmaddr, fd); if (!end) { strtok(name, "\n"); strtok(nmaddr, "\n"); curr->next = new Person(name, nmaddr); curr = curr->next; NumOfPersons++; } do end = !myfgets(other, sizeof other, fd); while (other[0] != '\n' && !end); } fclose(fd); } MakeChain(); if (NumOfPersons > 1) { qsort(people, NumOfPersons, sizeof(Person *), perscomp); if (NumOfActive > 1) qsort(living, NumOfActive, sizeof(Person *), perscomp); ReChain(); } } void AddressBook::MakeChain() { delete[] people; delete[] living; NumOfActive = 0; if (NumOfPersons) { people = new Person *[NumOfPersons]; living = new Person *[NumOfPersons]; curr = head.next; int c = 0; while (curr) { if (!curr->killed) { people[c++] = curr; if (!filter || (searchstr(curr->name, filter) || searchstr(curr->netmail_addr, filter))) living[NumOfActive++] = curr; } curr = curr->next; } } else people = living = 0; } void AddressBook::ReChain() { head.next = people[0]; for (int c = 0; c < (NumOfPersons - 1); c++) people[c]->next = people[c + 1]; people[NumOfPersons - 1]->next = 0; } void AddressBook::DestroyChain() { while (NumOfPersons) delete people[--NumOfPersons]; delete[] people; } void AddressBook::Init() { addfname = mm.resourceObject->get(AddressFile); ReadFile(); } mmail-0.52/interfac/lettpost.cc000644 000765 000024 00000052307 13432012334 017472 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * message editing (continuation of the LetterWindow class) Copyright 1996 Kolossvary Tamas Copyright 1997 John Zero Copyright 1997-2019 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #include "interfac.h" void LetterWindow::set_Letter_Params(net_address &nm, const char *to) { NM = nm; delete[] To; To = strdupplus(to); } void LetterWindow::QuoteText(FILE *reply) { char TMP[81]; int i; bool inet = !(!(mm.areaList->getType() & INTERNET)); const char *From = mm.letterList->getFrom(); int width = mm.resourceObject->getInt(QuoteWrapCols); if ((width < 20) || (width > 80)) width = 78; width -= inet ? 2 : 6; char c; const char *s, *quotestr = mm.resourceObject->get(inet ? InetQuote : QuoteHead); // Format header: while ((c = *quotestr++) != 0) { if (c != '%') fputc(c, reply); else { s = 0; switch (tolower(*quotestr)) { case 'f': s = From; break; case 't': s = mm.letterList->getTo(); break; case 'd': s = mm.letterList->getDate(); break; case 's': s = mm.letterList->getSubject(); break; case 'a': s = mm.areaList->getDescription(); break; case 'n': s = "\n"; break; case '%': s = "%"; } if (s) { sprintf(TMP, "%.80s", s); letterconv_in(TMP); fputs(TMP, reply); quotestr++; } } } fputs("\n\n", reply); // Set quote initials if necessary: if (!inet) { char initials[4]; // Start off with the first two letters of the name strncpy(initials, From, 2); initials[2] = '\0'; initials[3] = '\0'; // Find the first letters of each of the second and last // words in the name, if available i = 1; for (int j = 1; j < 3; j++) { bool end = false; while (From[i] && !end) { if ((From[i - 1] == ' ') && (From[i] != ' ')) { initials[j] = From[i]; if (j == 1) end = true; } i++; } } letterconv_in(initials); sprintf(TMP, " %s> ", initials); } else strcpy(TMP, "> "); // Output quoted text: MakeChain(width, true); for (i = 0; i < NumOfLines; i++) { Line *curr = linelist[i]; if (!((Sigline == curr->attr) || (!curr->length && (i < (NumOfLines - 1)) && (Sigline == linelist[i + 1]->attr) ))) { fputs(curr->length ? ((Quoted == curr->attr) ? (inet ? ">" : ((curr->text[0] == ' ') ? "" : " ")) : TMP) : (inet ? ">" : ""), reply); curr->out(reply); } } MakeChain(COLS); } int LetterWindow::HeaderLine(ShadowedWin &win, char *buf, int limit, int pos, coltype color) { areaconv_in(buf); int getit = win.getstring(pos, 8, buf, limit, color, color); areaconv_out(buf); return getit; } int LetterWindow::EnterHeader(char *FROM, char *TO, char *SUBJ, bool &privat) { static const char *noyes[] = { "No", "Yes" }; char NETADD[100]; int result, current, maxitems = 2; bool end = false; bool hasNet = mm.areaList->isEmail(); bool hasNews = mm.areaList->isUsenet(); bool hasTo = hasNews || mm.areaList->hasTo(); if (hasNet) { sprintf(NETADD, "%.99s", (const char *) NM); maxitems++; } else NM.isSet = false; if (hasTo) maxitems++; ShadowedWin rep_header(maxitems + 2, COLS - 4, (LINES / 2) - 3, C_LETEXT); rep_header.put(1, 2, "From:"); if (hasTo) rep_header.put(2, 2, " To:"); if (hasNet) rep_header.put(3, 2, "Addr:"); rep_header.put(maxitems, 2, "Subj:"); rep_header.attrib(C_LEGET2); areaconv_in(FROM); rep_header.put(1, 8, FROM, 69); areaconv_out(FROM); if (hasNet && !NM.isSet) { //NM.isInternet = mm.areaList->isInternet(); TO[0] = '\0'; current = 1; } else { if (hasTo) { rep_header.attrib(C_LEGET1); areaconv_in(TO); rep_header.put(2, 8, TO, 69); areaconv_out(TO); } if (hasNet) { rep_header.attrib(C_LEGET2); areaconv_in(NETADD); rep_header.put(3, 8, NETADD, 69); areaconv_out(NETADD); } current = maxitems - 1; } rep_header.update(); do { result = HeaderLine(rep_header, (current == (maxitems - 1)) ? SUBJ : (((current == 2) && hasNet) ? NETADD : ((current && hasTo) ? TO : FROM)), ((current == 1) && hasNews) ? 512 : ((current == (maxitems - 1)) ? mm.areaList->maxSubLen() : (((current == 2) && NM.isInternet) ? 72 : mm.areaList->maxToLen())), current + 1, (current & 1) ? C_LEGET1 : C_LEGET2); switch (result) { case 0: end = true; break; case 1: current++; if (current == maxitems) end = true; break; case 2: if (current > 0) current--; break; case 3: if (current < maxitems - 1) current++; } } while (!end); if (result) { int pmode = (!hasNet ? mm.areaList->hasPublic() : 0) + ((mm.areaList->hasPrivate() && !mm.areaList->isUsenet()) ? 2 : 0); if (hasNet) NM = NETADD; switch (pmode) { case 1: privat = false; break; case 2: privat = true; break; case 3: if (!ui->WarningWindow("Make letter private?", privat ? 0 : noyes)) privat = !privat; } } return result; } long LetterWindow::reconvert(const char *reply_filename) { FILE *src, *dest; char *outname = mytmpnam(); src = fopen(reply_filename, "rt"); dest = fopen(outname, "wt"); fseek(src, 0, SEEK_END); long replen = ftell(src); rewind(src); char *body = new char[((replen > MAXBLOCK) ? MAXBLOCK : replen) + 1]; long totlen = replen; replen = 0; while (totlen > MAXBLOCK) { long blklen = (long) fread(body, 1, MAXBLOCK, src); body[blklen] = '\0'; areaconv_out(body); replen += (long) fwrite(body, 1, blklen, dest); totlen -= blklen; } totlen = (long) fread(body, 1, totlen, src); fclose(src); body[totlen] = '\0'; areaconv_out(body); while (body[totlen - 1] == '\n') totlen--; replen += (long) fwrite(body, 1, totlen, dest); fclose(dest); delete[] body; remove(reply_filename); rename(outname, reply_filename); delete[] outname; return replen; } void LetterWindow::setToFrom(char key, char *TO, char *FROM) { char format[7]; sprintf(format, "%%.%ds", mm.areaList->maxToLen()); bool usealias = mm.areaList->getUseAlias(); if (usealias) { const char *name = mm.packet->getAliasName(); sprintf(FROM, format, name ? name : ""); if (!FROM[0]) usealias = false; } if (!usealias) { const char *name = mm.packet->getLoginName(); sprintf(FROM, format, name ? name : ""); } if (mm.areaList->isUsenet()) { const char *newsgrps = 0; if (key != 'E') { newsgrps = mm.letterList->getFollow(); if (!newsgrps) newsgrps = mm.letterList->getNewsgrps(); } if (!newsgrps) newsgrps = mm.areaList->getDescription(); sprintf(TO, "%.512s", newsgrps); } else if (key == 'E') strcpy(TO, (To ? To : "All")); else if (mm.areaList->isInternet()) { if (key == 'O') { sprintf(TO, format, fromName(mm.letterList->getTo())); NM = fromAddr(mm.letterList->getTo()); } else { const char *rep = mm.letterList->getReply(); sprintf(TO, format, mm.letterList->getFrom()); if (rep) NM = fromAddr(rep); } } else sprintf(TO, format, (key == 'O') ? mm.letterList->getTo() : mm.letterList->getFrom()); } void LetterWindow::EnterLetter(int replyto_area, char key) { FILE *reply; char FROM[74], TO[514], SUBJ[514]; const char *orig_id; mystat fileStat; time_t oldtime; long replen, replyto_num; bool privat; mm.areaList->gotoArea(replyto_area); bool news = mm.areaList->isUsenet(); bool inet = news || mm.areaList->isInternet(); // HEADER setToFrom(key, TO, FROM); if (key == 'E') SUBJ[0] = '\0'; //we don't have subject yet else { const char *s = stripre(mm.letterList->getSubject()); int len = strlen(s); bool useRe = (inet || mm.resourceObject->getInt(ReOnReplies)) && ((len + 4) <= mm.areaList->maxSubLen()); sprintf(SUBJ, useRe ? "Re: %.509s" : "%.512s", s); } privat = (key == 'E') ? false : mm.letterList->getPrivate(); if (!EnterHeader(FROM, TO, SUBJ, privat)) { NM.isSet = false; delete[] To; To = 0; ui->areas.Select(); ui->redraw(); return; } // Don't use refnum if posting in different area: replyto_num = ((key == 'E') || (key == 'N') || (replyto_area != mm.letterList->getAreaID())) ? 0 : mm.letterList->getMsgNum(); orig_id = replyto_num ? mm.letterList->getMsgID() : 0; // BODY char *reply_filename = mytmpnam(); // Quote the old text if (key != 'E') { reply = fopen(reply_filename, "wt"); QuoteText(reply); fclose(reply); } fileStat.init(reply_filename); oldtime = fileStat.fdate(); // Edit the reply edit(reply_filename); ui->areas.Select(); ui->redraw(); // Check if modified fileStat.init(reply_filename); if (fileStat.fdate() == oldtime) if (ui->WarningWindow("Cancel this letter?")) { remove(reply_filename); ui->redraw(); return; } // Mark original as replied if (key != 'E') { int origatt = mm.letterList->getStatus(); mm.letterList->setStatus(origatt | MS_REPLIED); if (!(origatt & MS_REPLIED)) ui->setAnyRead(); } reply = fopen(reply_filename, "at"); // Signature bool sigset = false; const char *sg = mm.resourceObject->get(sigFile); if (sg && *sg) { FILE *s = fopen(sg, "rt"); if (s) { int c; fputs(inet ? "\n-- \n" : "\n", reply); while ((c = fgetc(s)) != EOF) if (c != '\r') fputc(c, reply); fclose(s); sigset = true; } } // Tagline bool useTag = mm.resourceObject->getInt(UseTaglines) && ui->taglines.NumOfItems() && ui->Tagwin(); if (useTag) fprintf(reply, inet ? (sigset ? "\n%s\n" : "\n-- \n%s\n") : "\n... %s\n", ui->taglines.getCurrent()); else if (!inet) fprintf(reply, " \n"); // Tearline (not for Blue Wave -- it does its own) if (!inet) { const char *tear = mm.areaList->getTear(); if (tear) fprintf(reply, "%s\n", tear); } fclose(reply); // Reconvert the text mm.areaList->gotoArea(replyto_area); replen = reconvert(reply_filename); mm.areaList->enterLetter(replyto_area, FROM, news ? "All" : TO, SUBJ, orig_id, news ? TO : 0, replyto_num, privat, NM, reply_filename, replen); delete[] reply_filename; NM.isSet = false; delete[] To; To = 0; ui->areas.Select(); ui->setUnsaved(); ui->redraw(); } void LetterWindow::forward_header(FILE *fd, const char *FROM, const char *TO, const char *SUBJ, int replyto_area, bool is_reply) { enum {area, date, from, to, subj, items}; static const char *names[items] = {"ly in", "ly on", "ly by", "ly to", " subj"}; char Header[512], *p; int j; int oldarea = mm.letterList->getAreaID(); mm.areaList->gotoArea(mm.letterList->getAreaID()); const char *head[items] = { mm.areaList->getDescription(), mm.letterList->getDate(), mm.letterList->getFrom(), mm.letterList->getTo(), mm.letterList->getSubject() }; const char *org[items] = { 0, 0, FROM, TO, SUBJ }; bool use[items]; use[area] = (oldarea != replyto_area); use[date] = !is_reply; for (j = from; j < items; j++) use[j] = !(!strcasecmp(org[j], head[j])); ui->areas.Select(); bool anyused = false; for (j = 0; j < items; j++) if (use[j]) { p = Header + sprintf(Header, "%.511s", head[j]); if (((j == from) && mm.areaList->isEmail()) || ((j == to) && mm.areaList->isReplyArea())) netAdd(p); letterconv_in(Header); fprintf(fd, " * Original%s: %s\n", names[j], Header); anyused = true; } if (anyused) fprintf(fd, "\n"); } void LetterWindow::EditLetter(bool forwarding) { FILE *reply; char FROM[74], TO[514], SUBJ[514]; long siz; int replyto_num, replyto_area; bool privat, newsflag = false; bool is_reply_area = (mm.areaList->getAreaNo() == REPLY_AREA); NM = mm.letterList->getNetAddr(); replyto_area = ui->areaMenu(); if (replyto_area == -1) return; // The refnum is only good for the original area: replyto_num = (replyto_area != mm.letterList->getAreaID()) ? 0 : mm.letterList->getReplyTo(); char *msgid = strdupplus(mm.letterList->getMsgID()); mm.areaList->gotoArea(replyto_area); if (forwarding) { if (mm.areaList->isEmail()) { ui->areas.Select(); ui->addressbook(); mm.areaList->gotoArea(replyto_area); } else { NM.isSet = false; newsflag = mm.areaList->isUsenet(); } setToFrom('E', TO, FROM); sprintf(SUBJ, "%.513s", mm.letterList->getSubject()); privat = false; } else { const char *newsgrps = mm.letterList->getNewsgrps(); newsflag = !(!newsgrps); strcpy(FROM, mm.letterList->getFrom()); sprintf(TO, "%.512s", newsflag ? newsgrps : mm.letterList->getTo()); sprintf(SUBJ, "%.512s", mm.letterList->getSubject()); privat = mm.letterList->getPrivate(); } if (!EnterHeader(FROM, TO, SUBJ, privat)) { NM.isSet = false; ui->areas.Select(); ui->redraw(); return; } DestroyChain(); // current letter's chain reset char *reply_filename = mytmpnam(); reply = fopen(reply_filename, "wt"); if (forwarding) forward_header(reply, FROM, TO, SUBJ, replyto_area, is_reply_area); letter_body *msgBody = mm.letterList->getBody(); while (msgBody) { char *body = msgBody->getText(); int offset = 0; letterconv_in(body); // Can't use MakeChain here, or it will wrap the text; so: if (!hidden) // skip hidden lines at start while (*body == 1) { do { body++; offset++; } while (*body && (*body != '\n')); while (*body == '\n') { body++; offset++; } } fwrite(body, msgBody->getLength() - offset, 1, reply); msgBody = msgBody->next; } fclose(reply); edit(reply_filename); siz = reconvert(reply_filename); if (!forwarding) mm.areaList->killLetter(mm.letterList->getAreaID(), mm.letterList->getMsgNum()); mm.areaList->enterLetter(replyto_area, FROM, newsflag ? "All" : TO, SUBJ, msgid, newsflag ? TO : 0, replyto_num, privat, NM, reply_filename, siz); delete[] reply_filename; delete[] msgid; if (is_reply_area) { mm.letterList->rrefresh(); ui->letters.ResetActive(); } ui->areas.Select(); NM.isSet = false; ui->redraw(); ui->setUnsaved(); } bool LetterWindow::SplitLetter(int lines) { static int eachmax = mm.resourceObject->getInt(MaxLines); if (!lines) { char maxlinesA[55]; sprintf(maxlinesA, "%d", eachmax); if (!ui->savePrompt( "Max lines per part? (WARNING: Split is not reversible!)", maxlinesA) || !sscanf(maxlinesA, "%d", &eachmax)) return false; } unsigned int maxlines = lines ? lines : eachmax; if (maxlines < 20) { ui->nonFatalError("Split at less than 20 lines not allowed"); return false; } MakeChain(80); unsigned int orglines = NumOfLines; unsigned int parts = (orglines - 1) / maxlines + 1; if (parts == 1) return false; NM = mm.letterList->getNetAddr(); int replyto_area = mm.letterList->getAreaID(); int replyto_num = mm.letterList->getReplyTo(); char ORGSUBJ[514], *from, *to, *msgid, *newsgrps; from = strdupplus(mm.letterList->getFrom()); to = strdupplus(mm.letterList->getTo()); msgid = replyto_num ? strdupplus(mm.letterList->getMsgID()) : 0; newsgrps = strdupplus(mm.letterList->getNewsgrps()); sprintf(ORGSUBJ, "%.510s", mm.letterList->getSubject()); unsigned int x = parts; unsigned int padsize = 1; while (x /= 10) padsize++; bool privat = mm.letterList->getPrivate(); unsigned int clines = 0; mm.areaList->killLetter(replyto_area, mm.letterList->getMsgNum()); for (unsigned int partno = 1; partno <= parts; partno++) { FILE *reply; char SUBJ[514]; char *reply_filename = mytmpnam(); unsigned int endline = (orglines > maxlines) ? maxlines: orglines; unsigned long replen = 0; reply = fopen(reply_filename, "wt"); for (unsigned int i = clines; i < (clines + endline); i++) { linelist[i]->out(reply); replen += linelist[i]->length + 1; } fclose(reply); sprintf(SUBJ, "%.499s (%0*d/%d)", ORGSUBJ, padsize, partno, parts); mm.areaList->enterLetter(replyto_area, from, to, SUBJ, msgid, newsgrps, replyto_num, privat, NM, reply_filename, (long) replen); delete[] reply_filename; clines += endline; orglines -= endline; } delete[] newsgrps; delete[] msgid; delete[] to; delete[] from; NM.isSet = false; mm.letterList->rrefresh(); if (!lines) { ui->letters.ResetActive(); ui->areas.Select(); ui->setUnsaved(); } return true; } void LetterWindow::GetTagline() { ui->taglines.EnterTagline(tagline1); ReDraw(); } bool LetterWindow::EditOriginal() { int old_area = mm.letterList->getAreaID(); long old_mnum = mm.letterList->getMsgNum(); int orig_area = mm.areaList->getAreaNo(); int orig_mnum = mm.letterList->getCurrent(); int oldPos = position; position = 0; letter_list *old_list = mm.letterList; mm.areaList->gotoArea(REPLY_AREA); mm.areaList->getLetterList(); ui->areas.ResetActive(); bool found = mm.letterList->findReply(old_area, old_mnum); if (found && ui->WarningWindow("A reply exists. Re-edit it?")) EditLetter(false); else found = false; position = oldPos; delete mm.letterList; mm.letterList = old_list; mm.areaList->gotoArea(orig_area); ui->areas.ResetActive(); mm.letterList->gotoLetter(orig_mnum); ui->letters.ResetActive(); return found; } void LetterWindow::SplitAll(int lines) { letter_list *old_list = 0; statetype state = ui->active(); bool list_is_active = (state == letter) || (state == letterlist); ui->areas.Select(); int orig_area = mm.areaList->getAreaNo(); bool is_reply_area = (orig_area == REPLY_AREA) && list_is_active; if (!is_reply_area) { if (list_is_active) old_list = mm.letterList; mm.areaList->gotoArea(REPLY_AREA); mm.areaList->getLetterList(); ui->areas.ResetActive(); } bool anysplit = false; int last = mm.letterList->noOfActive(); for (int i = 0; i < last; i++) { mm.letterList->gotoActive(i); if (SplitLetter(lines)) { i--; last--; anysplit = true; } } if (is_reply_area) ui->letters.ResetActive(); else { delete mm.letterList; if (list_is_active) mm.letterList = old_list; } mm.areaList->gotoArea(orig_area); ui->areas.ResetActive(); if ((state == letter) && !is_reply_area) MakeChain(COLS); if (anysplit) ui->nonFatalError("Some replies were split"); } mmail-0.52/interfac/isoconv.h000644 000765 000024 00000001237 13076204225 017140 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * conversion tables ISO 8859-1 <-> IBM codepage 437 Copyright 1997 Peter Krefting , Toth Istvan Copyright 1998-2017 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #ifndef CONVTAB_H #define CONVTAB_H extern bool isoConsole; // ISO8859-1 <--> CP437 (DOS) conversion? extern const char *dos2isotab; extern const char *iso2dostab; char *charconv_in(char *); char *charconv_out(char *); char *letterconv_in(char *); char *letterconv_out(char *); char *areaconv_in(char *); char *areaconv_out(char *); #endif mmail-0.52/colors/tuukka.col000644 000765 000024 00000004533 13254716052 017026 0ustar00wmcbrinestaff000000 000000 # ----------------------------------------------------- # Tuukka Lehtinen's MultiMail Color Scheme (Monochrome) # ----------------------------------------------------- Version: 0.51 # Background colors Main_Back: White, Black Main_Border: Black, Black, Bold Main_BottSeparator: White, Black # Bottom help window BottHelp_Descrip: White, Black, Bold BottHelp_Keys: White, Black, Bold # Pop-up help Help_Border: White, Black, Bold Help_Text: White, Black, Bold # Welcome window (vanity plate) Welcome_Border: White, Black, Bold Welcome_Header: White, Black, Bold Welcome_Text: White, Black, Bold # Address book Address_Border: White, Black, Bold Address_Descrip: White, Black, Bold Address_List: White, Black # Warning window Warn_Text: Black, Black, Bold Warn_Keys: White, Black, Bold # Letter window Letter_Text: White, Black, Bold Letter_Quoted: White, Black Letter_Tagline: White, Black, Bold Letter_Tearline: White, Black, Bold Letter_Hidden: White, Black, Bold Letter_Origin: White, Black, Bold Letter_Border: White, Black # Letter header LH_Text: White, Black LH_Msgnum: White, Black, Bold LH_From: White, Black, Bold LH_To: White, Black, Bold LH_Subject: Black, Black, Bold LH_Date: White, Black, Bold LH_FlagsHigh: Black, Black, Bold LH_Flags: White, Black # Packet list Packet_Border: Black, Black, Bold Packet_Header: White, Black, Bold Packet_List: White, Black, Bold # Little area list LittleArea_Header: White, Black, Bold LittleArea_List: White, Black, Bold # Area list Area_Reply: White, Black Area_List: White, Black Area_InfoDescrip: Black, Black, Bold Area_InfoText: White, Black, Bold Area_TopText: White, Black, Bold Area_Border: Black, Black, Bold Area_Header: White, Black, Bold # Header editor HeadEdit_Text: White, Black, Bold HeadEdit_Input1: White, Black HeadEdit_Input2: White, Black, Bold # Save filename input Save_Border: White, Black, Bold Save_Header: White, Black, Bold Save_Input: White, Black, Bold # Letter list LettList_Text: White, Black LettList_Personal: White, Black LettList_Border: Black, Black, Bold LettList_TopText: White, Black, Bold LettList_Area: White, Black, Bold LettList_Header: Black, Black, Bold # Taglines Tag_Border: Black, Black, Bold Tag_Text: White, Black, Bold Tag_Keys: White, Black, Bold Tag_Input1: White, Black, Bold Tag_Input2: White, Black, Bold Tag_List: White, Black, Bold # Shadows Shadow: Black, Black, Bold mmail-0.52/colors/COLORS.md000644 000765 000024 00000006635 13254716342 016355 0ustar00wmcbrinestaff000000 000000 Color files for MultiMail ========================= The color file lets you specify the colors used by MultiMail (on color terminals). When you run MultiMail for the first time, the file "colors" will be automatically generated in the "mmail" directory, listing the default colors. The format for each line is "ItemName: \, \, \", where foreground and background are one of Black, Blue, Green, Cyan, Red, Magenta, Yellow, or White, and the attribute may be Bold or Reverse. Only the first three letters are actually checked, and case is not signifigant. If no color for a given ItemName is defined, the default will be used. Lines beginning with '#' are commented out. "Bold" is mainly used to indicate a bright foreground color, but may also (or alternatively) cause actual boldfacing on some terminals (like xterm). "Reverse" is for the benefit of monochrome terminals (xterm again), so that highlights still show up there. (Yes, color schemes are fairly useless on a monochrome terminal; but with care, this feature lets you set up the color file once, so you can use MultiMail from both color and mono terminals without having to change anything.) You can omit the foreground and background colors, as well as the attribute. (If only one color is specified, it's taken as the foreground color.) If omitted, the last-defined foreground or background color is used. (Note that this does NOT work for the attribute, which is reset with each color. So, if you have an item with the Bold attribute, and want the next item to be exactly the same, you still have to specify "Bold".) I did this because color schemes tend to have a lot of repeated values, especially for the background color. See the included color schemes for examples. Also note that an ItemName with no values after it is NOT the same as an ItemName that's commented out or removed. In the first case, the colors will default to those defined for the previous item. In the second case, the colors used will be the default colors for that item (as found in tradit.col, or the autogenerated "colors" file). You can switch between color schemes by changing the "ColorFile" keyword in your .mmailrc to point to a new file. The example color files included here are: - aqua.col - This is the first color scheme I've made for myself, and it's the one I'm using now. It's based on blue and cyan, with white backgrounds for black text, and just a hint of red. :-) - gilmore.col - Donated by Gary Gilmore. - holger2.col - Donated by Holger Granholm. - ingo.col - Approximately the color scheme used by Ingo Brueckl in his own version of MultiMail. It resembles the version 0.1 color scheme. - tonys.col - Donated by Tony Summerfelt. - toutant.col - Donated by David Toutant. - tradit.col - The standard color scheme used in versions 0.2 through 0.37, based on the version 0.1 scheme, with signifigant alterations by John Zero. - tuukka.col - A monochrome scheme, donated by Tuukka Lehtinen. - ver01.col - Approximately, the original color scheme found in the version 0.1 series, as devised by Kolossvary Tamas. (Some things don't quite translate, due to differences in the screen layout.) I welcome additonal color schemes. If you have an interesting one, please send it to . mmail-0.52/colors/aqua.col000644 000765 000024 00000003346 13254715757 016465 0ustar00wmcbrinestaff000000 000000 # ------------------------------- # Aqua Color Scheme for MultiMail # ------------------------------- # # The first one I've done for myself. Light colors, with Blue and Cyan. # This is what I'm using right now. Version: 0.51 # Background colors Main_Back: Black, Cyan Main_Border: Blue Main_BottSeparator: Magenta # Bottom help window BottHelp_Descrip: Black, Cyan BottHelp_Keys: Red # Pop-up help Help_Border: Blue, Cyan Help_Text: Black # Welcome window (vanity plate) Welcome_Border: Blue, White Welcome_Header: Red Welcome_Text: Black # Address book Address_Border: Blue, Cyan Address_Descrip: Red Address_List: Black # Warning window Warn_Text: Black, Cyan Warn_Keys: Red # Letter window Letter_Text: Black, White Letter_Quoted: Bold Letter_Tagline: Blue Letter_Tearline: Red Letter_Hidden: Letter_Origin: Blue Letter_Border: Reverse # Letter header LH_Text: Blue, Cyan LH_Msgnum: Black LH_From: LH_To: LH_Subject: LH_Date: LH_FlagsHigh: Cyan, Black, Reverse LH_Flags: White, Cyan # Packet list Packet_Border: Blue, White Packet_Header: Red Packet_List: Black # Little area list LittleArea_Header: Blue, Cyan LittleArea_List: Black # Area list Area_Reply: Blue, White Area_List: Black Area_InfoDescrip: Blue Area_InfoText: Black Area_TopText: Red Area_Border: Blue Area_Header: Magenta # Header editor HeadEdit_Text: Blue, Cyan HeadEdit_Input1: Black HeadEdit_Input2: # Save filename input Save_Border: Blue, Cyan Save_Header: Red Save_Input: Black # Letter list LettList_Text: Black, White LettList_Personal: Red LettList_Border: Blue LettList_TopText: Red LettList_Area: Magenta LettList_Header: Red # Taglines Tag_Border: Blue, Cyan Tag_Text: Black Tag_Keys: Red Tag_Input1: Tag_Input2: Black Tag_List: # Shadows Shadow: Black, Black, Bold mmail-0.52/colors/ver01.col000644 000765 000024 00000004156 13254716062 016461 0ustar00wmcbrinestaff000000 000000 # ----------------------------------------------------------- # Version 0.1 MultiMail Color Scheme (after Kolossvary Tamas) # ----------------------------------------------------------- # # This is an approximation of the look of the version 0.1x series. (Some # things aren't translatable.) Version: 0.51 # Background colors Main_Back: White, Black Main_Border: Blue, Bold Main_BottSeparator: Magenta # Bottom help window BottHelp_Descrip: White, Black, Bold BottHelp_Keys: Yellow, Bold # Pop-up help Help_Border: White, Blue, Bold Help_Text: Yellow, Bold # Welcome window (vanity plate) Welcome_Border: Yellow, Blue, Bold Welcome_Header: Bold Welcome_Text: Cyan, Bold # Address book Address_Border: White, Green, Bold Address_Descrip: Green, Bold Address_List: Red # Warning window Warn_Text: White, Red, Bold Warn_Keys: Yellow, Bold # Letter window Letter_Text: Green, Black Letter_Quoted: White Letter_Tagline: Magenta, Bold Letter_Tearline: Red Letter_Hidden: Letter_Origin: White, Bold Letter_Border: Yellow, Red, Bold # Letter header LH_Text: Blue, Black, Bold LH_Msgnum: Red LH_From: Green LH_To: White LH_Subject: Yellow, Bold LH_Date: Yellow, Bold LH_FlagsHigh: Bold LH_Flags: # Packet list Packet_Border: Yellow, Blue, Bold Packet_Header: Green, Bold Packet_List: Cyan, Bold # Little area list LittleArea_Header: White, Blue, Bold LittleArea_List: Yellow, Bold # Area list Area_Reply: Yellow, Black Area_List: White Area_InfoDescrip: Yellow, Bold Area_InfoText: White, Bold Area_TopText: Green, Bold Area_Border: Yellow, Bold Area_Header: Magenta # Header editor HeadEdit_Text: White, Blue, Bold HeadEdit_Input1: Cyan HeadEdit_Input2: Green, Bold # Save filename input Save_Border: White, Red, Bold Save_Header: Bold Save_Input: Yellow, Bold # Letter list LettList_Text: White, Blue LettList_Personal: Green LettList_Border: Yellow, Bold LettList_TopText: White, Bold LettList_Area: Green, Bold LettList_Header: Yellow, Bold LettList_Lines: White # Taglines Tag_Border: Yellow, Yellow, Bold Tag_Text: White, Bold Tag_Keys: Green, Bold Tag_Input1: Bold Tag_Input2: Magenta, Bold Tag_List: Bold # Shadows Shadow: Black, Black, Bold mmail-0.52/colors/gilmore.col000644 000765 000024 00000004406 13254715771 017166 0ustar00wmcbrinestaff000000 000000 # ------------------------------------- # Gary Gilmore's MultiMail Color Scheme # ------------------------------------- Version: 0.51 # Background colors Main_Back: White, Black Main_Border: Blue, Black, Bold Main_BottSeparator: Magenta, Black # Bottom help window BottHelp_Descrip: White, Black, Bold BottHelp_Keys: Yellow, Black, Bold # Pop-up help Help_Border: White, Blue, Bold Help_Text: Yellow, Blue, Bold # Welcome window (vanity plate) Welcome_Border: Yellow, Blue, Bold Welcome_Header: Yellow, Blue, Bold Welcome_Text: Cyan, Blue, Bold # Address book Address_Border: White, Green, Bold Address_Descrip: Green, Green, Bold Address_List: Red, Green # Warning window Warn_Text: White, Red, Bold Warn_Keys: Yellow, Red, Bold # Letter window Letter_Text: Cyan, Black Letter_Quoted: White, Black Letter_Tagline: Cyan, Black Letter_Tearline: Cyan, Black Letter_Hidden: Magenta, Black, Bold Letter_Origin: Cyan, Black Letter_Border: Yellow, Blue, Bold # Letter header LH_Text: White, Blue, Bold LH_Msgnum: Red, Blue, Bold LH_From: Yellow, Blue, Bold LH_To: Yellow, Blue, Bold LH_Subject: Yellow, Blue, Bold LH_Date: White, Blue, Bold LH_FlagsHigh: Yellow, Blue, Bold LH_Flags: White, Blue # Packet list Packet_Border: Yellow, Blue, Bold Packet_Header: Green, Blue, Bold Packet_List: Cyan, Blue, Bold # Little area list LittleArea_Header: White, Blue, Bold LittleArea_List: White, Blue, Bold # Area list Area_Reply: Green, Black Area_List: Cyan, Black Area_InfoDescrip: Yellow, Blue, Bold Area_InfoText: White, Blue, Bold Area_TopText: Yellow, Red, Bold Area_Border: Yellow, Blue, Bold Area_Header: Magenta, Blue, Bold # Header editor HeadEdit_Text: White, Blue, Bold HeadEdit_Input1: Yellow, Black, Bold HeadEdit_Input2: Yellow, Black, Bold # Save filename input Save_Border: White, Red, Bold Save_Header: White, Red, Bold Save_Input: Yellow, Red, Bold # Letter list LettList_Text: White, Blue LettList_Personal: Green, Blue LettList_Border: Yellow, Blue, Bold LettList_TopText: White, Blue, Bold LettList_Area: Green, Blue, Bold LettList_Header: Yellow, Blue, Bold # Taglines Tag_Border: Yellow, Blue, Bold Tag_Text: White, Blue, Bold Tag_Keys: Red, Blue, Bold Tag_Input1: Green, Yellow, Bold Tag_Input2: Yellow, Blue, Bold Tag_List: Yellow, Black, Bold # Shadows Shadow: Black, Black, Bold mmail-0.52/colors/tonys.col000644 000765 000024 00000004443 13254716022 016673 0ustar00wmcbrinestaff000000 000000 # ---------------------------------------- # Tony Summerfelt's MultiMail Color Scheme # ---------------------------------------- Version: 0.51 # Background colors Main_Back: White, Black Main_Border: Cyan, Black, Bold Main_BottSeparator: Cyan, Black, Bold # Bottom help window BottHelp_Descrip: White, Black, Bold BottHelp_Keys: Yellow, Black, Bold # Pop-up help Help_Border: Cyan, Black, Bold Help_Text: Red, Black, Bold # Welcome window (vanity plate) Welcome_Border: Cyan, Black, Bold Welcome_Header: Yellow, Black, Bold Welcome_Text: Red, Black, Bold # Address book Address_Border: Cyan, Black, Bold Address_Descrip: Red, Black, Bold Address_List: Red, Black, Bold # Warning window Warn_Text: Yellow, Red, Bold Warn_Keys: Yellow, Red, Bold # Letter window Letter_Text: Red, Black, Bold Letter_Quoted: Yellow, Black, Bold Letter_Tagline: Cyan, Black, Bold Letter_Tearline: Magenta, Black, Bold Letter_Hidden: Magenta, Black, Bold Letter_Origin: Cyan, Black Letter_Border: Cyan, Black, Bold # Letter header LH_Text: Cyan, Blue, Bold LH_Msgnum: White, Blue, Bold LH_From: Cyan, Blue, Bold LH_To: Cyan, Blue, Bold LH_Subject: Cyan, Blue, Bold LH_Date: White, Blue, Bold LH_FlagsHigh: Yellow, Blue, Bold LH_Flags: White, Blue, Bold # Packet list Packet_Border: Cyan, Black, Bold Packet_Header: Yellow, Black, Bold Packet_List: Red, Black, Bold # Little area list LittleArea_Header: Yellow, Black, Bold LittleArea_List: Red, Black, Bold # Area list Area_Reply: Red, Black Area_List: Red, Black Area_InfoDescrip: Red, Black, Bold Area_InfoText: Red, Black, Bold Area_TopText: Yellow, Black, Bold Area_Border: Cyan, Black, Bold Area_Header: Yellow, Black, Bold # Header editor HeadEdit_Text: Yellow, Black, Bold HeadEdit_Input1: Red, Black, Bold HeadEdit_Input2: Red, Black, Bold # Save filename input Save_Border: Cyan, Black, Bold Save_Header: Red, Black, Bold Save_Input: Red, Black, Bold # Letter list LettList_Text: Red, Black LettList_Personal: Red, Black LettList_Border: Cyan, Black, Bold LettList_TopText: Yellow, Black, Bold LettList_Area: White, Black, Bold LettList_Header: Yellow, Black, Bold # Taglines Tag_Border: Cyan, Black, Bold Tag_Text: Red, Black, Bold Tag_Keys: Yellow, Black, Bold Tag_Input1: Cyan, Blue, Bold Tag_Input2: Cyan, Blue, Bold Tag_List: Red, Black, Bold # Shadows Shadow: Black, Black, Bold mmail-0.52/colors/ingo.col000644 000765 000024 00000004014 13254716011 016443 0ustar00wmcbrinestaff000000 000000 # ------------------------------------- # Ingo Brueckl's MultiMail Color Scheme # ------------------------------------- # # More or less. (Some things don't translate.) Version: 0.51 # Background colors Main_Back: White, Black Main_Border: Blue, Bold Main_BottSeparator: Magenta # Bottom help window BottHelp_Descrip: White, Black, Bold BottHelp_Keys: Yellow, Bold # Pop-up help Help_Border: White, Blue, Bold Help_Text: Yellow, Bold # Welcome window (vanity plate) Welcome_Border: Blue, Black, Bold Welcome_Header: Yellow, Bold Welcome_Text: Cyan, Bold # Address book Address_Border: Red, Black Address_Descrip: White, Bold Address_List: Blue, Bold # Warning window Warn_Text: White, Red, Bold Warn_Keys: Yellow, Bold # Letter window Letter_Text: White, Black Letter_Quoted: Yellow Letter_Tagline: White Letter_Tearline: Blue, Bold Letter_Hidden: Bold Letter_Origin: Green, Bold Letter_Border: Yellow, Magenta, Bold # Letter header LH_Text: White, Cyan, Bold LH_Msgnum: Green, Bold LH_From: Blue LH_To: LH_Subject: Black LH_Date: Green, Bold LH_FlagsHigh: Yellow, Bold LH_Flags: White # Packet list Packet_Border: Yellow, Blue, Bold Packet_Header: Green, Bold Packet_List: Cyan, Bold # Little area list LittleArea_Header: Yellow, Blue, Bold LittleArea_List: White, Bold # Area list Area_Reply: Magenta, Black Area_List: Cyan Area_InfoDescrip: Yellow, Bold Area_InfoText: White, Bold Area_TopText: Bold Area_Border: Blue, Bold Area_Header: Green, Bold # Header editor HeadEdit_Text: White, Blue, Bold HeadEdit_Input1: Cyan, Bold HeadEdit_Input2: Yellow, Bold # Save filename input Save_Border: Yellow, Blue, Bold Save_Header: White, Bold Save_Input: Cyan, Bold # Letter list LettList_Text: Cyan, Blue LettList_Personal: Magenta LettList_Border: Yellow, Bold LettList_TopText: White, Bold LettList_Area: Yellow, Bold LettList_Header: Green, Bold LettList_Lines: Cyan # Taglines Tag_Border: Red, Black Tag_Text: White, Bold Tag_Keys: Yellow, Bold Tag_Input1: Cyan, Bold Tag_Input2: Blue, Bold Tag_List: Bold # Shadows Shadow: Black, Black, Bold mmail-0.52/colors/tradit.col000644 000765 000024 00000004222 13254716041 017002 0ustar00wmcbrinestaff000000 000000 # ---------------------------------------------------- # Traditional MultiMail Color Scheme (after John Zero) # ---------------------------------------------------- # # This is basically the scheme used since version 0.2. It's the same as # the default scheme -- though that may change in the future. Version: 0.51 # Background colors Main_Back: White, Black Main_Border: Blue, Bold Main_BottSeparator: Magenta # Bottom help window BottHelp_Descrip: White, Black, Bold BottHelp_Keys: Yellow, Bold # Pop-up help Help_Border: White, Blue, Bold Help_Text: Yellow, Bold # Welcome window (vanity plate) Welcome_Border: Yellow, Blue, Bold Welcome_Header: Bold Welcome_Text: Cyan, Bold # Address book Address_Border: White, Green, Bold Address_Descrip: Green, Bold Address_List: Red # Warning window Warn_Text: White, Red, Bold Warn_Keys: Yellow, Bold # Letter window Letter_Text: White, Blue, Bold Letter_Quoted: Letter_Tagline: Cyan, Bold Letter_Tearline: Blue, Bold Letter_Hidden: Bold Letter_Origin: Bold Letter_Border: Magenta, White, Reverse # Letter header LH_Text: Blue, Cyan LH_Msgnum: White, Bold LH_From: Green, Bold LH_To: White, Bold LH_Subject: Blue, Bold LH_Date: White, Bold LH_FlagsHigh: Yellow, Bold LH_Flags: White # Packet list Packet_Border: Yellow, Blue, Bold Packet_Header: Green, Bold Packet_List: Cyan, Bold # Little area list LittleArea_Header: White, Blue, Bold LittleArea_List: Bold # Area list Area_Reply: Yellow, Blue Area_List: Cyan Area_InfoDescrip: Yellow, Bold Area_InfoText: White, Bold Area_TopText: Green, Bold Area_Border: Yellow, Bold Area_Header: Magenta, Bold # Header editor HeadEdit_Text: White, Blue, Bold HeadEdit_Input1: Cyan HeadEdit_Input2: Green, Bold # Save filename input Save_Border: White, Red, Bold Save_Header: Bold Save_Input: Yellow, Bold # Letter list LettList_Text: White, Blue LettList_Personal: Green LettList_Border: Yellow, Bold LettList_TopText: White, Bold LettList_Area: Green, Bold LettList_Header: Yellow, Bold LettList_Lines: White # Taglines Tag_Border: Yellow, Yellow, Bold Tag_Text: White, Bold Tag_Keys: Green, Bold Tag_Input1: Bold Tag_Input2: Magenta, Bold Tag_List: Bold # Shadows Shadow: Black, Black, Bold mmail-0.52/colors/toutant.col000644 000765 000024 00000004464 13254716031 017220 0ustar00wmcbrinestaff000000 000000 # -------------------------------------- # David Toutant's MultiMail Color Scheme # -------------------------------------- Version: 0.51 # Background colors Main_Back: White, Black, Bold Main_Border: Blue, Black, Bold Main_BottSeparator: Magenta, Black # Bottom help window BottHelp_Descrip: White, Black, Bold BottHelp_Keys: Yellow, Black, Bold # Pop-up help Help_Border: White, Blue, Bold Help_Text: Yellow, Blue, Bold # Welcome window (vanity plate) Welcome_Border: Blue, Black, Bold Welcome_Header: Yellow, Black, Bold Welcome_Text: Green, Black, Bold # Address book Address_Border: Red, Black Address_Descrip: White, Black, Bold Address_List: Blue, Black, Bold # Warning window Warn_Text: White, Red, Bold Warn_Keys: Yellow, Red, Bold # Letter window Letter_Text: Black, Cyan Letter_Quoted: White, Cyan, Bold Letter_Tagline: Red, Cyan Letter_Tearline: Yellow, Cyan, Bold Letter_Hidden: Blue, Cyan, Bold Letter_Origin: Green, Cyan, Bold Letter_Border: Yellow, Magenta, Bold # Letter header LH_Text: White, Black, Bold LH_Msgnum: Green, Black, Bold LH_From: Magenta, Black, Bold LH_To: White, Black, Bold LH_Subject: Yellow, Black, Bold LH_Date: Green, Black, Bold LH_FlagsHigh: Yellow, Black, Bold LH_Flags: White, Black, Bold # Packet list Packet_Border: Yellow, Blue, Bold Packet_Header: Green, Blue, Bold Packet_List: Cyan, Blue, Bold # Little area list LittleArea_Header: Yellow, Blue, Bold LittleArea_List: White, Blue, Bold # Area list Area_Reply: Yellow, Blue, Bold Area_List: White, Blue, Bold Area_InfoDescrip: Yellow, Blue, Bold Area_InfoText: White, Blue, Bold Area_TopText: White, Blue, Bold Area_Border: Blue, Blue, Bold Area_Header: Green, Blue, Bold # Header editor HeadEdit_Text: White, Blue, Bold HeadEdit_Input1: Cyan, Blue, Bold HeadEdit_Input2: Yellow, Blue, Bold # Save filename input Save_Border: Yellow, Blue, Bold Save_Header: White, Blue, Bold Save_Input: Cyan, Blue, Bold # Letter list LettList_Text: Cyan, Blue LettList_Personal: Yellow, Blue, Bold LettList_Border: Yellow, Blue, Bold LettList_TopText: White, Blue, Bold LettList_Area: Yellow, Blue, Bold LettList_Header: Green, Blue, Bold # Taglines Tag_Border: Red, Black Tag_Text: White, Black, Bold Tag_Keys: Yellow, Black, Bold Tag_Input1: Cyan, Black, Bold Tag_Input2: Blue, Black, Bold Tag_List: Blue, Black, Bold # Shadows Shadow: Black, Black, Bold mmail-0.52/colors/holger2.col000644 000765 000024 00000004450 13254716001 017054 0ustar00wmcbrinestaff000000 000000 # ---------------------------------------- # Holger Granholm's MultiMail Color Scheme # ---------------------------------------- Version: 0.51 # Background colors Main_Back: White, Black Main_Border: Blue, Black, Bold Main_BottSeparator: Magenta, Black # Bottom help window BottHelp_Descrip: White, Black, Bold BottHelp_Keys: Yellow, Black, Bold # Pop-up help Help_Border: White, Blue, Bold Help_Text: Yellow, Blue, Bold # Welcome window (vanity plate) Welcome_Border: Yellow, Blue, Bold Welcome_Header: Yellow, Blue, Bold Welcome_Text: Cyan, Blue, Bold # Address book Address_Border: White, Green, Bold Address_Descrip: Green, Green, Bold Address_List: Red, Green # Warning window Warn_Text: White, Red, Bold Warn_Keys: Yellow, Red, Bold # Letter window Letter_Text: Cyan, Blue, Bold Letter_Quoted: Green, Blue, Bold Letter_Tagline: Magenta, Blue, Bold Letter_Tearline: Red, Blue, Bold Letter_Hidden: Red, Blue Letter_Origin: White, Blue, Bold Letter_Border: Yellow, Red, Bold # Letter header LH_Text: White, Black LH_Msgnum: Red, Black, Bold LH_From: Cyan, Black, Bold LH_To: Green, Black, Bold LH_Subject: Yellow, Black, Bold LH_Date: Yellow, Black, Bold LH_FlagsHigh: Yellow, Black, Bold LH_Flags: Yellow, Black # Packet list Packet_Border: Yellow, Blue, Bold Packet_Header: Green, Blue, Bold Packet_List: Cyan, Blue, Bold # Little area list LittleArea_Header: White, Blue, Bold LittleArea_List: Yellow, Blue, Bold # Area list Area_Reply: Yellow, Black, Bold Area_List: White, Black Area_InfoDescrip: Yellow, Black, Bold Area_InfoText: White, Black, Bold Area_TopText: Cyan, Black, Bold Area_Border: Yellow, Black, Bold Area_Header: White, Black # Header editor HeadEdit_Text: White, Blue, Bold HeadEdit_Input1: Cyan, Blue HeadEdit_Input2: Green, Blue, Bold # Save filename input Save_Border: White, Red, Bold Save_Header: White, Red, Bold Save_Input: Yellow, Red, Bold # Letter list LettList_Text: White, Blue LettList_Personal: Cyan, Blue, Bold LettList_Border: Yellow, Blue, Bold LettList_TopText: White, Blue, Bold LettList_Area: Green, Blue, Bold LettList_Header: Yellow, Blue, Bold # Taglines Tag_Border: Yellow, Yellow, Bold Tag_Text: White, Yellow, Bold Tag_Keys: Green, Yellow, Bold Tag_Input1: Green, Yellow, Bold Tag_Input2: Magenta, Yellow, Bold Tag_List: White, Blue, Bold # Shadows Shadow: Black, Black, Bold mmail-0.52/mmail/opx.h000644 000765 000024 00000002751 13065426117 015600 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * OPX Copyright 1999-2017 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #ifndef OPX_H #define OPX_H #include "pktbase.h" #include "opxstrct.h" class opxpack : public pktbase { ocfgHeader confhead; char *bulletins; char *pstrget(void *); void readBrdinfoDat(); void buildIndices(); void getblk(int, long &, long, unsigned char *&, unsigned char *&); void endproc(letter_header &); public: opxpack(mmail *); ~opxpack(); area_header *getNextArea(); letter_header *getNextLetter(); ocfgHeader *offhead(); const char *oldFlagsName(); bool readOldFlags(); bool saveOldFlags(); }; class opxreply : public pktreply { class upl_opx : public upl_base { public: fidoHead rhead; net_address na; char *msgid; int area; upl_opx(const char * = 0); ~upl_opx(); }; int getArea(const char *); bool getRep1(const char *, upl_opx *); void getReplies(FILE *); const char *freeFileName(upl_opx *); void addRep1(FILE *, upl_base *, int); void addHeader(FILE *); void repFileName(); const char *repTemplate(bool); public: opxreply(mmail *, specific_driver *); ~opxreply(); area_header *getNextArea(); letter_header *getNextLetter(); void enterLetter(letter_header &, const char *, long); bool getOffConfig(); bool makeOffConfig(); }; #endif mmail-0.52/mmail/driverl.cc000644 000765 000024 00000006565 13246536553 016615 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * driver_list Copyright 1996-1997 Toth Istvan Copyright 1998-2018 William McBrine , Robert Vukovic Distributed under the GNU General Public License, version 3 or later. */ #include "bw.h" #include "qwk.h" #include "omen.h" #include "soup.h" #include "opx.h" enum pktype {PKT_QWK, PKT_BW, PKT_OMEN, PKT_SOUP, PKT_OPX, PKT_UNDEF}; // ------------------------------------------------ // Virtual specific_driver and reply_driver methods // ------------------------------------------------ specific_driver::~specific_driver() { } reply_driver::~reply_driver() { } // ------------------ // DriverList methods // ------------------ driver_list::driver_list(mmail *mm) { pktype mode; file_list *wl = mm->workList; // This is the new way to set the packet type if (wl->exists("control.dat") && wl->exists("messages.dat")) mode = PKT_QWK; else if (wl->exists(".inf")) mode = PKT_BW; else if (wl->exists("brdinfo.dat")) mode = PKT_OPX; else if (wl->exists("areas")) mode = PKT_SOUP; else if (wl->exists("system")) mode = PKT_OMEN; else mode = PKT_UNDEF; switch (mode) { case PKT_BW: driverList[1].driver = new bluewave(mm); driverList[0].driver = new bwreply(mm, driverList[1].driver); break; case PKT_QWK: driverList[1].driver = new qwkpack(mm); driverList[0].driver = new qwkreply(mm, driverList[1].driver); break; case PKT_OMEN: driverList[1].driver = new omen(mm); driverList[0].driver = new omenrep(mm, driverList[1].driver); break; case PKT_SOUP: driverList[1].driver = new soup(mm); driverList[0].driver = new souprep(mm, driverList[1].driver); break; case PKT_OPX: driverList[1].driver = new opxpack(mm); driverList[0].driver = new opxreply(mm, driverList[1].driver); break; default: driverList[1].driver = 0; driverList[0].driver = 0; } noOfDrivers = (mode != PKT_UNDEF) ? 2 : 0; if (noOfDrivers) { driverList[1].read = new main_read_class(mm, driverList[1].driver); driverList[0].read = new reply_read_class(mm, driverList[0].driver); } else { driverList[1].read = 0; driverList[0].read = 0; } } driver_list::~driver_list() { while (noOfDrivers--) { delete driverList[noOfDrivers].read; delete driverList[noOfDrivers].driver; } } void driver_list::initRead() { if (driverList[0].read) driverList[0].read->init(); if (driverList[1].read) driverList[1].read->init(); } int driver_list::getNoOfDrivers() const { return noOfDrivers; } specific_driver *driver_list::getDriver(int areaNo) { int c = (areaNo != REPLY_AREA); return driverList[c].driver; } reply_driver *driver_list::getReplyDriver() { return (reply_driver *) driverList[0].driver; } read_class *driver_list::getReadObject(specific_driver *driver) { int c = (driver == driverList[1].driver); return driverList[c].read; } int driver_list::getOffset(specific_driver *driver) { return (driver == driverList[1].driver); } mmail-0.52/mmail/qwk.cc000644 000765 000024 00000065366 13432107551 015741 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * QWK Copyright 1997 John Zero Copyright 1997-2019 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #include "qwk.h" #include "compress.h" unsigned char *onecomp(unsigned char *p, char *dest, const char *comp) { size_t len = strlen(comp); if (!strncasecmp((char *) p, comp, len)) { p += len; while (*p == ' ') p++; int x; for (x = 0; *p && (*p != '\n') && (x < 71); x++) dest[x] = *p++; dest[x] = '\0'; while (*p == '\n') p++; return p; } return 0; } // ----------------------------------------------------------------- // The qheader methods // ----------------------------------------------------------------- bool qheader::init(FILE *datFile) { qwkmsg_header qh; char buf[9], *err; if (!fread(&qh, 1, sizeof qh, datFile)) return false; getQfield(from, qh.from, 25); getQfield(to, qh.to, 25); getQfield(subject, qh.subject, 25); cropesp(from); cropesp(to); cropesp(subject); getQfield(date, qh.date, 8); date[2] = '-'; date[5] = '-'; // To deal with some broken messages strcat(date, " "); getQfield(buf, qh.time, 5); strcat(date, buf); getQfield(buf, qh.refnum, 8); refnum = atol(buf); getQfield(buf, qh.msgnum, 7); cropesp(buf); msgnum = strtol(buf, &err, 10); if (*err) return false; // bogus message getQfield(buf, qh.chunks, 6); msglen = (atol(buf) - 1) << 7; privat = (qh.status == '*') || (qh.status == '+'); // Is this a block of net-status flags? netblock = !qh.status || (qh.status == '\xff'); origArea = getshort(&qh.confLSB); return true; } // Just the fields needed for building the index: bool qheader::init_short(FILE *datFile) { qwkmsg_header qh; char buf[9], *err; long rawlen; if (!fread(&qh, 1, sizeof qh, datFile)) return false; getQfield(to, qh.to, 25); cropesp(to); getQfield(buf, qh.chunks, 6); rawlen = strtol(buf, &err, 10); if ((*err && *err != ' ') || (rawlen < 2)) { netblock = true; // bad header, keep scanning return true; } msglen = (rawlen - 1) << 7; // Is this a block of net-status flags? netblock = !qh.status || (qh.status == '\xff'); origArea = getshort(&qh.confLSB); return true; } // Write the header to a file, except for the length: void qheader::output(FILE *repFile) { qwkmsg_header qh; char buf[10]; size_t sublen, tolen, fromlen; sublen = strlen(subject); if (sublen > 25) sublen = 25; tolen = strlen(to); if (tolen > 25) tolen = 25; fromlen = strlen(from); if (fromlen > 25) fromlen = 25; memset(&qh, ' ', sizeof qh); sprintf(buf, " %-6ld", msgnum); memcpy(qh.msgnum, buf, 7); putshort(&qh.confLSB, msgnum); if (refnum) { sprintf(buf, " %-7ld", refnum); memcpy(qh.refnum, buf, 8); } memcpy(qh.to, to, tolen); memcpy(qh.from, from, fromlen); memcpy(qh.subject, subject, sublen); qh.alive = (char) 0xE1; memcpy(qh.date, date, 8); memcpy(qh.time, &date[9], 5); if (privat) qh.status = '*'; fwrite(&qh, 1, sizeof qh, repFile); } // Pad out with spaces, as necessary, and write the length to the header: void qheader::set_length(FILE *repFile, long headerpos, long curpos) { long length; for (length = curpos - headerpos; (length & 0x7f); length++) fputc(' ', repFile); fseek(repFile, headerpos + CHUNK_OFFSET, SEEK_SET); fprintf(repFile, "%-6ld", (length >> 7)); fseek(repFile, headerpos + length, SEEK_SET); } // ----------------------------------------------------------------- // The QWK methods // ----------------------------------------------------------------- qwkpack::qwkpack(mmail *mmA) : pktbase(mmA) { qwke = !(!mm->workList->exists("toreader.ext")); readControlDat(); readDoorId(); if (qwke) readToReader(); infile = mm->workList->ftryopen("messages.dat"); if (!infile) fatalError("Could not open MESSAGES.DAT"); readIndices(); listBulletins((const char (*)[13]) newsfile, 1); } qwkpack::~qwkpack() { cleanup(); } unsigned long qwkpack::MSBINtolong(unsigned const char *ms) { return ((((unsigned long) ms[0] + ((unsigned long) ms[1] << 8) + ((unsigned long) ms[2] << 16)) | 0x800000L) >> (24 + 0x80 - ms[3])); } area_header *qwkpack::getNextArea() { int cMsgNum = areas[ID].nummsgs; bool x = (areas[ID].num == -1); area_header *tmp = new area_header(mm, ID + 1, areas[ID].numA, areas[ID].name, (x ? "Letters addressed to you" : areas[ID].name), (greekqwk ? (x ? "GreekQWK personal" : "GreekQWK") : (qwke ? (x ? "QWKE personal" : "QWKE") : (x ? "QWK personal" : "QWK"))), areas[ID].attr | hasOffConfig | (cMsgNum ? ACTIVE : 0), cMsgNum, 0, qwke ? 71 : 25, qwke ? 71 : 25); ID++; return tmp; } bool qwkpack::isQWKE() { return qwke; } bool qwkpack::isGreekQWK() { return greekqwk; } bool qwkpack::externalIndex() { const char *p; bool hasNdx; hasPers = !(!mm->workList->exists("personal.ndx")); p = mm->workList->exists(".ndx"); hasNdx = !(!p); if (hasNdx) { struct { unsigned char MSB[4]; unsigned char confnum; } ndx_rec; FILE *idxFile; char fname[13]; if (!hasPers) { areas++; maxConf--; } // Store the size of the .DAT for future comparison as // a check against invalid .NDX entries. fseek(infile, 0, SEEK_END); unsigned long endpoint = (unsigned long) ftell(infile); if (endpoint > 128) endpoint -= 128; while (p) { int x, cMsgNum = 0; memcpy(fname, p, strlen(p) - 4); fname[strlen(p) - 4] = '\0'; x = atoi(fname); if (!x) { if (!strcasecmp(fname, "personal")) x = -1; else if (strcmp(fname, "000")) x = -2; // fname is not a num } if (x != -2) { x = getXNum(x); if (-1 == x) // fname is a num but not a hasNdx = false; // valid conference } if (x >= 0) { idxFile = mm->workList->ftryopen(p); if (idxFile) { cMsgNum = mm->workList->getSize() / ndxRecLen; body[x] = new bodytype[cMsgNum]; for (int y = 0; y < cMsgNum; y++) { // If any .NDX entries appear corrupted, we're // better off aborting this and using the new // (purely .DAT-based) indexing method (in // readIndices()). if (!fread(&ndx_rec, ndxRecLen, 1, idxFile)) { hasNdx = false; break; } unsigned long temp = MSBINtolong(ndx_rec.MSB) << 7; if ((temp < 256) || (temp > endpoint)) { hasNdx = false; // use other method break; } else body[x][y].pointer = temp; } fclose(idxFile); } if (hasNdx) { areas[x].nummsgs = cMsgNum; numMsgs += cMsgNum; } } p = hasNdx ? mm->workList->getNext(".ndx") : 0; } // Clean up after aborting if (!hasNdx) { if (!hasPers) { areas--; maxConf++; } for (int x = 0; x < maxConf; x++) { delete[] body[x]; body[x] = 0; areas[x].nummsgs = 0; } numMsgs = 0; } } return hasNdx; } void qwkpack::readIndices() { int x; body = new bodytype *[maxConf]; for (x = 0; x < maxConf; x++) { body[x] = 0; areas[x].nummsgs = 0; } numMsgs = 0; if (mm->resourceObject->getInt(IgnoreNDX) || !externalIndex()) { ndx_fake base, *tmpndx = &base; long counter; int personal = 0; qheader qHead; fseek(infile, 128, SEEK_SET); while (qHead.init_short(infile)) if (!qHead.netblock) { // skip net-status flags counter = ftell(infile); x = getXNum(qHead.origArea); if (-1 != x) { tmpndx->next = new ndx_fake; tmpndx = tmpndx->next; tmpndx->confnum = x; if (!strcasecmp(qHead.to, LoginName) || (qwke && !strcasecmp(qHead.to, AliasName))) { tmpndx->pers = true; personal++; } else tmpndx->pers = false; tmpndx->pointer = counter; tmpndx->length = 0; // temp areas[x].nummsgs++; numMsgs++; } fseek(infile, qHead.msglen, SEEK_CUR); } initBody(base.next, personal); } } letter_header *qwkpack::getNextLetter() { static net_address nullNet; qheader q; unsigned long pos, rawpos; int areaID, letterID; rawpos = body[currentArea][currentLetter].pointer; pos = rawpos - 128; fseek(infile, pos, SEEK_SET); if (q.init(infile)) { if (areas[currentArea].num == -1) { areaID = getXNum(q.origArea); letterID = getYNum(areaID, rawpos); } else { areaID = currentArea; letterID = currentLetter; } } else areaID = letterID = -1; if ((-1 == areaID) || (-1 == letterID)) return new letter_header(mm, "MESSAGES.DAT", "READING", "ERROR", "ERROR", 0, 0, 0, 0, 0, false, 0, this, nullNet, false); body[areaID][letterID].msgLength = q.msglen; currentLetter++; return new letter_header(mm, q.subject, q.to, q.from, q.date, 0, q.refnum, letterID, q.msgnum, areaID, q.privat, q.msglen, this, nullNet, !(!(areas[areaID].attr & LATINCHAR))); } void qwkpack::getblk(int, long &offset, long blklen, unsigned char *&p, unsigned char *&begin) { int linebreak = greekqwk ? 12 : 227; for (long count = 0; count < blklen; count++) { int kar = fgetc(infile); if (!kar) kar = ' '; *p++ = (kar == linebreak) ? '\n' : kar; if (kar == linebreak) { begin = p; offset = ftell(infile); } } } void qwkpack::postfirstblk(unsigned char *&p, letter_header &mhead) { // Get extended (QWKE-type) info, if available: char extsubj[72]; // extended subject or other line bool anyfound; unsigned char *q; do { anyfound = false; q = onecomp(p, extsubj, "subject:"); if (!q) { q = onecomp(p + 1, extsubj, "@subject:"); if (q) { extsubj[strlen(extsubj) - 1] = '\0'; cropesp(extsubj); } } // For WWIV QWK door: if (!q) q = onecomp(p, extsubj, "title:"); if (q) { p = q; mhead.changeSubject(extsubj); anyfound = true; } // To and From are now checked only in QWKE packets: if (qwke) { q = onecomp(p, extsubj, "to:"); if (q) { p = q; mhead.changeTo(extsubj); anyfound = true; } q = onecomp(p, extsubj, "from:"); if (q) { p = q; mhead.changeFrom(extsubj); anyfound = true; } } } while (anyfound); } void qwkpack::endproc(letter_header &mhead) { // Change to Latin character set, if necessary: checkLatin(mhead); } void qwkpack::readControlDat() { char *p, *q; infile = mm->workList->ftryopen("control.dat"); if (!infile) fatalError("Could not open CONTROL.DAT"); BBSName = strdupplus(nextLine()); // 1: BBS name nextLine(); // 2: city/state nextLine(); // 3: phone# q = nextLine(); // 4: sysop's name size_t slen = strlen(q); if (slen > 6) { p = q + slen - 5; if (!strcasecmp(p, "Sysop")) { if (*--p == ' ') p--; if (*p == ',') *p = '\0'; } } SysOpName = strdupplus(q); q = nextLine(); // 5: doorserno, BBSid strtok(q, ","); p = strtok(0, " "); strncpy(packetBaseName, p, 8); packetBaseName[8] = '\0'; nextLine(); // 6: time & date p = nextLine(); // 7: user's name cropesp(p); LoginName = strdupplus(p); AliasName = strdupplus(p); nextLine(); // 8: blank/any nextLine(); // 9: anything nextLine(); // 10: # messages (unreliable) maxConf = atoi(nextLine()) + 2; // 11: Max conf # areas = new AREAs[maxConf]; areas[0].num = -1; strcpy(areas[0].numA, "PERS"); areas[0].name = strdupplus("PERSONAL"); areas[0].attr = PUBLIC | PRIVATE | COLLECTION; int c; for (c = 1; c < maxConf; c++) { areas[c].num = atoi(nextLine()); // conf # sprintf(areas[c].numA, "%d", areas[c].num); areas[c].name = strdupplus(nextLine()); // conf name areas[c].attr = PUBLIC | PRIVATE; } hello = strdupplus(nextLine()); strncpy(newsfile[0], nextLine(), 12); goodbye = strdupplus(nextLine()); fclose(infile); } void qwkpack::readDoorId() { bool hasAdd = false, hasDrop = false; if (!qwke) strcpy(controlname, "QMAIL"); greekqwk = false; infile = mm->workList->ftryopen("door.id"); if (infile) { const char *s; char tmp[80], *t; t = strdupplus(nextLine()); // DOOR = s = nextLine(); // VERSION = sprintf(tmp, "%s %s", strchr(t, '=') + 2, strchr(s, '=') + 2); delete[] t; DoorProg = strdupplus(tmp); s = nextLine(); // SYSTEM = BBSProg = strdupplus(strchr(s, '=') + 2); while (!feof(infile)) { s = nextLine(); if (!strcasecmp(s, "CONTROLTYPE = ADD")) hasAdd = true; else if (!strcasecmp(s, "CONTROLTYPE = DROP")) hasDrop = true; else if (!strncasecmp(s, "CONTROLNAME", 11)) sprintf(controlname, "%.25s", s + 14); else if (!strcasecmp(s, "GREEKQWK")) greekqwk = true; } fclose(infile); } if (!qwke) hasOffConfig = (hasAdd && hasDrop) ? OFFCONFIG : 0; } // Read the QWKE file TOREADER.EXT void qwkpack::readToReader() { char *s; int cnum; unsigned long attr; hasOffConfig = OFFCONFIG; infile = mm->workList->ftryopen("toreader.ext"); if (infile) { while (!feof(infile)) { s = nextLine(); if (!strncasecmp(s, "area ", 5)) { if (sscanf(s + 5, "%d %s", &cnum, s) == 2) { attr = SUBKNOWN; // If a group is marked subscribed: if (strchr(s, 'a')) attr |= ACTIVE; else if (strchr(s, 'p')) attr |= (ACTIVE | PERSONLY); else if (strchr(s, 'g')) attr |= (ACTIVE | PERSALL); // "Handles" or "Anonymous": if (strchr(s, 'H') || strchr(s, 'A')) attr |= ALIAS; // Public-only/Private-only: if (strchr(s, 'P')) attr |= PRIVATE; else if (strchr(s, 'O')) attr |= PUBLIC; else attr |= (PUBLIC | PRIVATE); // Read-only: if (strchr(s, 'R')) attr |= READONLY; /* Set character set to Latin-1 for Internet or Usenet areas -- but is this the right thing here? */ if (strchr(s, 'U') || strchr(s, 'I')) attr |= LATINCHAR; if (strchr(s, 'E')) attr |= ECHOAREA; areas[getXNum(cnum)].attr = attr; } } else if (!strncasecmp(s, "alias ", 6)) { cropesp(s); delete[] AliasName; AliasName = strdupplus(s + 6); } } fclose(infile); } } const char *qwkpack::ctrlName() { return controlname; } // ----------------------------------------------------------------- // The QWK reply methods // ----------------------------------------------------------------- qwkreply::upl_qwk::upl_qwk(const char *name) : pktreply::upl_base(name) { memset(&qHead, 0, sizeof(qHead)); } qwkreply::qwkreply(mmail *mmA, specific_driver *baseClassA) : pktreply(mmA, baseClassA) { qwke = ((qwkpack *) baseClass)->isQWKE(); greekqwk = ((qwkpack *) baseClass)->isGreekQWK(); } qwkreply::~qwkreply() { } bool qwkreply::getRep1(FILE *rep, upl_qwk *l) { FILE *replyFile; char *p, *q, blk[1280]; if (!l->qHead.init(rep)) return false; replyFile = fopen(l->fname, "wt"); if (!replyFile) return false; long count, length = 0, chunks = l->qHead.msglen >> 7; char linebreak = greekqwk ? ((char) 12) : ((char) 227); bool firstblk = true; while (chunks) { count = (chunks > 10) ? 10 : chunks; chunks -= count; count <<= 7; if (!fread(blk, 1, count, rep)) fatalError("Error reading reply file"); for (p = blk; p < (blk + count); p++) if (*p == linebreak) *p = '\n'; // PI-softcr p = blk; // Get extended (QWKE-type) info, if available: if (firstblk) { firstblk = false; bool anyfound; do { anyfound = false; q = (char *) onecomp((unsigned char *) p, l->qHead.subject, "subject:"); if (q) { p = q; anyfound = true; } if (qwke) { q = (char *) onecomp((unsigned char *) p, l->qHead.to, "to:"); if (q) { p = q; anyfound = true; } q = (char *) onecomp((unsigned char *) p, l->qHead.from, "from:"); if (q) { p = q; anyfound = true; } } } while (anyfound); } q = blk + count - 1; if (!chunks) for (; ((*q == ' ') || (*q == '\n')) && (q > blk); q--); length += (long) fwrite(p, 1, q - p + 1, replyFile); } fclose(replyFile); l->qHead.msglen = l->msglen = length; return true; } void qwkreply::getReplies(FILE *repFile) { fseek(repFile, 128, SEEK_SET); noOfLetters = 0; upl_qwk baseUplList, *currUplList = &baseUplList; while (!feof(repFile)) { currUplList->nextRecord = new upl_qwk; currUplList = (upl_qwk *) currUplList->nextRecord; if (!getRep1(repFile, currUplList)) { delete currUplList; break; } noOfLetters++; } uplListHead = baseUplList.nextRecord; } area_header *qwkreply::getNextArea() { return new area_header(mm, 0, "REPLY", "REPLIES", "Letters written by you", (greekqwk ? "GreekQWK replies" : (qwke ? "QWKE replies" : "QWK replies")), (COLLECTION | REPLYAREA | ACTIVE | PUBLIC | PRIVATE), noOfLetters, 0, qwke ? 71 : 25, qwke ? 71 : 25); } letter_header *qwkreply::getNextLetter() { static net_address nullNet; upl_qwk *current = (upl_qwk *) uplListCurrent; int area = ((qwkpack *) baseClass)-> getXNum((int) current->qHead.msgnum) + 1; letter_header *newLetter = new letter_header(mm, current->qHead.subject, current->qHead.to, current->qHead.from, current->qHead.date, 0, current->qHead.refnum, currentLetter, currentLetter, area, current->qHead.privat, current->qHead.msglen, this, nullNet, mm->areaList->isLatin(area)); currentLetter++; uplListCurrent = uplListCurrent->nextRecord; return newLetter; } void qwkreply::enterLetter(letter_header &newLetter, const char *newLetterFileName, long length) { // Specify the format separately from strftime() to suppress // GCC's Y2K warning: const char *datefmt_qwk = "%m-%d-%y %H:%M"; upl_qwk *newList = new upl_qwk(newLetterFileName); strncpy(newList->qHead.subject, newLetter.getSubject(), sizeof(newList->qHead.subject) - 1); strncpy(newList->qHead.from, newLetter.getFrom(), sizeof(newList->qHead.from) - 1); strncpy(newList->qHead.to, newLetter.getTo(), sizeof(newList->qHead.to) - 1); newList->qHead.msgnum = atol(mm->areaList->getShortName()); newList->qHead.privat = newLetter.getPrivate(); newList->qHead.refnum = newLetter.getReplyTo(); time_t now = time(0); strftime(newList->qHead.date, 15, datefmt_qwk, localtime(&now)); newList->qHead.msglen = newList->msglen = length; addUpl(newList); } void qwkreply::addRep1(FILE *rep, upl_base *node, int) { FILE *replyFile; upl_qwk *l = (upl_qwk *) node; long count = 0; char linebreak = greekqwk ? ((char) 12) : ((char) 227); long headerpos = ftell(rep); l->qHead.output(rep); bool longfrom = strlen(l->qHead.from) > 25; bool longto = strlen(l->qHead.to) > 25; bool longsubj = strlen(l->qHead.subject) > 25; if (longfrom) fprintf(rep, "From: %s%c", l->qHead.from, linebreak); if (longto) fprintf(rep, "To: %s%c", l->qHead.to, linebreak); if (longsubj) fprintf(rep, "Subject: %s%c", l->qHead.subject, linebreak); if (longfrom || longto || longsubj) fprintf(rep, "%c", linebreak); replyFile = fopen(l->fname, "rt"); if (replyFile) { int c, lastsp = 0; while ((c = fgetc(replyFile)) != EOF) { count++; if ((count > 80) && lastsp) { fseek(replyFile, lastsp - count, SEEK_CUR); fseek(rep, lastsp - count, SEEK_CUR); c = '\n'; } if ('\n' == c) { fputc(linebreak, rep); count = lastsp = 0; } else { fputc(c, rep); if (' ' == c) lastsp = count; } } fclose(replyFile); } fputc(linebreak, rep); long curpos = ftell(rep); l->qHead.set_length(rep, headerpos, curpos); } void qwkreply::addHeader(FILE *repFile) { char tmp[129]; sprintf(tmp, "%-128s", baseClass->getBaseName()); fwrite(tmp, 128, 1, repFile); } void qwkreply::repFileName() { int x; const char *basename = baseClass->getBaseName(); for (x = 0; basename[x]; x++) { replyPacketName[x] = tolower(basename[x]); replyInnerName[x] = toupper(basename[x]); } strcpy(replyPacketName + x, ".rep"); strcpy(replyInnerName + x, ".MSG"); } const char *qwkreply::repTemplate(bool offres) { static char buff[30]; sprintf(buff, (offres && qwke) ? "%s TODOOR.EXT" : "%s", replyInnerName); return buff; } bool qwkreply::getOffConfig() { bool status = false; if (qwke) { FILE *olc; upWorkList = new file_list(mm->resourceObject->get(UpWorkDir)); olc = upWorkList->ftryopen("todoor.ext"); if (olc) { char line[128], mode; int areaQWK, areaNo; while (!feof(olc)) { myfgets(line, sizeof line, olc); if (sscanf(line, "AREA %d %c", &areaQWK, &mode) == 2) { areaNo = ((qwkpack *) baseClass)->getXNum(areaQWK) + 1; mm->areaList->gotoArea(areaNo); if (mode == 'D') mm->areaList->Drop(); else mm->areaList->Add(); } } fclose(olc); upWorkList->kill(); status = true; } delete upWorkList; } return status; } bool qwkreply::makeOffConfig() { FILE *todoor = 0; // warning suppression net_address bogus; letter_header *ctrlMsg; const char *myname = 0, *ctrlName = 0; if (qwke) { todoor = fopen("TODOOR.EXT", "wb"); if (!todoor) return false; } else { myname = baseClass->getLoginName(); ctrlName = ((qwkpack *) baseClass)->ctrlName(); } int oldarea = mm->areaList->getAreaNo(); int maxareas = mm->areaList->noOfAreas(); for (int areaNo = 0; areaNo < maxareas; areaNo++) { mm->areaList->gotoArea(areaNo); unsigned long attrib = mm->areaList->getType(); if (attrib & (ADDED | DROPPED)) { if (qwke) fprintf(todoor, "AREA %s %c\r\n", mm->areaList->getShortName(), (attrib & ADDED) ? ((attrib & PERSONLY) ? 'p' : ((attrib & PERSALL) ? 'g' : 'a')) : 'D'); else { ctrlMsg = new letter_header(mm, (attrib & ADDED) ? "ADD" : "DROP", ctrlName, myname, "", 0, 0, 0, 0, areaNo, false, 0, this, bogus, false); enterLetter(*ctrlMsg, "", 0); delete ctrlMsg; if (attrib & ADDED) mm->areaList->Drop(); else mm->areaList->Add(); } } } mm->areaList->gotoArea(oldarea); if (qwke) fclose(todoor); else mm->areaList->refreshArea(); return true; } mmail-0.52/mmail/pktbase.h000644 000765 000024 00000007471 13065426211 016422 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * Packet base class Copyright 1999-2017 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #ifndef PKT_H #define PKT_H #include "mmail.h" class pktbase : public specific_driver { protected: struct bodytype { long pointer, msgLength; } **body; struct AREAs { char *name; int num, nummsgs; unsigned long attr; char numA[10]; // padded to deal with alignment bug (EMX) } *areas; struct ndx_fake { int confnum; long pointer, length; bool pers; ndx_fake *next; }; mmail *mm; letter_body *bodyString; file_header **bulletins; FILE *infile; char packetBaseName[9]; char *LoginName, *AliasName, *BBSName, *SysOpName, *DoorProg, *BBSProg; char *hello, *goodbye; int maxConf, numMsgs, ID, currentArea, currentLetter; unsigned long hasOffConfig; bool hasPers; void cleanup(); void initBody(ndx_fake *, int); int getYNum(int, unsigned long); void checkLatin(letter_header &); const char *getHidden(const char *, char *&); void fidocheck(letter_header &); void listBulletins(const char [][13], int, int = 2); char *nextLine(); virtual void prefirstblk(); virtual void getblk(int, long &, long, unsigned char *&, unsigned char *&); virtual void postfirstblk(unsigned char *&, letter_header &); virtual void endproc(letter_header &); public: pktbase(mmail *); ~pktbase(); int getXNum(int); int getNoOfAreas(); virtual int getNoOfLetters(); void selectArea(int); void resetLetters(); bool hasPersArea(); virtual bool hasPersonal(); virtual bool isLatin(); virtual const char *oldFlagsName(); virtual bool readOldFlags(); virtual bool saveOldFlags(); virtual letter_body *getBody(letter_header &); const char *getLoginName(); const char *getAliasName(); const char *getBBSName(); const char *getSysOpName(); const char *getBBSProg(); const char *getDoorProg(); file_header *getHello(); file_header *getGoodbye(); file_header **getBulletins(); virtual const char *getTear(int); const char *getBaseName(); }; class pktreply : public reply_driver { protected: class upl_base { public: char *fname; upl_base *nextRecord; long msglen; upl_base(const char *); ~upl_base(); } *uplListHead, *uplListCurrent; mmail *mm; pktbase *baseClass; file_list *upWorkList; letter_body *replyText; char replyPacketName[13], replyInnerName[13]; int currentLetter, noOfLetters; bool replyExists; void uncompress(); virtual void getReplies(FILE *) = 0; void readRep(); virtual void repFileName() = 0; void addUpl(upl_base *); virtual void addRep1(FILE *, upl_base *, int) = 0; virtual void addHeader(FILE *) = 0; virtual const char *repTemplate(bool) = 0; public: pktreply(mmail *, specific_driver *); ~pktreply(); bool checkForReplies(); void init(); int getNoOfAreas(); void selectArea(int); int getNoOfLetters(); void resetLetters(); letter_body *getBody(letter_header &); bool hasPersArea(); bool hasPersonal(); bool isLatin(); const char *oldFlagsName(); bool readOldFlags(); bool saveOldFlags(); const char *getLoginName(); const char *getAliasName(); const char *getBBSName(); const char *getSysOpName(); const char *getBBSProg(); const char *getDoorProg(); file_header *getHello(); file_header *getGoodbye(); file_header *getFileList(); file_header **getBulletins(); const char *getTear(int); void killLetter(int); area_header *refreshArea(); bool makeReply(); void deleteReplies(); }; #endif mmail-0.52/mmail/mmail.cc000644 000765 000024 00000010577 13245653165 016241 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * mmail class Copyright 1996 Toth Istvan Copyright 1998-2018 William McBrine , Robert Vukovic Distributed under the GNU General Public License, version 3 or later. */ #include "mmail.h" #include "compress.h" #include "../interfac/error.h" net_address::net_address() { isSet = isInternet = false; zone = 0; inetAddr = 0; } net_address::net_address(net_address &x) { isSet = isInternet = false; zone = 0; inetAddr = 0; copy(x); } net_address::~net_address() { if (isSet && isInternet) delete[] inetAddr; } bool net_address::operator==(net_address &x) { if (isInternet != x.isInternet) return false; if (isInternet) return !strcmp(inetAddr, x.inetAddr); else return (zone == x.zone) && (net == x.net) && (node == x.node) && (point == x.point); } net_address &net_address::operator=(const char *source) { isInternet = source ? !(!strchr(source, '@')) : false; if (isInternet) { delete[] inetAddr; inetAddr = strdupplus(source); isSet = true; } else { if (sscanf(source ? source : "", "%u:%u/%u.%u", &zone, &net, &node, &point) == 3) point = 0; isSet = !(!zone); } return *this; } void net_address::copy(net_address &x) { isSet = x.isSet; if (isSet) { isInternet = x.isInternet; if (isInternet) inetAddr = strdupplus(x.inetAddr); else { zone = x.zone; net = x.net; node = x.node; point = x.point; } } } net_address &net_address::operator=(net_address &x) { copy(x); return *this; } net_address::operator const char *() { static char netText[25]; if (isSet) if (isInternet) return inetAddr; else if (point) sprintf(netText, "%u:%u/%u.%u", zone, net, node, point); else sprintf(netText, "%u:%u/%u", zone, net, node); else netText[0] = '\0'; return netText; } mmail::mmail() { resourceObject = new resource(); } mmail::~mmail() { delete resourceObject; } void mmail::Delete() { delete areaList; delete driverList; delete workList; } // Open a packet pktstatus mmail::selectPacket(const char *packetName) { pktstatus result; const char *x = strrchr(packetName, '/'); if (!x) x = strrchr(packetName, '\\'); if (x) { size_t len = x - packetName; char *fname = new char[len + 1]; strncpy(fname, packetName, len); fname[len] = '\0'; mychdir(error.getOrigDir()); mychdir(fname); delete[] fname; fname = mygetcwd(); resourceObject->set_noalloc(PacketDir, fname); packetName = x + 1; } resourceObject->set(PacketName, packetName); // Uncompression is done here char *fpath = fullpath(resourceObject->get(PacketDir), packetName); if (!resourceObject->get(oldPacketName) || strcmp(packetName, resourceObject->get(oldPacketName))) { resourceObject->set(oldPacketName, packetName); result = uncompressFile(resourceObject, fpath, resourceObject->get(WorkDir), true); if (result != PKT_OK) return result; } delete[] fpath; workList = new file_list(resourceObject->get(WorkDir)); if (!workList->getNoOfFiles()) { delete workList; return PKT_NOFILES; } driverList = new driver_list(this); packet = driverList->getDriver(REPLY_AREA + 1); reply = driverList->getReplyDriver(); if (!driverList->getNoOfDrivers()) { delete driverList; delete workList; return PTYPE_UNK; } return PKT_OK; } // Save last read pointers bool mmail::saveRead() { return driverList->getReadObject(packet)->saveAll(); } // Is there a reply packet? bool mmail::checkForReplies() { return reply->checkForReplies(); } // Create a reply packet bool mmail::makeReply() { return reply->makeReply(); } void mmail::deleteReplies() { reply->deleteReplies(); // to reset the "replyExists" flag (inelegant, I know): checkForReplies(); } void mmail::openReply() { reply->init(); } bool mmail::getOffConfig() { return reply->getOffConfig(); } mmail-0.52/mmail/opx.cc000644 000765 000024 00000060214 13432114254 015726 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * OPX Copyright 1999-2019 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #include "opx.h" #include "compress.h" // ----------------------------------------------------------------- // The OPX methods // ----------------------------------------------------------------- opxpack::opxpack(mmail *mmA) : pktbase(mmA) { readBrdinfoDat(); infile = mm->workList->ftryopen("mail.dat"); if (!infile) fatalError("Could not open MAIL.DAT"); buildIndices(); } opxpack::~opxpack() { cleanup(); } area_header *opxpack::getNextArea() { int cMsgNum = areas[ID].nummsgs; bool x = (areas[ID].num == - 1); area_header *tmp = new area_header(mm, ID + 1, areas[ID].numA, areas[ID].name, (x ? "Letters addressed to you" : areas[ID].name), (x ? "OPX personal" : "OPX"), areas[ID].attr | (cMsgNum ? ACTIVE : 0), cMsgNum, 0, 35, 71); ID++; return tmp; } void opxpack::buildIndices() { FILE *fdxFile = 0; // warning suppression msgHead mhead; fdxHeader fhead; fdxRec frec; int x, totMsgs = 0; body = new bodytype *[maxConf]; for (x = 0; x < maxConf; x++) { body[x] = 0; areas[x].nummsgs = 0; } ndx_fake base, *tmpndx = &base; long counter, length, personal = 0; long mdatlen = mm->workList->getSize(); bool hasFdx = (mm->workList->exists("mail.fdx") != 0); if (hasFdx) { fdxFile = mm->workList->ftryopen("mail.fdx"); if (fdxFile) { if (fread(&fhead, FDX_HEAD_SIZE, 1, fdxFile) && getshort(fhead.PageCount) == 1) { totMsgs = getshort(fhead.RowsInPage); fseek(fdxFile, 4, SEEK_CUR); if (!fread(&frec, FDX_REC_SIZE, 1, fdxFile)) { hasFdx = false; fclose(fdxFile); } } else { // Skip it, we don't understand it hasFdx = false; fclose(fdxFile); } } else hasFdx = false; } numMsgs = 0; while ((hasFdx ? (numMsgs < totMsgs) : fread(&mhead, MSG_HEAD_SIZE, 1, infile))) { bool pers; if (hasFdx) { counter = getlong(frec.offset) + MSG_HEAD_SIZE; x = getXNum(getshort(frec.confnum)); pers = ('D' == frec.msgtype); bool fdxAvail = (numMsgs < (totMsgs - 1)); if (fdxAvail) if (!fread(&frec, FDX_REC_SIZE, 1, fdxFile)) fatalError("Error reading .FDX file"); length = (fdxAvail ? getlong(frec.offset) : mdatlen) - counter; } else { counter = ftell(infile); x = getXNum(getshort(mhead.confnum)); pers = ('D' == mhead.msgtype); length = getshort(mhead.length) - 0xbe; fseek(infile, length, SEEK_CUR); } tmpndx->next = new ndx_fake; tmpndx = tmpndx->next; tmpndx->confnum = x; tmpndx->pers = pers; if (pers) personal++; tmpndx->pointer = counter; tmpndx->length = length; numMsgs++; areas[x].nummsgs++; } if (hasFdx) fclose(fdxFile); initBody(base.next, personal); } letter_header *opxpack::getNextLetter() { msgHead mhead; unsigned long pos, rawpos; int areaID, letterID; rawpos = body[currentArea][currentLetter].pointer; pos = rawpos - MSG_HEAD_SIZE; fseek(infile, pos, SEEK_SET); if (!fread(&mhead, MSG_HEAD_SIZE, 1, infile)) fatalError("Error reading MAIL.DAT"); if (areas[currentArea].num == -1) { areaID = getXNum(getshort(mhead.confnum)); letterID = getYNum(areaID, rawpos); } else { areaID = currentArea; letterID = currentLetter; } net_address na; if (areas[areaID].attr & INTERNET) na = mhead.f.from; else { na.zone = getshort(mhead.f.orig_zone); if (na.zone) { na.net = getshort(mhead.f.orig_net); na.node = getshort(mhead.f.orig_node); na.point = 0; // set from getBody() na.isSet = true; } } bool privat = getshort(mhead.f.attr) & OPX_PRIVATE; char date[30]; strftime(date, 30, "%b %d %Y %H:%M", getdostime(getlong(mhead.f.date_written))); currentLetter++; return new letter_header(mm, mhead.f.subject, mhead.f.to, mhead.f.from, date, 0, getshort(mhead.f.reply), letterID, getshort(mhead.msgnum), areaID, privat, getshort(mhead.length), this, na, !(!(areas[areaID].attr & LATINCHAR))); } void opxpack::getblk(int, long &offset, long blklen, unsigned char *&p, unsigned char *&begin) { int lastkar = -1; for (long count = 0; count < blklen; count++) { int kar = fgetc(infile); if (!kar) kar = ' '; // "Soft CRs" are misused in some OPX packets: if (kar == 0x8d) kar = '\n'; // Line endings are a mess in OPX -- can be // CR, LF, _or_ CR/LF! if ((lastkar == '\r') && (kar != '\n')) { *p++ = '\n'; begin = p; offset = ftell(infile) - 1; } if (kar != '\r') *p++ = kar; if (kar == '\n') { begin = p; offset = ftell(infile); } lastkar = kar; } } void opxpack::endproc(letter_header &mhead) { // Extra header info embedded in the text: char *end; const char *s = getHidden("\001INETORIG ", end); if (s) { net_address &na = mhead.getNetAddr(); na = s; if (end) *end = '\n'; } else fidocheck(mhead); } // Read a Borland Pascal pstring, return a C string char *opxpack::pstrget(void *src) { unsigned len = (unsigned) *((unsigned char *) src); char *dest = new char[len + 1]; strncpy(dest, ((const char *) src) + 1, len); dest[len] = '\0'; return dest; } void opxpack::readBrdinfoDat() { FILE *brdinfoFile, *ocfgFile; brdHeader header; brdRec boardrec; ocfgRec offrec; char *p, *q, tmp[80]; int brdCount, extCount; bool hasExtra; ocfgFile = mm->workList->ftryopen("dusrcfg.dat"); if (ocfgFile) { if (fread(&confhead, OCFG_HEAD_SIZE, 1, ocfgFile)) hasOffConfig = OFFCONFIG; else { hasOffConfig = 0; fclose(ocfgFile); } } else hasOffConfig = 0; brdinfoFile = mm->workList->ftryopen("brdinfo.dat"); if (!brdinfoFile) fatalError("Could not open BRDINFO.DAT"); if (!fread(&header, BRD_HEAD_SIZE, 1, brdinfoFile)) fatalError("Error reading BRDINFO.DAT"); p = pstrget(&header.bbsid); strcpy(packetBaseName, p); delete[] p; BBSName = pstrget(&header.bbsname); SysOpName = pstrget(&header.sysopname); BBSProg = pstrget(&header.bbstype); p = pstrget(&header.doorid); q = pstrget(&header.doorver); sprintf(tmp, "%s %s", p, q); DoorProg = strdupplus(tmp); delete[] q; delete[] p; LoginName = pstrget(&header.username); AliasName = strdupplus(LoginName); bulletins = header.readerfiles ? new char[header.readerfiles * 13] : 0; int c; for (c = 0; c < header.readerfiles; c++) { pstring(readerf,12); if (!fread(&readerf, 13, 1, brdinfoFile)) fatalError("Error reading BRDINFO.DAT"); strncpy(bulletins + c * 13, (char *) readerf + 1, *readerf); bulletins[c * 13 + *readerf] = '\0'; } if (header.readerfiles > 1) { listBulletins((const char (*)[13]) (bulletins + 13), header.readerfiles - 1, 1); hello = strdupplus(bulletins); } else listBulletins(0, 0, 1); // Skip old numofareas byte: fseek(brdinfoFile, 1, SEEK_CUR); maxConf = getshort(header.numofareas) + 1; areas = new AREAs[maxConf]; hasExtra = (mm->workList->exists("extareas.dat") != 0); extCount = hasExtra ? (mm->workList->getSize() / BRD_REC_SIZE) : 0; brdCount = maxConf - extCount; areas[0].num = -1; strcpy(areas[0].numA, "PERS"); areas[0].name = strdupplus("PERSONAL"); areas[0].attr = PUBLIC | PRIVATE | COLLECTION; for (c = 1; c < maxConf; c++) { if (hasExtra && (c == brdCount)) { fclose(brdinfoFile); brdinfoFile = mm->workList->ftryopen("extareas.dat"); if (!brdinfoFile) fatalError("Could not open EXTAREAS.DAT"); } if (!fread(&boardrec, BRD_REC_SIZE, 1, brdinfoFile)) fatalError("Error reading BRDINFO.DAT"); areas[c].num = getshort(boardrec.confnum); sprintf(areas[c].numA, "%d", areas[c].num); areas[c].name = pstrget(&boardrec.name); bool selected = !(!boardrec.scanned); if (hasOffConfig) { if (fread(&offrec, OCFG_REC_SIZE, 1, ocfgFile) && getshort(offrec.confnum) == (unsigned) areas[c].num) selected = !(!offrec.scanned); } unsigned rawattr = getshort(boardrec.attrib); areas[c].attr = (((rawattr & OPX_NETMAIL) | (boardrec.attrib2 & OPX_INTERNET)) ? NETMAIL : 0) | ((rawattr & OPX_PRIVONLY) ? 0 : PUBLIC) | ((rawattr & OPX_PUBONLY) ? 0 : PRIVATE) | (((boardrec.attrib2 & OPX_USENET) | (boardrec.attrib2 & OPX_INTERNET)) ? (INTERNET | LATINCHAR) : 0) | (selected ? ACTIVE : 0) | hasOffConfig | (hasOffConfig ? SUBKNOWN : 0); } fclose(brdinfoFile); if (hasOffConfig) fclose(ocfgFile); } ocfgHeader *opxpack::offhead() { return &confhead; } const char *opxpack::oldFlagsName() { return "mail.fdx"; } // Read in an .FDX file bool opxpack::readOldFlags() { FILE *fdxFile; fdxHeader fhead; fdxRec frec; int totmsgs, area = -1, lastarea, msgnum; letter_list *ll = 0; fdxFile = mm->workList->ftryopen(oldFlagsName()); if (!fdxFile) return false; if (!fread(&fhead, FDX_HEAD_SIZE, 1, fdxFile) || getshort(fhead.PageCount) != 1) { fclose(fdxFile); return false; } fseek(fdxFile, 4, SEEK_CUR); totmsgs = getshort(fhead.RowsInPage); area_list *al = mm->areaList; for (int x = 0; x < totmsgs; x++) { if (!fread(&frec, FDX_REC_SIZE, 1, fdxFile)) fatalError("Error reading .FDX file"); lastarea = area; area = getshort(frec.confnum); msgnum = getshort(frec.msgnum); if (lastarea != area) { delete ll; al->gotoArea(getXNum(area) + 1); al->getLetterList(); ll = mm->letterList; ll->gotoLetter(-1); } ll->findMsgNum(msgnum); int stat = ((frec.flags & FDX_READ) ? MS_READ : 0) | ((frec.flags & FDX_REPLIED) ? MS_REPLIED : 0) | ((frec.marks & FDX_TAGGED) ? MS_MARKED : 0) | ((frec.msgtype == 'D') ? MS_PERSTO : 0); ll->setStatus(stat); } delete ll; fclose(fdxFile); return true; } // Write out an .FDX file bool opxpack::saveOldFlags() { FILE *fdxFile; // If there are more messages than will fit in a 64K block, // it would need multiple pages, so forget it: if (((long) numMsgs * FDX_REC_SIZE) > 0x10000) return false; fdxFile = fopen(oldFlagsName(), "wb"); if (!fdxFile) return false; // Fill the header and write it out: fdxHeader fhead; putshort(fhead.RowsInPage, numMsgs); putshort(fhead.ColsInPage, 1); putshort(fhead.PagesDown, 1); putshort(fhead.PagesAcross, 1); putshort(fhead.ElSize, FDX_REC_SIZE); putshort(fhead.PageSize, numMsgs * FDX_REC_SIZE); putshort(fhead.PageCount, 1); putlong(fhead.NextAvail, (long) numMsgs * FDX_REC_SIZE + FDX_HEAD_SIZE + 4); memcpy(fhead.ID, "\006VARRAY", 7); fwrite(&fhead, FDX_HEAD_SIZE, 1, fdxFile); plong addr; putlong(addr, FDX_HEAD_SIZE + 4); fwrite(addr, 4, 1, fdxFile); // And now, the body: area_list *al = mm->areaList; int maxareas = al->noOfAreas(); for (int c = 0; c < maxareas; c++) { al->gotoArea(c); if (!al->isCollection()) { al->getLetterList(); letter_list *ll = mm->letterList; for (int d = 0; d < ll->noOfLetter(); d++) { ll->gotoLetter(d); int stat = ll->getStatus(); fdxRec frec; int anum = atoi(al->getShortName()); putshort(frec.confnum, anum); putshort(frec.msgnum, ll->getMsgNum()); long offset = body[c - 1][d].pointer - MSG_HEAD_SIZE; putlong(frec.offset, offset); frec.flags = ((stat & MS_READ) ? FDX_READ : 0) | ((stat & MS_REPLIED) ? FDX_REPLIED : 0); frec.marks = (stat & MS_MARKED) ? FDX_TAGGED : 0; frec.msgtype = ((stat & MS_PERSTO) ? 'D' : ' '); fwrite(&frec, FDX_REC_SIZE, 1, fdxFile); } delete ll; } } fclose(fdxFile); return true; } // ----------------------------------------------------------------- // The OPX reply methods // ----------------------------------------------------------------- opxreply::upl_opx::upl_opx(const char *name) : pktreply::upl_base(name) { memset(&rhead, 0, sizeof(rhead)); msgid = 0; } opxreply::upl_opx::~upl_opx() { delete[] msgid; } opxreply::opxreply(mmail *mmA, specific_driver *baseClassA) : pktreply(mmA, baseClassA) { } opxreply::~opxreply() { } int opxreply::getArea(const char *fname) { int sum = 0; fname = strchr(fname, '.') + 1; for (int x = 0; x < 3; x++) { sum *= 36; unsigned char c = toupper(fname[x]); if ((c >= '0') && (c <= '9')) sum += c - '0'; else if ((c >= 'A') && (c <= 'Z')) sum += c - 'A' + 10; } return sum; } bool opxreply::getRep1(const char *orgname, upl_opx *l) { FILE *orgfile, *destfile; int c; long count = 0; orgfile = fopen(orgname, "rb"); if (orgfile) { if (!fread(&l->rhead, FIDO_HEAD_SIZE, 1, orgfile)) fatalError("Error reading reply file"); l->area = getArea(orgname); net_address na; na.zone = getshort(l->rhead.dest_zone); if (na.zone) { na.net = getshort(l->rhead.dest_net); na.node = getshort(l->rhead.dest_node); na.point = 0; na.isSet = true; } destfile = fopen(l->fname, "wt"); if (destfile) { while ((c = fgetc(orgfile)) != EOF) { if (c == '\001') { c = fgetc(orgfile); int x; bool isReply = (c == 'R'), isPoint = (c == 'T'), isInet = (c == 'I'); if (isReply || isPoint || isInet) { for (x = 0; x < (isInet ? 8 : (isPoint ? 4 : 6)); x++) fgetc(orgfile); } x = 0; while ((c != EOF) && (c != '\n')) { c = fgetc(orgfile); x++; } if (isReply || isPoint || isInet) { char *tmp = new char[x]; fseek(orgfile, x * -1, SEEK_CUR); if (!fread(tmp, 1, x, orgfile)) fatalError("Error reading reply file"); strtok(tmp, "\r"); if (isReply) l->msgid = tmp; else { if (isInet) na = tmp; else sscanf(tmp, "%u", &na.point); delete[] tmp; } } c = '\r'; } if (c && (c != '\r')) { fputc(c, destfile); count++; } } fclose(destfile); } l->na = na; fclose(orgfile); } l->msglen = count; remove(orgname); return true; } void opxreply::getReplies(FILE *) { noOfLetters = 0; upl_opx baseUplList, *currUplList = &baseUplList; const char *p; upWorkList->gotoFile(-1); while ((p = upWorkList->getNext("!")) != 0) { currUplList->nextRecord = new upl_opx; currUplList = (upl_opx *) currUplList->nextRecord; if (!getRep1(p, currUplList)) { delete currUplList; break; } noOfLetters++; } uplListHead = baseUplList.nextRecord; } area_header *opxreply::getNextArea() { return new area_header(mm, 0, "REPLY", "REPLIES", "Letters written by you", "OPX replies", (COLLECTION | REPLYAREA | ACTIVE | PUBLIC | PRIVATE), noOfLetters, 0, 35, 71); } letter_header *opxreply::getNextLetter() { upl_opx *current = (upl_opx *) uplListCurrent; char date[30]; strftime(date, 30, "%b %d %Y %H:%M", getdostime(getlong(current->rhead.date_written))); int area = ((opxpack *) baseClass)->getXNum(current->area) + 1; letter_header *newLetter = new letter_header(mm, current->rhead.subject, current->rhead.to, current->rhead.from, date, current->msgid, getshort(current->rhead.reply), currentLetter, currentLetter, area, getshort(current->rhead.attr) & OPX_PRIVATE, current->msglen, this, current->na, mm->areaList->isLatin(area)); currentLetter++; uplListCurrent = uplListCurrent->nextRecord; return newLetter; } void opxreply::enterLetter(letter_header &newLetter, const char *newLetterFileName, long length) { // Specify the format separately from strftime() to suppress // GGC's Y2K warning: const char *datefmt_opx = "%d %b %y %H:%M:%S"; upl_opx *newList = new upl_opx(newLetterFileName); int attrib = newLetter.getPrivate() ? OPX_PRIVATE : 0; strncpy(newList->rhead.subject, newLetter.getSubject(), 71); strncpy(newList->rhead.from, newLetter.getFrom(), 35); strncpy(newList->rhead.to, newLetter.getTo(), 35); const char *msgid = newLetter.getMsgID(); if (msgid) newList->msgid = strdupplus(msgid); newList->area = atoi(mm->areaList->getShortName()); newList->na = newLetter.getNetAddr(); putshort(newList->rhead.attr, attrib); putshort(newList->rhead.reply, newLetter.getReplyTo()); time_t now = time(0); strftime(newList->rhead.date, 20, datefmt_opx, localtime(&now)); unsigned long dostime = mkdostime(localtime(&now)); putlong(newList->rhead.date_written, dostime); putlong(newList->rhead.date_arrived, dostime); newList->msglen = length; addUpl(newList); } const char *opxreply::freeFileName(upl_opx *l) { static char fname[13]; char ext[4]; unsigned area[3], x; area[2] = l->area; area[0] = area[2] / (36 * 36); area[2] %= (36 * 36); area[1] = area[2] / 36; area[2] %= 36; for (x = 0; x < 3; x++) ext[x] = area[x] + ((area[x] < 10) ? '0' : ('A' - 10)); ext[3] = '\0'; mystat st; int reply = getshort(l->rhead.reply); if (reply) { sprintf(fname, "!R%d.%s", reply, ext); if (!st.init(fname)) return fname; } x = 1; do sprintf(fname, "!N%d.%s", x++, ext); while (st.init(fname)); return fname; } void opxreply::addRep1(FILE *, upl_base *node, int) { FILE *orgfile, *destfile; upl_opx *l = (upl_opx *) node; const char *dest; dest = freeFileName(l); orgfile = fopen(l->fname, "rt"); if (orgfile) { destfile = fopen(dest, "wb"); if (destfile) { fwrite(&l->rhead, FIDO_HEAD_SIZE, 1, destfile); bool skipPID = false; if (l->na.isSet) { if (l->na.isInternet) { fprintf(destfile, "\001INETDEST %s\r\n", (const char *) l->na); skipPID = true; } else { putshort(l->rhead.dest_zone, l->na.zone); putshort(l->rhead.dest_net, l->na.net); putshort(l->rhead.dest_node, l->na.node); // I should probably add the originating // address here, but it's frequently // bogus. Let's try it this way. if (l->na.point) fprintf(destfile, "\001TOPT %d\r\n", l->na.point); } } if (l->msgid) fprintf(destfile, "\001REPLY: %s\r\n", l->msgid); if (!skipPID) fprintf(destfile, "\001PID: " MM_NAME "/%s v" MM_VERNUM "\r\n", sysname()); int c, count = 0, lastsp = 0; while ((c = fgetc(orgfile)) != EOF) { count++; if ((count > 80) && lastsp) { fseek(orgfile, lastsp - count, SEEK_CUR); fseek(destfile, lastsp - count, SEEK_CUR); c = '\n'; } if ('\n' == c) { fprintf(destfile, "\r\n"); count = lastsp = 0; } else { fputc(c, destfile); if (' ' == c) lastsp = count; } } fprintf(destfile, "\r\n"); fputc(0, destfile); fclose(destfile); } fclose(orgfile); } } void opxreply::addHeader(FILE *repFile) { // .ID -- fake it. long magic = -598939720L; time_t now = time(0); fprintf(repFile, "FALSE\r\n\r\n4.4\r\nTRUE\r\n%ld\r\n0\r\n%ld\r\n", (signed long) mkdostime(localtime(&now)), magic); } void opxreply::repFileName() { int x; const char *basename = baseClass->getBaseName(); for (x = 0; basename[x]; x++) { replyPacketName[x] = tolower(basename[x]); replyInnerName[x] = toupper(basename[x]); } strcpy(replyPacketName + x, ".rep"); strcpy(replyInnerName + x, ".ID"); } const char *opxreply::repTemplate(bool) { return "*.*"; } bool opxreply::getOffConfig() { FILE *olc; bool status = false; upWorkList = new file_list(mm->resourceObject->get(UpWorkDir)); olc = upWorkList->ftryopen("rusrcfg.dat"); if (olc) { ocfgHeader offhead; ocfgRec offrec; int areaOPX, areaNo; if (!fread(&offhead, OCFG_HEAD_SIZE, 1, olc)) fatalError("Error reading RUSRCFG.DAT"); int totareas = getshort(offhead.numofareas); for (int i = 0; i < totareas; i++) { if (!fread(&offrec, OCFG_REC_SIZE, 1, olc)) fatalError("Error reading RUSRCFG.DAT"); areaOPX = getshort(offrec.confnum); areaNo = ((opxpack *) baseClass)->getXNum(areaOPX) + 1; mm->areaList->gotoArea(areaNo); if (offrec.scanned) mm->areaList->Add(); else mm->areaList->Drop(); } fclose(olc); upWorkList->kill(); status = true; } delete upWorkList; return status; } bool opxreply::makeOffConfig() { FILE *olc; ocfgRec offrec; olc = fopen("RUSRCFG.DAT", "wb"); if (!olc) return false; fwrite(((opxpack *) baseClass)->offhead(), OCFG_HEAD_SIZE, 1, olc); int oldarea = mm->areaList->getAreaNo(); int maxareas = mm->areaList->noOfAreas(); for (int areaNo = 0; areaNo < maxareas; areaNo++) { mm->areaList->gotoArea(areaNo); unsigned long attrib = mm->areaList->getType(); int anum = atoi(mm->areaList->getShortName()); if (!(attrib & COLLECTION)) { putshort(offrec.confnum, anum); offrec.scanned = ((((attrib & ACTIVE) && !(attrib & DROPPED)) || (attrib & ADDED))); fwrite(&offrec, OCFG_REC_SIZE, 1, olc); } } mm->areaList->gotoArea(oldarea); fclose(olc); return true; } mmail-0.52/mmail/read.cc000644 000765 000024 00000012034 13065430373 016035 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * main_read_class, reply_read_class Copyright 1996-1997 Toth Istvan Copyright 1997-2017 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #include "mmail.h" #include "compress.h" /* read_class -- virtual */ read_class::~read_class() { } /* main_read_class -- for regular areas */ main_read_class::main_read_class(mmail *mmA, specific_driver *driverA) : mm(mmA), driver(driverA) { ro = mm->resourceObject; noOfAreas = driver->getNoOfAreas(); noOfLetters = new int[noOfAreas]; readStore = new int *[noOfAreas]; for (int c = 0; c < noOfAreas; c++) { driver->selectArea(c); int numlett = driver->getNoOfLetters(); noOfLetters[c] = numlett; readStore[c] = numlett ? new int[numlett] : 0; for (int d = 0; d < numlett; d++) readStore[c][d] = 0; } hasPersArea = driver->hasPersArea(); hasPersNdx = !(!(mm->workList->exists("personal.ndx"))); } main_read_class::~main_read_class() { while(noOfAreas--) delete[] readStore[noOfAreas]; delete[] readStore; delete[] noOfLetters; } void main_read_class::init() { // If basename.red not found, look for any .red file; // then look for an old-style file, and use the most recent: file_header *redfile, *oldfile; file_list *wl = mm->workList; redfile = wl->existsF(readFilePath(ro->get(PacketName))); if (!redfile) redfile = wl->existsF(".red"); const char *oldFileN = driver->oldFlagsName(); oldfile = oldFileN ? wl->existsF(oldFileN) : 0; bool oldused = (oldfile && (!redfile || (oldfile->getDate() > redfile->getDate()))); if (oldused) { int oldsort = lsorttype; lsorttype = LS_MSGNUM; oldused = driver->readOldFlags(); lsorttype = oldsort; } if (!oldused) { FILE *readFile; const char *readFileN = redfile ? redfile->getName() : 0; readFile = readFileN ? wl->ftryopen(readFileN) : 0; if (readFile) { // Don't init personal area, unless using QWK personal.ndx // (this is for backwards compatibility): int skip = hasPersArea && !hasPersNdx; for (int c = skip; c < noOfAreas; c++) for (int d = 0; d < noOfLetters[c]; d++) readStore[c][d] = fgetc(readFile); fclose(readFile); } } } void main_read_class::setRead(int area, int letter, bool value) { if (value) readStore[area][letter] |= MS_READ; else readStore[area][letter] &= ~MS_READ; } bool main_read_class::getRead(int area, int letter) { return !(!(readStore[area][letter] & MS_READ)); } void main_read_class::setStatus(int area, int letter, int value) { readStore[area][letter] = value; } int main_read_class::getStatus(int area, int letter) { return readStore[area][letter]; } int main_read_class::getNoOfUnread(int area) { int tmp = 0; for (int c = 0; c < noOfLetters[area]; c++) if (!(readStore[area][c] & MS_READ)) tmp++; return tmp; } int main_read_class::getNoOfMarked(int area) { int tmp = 0; for (int c = 0; c < noOfLetters[area]; c++) if (readStore[area][c] & MS_MARKED) tmp++; return tmp; } bool main_read_class::saveAll() { const char *readFileN = 0, *oldFileN = driver->oldFlagsName(); bool oldused = !(!oldFileN); if (mychdir(ro->get(WorkDir))) fatalError("Unable to change to work directory"); if (oldused) { int oldsort = lsorttype; lsorttype = LS_MSGNUM; oldused = driver->saveOldFlags(); lsorttype = oldsort; } if (!oldused) { FILE *readFile; readFileN = readFilePath(ro->get(PacketName)); readFile = fopen(readFileN, "wb"); for (int c = (hasPersArea && !hasPersNdx); c < noOfAreas; c++) for (int d = 0; d < noOfLetters[c]; d++) fputc(readStore[c][d], readFile); fclose(readFile); } // add the .red file to the packet return !compressAddFile(ro, ro->get(PacketDir), ro->get(PacketName), oldFileN ? oldFileN : readFileN); } const char *main_read_class::readFilePath(const char *FileN) { static char tmp[13]; sprintf(tmp, "%.8s.red", findBaseName(FileN)); return tmp; } /* reply_read_class -- for reply areas */ /* (Formerly known as dummy_read_class, because it does almost nothing) */ reply_read_class::reply_read_class(mmail *, specific_driver *) { } reply_read_class::~reply_read_class() { } void reply_read_class::init() { } void reply_read_class::setRead(int, int, bool) { } bool reply_read_class::getRead(int, int) { return true; } void reply_read_class::setStatus(int, int, int) { } int reply_read_class::getStatus(int, int) { return 1; } int reply_read_class::getNoOfUnread(int) { return 0; } int reply_read_class::getNoOfMarked(int) { return 0; } bool reply_read_class::saveAll() { return true; } mmail-0.52/mmail/area.cc000644 000765 000024 00000030116 13065427460 016036 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * area_header and area_list Copyright 1996-1997 Toth Istvan Copyright 1998-2017 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #include "mmail.h" // ----------------------------------------------------------------- // Area header methods // ----------------------------------------------------------------- area_header::area_header(mmail *mmA, int numA, const char *shortNameA, const char *nameA, const char *descriptionA, const char *areaTypeA, unsigned long typeA, int noOfLettersA, int noOfPersonalA, int maxtolenA, int maxsublenA) : mm(mmA), shortName(shortNameA), name(nameA), description(descriptionA), areaType(areaTypeA), type(typeA), noOfLetters(noOfLettersA), noOfPersonal(noOfPersonalA), maxtolen(maxtolenA), maxsublen(maxsublenA) { noOfReplies = 0; driver = mm->driverList->getDriver(numA); num = numA - mm->driverList->getOffset(driver); } const char *area_header::getName() const { return name; } const char *area_header::getShortName() const { return shortName; } const char *area_header::getDescription() const { return description; } const char *area_header::getTear() { return driver->getTear(num); } const char *area_header::getAreaType() const { return areaType; } unsigned long area_header::getType() const { return type; } int area_header::getNoOfLetters() const { return noOfLetters; } int area_header::getNoOfUnread() { return (mm->driverList->getReadObject(driver))->getNoOfUnread(num); } int area_header::getNoOfMarked() { return (mm->driverList->getReadObject(driver))->getNoOfMarked(num); } int area_header::getNoOfPersonal() const { return noOfPersonal; } bool area_header::getUseAlias() const { return !(!(type & ALIAS)); } bool area_header::isCollection() const { return !(!(type & COLLECTION)); } bool area_header::isReplyArea() const { return !(!(type & REPLYAREA)); } bool area_header::isActive() const { return !(!(type & (ACTIVE | ADDED | DROPPED | HASREPLY))); } bool area_header::isNetmail() const { return (type & NETMAIL) && !(type & INTERNET); } bool area_header::isInternet() const { return (type & NETMAIL) && (type & INTERNET); } bool area_header::isEmail() const { return !(!(type & NETMAIL)); } bool area_header::isUsenet() const { return !(type & NETMAIL) && (type & INTERNET); } bool area_header::isLatin() const { return !(!(type & LATINCHAR)); } bool area_header::isReadOnly() const { return !(!(type & READONLY)); } bool area_header::hasTo() const { return !((type & INTERNET) && !(type & NETMAIL)); } bool area_header::hasPublic() const { return !(!(type & PUBLIC)); } bool area_header::hasPrivate() const { return !(!(type & PRIVATE)); } int area_header::maxToLen() const { return maxtolen; } int area_header::maxSubLen() const { return maxsublen; } bool area_header::hasOffConfig() const { return !(!(type & OFFCONFIG)); } void area_header::Add() { if (!(type & COLLECTION)) { if (type & DROPPED) type &= ~DROPPED; else if (!(type & ACTIVE) || !(type & SUBKNOWN)) type |= ADDED; } } void area_header::Drop() { if (!(type & COLLECTION)) { if (type & ADDED) type &= ~ADDED; else if ((type & ACTIVE) || !(type & SUBKNOWN)) type |= DROPPED; } } void area_header::addReply() { type |= HASREPLY; noOfReplies++; } void area_header::killReply() { noOfReplies--; if (!noOfReplies) type &= ~HASREPLY; } // ----------------------------------------------------------------- // Arealist methods // ----------------------------------------------------------------- area_list::area_list(mmail *mmA) : mm(mmA) { no = mm->packet->getNoOfAreas() + 1; filter = 0; activeHeader = new int[no]; areaHeader = new area_header *[no]; specific_driver *actDriver; for (int c = 0; c < no; c++) { actDriver = mm->driverList->getDriver(c); areaHeader[c] = actDriver->getNextArea(); } current = 0; almode = mm->resourceObject->getInt(AreaMode) - 1; relist(); // 1. Find out what types of areas we have (i.e. qwk, usenet... ) // 2. Create the appropriate driver objects // 3. Find out the number of areas for each type // 4. Allocate the memory for the area_header descriptions // 5. Fill the area headers } area_list::~area_list() { while (no) delete areaHeader[--no]; delete[] areaHeader; delete[] activeHeader; delete[] filter; } bool area_list::relist() { bool anyfound = !filter; noActive = 0; almode++; if (almode == 3) almode = 0; // Check if Active/Subscribed distincion makes sense -- checks // the last area, instead of making a global per-packet check; // bogus, but it works, because this value is always the same for // each area in a packet: if ((almode == 1) && !(areaHeader[no - 1]->getType() & SUBKNOWN)) almode++; int c = current; for (current = 0; current < no; current++) if ( ((current == REPLY_AREA) || (getType() & HASREPLY)) || ( (!filter || filterCheck(filter)) && ((almode == 0) || ((almode == 1) && areaHeader[current]->isActive()) || ((almode == 2) && getNoOfLetters())) ) ) { activeHeader[noActive++] = current; if (!anyfound) anyfound = (filterCheck(filter) != 0); } current = c; return anyfound; } int area_list::getRepList() { current = REPLY_AREA; getLetterList(); int max = mm->letterList->noOfLetter(); for (int x = 0; x < max; x++) { mm->letterList->gotoLetter(x); int area = mm->letterList->getAreaID(); areaHeader[area]->addReply(); } delete mm->letterList; return max; } void area_list::updatePers() { // This routine makes some assumptions -- that there's at most one // PERS area, and that if present, it's the second area -- that // are valid as the program is currently written, but that are not // made elsewhere in this class. if (mm->packet->hasPersArea()) { int c = current; current = REPLY_AREA + 1; if (isCollection() && !isReplyArea()) { letter_list *ll = mm->letterList; getLetterList(); delete mm->letterList; mm->letterList = ll; } current = c; } } bool area_list::isShortlist() const { return !(!almode); } int area_list::getMode() const { return almode; } void area_list::setMode(int newmode) { almode = newmode; } const char *area_list::getShortName() const { return areaHeader[current]->getShortName(); } const char *area_list::getName() const { return areaHeader[current]->getName(); } const char *area_list::getName(int area) { if ((area < 0) || (area >= no)) fatalError("Internal error in area_list::getName"); return areaHeader[area]->getName(); } const char *area_list::getDescription() const { return areaHeader[current]->getDescription(); } const char *area_list::getDescription(int area) { if ((area < 0) || (area >= no)) fatalError("Internal error in area_list::getDescription"); return areaHeader[area]->getDescription(); } const char *area_list::getAreaType() const { return areaHeader[current]->getAreaType(); } const char *area_list::getTear() { return areaHeader[current]->getTear(); } unsigned long area_list::getType() const { return areaHeader[current]->getType(); } int area_list::getNoOfLetters() const { return areaHeader[current]->getNoOfLetters(); } int area_list::getNoOfUnread() const { return areaHeader[current]->getNoOfUnread(); } int area_list::getNoOfMarked() const { return areaHeader[current]->getNoOfMarked(); } int area_list::getNoOfPersonal() const { return areaHeader[current]->getNoOfPersonal(); } void area_list::getLetterList() { mm->letterList = new letter_list(mm, current, getType()); } int area_list::noOfAreas() const { return no; } int area_list::noOfActive() const { return noActive; } void area_list::gotoArea(int currentA) { if ((currentA >= 0) && (currentA < no)) current = currentA; } void area_list::gotoActive(int activeA) { if ((activeA >= 0) && (activeA < noActive)) current = activeHeader[activeA]; } int area_list::getAreaNo() const { return current; } int area_list::getActive() { int c; for (c = 0; c < noActive; c++) if (activeHeader[c] >= current) break; return c; } void area_list::enterLetter(int areaNo, const char *from, const char *to, const char *subject, const char *replyID, const char *newsgrp, int replyTo, bool privat, net_address &netAddress, const char *filename, long length) { gotoArea(areaNo); areaHeader[current]->addReply(); letter_header newLetter(mm, subject, to, from, "", replyID, replyTo, 0, 0, areaNo, privat, 0, mm->reply, netAddress, isLatin(), newsgrp); mm->reply->enterLetter(newLetter, filename, length); refreshArea(); } void area_list::killLetter(int areaNo, long letterNo) { areaHeader[areaNo]->killReply(); mm->reply->killLetter((int) letterNo); refreshArea(); } void area_list::refreshArea() { delete areaHeader[REPLY_AREA]; areaHeader[REPLY_AREA] = mm->reply->refreshArea(); if (current == REPLY_AREA) mm->letterList->rrefresh(); } bool area_list::getUseAlias() const { return areaHeader[current]->getUseAlias(); } bool area_list::isCollection() const { return areaHeader[current]->isCollection(); } bool area_list::isReplyArea() const { return areaHeader[current]->isReplyArea(); } bool area_list::isEmail() const { return areaHeader[current]->isEmail(); } bool area_list::isNetmail() const { return areaHeader[current]->isNetmail(); } int area_list::findNetmail() const { int c; for (c = 0; c < no; c++) if (areaHeader[c]->isNetmail()) break; return (c < no) ? c : -1; } bool area_list::isInternet() const { return areaHeader[current]->isInternet(); } int area_list::findInternet() const { int c; for (c = 0; c < no; c++) if (areaHeader[c]->isInternet()) break; return (c < no) ? c : -1; } bool area_list::isUsenet() const { return areaHeader[current]->isUsenet(); } bool area_list::isLatin() const { return areaHeader[current]->isLatin(); } bool area_list::isLatin(int area) { if ((area < 0) || (area >= no)) fatalError("Internal error in area_list::isLatin"); return areaHeader[area]->isLatin(); } bool area_list::isReadOnly() const { return areaHeader[current]->isReadOnly(); } bool area_list::hasTo() const { return areaHeader[current]->hasTo(); } bool area_list::hasPublic() const { return areaHeader[current]->hasPublic(); } bool area_list::hasPrivate() const { return areaHeader[current]->hasPrivate(); } int area_list::maxToLen() const { return areaHeader[current]->maxToLen(); } int area_list::maxSubLen() const { return areaHeader[current]->maxSubLen(); } bool area_list::hasOffConfig() const { return areaHeader[current]->hasOffConfig(); } void area_list::Add() { areaHeader[current]->Add(); } void area_list::Drop() { areaHeader[current]->Drop(); } bool area_list::anyChanged() const { for (int c = 0; c < no; c++) if (areaHeader[c]->getType() & (ADDED | DROPPED)) return true; return false; } const char *area_list::getFilter() const { return filter; } void area_list::setFilter(const char *newfilter) { delete[] filter; filter = (newfilter && *newfilter) ? strdupplus(newfilter) : 0; almode--; if (!relist()) { delete[] filter; filter = 0; almode--; relist(); } } const char *area_list::filterCheck(const char *item) { const char *s = searchstr(getShortName(), item); if (!s) { s = searchstr(getName(), item); if (!s) s = searchstr(getDescription(), item); } return s; } mmail-0.52/mmail/soup.h000644 000765 000024 00000004150 13065426413 015752 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * SOUP Copyright 1999-2017 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #ifndef SOUP_H #define SOUP_H #include "pktbase.h" class sheader { enum {date, from, to, reply, subject, newsgrps, follow, refs, msgid, items}; static const char *compare[items]; char *values[items]; public: long msglen; bool has8bit, qpenc; sheader(); ~sheader(); bool init(FILE *); bool init(const char *, const char *, const char *, const char *, const char *, const char *, long); void output(FILE *, const char *, bool, bool); const char *From(); const char *Subject(); const char *Date(); const char *ReplyTo(); const char *To(); const char *Newsgrps(); const char *Follow(); const char *Msgid(); const char *Refs(); }; class soup : public pktbase { struct AREAs { char *name; int nummsgs; unsigned long attr; char mode; char numA[10], msgfile[10]; AREAs *next; } **areas; bool msgopen(int); bool parseFrom(const char *); void buildIndices(); void readAreas(); public: soup(mmail *); ~soup(); area_header *getNextArea(); int getNoOfLetters(); letter_header *getNextLetter(); letter_body *getBody(letter_header &); const char *getTear(int); bool isLatin(); }; class souprep : public pktreply { class upl_soup : public upl_base { public: sheader sHead; net_address na; int origArea; long refnum; bool privat; upl_soup(const char * = 0); }; bool getRep1(FILE *, upl_soup *); void getReplies(FILE *); void addRep1(FILE *, upl_base *, int); void addHeader(FILE *); void repFileName(); const char *repTemplate(bool); public: souprep(mmail *, specific_driver *); ~souprep(); area_header *getNextArea(); letter_header *getNextLetter(); void enterLetter(letter_header &, const char *, long); bool getOffConfig(); bool makeOffConfig(); }; #endif mmail-0.52/mmail/misc.h000644 000765 000024 00000002771 13065425720 015726 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * miscellaneous routines (global) Copyright 1996-1997 Toth Istvan Copyright 1997-2017 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #ifndef MISC_H #define MISC_H extern "C" { #include #include } unsigned getshort(const unsigned char *); unsigned long getlong(const unsigned char *); unsigned long getblong(const unsigned char *); void putshort(unsigned char *, unsigned); void putlong(unsigned char *, unsigned long); void putblong(unsigned char *, unsigned long); struct tm *getdostime(unsigned long); unsigned long mkdostime(struct tm *); char *cropesp(char *); char *unspace(char *); char *strdupplus(const char *); char *strdupblank(const char *); char *fullpath(const char *, const char *); char *quotespace(const char *); const char *findBaseName(const char *); char *fixPath(const char *); int getNumExt(const char *); const char *stripre(const char *); const char *searchstr(const char *, const char *, int = -1); const char *fromAddr(const char *); const char *fromName(const char *); bool quoteIt(const char *); void headdec(const char *, const char *, char *); void headenc(const unsigned char *, const char *, FILE *); unsigned char *qpdecode(unsigned char *); long qpdecode(FILE *, FILE *); void qpencode(FILE *, FILE *); void fatalError(const char *); // actually in ../interfac/main.cc! void pauseError(const char *); // actually in ../interfac/main.cc! #endif mmail-0.52/mmail/bw.cc000644 000765 000024 00000072161 13436520650 015541 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * Blue Wave class Copyright 1996-1997 Toth Istvan Copyright 1998-2019 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #include "bw.h" #include "compress.h" // ----------------------------------------------------------------- // The Blue Wave methods // ----------------------------------------------------------------- bluewave::bluewave(mmail *mmA) : pktbase(mmA) { persNdx = 0; findInfBaseName(); initInf(); ftiFile = openFile("FTI"); initMixID(); body = new bodytype *[maxConf]; infile = openFile("DAT"); } bluewave::~bluewave() { if (!hasPers) { areas--; mixID--; } while (maxConf--) delete[] body[maxConf]; delete[] mixID; delete[] areas; delete[] mixRecord; delete[] persNdx; fclose(ftiFile); } bool bluewave::hasPersonal() { return true; } area_header *bluewave::getNextArea() { int x, totmsgs, numpers; unsigned flags_raw; unsigned long flags_cooked; bool isPers = hasPers && !ID; x = mixID[ID]; if (isPers) totmsgs = numpers = personal; else if (x != -1) { totmsgs = getshort(mixRecord[x].totmsgs); numpers = getshort(mixRecord[x].numpers); } else totmsgs = numpers = 0; body[ID] = totmsgs ? new bodytype[totmsgs] : 0; bool inet = isPers ? false : (areas[ID].network_type == INF_NET_INTERNET); flags_raw = getshort(areas[ID].area_flags); flags_cooked = isPers ? (PUBLIC | PRIVATE | COLLECTION | ACTIVE) : (hasOffConfig | SUBKNOWN | ((x != -1) ? ACTIVE : 0) | ((flags_raw & (INF_ALIAS_NAME | INF_ANY_NAME)) ? ALIAS : 0) | ((flags_raw & INF_NETMAIL) ? NETMAIL : 0) | (inet ? (INTERNET | LATINCHAR) : 0) | ((flags_raw & INF_ECHO) ? ECHOAREA : 0) | ((flags_raw & INF_NO_PUBLIC) ? 0 : PUBLIC) | ((flags_raw & INF_NO_PRIVATE) ? 0 : PRIVATE) | ((flags_raw & INF_PERSONAL) ? PERSONLY : 0) | ((flags_raw & INF_TO_ALL) ? PERSALL : 0) | ((flags_raw & INF_POST) ? 0 : READONLY)); area_header *tmp = new area_header(mm, ID + 1, isPers ? "PERS" : (char *) areas[ID].areanum, isPers ? "PERSONAL" : (char *) areas[ID].echotag, (isPers ? "Letters addressed to you" : (areas[ID].title[0] ? (char *) areas[ID].title : (char *) areas[ID].echotag)), (isPers ? "Blue Wave personal" : "Blue Wave"), flags_cooked, totmsgs, numpers, from_to_len, inet ? 511 : subject_len); ID++; return tmp; } int bluewave::getNoOfLetters() { int x = mixID[currentArea]; bool isPers = hasPers && !currentArea; return isPers ? personal : ((x != -1) ? getshort(mixRecord[x].totmsgs) : 0); } letter_header *bluewave::getNextLetter() { FTI_REC ftiRec; int areaID, letterID; if (currentLetter >= getNoOfLetters()) return 0; if (hasPers && !currentArea) { areaID = persNdx[currentLetter].area; letterID = persNdx[currentLetter].msgnum; } else { areaID = currentArea; letterID = currentLetter; } fseek(ftiFile, getlong(mixRecord[mixID[areaID]].msghptr) + (long) letterID * ftiStructLen, SEEK_SET); if (!fread(&ftiRec, ORIGINAL_FTI_STRUCT_LEN, 1, ftiFile)) fatalError("Error reading .FTI file"); long msglength = getlong(ftiRec.msglength); body[areaID][letterID].pointer = getlong(ftiRec.msgptr); body[areaID][letterID].msgLength = msglength; cropesp((char *) ftiRec.from); cropesp((char *) ftiRec.to); cropesp((char *) ftiRec.subject); int flags = getshort(ftiRec.flags); net_address na; na.zone = getshort(ftiRec.orig_zone); if (na.zone) { na.net = getshort(ftiRec.orig_net); na.node = getshort(ftiRec.orig_node); na.point = 0; // set from getBody() na.isSet = true; } currentLetter++; // Y2K precaution -- the date field may not be 0-terminated: ftiRec.date[19] = '\0'; return new letter_header(mm, (char *) ftiRec.subject, (char *) ftiRec.to, (char *) ftiRec.from, (char *) ftiRec.date, 0, getshort(ftiRec.replyto), letterID, getshort(ftiRec.msgnum), areaID, !(!(flags & MSG_NET_PRIVATE)), msglength, this, na, (areas[areaID].network_type == INF_NET_INTERNET)); } void bluewave::getblk(int AreaID, long &offset, long blklen, unsigned char *&p, unsigned char *&begin) { bool internet, strip; internet = (areas[AreaID].network_type == INF_NET_INTERNET); strip = !internet && mm->resourceObject->getInt(StripSoftCR); for (long count = 0; count < blklen; count++) { int kar = fgetc(infile); // Skip leading space, if present: if (!count && (kar == ' ')) kar = fgetc(infile); // Some buggy packets: if (!kar || (kar == EOF)) kar = ' '; if (kar == '\r') { *p++ = '\n'; begin = p; offset = ftell(infile); } else if ((kar != '\n') && (!strip || (kar != 0x8D))) *p++ = kar; } } void bluewave::endproc(letter_header &mhead) { // Extra header info embedded in the text: const char *s; char *end; net_address &na = mhead.getNetAddr(); int AreaID = mhead.getAreaID() - 1; if (areas[AreaID].network_type == INF_NET_INTERNET) { // Get address from "From:" line: if (!na.isSet) { s = getHidden("\001From: ", end); if (s) { na.isInternet = true; na = fromAddr(s); if (end) *end = '\n'; } } // Get Message-ID/References: if (!mhead.getMsgID()) { s = getHidden("\001Message-ID: ", end); if (s) { const char *s2; char *end2, *final; if (end) *end = '\n'; s2 = getHidden("\001References: ", end2); if (end) *end = '\0'; if (s2) { final = new char[strlen(s) + strlen(s2) + 2]; sprintf(final, "%s %s", s2, s); if (end2) *end2 = '\n'; } else final = (char *) s; mhead.changeMsgID(final); if (s2) delete[] final; if (end) *end = '\n'; } } // Get Newsgroups: if (!mhead.getNewsgrps()) { s = getHidden("\001Newsgroups: ", end); if (s) { mhead.changeNewsgrps(s); if (end) *end = '\n'; } } // Get Followup-To: if (!mhead.getFollow()) { s = getHidden("\001Followup-To: ", end); if (s) { mhead.changeFollow(s); if (end) *end = '\n'; } } // Get extended subject: s = getHidden("\001Subject: ", end); if (s) { mhead.changeSubject(s); if (end) *end = '\n'; } // Replace the date (there's more info in the standard // Usenet date header; plus, it differs from the value // in the Blue Wave header in the packets I have): s = getHidden("\001Date: ", end); if (s) { mhead.changeDate(s); if (end) *end = '\n'; } } else fidocheck(mhead); } void bluewave::findInfBaseName() { const char *q = mm->workList->exists(".inf"); size_t len = strlen(q) - 4; if (len > 8) len = 8; memcpy(packetBaseName, q, len); packetBaseName[len] = '\0'; } // Read .INF file void bluewave::initInf() { FILE *infFile = openFile("INF"); // Header if (!fread(&infoHeader, ORIGINAL_INF_HEADER_LEN, 1, infFile)) fatalError("Error reading .INF file"); infoHeaderLen = getshort(infoHeader.inf_header_len); if (infoHeaderLen < ORIGINAL_INF_HEADER_LEN) infoHeaderLen = ORIGINAL_INF_HEADER_LEN; infoAreainfoLen = getshort(infoHeader.inf_areainfo_len); if (infoAreainfoLen < ORIGINAL_INF_AREA_LEN) infoAreainfoLen = ORIGINAL_INF_AREA_LEN; from_to_len = infoHeader.from_to_len; if (!from_to_len || (from_to_len > 35)) from_to_len = 35; subject_len = infoHeader.subject_len; if (!subject_len || (subject_len > 71)) subject_len = 71; mixStructLen = getshort(infoHeader.mix_structlen); if (mixStructLen < ORIGINAL_MIX_STRUCT_LEN) mixStructLen = ORIGINAL_MIX_STRUCT_LEN; ftiStructLen = getshort(infoHeader.fti_structlen); if (ftiStructLen < ORIGINAL_FTI_STRUCT_LEN) ftiStructLen = ORIGINAL_FTI_STRUCT_LEN; hasOffConfig = (getshort(infoHeader.ctrl_flags) & INF_NO_CONFIG) ? 0 : OFFCONFIG; LoginName = strdupplus((char *) infoHeader.loginname); AliasName = strdupplus((char *) infoHeader.aliasname); SysOpName = strdupplus((char *) infoHeader.sysop); BBSName = strdupplus((char *) infoHeader.systemname); // Areas maxConf = (mm->workList->getSize() - infoHeaderLen) / infoAreainfoLen + 1; areas = new INF_AREA_INFO[maxConf]; mixID = new int[maxConf]; for (int d = 1; d < maxConf; d++) { fseek(infFile, (infoHeaderLen + (d - 1) * infoAreainfoLen), SEEK_SET); if (!fread(&areas[d], ORIGINAL_INF_AREA_LEN, 1, infFile)) fatalError("Premature EOF in bluewave::initInf"); mixID[d] = -1; } fclose(infFile); // Bulletins, etc. listBulletins((const char (*)[13]) infoHeader.readerfiles, 5); } // Read .MIX file, match .INF records to .MIX records void bluewave::initMixID() { // Read .MIX file FILE *mixFile = openFile("MIX"); noOfMixRecs = (int) (mm->workList->getSize() / mixStructLen); mixRecord = new MIX_REC[noOfMixRecs]; if (!fread(mixRecord, mixStructLen, noOfMixRecs, mixFile)) fatalError("Error reading .MIX file"); fclose(mixFile); // Match records int mixIndex = 0, c, d; personal = 0; for (c = 0; c < noOfMixRecs; c++) { for (d = mixIndex + 1; d < maxConf; d++) if (!strncasecmp((char *) areas[d].areanum, (char *) mixRecord[c].areanum, 6)) { mixID[d] = c; mixIndex = d; break; } personal += getshort(mixRecord[c].numpers); } hasPers = !(!personal); if (hasPers) { persNdx = new perstype[personal]; int maxpers = personal; personal = 0; FTI_REC ftiRec; for (c = 1; c < maxConf; c++) if ((mixID[c] != -1) && getshort(mixRecord[mixID[c]].numpers)) { int totmsgs = getshort(mixRecord[mixID[c]].totmsgs); for (d = 0; d < totmsgs; d++) { fseek(ftiFile, getlong(mixRecord[mixID[c]].msghptr) + (long) d * ftiStructLen, SEEK_SET); if (!fread(&ftiRec, ORIGINAL_FTI_STRUCT_LEN, 1, ftiFile)) fatalError("Error reading .FTI file"); cropesp((char *) ftiRec.to); if ((!strcasecmp((char *) ftiRec.to, LoginName) || !strcasecmp((char *) ftiRec.to, AliasName)) && (personal < maxpers)) { persNdx[personal].area = c; persNdx[personal++].msgnum = d; } } } } else { areas++; mixID++; maxConf--; } } FILE *bluewave::openFile(const char *extent) { FILE *tmp; char fname[25]; sprintf(fname, "%s.%s", packetBaseName, extent); tmp = mm->workList->ftryopen(fname); if (!tmp) { sprintf(fname, "Could not open .%s file", extent); fatalError(fname); } return tmp; } const char *bluewave::oldFlagsName() { static char xtiFileN[13]; sprintf(xtiFileN, "%s.xti", packetBaseName); return xtiFileN; } // Read in an .XTI file bool bluewave::readOldFlags() { FILE *xtiFile; xtiFile = mm->workList->ftryopen(oldFlagsName()); if (!xtiFile) return false; area_list *al = mm->areaList; int maxareas = al->noOfAreas(); for (int c = 0; c < maxareas; c++) { al->gotoArea(c); if (!al->isCollection()) { al->getLetterList(); letter_list *ll = mm->letterList; for (int d = 0; d < ll->noOfLetter(); d++) { XTI_REC xtiRec; int stat; if (!fread(&xtiRec, sizeof xtiRec, 1, xtiFile)) fatalError("Error reading .XTI file"); stat = ((xtiRec.flags & XTI_HAS_READ) ? MS_READ : 0) | ((xtiRec.flags & XTI_HAS_REPLIED) ? MS_REPLIED : 0) | ((xtiRec.flags & XTI_IS_PERSONAL) ? MS_PERSTO : 0) | ((xtiRec.flags & XTI_HAS_SAVED) ? MS_SAVED : 0) | (xtiRec.marks ? MS_MARKED : 0); ll->gotoLetter(d); ll->setStatus(stat); } delete ll; } } fclose(xtiFile); return true; } // Write out an .XTI file bool bluewave::saveOldFlags() { FILE *xtiFile; xtiFile = fopen(oldFlagsName(), "wb"); if (!xtiFile) return false; area_list *al = mm->areaList; int maxareas = al->noOfAreas(); for (int c = 0; c < maxareas; c++) { al->gotoArea(c); if (!al->isCollection()) { al->getLetterList(); letter_list *ll = mm->letterList; for (int d = 0; d < ll->noOfLetter(); d++) { ll->gotoLetter(d); int stat = ll->getStatus(); XTI_REC xtiRec; xtiRec.flags = ((stat & MS_READ) ? XTI_HAS_READ : 0) | ((stat & MS_REPLIED) ? XTI_HAS_REPLIED : 0) | ((stat & MS_PERSTO) ? XTI_IS_PERSONAL : 0) | ((stat & MS_SAVED) ? XTI_HAS_SAVED : 0); xtiRec.marks = (stat & MS_MARKED) ? XTI_MARK_SAVE : 0; fwrite(&xtiRec, sizeof xtiRec, 1, xtiFile); } delete ll; } } fclose(xtiFile); return true; } const char *bluewave::getTear(int) { return 0; } INF_HEADER &bluewave::getInfHeader() { return infoHeader; } // ----------------------------------------------------------------- // The Blue Wave reply methods // ----------------------------------------------------------------- bwreply::upl_bw::upl_bw(const char *name) : pktreply::upl_base(name) { memset(&uplRec, 0, sizeof(uplRec)); msgid = newsgrps = extsubj = 0; } bwreply::upl_bw::~upl_bw() { delete[] msgid; delete[] newsgrps; delete[] extsubj; } bwreply::bwreply(mmail *mmA, specific_driver *baseClassA) : pktreply(mmA, baseClassA) { uplHeader = new UPL_HEADER; } bwreply::~bwreply() { delete uplHeader; } bool bwreply::getRep1(FILE *uplFile, upl_bw *l, int recnum) { FILE *orgfile, *destfile; const char *orgname; int c; long count = 0; fseek(uplFile, getshort(uplHeader->upl_header_len) + recnum * getshort(uplHeader->upl_rec_len), SEEK_SET); if (fread(&(l->uplRec), sizeof(UPL_REC), 1, uplFile) != 1) return false; orgname = upWorkList->exists((char *) l->uplRec.filename); orgfile = fopen(orgname, "rb"); if (orgfile) { destfile = fopen(l->fname, "wt"); if (destfile) { while ((c = fgetc(orgfile)) != EOF) { if (c == '\001') { c = fgetc(orgfile); int x; bool isRef = (c == 'R'), isNews = (c == 'N'), isSubj = (c == 'S'); if (isRef || isNews || isSubj) { for (x = 0; x < (isSubj ? 8 : 11); x++) fgetc(orgfile); } x = 0; while ((c != EOF) && (c != '\n')) { c = fgetc(orgfile); x++; } if (isRef || isNews || isSubj) { char *tmp = new char[x]; fseek(orgfile, x * -1, SEEK_CUR); if (!fread(tmp, 1, x, orgfile)) fatalError("Error reading reply file"); strtok(tmp, "\r"); if (isRef) l->msgid = tmp; else if (isNews) l->newsgrps = tmp; else l->extsubj = tmp; } c = '\r'; } if (c != '\r') { fputc(c, destfile); count++; } } fclose(destfile); } fclose(orgfile); } l->msglen = count; remove(orgname); return true; } void bwreply::getReplies(FILE *uplFile) { if (!fread(uplHeader, sizeof(UPL_HEADER), 1, uplFile)) fatalError("Error reading UPL file"); noOfLetters = (upWorkList->getSize() - getshort(uplHeader->upl_header_len)) / getshort(uplHeader->upl_rec_len); upl_bw baseUplList, *currUplList = &baseUplList; for (int c = 0; c < noOfLetters; c++) { currUplList->nextRecord = new upl_bw; currUplList = (upl_bw *) currUplList->nextRecord; if (!getRep1(uplFile, currUplList, c)) { delete currUplList; break; } } uplListHead = baseUplList.nextRecord; } area_header *bwreply::getNextArea() { return new area_header(mm, 0, "REPLY", "REPLIES", "Letters written by you", "Blue Wave replies", (COLLECTION | REPLYAREA | ACTIVE | PUBLIC | PRIVATE), noOfLetters, 0, 35, 71); } int bwreply::getAreaFromTag(const char *tag) { int areaNo = 0; for (int c = 0; c < mm->areaList->noOfAreas(); c++) if (!strcmp(mm->areaList->getName(c), tag)) { areaNo = c; break; } return areaNo; } letter_header *bwreply::getNextLetter() { upl_bw *current = (upl_bw *) uplListCurrent; time_t unixTime = (time_t) getlong(current->uplRec.unix_date); char date[30]; strftime(date, 30, "%b %d %Y %H:%M", localtime(&unixTime)); int areaNo = getAreaFromTag((char *) current->uplRec.echotag); const char *msgid; net_address na; bool isInet = (current->uplRec.network_type == INF_NET_INTERNET); if (isInet) { na.isInternet = true; na = (char *) current->uplRec.net_dest; msgid = current->msgid; } else { na.zone = getshort(current->uplRec.destzone); if (na.zone) { na.net = getshort(current->uplRec.destnet); na.node = getshort(current->uplRec.destnode); na.point = getshort(current->uplRec.destpoint); na.isSet = true; } msgid = (char *) current->uplRec.net_dest; if (!strncasecmp(msgid, "REPLY: ", 7)) msgid += 7; else msgid = 0; } letter_header *newLetter = new letter_header(mm, current->extsubj ? current->extsubj : (char *) current->uplRec.subj, (char *) current->uplRec.to, (char *) current->uplRec.from, date, msgid, getlong(current->uplRec.replyto), currentLetter, currentLetter, areaNo, !(!(getshort(current->uplRec.msg_attr) & UPL_PRIVATE)), current->msglen, this, na, isInet, current->newsgrps); currentLetter++; uplListCurrent = uplListCurrent->nextRecord; return newLetter; } void bwreply::enterLetter(letter_header &newLetter, const char *newLetterFileName, long length) { upl_bw *newList = new upl_bw(newLetterFileName); int msg_attr = 0; // fill the fields of UPL_REC strncpy((char *) newList->uplRec.from, newLetter.getFrom(), 35); strncpy((char *) newList->uplRec.to, newLetter.getTo(), 35); strncpy((char *) newList->uplRec.subj, newLetter.getSubject(), 71); strcpy((char *) newList->uplRec.echotag, mm->areaList->getName()); bool usenet = mm->areaList->isUsenet(); bool internet = mm->areaList->isInternet(); if (internet || usenet) newList->uplRec.network_type = INF_NET_INTERNET; const char *msgid = newLetter.getMsgID(); if (msgid) { if (!(internet || usenet)) sprintf((char *) newList->uplRec.net_dest, "REPLY: %.92s", msgid); else if (usenet) newList->msgid = strdupplus(msgid); } if (usenet) { newList->newsgrps = strdupplus(newLetter.getNewsgrps()); if (strlen(newLetter.getSubject()) > 71) newList->extsubj = strdupplus(newLetter.getSubject()); } putlong(newList->uplRec.unix_date, (long) time(0)); net_address &na = newLetter.getNetAddr(); if (na.isSet) { msg_attr |= UPL_NETMAIL; if (na.isInternet) sprintf((char *) newList->uplRec.net_dest, "%.99s", (const char *) na); else { putshort(newList->uplRec.destzone, na.zone); putshort(newList->uplRec.destnet, na.net); putshort(newList->uplRec.destnode, na.node); putshort(newList->uplRec.destpoint, na.point); } } putlong(newList->uplRec.replyto, newLetter.getReplyTo()); if (newLetter.getPrivate()) msg_attr |= UPL_PRIVATE; if (newLetter.getReplyTo()) msg_attr |= UPL_IS_REPLY; putshort(newList->uplRec.msg_attr, msg_attr); newList->msglen = length; addUpl(newList); } void bwreply::addRep1(FILE *uplFile, upl_base *node, int) { FILE *orgfile, *destfile; upl_bw *l = (upl_bw *) node; const char *dest; int c; dest = freeFileName(); strcpy((char *) l->uplRec.filename, dest); orgfile = fopen(l->fname, "rt"); if (orgfile) { destfile = fopen(dest, "wb"); if (destfile) { if (l->uplRec.network_type == INF_NET_INTERNET) { fprintf(destfile, *(l->uplRec.net_dest) ? "\001X-Mail" : "\001X-News"); fprintf(destfile, "reader: " MM_NAME " Offline Reader for %s v" MM_VERNUM "\r\n", sysname()); if (l->msgid) fprintf(destfile, "\001References: %s\r\n", l->msgid); if (l->newsgrps) fprintf(destfile, "\001Newsgroups: %s\r\n", l->newsgrps); if (l->extsubj) fprintf(destfile, "\001Subject: %s\r\n", l->extsubj); } while ((c = fgetc(orgfile)) != EOF) { if (c == '\n') fputc('\r', destfile); fputc(c, destfile); } fclose(destfile); } fclose(orgfile); } fwrite(&(l->uplRec), sizeof(UPL_REC), 1, uplFile); } void bwreply::addHeader(FILE *uplFile) { UPL_HEADER newUplHeader; memset(&newUplHeader, 0, sizeof(UPL_HEADER)); putshort(newUplHeader.upl_header_len, sizeof(UPL_HEADER)); putshort(newUplHeader.upl_rec_len, sizeof(UPL_REC)); newUplHeader.reader_major = MM_MAJOR; newUplHeader.reader_minor = MM_MINOR; strncpy((char *) newUplHeader.vernum, MM_VERNUM, 20); for (int c = 0; newUplHeader.vernum[c]; newUplHeader.vernum[c++] -= 10); const char *name = sysname(); size_t len = strlen(name); if (len < 80 - sizeof(MM_NAME)) sprintf((char *) newUplHeader.reader_name, MM_NAME "/%s", name); else strcpy((char *) newUplHeader.reader_name, MM_NAME); if (len < 16 - sizeof(MM_NAME)) strcpy((char *) newUplHeader.reader_tear, (char *) newUplHeader.reader_name); else if (len < 16 - sizeof(MM_SNAME)) sprintf((char *) newUplHeader.reader_tear, MM_SNAME "/%s", name); else strcpy((char *) newUplHeader.reader_tear, MM_NAME); strcpy((char *) newUplHeader.loginname, baseClass->getLoginName()); strcpy((char *) newUplHeader.aliasname, baseClass->getAliasName()); fwrite(&newUplHeader, sizeof(UPL_HEADER), 1, uplFile); } const char *bwreply::freeFileName() { mystat st; static char testFileName[13]; for (long c = 0; c <= 99999; c++) { sprintf(testFileName, "%05ld.MSG", c); if (!st.init(testFileName)) break; } return testFileName; } void bwreply::repFileName() { int x; const char *basename = baseClass->getBaseName(); for (x = 0; basename[x]; x++) { replyPacketName[x] = tolower(basename[x]); replyInnerName[x] = toupper(basename[x]); } strcpy(replyPacketName + x, ".new"); strcpy(replyInnerName + x, ".UPL"); } const char *bwreply::repTemplate(bool offres) { static char buff[20]; if (offres) sprintf(buff, "*.*"); else sprintf(buff, "%s *.MSG", replyInnerName); return buff; } char *bwreply::nextLine(FILE *olc) { static char line[128]; char *end = myfgets(line, sizeof line, olc); if (end) while ((*end == '\n') || (*end == '\r')) *end-- = '\0'; return line; } bool bwreply::getOffConfig() { PDQ_REC pdqrec; FILE *olc; #ifdef BOGUS_WARNING char *line = 0; #else char *line; #endif bool status, oldstyle; upWorkList = new file_list(mm->resourceObject->get(UpWorkDir)); olc = upWorkList->ftryopen(".pdq"); if (olc) oldstyle = true; else { olc = upWorkList->ftryopen(".olc"); oldstyle = false; } if (olc) { if (oldstyle) { fseek(olc, sizeof(PDQ_HEADER), SEEK_SET); if (!fread(&pdqrec, 1, PDQ_REC_SIZE, olc)) fatalError("Error reading .PDQ file"); } else { nextLine(olc); do line = nextLine(olc); while (line[0] != '['); } int areaNo = -1; int maxareas = mm->areaList->noOfAreas(); do { if (oldstyle) line = (char *) pdqrec.echotag; else { line++; line[strlen(line) - 1] = '\0'; } for (int c = areaNo + 1; c < maxareas; c++) { mm->areaList->gotoArea(c); if (!strcmp(mm->areaList->getName(), line)) { mm->areaList->Add(); areaNo = c; break; } else mm->areaList->Drop(); } if (oldstyle) { if (!fread(&pdqrec, 1, PDQ_REC_SIZE, olc)) fatalError("Error reading .PDQ file"); } else { nextLine(olc); nextLine(olc); line = nextLine(olc); } } while (!feof(olc)); fclose(olc); upWorkList->kill(); while (++areaNo < maxareas) { mm->areaList->gotoArea(areaNo); mm->areaList->Drop(); } status = true; } else status = false; delete upWorkList; return status; } bool bwreply::makeOffConfig() { FILE *olc; char fname[13]; INF_HEADER &infoHeader = ((bluewave *) baseClass)->getInfHeader(); bool oldstyle = (infoHeader.ver < 3); sprintf(fname, oldstyle ? "%s.PDQ" : "%s.OLC", findBaseName(replyInnerName)); olc = fopen(fname, "wb"); if (!olc) return false; if (oldstyle) { PDQ_HEADER pdqhead; int i; memcpy(pdqhead.keywords, infoHeader.keywords, sizeof infoHeader.keywords); memcpy(pdqhead.filters, infoHeader.filters, sizeof infoHeader.filters); for (i = 0; i < 3; i++) { strncpy((char *) pdqhead.macros[i], (const char *) infoHeader.macros[i], 77); pdqhead.macros[i][77] = '\0'; } memcpy(pdqhead.password, infoHeader.password, sizeof infoHeader.password); pdqhead.passtype = infoHeader.passtype; int oflags = getshort(infoHeader.uflags); oflags |= PDQ_AREA_CHANGES; putshort(pdqhead.flags, oflags); fwrite(&pdqhead, 1, sizeof pdqhead, olc); } else fprintf(olc, "[Global Mail Host Configuration]\r\n" "AreaChanges = ON\r\n\r\n"); int oldarea = mm->areaList->getAreaNo(); int maxareas = mm->areaList->noOfAreas(); for (int x = 0; x < maxareas; x++) { mm->areaList->gotoArea(x); unsigned long attrib = mm->areaList->getType(); if (!(attrib & COLLECTION) && (((attrib & ACTIVE) && !(attrib & DROPPED)) || (attrib & ADDED))) { if (oldstyle) { PDQ_REC p; memset(&p, 0, sizeof p); strcpy((char *) (p.echotag), mm->areaList->getName()); fwrite(&p, 1, PDQ_REC_SIZE, olc); } else fprintf(olc, "[%s]\r\nScan = %s\r\n\r\n", mm->areaList->getName(), (attrib & PERSONLY) ? "PERSONLY" : ((attrib & PERSALL) ? "PERSALL" : "ALL")); } } mm->areaList->gotoArea(oldarea); fclose(olc); return true; } mmail-0.52/mmail/letter.cc000644 000765 000024 00000032544 13065430005 016421 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * letter_header and letter_list Copyright 1996-1997 Toth Istvan Copyright 1997-2017 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #include "mmail.h" int lsorttype; // Outside the classes because it needs to be accessible // by lettercomp(), which is outside because it needs to // be for qsort(). :-/ extern "C" int lmsgncomp(const void *a, const void *b) { return (*((letter_header **) a))->getLetterID() - (*((letter_header **) b))->getLetterID(); } extern "C" int lettercomp(const void *A, const void *B) { const char *x, *y; int d, l1, l2; letter_header **a = (letter_header **) A; letter_header **b = (letter_header **) B; switch(lsorttype) { case LS_SUBJ: x = stripre((*a)->getSubject()); y = stripre((*b)->getSubject()); break; case LS_FROM: x = (*a)->getFrom(); y = (*b)->getFrom(); break; default: x = (*a)->getTo(); y = (*b)->getTo(); } l1 = strlen(x); l2 = strlen(y); if (!l1 || !l2) // For idiots who don't add a Subject d = l2 - l1; else { if (l2 < l1) l1 = l2; d = strncasecmp(x, y, l1); if (!d) d = lmsgncomp(A, B); } return d; } // ----------------------------------------------------------------- // Letter body methods // ----------------------------------------------------------------- letter_body::letter_body(char *textA, long lengthA, long offsetA, bool hiddenA) : text(textA), length(lengthA), offset(offsetA), hidden(hiddenA) { next = 0; } letter_body::~letter_body() { delete[] text; delete next; } char *letter_body::getText() { return text + offset; } long letter_body::getLength() { return length - offset; } bool letter_body::isHidden() { return hidden; } // ----------------------------------------------------------------- // Letter header methods // ----------------------------------------------------------------- letter_header::letter_header(mmail *mmA, const char *subjectA, const char *toA, const char *fromA, const char *dateA, const char *msgidA, long replyToA, int LetterIDA, long msgNumA, int AreaIDA, bool privatA, int lengthA, specific_driver *driverA, net_address &netAddrA, bool charsetA, const char *newsgrpsA, const char *followA, const char *replyA, bool qpencA) : driver(driverA), replyTo(replyToA), LetterID(LetterIDA), AreaID(AreaIDA), privat(privatA), length(lengthA), msgNum(msgNumA), netAddr(netAddrA), charset(charsetA), qpenc(qpencA) { dl = mmA->driverList; readO = dl->getReadObject(driver); const char *cset = mmA->resourceObject->get(outCharset); subject = strdupblank(subjectA); headdec(subjectA, cset, subject); to = strdupblank(toA); headdec(toA, cset, to); from = strdupblank(fromA); headdec(fromA, cset, from); date = strdupplus(dateA); msgid = strdupplus(msgidA); newsgrps = strdupplus(newsgrpsA); follow = strdupplus(followA); reply = strdupplus(replyA); const char *login = mmA->packet->getLoginName(); const char *alias = mmA->packet->getAliasName(); persto = ((login && *login && !strcasecmp(to, login)) || (alias && *alias && !strcasecmp(to, alias))); persfrom = ((login && *login && !strcasecmp(from, login)) || (alias && *alias && !strcasecmp(from, alias))); } letter_header::~letter_header() { delete[] subject; delete[] to; delete[] from; delete[] date; delete[] msgid; delete[] newsgrps; delete[] follow; delete[] reply; } void letter_header::changeSubject(const char *newsubj) { delete[] subject; subject = strdupplus(newsubj); } void letter_header::changeTo(const char *newto) { delete[] to; to = strdupplus(newto); } void letter_header::changeFrom(const char *newfrom) { delete[] from; from = strdupplus(newfrom); } void letter_header::changeDate(const char *newdate) { delete[] date; date = strdupplus(newdate); } void letter_header::changeMsgID(const char *newmsgid) { delete[] msgid; msgid = strdupplus(newmsgid); } void letter_header::changeNewsgrps(const char *news) { delete[] newsgrps; newsgrps = strdupplus(news); } void letter_header::changeFollow(const char *newfollow) { delete[] follow; follow = strdupplus(newfollow); } void letter_header::changeReplyTo(const char *newreply) { delete[] reply; reply = strdupplus(newreply); } const char *letter_header::getSubject() const { return subject; } const char *letter_header::getTo() const { return to; } const char *letter_header::getFrom() const { return from; } const char *letter_header::getDate() const { return date; } const char *letter_header::getMsgID() const { return msgid; } const char *letter_header::getNewsgrps() const { return newsgrps; } const char *letter_header::getFollow() const { return follow; } const char *letter_header::getReply() const { return reply; } long letter_header::getReplyTo() const { return replyTo; } int letter_header::getLetterID() const { return LetterID; } int letter_header::getAreaID() const { return AreaID + dl->getOffset(driver); } bool letter_header::getPrivate() const { return privat; } letter_body *letter_header::getBody() { return driver->getBody(*this); } int letter_header::getLength() const { return length; } bool letter_header::getRead() { return readO->getRead(AreaID, LetterID); } void letter_header::setRead() { readO->setRead(AreaID, LetterID, true); } int letter_header::getStatus() { return readO->getStatus(AreaID, LetterID) | (persto ? MS_PERSTO : 0) | (persfrom ? MS_PERSFROM : 0); } void letter_header::setStatus(int stat) { readO->setStatus(AreaID, LetterID, stat); } long letter_header::getMsgNum() const { return msgNum; } net_address &letter_header::getNetAddr() { return netAddr; } bool letter_header::isPersonal() const { return persfrom || persto; } bool letter_header::isLatin() const { return charset; } void letter_header::setLatin(bool charsetA) { charset = charsetA; } bool letter_header::isQP() const { return qpenc; } void letter_header::setQP(bool qpencA) { qpenc = qpencA; } // ----------------------------------------------------------------- // Letterlist methods // ----------------------------------------------------------------- letter_list::letter_list(mmail *mmA, int areaNumberA, unsigned long typeA) : mm(mmA), areaNumber(areaNumberA), type(typeA) { dl = mm->driverList; driver = dl->getDriver(areaNumber); areaNumber -= dl->getOffset(driver); readO = dl->getReadObject(driver); isColl = (type & COLLECTION) && !(type & REPLYAREA); init(); } letter_list::~letter_list() { cleanup(); } void letter_list::init() { driver->selectArea(areaNumber); noOfLetters = driver->getNoOfLetters(); filter = 0; activeHeader = new int[noOfLetters]; letterHeader = new letter_header *[noOfLetters]; driver->resetLetters(); for (int c = 0; c < noOfLetters; c++) letterHeader[c] = driver->getNextLetter(); currentLetter = 0; llmode = mm->resourceObject->getInt(LetterMode) - 1; sort(); relist(); } void letter_list::relist() { int stat, c = currentLetter; noActive = 0; while (noOfLetters && !noActive) { switch (llmode) { case 0: llmode++; break; case 1: if (readO->getNoOfMarked(areaNumber)) { llmode++; break; } default: llmode = 0; } for (currentLetter = 0; currentLetter < noOfLetters; currentLetter++) if (!filter || filterCheck(filter)) { stat = getStatus(); if ((llmode == 0) || ((llmode == 1) && !(stat & MS_READ)) || ((llmode == 2) && (stat & MS_MARKED))) activeHeader[noActive++] = currentLetter; } // If filter caught nothing, clear it and try again if (filter && !noActive) { llmode--; delete[] filter; filter = 0; } } currentLetter = c; } void letter_list::cleanup() { while (noOfLetters) delete letterHeader[--noOfLetters]; delete[] letterHeader; delete[] activeHeader; delete[] filter; } void letter_list::sort() { if ((noOfLetters > 1) && !(isColl || ((areaNumber + dl->getOffset(driver)) == REPLY_AREA))) qsort(letterHeader, noOfLetters, sizeof(letter_header *), (lsorttype == LS_MSGNUM) ? lmsgncomp : lettercomp); } void letter_list::resort() { if (lsorttype == LS_TO) lsorttype = LS_SUBJ; else { lsorttype++; if ((lsorttype == LS_TO) && ((type & INTERNET) && !(type & NETMAIL))) lsorttype = LS_SUBJ; } sort(); llmode--; relist(); } int letter_list::getMode() const { return llmode; } void letter_list::setMode(int newmode) { llmode = newmode; } int letter_list::noOfLetter() const { return noOfLetters; } int letter_list::noOfActive() const { return noActive; } const char *letter_list::getSubject() const { return letterHeader[currentLetter]->getSubject(); } const char *letter_list::getFrom() const { return letterHeader[currentLetter]->getFrom(); } long letter_list::getMsgNum() const { return letterHeader[currentLetter]->getMsgNum(); } const char *letter_list::getTo() const { return letterHeader[currentLetter]->getTo(); } const char *letter_list::getDate() const { return letterHeader[currentLetter]->getDate(); } const char *letter_list::getMsgID() const { return letterHeader[currentLetter]->getMsgID(); } const char *letter_list::getNewsgrps() const { return letterHeader[currentLetter]->getNewsgrps(); } const char *letter_list::getFollow() const { return letterHeader[currentLetter]->getFollow(); } const char *letter_list::getReply() const { return letterHeader[currentLetter]->getReply(); } long letter_list::getReplyTo() const { return letterHeader[currentLetter]->getReplyTo(); } int letter_list::getAreaID() const { return letterHeader[currentLetter]->getAreaID(); } bool letter_list::getPrivate() const { return letterHeader[currentLetter]->getPrivate(); } letter_body *letter_list::getBody() { return letterHeader[currentLetter]->getBody(); } int letter_list::getLength() const { return letterHeader[currentLetter]->getLength(); } net_address &letter_list::getNetAddr() { return letterHeader[currentLetter]->getNetAddr(); } bool letter_list::isPersonal() const { return letterHeader[currentLetter]->isPersonal(); } bool letter_list::isLatin() const { return letterHeader[currentLetter]->isLatin(); } bool letter_list::isQP() const { return letterHeader[currentLetter]->isQP(); } void letter_list::setQP(bool qpencA) { letterHeader[currentLetter]->setQP(qpencA); } bool letter_list::getRead() { bool read = letterHeader[currentLetter]->getRead(); if (isColl) readO->setRead(areaNumber, currentLetter, read); return read; } void letter_list::setRead() { if (isColl) readO->setRead(areaNumber, currentLetter, true); letterHeader[currentLetter]->setRead(); } int letter_list::getStatus() { int stat = letterHeader[currentLetter]->getStatus(); if (isColl) readO->setStatus(areaNumber, currentLetter, stat); return stat; } void letter_list::setStatus(int stat) { if (isColl) readO->setStatus(areaNumber, currentLetter, stat); letterHeader[currentLetter]->setStatus(stat); } int letter_list::getCurrent() const { return currentLetter; } int letter_list::getActive() const { int c; for (c = 0; c < noActive; c++) if (activeHeader[c] >= currentLetter) break; return c; } void letter_list::gotoLetter(int newLetter) { if (newLetter < noOfLetters) currentLetter = newLetter; } void letter_list::gotoActive(int activeA) { if ((activeA >= 0) && (activeA < noActive)) currentLetter = activeHeader[activeA]; } void letter_list::rrefresh() { cleanup(); init(); gotoActive(noActive - 1); } bool letter_list::findMsgNum(long msgnum) { bool found = false; for (int x = 1; !found && (x <= noOfLetters); x++) { int y = (currentLetter + x) % noOfLetters; found = (letterHeader[y]->getMsgNum() == msgnum); if (found) currentLetter = y; } return found; } bool letter_list::findReply(int area, long msgnum) { bool found = false; for (int x = 0; !found && (x < noOfLetters); x++) { found = (letterHeader[x]->getReplyTo() == msgnum) && (letterHeader[x]->getAreaID() == area); if (found) currentLetter = x; } return found; } const char *letter_list::getFilter() const { return filter; } void letter_list::setFilter(const char *newfilter) { delete[] filter; filter = (newfilter && *newfilter) ? strdupplus(newfilter) : 0; llmode--; relist(); } const char *letter_list::filterCheck(const char *item) { const char *s = searchstr(getFrom(), item); if (!s) { s = searchstr(getTo(), item); if (!s) s = searchstr(getSubject(), item); } return s; } mmail-0.52/mmail/compress.cc000644 000765 000024 00000006776 13065427611 016776 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * compress and decompress packets Copyright 1997 John Zero Copyright 1998-2017 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #include "compress.h" enum atype {A_ARJ, A_ZIP, A_LHA, A_RAR, A_TAR, A_UNKNOWN, A_UNEXIST}; atype lastAType = A_UNKNOWN; // saves last atype for compress routine atype getArchiveType(const char *fname) { FILE *f; unsigned magic; atype tip = A_UNKNOWN; f = fopen(fname, "rb"); if (!f) return A_UNEXIST; magic = fgetc(f) << 8; magic += fgetc(f); switch (magic) { case 0x60EA: tip = A_ARJ; break; case 0x1F8B: tip = A_TAR; // actually the GZIP signature break; case 0x504B: // PK - check for ZIP if (3 == fgetc(f)) if (4 == fgetc(f)) tip = A_ZIP; break; case 0x5261: // Ra - check for RAR if ('r' == fgetc(f)) if ('!' == fgetc(f)) tip = A_RAR; break; default: // can be LHA - check 3. and 4. bytes if ('-' == fgetc(f)) if ('l' == fgetc(f)) tip = A_LHA; // we should check another byte (l/z) but i'm lazy } fclose(f); return tip; } // clears the working directory and uncompresses the packet into it. pktstatus uncompressFile(resource *ro, const char *fname, const char *todir, bool setAType) { static const int uncstr[] = { arjUncompressCommand, zipUncompressCommand, lhaUncompressCommand, rarUncompressCommand, tarUncompressCommand, unknownUncompressCommand }; clearDirectory(todir); atype at = getArchiveType(fname); if (at == A_UNEXIST) return PKT_UNFOUND; if (setAType) lastAType = at; return mysystem2(ro->get(uncstr[at]), fname) ? UNCOMP_FAIL : PKT_OK; } int compressAddFile(resource *ro, const char *arcdir, const char *arcfile, const char *addfname) { static const int cmpstr[] = { arjCompressCommand, zipCompressCommand, lhaCompressCommand, rarCompressCommand, tarCompressCommand, unknownCompressCommand }; int result; char *filepath = fullpath(arcdir, arcfile); mystat st(filepath); #ifdef TAR_KLUDGE // For tar files, forget the parameter passed -- just archive // everything. (Adding a single file would be a multi-step // process. You could do it via a script.) if (lastAType == A_TAR) addfname = "*"; #endif if (!st.readable() || st.writeable()) { const char *cm = ro->get(cmpstr[lastAType]); char *qname = canonize(quotespace(filepath)); char *cmdline = new char[strlen(qname) + strlen(cm) + strlen(addfname) + 6]; sprintf(cmdline, "%s %s %s", cm, qname, addfname); result = mysystem(cmdline); st.reset_date(filepath); if (lastAType == A_LHA) { // then the fixup strcpy(filepath, arcfile); strtok(filepath, "."); sprintf(cmdline, "%s/%s.bak", arcdir, filepath); remove(cmdline); } delete[] cmdline; delete[] qname; } else result = -1; delete[] filepath; return result; } // mainly for use with OMEN replies const char *defExtent() { static const char *ext[] = {"arj", "zip", "lzh", "rar", "tgz", ""}; return ext[lastAType]; } mmail-0.52/mmail/bluewave.h000644 000765 000024 00000203754 12542072425 016610 0ustar00wmcbrinestaff000000 000000 /*****************************************************************************/ /* */ /* The Blue Wave Offline Mail System Packet Structures */ /* Copyright 1990-1995 by Cutting Edge Computing. All rights reserved. */ /* Created by George Hatchew */ /* */ /* Version 3 - November 30, 1995 */ /* */ /* --------------------------------------------------------- */ /* DISTRIBUTION OF THIS FILE IS LIMITED BY THE TERMS */ /* SPECIFIED IN THE BLUE WAVE STRUCTURE DOCUMENTATION! */ /* --------------------------------------------------------- */ /* */ /* These data structures should be usable with any C compiler that */ /* supports the 1989 ANSI/ISO C language standard. They are NOT */ /* guaranteed to be usable with older compilers, which largely relied */ /* on the definition of the language as specified in Kernighan & Ritchie's */ /* _The C Programming Language (1st Edition)_. */ /* */ /*****************************************************************************/ #ifndef __BLUEWAVE_H /* An extra safeguard to prevent this header from */ #define __BLUEWAVE_H /* being included twice in the same source file */ #define PACKET_LEVEL 3 /* The current mail packet revision level, */ /* used in the "ver" field of the *.INF */ /* file header. */ /* ** This header defines the data structures for the following files in the ** official Blue Wave offline mail specification: ** ** Door: *.INF BBS and message area information ** *.MIX Quick index to *.FTI records ** *.FTI Information for all packet messages ** *.DAT Packet message text ** ** Reader: *.NET NetMail reply message information ** *.UPI Information for all other reply messages ** *.UPL Reply message information ** (alternative to *.NET and *.UPI) ** *.REQ List of files to download from BBS ** *.PDQ Offline door configuration information ** (packet version 2 and earlier) ** *.OLC Offline door configuration information ** (packet version 3 and later) ** ** Misc: *.MSG Fido-style message header ** (used *only* in the *.NET structure) ** *.XTI Extended message packet information ** (not an official part of the Blue Wave ** packet specification; is used by the Blue ** Wave reader only) ** ** The door files (plus individual files for BBS bulletins) comprise a Blue ** Wave message packet, and the reader files (plus individual files for each ** message) comprise a Blue Wave reply packet. ** ** In order to cover ALL BASES, and to be able to say that you were warned, ** *ALL* unused fields should be set to ASCII NUL (0). Any future ** implementation of reserved fields will rely on the premise that the field ** will be 0 if not implemented! The same warning follows for BITMAPPED ** fields. If a bit is not implemented or is not used, TURN IT OFF (0). ** (Clearing an entire structure can be easily accomplished via the memset() ** function. Example: "memset(&ftirec, 0, sizeof(FTI_REC))".) */ /*****************************************************************************/ /* >>>>>>>>>>>>>>>>>>>>>>> DATA TYPE DEFINITIONS <<<<<<<<<<<<<<<<<<<<<<<<< */ /*****************************************************************************/ /* ** The data type definitions below help make these structures a little more ** universal between environments. The 8-bit, 16-bit, and 32-bit data types ** defined below can be used as-is with virtually all MS-DOS and OS/2 C/C++ ** compilers, but can be changed if necessary should your compiler define ** data types in a different fashion. (Note that the tCHAR and tINT types ** are currently not used; they are included simply for completeness.) ** ** If you are programming for a system that employs a CPU which stores multi- ** byte integers in a manner other than in Intel format (LSB-MSB, or "little ** endian"), simply #define BIG_ENDIAN before #including this header. As ** shown below, this will define the data types as arrays of bytes; the ** drawback is that *YOU* will have to write functions to convert the data, ** since the Blue Wave packet specification requires the data to be in Intel- ** style little-endian format. ** ** IMPORTANT NOTE ABOUT COMPILERS AND STRUCTURES: ** All structures *must* be "packed" (i.e., the compiler MUST NOT insert ** padding bytes between structure elements in order to force elements onto ** word boundaries). The Blue Wave products expect them to be packed; if ** they aren't, you're bound to get some *very* interesting results. */ #ifdef BIG_ENDIAN typedef signed char tCHAR; /* 8 bit signed values */ typedef unsigned char tBYTE; /* 8 bit unsigned values */ typedef unsigned char tINT[2]; /* little-endian 16 bit signed */ typedef unsigned char tWORD[2]; /* little-endian 16 bit unsigned */ typedef unsigned char tLONG[4]; /* little-endian 32 bit signed */ typedef unsigned char tDWORD[4]; /* little-endian 32 bit unsigned */ #else typedef signed char tCHAR; /* 8 bit signed values */ typedef unsigned char tBYTE; /* 8 bit unsigned values */ typedef signed short tINT; /* 16 bit signed values */ typedef unsigned short tWORD; /* 16 bit unsigned values */ typedef signed long tLONG; /* 32 bit signed values */ typedef unsigned long tDWORD; /* 32 bit unsigned values */ #endif /*****************************************************************************/ /* >>>>>>>>>>>>>>>>>>>>> DOOR DATA FILE STRUCTURES <<<<<<<<<<<<<<<<<<<<<<< */ /*****************************************************************************/ /* ** Name of file: *.INF ** ** Description: The *.INF file is the source of information for just about ** everything from the host BBS, as well as definitions for ** all of the message areas that are available to the user ** and their status (Local, EchoMail, NetMail, Read Only, ** etc.). ** ** File format: INF_HEADER { only included one time! } ** INF_AREA_INFO { repeated for as many msg bases } ** INF_AREA_INFO { as are available to the user } ** ... */ /* Bit-masks for INF_HEADER.UFLAGS field */ #define INF_HOTKEYS 0x0001 /* User uses "hotkeys" in door prompts */ #define INF_XPERT 0x0002 /* Short menus displayed in door */ #define INF_RES1 0x0004 /* RESERVED -- DO NOT USE! */ #define INF_GRAPHICS 0x0008 /* Enable ANSI control sequences in door */ #define INF_NOT_MY_MAIL 0x0010 /* Do not bundle mail from user */ #define INF_EXT_INFO 0x0020 /* Download extended info with messages */ /* (* VERSION 3 AND LATER ONLY *) */ #define INF_NUMERIC_EXT 0x0040 /* Use numeric extensions on packets */ /* (* VERSION 3 AND LATER ONLY *) */ /* Bit-masks for INF_HEADER.NETMAIL_FLAGS field */ #define INF_CAN_CRASH 0x0002 /* Allow Crash status */ #define INF_CAN_ATTACH 0x0010 /* Allow File Attach messages */ #define INF_CAN_KSENT 0x0080 /* Allow Kill/Sent status */ #define INF_CAN_HOLD 0x0200 /* Allow Hold status */ #define INF_CAN_IMM 0x0400 /* Allow Immediate status */ #define INF_CAN_FREQ 0x0800 /* Allow File Request messages */ #define INF_CAN_DIRECT 0x1000 /* Allow Direct status */ /* Bit-masks for INF_HEADER.CTRL_FLAGS field */ #define INF_NO_CONFIG 0x0001 /* Do not allow offline configuration */ #define INF_NO_FREQ 0x0002 /* Do not allow file requesting */ /* Values for INF_HEADER.FILE_LIST_TYPE field */ #define INF_FLIST_NONE 0 /* Door does not generate a list file */ #define INF_FLIST_TEXT 1 /* Door generates plain text list file */ #define INF_FLIST_ANSI 2 /* Door generates ANSI list file */ typedef struct /* INF_HEADER */ { tBYTE ver; /* Packet version type (currently 2) */ tBYTE readerfiles[5][13]; /* Files to be displayed by reader */ tBYTE regnum[9]; /* User's registration number */ tBYTE mashtype; /* Currently unused (door fills with 0) */ /* Reserved for Blue Wave reader to store */ /* the compression type the packet uses. */ tBYTE loginname[43]; /* Name user types at BBS login */ tBYTE aliasname[43]; /* User's "other" name */ tBYTE password[21]; /* Password */ /* All bytes should be the actually ASCII */ /* value plus 10. Lame security, yes, */ /* but it does prevent "TYPE *.INF" from */ /* showing the password. */ tBYTE passtype; /* Password type */ /* 0=none 1=door 2=reader 3=both */ tWORD zone; /* Main network address of host BBS */ tWORD net; /* (zone:net/node.point) */ tWORD node; tWORD point; tBYTE sysop[41]; /* Name of SysOp of host BBS */ tWORD ctrl_flags; /* Flags to control reader capabilities */ /* (* VERSION 3 AND LATER ONLY *) */ tBYTE systemname[65]; /* Name of host BBS */ tBYTE maxfreqs; /* Max number of file requests allowed */ tWORD is_QWK; /* Whether *.INF belongs to a QWK packet */ tBYTE obsolete2[4]; /* OBSOLETE -- DO NOT USE! */ tWORD uflags; /* Bit-mapped door options/toggles */ tBYTE keywords[10][21]; /* User's entire set of door keywords */ tBYTE filters[10][21]; /* User's entire set of door filters */ tBYTE macros[3][80]; /* User's door bundling command macros */ tWORD netmail_flags; /* Bit-mapped NetMail options */ tWORD credits; /* NetMail credits */ tWORD debits; /* NetMail debits */ tBYTE can_forward; /* 0=Message forwarding not allowed */ tWORD inf_header_len; /* Size of INF_HEADER structure */ tWORD inf_areainfo_len; /* Size of INF_AREA_INFO structure */ tWORD mix_structlen; /* Size of MIX_REC structure */ tWORD fti_structlen; /* Size of FTI_REC structure */ tBYTE uses_upl_file; /* If this field is not zero, the door that */ /* created this packet can receive reply */ /* packets in the new *.UPL file format. */ /* Otherwise, the old *.UPI and *.NET */ /* files must be used. */ tBYTE from_to_len; /* The maximum length of the FROM: and TO: */ /* fields that the host BBS can support. */ /* If this value is 0 or is greater than */ /* 35, then 35 must be used (the upload */ /* file formats only allow for a maximum */ /* of 35 characters). */ tBYTE subject_len; /* The maximum length of the SUBJECT: field */ /* that the host BBS can support. If */ /* this value is 0 or is greater than 71, */ /* then 71 must be used (the upload file */ /* formats only allow for a maximum of 71 */ /* characters). */ tBYTE packet_id[9]; /* Original root name of the mail packet, */ /* as specified by the mail door. All */ /* files in the packet that are created */ /* by the mail door will use this root */ /* name, as will the reader when creating */ /* the upload files. Thus, even if the */ /* packets themselves are renamed to */ /* something completely different, the */ /* mail doors and readers will still be */ /* able to work with the proper files. */ tBYTE file_list_type; /* New file listing type */ /* (* VERSION 3 AND LATER ONLY *) */ /* Specifies the type of new file list */ /* that is generated by the door (see */ /* INF_FLIST_xxx, above). This field is */ /* intended for use with offline config. */ tBYTE auto_macro[3]; /* Auto-macro indicator flags */ /* (* VERSION 3 AND LATER ONLY *) */ /* Specifies which macros are auto macros */ /* (i.e. execute automatically after mail */ /* is scanned). */ tINT max_packet_size; /* Maximum size of uncompressed packet */ /* (* VERSION 3 AND LATER ONLY *) */ /* Specifies, in K, the maximum size of */ /* an uncompressed mail packet. A value */ /* of 0 indicates no maximum length. */ /* This field is intended for use with */ /* offline config. */ tBYTE reserved[228]; /* RESERVED FOR FUTURE USE */ /* This field MUST be filled with ASCII */ /* NUL (0x00) characters in order for */ /* future additional features to work */ /* properly! */ } INF_HEADER; /* ** Notes about the INF_HEADER.XXXXX_LEN fields, above: ** ** Door authors should take the few extra lines of code to fill in the ** structure lengths defined above. Doing so will make the Blue Wave data ** structures extensible and adaptable to almost any kind of file change that ** may be required in the future. The readers that use this mail packet ** format should contain code to handle a structure length that is longer or ** shorter than they expect. ** ** Reader authors need to take the time to code for possible extensions to ** this file format. If the data fields are LONGER than expected, simply do ** a seek to move to the next record, and ignore the extra information. If ** the data fields are SHORTER than expected, a simple "Please upgrade your ** reader" should suffice. However, you should never encounter a ** record size smaller than the ones defined here. Any extra information ** that is sent in the packets probably would not be crucial, and you may be ** able to continue with reading the packet anyway. ** ** It should be noted that all current Blue Wave doors set these fields to 0, ** as this extensibility was not added until recently. If the structure ** sizes are 0, the reader will assume that all records are of the sizes ** defined here. (Blue Wave readers below version 2.10 do NOT allow for ** extensible data files. Version 2.10, and all subsequent versions, WILL ** handle them properly.) DO NOT EXTEND THESE STRUCTURE FORMATS WITHOUT ** NOTIFYING CUTTING EDGE COMPUTING FIRST! If the extended information will ** benefit programs/users, it will be officially added to the packet format. ** ** The original values for the INF_HEADER.XXXXX_LEN structures are as below, ** defined as macros which you can use in your programs. Remember, if the ** value in INF_HEADER.XXXXX_LEN is 0, you must use these values instead! */ #define ORIGINAL_INF_HEADER_LEN 1230 /* Original *.INF header len */ #define ORIGINAL_INF_AREA_LEN 80 /* Original *.INF area rec len */ #define ORIGINAL_MIX_STRUCT_LEN 14 /* Original *.MIX record len */ #define ORIGINAL_FTI_STRUCT_LEN 186 /* Original *.FTI record len */ /* ** Below is some sample C code for reading in the variable length *.INF ** structure, which is the most "difficult" one to do. Note the sections of ** code which use the ORIGINAL_XXXXX_LEN macros; these are the sections that ** determine the proper structure length. (Comments are preceeded by "#" ** signs, since using C comment symbols would make most compilers think that ** nested comments are in use, a practice which normally is not allowed.) ** ** int read_inf_file(void) ** { ** INF_HEADER inf_header; ** INF_AREA_INFO inf_info; ** FILE *inf_file=NULL; ** tWORD record_num=0u; ** tWORD inf_header_slen, inf_area_slen; ** tLONG seek_pos=0L; ** ** inf_file = fopen("WILDBLUE.INF", "rb"); ** if (inf_file == NULL) ** return 0; ** ** fread(&inf_header, sizeof(INF_HEADER), 1, inf_file); ** puts(inf_header.loginname); ** puts(inf_header.aliasname); ** ** # Test and verify the validity of the structure lengths. ** ** if (inf_header.inf_header_len < ORIGINAL_INF_HEADER_LEN) ** inf_header_slen = ORIGINAL_INF_HEADER_LEN; ** else ** inf_header_slen = inf_header.inf_header_len; ** ** if (inf_header.inf_areainfo_len < ORIGINAL_INF_AREA_LEN) ** inf_area_slen = ORIGINAL_INF_AREA_LEN; ** else ** inf_area_slen = inf_header.inf_areainfo_len; ** ** # now, move to the END of the header, since it may be longer ** # than we expect it to be. Use fseek()... ** ** fseek(inf_file, (long)inf_header_slen, SEEK_SET); ** ** record_num = 0U; ** while(fread(&inf_info, sizeof(INF_AREA_INFO), 1, inf_file)) ** { ** puts(inf_info.title); ** record_num++; ** ** # we need to seek past the header, and then [record_num] ** # number of recs. ** ** seek_pos = (long)(inf_header_slen+(record_num*inf_area_slen)); ** fseek(inf_file, seek_pos, SEEK_SET); ** } ** ** fclose(inf_file); ** return 1; ** } */ /* Bit-masks for INF_AREA_INFO.AREA_FLAGS field */ #define INF_SCANNING 0x0001 /* On=User is active for area */ #define INF_ALIAS_NAME 0x0002 /* On=Alias name, Off=Login name */ /* If ON, use INF_HEADER.ALIASNAME when */ /* addressing new mail or replies for the */ /* message area. If OFF, the reader uses */ /* the INF_HEADER.LOGINNAME for this */ /* purpose. */ #define INF_ANY_NAME 0x0004 /* On=Allow any name to be entered */ /* If ON, any name can be entered in the */ /* From: field when addressing new mail */ /* or replies for the message area. If */ /* OFF, the normal rules apply. */ #define INF_ECHO 0x0008 /* On=Network mail, Off=Local mail */ #define INF_NETMAIL 0x0010 /* On=E-mail, Off=Conference mail */ /* Refer to the chart below (the values */ /* for the NETWORK_TYPE field) for info */ /* on how these two flags should be set */ /* for message areas. */ #define INF_POST 0x0020 /* On=User can post, Off=User CANNOT post */ #define INF_NO_PRIVATE 0x0040 /* On=Private messages are NOT allowed */ #define INF_NO_PUBLIC 0x0080 /* On=Public messages are NOT allowed */ #define INF_NO_TAGLINE 0x0100 /* On=Taglines are not allowed */ #define INF_NO_HIGHBIT 0x0200 /* On=ASCII 1-127 only, Off=ASCII 1-255 */ /* If ON, only ASCII values 1 to 127 are */ /* allowed in messages. If OFF, all */ /* values from 1 to 255 are allowed. Due */ /* to the fact that ASCII value 0 is used */ /* in C as a string terminator, the value */ /* 0 should not be allowed in messages at */ /* all. */ #define INF_NOECHO 0x0400 /* On=User can prevent messages from being */ /* sent through the network */ #define INF_HASFILE 0x0800 /* On=User can attach files to messages */ #define INF_PERSONAL 0x1000 /* On=User is downloading only personal */ /* msgs in this message area. The flag */ /* INF_SCANNING also needs to be ON. */ /* (* VERSION 3 AND LATER ONLY *) */ #define INF_TO_ALL 0x2000 /* On=User is downloading messages to "All" */ /* and personal messages only in this */ /* area. The flag INF_SCANNING also */ /* needs to be ON. INF_PERSONAL should */ /* *not* be set, as this flag implies the */ /* downloading of personal messages also. */ /* (* VERSION 3 AND LATER ONLY *) */ /* Values for INF_AREA_INFO.NETWORK_TYPE field */ #define INF_NET_FIDONET 0 /* FidoNet-style E-mail and conferences */ /* Local = INF_ECHO=off, NETMAIL=off */ /* EchoMail = INF_ECHO=on, NETMAIL=off */ /* GroupMail = INF_ECHO=on, NETMAIL=off */ /* NetMail = INF_ECHO=on, NETMAIL=on */ #define INF_NET_INTERNET 1 /* Internet E-mail and Usenet newsgroups */ /* Local = INF_ECHO=off, NETMAIL=off */ /* Newsgroup = INF_ECHO=on, NETMAIL=off */ /* E-mail = INF_ECHO=on, NETMAIL=on */ typedef struct /* INF_AREA_INFO */ { tBYTE areanum[6]; /* Area number this record corresponds to */ tBYTE echotag[21]; /* Area tag name (*.BRD name for Telegard) */ tBYTE title[50]; /* Area description/title */ tWORD area_flags; /* Bit-mapped area options */ tBYTE network_type; /* Network mail type (see above) */ } INF_AREA_INFO; /*---------------------------------------------------------------------------*/ /* ** Name of file: *.MIX ** ** Description: The *.MIX file is a very small file, with one record for ** every message area that was scanned. It contains the ** information to get into the *.FTI file. ** ** File format: MIX_REC { repeated for each message area scanned } ** MIX_REC ** ... */ typedef struct /* MIX_REC */ { tBYTE areanum[6]; /* Area number this record corresponds to */ /* This is the ASCII representation of the */ /* actual area number shown on the host BBS. */ tWORD totmsgs; /* Total number of messages for this area */ tWORD numpers; /* Total number of personal messages in this area */ tLONG msghptr; /* Pointer to first message header in *.FTI file */ } MIX_REC; /*---------------------------------------------------------------------------*/ /* ** Name of file: *.FTI ** ** Description: The *.FTI file contains the information for each message ** in the packet. Each record includes all of the ** information about the message, including the pointer to ** the actual message text in the *.DAT file. ** ** NOTE: Messages in the *.FTI file will ALWAYS be in area ** number order. That is to say, if the MIX_REC ** indicates there are 100 messages for this area, ** all 100 messages will follow in sequential order. ** ** File format: FTI_REC { repeated for as many messages } ** FTI_REC { as obtained from the host BBS } ** ... */ /* Bit-masks for FTI_REC.FLAGS field */ #define FTI_MSGPRIVATE 0x0001 /* Private = For addressee ONLY */ #define FTI_MSGCRASH 0x0002 /* Crash = High priority mail */ #define FTI_MSGREAD 0x0004 /* Read = Message read by addressee */ #define FTI_MSGSENT 0x0008 /* Sent = Message sent */ #define FTI_MSGFILE 0x0010 /* File Attach = Send file(s) */ #define FTI_MSGFWD 0x0020 /* Forward = Message to/from others */ #define FTI_MSGORPHAN 0x0040 /* Orphan = Message destination unknown */ #define FTI_MSGKILL 0x0080 /* Kill/Sent = Delete after sending */ #define FTI_MSGLOCAL 0x0100 /* Local = Message originated here */ #define FTI_MSGHOLD 0x0200 /* Hold = Hold for pickup, don't send */ #define FTI_MSGIMMEDIATE 0x0400 /* Immediate = Send message NOW */ #define FTI_MSGFRQ 0x0800 /* File Request = Request file(s) */ #define FTI_MSGDIRECT 0x1000 /* Direct = Send direct, no routing */ #define FTI_MSGUNUSED1 0x2000 /* */ #define FTI_MSGUNUSED2 0x4000 /* */ #define FTI_MSGURQ 0x8000 /* Update Request = Req updated file(s) */ typedef struct /* FTI_REC */ { tBYTE from[36]; /* Person message is from */ tBYTE to[36]; /* Person message is to */ tBYTE subject[72]; /* Subject/title of message */ tBYTE date[20]; /* Origin date of message */ /* Depending on the host BBS's date storage */ /* format, the EXACT format of this field */ /* will change. Some will take all 19 bytes, */ /* others may take only 10. */ tWORD msgnum; /* Number of THIS message on BBS */ tWORD replyto; /* "This is a reply to #xx" */ /* Not used for every message. When non- */ /* zero, there is a previous message in */ /* the thread. */ tWORD replyat; /* "There is a reply at #xx" */ /* Not used for every message. When non- */ /* zero, there is a reply to this message. */ tLONG msgptr; /* Offset to start of message in *.DAT file */ /* Seek to this exact offset in the *.DAT */ /* file, then read "msglength" bytes from */ /* the file to load the entire message text. */ tLONG msglength; /* Length of message text (in bytes) */ tWORD flags; /* Bit-mapped message status flags */ tWORD orig_zone; /* Origin address of message */ /* These three fields will most likely be 0, */ /* unless the current message belongs to a */ /* NetMail message base. */ tWORD orig_net; tWORD orig_node; } FTI_REC; /*---------------------------------------------------------------------------*/ /* ** Name of file: *.DAT ** ** Description: The *.DAT file is an unstructured file which contains the ** text of every message obtained from the host BBS. ** Valid messages begin with an ASCII space (0x20) character ** (which is NOT to be considered part of the message!) ** followed by zero or more bytes which constitute the ** message text. The pointer to the text for each message is ** stored in FTI_REC.MSGPTR, and the length of the text for ** each message is stored in FTI_REC.MSGLENGTH. ** ** File format: Unstructured */ /*****************************************************************************/ /* >>>>>>>>>>>>>>>>> MISCELLANEOUS DATA FILE STRUCTURES <<<<<<<<<<<<<<<<<< */ /*****************************************************************************/ /* ** Name of file: *.MSG ** ** Description: The Fido *.MSG message (named for the BBS program on which ** it originated) has become a de-facto standard among BBS ** implementations, due to the sheer number of utilities ** available that operate with *.MSG messages. It is as ** close to a universal message format as one can get in ** FidoNet (and FidoNet-style networks), and is the reason ** why it is used here (well, the *.MSG header, anyway). ** ** NOTE: Most of the fields in the FTI_REC structure (shown ** above) correspond to similar fields in MSG_REC. ** This was done deliberately, in order to make ** *.FTI file processing a little more intuitive for ** programmers. Also note that MSG_REC is only used ** by the NET_REC structure, which will soon become ** obsolete (replaced by UPL_REC). ** ** File format: MSG_REC { only included one time! } ** message text { text can be terminated by an ASCII NUL } ** { character (0x00), or by an ASCII CR, } ** { LF, NUL (0x0D 0x0A 0x00) sequence } */ /* Bit-masks for MSG_REC.ATTR field */ #define MSG_NET_PRIVATE 0x0001 /* Private */ #define MSG_NET_CRASH 0x0002 /* Crash mail */ #define MSG_NET_RECEIVED 0x0004 /* Received */ #define MSG_NET_SENT 0x0008 /* Sent */ #define MSG_NET_FATTACH 0x0010 /* File attached */ #define MSG_NET_INTRANSIT 0x0020 /* In-transit */ #define MSG_NET_ORPHAN 0x0040 /* Orphaned */ #define MSG_NET_KILL 0x0080 /* Kill after sending */ #define MSG_NET_LOCAL 0x0100 /* Local message */ #define MSG_NET_HOLD 0x0200 /* Hold for pickup */ #define MSG_NET_RESERVED 0x0400 /* RESERVED */ #define MSG_NET_FREQ 0x0800 /* File request */ #define MSG_NET_RREQ 0x1000 /* Return receipt request */ #define MSG_NET_RECEIPT 0x2000 /* Return receipt message */ #define MSG_NET_AREQ 0x4000 /* Audit request */ #define MSG_NET_FUREQ 0x8000 /* File update request */ typedef struct /* MSG_REC (will soon be obsolete) */ { tBYTE from[36]; /* Person message is from */ tBYTE to[36]; /* Person message is to */ tBYTE subj[72]; /* Subject/title of message */ tBYTE date[20]; /* Creation date/time */ /* This date/time is usually in either of the */ /* Fido-sanctioned formats "DD MMM YY HH:MM:SS" */ /* or "WWW DD MMM YY HH:MM", but due to the */ /* chaotic nature of FidoNet-compatible software, */ /* this CANNOT be relied upon! */ tWORD times; /* Number of times read (fairly obsolete) */ tWORD dest; /* Destination node (of net/node) */ tWORD orig; /* Origin node (of net/node) */ tWORD cost; /* Cost of sending message (usually in US cents) */ tWORD orig_net; /* Origin net (of net/node) */ tWORD destnet; /* Destination net (of net/node) */ tLONG unused1; /* Undefined */ tLONG unused2; /* Some software (Opus and Maximus, for example) */ /* uses these fields to store the sent/received */ /* date/time as bit-packed fields, using the same */ /* format used in MS-DOS directory entries. */ tWORD reply; /* Message # that this message replies to */ tWORD attr; /* Message attributes and behavior flags */ tWORD up; /* Message # that replies to this message */ } MSG_REC; /*---------------------------------------------------------------------------*/ /* ** Name of file: *.XTI ** ** Description: The *.XTI file contains extended information for each ** message in the packet. The number of records in the *.XTI ** file will always equal the number of messages in the ** packet, with each record corresponding to a record in the ** *.FTI file (i.e. record #1 in the *.XTI file corresponds ** to record #1 in the *.FTI file, and so on). ** ** NOTE: This file is currently created ONLY by the Blue ** Wave reader, and is not a part of the official ** Blue Wave packet specification; it is merely ** documented here for third party programmers to use ** if they so desire. How other readers store which ** messages have been read/replied-to/marked is left ** as an option to be implemented by the individual ** reader authors. You may use this method if you so ** desire; however, PLEASE do not name any external ** files not conforming to this specification as ** .XTI, due to the fact that the Blue ** Wave reader will expect the file to be in the ** format described. If it's not in the expected ** format, things will get interesting. :-) ** ** File format: XTI_REC { repeated for as many messages } ** XTI_REC { as obtained from the host BBS } ** ... */ /* Bit-masks for XTI_REC.FLAGS field */ #define XTI_HAS_READ 0x01 /* Message has been read */ #define XTI_HAS_REPLIED 0x02 /* Message has been replied to */ #define XTI_IS_PERSONAL 0x04 /* Message is personal */ #define XTI_IS_TAGGED 0x08 /* Message has been 'tagged' */ #define XTI_HAS_SAVED 0x10 /* Message has been saved */ #define XTI_HAS_PRINTED 0x20 /* Message has been printed */ /* Bit-masks for XTI_REC.MARKS field */ #define XTI_MARK_SAVE 0x01 /* Message marked for saving */ #define XTI_MARK_REPLY 0x02 /* Message marked for replying */ #define XTI_MARK_PRINT 0x04 /* Message marked for printing */ #define XTI_MARK_DELETE 0x08 /* Message marked for deletion */ typedef struct /* XTI_REC */ { tBYTE flags; /* Bit-mapped message flags */ tBYTE marks; /* Bit-mapped message markers */ } XTI_REC; /*****************************************************************************/ /* >>>>>>>>>>>>>>>>>>>> READER DATA FILE STRUCTURES <<<<<<<<<<<<<<<<<<<<<< */ /*****************************************************************************/ /* ** Name of file: *.NET ** ** Description: The *.NET file is created ONLY when there is NetMail to be ** sent. It contains the FULL header of the Fido-style *.MSG ** structure plus the fields defined below (which aren't part ** of the standard *.MSG structure yet required by the door). ** ** NOTE: Readers should only generate a *.NET file if ** INF_HEADER.USES_UPL_FILE is not set *AND* the ** mail packet format is version 2 or earlier. ** Doors should process *.NET files *ONLY* in cases ** where a *.UPL file is not present. ** ** File format: NET_REC { repeated for as many NetMail } ** NET_REC { messages as exist in the packet } ** ... */ typedef struct /* NET_REC */ { MSG_REC msg; /* The Fido-style *.MSG header */ tBYTE fname[13]; /* Filename the message text is in */ tBYTE echotag[21]; /* NetMail area tag (*.BRD name for Telegard) */ tWORD zone; /* Destination zone (of zone:net/node.point) */ tWORD point; /* Destination point (of zone:net/node.point) */ tLONG unix_date; /* Date/time of message */ /* This Unix-style date/time value (number */ /* of seconds since 01/01/70) is converted */ /* to the date/time storage method used by */ /* the host BBS. */ } NET_REC; /*---------------------------------------------------------------------------*/ /* ** Name of file: *.UPI ** ** Description: The *.UPI file contains the information for each message ** in the reply packet, as well as information on the reader ** version and registration numbers. Each record includes ** all of the information about the message. ** ** NOTE: Readers should only generate a *.UPI file if ** INF_HEADER.USES_UPL_FILE is not set *AND* the ** mail packet format is version 2 or earlier. ** Doors should process *.UPI files *ONLY* in cases ** where a *.UPL file is not present. ** ** File format: UPI_HEADER { only included one time! } ** UPI_REC { repeated for as many msg bases } ** UPI_REC { as are available to the user } ** ... */ typedef struct /* UPI_HEADER */ { tBYTE regnum[9]; /* Reader registration number */ tBYTE vernum[13]; /* Reader version number */ /* All bytes should be the actually ASCII */ /* value plus 10. Lame security, yes, but it */ /* does prevent "TYPE *.UPI" from showing the */ /* version number. */ tBYTE future[33]; /* RESERVED FOR FUTURE USE */ #ifdef PAD_SIZES_EVEN tBYTE evenpad; /* If your compiler pads structures out to even */ /* numbered sizes, define PAD_SIZES_EVEN */ /* before including this header. When the */ /* *.UPI file is written, be sure to write */ /* sizeof(UPI_HEADER) - 1 bytes, otherwise */ /* your compiler may insert an extra byte not */ /* explicitly specified here. */ #endif } UPI_HEADER; /* Bit-masks for UPI_REC.FLAGS field */ #define UPI_RES1 0x01 /* RESERVED FOR FUTURE USE */ #define UPI_RES2 0x02 /* RESERVED FOR FUTURE USE */ #define UPI_RES3 0x04 /* RESERVED FOR FUTURE USE */ #define UPI_RES4 0x08 /* RESERVED FOR FUTURE USE */ #define UPI_RES5 0x10 /* RESERVED FOR FUTURE USE */ #define UPI_RES6 0x20 /* RESERVED FOR FUTURE USE */ #define UPI_PRIVATE 0x40 /* Message is PRIVATE */ #define UPI_NO_ECHO 0x80 /* Message is NOT to be echoed */ /* This feature is not yet implemented in */ /* the Blue Wave reader or doors, as none */ /* of the currently supported BBS software */ /* has support for this feature. */ typedef struct /* UPI_REC */ { tBYTE from[36]; /* Person message is from */ tBYTE to[36]; /* Person message is to */ tBYTE subj[72]; /* Subject/title of message */ tLONG unix_date; /* Date/time of message */ /* This Unix-style date/time value (number */ /* of seconds since 01/01/70) is converted */ /* to the date/time storage method used by */ /* the host BBS. */ tBYTE fname[13]; /* Filename the message text is in */ tBYTE echotag[21]; /* Area tag name (*.BRD name for Telegard) */ tBYTE flags; /* Bit-mapped flags */ tBYTE reedit; /* INTERNAL USE ONLY! */ /* This flag is used internally by the Blue */ /* Wave reader. Doors should ignore this */ /* field during reply packet processing. */ } UPI_REC; /*---------------------------------------------------------------------------*/ /* ** Name of file: *.UPL ** ** Description: The *.UPL file contains the information for each message ** in the reply packet, as well as information on the reader ** version and registration numbers. Each record includes ** all of the information about the message. ** ** NOTE: Readers should only generate a *.UPL file if ** INF_HEADER.USES_UPL_FILE is set *AND/OR* the mail ** packet format is version 3 or later. Doors should ** process *.UPL files in all cases where one is ** present. ** ** File format: UPL_HEADER { only included one time! } ** UPL_REC { repeated for as many messages } ** UPL_REC { as are included in the packet } ** ... */ typedef struct /* UPL_HEADER */ { tBYTE regnum[10]; /* Reader registration number (if desired) */ tBYTE vernum[20]; /* Reader version number as a string. */ /* All bytes should be the actually ASCII */ /* value plus 10. Lame security, yes, but it */ /* does prevent "TYPE *.UPL" from showing the */ /* version number. */ /* Examples: "2.10a Beta" */ /* "2.11" */ tBYTE reader_major; /* Major version of the reader (number to the */ /* left of the decimal point) */ tBYTE reader_minor; /* Minor version of the reader (number to the */ /* right of the decimal point) */ tBYTE reader_name[80]; /* String containing name of the reader, such */ /* as "The Blue Wave Offline Mail Reader". */ /* This is provided for door programmers that */ /* wish to display the name of the reader */ /* that created the reply packet. (Filling */ /* it is mandatory but using it is optional.) */ tWORD upl_header_len; /* Size of UPL_HEADER structure */ tWORD upl_rec_len; /* Size of UPL_REC structure */ /* NOTE: Refer to the INF_HEADER section for */ /* more information on using the size */ /* fields. */ tBYTE loginname[44]; /* Name found in INF_HEADER.LOGINNAME. This is */ /* provided for door authors as a security */ /* measure to implement as they wish. */ tBYTE aliasname[44]; /* Name found in INF_HEADER.ALIASNAME */ tBYTE reader_tear[16]; /* String containing abbreviated name of the */ /* reader, such as "Blue Wave", "Q-Blue", */ /* "Wave Rider", etc. This is provided for */ /* doors programmers that wish to add to the */ /* tear line the name of the reader that */ /* created the reply packet. (Filling it is */ /* mandatory but using it is optional.) If */ /* this field is blank, the tear line to be */ /* generated is left to the discretion of the */ /* door author. */ tBYTE compress_type; /* Compression type required for mail packet */ /* The Blue Wave reader uses this internally */ /* to store the compression type required for */ /* this particular mail packet. */ tBYTE flags; /* Reader processing flags */ /* The Blue Wave reader uses this internally */ /* to store flags required for later */ /* processing. */ /* 0x01 = Was a .QWK packet. */ /* 0x02 = Host requires a *.UPI file */ tBYTE not_registered; /* Reader is not registered to user */ /* If this byte is set to a non-zero value, */ /* the Blue Wave doors will assume that the */ /* user's reader was not registered, and will */ /* place "[NR]" at the end of the tear line. */ /* Third-party doors may use this flag for */ /* the same purpose; its use is optional by */ /* mail readers (especially if you don't care */ /* whether or not "[NR]" shows up on the tear */ /* line ). */ tBYTE pad[33]; /* RESERVED FOR FUTURE USE, and to pad struct */ /* out to a 'nice' 256 bytes */ } UPL_HEADER; /* Bit-masks for UPL_REC.MSG_ATTR field */ #define UPL_INACTIVE 0x0001 /* Message is INACTIVE */ /* Doors should NOT attempt to import this */ /* message. */ #define UPL_PRIVATE 0x0002 /* Message is PRIVATE */ #define UPL_NO_ECHO 0x0004 /* Message is NOT to be echoed */ /* This feature is not yet implemented in */ /* the Blue Wave reader or doors, as none */ /* of the currently supported BBS software */ /* has support for this feature. */ #define UPL_HAS_FILE 0x0008 /* Message has file "attached" to it */ /* It is up to the door to check the */ /* validity of this flag. If the file is */ /* contained in the mail packet, great. */ /* If not, the door should probably prompt */ /* the user to begin uploading the file */ /* after importing the messages. (Not yet */ /* implemented in the Blue Wave reader.) */ #define UPL_NETMAIL 0x0010 /* Message is network mail */ /* Indicates NetMail/E-mail message. The */ /* NETWORK_TYPE field (see below) will */ /* indicate which fields should be used */ /* for addressing the message. */ #define UPL_IS_REPLY 0x0020 /* Indicates that the message is a reply to */ /* an existing message, rather than being */ /* a completely new message. */ #define UPL_MRES7 0x0040 /* RESERVED FOR FUTURE USE */ #define UPL_MRES8 0x0080 /* RESERVED FOR FUTURE USE */ /* All of the other 8 bits of this field are */ /* also reserved for future use. This */ /* should provide for plenty of expansion */ /* for future development. */ /* Bit-masks for UPL_REC.NETMAIL_ATTR field */ #define UPL_NRES1 0x0001 /* RESERVED FOR FUTURE USE */ #define UPL_NETCRASH 0x0002 /* Crash = High priority mail */ #define UPL_NRES2 0x0004 /* RESERVED FOR FUTURE USE */ #define UPL_NRES3 0x0008 /* RESERVED FOR FUTURE USE */ #define UPL_NETFILE 0x0010 /* File Attach = Send file(s) listed */ /* in Subject field */ #define UPL_NRES4 0x0020 /* RESERVED FOR FUTURE USE */ #define UPL_NRES5 0x0040 /* RESERVED FOR FUTURE USE */ #define UPL_NETKILL 0x0080 /* Kill/Sent = Delete after sending */ #define UPL_NETLOCAL 0x0100 /* Local = Message originated here */ #define UPL_NETHOLD 0x0200 /* Hold = Hold for pickup, do not send */ #define UPL_NETIMMEDIATE 0x0400 /* Immediate = Send message NOW */ #define UPL_NETFRQ 0x0800 /* File Request = Request file(s) */ /* listed in Subject field */ #define UPL_NETDIRECT 0x1000 /* Direct = Send direct, no routing */ #define UPL_NRES6 0x2000 /* RESERVED FOR FUTURE USE */ #define UPL_NRES7 0x4000 /* RESERVED FOR FUTURE USE */ #define UPL_NETURQ 0x8000 /* Update Request = Request updated */ /* file(s) listed in Subject field */ /* Values for UPL_REC.NETWORK_TYPE field */ #define UPL_NET_FIDONET 0 /* FidoNet-style E-mail and conferences */ /* UPL_NETMAIL=off - Local, Echo, Group */ /* UPL_NETMAIL=on - NetMail */ #define UPL_NET_INTERNET 1 /* Internet E-mail and Usenet newsgroups */ /* UPL_NETMAIL=off - Local, Newsgroup */ /* UPL_NETMAIL=on - E-mail */ typedef struct /* UPL_REC */ { tBYTE from[36]; /* Person message is from */ /* NOTE: Doors should validate this field! */ tBYTE to[36]; /* Person message is to (non-Internet) */ /* For Internet E-mail, the NET_DEST field */ /* should be used to store the destination */ /* name/address, leaving this field blank. */ /* For Usenet newsgroups, this field should be */ /* left blank, as newsgroups don't use a "To:" */ /* field. */ tBYTE subj[72]; /* Subject/Title of message */ tWORD destzone; /* Destination address (FidoNet only) */ /* If the message is not a FidoNet NetMail */ /* message, this field (and the subsequent */ /* three fields as well) should be set to */ /* zero. */ tWORD destnet; tWORD destnode; tWORD destpoint; tWORD msg_attr; /* Bit-mapped message attributes */ tWORD netmail_attr; /* Bit-mapped NetMail attributes (FidoNet only) */ /* If the message is not a FidoNet NetMail */ /* message, this field should not be used. */ tLONG unix_date; /* Date/time of message */ /* This Unix-style date/time value (number */ /* of seconds since 01/01/70) is converted to */ /* the date/time storage method used by the */ /* host BBS. */ tDWORD replyto; /* This unsigned long word stores the message # */ /* that this message is a reply to. This */ /* should be the same as FTI.MSGNUM. Note, */ /* however, that FTI.MSGNUM is a word. C */ /* programmers especially will need to */ /* properly typecast the value (i.e. */ /* upl.replyto=(tDWORD)fti.msgnum). As */ /* messaging/BBS systems become more complex, */ /* FTI.MSGNUM may become obsolete, and a */ /* tDWORD variable may be used in its place. */ tBYTE filename[13]; /* Filename the message text is in */ /* If this file does not exist in the upload */ /* packet then doors should consider this an */ /* invalid record. */ tBYTE echotag[21]; /* Area tag the message goes in */ /* This must correspond exactly to the */ /* INF_AREA_INFO.ECHOTAG field for the message */ /* area this message belongs to. Simple area */ /* number matching has proven not to work */ /* simply because sysops are finicky people, */ /* and seem to constantly renumber/change the */ /* message area numbers on the host BBS. */ /* Using an echotag helps to alleviate this */ /* problem. C_ECHO will be C_ECHO on the BBS, */ /* whether it is msg area 17 on the host BBS */ /* or whether it is area 207. Doors should do */ /* a case-INSENSITIVE compare on this field to */ /* find where the message belongs. */ tWORD area_flags; /* The Blue Wave Offline Mail Reader uses this */ /* word internally to store the same value as */ /* in INF_AREA_INFO.AREA_FLAGS. The purpose */ /* of this word is to hold the original */ /* information about the message area so that */ /* later message editing processes can be */ /* controlled properly. For example, if a */ /* user later wanted to edit this message, the */ /* reader would know instantly whether this is */ /* a NETMAIL area, whether PVT messages are */ /* allowed, etc. This allows re-editing of */ /* the message, even when there is not a */ /* corresponding *.INF file laying around, or */ /* the area is not listed in the *.INF file */ /* you currently have to work with. DOOR */ /* AUTHORS SHOULD IGNORE THIS FIELD WHEN */ /* IMPORTING MESSAGES! */ tBYTE f_attach[13]; /* If the UPL_HAS_FILE flag is set, this field */ /* will contain the file name that is attached */ /* to the message. */ tBYTE user_area[6]; /* User-defined storage. Doors should ignore */ /* this field, and reader authors should feel */ /* free to utilize this field for their own */ /* internal use, if necessary. */ tBYTE network_type; /* Indicates the network type. This field must */ /* hold the same value as the NETWORK_TYPE */ /* field in INF_AREA_INFO, allowing doors and */ /* readers to properly handle the message. */ /* (Values duplicated as UPL_NET_xxx, above.) */ /* For FidoNet NetMail and Internet E-mail, it */ /* also indicates which fields should be used */ /* for addressing and status information (as */ /* indicated in comments above and below). */ tBYTE net_dest[100]; /* Network destination address (non-FidoNet) */ /* Internet E-mail messages should use this */ /* field to store the destination address. */ } UPL_REC; /*---------------------------------------------------------------------------*/ /* ** Name of file: *.REQ ** ** Description: The *.REQ file is simply a list of filenames the user ** wants to request from the host BBS. Wildcard characters ** ("*" and "?" under MS-DOS) are allowed, but are not ** guaranteed to produce accurate results on all door ** implementations. ** ** NOTE: Current Blue Wave doors do not accept wildcard ** characters in filenames, and will consider any ** filenames which contain them as being invalid. ** Additionally, if there are more than 10 entries in ** the *.REQ file, current Blue Wave doors will read ** the first 10 and discard the rest. These are ** limitations of the Blue Wave doors, not of the ** Blue Wave format itself. ** ** File format: REQ_REC { repeated for as many files as } ** REQ_REC { requested from the host BBS } ** ... */ typedef struct /* REQ_REC */ { tBYTE filename[13]; /* Name of file to request */ } REQ_REC; /*---------------------------------------------------------------------------*/ /* ** Name of file: *.PDQ ** ** Description: The *.PDQ file contains the information used for the ** offline configuration feature of the mail door. After the ** header is a series of records which indicate the message ** areas to enable for scanning the next time a mail packet ** is requested. ** ** NOTE: Readers should generate a *.PDQ file *ONLY* if ** the mail packet format is version 2 or earlier; ** otherwise, a *.OLC file should be generated. ** Doors should process *.PDQ files *ONLY* in cases ** where a *.OLC file is not present. ** ** If the AREA_CHANGES flag in PDQ_HEADER.FLAGS is ** set, the door should process the offline ** configuration as well as changes to the list of ** areas the user wants to download. In the Blue ** Wave door, this is done by first turning OFF all ** message areas that were active, then turning ON ** the ones specified in the *.PDQ file. This seems ** to be the simplest, most straight-forward method ** of accomplishing this task, though other, more ** complex schemes could easily have been devised. ** ** File format: PDQ_HEADER { only included one time! ** PDQ_REC { repeated for as many message areas } ** PDQ_REC { as the user wishes to enable } ** ... */ /* Bit-masks for PDQ_HEADER.FLAGS field */ #define PDQ_HOTKEYS 0x0001 /* Toggle "hotkeys" in prompts */ #define PDQ_XPERT 0x0002 /* Toggle expert mode (menu displays) */ #define PDQ_AREA_CHANGES 0x0004 /* Change active message areas */ #define PDQ_GRAPHICS 0x0008 /* Toggle IBM 8-bit ASCII characters */ #define PDQ_NOT_MY_MAIL 0x0010 /* Toggle bundling mail from user */ typedef struct /* PDQ_HEADER */ { tBYTE keywords[10][21]; /* User's entire set of door keywords */ tBYTE filters[10][21]; /* User's entire set of door filters */ tBYTE macros[3][78]; /* User's door bundling command macros */ tBYTE password[21]; /* Password */ tBYTE passtype; /* Password type */ /* 0=none 1=door 2=reader 3=both */ tWORD flags; /* Bit-mapped flags */ } PDQ_HEADER; typedef struct /* PDQ_REC */ { tBYTE echotag[21]; /* Echo tag of message area to activate */ /* With Telegard systems, this should */ /* be the name of the *.BRD file, rather */ /* than the actual echo tag. */ } PDQ_REC; /*---------------------------------------------------------------------------*/ /* ** Name of file: *.OLC ** ** Description: The *.OLC file contains the information used for the ** offline configuration feature of the mail door. ** ** NOTE: Readers should generate a *.OLC file *ONLY* if ** the mail packet format is version 3 or later; ** otherwise, a *.PDQ file should be generated. ** Doors should process *.OLC files in all cases ** where one is present. ** ** File format: ASCII text (lines terminated with CRLF) ** ** Comments: Refer to the Blue Wave Developer's Kit documentation ** for details on the exact format of the *.OLC file. */ /*---------------------------------------------------------------------------*/ #endif /* __BLUEWAVE_H */ mmail-0.52/mmail/compress.h000644 000765 000024 00000000763 13065425631 016626 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * compress and decompress packets Copyright 1997 John Zero Copyright 1998-2017 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #ifndef COMPRESS_H #define COMPRESS_H #include "mmail.h" pktstatus uncompressFile(resource *, const char *, const char *, bool = false); int compressAddFile(resource *, const char *, const char *, const char *); const char *defExtent(); #endif mmail-0.52/mmail/soup.cc000644 000765 000024 00000063001 13245757414 016120 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * SOUP Copyright 1999-2018 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #include "soup.h" #include "compress.h" // ----------------------------------------------------------------- // The sheader methods // ----------------------------------------------------------------- const char *sheader::compare[] = { "Date:", "From:", "To:", "Reply-To:", "Subject:", "Newsgroups:", "Followup-To:", "References:", "Message-ID:" }; sheader::sheader() { for (int c = 0; c < items; c++) values[c] = 0; has8bit = qpenc = false; } sheader::~sheader() { for (int c = 0; c < items; c++) delete[] values[c]; } // Read and parse header; assumes msg is already at start of text bool sheader::init(FILE *msg) { char buffer[1000], *end; int lastc = -1; do { if (feof(msg)) return false; // Get first 999 bytes of header line if (!fgets(buffer, sizeof buffer, msg)) return false; end = buffer + strlen(buffer) - 1; // If there's more, rewind to last space; remainder // will be handled as a continuation if (*end != '\n') { long skip = -1; while ((*end != ' ') && (end > (buffer + 1))) { end--; skip--; } if (end > (buffer + 1)) { fseek(msg, skip, SEEK_CUR); *end = '\0'; } else // give up on getting the rest while (fgetc(msg) != '\n'); } // If the line isn't blank... if (buffer != end) { if (*end == '\n') *end = '\0'; char *sp = strpbrk(buffer, " \t"); // continuation if ((sp == buffer) && (lastc != -1)) { while ((*sp == ' ') || (*sp == '\t')) sp++; char *newval = new char[strlen(values[lastc]) + strlen(sp) + 2]; sprintf(newval, "%s %s", values[lastc], sp); delete[] values[lastc]; values[lastc] = newval; } else // new header if (sp) { *sp = '\0'; int c; for (c = 0; c < items; c++) if (!values[c] && !strcasecmp(buffer, compare[c])) { do sp++; while ((*sp == ' ') || (*sp == '\t')); values[c] = strdupplus(sp); lastc = c; break; } if (c == items) { lastc = -1; *sp = ' '; if (!strcasecmp(buffer, "content-transfer-encoding: quoted-printable")) qpenc = true; } } } } while (buffer != end); // End of header == blank line // If the last msgid is truncated, drop it if (values[refs]) { end = strrchr(values[refs], '<'); if (!end) { // no Message-ID's! delete values[refs]; values[refs] = 0; } else if (!strchr(end, '>')) { if (end[-1] == ' ') end--; *end = '\0'; } } return true; } // Set header values from strings bool sheader::init(const char *fromA, const char *toName, const char *toAddr, const char *subjectA, const char *newsA, const char *refsA, long length) { values[from] = strdupplus(fromA); values[subject] = strdupplus(subjectA); values[newsgrps] = strdupplus(newsA); values[refs] = strdupplus(refsA); if ((toName && toAddr) && strcmp(toName, "All")) { if (*toName && strcmp(toName, toAddr)) { values[to] = new char[strlen(toName) + strlen(toAddr) + 6]; sprintf(values[to], quoteIt(toName) ? "\"%s\" <%s>" : "%s <%s>", toName, toAddr); } else values[to] = strdupplus(toAddr); } char dateA[40]; time_t t; time(&t); strftime(dateA, sizeof dateA, "%a, %d %b %Y %X GMT", gmtime(&t)); values[date] = strdupplus(dateA); msglen = length; return true; } // Write out the header in 'b' or 'B' form void sheader::output(FILE *msg, const char *cset, bool useQPHead, bool useQPBody) { static const char *MIMEhead = "MIME-Version: 1.0\nContent-Type: text/plain; " "charset=%.14s\nContent-Transfer-Encoding: %s\n"; static const char *eightbit = "8bit", *QP = "quoted-printable"; for (int c = 0; c <= refs; c++) if (values[c] && values[c][0]) { fprintf(msg, "%s ", compare[c]); if (((c == from) || (c == to) || (c == subject)) && useQPHead) headenc((unsigned char *) values[c], cset, msg); else fprintf(msg, "%s", values[c]); fprintf(msg, "\n"); } if (has8bit) fprintf(msg, MIMEhead, cset, useQPBody ? QP : eightbit); fprintf(msg, "User-Agent: " MM_NAME "/" MM_VERNUM " (SOUP; %s)\n\n", sysname()); } const char *sheader::From() { return values[from] ? values[from] : ""; } const char *sheader::Subject() { return values[subject] ? values[subject] : ""; } const char *sheader::Date() { return values[date] ? values[date] : ""; } const char *sheader::ReplyTo() { return values[reply]; } const char *sheader::To() { return values[to]; } const char *sheader::Newsgrps() { return values[newsgrps]; } const char *sheader::Follow() { return values[follow]; } const char *sheader::Msgid() { return values[msgid]; } const char *sheader::Refs() { return values[refs]; } // ----------------------------------------------------------------- // The SOUP methods // ----------------------------------------------------------------- soup::soup(mmail *mmA) : pktbase(mmA) { strncpy(packetBaseName, findBaseName(mm->resourceObject->get(PacketName)), 8); packetBaseName[8] = '\0'; hasOffConfig = false; // :-( For now, at least. readAreas(); buildIndices(); const char x[1][13] = {"info"}; listBulletins(x, 1, 0); hasPers = false; } soup::~soup() { while (maxConf--) { delete[] body[maxConf]; delete[] areas[maxConf]->name; delete areas[maxConf]; } delete[] areas; } bool soup::msgopen(int area) { static int oldArea; if (!infile) oldArea = -1; // Reset (new packet) if (area != oldArea) { if (oldArea != -1) fclose(infile); oldArea = area; char tmp[13]; const char *fname = areas[area]->msgfile; if (!(*fname)) return false; sprintf(tmp, "%.8s.MSG", fname); infile = mm->workList->ftryopen(tmp); if (!infile) fatalError("Could not open .MSG file"); } return true; } area_header *soup::getNextArea() { int cMsgNum = areas[ID]->nummsgs; area_header *tmp = new area_header(mm, ID + 1, areas[ID]->numA, areas[ID]->msgfile, areas[ID]->name, "SOUP", areas[ID]->attr | (cMsgNum ? ACTIVE : 0), cMsgNum, 0, 99, 511); ID++; return tmp; } int soup::getNoOfLetters() { return areas[currentArea]->nummsgs; } // Check for valid "From "-line message separator bool soup::parseFrom(const char *s) { // Based on the VALID macro from the IMAP toolkit // by Mark Crispin // Copyright 1989-2000 University of Washington // http://www.washington.edu/imap/ // Modified by William McBrine int ti = 0; // Time Index (location of date string) const char *x = s + strlen(s); while (('\n' == x[-1]) || ('\r' == x[-1])) x--; if ((x - s) > 40) { int c; for (c = -1; x[c] != ' '; c--); if (!strncmp(x + c - 12, " remote from", 12)) x += c - 12; } if ((x - s) > 26) { if (' ' == x[-5]) { // Year is last field? if (':' == x[-8]) ti = -5; else if (' ' == x[-9]) ti = -9; else if ((' ' == x[-11]) && (('+' == x[-10]) || ('-' == x[-10]))) ti = -11; } else if (' ' == x[-4]) { // Or three-letter time zone? if (' ' == x[-9]) ti = -9; } else if (' ' == x[-6]) { // Or numeric time zone? if ((' ' == x[-11]) && (('+' == x[-5]) || ('-' == x[-5]))) ti = -11; } if (ti && !((':' == x[ti - 3]) && (' ' == x[ti -= ((':' == x[ti - 6]) ? 9 : 6)]) && (' ' == x[ti - 3]) && (' ' == x[ti - 7]) && (' ' == x[ti - 11]))) ti = 0; } return (ti != 0); } // Build indices from *.MSG -- does not handle type 'M', nor *.IDX void soup::buildIndices() { int x, cMsgNum; numMsgs = 0; body = new bodytype *[maxConf]; ndx_fake base, *oldndx, *tmpndx; for (x = 0; x < maxConf; x++) { body[x] = 0; cMsgNum = 0; if (msgopen(x)) { tmpndx = &base; switch (areas[x]->mode) { case 'B': case 'b': case 'u': case 'n': // bogus identifer used by GNUS, same as u long offset, counter; while (!feof(infile)) { if (toupper(areas[x]->mode) == 'B') { unsigned char offsetA[4]; if (fread(offsetA, 1, 4, infile) == 4) offset = getblong(offsetA); else offset = -1; } else { char buffer[128]; if (myfgets(buffer, sizeof buffer, infile)) sscanf(buffer, "#! rnews %ld", &offset); else offset = -1; } if (offset != -1) { counter = ftell(infile); fseek(infile, offset, SEEK_CUR); tmpndx->next = new ndx_fake; tmpndx = tmpndx->next; tmpndx->pointer = counter; tmpndx->length = offset; numMsgs++; cMsgNum++; } } break; case 'm': char buffer[128]; long c, lastc = -1; while (!feof(infile)) { c = ftell(infile); if (myfgets(buffer, sizeof buffer, infile)) if (!strncmp(buffer, "From ", 5)) if (parseFrom(buffer)) { if (lastc != -1) { tmpndx->next = new ndx_fake; tmpndx = tmpndx->next; tmpndx->pointer = lastc; tmpndx->length = c - lastc; numMsgs++; cMsgNum++; } lastc = c; } } if (lastc != -1) { tmpndx->next = new ndx_fake; tmpndx = tmpndx->next; tmpndx->pointer = lastc; tmpndx->length = ftell(infile) - lastc; numMsgs++; cMsgNum++; } } } areas[x]->nummsgs = cMsgNum; if (cMsgNum) { body[x] = new bodytype[cMsgNum]; tmpndx = base.next; for (int y = 0; y < cMsgNum; y++) { body[x][y].pointer = tmpndx->pointer; body[x][y].msgLength = tmpndx->length; oldndx = tmpndx; tmpndx = tmpndx->next; delete oldndx; } } } } letter_header *soup::getNextLetter() { sheader sHead; long len = body[currentArea][currentLetter].msgLength; // Read in header: if (msgopen(currentArea)) { fseek(infile, body[currentArea][currentLetter].pointer, SEEK_SET); sHead.init(infile); } // Get address from "From:" line: net_address na; na = fromAddr(sHead.From()); // Get name from "From:" line: const char *fr = fromName(sHead.From()); // Join Message-ID and References lines: const char *refs = sHead.Refs(), *msgid = sHead.Msgid(); char *fullref = 0; if (refs) { if (msgid) { fullref = new char[strlen(msgid) + strlen(refs) + 2]; sprintf(fullref, "%s %s", refs, msgid); msgid = fullref; } else msgid = refs; } letter_header *tmp = new letter_header(mm, sHead.Subject(), sHead.To() ? sHead.To() : "All", *fr ? fr : (const char *) na, sHead.Date(), msgid, 0, currentLetter, currentLetter + 1, currentArea, false, len, this, na, true, sHead.Newsgrps(), sHead.Follow(), sHead.ReplyTo(), sHead.qpenc); currentLetter++; delete[] fullref; return tmp; } // returns the body of the requested letter letter_body *soup::getBody(letter_header &mhead) { int AreaID, LetterID; long length, offset; letter_body head(0, 0), *currblk = &head; AreaID = mhead.getAreaID() - 1; LetterID = mhead.getLetterID(); delete bodyString; length = limitmem(body[AreaID][LetterID].msgLength); offset = body[AreaID][LetterID].pointer; msgopen(AreaID); bool firstblk = true; if (!length) head.next = new letter_body(strdupplus("\n"), 1); else while (length) { unsigned char *p, *src, *begin; long count, blklen, oldoffs; fseek(infile, offset, SEEK_SET); oldoffs = offset; if (firstblk) { int lastkar = -1, kar = -1; do { if ('\r' != kar) lastkar = kar; kar = fgetc(infile); } while (!(('\n' == kar) && ('\n' == lastkar))); count = ftell(infile) - offset; fseek(infile, offset, SEEK_SET); src = new unsigned char[count + 1]; p = src; getblk(0, offset, count, p, begin); *p = '\0'; currblk->next = new letter_body((char *) src, count, 0, true); currblk = currblk->next; oldoffs = offset = ftell(infile); length -= count; } blklen = (length > MAXBLOCK) ? MAXBLOCK : length; src = begin = p = new unsigned char[blklen + 1]; getblk(0, offset, blklen, p, begin); if (length > MAXBLOCK) { if (begin > src) count = begin - src; else { offset = ftell(infile); count = p - src; } length -= (offset - oldoffs); } else { // Strip blank lines do p--; while ((*p == ' ') || (*p == '\n')); length = 0; count = p - src + 1; } src[count] = '\0'; if (mhead.isQP()) { p = qpdecode(src); *p = '\0'; count = p - src; } p = src; currblk->next = new letter_body((char *) src, count, p - src); currblk = currblk->next; firstblk = false; } bodyString = head.next; head.next = 0; // Prevent deletion of chain return bodyString; } // Area and packet init void soup::readAreas() { file_list *wl = mm->workList; // Info not available in SOUP: const char *defName = mm->resourceObject->get(UserName); const char *defAddr = mm->resourceObject->get(InetAddr); if (defAddr) { if (defName && *defName && strcmp(defName, defAddr)) { LoginName = new char[strlen(defName) + strlen(defAddr) + 6]; sprintf(LoginName, quoteIt(defName) ? "\"%s\" <%s>" : "%s <%s>", defName, defAddr); } else LoginName = strdupplus(defAddr); } // AREAS: maxConf = 1; AREAs base, *tmparea; // Email area is always present: base.next = new AREAs; tmparea = base.next; memset(tmparea, 0, sizeof(AREAs)); tmparea->name = strdupplus("Email"); tmparea->attr = ACTIVE | LATINCHAR | INTERNET | NETMAIL | PRIVATE; strcpy(tmparea->numA, "0"); FILE *afile = wl->ftryopen("areas"); if (afile) { char buffer[128], *msgfile, *name, *rawattr; do if (myfgets(buffer, sizeof buffer, afile)) { msgfile = strtok(buffer, "\t"); name = strtok(0, "\t"); rawattr = strtok(0, "\t"); // If the Email area has not been set yet, the // first email area in the packet (in any) becomes it. if (!base.next->msgfile[0] && ((rawattr[2] == 'm') || ((rawattr[2] != 'n') && !((*rawattr == 'u') || (*rawattr == 'B'))))) { strncpy(base.next->msgfile, msgfile, 9); delete[] base.next->name; base.next->name = strdupplus(name); base.next->mode = *rawattr; } else { tmparea->next = new AREAs; tmparea = tmparea->next; memset(tmparea, 0, sizeof(AREAs)); strncpy(tmparea->msgfile, msgfile, 9); tmparea->name = strdupplus(name); tmparea->mode = *rawattr; tmparea->attr = ACTIVE | LATINCHAR | INTERNET | (((rawattr[2] == 'n') || ((rawattr[2] != 'm') && ((*rawattr == 'B') || (*rawattr == 'u')))) ? PUBLIC : (NETMAIL | PRIVATE)); sprintf(tmparea->numA, "%5d", maxConf++); } } while (!feof(afile)); fclose(afile); areas = new AREAs *[maxConf]; tmparea = base.next; for (int i = 0; i < maxConf; i++) { areas[i] = tmparea; tmparea = tmparea->next; } } else areas = 0; } const char *soup::getTear(int) { return 0; } bool soup::isLatin() { return true; } // ----------------------------------------------------------------- // The SOUP reply methods // ----------------------------------------------------------------- souprep::upl_soup::upl_soup(const char *name) : pktreply::upl_base(name) { } souprep::souprep(mmail *mmA, specific_driver *baseClassA) : pktreply(mmA, baseClassA) { } souprep::~souprep() { } // convert one reply to MultiMail's internal format bool souprep::getRep1(FILE *rep, upl_soup *l) { FILE *orgfile, *destfile; char buffer[128], *msgfile, *mnflag; //, *rawattr; long count = 0; bool has8bit = false; if (!myfgets(buffer, sizeof buffer, rep)) return false; msgfile = strtok(buffer, "\t"); mnflag = strtok(0, "\t"); //rawattr = strtok(0, "\t"); l->privat = (*mnflag == 'm'); orgfile = upWorkList->ftryopen(msgfile); if (orgfile) { fseek(orgfile, 4, SEEK_SET); if (!l->sHead.init(orgfile)) return false; const char *to = l->sHead.To(); if (to) l->na = fromAddr(to); destfile = fopen(l->fname, "wt"); if (destfile) { if (l->sHead.qpenc) { count = qpdecode(orgfile, destfile); has8bit = true; } else { int c; while ((c = fgetc(orgfile)) != EOF) { fputc(c, destfile); if (c & 0x80) has8bit = true; count++; } } fclose(destfile); } fclose(orgfile); } l->msglen = l->sHead.msglen = count; l->sHead.has8bit = has8bit; l->refnum = 0; l->origArea = l->privat ? 1 : -1; remove(msgfile); return true; } // convert all replies void souprep::getReplies(FILE *repFile) { noOfLetters = 0; upl_soup baseUplList, *currUplList = &baseUplList; while (!feof(repFile)) { currUplList->nextRecord = new upl_soup; currUplList = (upl_soup *) currUplList->nextRecord; if (!getRep1(repFile, currUplList)) { delete currUplList; break; } noOfLetters++; } uplListHead = baseUplList.nextRecord; } area_header *souprep::getNextArea() { return new area_header(mm, 0, "REPLY", "REPLIES", "Letters written by you", "SOUP replies", (COLLECTION | REPLYAREA | ACTIVE | PUBLIC | PRIVATE), noOfLetters, 0, 99, 511); } letter_header *souprep::getNextLetter() { upl_soup *current = (upl_soup *) uplListCurrent; const char *to = current->sHead.To(); const char *ng = current->sHead.Newsgrps(); int cn = current->origArea; if (ng && (cn == -1)) { for (int c = 2; c < mm->areaList->noOfAreas(); c++) if (strstr(ng, mm->areaList->getDescription(c))) { cn = c; break; } if (cn == -1) cn = 2; current->origArea = cn; } letter_header *newLetter = new letter_header(mm, current->sHead.Subject(), to ? fromName(to) : "All", current->sHead.From(), current->sHead.Date(), current->sHead.Refs(), current->refnum, currentLetter, currentLetter, cn, current->privat, current->msglen, this, current->na, true, ng); currentLetter++; uplListCurrent = uplListCurrent->nextRecord; return newLetter; } void souprep::enterLetter(letter_header &newLetter, const char *newLetterFileName, long length) { upl_soup *newList = new upl_soup(newLetterFileName); newList->origArea = mm->areaList->getAreaNo(); newList->privat = newLetter.getPrivate(); newList->refnum = newLetter.getReplyTo(); newList->na = newLetter.getNetAddr(); newList->sHead.init(newLetter.getFrom(), newLetter.getTo(), (const char *) newLetter.getNetAddr(), newLetter.getSubject(), newLetter.getNewsgrps(), newLetter.getMsgID(), length); newList->msglen = length; // Check for 8-bit characters -- there should be a better way to do this: FILE *tmp; bool has8bit = false; tmp = fopen(newLetterFileName, "rt"); if (tmp) { int c; while ((c = fgetc(tmp)) != EOF) if (c & 0x80) has8bit = true; fclose(tmp); } newList->sHead.has8bit = has8bit; addUpl(newList); } // write out one reply in SOUP format void souprep::addRep1(FILE *rep, upl_base *node, int recnum) { FILE *orgfile, *destfile; upl_soup *l = (upl_soup *) node; char dest[13]; sprintf(dest, "R%07d.MSG", recnum); fprintf(rep, "R%07d\t%sn\n", recnum, l->privat ? "mail\tb" : "news\tB"); orgfile = fopen(l->fname, "rt"); if (orgfile) { destfile = fopen(dest, "wb"); if (destfile) { unsigned char outlen[4]; putblong(outlen, 0L); fwrite(outlen, 4, 1, destfile); bool useQPbody = l->privat ? mm->resourceObject->getInt(UseQPMail) : mm->resourceObject->getInt(UseQPNews); bool useQPhead = l->privat ? mm->resourceObject->getInt(UseQPMailHead) : mm->resourceObject->getInt(UseQPNewsHead); l->sHead.output(destfile, mm->resourceObject->get(outCharset), useQPhead, useQPbody); if (useQPbody && l->sHead.has8bit) qpencode(orgfile, destfile); else { int c, count = 0, lastsp = 0; while ((c = fgetc(orgfile)) != EOF) { count++; if ((count > 80) && lastsp) { fseek(orgfile, lastsp - count, SEEK_CUR); fseek(destfile, lastsp - count, SEEK_CUR); c = '\n'; } if ('\n' == c) count = lastsp = 0; else if (' ' == c) lastsp = count; fputc(c, destfile); } } putblong(outlen, ftell(destfile) - 4L); fseek(destfile, 0, SEEK_SET); fwrite(outlen, 4, 1, destfile); fseek(destfile, 0, SEEK_END); fclose(destfile); } fclose(orgfile); } } void souprep::addHeader(FILE *) { } // set names for reply packet files void souprep::repFileName() { const char *basename = baseClass->getBaseName(); sprintf(replyPacketName, "%s.rep", basename); sprintf(replyInnerName, "REPLIES"); } // list files to be archived when creating reply packet const char *souprep::repTemplate(bool) { return "REPLIES *.MSG"; } // re-read an offline config file -- not implemented yet bool souprep::getOffConfig() { return false; } bool souprep::makeOffConfig() { return false; } mmail-0.52/mmail/pktbase.cc000644 000765 000024 00000041512 13065430265 016556 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * Packet base class -- common methods Copyright 1999-2017 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #include "compress.h" #include "pktbase.h" /* Not all these methods are used in all derived classes. The formats I think of as "QWK-like" -- QWK, OMEN and OPX -- have the most in common: message headers and bodies concatenated in a single file, a separate file for the area list, and replies matched by conference number. */ // ----------------------------------------------------------------- // The packet methods // ----------------------------------------------------------------- pktbase::pktbase(mmail *mmA) { mm = mmA; ID = 0; bodyString = 0; bulletins = 0; infile = 0; LoginName = AliasName = BBSName = SysOpName = BBSProg = DoorProg = 0; hello = goodbye = 0; } pktbase::~pktbase() { if (infile) fclose(infile); delete[] body; delete bodyString; delete[] bulletins; delete[] goodbye; delete[] hello; delete[] DoorProg; delete[] BBSProg; delete[] SysOpName; delete[] BBSName; delete[] AliasName; delete[] LoginName; } // Clean up for the QWK-like packets void pktbase::cleanup() { if (!hasPers) { areas--; maxConf++; } while (maxConf--) { delete[] body[maxConf]; delete[] areas[maxConf].name; } delete[] areas; } // Final index build for QWK-like packets void pktbase::initBody(ndx_fake *tmpndx, int personal) { int x, cMsgNum; hasPers = !(!personal); if (hasPers) { body[0] = new bodytype[personal]; areas[0].nummsgs = personal; personal = 0; } else { areas++; maxConf--; } for (x = hasPers; x < maxConf; x++) { cMsgNum = areas[x].nummsgs; if (cMsgNum) { body[x] = new bodytype[cMsgNum]; areas[x].nummsgs = 0; } } for (x = 0; x < numMsgs; x++) { int current = tmpndx->confnum - !hasPers; body[current][areas[current].nummsgs].pointer = tmpndx->pointer; body[current][areas[current].nummsgs++].msgLength = tmpndx->length; if (tmpndx->pers) { body[0][personal].pointer = tmpndx->pointer; body[0][personal++].msgLength = tmpndx->length; } ndx_fake *oldndx = tmpndx; tmpndx = tmpndx->next; delete oldndx; } } // Match the conference number to the internal number int pktbase::getXNum(int area) { static int lastres = 0; if ((lastres >= maxConf) || (areas[lastres].num > area)) lastres = 0; for (int x = 0; x < maxConf; x++) if (areas[lastres].num == area) break; else { lastres++; lastres %= maxConf; } return (areas[lastres].num == area) ? lastres : -1; } // Find the original number of a message in a collection area int pktbase::getYNum(int area, unsigned long rawpos) { static int lastarea = -1; static int lastres = 0; if (lastarea != area) { lastarea = area; lastres = 0; } if (-1 == area) return -1; int x, limit = areas[area].nummsgs; for (x = 0; x < limit; x++) if ((unsigned long) body[area][lastres].pointer == rawpos) break; else { lastres++; lastres %= limit; } return (x < limit) ? lastres : -1; } int pktbase::getNoOfLetters() { return areas[currentArea].nummsgs; } bool pktbase::hasPersArea() { return hasPers; } bool pktbase::hasPersonal() { return false; } bool pktbase::isLatin() { return false; } const char *pktbase::oldFlagsName() { return 0; } bool pktbase::readOldFlags() { return false; } bool pktbase::saveOldFlags() { return false; } int pktbase::getNoOfAreas() { return maxConf; } void pktbase::selectArea(int area) { currentArea = area; resetLetters(); } void pktbase::resetLetters() { currentLetter = 0; } // returns the body of the requested letter letter_body *pktbase::getBody(letter_header &mhead) { int AreaID, LetterID; long length, offset; letter_body head(0, 0), *currblk = &head; AreaID = mhead.getAreaID() - 1; LetterID = mhead.getLetterID(); delete bodyString; length = limitmem(body[AreaID][LetterID].msgLength); offset = body[AreaID][LetterID].pointer; bool firstblk = true; if (!length) head.next = new letter_body(strdupplus("\n"), 1); else while (length) { unsigned char *p, *src, *begin; long count, blklen, oldoffs; fseek(infile, offset, SEEK_SET); oldoffs = offset; if (firstblk) prefirstblk(); blklen = (length > MAXBLOCK) ? MAXBLOCK : length; src = begin = p = new unsigned char[blklen + 1]; getblk(AreaID, offset, blklen, p, begin); if (length > MAXBLOCK) { if (begin > src) count = begin - src; else { offset = ftell(infile); count = p - src; } length -= (offset - oldoffs); } else { // Strip blank lines do p--; while ((*p == ' ') || (*p == '\n')); length = 0; count = p - src + 1; } src[count] = '\0'; p = src; if (firstblk) postfirstblk(p, mhead); currblk->next = new letter_body((char *) src, count, p - src); currblk = currblk->next; firstblk = false; } bodyString = head.next; head.next = 0; // Prevent deletion of chain endproc(mhead); return bodyString; } void pktbase::getblk(int, long &offset, long blklen, unsigned char *&p, unsigned char *&begin) { for (long count = 0; count < blklen; count++) { int kar = fgetc(infile); if (!kar) kar = ' '; if (kar != '\r') *p++ = kar; if (kar == '\n') { begin = p; offset = ftell(infile); } } } // The new getBody() framework uses 4 subprocedures, only one of which // must be defined in all derived classes. Here are dummy procedures for // the others. void pktbase::prefirstblk() { } void pktbase::postfirstblk(unsigned char *&, letter_header &) { } void pktbase::endproc(letter_header &) { } // Check the character set kludge lines void pktbase::checkLatin(letter_header &mhead) { const char *s = strstr(bodyString->getText(), "\001CHRS: L"); if (!s) s = strstr(bodyString->getText(), "\001CHARSET: L"); if (s) mhead.setLatin(true); } // Find a hidden line and return its contents const char *pktbase::getHidden(const char *pattern, char *&end) { char *s = strstr(bodyString->getText(), pattern); if (s) { s += strlen(pattern); end = strchr(s, '\n'); if (end) *end = '\0'; } return s; } // Check FMPT, MSGID and character set void pktbase::fidocheck(letter_header &mhead) { const char *s; char *end; net_address &na = mhead.getNetAddr(); // Add point to netmail address, if possible/necessary: if (na.isSet) if (!na.point) { s = strstr(bodyString->getText(), "\001FMPT"); if (s) sscanf(s, "\001FMPT%u\n", &na.point); } // Get MSGID: if (!mhead.getMsgID()) { s = getHidden("\001MSGID: ", end); if (s) { mhead.changeMsgID(s); if (end) *end = '\n'; } } // Change to Latin character set, if necessary: checkLatin(mhead); } // Build a list of bulletin files void pktbase::listBulletins(const char x[][13], int d, int generic) { file_list *wl = mm->workList; int filecount = 0; bulletins = new file_header *[wl->getNoOfFiles() + 1]; for (int c = 0; c < d; c++) if (x[c][0]) { if (!hello && (!strncasecmp("hello", x[c], 5) || !strncasecmp("welcome", x[c], 7))) hello = strdupplus(x[c]); else if (!goodbye && !strncasecmp("goodbye", x[c], 7)) goodbye = strdupplus(x[c]); else wl->addItem(bulletins, x[c], filecount); } if (generic) { wl->addItem(bulletins, "blt", filecount); if (generic == 2) wl->addItem(bulletins, ".txt", filecount); } wl->addItem(bulletins, "newfiles.", filecount); wl->addItem(bulletins, "nfile", filecount); if (filecount) bulletins[filecount] = 0; else { delete[] bulletins; bulletins = 0; } } const char *pktbase::getLoginName() { return LoginName; } const char *pktbase::getAliasName() { return AliasName; } const char *pktbase::getBBSName() { return BBSName; } const char *pktbase::getSysOpName() { return SysOpName; } const char *pktbase::getBBSProg() { return BBSProg; } const char *pktbase::getDoorProg() { return DoorProg; } file_header *pktbase::getHello() { return (hello && *hello) ? mm->workList->existsF(hello) : 0; } file_header *pktbase::getGoodbye() { return (goodbye && *goodbye) ? mm->workList->existsF(goodbye) : 0; } file_header **pktbase::getBulletins() { return bulletins; } const char *pktbase::getTear(int) { static char tear[80]; sprintf(tear, "--- " MM_NAME "/%.58s v" MM_VERNUM, sysname()); return tear; } const char *pktbase::getBaseName() { return packetBaseName; } char *pktbase::nextLine() { static char line[128]; char *end = myfgets(line, sizeof line, infile); if (end) { while ((*end == '\n') || (*end == '\r')) *end-- = '\0'; } else line[0] = '\0'; return line; } // ----------------------------------------------------------------- // The reply methods // ----------------------------------------------------------------- pktreply::upl_base::upl_base(const char *name) { if (name) fname = strdupplus(name); else fname = mytmpnam(); nextRecord = 0; msglen = 0; } pktreply::upl_base::~upl_base() { delete[] fname; } pktreply::pktreply(mmail *mmA, specific_driver *baseClassA) { mm = mmA; baseClass = (pktbase *) baseClassA; replyText = 0; uplListHead = 0; replyExists = false; } pktreply::~pktreply() { if (replyExists) { upl_base *next, *curr = uplListHead; while (noOfLetters--) { remove(curr->fname); next = curr->nextRecord; delete curr; curr = next; } delete replyText; } } bool pktreply::checkForReplies() { repFileName(); mychdir(mm->resourceObject->get(ReplyDir)); mystat st(replyPacketName); replyExists = st.writeable(); return replyExists; } void pktreply::init() { if (replyExists) { uncompress(); readRep(); currentLetter = 1; } else noOfLetters = currentLetter = 0; } void pktreply::uncompress() { resource *ro = mm->resourceObject; char *tmppath = fullpath(ro->get(ReplyDir), replyPacketName); uncompressFile(ro, tmppath, ro->get(UpWorkDir)); delete[] tmppath; } int pktreply::getNoOfAreas() { return 1; } void pktreply::selectArea(int ID) { if (ID == 0) resetLetters(); } int pktreply::getNoOfLetters() { return noOfLetters; } void pktreply::resetLetters() { currentLetter = 1; uplListCurrent = uplListHead; } letter_body *pktreply::getBody(letter_header &mhead) { FILE *replyFile; upl_base *actUplList; long length, offset = 0; letter_body head(0,0), *currblk = &head; int ID = mhead.getLetterID(); delete replyText; actUplList = uplListHead; for (int c = 1; c < ID; c++) actUplList = actUplList->nextRecord; length = limitmem(actUplList->msglen); replyFile = fopen(actUplList->fname, "rt"); while (length) { if (replyFile) { unsigned char *p, *src, *begin; long blklen, count, oldoffs; fseek(replyFile, offset, SEEK_SET); oldoffs = offset; blklen = (length > MAXBLOCK) ? MAXBLOCK : length; src = begin = p = new unsigned char[blklen + 1]; for (count = 0; count < blklen; count++) { int kar = fgetc(replyFile); if (!kar) kar = ' '; *p++ = kar; if (kar == '\n') { begin = p; offset = ftell(replyFile); } } if (length > MAXBLOCK) { if (begin > src) p = begin; else offset = ftell(replyFile); length -= (offset - oldoffs); } else length = 0; *p = '\0'; count = p - src; currblk->next = new letter_body((char *) src, count); currblk = currblk->next; } else { head.next = new letter_body(strdupplus("\n"), 1); length = 0; } } if (replyFile) fclose(replyFile); replyText = head.next; head.next = 0; return replyText; } bool pktreply::hasPersArea() { return false; } bool pktreply::hasPersonal() { return false; } bool pktreply::isLatin() { return false; } const char *pktreply::oldFlagsName() { return 0; } bool pktreply::readOldFlags() { return false; } bool pktreply::saveOldFlags() { return false; } const char *pktreply::getLoginName() { return 0; } const char *pktreply::getAliasName() { return 0; } const char *pktreply::getBBSName() { return 0; } const char *pktreply::getSysOpName() { return 0; } const char *pktreply::getBBSProg() { return 0; } const char *pktreply::getDoorProg() { return 0; } file_header *pktreply::getHello() { return 0; } file_header *pktreply::getGoodbye() { return 0; } file_header *pktreply::getFileList() { return 0; } file_header **pktreply::getBulletins() { return 0; } const char *pktreply::getTear(int) { return 0; } void pktreply::readRep() { upWorkList = new file_list(mm->resourceObject->get(UpWorkDir)); FILE *repFile = upWorkList->ftryopen(replyInnerName); if (repFile) { getReplies(repFile); fclose(repFile); remove(upWorkList->exists(replyInnerName)); } else fatalError("Error opening reply packet"); delete upWorkList; } void pktreply::addUpl(upl_base *newList) { if (!noOfLetters) uplListHead = newList; else { upl_base *workList = uplListHead; for (int c = 1; c < noOfLetters; c++) //go to last elem workList = workList->nextRecord; workList->nextRecord = newList; } noOfLetters++; replyExists = true; } void pktreply::killLetter(int letterNo) { upl_base *actUplList, *tmpUplList; if (!noOfLetters || (letterNo < 1) || (letterNo > noOfLetters)) fatalError("Internal error in pktreply::killLetter"); if (letterNo == 1) { tmpUplList = uplListHead; uplListHead = uplListHead->nextRecord; } else { actUplList = uplListHead; for (int c = 1; c < letterNo - 1; c++) actUplList = actUplList->nextRecord; tmpUplList = actUplList->nextRecord; actUplList->nextRecord = (letterNo == noOfLetters) ? 0 : actUplList->nextRecord->nextRecord; } noOfLetters--; remove(tmpUplList->fname); delete tmpUplList; resetLetters(); } area_header *pktreply::refreshArea() { return getNextArea(); } bool pktreply::makeReply() { if (mychdir(mm->resourceObject->get(UpWorkDir))) fatalError("Could not cd to upworkdir in pktreply::makeReply"); bool offres = mm->areaList->anyChanged(); if (offres) offres = makeOffConfig(); if (!noOfLetters && !offres) { deleteReplies(); return true; } FILE *repFile; repFile = fopen(replyInnerName, "wb"); //!! no check yet addHeader(repFile); upl_base *actUplList = uplListHead; for (int c = 0; c < noOfLetters; c++) { addRep1(repFile, actUplList, c); actUplList = actUplList->nextRecord; } fclose(repFile); // delete old packet deleteReplies(); // pack the files int result = compressAddFile(mm->resourceObject, mm->resourceObject->get(ReplyDir), replyPacketName, repTemplate(offres)); // clean up the work area clearDirectory(mm->resourceObject->get(UpWorkDir)); return !result && checkForReplies(); } void pktreply::deleteReplies() { char *tmppath = fullpath(mm->resourceObject->get(ReplyDir), replyPacketName); remove(tmppath); delete[] tmppath; } mmail-0.52/mmail/resource.h000644 000765 000024 00000004462 13065426326 016624 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * resource class Copyright 1996-1997 Toth Istvan Copyright 1997-2017 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #ifndef RESOURCE_H #define RESOURCE_H enum { UserName, InetAddr, QuoteHead, InetQuote, outCharset, noOfRaw }; enum { homeDir = noOfRaw, mmHomeDir, PacketDir, TempDir, BaseDir, WorkDir, UncompressCommand, PacketName, ReplyDir, CompressCommand, UpWorkDir, editor, SaveDir, AddressFile, TaglineFile, arjUncompressCommand, zipUncompressCommand, lhaUncompressCommand, rarUncompressCommand, tarUncompressCommand, unknownUncompressCommand, arjCompressCommand, zipCompressCommand, lhaCompressCommand, rarCompressCommand, tarCompressCommand, unknownCompressCommand, sigFile, ColorFile, oldPacketName, noOfStrings }; enum { PacketSort = noOfStrings, AreaMode, LetterSort, LetterMode, Charset, UseTaglines, AutoSaveReplies, StripSoftCR, BeepOnPers, UseLynxNav, ReOnReplies, QuoteWrapCols, MaxLines, UseQPMailHead, UseQPNewsHead, UseQPMail, UseQPNews, ExpertMode, IgnoreNDX, Mouse, #ifdef USE_SPAWNO swapOut, #endif UseColors, Transparency, BackFill, ClockMode, noOfResources }; class baseconfig { protected: const char **names, **comments, **intro; int configItemNum; bool parseConfig(const char *); void newConfig(const char *); virtual void processOne(int, const char *) = 0; virtual const char *configLineOut(int) = 0; public: void processOneByName(const char *, const char *); virtual ~baseconfig(); }; class resource : public baseconfig { static const char *rc_names[], *rc_intro[], *rc_comments[]; static const int startUp[], defInt[]; char *resourceData[noOfStrings]; int resourceInt[noOfResources - noOfStrings]; void homeInit(); void mmEachInit(int, const char *); void subPath(int, const char *); void initinit(); void mmHomeInit(); void processOne(int, const char *); const char *configLineOut(int); bool checkPath(const char *, bool); bool verifyPaths(); public: resource(); ~resource(); const char *get(int) const; int getInt(int) const; void set(int, const char *); void set_noalloc(int, char *); void set(int, int); }; #endif mmail-0.52/mmail/resource.cc000644 000765 000024 00000042524 13245266333 016763 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * resource class Copyright 1996-1997 Toth Istvan Copyright 1997-2018 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #include "mmail.h" #include "../interfac/error.h" /* Default filenames. */ #ifdef __MSDOS__ # define DEFEDIT "edit" # define DEFZIP "pkzip -#" # define DEFUNZIP "pkunzip -# -o" # define DEFLHA "lha a /m" # define DEFUNLHA "lha e" #else # ifdef __WIN32__ # define DEFEDIT "start /w notepad" # else # ifdef __OS2__ # define DEFEDIT "tedit" # else # define DEFEDIT "vi" # endif # endif # define DEFZIP "zip -jkq" # define DEFUNZIP "unzip -joLq" # define DEFLHA "lha af" # define DEFUNLHA "lha efi" #endif #define DEFARJ "arj a -e" #define DEFUNARJ "arj e" #define DEFRAR "rar u -ep -inul" #define DEFUNRAR "rar e -cl -o+ -inul" #define DEFTAR "tar zcf" #define DEFUNTAR "tar zxf" #define DEFNONE "xxcompress" #define DEFUNNONE "xxuncompress" #ifdef DOSNAMES # define RCNAME "mmail.rc" # define ADDRBOOK "address.bk" #else # define RCNAME ".mmailrc" # define ADDRBOOK "addressbook" #endif // ================ // baseconfig class // ================ baseconfig::~baseconfig() { } bool baseconfig::parseConfig(const char *configFileName) { FILE *configFile; char buffer[256], *pos, *resName, *resValue; int vermajor = 0, verminor = 0; configFile = fopen(configFileName, "rt"); if (configFile) { while (myfgets(buffer, sizeof buffer, configFile)) { if ((buffer[0] != '#') && (buffer[0] != '\n')) { pos = buffer; //leading spaces while (*pos == ' ' || *pos == '\t') pos++; //skip "bw" -- for backwards compatiblity if (*pos == 'b' && pos[1] == 'w') pos += 2; //resName resName = pos; while (*pos != ':' && *pos != '=' && *pos != ' ' && *pos != '\t' && *pos) pos++; if (*pos) *pos++ = '\0'; //chars between strings while (*pos == ' ' || *pos == '\t' || *pos == ':' || *pos == '=') pos++; //resValue resValue = pos; while (*pos != '\n' && *pos) pos++; *pos = '\0'; if (!strncasecmp("ver", resName, 3)) sscanf(resValue, "%d.%d", &vermajor, &verminor); else processOneByName(resName, resValue); } } fclose(configFile); } // Does the config file need updating? return (vermajor < MM_MAJOR) || ((vermajor == MM_MAJOR) && (verminor < MM_MINOR)); } void baseconfig::newConfig(const char *configname) { FILE *fd; const char **p; printf("Updating %s...\n", configname); fd = fopen(configname, "wt"); if (fd) { for (p = intro; *p; p++) fprintf(fd, "# %s\n", *p); fprintf(fd, "\nVersion: " MM_VERNUM "\n"); for (int x = 0; x < configItemNum; x++) { if (comments[x]) fprintf(fd, "\n# %s\n", comments[x]); fprintf(fd, "%s: %s\n", names[x], configLineOut(x)); } fclose(fd); } else pauseError("Error writing config file"); } void baseconfig::processOneByName(const char *resName, const char *resValue) { int c; for (c = 0; c < configItemNum; c++) if (!strcasecmp(names[c], resName)) { processOne(c, resValue); break; } if (c == configItemNum) printf("Unrecognized keyword: %s\n", resName); } // ============== // resource class // ============== const int startUpLen = 51 #ifdef USE_SPAWNO + 1 #endif ; const char *resource::rc_names[startUpLen] = { "UserName", "InetAddr", "QuoteHead", "InetQuote", "mmHomeDir", "TempDir", "signature", "editor", "PacketDir", "ReplyDir", "SaveDir", "AddressBook", "TaglineFile", "ColorFile", "UseColors", "Transparency", "BackFill", "arjUncompressCommand", "zipUncompressCommand", "lhaUncompressCommand", "rarUncompressCommand", "tarUncompressCommand", "unknownUncompressCommand", "arjCompressCommand", "zipCompressCommand", "lhaCompressCommand", "rarCompressCommand", "tarCompressCommand", "unknownCompressCommand", "PacketSort", "AreaMode", "LetterSort", "LetterMode", "ClockMode", "Charset", "UseTaglines", "AutoSaveReplies", "StripSoftCR", "BeepOnPers", "UseLynxNav", "ReOnReplies", "QuoteWrapCols", "MaxLines", "outCharset", "UseQPMailHead", "UseQPNewsHead", "UseQPMail", "UseQPNews", "ExpertMode", "IgnoreNDX", "Mouse" #ifdef USE_SPAWNO , "swapOut" #endif }; const char *resource::rc_intro[] = { "-----------------------", MM_NAME " configuration", "-----------------------", "", "Any of these keywords may be omitted, in which case the default values", "(shown here) will be used.", "", "If you change either of the base directories, all the subsequent paths", "will be changed, unless they're overridden in the individual settings.", "", "Please see the man page for a more thorough explanation of these options.", 0 }; const char *resource::rc_comments[startUpLen] = { "Your name, as you want it to appear on replies (used mainly in SOUP)", "Your Internet email address (used only in SOUP replies)", "Quote header for replies (non-Internet)", "Quote header for Internet email and Usenet replies", "Base directories (derived from $HOME or $MMAIL)", 0, "Signature (file) that should be appended to each message. (Not used\n" "# unless specified here.)", "Editor for replies = $EDITOR; or if not defined, " DEFEDIT, MM_NAME " will look for packets here", "Reply packets go here", "Saved messages go in this directory, by default", "Full paths to the address book, tagline and color specification files", 0, 0, "Color or monochrome? (Mono mode uses the default colors)", "Make backgrounds transparent? (Only works on some platforms)", "Fill background with checkerboard pattern (ACS_BOARD)?", "Decompression commands (must include an option to junk/discard paths!)", 0, 0, 0, 0, 0, "Compression commands (must include an option to junk/discard paths!)", 0, 0, 0, 0, 0, "Default sort for packet list: by Name or Time (most recent first)", "Default mode for area list: All, Subscribed, or Active", "Default sort for letter list: by Subject, Number, From or To", "Default mode for letter list: All or Unread", "Clock in letter window: Off, Time (of day), or Elapsed (since startup)", "Console character set: CP437 (IBM PC) or Latin-1 (ISO-8859-1)", "Prompt to add taglines to replies?", "Save replies after editing without prompting?", "Strip \"soft carriage returns\" (char 141) from messages?", "Beep when a personal message is opened in the letter window?", "Use Lynx-like navigation (right arrow selects, left backs out)?", "Add \"Re: \" prefix on Subject of replies? (Note that it will be added\n" "# in Internet email and Usenet areas regardless of this setting.)", "Wrap quoted text at this column width (including quote marks)", "Maximum lines per part for reply split (see docs)", "8-bit character set for SOUP packets (see docs)", "Quoted-printable options for outgoing messages (see docs)", 0, 0, 0, "Suppress help messages (use more of the screen for content)", "For QWK only: Generate indexes from MESSAGES.DAT instead of *.NDX", "Allow use of the mouse?" #ifdef USE_SPAWNO , "Attempt to swap MultiMail out of conventional memory when shelling" #endif }; const int resource::startUp[startUpLen] = { UserName, InetAddr, QuoteHead, InetQuote, mmHomeDir, TempDir, sigFile, editor, PacketDir, ReplyDir, SaveDir, AddressFile, TaglineFile, ColorFile, UseColors, Transparency, BackFill, arjUncompressCommand, zipUncompressCommand, lhaUncompressCommand, rarUncompressCommand, tarUncompressCommand, unknownUncompressCommand, arjCompressCommand, zipCompressCommand, lhaCompressCommand, rarCompressCommand, tarCompressCommand, unknownCompressCommand, PacketSort, AreaMode, LetterSort, LetterMode, ClockMode, Charset, UseTaglines, AutoSaveReplies, StripSoftCR, BeepOnPers, UseLynxNav, ReOnReplies, QuoteWrapCols, MaxLines, outCharset, UseQPMailHead, UseQPNewsHead, UseQPMail, UseQPNews, ExpertMode, IgnoreNDX, Mouse #ifdef USE_SPAWNO , swapOut #endif }; const int resource::defInt[] = { 1, // PacketSort == by time 1, // AreaMode == subscribed 0, // LetterSort == by subject 1, // LetterMode == unread #ifdef DOSCHARS 0, // Charset == CP437 #else 1, // Charset == Latin-1 #endif 1, // UseTaglines == Yes 1, // AutoSaveReplies == Yes 0, // StripSoftCR == No 0, // BeepOnPers == No 1, // UseLynxNav == Yes 1, // ReOnReplies == Yes 78, // QuoteWrapCols 0, // MaxLines == disabled 1, // UseQPMailHead == Yes 1, // UseQPNewsHead == Yes 1, // UseQPMail == Yes 0, // UseQPNews == No 0, // ExpertMode == No 0, // IgnoreNDX = No 1, // Mouse = Yes #ifdef USE_SPAWNO 1, // swapOut == Yes #endif 1, // UseColors == Yes 0, // Transparency == No 1, // BackFill == Yes 1 // ClockMode == Time }; resource::resource() { names = rc_names; intro = rc_intro; comments = rc_comments; configItemNum = startUpLen; int c; for (c = 0; c < noOfStrings; c++) resourceData[c] = 0; for (c = noOfStrings; c < noOfResources; c++) { int d = c - noOfStrings; resourceInt[d] = defInt[d]; } set(outCharset, "iso-8859-1"); initinit(); homeInit(); mmHomeInit(); char *configFileName = fullpath(resourceData[homeDir], RCNAME); if (parseConfig(configFileName)) { newConfig(configFileName); printf("\nWelcome to " MM_NAME " v" MM_VERNUM "!\n\n" "A new or updated " RCNAME " has been written. " "If you continue now, " MM_NAME " will\nuse the default " "values for any new keywords. (Existing keywords have been " "\npreserved.) If you wish to edit your " RCNAME " first, " "say 'Y' at the prompt.\n\nEdit " RCNAME " now? (y/n) "); char inp = fgetc(stdin); if (toupper(inp) == 'Y') { mysystem2(resourceData[editor], configFileName); parseConfig(configFileName); } } delete[] configFileName; if (!verifyPaths()) fatalError("Unable to access data directories"); resourceData[BaseDir] = mytmpdir(resourceData[TempDir]); bool tmpok = checkPath(resourceData[BaseDir], false); if (!tmpok) fatalError("Unable to create temp directory"); subPath(WorkDir, "work"); subPath(UpWorkDir, "upwork"); } resource::~resource() { clearDirectory(resourceData[WorkDir]); clearDirectory(resourceData[UpWorkDir]); mychdir(resourceData[BaseDir]); myrmdir(resourceData[WorkDir]); myrmdir(resourceData[UpWorkDir]); clearDirectory(resourceData[BaseDir]); mychdir(resourceData[TempDir]); myrmdir(resourceData[BaseDir]); for (int c = 0; c < noOfStrings; c++) delete[] resourceData[c]; } bool resource::checkPath(const char *onepath, bool show) { if (mychdir(onepath)) { if (show) printf("Creating %s...\n", onepath); if (mymkdir(onepath)) return false; } return true; } bool resource::verifyPaths() { if (checkPath(resourceData[mmHomeDir], true)) if (checkPath(resourceData[PacketDir], true)) if (checkPath(resourceData[ReplyDir], true)) if (checkPath(resourceData[SaveDir], true)) return true; return false; } void resource::processOne(int c, const char *resValue) { if (*resValue) { c = startUp[c]; if (c < noOfStrings) { // Canonized for the benefit of the Windows version: set_noalloc(c, (c >= noOfRaw) ? canonize(fixPath(resValue)) : strdupplus(resValue)); if (mmHomeDir == c) mmHomeInit(); } else { int x = 0; char r = toupper(*resValue); switch (c) { case PacketSort: x = (r == 'T'); break; case AreaMode: x = (r == 'S'); if (!x) { r = toupper(resValue[1]); if (r == 'C') x = 2; } break; case LetterSort: switch (r) { case 'N': x = 1; break; case 'F': x = 2; break; case 'T': x = 3; } break; case LetterMode: x = (r == 'U'); break; case ClockMode: switch (r) { case 'O': x = 0; break; case 'T': x = 1; break; case 'E': x = 2; } break; case Charset: x = (r == 'L'); break; case QuoteWrapCols: case MaxLines: sscanf(resValue, "%d", &x); break; default: x = (r == 'Y'); } set(c, x); } } } const char *resource::configLineOut(int x) { static const char *pktopt[] = {"Name", "Time"}, *areaopt[] = {"All", "Subscribed", "Active"}, *lttopt1[] = {"Subject", "Number", "From", "To"}, *lttopt2[] = {"All", "Unread"}, *clockopt[] = {"Off", "Time", "Elapsed"}, *charopt[] = {"CP437", "Latin-1"}, *stdopt[] = {"No", "Yes"}; x = startUp[x]; if ((x == MaxLines) || (x == QuoteWrapCols)) { static char value[8]; sprintf(value, "%d", getInt(x)); return value; } else return (x < noOfStrings) ? get(x) : ((x == PacketSort) ? pktopt : ((x == AreaMode) ? areaopt : ((x == LetterSort) ? lttopt1 : ((x == LetterMode) ? lttopt2 : ((x == ClockMode) ? clockopt : ((x == Charset) ? charopt : stdopt))))))[getInt(x)]; } const char *resource::get(int ID) const { if (ID >= noOfStrings) fatalError("String resource out of range"); return resourceData[ID]; } int resource::getInt(int ID) const { if (ID < noOfStrings) fatalError("Integer resource out of range"); ID -= noOfStrings; return resourceInt[ID]; } void resource::set(int ID, const char *newValue) { if (ID >= noOfStrings) fatalError("String resource out of range"); delete[] resourceData[ID]; resourceData[ID] = strdupplus(newValue); } void resource::set_noalloc(int ID, char *newValue) { if (ID >= noOfStrings) fatalError("String resource out of range"); delete[] resourceData[ID]; resourceData[ID] = newValue; } void resource::set(int ID, int newValue) { if (ID < noOfStrings) fatalError("Integer resource out of range"); ID -= noOfStrings; resourceInt[ID] = newValue; } // -------------------------------------------------------------------- // The resource initializer functions // -------------------------------------------------------------------- void resource::homeInit() { bool usingHOME = false; const char *envhome = getenv("MMAIL"); if (!envhome) { envhome = getenv("HOME"); if (envhome) usingHOME = true; else envhome = error.getOrigDir(); } set_noalloc(homeDir, canonize(fixPath(envhome))); if (usingHOME) set_noalloc(mmHomeDir, canonize(fullpath(resourceData[homeDir], "mmail"))); else set(mmHomeDir, resourceData[homeDir]); } void resource::mmEachInit(int index, const char *dirname) { set_noalloc(index, canonize(fullpath(resourceData[mmHomeDir], dirname))); } void resource::subPath(int index, const char *dirname) { char *tmp = fullpath(resourceData[BaseDir], dirname); set_noalloc(index, tmp); if (!checkPath(tmp, 0)) fatalError("tmp Dir could not be created"); } void resource::initinit() { set(arjUncompressCommand, DEFUNARJ); set(zipUncompressCommand, DEFUNZIP); set(lhaUncompressCommand, DEFUNLHA); set(rarUncompressCommand, DEFUNRAR); set(tarUncompressCommand, DEFUNTAR); set(unknownUncompressCommand, DEFUNNONE); set(arjCompressCommand, DEFARJ); set(zipCompressCommand, DEFZIP); set(lhaCompressCommand, DEFLHA); set(rarCompressCommand, DEFRAR); set(tarCompressCommand, DEFTAR); set(unknownCompressCommand, DEFNONE); set(UncompressCommand, DEFUNZIP); set(CompressCommand, DEFZIP); set(sigFile, ""); set(UserName, ""); set(InetAddr, ""); set(QuoteHead, "-=> %f wrote to %t <=-"); set(InetQuote, "On %d, %f wrote:"); char *p = getenv("EDITOR"); set(editor, (p ? p : DEFEDIT)); } void resource::mmHomeInit() { set(TempDir, resourceData[mmHomeDir]); mmEachInit(PacketDir, "down"); mmEachInit(ReplyDir, "up"); mmEachInit(SaveDir, "save"); mmEachInit(AddressFile, ADDRBOOK); mmEachInit(TaglineFile, "taglines"); mmEachInit(ColorFile, "colors"); } mmail-0.52/mmail/omen.h000644 000765 000024 00000003467 13065426064 015736 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * OMEN Copyright 1999-2017 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #ifndef OMEN_H #define OMEN_H #include "pktbase.h" // HEADERxy.BBS records: struct omenReplyRec { unsigned char command; unsigned char curboard, moveboard; unsigned char msgnumber[2]; unsigned char tolen; char to[35]; unsigned char sublen; char subject[72]; unsigned char destzone[2], destnet[2], destnode[2]; unsigned char netattrib; unsigned char aliaslen; char alias[20]; unsigned char curhighboard; unsigned char movehighboard; unsigned char msghighnumber[2]; char extraspace[4]; }; class omen : public pktbase { char extent[4]; unsigned useLatin; void readSystemBBS(); void buildIndices(); void prefirstblk(); public: omen(mmail *); ~omen(); area_header *getNextArea(); letter_header *getNextLetter(); const char *getExtent(); bool isLatin(); }; class omenrep : public pktreply { class upl_omen : public upl_base { omenReplyRec omen_rec; public: char subject[73], to[36]; net_address na; long refnum; int origArea; bool privat; upl_omen(const char * = 0); bool init(FILE *); void output(FILE *); }; bool getRep1(FILE *, upl_omen *, int); void getReplies(FILE *); void addRep1(FILE *, upl_base *, int); void addHeader(FILE *); void repFileName(); const char *repTemplate(bool); public: omenrep(mmail *, specific_driver *); ~omenrep(); area_header *getNextArea(); letter_header *getNextLetter(); void enterLetter(letter_header &, const char *, long); bool getOffConfig(); bool makeOffConfig(); }; #endif mmail-0.52/mmail/misc.cc000644 000765 000024 00000035037 13245653121 016063 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * miscellaneous routines (global) Copyright 1996-1997 Toth Istvan Copyright 1997-2018 William McBrine , Peter Krefting Distributed under the GNU General Public License, version 3 or later. */ #include "mmail.h" // get a little-endian short, return an int unsigned getshort(const unsigned char *x) { return ((unsigned) x[1] << 8) + (unsigned) x[0]; } // get a little-endian long unsigned long getlong(const unsigned char *x) { return ((unsigned long) x[3] << 24) + ((unsigned long) x[2] << 16) + ((unsigned long) x[1] << 8) + (unsigned long) x[0]; } // get a big-endian long unsigned long getblong(const unsigned char *x) { return ((unsigned long) x[0] << 24) + ((unsigned long) x[1] << 16) + ((unsigned long) x[2] << 8) + (unsigned long) x[3]; } // put an int into a little-endian short void putshort(unsigned char *dest, unsigned source) { dest[0] = source & 0xff; dest[1] = (source & 0xff00) >> 8; } // put a long into a little-endian long void putlong(unsigned char *dest, unsigned long source) { dest[0] = source & 0xff; dest[1] = (source & 0xff00) >> 8; dest[2] = (source & 0xff0000) >> 16; dest[3] = (source & 0xff000000) >> 24; } // put a long into a big-endian long void putblong(unsigned char *dest, unsigned long source) { dest[0] = (source & 0xff000000) >> 24; dest[1] = (source & 0xff0000) >> 16; dest[2] = (source & 0xff00) >> 8; dest[3] = source & 0xff; } // convert MS-DOS-style date/time to struct tm struct tm *getdostime(unsigned long packed) { static struct tm unpacked; unpacked.tm_mday = packed & 0x1f; packed >>= 5; unpacked.tm_mon = (packed & 0x0f) - 1; packed >>= 4; unpacked.tm_year = (packed & 0x7f) + 80; packed >>= 7; unpacked.tm_sec = (packed & 0x1f) << 1; packed >>= 5; unpacked.tm_min = packed & 0x3f; packed >>= 6; unpacked.tm_hour = packed & 0x1f; return &unpacked; } // convert struct tm to MS-DOS-style date/time unsigned long mkdostime(struct tm *unpacked) { unsigned long packed; packed = unpacked->tm_hour; packed <<= 6; packed |= unpacked->tm_min; packed <<= 5; packed |= unpacked->tm_sec >> 1; packed <<= 7; packed |= unpacked->tm_year - 80; packed <<= 4; packed |= unpacked->tm_mon + 1; packed <<= 5; packed |= unpacked->tm_mday; return packed; } // takes off the spaces from the end of a string char *cropesp(char *st) { char *p; for (p = st + strlen(st) - 1; (p > st) && (*p == ' '); p--); p[1] = '\0'; return st; } // converts spaces to underline characters char *unspace(char *source) { for (unsigned c = 0; c < strlen(source); c++) if (source[c] == ' ') source[c] = '_'; return source; } // allocate and copy a string char *strdupplus(const char *original) { char *tmp; if (original) { tmp = new char[strlen(original) + 1]; strcpy(tmp, original); } else tmp = 0; return tmp; } // allocate but do not copy char *strdupblank(const char *original) { return original ? new char[strlen(original) + 1] : 0; } // tmp string = Path + filename char *fullpath(const char *dir, const char *name) { char *fp = new char[strlen(dir) + strlen(name) + 4]; sprintf(fp, "%s/%s", dir, name); return fp; } // If there's a space in the string, return it quoted char *quotespace(const char *pathname) { char *result; const char *sp = strchr(pathname, ' '); if (sp) { result = new char[strlen(pathname) + 3]; sprintf(result, "\"%s\"", pathname); } else result = strdupplus(pathname); return result; } const char *findBaseName(const char *fileName) { const int maxbaselen = 30; int c, d; static char tmp[maxbaselen + 2]; for (c = 0; (fileName[c] != '.') && (fileName[c]); c++); if (c > maxbaselen) c = maxbaselen; for (d = 0; d < c; d++) tmp[d] = tolower(fileName[d]); tmp[d] = '\0'; return tmp; } // For consistency, no path should end in a slash: char *fixPath(const char *path) { char *tmp; size_t len = strlen(path); char d = path[len - 1]; if ((d == '/') || (d == '\\')) { tmp = new char[len + 2]; sprintf(tmp, "%s.", path); } else tmp = strdupplus(path); return tmp; } int getNumExt(const char *fileName) { int retval = -1; const char *lastp = strrchr(fileName, '.'); if (lastp) { lastp++; if (strlen(lastp) == 3) { bool isnum = true; for (int x = 0; x < 3; x++) isnum = isnum && isdigit(lastp[x]); if (isnum) retval = atoi(lastp); } } return retval; } const char *stripre(const char *subject) { while (!strncasecmp(subject, "re: ", 4)) subject += 4; return subject; } // basically the equivalent of "strcasestr()", if there were such a thing const char *searchstr(const char *source, const char *item, int slen) { const char *s; char first[3], oldc = '\0'; char *end = (-1 != slen) ? ((char *) source + slen) : 0; size_t ilen = strlen(item) - 1; bool found = false; first[0] = tolower(*item); first[1] = toupper(*item); first[2] = oldc; if (end) { oldc = *end; *end = '\0'; } item++; do { s = strpbrk(source, first); if (s) { source = s + 1; found = !strncasecmp(source, item, ilen); } } while (s && !found && *source); if (end) *end = oldc; return found ? s : 0; } // Find the address in "Foo " or "foo@bar.baz (Foo)" const char *fromAddr(const char *source) { static char tmp[100]; const char *index = source; while (*index) { if (*index == '"') do index++; while (*index && (*index != '"')); if ((*index == '<') || ((*index == '(') && (index > source))) break; if (*index) index++; } bool bracket = (*index == '<'); const char *end = bracket ? strchr(index, '>') : index - (*index == '('); index = bracket ? (index + 1) : source; if (end) { size_t len = end - index; if (len > 99) len = 99; strncpy(tmp, index, len); tmp[len] = '\0'; return tmp; } return source; } // Find the name portion of the address const char *fromName(const char *source) { static char tmp[100]; const char *end = 0, *fr = source; while (*fr) { if (*fr == '"') { fr++; end = strchr(fr, '"'); break; } if (*fr == '(') { fr++; end = strchr(fr, ')'); break; } if ((*fr == '<') && (fr > source)) { end = fr - 1; if (*end != ' ') end++; fr = source; break; } if (*fr) fr++; } if (end) { size_t len = end - fr; if (len > 99) len = 99; strncpy(tmp, fr, len); tmp[len] = '\0'; return tmp; } return source; } // Should a name be quoted in an address? bool quoteIt(const char *s) { bool flag = false; while (*s) { int c = toupper(*s++); if (!(((c >= 'A') && (c <= 'Z')) || (c == ' '))) { flag = true; break; } } return flag; } // MIME decoding for header lines (=?iso-8859-1?Q? and =?iso-8859-1?B?) void headdec(const char *source, const char *cset, char *dest) { // Copyright 1999 Peter Krefting // Modified by William McBrine // May be used in any way as long as this copyright is included // in the derived work. bool isqp, isb64; int c, b64buf, b64count; if (source) for (isqp = isb64 = false, b64buf = b64count = 0; *source; source++) { c = *source; if (isqp) { if ('_' == c) // QP space *dest++ = ' '; else if (' ' == c) { // QP end *dest++ = ' '; isqp = false; } else if ('?' == c && '=' == source[1]) { source++; isqp = false; } else { if ('=' == c) { // QP escape char hex[3] = "00"; hex[0] = *++source; hex[1] = *++source; sscanf(hex, "%x", (unsigned *) &c); } *dest++ = c; } } else if (isb64) { if ('?' == c && '=' == source[1]) { source++; isb64 = false; } else { // Update base64 buffer static const char *base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" // 0-25 "abcdefghijklmnopqrstuvwxyz" // 26-51 "0123456789+/="; // 52-63 + pad int b64val; const char *p = strchr(base64, c); b64val = p ? (p - base64) : 0; if (64 == b64val) b64val = 0; b64buf = (b64buf << 6) | b64val; b64count++; switch (b64count) { case 2: c = (b64buf & 0xff0) >> 4; if (c) *dest++ = c; b64buf &= 0x0f; break; case 3: c = (b64buf & 0x3fc) >> 2; if (c) *dest++ = c; b64buf &= 0x03; break; case 4: if (b64buf) *dest++ = b64buf; b64buf = b64count = 0; } } } else if ('=' == c && '?' == source[1]) { size_t clen = strlen(cset); if (!strncasecmp(source + 2, cset, clen)) { char d = toupper(source[3 + clen]); if (('Q' == d) || ('B' == d)) { source += 4 + clen; if ('Q' == d) isqp = true; else { b64buf = b64count = 0; isb64 = true; } } else *dest++ = c; } else *dest++ = c; } else *dest++ = c; } *dest = '\0'; } // write a string to a file in RFC 2047 (pseudo-QP) form void headenc(const unsigned char *in, const char *cset, FILE *out) { bool qp = false; while (*in) { if (!qp && (*in & 0x80)) { fprintf(out, "=?%s?Q?", cset); qp = true; } if (qp) switch (*in) { case ' ': if ('<' != in[1]) fputc('_', out); else { fprintf(out, "?= "); qp = false; } break; case '\"': fprintf(out, "?=\""); qp = false; break; default: if (*in & 0x80 || '=' == *in || '?' == *in || '_' == *in) fprintf(out, "=%02x", *in); else fputc(*in, out); } else fputc(*in, out); in++; } if (qp) fprintf(out, "?="); } // decode quoted-printable text in the body of a message unsigned char *qpdecode(unsigned char *source) { unsigned char *dest = source; while (*source) { *dest = *source++; if (*dest == '=') { unsigned char hex[3]; hex[0] = *source++; if (hex[0] == '\n') dest--; else { unsigned i; hex[1] = *source++; hex[2] = '\0'; sscanf((char *) hex, "%x", &i); *dest = i; } } dest++; } return dest; } long qpdecode(FILE *source, FILE *dest) { int c; long count = 0; while ((c = fgetc(source)) != EOF) { if ('=' == c) { unsigned char hex[3]; hex[0] = fgetc(source); if (hex[0] != '\n') { unsigned i; hex[1] = fgetc(source); hex[2] = '\0'; sscanf((char *) hex, "%x", &i); fputc(i, dest); count++; } } else { fputc(c, dest); count++; } } return count; } // write text to a file in Quoted-Printable form void qpencode(FILE *src, FILE *dest) { unsigned char buf[77]; int c, col = 0, lastcol = 0; long lastsp = 0; while ((c = fgetc(src)) != EOF) { bool quotethis = (c & 0x80) || ('=' == c); if ((quotethis && (col > 72)) || (col > 74)) { int d = fgetc(src); ungetc(d, src); if ((d != EOF) && (d != '\n')) { if (lastcol) { fseek(src, lastsp, SEEK_SET); col = lastcol + 1; } else // Warning! Second ungetc() not guaranteed! ungetc(c, src); buf[col++] = '='; c = '\n'; } } if ('\n' == c) { buf[col] = '\0'; fprintf(dest, "%s\n", buf); col = lastcol = 0; } else { if (quotethis) { sprintf((char *) buf + col, "=%02X", c); col += 3; } else { if (' ' == c) { lastcol = col; lastsp = ftell(src); } buf[col++] = c; } } } if (col) { buf[col] = '\0'; fprintf(dest, "%s\n", buf); } } mmail-0.52/mmail/mmail.h000644 000765 000024 00000033016 13065426006 016064 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * mmail class Copyright 1996 Toth Istvan Copyright 1998-2017 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #ifndef MMAIL_H #define MMAIL_H #include "../config.h" #include "../mmail/misc.h" #include "../mmail/resource.h" #include "../interfac/mysystem.h" extern "C" { #include #include /* QNX needs strings.h for strcasecmp(); seems harmless on others */ #ifndef USE_STRICMP # include #endif } #include // Number of the Reply area #define REPLY_AREA 0 // Area types -- bit f. #define COLLECTION 1L #define REPLYAREA 2L #define ACTIVE 4L #define ALIAS 8L #define NETMAIL 0x10L #define INTERNET 0x20L #define PUBLIC 0x40L #define PRIVATE 0x80L #define LATINCHAR 0x100L #define ECHOAREA 0x200L #define PERSONLY 0x400L #define PERSALL 0x800L #define SUBKNOWN 0x1000L #define ADDED 0x2000L #define DROPPED 0x4000L #define OFFCONFIG 0x8000L #define READONLY 0x10000L #define HASREPLY 0x20000L // Mail statuses -- bit f. enum {MS_READ = 1, MS_REPLIED = 2, MS_MARKED = 4, MS_PERSTO = 8, MS_PERSFROM = 0x10, MS_SAVED = 0x20}; // For letter_list::sort enum {LS_SUBJ, LS_MSGNUM, LS_FROM, LS_TO}; enum pktstatus {PKT_OK, UNCOMP_FAIL, PTYPE_UNK, NEW_DIR, PKT_NOFILES, PKT_UNFOUND}; class mmail; class resource; class file_header; class file_list; class area_header; class area_list; class letter_body; class letter_header; class letter_list; class specific_driver; class reply_driver; class driver_list; class read_class; class net_address { char *inetAddr; void copy(net_address &); public: unsigned zone, net, node, point; bool isInternet, isSet; net_address(); net_address(net_address &); ~net_address(); bool operator==(net_address &); net_address &operator=(const char *); net_address &operator=(net_address &); operator const char *(); }; class mmail { public: resource *resourceObject; file_list *workList; driver_list *driverList; area_list *areaList; letter_list *letterList; specific_driver *packet; reply_driver *reply; mmail(); ~mmail(); pktstatus selectPacket(const char *); void Delete(); bool saveRead(); bool checkForReplies(); bool makeReply(); void deleteReplies(); void openReply(); bool getOffConfig(); }; class file_header { char *name; time_t date; off_t size; public: file_header *next; file_header(const char *, time_t, off_t); ~file_header(); const char *getName() const; time_t getDate() const; void setDate(); off_t getSize() const; }; class file_list { file_header **files, **dirs; char *DirName, *filter; int noOfFiles, noOfDirs, activeFile; bool sorttype, dirlist; void cleanup(); void relist(); void sort(); file_header *base() const; file_header *base(int) const; public: file_list(const char *, bool = false, bool = false); ~file_list(); void resort(); int getNoOfDirs() const; int getNoOfFiles() const; const char *getDirName() const; void gotoFile(int); char *changeDir(const char * = 0); int changeName(const char *); const char *getName() const; time_t getDate() const; void setDate(); off_t getSize() const; const char *getNext(const char *); file_header *getNextF(const char *); const char *exists(const char *); file_header *existsF(const char *); void addItem(file_header **, const char *, int &); char *expandName(const char *); FILE *ftryopen(const char *); void kill(); int nextNumExt(const char *); const char *getFilter() const; void setFilter(const char *); }; class area_header { mmail *mm; specific_driver *driver; const char *shortName, *name, *description, *areaType; unsigned long type; int noOfLetters, noOfPersonal, noOfReplies, num, maxtolen, maxsublen; public: area_header(mmail *, int, const char *, const char *, const char *, const char *, unsigned long, int, int, int, int); inline const char *getShortName() const; inline const char *getName() const; inline const char *getDescription() const; inline const char *getAreaType() const; inline const char *getTear(); inline unsigned long getType() const; inline int getNoOfLetters() const; inline int getNoOfUnread(); inline int getNoOfMarked(); inline int getNoOfPersonal() const; inline bool getUseAlias() const; inline bool isCollection() const; inline bool isReplyArea() const; inline bool isNetmail() const; inline bool isInternet() const; inline bool isEmail() const; inline bool isUsenet() const; inline bool isLatin() const; inline bool isReadOnly() const; inline bool hasTo() const; inline bool hasPublic() const; inline bool hasPrivate() const; inline int maxToLen() const; inline int maxSubLen() const; inline bool hasOffConfig() const; inline void Add(); inline void Drop(); inline void addReply(); inline void killReply(); bool isActive() const; }; class area_list { mmail *mm; area_header **areaHeader; int no, noActive, current, *activeHeader; int almode; char *filter; public: area_list(mmail *); ~area_list(); bool relist(); int getRepList(); void updatePers(); const char *getShortName() const; const char *getName() const; const char *getName(int); const char *getDescription() const; const char *getDescription(int); const char *getAreaType() const; const char *getTear(); unsigned long getType() const; int getNoOfLetters() const; int getNoOfUnread() const; int getNoOfMarked() const; int getNoOfPersonal() const; bool getUseAlias() const; bool isCollection() const; bool isReplyArea() const; bool isEmail() const; bool isNetmail() const; bool isInternet() const; bool isUsenet() const; bool isLatin() const; bool isLatin(int); bool isReadOnly() const; bool hasTo() const; bool hasPublic() const; bool hasPrivate() const; int maxToLen() const; int maxSubLen() const; bool hasOffConfig() const; void Add(); void Drop(); bool isShortlist() const; int getMode() const; void setMode(int); void getLetterList(); void enterLetter(int, const char *, const char *, const char *, const char *, const char *, int, bool, net_address &, const char *, long); void killLetter(int, long); void refreshArea(); void gotoArea(int); void gotoActive(int); int getAreaNo() const; int getActive(); int noOfAreas() const; int noOfActive() const; int findNetmail() const; int findInternet() const; bool anyChanged() const; const char *getFilter() const; void setFilter(const char *); const char *filterCheck(const char *); }; class letter_body { char *text; long length, offset; bool hidden; public: letter_body *next; letter_body(char *, long, long = 0, bool = false); ~letter_body(); char *getText(); long getLength(); bool isHidden(); }; class letter_header { driver_list *dl; read_class *readO; specific_driver *driver; char *subject, *to, *from, *date, *msgid, *newsgrps, *follow, *reply; long replyTo; int LetterID, AreaID; bool privat, persfrom, persto; int length; long msgNum; net_address netAddr; bool charset, qpenc; public: letter_header(mmail *, const char *, const char *, const char *, const char *, const char *, long, int, long, int, bool, int, specific_driver *, net_address &, bool = false, const char * = 0, const char * = 0, const char * = 0, bool = false); ~letter_header(); void changeSubject(const char *); void changeTo(const char *); void changeFrom(const char *); void changeDate(const char *); void changeMsgID(const char *); void changeNewsgrps(const char *); void changeFollow(const char *); void changeReplyTo(const char *); const char *getSubject() const; const char *getTo() const; const char *getFrom() const; const char *getDate() const; const char *getMsgID() const; const char *getNewsgrps() const; const char *getFollow() const; const char *getReply() const; net_address &getNetAddr(); long getReplyTo() const; bool getPrivate() const; inline letter_body *getBody(); int getLetterID() const; int getAreaID() const; inline long getMsgNum() const; inline int getLength() const; inline bool isPersonal() const; inline bool isLatin() const; bool isQP() const; void setLatin(bool); void setQP(bool); inline bool getRead(); inline void setRead(); inline int getStatus(); inline void setStatus(int); }; class letter_list { mmail *mm; driver_list *dl; specific_driver *driver; read_class *readO; letter_header **letterHeader; int noOfLetters, noActive, areaNumber, currentLetter; int *activeHeader; int llmode; unsigned long type; bool isColl; char *filter; void init(); void cleanup(); void sort(); public: letter_list(mmail *, int, unsigned long); ~letter_list(); void relist(); void resort(); int getMode() const; void setMode(int); const char *getSubject() const; const char *getTo() const; const char *getFrom() const; const char *getDate() const; const char *getMsgID() const; const char *getNewsgrps() const; const char *getFollow() const; const char *getReply() const; letter_body *getBody(); net_address &getNetAddr(); long getReplyTo() const; long getMsgNum() const; int getAreaID() const; bool getPrivate() const; int getLength() const; bool isPersonal() const; bool isLatin() const; bool isQP() const; void setQP(bool); int getStatus(); void setStatus(int); bool getRead(); void setRead(); void rrefresh(); bool findMsgNum(long); bool findReply(int, long); int noOfLetter() const; int noOfActive() const; void gotoLetter(int); void gotoActive(int); int getCurrent() const; int getActive() const; const char *getFilter() const; void setFilter(const char *); const char *filterCheck(const char *); }; class driver_list { struct driver_struct { specific_driver *driver; read_class *read; } driverList[2]; int noOfDrivers; public: driver_list(mmail *); ~driver_list(); void initRead(); int getNoOfDrivers() const; specific_driver *getDriver(int); reply_driver *getReplyDriver(); read_class *getReadObject(specific_driver *); int getOffset(specific_driver *); }; class read_class { public: virtual ~read_class(); virtual void init() = 0; virtual void setRead(int, int, bool) = 0; virtual bool getRead(int, int) = 0; virtual void setStatus(int, int, int) = 0; virtual int getStatus(int, int) = 0; virtual int getNoOfUnread(int) = 0; virtual int getNoOfMarked(int) = 0; virtual bool saveAll() = 0; }; class main_read_class : public read_class { mmail *mm; resource *ro; specific_driver *driver; int noOfAreas, **readStore, *noOfLetters; bool hasPersArea, hasPersNdx; public: main_read_class(mmail *, specific_driver *); ~main_read_class(); void init(); void setRead(int, int, bool); bool getRead(int, int); void setStatus(int, int, int); int getStatus(int, int); int getNoOfUnread(int); int getNoOfMarked(int); bool saveAll(); const char *readFilePath(const char *); }; class reply_read_class: public read_class { public: reply_read_class(mmail *, specific_driver *); ~reply_read_class(); void init(); void setRead(int, int, bool); bool getRead(int, int); void setStatus(int, int, int); int getStatus(int, int); int getNoOfUnread(int); int getNoOfMarked(int); bool saveAll(); }; class specific_driver { public: virtual ~specific_driver(); virtual bool hasPersArea() = 0; virtual bool hasPersonal() = 0; virtual bool isLatin() = 0; virtual const char *oldFlagsName() = 0; virtual bool readOldFlags() = 0; virtual bool saveOldFlags() = 0; virtual int getNoOfAreas() = 0; virtual area_header *getNextArea() = 0; virtual void selectArea(int) = 0; virtual int getNoOfLetters() = 0; virtual void resetLetters() = 0; virtual letter_header *getNextLetter() = 0; virtual letter_body *getBody(letter_header &) = 0; virtual const char *getLoginName() = 0; virtual const char *getAliasName() = 0; virtual const char *getBBSName() = 0; virtual const char *getSysOpName() = 0; virtual const char *getBBSProg() = 0; virtual const char *getDoorProg() = 0; virtual file_header *getHello() = 0; virtual file_header *getGoodbye() = 0; virtual file_header **getBulletins() = 0; virtual const char *getTear(int) = 0; }; class reply_driver : public specific_driver { public: virtual ~reply_driver(); virtual bool checkForReplies() = 0; virtual void init() = 0; virtual void enterLetter(letter_header &, const char *, long) = 0; virtual void killLetter(int) = 0; virtual area_header *refreshArea() = 0; virtual bool makeReply() = 0; virtual void deleteReplies() = 0; virtual bool getOffConfig() = 0; virtual bool makeOffConfig() = 0; }; // Letter sort type flag extern int lsorttype; #endif mmail-0.52/mmail/opxstrct.h000644 000765 000024 00000043375 12600035235 016656 0ustar00wmcbrinestaff000000 000000 /* OPX packet structures, in C Reverse engineered by William McBrine Placed in the Public Domain Version 1.2 of this document, Dec. 28, 2000 -- Separated Fido structure from MAIL.DAT header -- Updated email address, revised commentary Version 1.1 of this document (unreleased), Feb. 14, 2000 -- Modified description of line endings -- DUSRCFG.DAT and EXTAREAS.DAT, contributed by Armando Ramos -- MAIL.FDX info -- A few additional flags; realignment of brdRec -- More details on .ID -- Changed string definition Version 1.0 of this document, Oct. 27, 1999 -- First public documentation of the OPX format This is still not a complete specification. Although adequate for a reader, it should not be used as the basis for a door. BRDINFO.DAT contains the list of areas, along with things like sysop and BBS names; MAIL.DAT holds the actual messages. (These correspond roughly to CONTROL.DAT and MESSAGES.DAT, respectively, in QWK.) Each packet also has two index files, MAIL.FDX and MAIL.IDX. The .IDX file appears redundant, and is not documented here. The .FDX file is needed to ensure correct handling of long messages (> 64K), and is also used to store read markers. The new files list is NEWFILES.TXT, and a variety of bulletin files may be present. Offline config is handled through the optional files DUSRCFG.DAT (from the door) and RUSRCFG.DAT (from the reader). The structures were originally designed for Borland Pascal. Here's a macro to help define the strings in C terms: */ #define pstring(y,x) unsigned char y[x + 1] /* Note that in version 1.0, this was instead defined as: #define pstring(y,x) struct {unsigned char len; char data[x];} y But I found that on some systems, the structs were being padded for alignment. Here are macros for little-endian shorts (16-bit) and longs (32-bit) -- similar to the tWORD and tDWORD defintions in the Blue Wave specs, except that I don't draw a distinction between signed and unsigned, and I use only the portable (byte-by-byte) definition: */ typedef unsigned char pshort[2]; typedef unsigned char plong[4]; typedef unsigned char pbyte; /* ### Fido Message Header ### */ /* This is used both in MAIL.DAT, and in replies. It replaces the "repHead" struct used in earlier versions of this document, and changes the "msgHead" struct. Based on Fido packet specifications, this struct uses null-terminated (C-style) strings instead of the BP strings used elsewhere. */ #define FIDO_HEAD_SIZE 190 typedef struct { char from[36]; /* From: (null-terminated string) */ char to[36]; /* To: */ char subject[72]; /* Subject: */ char date[20]; /* Date in ASCII (not authoritative) */ pshort dest_zone; /* Fido zone number of destination -- in replies, set to 0 for non-netmail */ pshort dest_node; /* Node of dest. */ pshort orig_node; /* Node number of originating system */ pshort orig_zone; /* Zone of orig. */ pshort orig_net; /* Net of orig. */ pshort dest_net; /* Net of dest. */ plong date_written; /* Date in packed MS-DOS format */ plong date_arrived; /* Date the message arrived on the BBS, in packed MS-DOS format (meaningless in replies) */ pshort reply; /* Number of message that this replies to, if applicable */ pshort attr; /* Attributes */ pshort up; /* Number of message that replies to this one, if applicable (meaningless in replies) */ } fidoHead; /* "attr" is a bitfield with the following values: */ #define OPX_PRIVATE 1 /* Private message */ #define OPX_CRASH 2 /* Fido crashmail */ #define OPX_RECEIVED 4 /* Read by addressee */ #define OPX_SENT 8 #define OPX_FATTACH 16 #define OPX_ORPHAN 64 #define OPX_KILL 128 #define OPX_LOCAL 256 /* Some readers set this on every reply */ #define OPX_HOLD 512 #define OPX_FREQ 2048 #define OPX_RREQ 4096 #define OPX_RECEIPT 8192 #define OPX_FUREQ 32768 /* "Packed MS-DOS format" dates are the format used by MS-DOS in some of its time/date routines, and in the FAT file system. They can cover dates from 1980 through 2107 -- better than a signed 32-bit time_t, but still limited. The ASCII date field is deprecated. bits 00-04 = day of month bits 05-08 = month bits 09-15 = year - 1980 bits 16-20 = second / 2 bits 21-26 = minute bits 27-31 = hour */ /* ### BRDINFO.DAT structures ### */ /* The Header */ /* To ensure correct operation where alignment padding is used, when reading from or writing to disk, use the _SIZE defines given here rather than "sizeof": */ #define BRD_HEAD_SIZE 743 typedef struct { char unknown1[15]; pstring(doorid,20); /* ID of the door that made this */ pstring(bbsid,8); /* Like BBSID in QWK; used for replies */ pstring(bbsname,60); /* BBS name */ pstring(sysopname,50); /* Sysop's name */ char unknown2[81]; pstring(zone,6); /* Fidonet zone number of BBS, in ASCII */ pstring(net,6); /* Net number */ pstring(node,6); /* Node number */ char unknown3[252]; pstring(doorver,10); /* Version number of door */ char unknown4[2]; pstring(phoneno,26); /* Phone number of BBS */ char unknown5[4]; pstring(bbstype,123); /* BBS software name and version -- not the right length, I'm sure */ pstring(username,35); /* User's name */ char unknown6[21]; pshort numofareas; /* Number of conferences */ char unknown7[4]; pbyte readerfiles; /* Number of readerfiles */ } brdHeader; /* This is followed by a 13-byte record (a Pascal string[12]) for each readerfile, as specified in brdHeader.readerfiles. Then there's a single byte before the board records begin, which is an obsolete area counter. Note that in version 1.0 of this document, I chose to ignore this and assume that the board record started one byte earlier. */ /* Each Area */ #define BRD_REC_SIZE 86 typedef struct { pshort acclevel; /* Access level */ pbyte conflow; /* Low byte of conf. number (obsolete) */ pstring(name,70); /* Name of conference on BBS */ pshort confnum; /* Number of conference on BBS */ char unknown2[3]; pshort attrib; /* Area attributes (bitflags) */ char unknown3[2]; pbyte attrib2; /* More area attributes */ pbyte scanned; /* Subscribed flag -- not reliable */ pbyte oldattrib; /* Low byte of attrib (obsolete) */ } brdRec; /* Some of the flags in attrib appear to be: */ #define OPX_NETMAIL 1 #define OPX_PRIVONLY 4 #define OPX_PUBONLY 8 /* And in attrib2: */ #define OPX_INTERNET 64 #define OPX_USENET 128 /* After all the areas comes some extra data which I'm ignoring for now. It appears to be a list of the message number, conference number, and attribute (?) for each message in the packet. */ /* ### EXTAREAS.DAT ### */ /* Some packets have extra area data in the file EXTAREAS.DAT. This file consists entirely of brdRec records, as in BRDINFO.DAT. The procedure for handing area data when this file is present should be as follows: X = brdHeader.numofareas Y = Length of file "EXTAREAS.DAT" / Structure length of brdRec (86) Z = X - Y Read Z amount of area info from "BRDINFO.DAT" Read Y amount of area info from "EXTAREAS.DAT" */ /* ### MAIL.DAT structures ### */ /* Each message consists of the header, in a fixed format shown below, followed by the message text, whose length is specified in the header. Messages for all areas are concatenated. The first 14 bytes of the header are specific to OPX; the remainder is based on Fidonet packet structures. The length field specifies the length of the entire message, including the classic Fido header, but NOT including the OPX-specific part of the header (those first 14 bytes). Since the header size is fixed (AFAIK), a more useful interpretation of the length field might be the length of the text plus 0xBE bytes. Also, because the field is only a 16-bit integer, it will be invalid if a message longer than 64k is packed. */ /* OPX Message Header */ #define MSG_HEAD_SIZE 204 typedef struct { pshort msgnum; /* Message number on BBS */ pshort confnum; /* Conference number */ pshort length; /* Length of text + Fido header (0xBE) */ char unknown1; char msgtype; /* 'D' = Direct (personal), 'K' = Keyword, or ' ' */ char unknown2[6]; fidoHead f; /* Classic Fido header */ } msgHead; /* The message text consists of lines delimited by LF, CRLF, CR, or even a misused "soft CR" character (0x8D). There's no consistency, and I'm not sure whether this covers all the possible forms. Various Fido-style "hidden lines" may be present in the text, including "INETORIG
" on Internet email. */ /* ### MAIL.FDX structures ### */ /* Basically one header, and then one record per message; but it's a little trickier than that. */ /* The Header */ #define FDX_HEAD_SIZE 25 typedef struct { pshort RowsInPage; /* Normally the total number of messages */ pshort ColsInPage; /* Always 1, in MAIL.FDX */ pshort PagesDown; pshort PagesAcross; pshort ElSize; /* Size of element; i.e., sizeof(fdxRec) */ pshort PageSize; /* RowsInPage * ColsInPage * ElSize */ pshort PageCount; /* Normally 1, but see below */ plong NextAvail; /* Next "page" = Total file size, here */ char ID[7]; /* Always "\006VARRAY" */ } fdxHeader; /* This is followed by a table of pointers to pages; each is a plong. You're supposed to use this by looping from 1 to PageCount, reading each entry from the pointer table, then seeking to that position and reading RowsInPage records for each one. In practice, PageCount seems always to be 1, while RowsInPage is equivalent to the total number of messages in the packet. But I can't guarantee this. If more than one page were used, the RowsInPage value would become tricky, since only one value is used for all pages; if fdxRec records were split over multiple pages, the same number would have to be assigned to each page, as there seems to be no provision for having one page shorter than another. On the other hand, a second page would seem to be required for any packet with more than 5957 messages. (Perhaps such a large packet is simply not allowed?) PagesDown and PagesAcross also seem always to be 1. */ /* Each Message */ #define FDX_REC_SIZE 11 typedef struct { pshort confnum; /* Area number */ pshort msgnum; /* Message number */ char msgtype; /* 'D' = Direct (personal), 'K' = Keyword, or ' ' */ pbyte flags; /* Read, replied, etc. */ pbyte marks; /* Marked, etc. */ plong offset; /* Start of message in MAIL.DAT */ } fdxRec; /* Although these records contain the conference and message numbers themselves, they should be stored in the same order as the messages in MAIL.DAT. Message lengths can be calculated by subtracting the offset field of an fdxRec from that of the next one. */ /* "flags" is a bitfield with the following values: */ #define FDX_READ 0x01 /* Set when read by reader */ #define FDX_REPLIED 0x02 /* Set if a reply exists for this message */ #define FDX_SEARCH 0x04 /* Set if message is a search hit */ /* "marks" is a bitfield with the following values: */ #define FDX_KILL 0x01 #define FDX_FILE 0x02 #define FDX_PRINT 0x04 #define FDX_READ2 0x08 #define FDX_URGENT 0x10 #define FDX_TAGGED 0x20 #define FDX_DOS 0x40 /* ### DUSRCFG.DAT structures ### */ /* Only some packets have this. It indicates which areas are subscribed to, and enables offline config. There's one header record, followed by one record for each area. */ /* The Header */ #define OCFG_HEAD_SIZE 120 typedef struct { plong unknown1; pbyte flags1, flags2, flags3, flags4; /* Bit-mapped options */ pshort numofareas; /* Total number of areas */ pbyte helplevel; /* Door Menu Help Level: 0 = NOVICE, 1 = EXPERT, 2 = GXPRESS */ pbyte flags5; char unknown2[108]; } ocfgHeader; /* Values for the flags are as follows: */ /* Bit-masks for flags1 */ #define OCFG_GRAPHICS 1 /* Use Door Ansi Graphics */ #define OCFG_HOTKEYS 2 /* Use Door Menu Hot Keys */ #define OCFG_GROUPMAIL 4 /* Accept Group Mail */ #define OCFG_MY_MAIL 8 /* Scan Your Own Mail */ /* Bit-masks for flags2 */ #define OCFG_NEWFILES 4 /* Scan for New Files */ #define OCFG_MAIL_ONLY 64 /* Show Areas with Mail Only */ /* Bit-masks for flags3 */ #define OCFG_VACATION 4 /* Use Vacation Saver Mail */ #define OCFG_BULLETIN 8 /* Scan For News Bulletins */ #define OCFG_IBM_CHAR 16 /* Use IBM Characters */ #define OCFG_REP_RECPT 32 /* Send REPLY Receipt */ #define OCFG_SEND_QWK_NDX 64 /* QWK: Send NDX index files */ #define OCFG_STRIP_QWK_KLUDGE 128 /* QWK: Strip Kludges Lines */ /* Bit-masks for flags4 */ #define OCFG_AUTO_XPRESS 1 /* Use Auto Xpress Starter */ #define OCFG_SEL_AREAS_ONLY 8 /* Send Selected Areas Only */ #define OCFG_PACKET_EXT 64 /* Use Packet Extension */ #define OCFG_USE_FLEX 128 /* Use Flex Assistant */ /* Bit-masks for flags5 */ #define OCFG_QWK_WRAP 1 /* QWK: Perform Word Wrapping */ #define OCFG_SKIP_RIP 2 /* Skip RIP Graphics */ #define OCFG_SEARCH_MSG 4 /* Search Message Body */ #define OCFG_COLORED_NEW_FILES 8 /* Colorized New Files List */ /* Each Area */ #define OCFG_REC_SIZE 3 typedef struct { pshort confnum; pbyte scanned; /* 1 = Scan, 0 = Don't scan */ } ocfgRec; /* After all the specified area records, some extra records sometimes appear at the end of the file; I don't know their purpose, if any. */ /* ### REPLIES ### */ /* Reply packets are named in the form .REP. Inside each packet is one file per message, in a form very close to that used in MAIL.DAT, with a header followed by the text; along with a text file named .ID. Where offline config is supported, RUSRCFG.DAT may also be included. The header is just the classic Fido header ("fidoHead"). This omits one crucial piece of information: the conference number! Instead, it's encoded in the filename of the message. The names come in two forms. Messages which are not replies to existing messages are named as: !N. where is the serial number of the message (this is ignored, AFAIK), in decimal, with no leading zeroes; and is the destination area, in _base 36_, with leading zeroes if needed to pad it out to three characters. Messages which are replies are named: !R. In this case, is the number of the message to which this is a reply. (This is redundant with the reply field in the header.) is again a decimal number with no leading zeroes, and is the same in both forms. Note that this system implies there can only be one reply to a given message in a single reply packet; some readers enforce this. (The limit can be circumvented by not using the R form for subsequent replies.) After the header comes the message text. The length is determined by the file length, since there's no length field in the header. The text is CRLF-delimited paragraphs. I'm not sure whether the lines should be forced to wrap (like QWK) or not (like Blue Wave); when I tested this, I got inconsistent results. Several Fido-style "hidden lines" (beginning with ctrl-A) may be present; one reader typically includes a PID and MSGID, and adds an INTL for netmail. Internet email is indicated by an "INETDEST" kludge line (ctrl-A, "INETDEST", a space (no colon), and then the address). Some readers end the text with a zero byte, but this doesn't appear to be necessary. */ /* .ID */ /* A CRLF-delimited plain ASCII text file with seven lines. It seems to be intended for security/validation purposes, mainly indicating the registration status of the reader. Line 1: A textual representation of a boolean, either "TRUE" or "FALSE". It's "TRUE" only if the packet comes from a registered reader AND the user name matches the registered user. Line 2: Blank, in replies where line 1 is "FALSE"; otherwise, a hexadecimal number of unknown significance. Line 3: Version number of the reader, in decimal. Line 4: Another boolean; always "TRUE" in my limited experience. I don't yet know its purpose. Line 5: The date the packet was created, in packed MS-DOS format, treated as a signed number and written out as a decimal number. Line 6: The "reader code" in decimal; 0 for unregistered readers. This shows up even when the names don't match. Line 7: Another number; it seems to be always "-598939720" when line 1 is "FALSE", but varies otherwise. Also, the file is given a weird time stamp, although that doesn't seem to be required by the doors I've tested it with. */ /* RUSRCFG.DAT */ /* This file is the same format as DUSRCFG.DAT, but is generated by the reader. It is only present when the configuration is changed, and may only be generated if a DUSRCFG.DAT file exists in the original packet. Unchanged information from DUSRCFG.DAT is carried over to RUSRCFG.DAT. */ mmail-0.52/mmail/qwk.h000644 000765 000024 00000005126 13432114704 015565 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * QWK Copyright 1997 John Zero Copyright 1997-2019 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #ifndef QWK_H #define QWK_H #include "pktbase.h" #define ndxRecLen 5 // Offset of the "chunks" field, so it can be written separately #define CHUNK_OFFSET 116 #define getQfield(d, s, l) { memcpy(d, s, l); d[l] = '\0'; } class qheader { struct qwkmsg_header { char status; char msgnum[7]; // in ASCII char date[8]; // ASCII MM-DD-YY date char time[5]; // time in HH:MM ASCII char to[25]; // TO char from[25]; // FROM char subject[25]; // subject of message char password[12]; // message passw. char refnum[8]; // in ASCII char chunks[6]; // number of 128 byte chunks char alive; // msg is alive/killed unsigned char confLSB; unsigned char confMSB; char res[3]; }; public: char from[72], to[72], subject[72], date[15]; long msglen; int origArea; long msgnum, refnum; bool privat, netblock; bool init(FILE *); bool init_short(FILE *); void output(FILE *); void set_length(FILE *, long, long); }; class qwkpack : public pktbase { char newsfile[1][13]; char controlname[26]; bool qwke, greekqwk; unsigned long MSBINtolong(unsigned const char *); void readControlDat(); void readDoorId(); void readToReader(); bool externalIndex(); void readIndices(); void getblk(int, long &, long, unsigned char *&, unsigned char *&); void postfirstblk(unsigned char *&, letter_header &); void endproc(letter_header &); public: qwkpack(mmail *); ~qwkpack(); area_header *getNextArea(); letter_header *getNextLetter(); bool isQWKE(); bool isGreekQWK(); const char *ctrlName(); }; class qwkreply : public pktreply { class upl_qwk : public upl_base { public: qheader qHead; upl_qwk(const char * = 0); }; bool qwke, greekqwk; bool getRep1(FILE *, upl_qwk *); void getReplies(FILE *); void addRep1(FILE *, upl_base *, int); void addHeader(FILE *); void repFileName(); const char *repTemplate(bool); public: qwkreply(mmail *, specific_driver *); ~qwkreply(); area_header *getNextArea(); letter_header *getNextLetter(); void enterLetter(letter_header &, const char *, long); bool getOffConfig(); bool makeOffConfig(); }; #endif mmail-0.52/mmail/filelist.cc000644 000765 000024 00000017115 13245654404 016745 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * file_header and file_list Copyright 1996-1997 Toth Istvan Copyright 1998-2018 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #include "mmail.h" extern "C" int fnamecomp(const void *a, const void *b) { int d; const char *p = (*((file_header **) a))->getName(); const char *q = (*((file_header **) b))->getName(); d = strcasecmp(p, q); if (!d) d = strcmp(q, p); return d; } extern "C" int ftimecomp(const void *a, const void *b) { long result = (*((file_header **) b))->getDate() - (*((file_header **) a))->getDate(); return (result > 0) ? 1 : (result < 0) ? -1 : 0; } // ---------------------------------------------------------------- // file_header methods // ---------------------------------------------------------------- file_header::file_header(const char *nameA, time_t dateA, off_t sizeA) : date(dateA), size(sizeA) { name = strdupplus(nameA); next = 0; } file_header::~file_header() { delete[] name; } const char *file_header::getName() const { return name; } time_t file_header::getDate() const { return date; } void file_header::setDate() { date = touchFile(name); } off_t file_header::getSize() const { return size; } // -------------------------------------------------------------- // file_list methods // -------------------------------------------------------------- file_list::file_list(const char *FileDir, bool sorttypeA, bool dirlistA) : sorttype(sorttypeA), dirlist(dirlistA) { DirName = strdupplus(FileDir); filter = 0; relist(); } file_list::~file_list() { cleanup(); delete[] DirName; delete[] filter; } void file_list::cleanup() { while (noOfFiles) delete files[--noOfFiles]; delete[] files; while (noOfDirs) delete dirs[--noOfDirs]; delete[] dirs; } void file_list::relist() { if (!myopendir(DirName)) fatalError("There is no Packet Dir!"); noOfFiles = noOfDirs = 0; file_header head("", 0, 0), dirhead("", 0, 0); file_header *filept = &head, *dirpt = &dirhead; const char *fname; mystat st; while ((fname = myreaddir(st)) != 0) if (!filter || searchstr(fname, filter)) if (dirlist || !st.isdir()) if (strcmp(fname, ".")) if (st.readable()) { file_header *trec = new file_header(fname, st.fdate(), st.fsize()); if (st.isdir()) { dirpt->next = trec; dirpt = dirpt->next; noOfDirs++; } else { filept->next = trec; filept = filept->next; noOfFiles++; } } int c; if (noOfDirs > 0) { dirs = new file_header *[noOfDirs]; dirpt = dirhead.next; c = 0; while (dirpt) { dirs[c++] = dirpt; dirpt = dirpt->next; } if (noOfDirs > 1) qsort(dirs, noOfDirs, sizeof(file_header *), fnamecomp); } else dirs = 0; files = new file_header *[noOfFiles]; filept = head.next; c = 0; while (filept) { files[c++] = filept; filept = filept->next; } sort(); } void file_list::sort() { if (noOfFiles > 1) qsort(files, noOfFiles, sizeof(file_header *), sorttype ? ftimecomp : fnamecomp); } void file_list::resort() { sorttype = !sorttype; sort(); } const char *file_list::getDirName() const { return DirName; } int file_list::getNoOfDirs() const { return noOfDirs; } int file_list::getNoOfFiles() const { return noOfFiles; } void file_list::gotoFile(int fileNo) { if (fileNo < (noOfFiles + noOfDirs)) activeFile = fileNo; } char *file_list::changeDir(const char *newpath) { char *newdir = 0; if (dirlist) { if (!newpath) newpath = getName(); mychdir(DirName); if (!mychdir(newpath)) newdir = mygetcwd(); } return newdir; } int file_list::changeName(const char *newname) { mychdir(DirName); return rename(getName(), newname); } file_header *file_list::base() const { return (activeFile < noOfDirs) ? dirs[activeFile] : files[activeFile - noOfDirs]; } file_header *file_list::base(int i) const { return (i < noOfDirs) ? dirs[i] : files[i - noOfDirs]; } const char *file_list::getName() const { return base()->getName(); } time_t file_list::getDate() const { return base()->getDate(); } void file_list::setDate() { base()->setDate(); } off_t file_list::getSize() const { return base()->getSize(); } const char *file_list::getNext(const char *fname) { if (fname) { bool isExt = (*fname == '.'); for (int c = activeFile + 1; c < (noOfFiles + noOfDirs); c++) { const char *q = base(c)->getName(); if (isExt) { size_t len = strlen(q); if (len > 5) { const char *p = q + len - 4; if (!strcasecmp(p, fname)) { activeFile = c; return q; } } } else if (!strncasecmp(q, fname, strlen(fname))) { activeFile = c; return q; } } } return 0; } file_header *file_list::getNextF(const char *fname) { return getNext(fname) ? base() : 0; } const char *file_list::exists(const char *fname) { gotoFile(-1); return getNext(fname); } file_header *file_list::existsF(const char *fname) { return exists(fname) ? base() : 0; } void file_list::addItem(file_header **list, const char *q, int &filecount) { file_header *p; int x; gotoFile(-1); while ((p = getNextF(q)) != 0) { for (x = 0; x < filecount; x++) if (list[x] == p) break; if (x == filecount) { list[x] = p; filecount++; } } } char *file_list::expandName(const char *fname) { return fullpath(DirName, fname); } FILE *file_list::ftryopen(const char *fname) { FILE *f; const char *p = exists(fname); if (p) { char *q = expandName(p); f = fopen(q, "rb"); delete[] q; } else f = 0; return f; } void file_list::kill() { if (activeFile >= noOfDirs) { int i = activeFile - noOfDirs; char *fname = expandName(getName()); remove(fname); delete[] fname; delete files[i]; noOfFiles--; for (; i < noOfFiles; i++) files[i] = files[i + 1]; } } int file_list::nextNumExt(const char *baseName) { int retval = -1; const char *nextName; gotoFile(-1); do { nextName = getNext(baseName); if (nextName) { int newval = getNumExt(nextName); if (newval > retval) retval = newval; } } while (nextName); if (retval == 999) retval = -1; return ++retval; } const char *file_list::getFilter() const { return filter; } void file_list::setFilter(const char *newfilter) { delete[] filter; filter = (newfilter && *newfilter) ? strdupplus(newfilter) : 0; cleanup(); relist(); if (filter && !(noOfDirs + noOfFiles)) { delete[] filter; filter = 0; cleanup(); relist(); } } mmail-0.52/mmail/bw.h000644 000765 000024 00000004323 13065425562 015402 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * Blue Wave Copyright 1996-1997 Toth Istvan Copyright 1998-2017 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #ifndef BW_H #define BW_H #include "pktbase.h" #ifndef BIG_ENDIAN #define BIG_ENDIAN #endif #include "bluewave.h" class bluewave : public pktbase { FILE *ftiFile; INF_HEADER infoHeader; INF_AREA_INFO *areas; MIX_REC *mixRecord; struct perstype { int area, msgnum; } *persNdx; long personal; int *mixID; int noOfMixRecs; int from_to_len; int subject_len; unsigned infoHeaderLen, infoAreainfoLen, mixStructLen, ftiStructLen; FILE *openFile(const char *); void findInfBaseName(); void initInf(); void initMixID(); void getblk(int, long &, long, unsigned char *&, unsigned char *&); void endproc(letter_header &); public: bluewave(mmail *); ~bluewave(); bool hasPersonal(); area_header *getNextArea(); int getNoOfLetters(); letter_header *getNextLetter(); const char *getTear(int); const char *oldFlagsName(); bool readOldFlags(); bool saveOldFlags(); INF_HEADER &getInfHeader(); }; class bwreply : public pktreply { class upl_bw : public upl_base { public: UPL_REC uplRec; char *msgid, *newsgrps, *extsubj; upl_bw(const char * = 0); ~upl_bw(); }; UPL_HEADER *uplHeader; int getAreaFromTag(const char *); bool getRep1(FILE *, upl_bw *, int); void getReplies(FILE *); const char *freeFileName(); void addRep1(FILE *, upl_base *, int); void addHeader(FILE *); void repFileName(); const char *repTemplate(bool); char *nextLine(FILE *); public: bwreply(mmail *, specific_driver *); ~bwreply(); area_header *getNextArea(); letter_header *getNextLetter(); void enterLetter(letter_header &, const char *, long); bool getOffConfig(); bool makeOffConfig(); }; /* To ensure correct operation where alignment padding is used, when reading from or writing to disk, use the _SIZE defines given here rather than "sizeof": */ #define UPI_HEAD_SIZE 55 #define PDQ_REC_SIZE 21 #endif mmail-0.52/mmail/omen.cc000644 000765 000024 00000040640 13432114236 016057 0ustar00wmcbrinestaff000000 000000 /* * MultiMail offline mail reader * OMEN Copyright 1999-2019 William McBrine Distributed under the GNU General Public License, version 3 or later. */ #include "omen.h" #include "compress.h" // Area attributes in SYSTEMxy.BBS enum {OM_WRITE = 1, OM_SYSOP = 2, OM_PRIVATE = 4, OM_PUBLIC = 8, OM_NETMAIL = 0x10, OM_ALIAS = 0x20, OM_ACTIVE = 0x40}; // ----------------------------------------------------------------- // The OMEN methods // ----------------------------------------------------------------- omen::omen(mmail *mmA) : pktbase(mmA) { strcpy(extent, defExtent()); readSystemBBS(); infile = mm->workList->ftryopen("newmsg"); if (!infile) fatalError("Could not open NEWMSGxy.TXT"); buildIndices(); const char x[1][13] = {"bullet"}; listBulletins(x, 1, 0); } omen::~omen() { cleanup(); } area_header *omen::getNextArea() { int cMsgNum = areas[ID].nummsgs; bool x = (areas[ID].num == -1); area_header *tmp = new area_header(mm, ID + 1, areas[ID].numA, areas[ID].name, (x ? "Letters addressed to you" : areas[ID].name), (x ? "OMEN personal" : "OMEN"), areas[ID].attr | (cMsgNum ? ACTIVE : 0), cMsgNum, 0, 35, 72); ID++; return tmp; } // Build indices from NEWMSGxy.TXT void omen::buildIndices() { int x; body = new bodytype *[maxConf]; for (x = 0; x < maxConf; x++) { body[x] = 0; areas[x].nummsgs = 0; } ndx_fake base, *tmpndx = &base; char junk[12]; const char *p; int personal = 0; const char *name = mm->resourceObject->get(UserName); size_t nlen = name ? strlen(name) : 0; bool checkpers = name && nlen; numMsgs = 0; while (!feof(infile)) { long counter = ftell(infile); p = nextLine(); if (!feof(infile) && (*p++ == 1)) { tmpndx->next = new ndx_fake; tmpndx = tmpndx->next; // get area number: sscanf(p, "#%s %d:", junk, &x); x = getXNum(x); tmpndx->confnum = x; tmpndx->pointer = counter; tmpndx->pers = false; if (checkpers) { p = nextLine(); p = strchr(p, '=') + 3; if (!strncasecmp(p, name, nlen)) { tmpndx->pers = true; personal++; } } numMsgs++; areas[x].nummsgs++; // skip rest of header: while (!feof(infile) && (fgetc(infile) != 2)); // find length of text: long tlen = 0; while (!feof(infile) && (fgetc(infile) != 3)) tlen++; tmpndx->length = tlen; } } initBody(base.next, personal); } letter_header *omen::getNextLetter() { char date[20], subject[73], c, priflag = 0, *from, *to, *net; unsigned long pos; int areaID, letterID, x; long msgnum, refnum = 0, junk; net_address na; pos = body[currentArea][currentLetter].pointer; fseek(infile, pos + 1, SEEK_SET); long len = body[currentArea][currentLetter].msgLength; // First header line -- msgnum, date, refnum, priflag: from = nextLine() + 1; to = strchr(from, ' '); *to = '\0'; msgnum = atol(from); from = to + 2; sscanf(from, "%d:", &areaID); while (!((from[0] == ' ') && (from[1] == ' '))) from = strchr(from + 1, ' '); from += 2; to = strchr(from, '('); if (to) { to[-2] = '\0'; sscanf(to, "(%ld/%ld)", &refnum, &junk); to = strchr(to + 1, '('); priflag = to[1]; } strcpy(date, from); // Second line -- to and from: na.zone = 0; from = nextLine(); to = strchr(from, '='); if (to) { to[-1] = '\0'; to += 3; net = strchr(to, '('); if (net) { net[-7] = '\0'; na = ++net; } } // Third line -- subject: // skip "Subj: " for (x = 0; x < 6; x++) fgetc(infile); x = 0; // can't use fgets because line is not LF-terminated do { c = fgetc(infile); subject[x++] = c; } while ((c != 2) && (x != 73)); subject[--x] = '\0'; if (areas[currentArea].num == -1) { areaID = getXNum(areaID); letterID = getYNum(areaID, pos); } else { areaID = currentArea; letterID = currentLetter; } currentLetter++; return new letter_header(mm, subject, to, from, date, 0, refnum, letterID, msgnum, areaID, (priflag == 'P'), len, this, na, !(!(areas[areaID].attr & LATINCHAR))); } void omen::prefirstblk() { // Skip header while (!feof(infile) && (fgetc(infile) != 2)); } // Area and packet init: SYSTEMxy.BBS, BNAMESxy.BBS, INFOxy.BBS void omen::readSystemBBS() { struct ATMP { unsigned char BrdNum, BrdStatus, BrdHighNum, BrdNameLen; char BrdName[16]; } *areatmp; file_list *wl = mm->workList; hasOffConfig = useLatin = 0; // The following info is unavailable in OMEN: const char *defName = mm->resourceObject->get(UserName); LoginName = strdupplus((defName && *defName) ? defName : "(set on upload)"); AliasName = strdupplus("(set on upload)"); // INFOxy.BBS: infile = wl->ftryopen("info"); if (infile) { while (!feof(infile)) { const char *line = nextLine(); if (!strncasecmp(line, "sysop:", 6)) SysOpName = strdupplus(line + 6); else if (!strcasecmp(line, "select:on")) hasOffConfig = OFFCONFIG; else if (!strcasecmp(line, "c_set:iso")) useLatin = LATINCHAR; else if (!strncasecmp(line, "origin:", 7)) DoorProg = strdupplus(line + 7); } fclose(infile); } // SYSTEMxy.BBS, and BNAMESxy.BBS if available: infile = wl->ftryopen("system"); if (infile) { const char *s = wl->getName(); packetBaseName[0] = toupper(s[6]); packetBaseName[1] = toupper(s[7]); packetBaseName[2] = '\0'; maxConf = (wl->getSize() - 41) / sizeof(ATMP) + 1; struct { unsigned char len; char name[41]; } b; if (!fread(&b, 1, 41, infile)) fatalError("Error reading system file"); b.name[b.len] = '\0'; BBSName = strdupplus(b.name); areas = new AREAs[maxConf]; areatmp = new ATMP[maxConf - 1]; if (!fread(areatmp, sizeof(ATMP), maxConf - 1, infile)) fatalError("Error reading system file"); fclose(infile); areas[0].num = -1; strcpy(areas[0].numA, "PERS"); areas[0].name = strdupplus("PERSONAL"); areas[0].attr = PUBLIC | PRIVATE | COLLECTION; infile = wl->ftryopen("bnames"); for (int x = 1; x < maxConf; x++) { areas[x].num = (int) (areatmp[x - 1].BrdHighNum << 8) + areatmp[x - 1].BrdNum; sprintf(areas[x].numA, "%d", areas[x].num); int a = areatmp[x - 1].BrdStatus; areas[x].attr = ((a & OM_ACTIVE) ? ACTIVE : 0) | ((a & OM_PRIVATE) ? PRIVATE : 0) | ((a & OM_PUBLIC) ? PUBLIC : 0) | ((a & OM_NETMAIL) ? NETMAIL : 0) | ((a & OM_ALIAS) ? ALIAS : 0) | ((a & OM_WRITE) ? 0 : READONLY) | hasOffConfig | useLatin; if (infile) // use long area names areas[x].name = strdupplus(strchr(nextLine(), ':') + 1); else { int len = areatmp[x - 1].BrdNameLen; areas[x].name = new char[len + 1]; strncpy(areas[x].name, areatmp[x - 1].BrdName, len); areas[x].name[len] = '\0'; } } if (infile) fclose(infile); delete[] areatmp; } } const char *omen::getExtent() { return extent; } bool omen::isLatin() { return !(!(useLatin & LATINCHAR)); } // ----------------------------------------------------------------- // The OMEN reply methods // ----------------------------------------------------------------- // Letter attributes in HEADERxy.BBS enum {OMR_SAVE = 1, OMR_DEL = 2, OMR_TOGGLE = 4, OMR_MOVE = 8, OMR_PRIVATE = 0x10, OMR_ALIAS = 0x20}; omenrep::upl_omen::upl_omen(const char *name) : pktreply::upl_base(name) { memset(&omen_rec, 0, sizeof(omen_rec)); } // convert OMEN reply packet header to C structures bool omenrep::upl_omen::init(FILE *rep) { if (fread(&omen_rec, sizeof omen_rec, 1, rep) != 1) return false; refnum = (getshort(omen_rec.msghighnumber) << 16) + getshort(omen_rec.msgnumber); privat = !(!(omen_rec.command & OMR_PRIVATE)); na.zone = getshort(omen_rec.destzone); if (na.zone) { na.net = getshort(omen_rec.destnet); na.node = getshort(omen_rec.destnode); na.point = 0; // points aren't supported :-( na.isSet = true; } strncpy(subject, omen_rec.subject, omen_rec.sublen); subject[omen_rec.sublen] = '\0'; strncpy(to, omen_rec.to, omen_rec.tolen); to[omen_rec.tolen] = '\0'; origArea = getshort(&omen_rec.curboard); return true; } // write out OMEN reply packet header void omenrep::upl_omen::output(FILE *rep) { memset(&omen_rec, 0, sizeof omen_rec); omen_rec.command = OMR_SAVE | (privat ? OMR_PRIVATE : 0); int refhigh = refnum >> 16; int reflow = refnum & 0xffff; putshort(omen_rec.msghighnumber, refhigh); putshort(omen_rec.msgnumber, reflow); if (na.isSet) { putshort(omen_rec.destzone, na.zone); putshort(omen_rec.destnet, na.net); putshort(omen_rec.destnode, na.node); } omen_rec.sublen = strlen(subject); memcpy(omen_rec.subject, subject, omen_rec.sublen); omen_rec.tolen = strlen(to); memcpy(omen_rec.to, to, omen_rec.tolen); putshort(&omen_rec.curboard, origArea); fwrite(&omen_rec, sizeof omen_rec, 1, rep); } omenrep::omenrep(mmail *mmA, specific_driver *baseClassA) : pktreply(mmA, baseClassA) { } omenrep::~omenrep() { } // convert one reply to MultiMail's internal format bool omenrep::getRep1(FILE *rep, upl_omen *l, int recnum) { FILE *orgfile, *destfile; char orgname[13]; int c; long count = 0; if (!l->init(rep)) return false; sprintf(orgname, "msg%s%02d.txt", baseClass->getBaseName(), recnum); orgfile = upWorkList->ftryopen(orgname); if (orgfile) { destfile = fopen(l->fname, "wt"); if (destfile) { while ((c = fgetc(orgfile)) != EOF) { if (c != '\r') { fputc(c, destfile); count++; } } fclose(destfile); } fclose(orgfile); upWorkList->kill(); } l->msglen = count; return true; } // convert all replies void omenrep::getReplies(FILE *repFile) { noOfLetters = upWorkList->getSize() / sizeof(omenReplyRec); upl_omen baseUplList, *currUplList = &baseUplList; for (int c = 0; c < noOfLetters; c++) { currUplList->nextRecord = new upl_omen; currUplList = (upl_omen *) currUplList->nextRecord; if (!getRep1(repFile, currUplList, c)) { delete currUplList; break; } } uplListHead = baseUplList.nextRecord; } area_header *omenrep::getNextArea() { return new area_header(mm, 0, "REPLY", "REPLIES", "Letters written by you", "OMEN replies", (COLLECTION | REPLYAREA | ACTIVE | PUBLIC | PRIVATE), noOfLetters, 0, 35, 72); } letter_header *omenrep::getNextLetter() { upl_omen *current = (upl_omen *) uplListCurrent; const char *defName = mm->resourceObject->get(UserName); letter_header *newLetter = new letter_header(mm, current->subject, current->to, (defName && *defName) ? defName : "(set on upload)", "(set on upload)", 0, current->refnum, currentLetter, currentLetter, ((omen *) baseClass)->getXNum(current->origArea) + 1, current->privat, current->msglen, this, current->na, ((omen *) baseClass)->isLatin()); currentLetter++; uplListCurrent = uplListCurrent->nextRecord; return newLetter; } void omenrep::enterLetter(letter_header &newLetter, const char *newLetterFileName, long length) { upl_omen *newList = new upl_omen(newLetterFileName); strncpy(newList->subject, newLetter.getSubject(), 72); strncpy(newList->to, newLetter.getTo(), 35); newList->origArea = atoi(mm->areaList->getShortName()); newList->privat = newLetter.getPrivate(); newList->refnum = newLetter.getReplyTo(); newList->na = newLetter.getNetAddr(); newList->msglen = length; addUpl(newList); } // write out one reply in OMEN format void omenrep::addRep1(FILE *rep, upl_base *node, int recnum) { FILE *orgfile, *destfile; upl_omen *l = (upl_omen *) node; char dest[13]; l->output(rep); sprintf(dest, "MSG%s%02d.TXT", baseClass->getBaseName(), recnum); orgfile = fopen(l->fname, "rt"); if (orgfile) { destfile = fopen(dest, "wb"); if (destfile) { int c, count = 0, lastsp = 0; while ((c = fgetc(orgfile)) != EOF) { count++; if ((count > 80) && lastsp) { fseek(orgfile, lastsp - count, SEEK_CUR); fseek(destfile, lastsp - count, SEEK_CUR); c = '\n'; } if ('\n' == c) { fprintf(destfile, "\r\n"); count = lastsp = 0; } else { fputc(c, destfile); if (' ' == c) lastsp = count; } } fclose(destfile); } fclose(orgfile); } } void omenrep::addHeader(FILE *) { } // set names for reply packet files void omenrep::repFileName() { const char *basename = baseClass->getBaseName(); sprintf(replyPacketName, "return%c%c.%s", tolower(basename[0]), tolower(basename[1]), ((omen *) baseClass)->getExtent()); sprintf(replyInnerName, "HEADER%s.BBS", basename); } // list files to be archived when creating reply packet const char *omenrep::repTemplate(bool offres) { static char buff[30]; sprintf(buff, offres ? "%s *.TXT *.CNF" : "%s *.TXT", replyInnerName); return buff; } // re-read an offline config file bool omenrep::getOffConfig() { FILE *olc; bool status = false; upWorkList = new file_list(mm->resourceObject->get(UpWorkDir)); olc = upWorkList->ftryopen("select"); if (olc) { char line[128]; int areaOMEN, areaNo, current = -1; area_list *al = mm->areaList; int maxareas = al->noOfAreas(); // Like Blue Wave, areas are dropped unless listed in // the config file. Unlike Blue Wave, we can't be certain // of an area's status (SUBKNOWN), hence the extra checks. do { myfgets(line, sizeof line, olc); sscanf(line, "%d", &areaOMEN); areaNo = ((omen *) baseClass)->getXNum(areaOMEN) + 1; for (int c = current + 1; c < maxareas; c++) { al->gotoArea(c); if (c == areaNo) { if (!(al->getType() & ACTIVE)) al->Add(); current = c; break; } else if (al->getType() & ACTIVE) al->Drop(); } } while (!feof(olc)); fclose(olc); upWorkList->kill(); status = true; } delete upWorkList; return status; } bool omenrep::makeOffConfig() { FILE *todoor; char fname[13]; sprintf(fname, "SELECT%s.CNF", baseClass->getBaseName()); todoor = fopen(fname, "wb"); if (!todoor) return false; int oldarea = mm->areaList->getAreaNo(); int maxareas = mm->areaList->noOfAreas(); for (int areaNo = 0; areaNo < maxareas; areaNo++) { mm->areaList->gotoArea(areaNo); unsigned long attrib = mm->areaList->getType(); if (!(attrib & COLLECTION) && (((attrib & ACTIVE) && !(attrib & DROPPED)) || (attrib & ADDED))) fprintf(todoor, "%s\n", mm->areaList->getShortName()); } mm->areaList->gotoArea(oldarea); fclose(todoor); return true; }