Download as txt, pdf, or txt
Download as txt, pdf, or txt
You are on page 1of 42

-----------------------------------------------------------------------

Mercury Daemon Interface for Mercury/32 v2.21 and later


-----------------------------------------------------------------------
Mercury Mail Transport System,
Copyright (c) 1993-99, David Harris, All Rights Reserved.
-----------------------------------------------------------------------

Contents

1: Introduction
1.1 - What is a Daemon?
1.2 - What uses could I have for a Daemon?

2: Technical overview
2.1 - Basic structure of a Daemon
2.2 - What do I need to write a Daemon?
2.3 - Installing and invoking a Daemon

3: Using the Mercury interface


3.1 - Including header files
3.2 - The M_INTERFACE structure
3.3 - Calling functions

4: Advanced topics
4.1 - DAEMON.INI
4.2 - "Resident" Daemons
4.3 - "Global" Daemons
4.4 - Daemon configuration
4.5 - Daemon Domains

5: Function reference
5.1 - General-purpose functions
get_variable
is_local_address
is_group
parse_address
extract_one_address
extract_cqtext
dlist_info
send_notification
get_delivery_path
get_date_and_time
write_profile
module_state

5.2 - Job-control and management functions


ji_scan_first_job
ji_scan_next_job
ji_end_scan
ji_open_job
ji_close_job
ji_rewind_job
ji_dispose_job
ji_process_job
ji_delete_job
ji_abort_job
ji_get_job_info
ji_create_job
ji_add_element
ji_add_data
ji_get_data
ji_get_next_element
ji_set_element_status
ji_set_element_resolvinfo
ji_set_diagnostics
ji_get_diagnostics
ji_increment_time
ji_get_job_by_id
ji_get_job_times

5.3 - Network and user database query functions


get_first_group_member
get_next_group_member
end_group_scan
is_valid_local_user
is_group_member
get_first_user_details
get_next_user_details
get_user_details
end_user_scan
read_pmprop
change_ownership
begin_single_delivery
end_single_delivery
create_object
verify_password
set_password

5.4 - Miscellaneous functions


mercury_command
get_date_string
rfc822_time
rfc821_time
select_printer
print_file

5.5 - File I/O and parsing routines


fm_open_file
fm_open_message
fm_close_message
fm_gets
fm_getc
fm_ungetc
fm_read
fm_getpos
fm_setpos
fm_get_folded_line
fm_find_header
fm_extract_message
parse_header
mime_prep_message
parse_mime
free_mime
fake_imessage
decode_mime_header
encode_mime_header
encode_base64_str
decode_base64_str

5.6 - Message composition routines


om_create_message
om_dispose_message
om_add_field
om_add_attachment
om_write_message
om_send_message
Notes on composing messages

5.7 - Statistics and logging interface


st_register_module
st_unregister_module
st_create_category
st_remove_category
st_set_hcategory
st_set_category
st_get_next_module
st_get_next_category
st_get_category_data
st_export_stats
logstring
logdata

Appendix A: Technical issues

-----------------------------------------------------------------------
1: Introduction
-----------------------------------------------------------------------

1.1 - What is a Daemon?

A Mercury/32 "Daemon" (a term inherited from the unix world) is a program


that provides extended services within the Mercury/32 Mail Transport
System. Daemons are associated with a particular e-mail address, and
when a message is sent to that address, Mercury invokes the Daemon to
process it.

A Daemon has access to extensive internal Mercury services, and can


easily perform complex operations such as address parsing and message
creation using simple function calls.

Daemons are implemented as Windows DLLs that (in their simplest form)
export a single function. There are no restrictions on what they can do,
and they have access to the full range of Windows programming services.

1.2 - What uses could I have for a Daemon?

The most obvious use for a Daemon is to perform custom processing on mail
messages; for instance, you might create a Daemon that accepts orders by
e-mail, checks their validity, verifies a credit card number, then
submits the order to a central database for processing. Another example:
you might create a Daemon that sends faxes: when a message arrives, the
Daemon looks for a fax number on the first line, then calls some other
service on the workstation and asks it to send the remainder of the mail
message as a fax to that number.

The only real limit on the uses a Daemon might have is your imagination
and the extent to which you are prepared to do some Windows programming
to realise what you have imagined.

-----------------------------------------------------------------------
2: Technical Overview
-----------------------------------------------------------------------

2.1 - Basic structure of a Daemon

In its simplest form, a Daemon is a simple 32-bit Windows DLL that


exports a single function, called "daemon". To invoke the Daemon, Mercury
loads the DLL and calls the "daemon" function with a reference to the
mail message (or "job"), a parameter block, and the delivery address that
triggered the call (so the same Daemon can be attached to multiple e-mail
addresses and can distinguish between them as required).

The prototype for the "daemon" function is as follows:

short _export daemon (void *job, M_INTERFACE *m, char *address,


char *parameter);

"job" is a handle to the mail job that triggered the call to the
daemon. Using this handle, you can access the data in the message,
by passing it to functions like "ji_get_data" (see below). You
must not close or delete this job. Note: on entry to your daemon
function, this job will be open - you should not attempt to open
it using "ji_open_job", nor should you close it.

"m" points to an M_INTERFACE structure: this structure contains pointers


to various Mercury internal functions that your Daemon can use to
parse addresses, query information and send mail.

"address" points to the address that triggered this invocation of the


Daemon. This allows a single Daemon to service multiple addresses,
and to adjust its behaviour depending on which address it is
servicing at any given time. You must not alter the contents of
this string in any way.

"parameter" points to any optional data specified in the Daemon's


alias entry (see below for more information on optional
parameters). This parameter will point to an empty string ("") if
there are no parameters. The maximum length of parameter data is
128 characters.

When the "daemon" function returns control to Mercury, Mercury will


unload the Daemon's DLL and delete the job - no further attempt will be
made to deliver it. The return value from the "daemon" function is
currently ignored and must be set to 0. The return value may be
meaningful in future.

2.2 - What do I need to write a Daemon?

A Daemon is simply a standard Windows DLL that exports a single entry


point; as such, you can use most standard tools to create one. The
functions exported to the Daemon via the M_INTERFACE structure all use
the C calling convention and expect C-type parameters (so, strings are
NUL-terminated arrays of characters).

The most logical tools for developing Daemons are Borland C++ v4.5 or
later, Borland C++/Builder, or Microsoft Visual C++. The sample code
provided with this documentation has been developed and tested using
Borland C++ v5.02.

2.3 - Installing and invoking a Daemon

Installing a Daemon so that Mercury can invoke it requires the creation


of an alias in a special form. The reason Daemons can only be invoked
via an alias is to ensure that only approved Daemons are run, for
security reasons. Aliases can be easily created and maintained from
within Mercury/32, using the "Aliases" option on the "Configuration"
menu.

The alias must be of the following form:

daemon_address@host.domain == daemon:path_to_dll[;parameter]

"daemon_address" should be whatever address will invoke the Daemon, while


"path_to_dll" should be the fully-qualified path to the Daemon's DLL
file. If your Daemon needs a parameter passed to it when its "daemon"
function is invoked, you can specify that parameter by placing a
semicolon after the filename, followed by the text you want your Daemon
to receive. The maximum length of the parameter is 128 characters.

Example: you wish to install a Daemon called "cookie"; the DLL file for
the Daemon is "c:\mercury\daemons\cookie.dll", and your domain is
"biscuit.com". You would create the following alias:

cookie@biscuit.com == daemon:c:\mercury\daemons\cookie.dll

If the Daemon's DLL file is found in the same directory as MERCURY.EXE,


then you can omit the path from the DLL's file specification.

Daemons can add aliases to or remove aliases from the system alias file
by themselves: an elegantly-written Daemon would provide a configuration
interface that automates the process of adding the aliases, rather than
relying on the user to do it.

-----------------------------------------------------------------------
3: Using the Mercury interface
-----------------------------------------------------------------------

3.1 - Including header files

In order to use the Mercury Daemon interface, you will need to add the
following line near the top of your C or C++ source file:

#include "daemon.h"

Note that you must include this line *after* you have included the
standard "windows.h" master header file.
3.2 - The M_INTERFACE structure

When your Daemon is invoked, Mercury passes it a large structure called


an M_INTERFACE. This structure contains some static data, and a number of
pointers to internal Mercury functions that your Daemon can call.

"dsize" The "dsize" parameter is the size of the M_INTERFACE


structure in bytes. A Daemon can use this as part of a version-
checking process.

"vmajor" The major version number of the copy of Mercury that is


running. For instance, for Mercury v2.15, this value will be "2".

"vminor" The minor version number of the copy of Mercury that is


running. For instance, for Mercury v2.15, this value will be "15".

3.3 - Calling functions

The remaining variables in the M_INTERFACE structure are pointers to


functions within Mercury that the Daemon can call to access core
services. To call one of these functions, simply use the pointer as if it
were a normal function - so, if you want to call the "get_variable"
function to retrieve the GV_QUEUENAME variable, you would use this line
of code

char *str
str = (char *) (m->get_variable (GV_QUEUENAME));

Note the C cast to a (char *) - this simply suppresses compiler warnings,


because the "get_variable" function always returns its results as a DWORD
value.

-----------------------------------------------------------------------
4: Advanced topics
-----------------------------------------------------------------------

4.1 - DAEMON.INI

For advanced Daemon functions such as Residency and Configuration,


Mercury scans a file called DAEMON.INI for parameters. DAEMON.INI uses a
slightly different syntax from MERCURY.INI, in that it uses "=" as a
separator between keyword and parameter instead of the ":" used in
MERCURY.INI. This design difference is intended to allow installers and
Daemons to use the Windows WritePrivateProfileString and
GetPrivateProfileString API functions to update and access the file.

4.2 - "Resident" Daemons

There may be occasions when a Daemon operates better by remaining in


memory at all times, instead of being loaded and unloaded as required.
Internally, a "Resident" Daemon is no different from a normal Daemon,
except that it can optionally export a "startup" function that Mercury
will call the when the Daemon is loaded.
To install a "Resident" Daemon, add a [Daemons] section to a file called
DAEMON.INI in the same directory as MERCURY.EXE, and include a name and the
full path to your Daemon's DLL on a line in that section.

Example: your Daemon is called "Cookie Daemon" and is located in


C:\MERCURY\DAEMONS\COOKIE.DLL

[Daemons]
Cookie Daemon = c:\mercury\daemons\cookie.dll

Mercury will load the Daemon at startup and will not unload it until
exiting. The Daemon can export a function with the following prototype:

short _export startup (M_INTERFACE *mi, UINT_32 *flags,


char *name, char *param)

If this function is present in the DLL, Mercury will call it with an


Interface block as soon as the DLL is loaded. Note that the Interface
block is not persistent - if you need to store it for later use, you must
allocate your own storage and make a copy of the structure in it.

The "flags" parameter to "startup" is a location where the Daemon can


return certain indicators about itself. At the time of writing this
specification, no flag values are defined - you should write 0 into the
location pointed to by the "flags" pointer.

The "name" parameter is the name string defined for the Daemon in
DAEMON.INI - you will typically use this if you have to create a message
box or error dialog.

The "param" parameter to "startup" is an optional string, presumably


containing configuration or run data for your Daemon. You can specify a
parameter by placing a semicolon and the parameter after the name of the DLL
in the entry in the [Daemons] section; so, using our Cookie Daemon above
as an example, if you wanted to pass the parameter "autocookie" to the
startup function, you would have an entry like this in DAEMON.INI:

[Daemons]
Cookie Daemon = c:\mercury\daemons\cookie.dll;autocookie

If a Daemon has no parameters, an empty string ("") will be passed in


"param". The maximum length of parameter data is 128 characters.

The "daemon" function of a Resident Daemon is called in exactly the same


way as it would be for a normal Daemon - the only difference is that the
Daemon's DLL is not loaded and unloaded as part of the process. A
Resident Daemon will commonly create a copy of the job in a file using
the "fm_extract_message" function, then spin off a thread to process the
file at its leisure.

Resident Daemons may fire threads at will if they wish to perform routine
processing (this is usually done from the "startup" function). All the
functions in the M_INTERFACE parameter block are thread-safe.

Resident Daemons may export a function called "closedown": if they do,


Mercury will call it as part of its shutdown process prior to unloading
the resident Daemon. The function should have this protoype:

short _export closedown (M_INTERFACE *m, DWORD code,


char *name, char *param);

The "code" parameter is reserved for future use and should be ignored
at present. The return from this function is currently unused but must
be set to 0. Only Resident Daemons (including "Global" Daemons) will
have this function called.

The "name" parameter is the name string defined for the Daemon in
DAEMON.INI - you will typically use this if you have to create a message
box or error dialog.

The "param" parameter is the same as was passed to the "startup" function
- see above for more details. If a Daemon has no parameters, an empty
string ("") will be passed in "param". The maximum length of parameter
data is 128 characters.

4.3 - "Global" Daemons

A "Global" Daemon is a specialized form of Resident Daemon that is


passed all messages processed by the Mercury core module. Global Daemons
are called before any other processing is done on the job, and can
instruct Mercury to process, delete or defer a job through their return
value. Examples of uses for Global Daemons include a Daemon that scans
all incoming and outgoing mail for viruses, or a Daemon that makes
archival copies of all incoming and outgoing mail for auditing purposes.

To install a "Global" Daemon, add a [Global Daemons] section to a file


called DAEMON.INI in the same directory as MERCURY.EXE, and include a
name and the full path to your Daemon's DLL on a line in that section.

Example: your Daemon is called "Spam Killer" and is located in


C:\MERCURY\DAEMONS\SPAMKILL.DLL

[Global Daemons]
Spam Killer = c:\mercury\daemons\spamkill.dll

Global Daemons are always Resident Daemons - see the preceding section
for information on this. When a Global Daemon's "daemon" function is
called, the "address" parameter is always set to "[Global]".

A Global Daemon may not delete a job directly, but it can return the
following values to Mercury to control the processing of the job:

0 - Process the job normally


1 - Delete the job without further processing
2 - Defer the job for the system defer time

If a Global Daemon instructs Mercury to delete a job, the job is deleted


at once, without further ado. No error notification is sent, nor is there
any indication other than a comment on the core module console display
that the job has been killed.

4.4 - Daemon configuration

Daemons may wish to add a configuration option to the Mercury


"Configuration" menu to allow the user to change settings or otherwise
control the behaviour of the Daemon. To do this, add a [Daemon_Config]
section to a file called DAEMON.INI in the same directory as MERCURY.EXE,
and include a name and the full path to your Daemon's configuration DLL
on a line in that section. The name is used to create your Daemon's
configuration menu line.

Example: your Daemon's configuration module is called "Cookie Daemon"


and is located in C:\MERCURY\DAEMONS\COOKIECF.DLL

[Daemon_Config]
Cookie Daemon = c:\mercury\daemons\cookiecf.dll

The configuration DLL can be either your Daemon's main processing DLL, or
a subsidiary DLL that only performs configuration functions - the choice
of which method to use will depend on your needs.

Mercury will add your Daemon's name to its Configuration menu, and when
the option is selected, will load your DLL and look for a function with
the following prototype:

short _export configure (M_INTERFACE *mi, char *name,


char *param);

"M_INTERFACE" is a regular parameter block. "name" is the name defined


in the [Daemon_Config] section (this allows the same DLL to service
multiple functions keyed on the name).

The "param" parameter to "config" is an optional string, presumably


containing configuration or run data for your Daemon. You can specify a
parameter by placing a semicolon and the parameter after the name of the
DLL in the entry in the [Daemons] section; so, using our Cookie Daemon
above as an example, if you wanted to pass the parameter "autoconfig" to
the config function, you would have an entry like this in DAEMON.INI:

[Daemon_Config]
Cookie Daemon = c:\mercury\daemons\cookiecf.dll;autoconfig

If a Daemon has no parameters, an empty string ("") will be passed in


"param". The maximum length of parameter data is 128 characters.

If your DLL refers to a Resident Daemon, Mercury will look for the
configuration function in the loaded copy and will not load the DLL
again. If your DLL needs to be loaded to perform configuration, its
"startup" function will not be called.

Both resident and non-resident Daemons can use configuration services if


they wish. What the "configure" function does when called is up to the
individual developer, but it is normal for it to show a dialog allowing
the user to change its configuration options.

4.5 - Daemon Domains

Occasionally, you may wish to have a Daemon that services all mail sent
to a particular subdomain. As an example, imagine a fax server Daemon
that treats the address portion as a fax number: for a server of this
kind, you would want to be able to send any "username" and have it
processed by the Daemon.

To create a "Daemon Domain" of this kind, select the "Mercury Core Module"
configuration option on the "Configuration" menu, and locate the group of
controls near the bottom of the dialog labelled "Domains recognized as
local by this server". Create a new domain entry where the "Domain name"
portion is the domain name to be handled by your Daemon, and the
"Host/server" portion is "daemon:path_to_dll". Once this domain entry has
been created, all mail sent to any user at the domain you have defined
will result in the Daemon being invoked.

Example: you have a fax server Daemon, C:\MERCURY\FAX.DLL; you want


all mail addressed to "fax.biscuit.com" to be passed to this Daemon.
You would create the following domain definition:

Host/Server Domain name


daemon:c:\mercury\fax.dll fax.biscuit.com

Note that this kind of operation will almost always require special name
server entries called "MX Entries" to advertise the domain name - contact
your service provider for more details on creating MX entries.

-----------------------------------------------------------------------
5: Function reference
-----------------------------------------------------------------------

5.1 - General-purpose functions

-------------------------------
DWORD get_variable (int index);
-------------------------------

Returns the value of an internal Mercury variable. The following


values can be passed for "index":

GV_QUEUENAME (1) (Returns "char *")


The name of the directory where Mercury's queue manager is
looking for jobs. The return value is a (char *) pointing to
a path, which may be in either drive letter or UNC format.

GV_SMTPQUEUENAME (2) (Returns "char *")


The name of the directory where Mercury's queue manager is
looking for outgoing mail jobs. This variable is not meaningful
under current versions of Mercury and should not be used.

GV_MYNAME (3) (Returns "char *")


The Internet name for this copy of Mercury (the domain portion
it will use when forming addresses).

GV_TTYFONT (4) (Returns "HFONT")


A handle to the font Mercury is currently using to draw text
in console listings.

GV_MAISERNAME (5) (Returns "char *")


The name of the Mercury mail server (usually "MAISER")

GV_FRAMEWINDOW (6) (Returns "HWND")


The handle to the Mercury Frame window: Daemons can use this
value to send messages to the Mercury core process.

GV_SYSFONT (7) (Returns "HFONT")


A handle to the font Mercury is using by default to draw
controls and general dialog text.

GV_BASEDIR (8) (Returns "char *")


Returns the directory from which MERCURY.EXE was run.

GV_SCRATCHDIR (9) (Returns "char *")


Returns a temporary working directory if one has been defined
in Mercury. If no temporary directory has been defined, this
value returns the same value as "GV_BASEDIR".

Daemons must treat all returned values as read-only and must not
attempt to modify them.

--------------------------------------------------------------
int is_local_address (char *address, char *uic, char *server);
--------------------------------------------------------------

Determine whether or not the address in "address" refers to a local


account (i.e, one to which Mercury can complete final delivery).

"uic" Receives the local username matching the address when


the function is successful. Allocate at least 256
characters for this string.

"server" Receives network-specific information associated with the


local user when the function is successful. This value may
have to be passed to other functions. Allocate at least
256 characters for this string.

Returns: 2 if the address is non-local but served by an alias


1 if the address is local;
0 if the address is not local;
-1 on error (local domain but no such user)

This function resolves aliases and synoyms, and performs any


necessary network lookups to validate the address.

---------------------------------------------------------------
int is_group (char *address, char *host, char *gname);
---------------------------------------------------------------

Determine whether the address contained in "address" refers to a valid


group to which Mercury can perform delivery.

"address" The simplified form of the address, with no domain portion


(so, pass "everyone", not "everyone@host.domain").

"host" Receives network-specific host information when the


function is successful. You may need to pass this value
to other functions.

"gname" Receives the name of the group on success.

Returns 1 if the address refers to a known group


0 if the address does not refer to a known group
----------------------------------------------------------
int parse_address (char *target, char *source, int limit);
----------------------------------------------------------

Reduce an RFC822 address to its simplest form, by discarding any


textual components it contains.

Example
"David Harris" (Pegasus Mail Author) <david@pmail.gen.nz>
would be reduced by this function to
david@pmail.gen.nz

"source" points to the address that is to be reduced. This string


is not changed as part of the process.

"target" points to the location where the reduced address should be


written. You may pass the same value as "source" if you
want to overwrite the address with the reduced form.

"limit" the maximum length of the resulting string. You should


pass the allocated size of "target" less one.

Returns 1 if the reduction was successful


0 on error (the address was malformed in some way)

---------------------------------------------------------------
int extract_one_address (char *dest, char *source, int offset);
---------------------------------------------------------------

Given a string potentially containing multiple addresses separated


by commas, extract the next address from the string into "dest".
The first time you call this function, pass zero for "offset"; for
each subsequent call, pass the value returned by the previous call
to the function, until it returns 0.

"dest" receives the next address from the string

"source" The string containing comma-separated addresses

"offset" 0 on the first call, the return from the previous call
to the function on subsequent calls.

Returns: > 0 on success ("dest" contains a valid address)


0 when no more addresses exist ("dest" is invalid)

--------------------------------------------------------
void extract_cqtext (char *dest, char *source, int len);
--------------------------------------------------------

Given a string containing a single address, strip out the address


part and return only the extra textual information.

Example:
"David Harris" <david@pmail.gen.nz>
will be reduced by this function to
David Harris
"dest" receives the reduced textual form

"source" points to the address to reduce

"len" the maximum number of characters to write into "dest".

Returns: Nothing.

------------------------------------------------------
int dlist_info (DLIST *dlist, char *lname, int number,
char *address, char *errbuf, LIST *modlist);
------------------------------------------------------

Returns information about the distribution list named in "lname".


For information on the DLIST data structure, see "daemon.h".

"dlist" points to a DLIST data structure into which the data


for the list should be written.

"lname" points to the simple name of the list (no domain part).

"number" if "lname" is NULL, then this variable is used to


determine which list to retrieve. Calling functions
can iterate through all the lists on the server by
setting "lname" to NULL then repeatedly incrementing
this variable in successive calls.

"address" if non-NULL, contains a simplified address form which


should be compared with the list of moderators for the
list. If the address matches any moderator in the list,
the "matched" field of the DLIST structure will be set
to 1.

"errbuf" receives a textual error message if the function fails.


Allocate at least 128 characters for this variable.

"modlist" points to a LIST data structure that receives a list


of all the moderators defined for the list. This value
can be NULL.

Returns: 0 on success
-1 on failure

Note the non-standard return value convention for this function.

-------------------------------------------------------------------
void send_notification (char *username, char *host, char *message);
-------------------------------------------------------------------

Send a short, one-line message directly to the given user.

"username" a username, as returned by "is_local_user"

"host" network-specific host information as returned by


"is_local_user".
"message" A message of up to 65 characters to send to the user

Returns Nothing

This function only works if a network enabler supporting broadcast


messages (such as the NetWare 3.x and 4.x enablers) has been installed
and is currently loaded in Mercury/32.

---------------------------------------------------------------
int get_delivery_path (char *path, char *username, char *host);
---------------------------------------------------------------

Retrieve the delivery path for the specified user/host combination.


"user" and "host" should be the values returned by "is_local_address".

"path" Receives the directory in which files should be created


for final delivery to the specified user. The string will
not end in "\" or "/". Allocate at least 256 characters
for this string

Returns: 1 if "path" contains a valid delivery path


0 on error ("path" is invalid)

---------------------------------
int get_date_and_time (BYTE *tm);
---------------------------------

Get the current date and time, querying the file server for the
information if possible. The information is written into a seven
byte structure laid out as follows:

Byte 0 - Years since 1900 (i.e, 2000 == 100)


Byte 1 - Month (ie, January == 1)
Byte 2 - Day (1 .. 31)
Byte 3 - Hour (0 - 24)
Byte 4 - Minute (0 - 59)
Byte 5 - Second (0 - 60)
Byte 6 - Day of week (Sunday == 0)

This function will obtain the date and time from the local workstation
if no server connection is available to provide it.

Returns: 1 if the time and date were correctly retrieved


0 on system error.

-----------------------------------------------
int write_profile (char *section, char *fname);
-----------------------------------------------

Write a profile section into MERCURY.INI. "section" should contain


the name of the section to write, enclosed in square brackets (so,
if you want to write a section called "CookieServ", you would pass
"[CookieServ]" as the "section" parameter). "fname" should be a full
path to a file containing the data to write into the section. The
data is written exactly as it appears and may use any syntax you
wish, although it is conventional to use "keyword : parameter".
The data in the file is expected to be line-oriented, and no single
line may exceed 254 characters.

Returns: 1 on success
0 on failure ("fname" is not accessible)

-----------------------------------------------------------
int module_state (char *modname, int set_value, int state);
-----------------------------------------------------------

Query or set the state for a protocol module. "modname" should point
to a string identifying the module name of the module whose state is
to be queried or set. If "set_value" is non-zero, then this function
call will attempt to set the module's state to the value contained
in "state". If "set_value" is zero, then "state" is ignored, and the
module's current state is returned.

A module's state is a bitmap where the low sixteen bits are reserved
and the high sixteen bits can be defined by individual modules for
whatever purpose they wish. The reserved bits have the following
possible values:

Bit 0: Online state, 1 = online, 0 = offline.


Bit 1: ("get" only) 1 = busy, 0 = idle.

Returns: -1: No such module


-2: Module does not support this operation
< -255: Module-specific error return value
>= 0: Previous state (success)

5.2 - Job control and interface functions

The functions in this section allow you to create, scan and manipulate
mail jobs in the Mercury queue.

----------------------------------------------------------
void *ji_scan_first_job (int type, int mode, void **data);
void * ji_scan_next_job (void **data);
void ji_end_scan (void **data);
----------------------------------------------------------

Routines for enumerating jobs in the queue. First, call


"ji_scan_first_job" and if it returns a non-NULL value, call
"ji_scan_next_job" until NULL is returned. A non-NULL return value
is the job handle for the job that is found - use this handle in
calls to other job management functions.

"type" Can have any one of the following values:


JT_GENERAL Messages for local delivery
JT_OUTGOING Messages scheduled for off-server delivery
JT_ANY Any type of message

"mode" Used to select jobs in particular states - choose from:


JE_READY Messages ready for processing
JE_COMPLETED Messages where all processing is finished
JE_FAILED Messages with delivery failures pending
JE_ANY Messages in any state

The "data" parameter is used by these routines to store state


information, and should be passed as part of each call. If
"ji_scan_first_job" returns a non-NULL value, then you *must* call
"ji_end_scan" when you have finished scanning with jobs, even if you
do not completely iterate through all the jobs in the queue.

None of these routines actually opens the job handle - you must call
ji_open_job, passing the job handle, if you need to access the data in
the job or modify its settings.

Returns: NULL if no further jobs exist


non-NULL (the job handle) on success

----------------------------------
int ji_open_job (void *jobhandle);
----------------------------------

Open a job. You must call this function before calling any function
that accesses the job's data or changes the job's settings. The job
is opened and locked, which will prevent it from being processed
during normal polling operations, and will prevent other processes
from opening it.

The job handle passed to this call should be a value returned by


"ji_scan_first_job", "ji_scan_next_job" or "ji_create_job".

Returns: 1 if the job was opened successfully.


0 on failure.

----------------------------------
int ji_close_job (void *jobhandle);
----------------------------------

Close a job opened with "ji_open_job". The job is unlocked and


released for normal processing. Other processes may access the job
once this routine has been called.

Calling this routine forces the job's overall status to be updated


(so, a job may change from JE_READY to JE_COMPLETED after this
function has been called).

Returns: 1 on success
0 on failure (job invalid).

------------------------------------------------
void ji_rewind_job (void *jobhandle, int flags);
------------------------------------------------

"Rewind" a job - reposition its file pointer at 0. "flags" can have


either of two values, "JR_CONTROL", in which case the control
information for the job is reset to the first address element, or
"JR_DATA", in which case the data associated with the file is set
for reading from the start.

Example: say you are scanning through the addresses to which a message
should be sent using "ji_get_next_element", and you strike a
condition that means that you need to start processing from the
first address element in the message again, you would call this
function with the "JR_CONTROL" parameter.

-------------------------------------
int ji_dispose_job (void *jobhandle);
-------------------------------------

Call this function when you have finished working with a job handle
returned by ji_scan_first_job, ji_scan_next_job, or ji_create_job.
This routine deallocates memory associated with the job - it does not
delete or in any other manner change the actual disk structures
associated with the job. This function must be called for every
non-NULL return value from the functions listed above.

Returns: 1 on success
0 on failure (job invalid).

-------------------------------------
int ji_process_job (void *jobhandle);
-------------------------------------

Indicates to the job manager that a formal attempt to process the


job has begun. The retry count for the job is incremented and (if
applicable) the last retry time is updated. Daemons should seldom
if ever have any cause to call this function.

Returns: 1 on success
0 on failure (invalid or closed job handle)

------------------------------------
int ji_delete_job (void *jobhandle);
------------------------------------

Permanently remove a job from the queue. This routine deletes all
files associated with the job (including diagnostic files) and then
discards the job handle. It is invalid to access the job handle after
it has been passed to this routine.

You should not call ji_dispose_job for handles passed to this routine.

Returns: 1 on success
0 on failure (invalid job handle)

----------------------------------------------
int ji_abort_job (void *jobhandle, int fatal);
----------------------------------------------

Abort processing a job. If the job was created using ji_create_job,


then it is discarded, otherwise its retry count is incremented by the
system default retry increment, the job is closed, and the job handle
is invalidated. It is an error to use or access the job handle after
calling this routine.

If "fatal" is non-zero, then the job is unilaterally failed (normally,


a job is only marked as failed if any of its addresses cannot be
delivered - setting this flag will fail even a job where all the
recipients have successfully received the mail).

You should not call ji_dispose_job for handles passed to this routine.

Returns: 1 on success
0 on failure (job handle was invalid)

---------------------------------------------------
int ji_get_job_info (void *jobhandle, JOBINFO *ji);
---------------------------------------------------

Get information about the specified job. The fields in the JOBINFO
structure are filled out with information from the current element
in the message's control stream.

typedef struct
{
int structlen; // Size of this structure
char jobstatus; // JS_READY, JS_COMPLETE or JS_FAILED
long jobflags; // Mercury internal processing flags
char status; // As jobstatus but for current element only
char *from; // Envelope address for message (read-only)
char *to; // Recipient address for current element
long dsize; // Total RFC822 size of the message
long rsize; // Number of bytes read since last rewind
int total_rcpts; // Total recipients for message
int total_failures; // Total failed recipients to date
int total_retries; // Total recipients marked for retry
char ip1 [16]; // Primary IP address for current element
char ip2 [16]; // Secondary IP address for " " "
char ip3 [16]; // Third-choice IP address for " " "
char ip4 [16]; // Fourth-choice IP address for " " "
char jobid [20]; // Unique identifier for message
} JOBINFO;

Returns: 1 on success
0 on failure

----------------------------------------------------------------------
void *ji_create_job (int type, char *from, unsigned char *start_time);
----------------------------------------------------------------------

Create a job in the mail queue.

"type" can be either JT_GENERAL, or JT_OUTGOING. You should usually


use "JT_GENERAL" unless you are explicitly creating a job which is
to be processed directly by the MercuryC SMTP client.

"from" is the address Mercury should write in the envelope of the


message.
"start_time" is a date and time in the 7-byte format described under
"get_date_and_time", which specifies the earliest time at which the
job can be processed. If NULL, the job can be processed as soon as
it has been closed.

The job handle returned by this function can be passed to any other
job management function in order to manipulate the queue job.

Returns: non-NULL on success (the job handle)


NULL on failure.

----------------------------------------------------
int ji_add_element (void *jobhandle, char *address);
----------------------------------------------------

Add an address to a job created using "ji_create_job". "address"


should be the full e-mail address of a single recipient for the
message. You should call this function for each address to which the
message should be sent.

Returns: 1 on success
0 on failure (invalid job handle)

----------------------------------------------
int ji_add_data (void *jobhandle, char *data);
----------------------------------------------

Add data for the message body to a job created using "ji_create_job".
You should call this function repeatedly for the headers and text of
the message until all the data has been written. This function does
not modify the data in any way - it is up to you to ensure that line
endings are correct (CR/LF pairs) and that the data conforms to the
standards for Internet mail. There is a clear expectation that the
data for the message will be passed to this function a line at a time.

Note: When you use this function to build an outgoing mail message,
you are effectively building the message exactly as it will be sent -
it is up to you to add all the necessary RFC822 headers, the blank
line that separates them from the message body, and the message body
itself.

Returns: 1 on success
0 on failure (invalid job handle)

--------------------------------------------------------------
char *ji_get_data (void *jobhandle, char *buffer, int buflen);
--------------------------------------------------------------

Get the next line of data from the current job. "buffer" should be
allocated to be at least 1024 characters in order to comply with
RFC821, but any length will be honoured. The line returned will
usually end in a CRLF pair, but may not if the buffer was too
small to accommodate the entire line.

Returns: "buffer" on success


NULL on failure (end of data)
-----------------------------------------------------
char *ji_get_next_element (void *jobhandle, int type,
JOBINFO *jobinfo);
-----------------------------------------------------

Get the next element from the job. Information about the element
is returned in "jobinfo". "type" can be JE_ANY for any type of entry,
JE_READY for the next entry marked as "ready for processing", or
JE_FAILED for the next entry marked as failed.

Returns: The e-mail address associated with the entry on success


NULL on failure, or no more elements.

-----------------------------------------------------
int ji_set_element_status (void *jobhandle, int mode,
unsigned char *date);
-----------------------------------------------------

Set the status fields of the current job element. "mode" can be one
of the following values:

JS_COMPLETED - "date" is ignored


JS_FAILED - "date" is ignored
JS_RETRY - "date" is used for requeuing if non-NULL
JS_PENDING - "date" is ignored

"JS_PENDING" status indicates a status that should be reset to


"JS_RETRY" if a job is aborted: it is set by the SMTP client
module when the remote SMTP server has accepted a RCPT TO: line
for the address, and is needed so that the job can be "rolled
back" if there's a subsequent network error.

Returns: 1 on success
0 on failure (invalid job handle or element)

----------------------------------------------------------------------
int ji_set_element_resolvinfo (void *jobhandle, char *ip1, char *ip2);
----------------------------------------------------------------------

Set the resolver information fields of the current job element. "ip1"
and "ip2" point to ASCII versions of IP addresses for servers that
should be used to deliver the element within the job. Daemons should
never need to use this function.

Returns: 1 on success
0 on failure (invalid job handle or element)

------------------------------------------------------------------
int ji_set_diagnostics (void *jobhandle, int forwhat, char *text);
------------------------------------------------------------------

Set the diagnostic field of the current job element to the text
contained in "text". It is legal (and probably even essential)
to call this function repeatedly; each time it is called, a line
is added to the diagnostic stream for the entry.

"forwhat" can be JD_ELEMENT to set information specific to


the current element, or JD_JOB to set diagnostics pertaining
to the job in general.

Returns: 1 on success
0 on failure (bad job handle or element record)

-------------------------------------------------------------------
int ji_get_diagnostics (void *jobhandle, int forwhat, char *fname);
-------------------------------------------------------------------

Get the diagnostics for the job or current job element. "fname"
should point to a buffer at least 128 characters long where the
diagnostic information should be placed: the buffer will be filled
in with a filename by this routine. If this routine returns 2,
it is the calling routine's responsibility to delete the file in
"fname" when it is finished with the data; if this routine returns
1, then the calling routine must NOT delete the file in "fname".
If 0 is returned, then no diagnostic information is available
for the element.

"forwhat" can be JD_ELEMENT to retrieve information specific to


the current element, or JD_JOB to retrieve diagnostics pertaining
to the job in general.

Returns: 2 "fname" is valid - delete it when finished


1 "fname" is valid - do NOT delete it when finished
0 "fname" is invalid - no diagnostic information available.

------------------------------------------------------------------
void ji_increment_time (unsigned char *tm, unsigned int add_secs);
------------------------------------------------------------------

Add "add_secs" seconds to the date and time specified in "tm",


which should be in the format described under "get_date_and_time".
You can subtract seconds from a time by passing a negative value for
"add_secs" to this function.

----------------------------------
void *ji_get_job_by_id (char *id);
----------------------------------

Given the ID number of a job (the "jobid" element of a JOBSTRUCT


structure), return a usable handle for that job if it still exists in
the queue. This function allows you to re-open a job by reference
without holding on to its jobhandle between calls. If this function is
successful, the job handle it returns can be used in any other ji_*
function expecting a job handle. The job handle is not open on return.

Returns: Non-NULL on success


NULL on failure (job no longer exists, or is locked).

---------------------------------------------------------------------
int ji_get_job_times (void *jobhandle, char *submitted, char *ready);
---------------------------------------------------------------------

Get the time of original submission for a job and the time it is
scheduled for processing. "submitted" and "ready" are the submission
and ready times respectively, in the same format as returned by
get_date_and_time. You can pass NULL for either of these parameters if
you do not require the value. The ready time for a job can be
inspected at any time, but the submission time for a job can only be
retrieved if the job has been successfully opened using ji_open_job.

Returns: Non-zero on success


0 on failure (invalid job handle)

5.3 - Network and user database query functions

------------------------------------------------------------------
int get_first_group_member (char *group, char *host, char *member,
int mlen, void **data);
int get_next_group_member (char *member, int mlen, void **data);
int end_group_scan (void **data);
------------------------------------------------------------------

Routines for enumerating the members of a group. First, call


"get_first_group_member", passing the name of the group in "group".
The host and username information will be returned in "host" and
"member" respectively, with "mlen" controlling the maximum nunber of
characters written into the member name (allocate at least 128
characters for both "host" and "member"). If "get_first_group_member"
returns non-zero, call "get_next_group_member" repeatedly to iterate
through the group's membership. When you have finished scanning
through the membership, call "end_group_scan". You should pass the
same address for "data" to all calls - these functions store state
information there.

If "get_first_group_member" returns a non-zero value, then you must


call "end_group_scan" when you have finished scanning, whether or
not you actually reach the end of the group's membership.

--------------------------------------------------------------------
int is_valid_local_user (char *address, char *username, char *host);
--------------------------------------------------------------------

Check whether "username" identifies a valid local user. This function


differs from "is_local_address" in that it does not resolve aliases
or synonyms, and will only return a success result if the address
represents a genuine, existing local user. Note that the "address"
parameter must have no domain portion - this routine expects a user
name portion only.

As with "is_local_address", this routine may modify the "username"


and "host" parameters - allocate at least 256 characters for each
of these parameters.

Returns 1 if the address is a valid local user


0 if the address does not represent a known user
------------------------------------------------------------------
int is_group_member (char *host, char *username, char *groupname);
------------------------------------------------------------------

Determine whether or not a user is a member of a specified group.

Returns: 1: the group exists and the user is a member


0: the group exists and the user is not a member
-1: the group does not exist

--------------------------------------------------------------------
int get_first_user_details (char *host, char *match, char *username,
int ulen, char *address, int alen, char *fullname, int flen,
void **data);
int get_next_user_details (char *username, int ulen, char *address,
int alen, char *fullname, int flen, void **data);
int end_user_scan (void **data);
--------------------------------------------------------------------

Enumerate the local users on the current system.

"match" - currently ignored; pass an empty string.


"username" - receives at most "ulen" bytes of the user's
login name
"address" - receives at most "alen" bytes of the user's
e-mail address.
"fullname" - receives at most "flen" bytes of the user's
full (personal) name.

If "get_first_user_details" returns a non-NULL value, then you must


call "end_user_scan" when you have finished scanning users, whether
or not you actually reach the end of the user list.

Returns: 1 if valid user details were found and returned


0 on error, or if no further users exist.

--------------------------------------------------------------------
int get_user_details (char *host, char *match, char *username,
int ulen, char *address, int alen, char *fullname, int flen);
--------------------------------------------------------------------

Return information about a single specific user, whose username is


contained in "match". All other parameters are the same as those in
"get_first_user_details".

Returns: 1 on success
0 on failure (no such user)

---------------------------------------------------------
void read_pmprop (char *userid, char *server, PMPROP *p);
---------------------------------------------------------

Get extended features settings for the specified user. "Extended


features" is a Pegasus Mail term and covers things like automatic
mail forwarding and address synonyms. Any given user will not
necessarily have an extended features record.

The principal field of the "PMPROP" structure from Mercury's


point of view is the "gw_auto_forward", which contains the address
to which all mail delivered by Mercury should be forwarded.

---------------------------------------------------------------
int change_ownership (char *fname, char *host, char *newowner);
---------------------------------------------------------------

Change the network ownership of the specified file. This function


is only meaningful when Mercury is using a network interface
module that supports the concept of changing the ownership of a
file.

Returns: 1 if the file's ownership was successfully changed


0 on error, or if the function is not supported.

-----------------------------------------------------------------
int begin_single_delivery (char *uic, char *server, void **data);
void end_single_delivery (void **data)
-----------------------------------------------------------------

These functions should not be called by Daemons; they are only


meaningful to true Mercury protocol modules.

-----------------------------------------------------------
INT_32 create_object (char *objectname, INT_32 objecttype,
char *id, INT_32 flags);
-----------------------------------------------------------

Create a new object in the system. "objectname" can be any valid


filename for the system in use, but must not contain spaces, and
should be 8 characters or less if compatibility with 16-bit systems
is required. "id" is the object's default identification (or personal
name) string.

"objecttype" can be OBJ_USER, or OBJ_ADMINISTRATOR

"flags" is a bitmap containing user creation control flags. The


following values are possible:
1 Copy any default messages into the new user's mailbox

This routine will fail if a user already exists with the name "username",
or if the user's mail directory cannot be created.

This routine is only available in Standalone Mercury systems - it is


not implemented in Network-level plugins.

Returns: 1 on success
0 on failure

----------------------------------------------------------------
int verify_password (char *username, char *host, char *password,
int select);
----------------------------------------------------------------

Given a username and host as returned by "is_local_user", verify


a password for that user.

"password" The password to verify. This may or may not be case-


sensitive, depending on the underlying operating system.

"select" indicates the type of password to verify: this variable


can have the following values

SYSTEM_PASSWORD (1) The user's login password


APOP_SECRET (2) The user's APOP shared secret

Returns: 1 if the password appears to be valid


0 if the password is invalid

----------------------------------------------------------------
INT_32 set_password (char *username, char *host, char *password,
INT_32 select);
----------------------------------------------------------------

Set the user's System password or APOP secret. "username" and "host"
should be values returned by a previous call to either
"is_valid_local_user" or "get_*_user_details". "select" should be
either APOP_SECRET or SYSTEM_PASSWORD. This function may not be
available on all systems.

Returns: 1 on success
0 on failure

5.4 - Miscellaneous functions

-----------------------------------------------------------------
DWORD mercury_command (DWORD selector, DWORD parm1, DWORD parm2);
-----------------------------------------------------------------

This function provides an extended interface into Mercury's core


services. In general, less-frequently-used functions, or functions
with simple parameter lists may be exposed via this mechanism.

The function operates much like the Windows "SendMessage" function:


you indicate which function is required in the "selector" parameter,
and the meanings of the "parm1" and "parm2" parameters will depend
on the selector. The return from this function also depends on the
selector value. Mercury does not return from this function until
the command has completed.

At the time of writing, the following selectors and their parameter


lists are defined; others will be added over time:

Selector: GET_MODULE_INTERFACE
Parm1: (char *) Pointer to name of module to locate
Parm2: Unused, must be 0
Returns: Protocol module command interface function pointer
Comments: This message is only for use by Mercury protocol
modules and should not be used by Daemons.

Selector: ADD_ALIAS
Parm1: (char *) Pointer to alias to add
Parm2: (char *) Pointer to address to associate with alias
Returns: Non-zero on success, 0 on failure
Comments: Add an alias to the system alias file. This function
will fail if an alias already exists using the string
supplied. Any type of alias may be added, including
Daemon:, File: and TFile: aliases.

Selector: DELETE_ALIAS
Parm1: (char *) Pointer to alias to delete
Parm2: Unused, must be 0
Returns: Non-zero on success, 0 on failure
Comments: Deletes the specified alias from the system alias file.
A Daemon that adds its own aliases for the addresses
it services will typically call this function before
adding its alias.

Selector: RESOLVE_ALIAS
Parm1: (char *) Pointer to alias to resolve
Parm2: (char *) Pointer to buffer to place address
Returns: Non-zero on match, 0 on no match or failure
Comments: Attempts to resolve the specified alias to its real-
world address form. You must allocate at least 180
characters to the buffer (parm2) parameter.

Selector: RESOLVE_SYNONYM
Parm1: (char *) Pointer to synonym to resolve
Parm2: (char *) Pointer to buffer to place address
Returns: Non-zero on match, 0 on no match or failure
Comments: Attempts to resolve the specified string as a
synonym (two-way alias). For more information on
synonyms, see the Mercury documentation. You must
allocate at least 180 characters for the buffer
(parm2) parameter.

Selector: QUEUE_STATE
Parm1: 0 to query current state, 1 to set state
Parm2: 1 to pause processing, 0 to enable it
Returns: The state of queue processing prior to the call
Comments: Allows you to pause and unpause the core module's
processing of the mail queue.

----------------------------------------------------------------
char *get_date_string (int selector, char *buf, BYTE *datetime);
----------------------------------------------------------------

Return a properly-formatted date string.

"selector" - either RFC_821_TIME or RFC_822_TIME


"buf" - where the formatted string should be written
"datetime" - NULL for the current time, or a 7-byte time structure
RFC821 time format uses a 2-digit year and is only used in timestamp
headings in "Received" headers for messages.

RFC822 time is conventional RFC822/RFC1123 date format, using a


4-digit year.

Both formats include a time zone if one is defined on the system.

"buf" must be at least 40 characters in length.

See "get_date_and_time" for a description of the format of "datetime"

Returns: buf

---------------------------------
char *rfc822_time (char *buffer);
char *rfc821_time (char *buffer);
---------------------------------

Provided for backwards-compatibility only. Use "get_date_string"


instead of these functions.

------------------------------------------------------
INT_32 select_printer (char *device_name, int maxlen);
------------------------------------------------------

Bring up a dialog that prompts the user to select a printer from


those available on the current system. Both local and network
printers are listed. The printer name is returned in "device_name"
if the user clicks "OK". The name returned is suitable for use in
the "print_file" function (see below).

Returns: 1 if OK was clicked


0 if Cancel was clicked

---------------------------------------------------------------------
INT_32 (* PRINT_FILE) (char *fname, char *printername, UINT_32 flags,
INT_32 lrmargin, INT_32 tbmargin, char *title, char *username,
char *fontname, INT_32 fontsize);
---------------------------------------------------------------------

Print a file or mail message. This function provides a simple way of


printing textual data, and has useful formatting options designed to
handle normal textual mail messages. Only textual data can be printed
- this routine will not print HTML, RTF or other formatted data types.

"fname" is the full pathname of the file to print

"printername" is the name of the printer on which the file is to be


printed; this can be any local or network printer. You can use the
"select_printer" function (see above) to allow the user to choose
a printer. If you pass NULL for this parameter, Mercury will use
the system's default printer.

"flags" is a bitmap of flags controlling the way the file will be


printed; the following values are available:
PRT_MESSAGE Print as an RFC822 mail message
PRT_REFORMAT Reformat long lines when printing
PRT_TIDY Print only "important" headers
PRT_FOOTER Print a footer on each page
PRT_NOHEADERS Print no message headers
PRT_FIRSTONLY Print only first line of headers
PRT_ITALICS Print quoted text in italics

"lrmargin" is the margin in millmetres (mm, 25.4mm == 1 inch) to


allow at the left and right sides of the printed page.

"tbmargin" is the margin in millimetres to allow at the top and


bottom of the printed page.

"title" is an optional string Mercury will use to identify the


document in the Windows print manager queue; the title is not
itself printed anywhere on the document. This paramter can be
NULL if not required.

"username" is the username Mercury should print in the footer on


each page if that option is selected. This parameter can be NULL
if it is not required.

"fontname" is the name of the font to use when printing. If this


parameter is NULL or zero-length, the default font and size will
be used.

"fontsize" is the size in points of the printer font. This


parameter is ignored if "fontname" is NULL or zero-length.

Returns: 1 on successful printing


0 on printing error.

5.5 - File I/O and parsing routines

The functions in this section allow Daemons to perform message and MIME
parsing on mail messages. The first group of functions allow the
manipulation of files in a portable manner (since open file handles
cannot be passed between DLLs and programs), while the second group of
functions perform parsing and analysis of complex mail messages.

------------------------------------------------
INT_32 fm_open_file (char *path, UINT_32 flags);
------------------------------------------------

Open any file. The file is opened in binary mode, so line endings
are returned as CRLF, not as simply LFs. The handle returned by this
function can be passed to any of the parsing or file manipulation
functions that expect an internal file handle.

"flags" can have any of the following values:

FF_NO_LINE_ENDINGS - tells "fm_gets" to remove CRLF terminators


from each line it reads from the file
Returns: > 0 (the file reference) on success
0 on failure

-----------------------------------------------------
INT_32 fm_open_message (IMESSAGE *im, UINT_32 flags);
-----------------------------------------------------

Provided for backwards compatibility with Pegasus Mail. Daemons should


use "fm_open_file" instead of calling this function.

---------------------------------
int fm_close_message (INT_32 id);
---------------------------------

Close a file opened using "fm_open_file" or "fm_open_message".

Returns: 1 on success
0 on failure

--------------------------------------------------
char (*fm_gets (char *buf, INT_32 max, INT_32 id);
--------------------------------------------------

Read the next line from a file opened using "fm_open_file" or


"fm_open_message". At most "max - 1" characters are read from the
file, and the line is always nul-terminated. CRLF characters will
be present unless the file was opened using the "FF_NO_LINE_ENDINGS"
flag, or possibly for the last line of the file.

Returns: "buf" on success


NULL on failure or EOF.

---------------------------
INT_16 fm_getc (INT_32 id);
---------------------------

Read the next character from the file.

Returns: the character on success


EOF on failure

-------------------------------------
void fm_ungetc (INT_16 c, INT_32 id);
-------------------------------------

"unget" the last character retrieved using "fm_gets" or "fm_getc".


Exactly one level of character may be "ungot" using this function.

---------------------------------------------------------
INT_32 fm_read (INT_32 id, char *buffer, INT_32 bufsize);
---------------------------------------------------------

Read at most "bufsize" bytes from the file. No character conversions


are done, and the data may not necessarily end on a line boundary.

Returns: > 0 on success (the number of bytes read)


= 0 on EOF
< 0 on error

------------------------------
INT_32 fm_getpos (INT_32 fil);
------------------------------

Get the current offset in the open file. The return from this function
may be passed to "fm_setpos" to reposition the file at a given point.

Returns: Current file offset.

---------------------------------------------
INT_16 fm_setpos (INT_32 fil, INT_32 offset);
---------------------------------------------

Reposition the file pointer for an open file. "offset" must be a


value returned by "fm_getpos", or 0 to rewind the file. You should
not assume a linear relationship between "offset" and the data in the
file.

Returns: > 0 on success


0 on error.

--------------------------------------------------------------
INT_32 fm_get_folded_line (INT_32 fil, char *line, int limit);
--------------------------------------------------------------

Read a header from a message, "unfolding" continuation lines as


required. This function understands RFC822 line folding rules for
message headers, and will return a single line containing the full
extent of a header. This is an extremely useful function for reading
through the headers of a message without worrying about dealing with
continuation lines. This routine guarantees that the returned line
will be nul-terminated and will not end in CRLF.

There is usually no reason to use this function to read the body of


a mail message, and doing so may lead to unexpected results.

Example: if the next lines in the message are:

From: David Harris


<david@pmail.gen.nz>
(Pegasus Mail Author)

Then this function will return the following single string:

From: David Harris <david@pmail.gen.nz> (Pegasus Mail Author)

Returns: > 0 on success (number of characters in line)


0 if the end of headers has been reached.
-------------------------------------------------------------------
char (*fm_find_header (INT_32 fil, char *name, char *buf, int len);
-------------------------------------------------------------------

Locate the header whose keyword is passed in "name" in the specified


message file. The entire line without the keyword is returned, with any
continuations unfolded into the data.

Returns: "buf" on success (points to start of field body)


NULL on failure.

-----------------------------------------------------------
int fm_extract_message (void *job, char *fname, int flags);
-----------------------------------------------------------

Take an open Mercury mail job and extract its contents to the file
specified in "fname".

"flags" can have combinations of the following values:

FFX_APPEND - append to the file if it exists - don't overwrite


FFX_NO_HEADERS - omit the message headers when writing to the file
FFX_TIDY_HEADERS - write only "significant" headers to the file
FFX_NOT_OPEN - the job is not currently open

If the "FFX_NOT_OPEN" flag is specified, then this routine will open


and close the job. When passing the "job" parameter supplied to the
"daemon" function, you must not specify this flag - the job is already
open and must remain open during processing.

-------------------------------------------
int parse_header (INT_32 fil, IMESSAGE *m);
-------------------------------------------

Perform comprehensive parsing on the open message file "fil", which


should have been opened using "fm_open_file". The information gleaned
from the message's headers is stored in the IMESSAGE structure, "m".
Applications using this function will be particularly interested in
the "flags" field of this structure, since it contains extensive
information about attachments and MIME formatting that might be
present in the message.

Returns: 1 on success
0 on failure (very rare)

---------------------------------------------------------------
int mime_prep_message (INT_32 fil, char *fname, int headers);
---------------------------------------------------------------

Given a non-multipart MIME message, create a "sanitized" version of


the message data in the file named by "fname". MIME encodings are
decoded as required, and any character set conversions that might
be needed are performed. If you pass an empty string for "fname",
Mercury will create a temporary file for you, returning the name
of that file in "fname".
Note that you must not call this function for a multipart message:
to extract a sanitized section from a multipart message, call
"parse_mime", traverse the multipart list until you find the section
you need, then pass that section to "fake_imessage".

If "headers" is non-zero, then the message headers from the message


will be included in the output file, otherwise they will be omitted.

Returns: > 0 on success


0 on failure

----------------------------------------
int parse_mime (INT_32 fil, IMIME *m);
----------------------------------------

Given any valid MIME message, parse its contents and return them in
the "IMIME" structure, "m".

The IMIME structure is a relatively complicated variable record which


can represent any MIME format, including arbitrarily nested Multipart
MIME messages. The fields in an IMIME structure are as follows:

primary - the logical message type, e.g MP_TEXT


secondary - the logical message sub-type, e.g. MPT_PLAIN
encoding - the encoding for this part, e.g ME_BASE_64
disposition - either MD_ATTACHMENT or MD_INLINE
p_string - the actual primary type string, e.g "TEXT"
s_string - the actual secondary sub-type string, e.g "PLAIN"
description - "Content-description" for this part, if any
section - the number of this section within the message
fname - the filename associated with this part, if any

d - a union, one data element from which will contain


part-specific information about this part. You
should select the appropriate record based on the
value of "primary".
primary == MP_TEXT : use "mpt"
primary == MP_MULTIPART : use "mpp"
primary == MP_APPLICATION : use "mpa"
primary == MP_MESSAGE : use "mpm"

The "mpm" record within the structure contains a list of other parts
in the message. It should be traversed by dereferencing the "top"
field of the "partlist" entry and casting its data field to "IMIME *".

Example: code to traverse the parts in a multipart message

IMIME *m, *m2;


LNODE *cur;

if (m->primary == MP_MULTIPART)
{
for (cur = m->d.mpp.partlist.top; cur != NULL; cur = cur->next)
{
m2 = (IMIME *) (cur->data);
// do whatever you need with that section here.
}
}
Note that it is valid, or even normal for a sub-part of a multipart
message to be itself a Multipart item. When this happens, the parts
of the nested item will have been properly parsed into its own "mpp"
structure, and can be followed by traversing its partlist using the
same technique.

Returns: 1 on success
0 on failure (rare, indicates serious malformatting)

--------------------------
void free_mime (IMIME *m);
--------------------------

Call this function when you have finished with an IMIME structure
successfully parsed by "parse_mime". This function deallocates any
lists or other memory allocation created to store the structure of
the MIME message.

-----------------------------------------------------------------
int fake_imessage (IMESSAGE *im, char *dest, char *src, IMIME *m,
char *boundary);
-----------------------------------------------------------------

"Sanitize" a single section of a multipart message. This function


extracts the section of the message contained in "src" and identified
by "m" (which is presumed to be a part from the "partlist" list of a
multipart message) to the file named in "dest". Any necessary decoding
and conversion is done on the section. "Boundary" should point to the
boundary string that delimits the section in the file (you should
usually pass "d.mpp.boundary" from the enclosing message's IMIME
structure for this).

Parsing information about the extracted section is stored in the


IMESSAGE structure "im".

It is the responsibility of the calling function to delete the file


"dest" when it is no longer required.

Returns: 1 on success
0 on failure

-----------------------------------------------
int decode_mime_header (char *dest, char *src);
-----------------------------------------------

Search for and decode 8-bit data encapsulated according to RFC-1522


rules in a string of data (presumably a header extracted from a message
using fm_get_folded_line()). No line breaks are permitted in the input
data.

"dest" should point to a buffer where the decoded header string should
be written. The destination buffer should be at least as large as
the input buffer.

"src" should point to the candidate string, which may contain multiple
RFC-1522-encoded strings.

Returns: 1 if encoded data was found (dest is valid)


0 if no encoded data was found (dest is invalid)
-1 on error (bad format or unrecognized char set)

--------------------------------------------------------
int encode_mime_header (char *dest, char *src, int raw);
--------------------------------------------------------

Encode a string using RFC1522 rules; transformation only occurs if the


input string contains 8-bit data.

"dest" should point to a buffer where the encoded string should be


written. The destination buffer should be at least twice as large
as the input buffer. Note that this routine NEVER generates
multiple encoded blocks in the string - either the whole string is
encoded, or none of it is. Furthermore, this routine only ever
generates the quoted-printable RFC1522 variant, never the BASE64
encoding, which seems to me to be a ludicrous thing to place in the
headers of a mail message.

"src" should point to the source string, which may or may not contain
8-bit data. "src" is expected to be in the WinANSI character set.

Returns: 1 if data was encoded (dest is valid)


0 if no data was encoded (dest is invalid)

----------------------------------------------------------
int encode_base64_str (char *dest, char *src, int srclen);
----------------------------------------------------------

Encode an arbitrary string of data in BASE64 format. Note that this


encoding process will produce an output line approximately a third
larger again than the input, and that MIME message conventions on
line length are ignored - the output will be a single line. This
function is useful for protocols like RFC2554 that require string
data to be transmitted as a BASE64-encoded quantity.

"dest" should point to a buffer where the BASE64 encoded data


should be written. The destination buffer should be at least twice
as large as the input buffer.

"src" is the source string, which may contain any data (it is not
limited to textual or ASCII characters).

"srclen" is the length in bytes of "src".

Returns 1 on success
0 on failure (very, very rare).

-----------------------------------------------------------
int decode_base64_str (char *dest, char *src, char *table);
-----------------------------------------------------------

Decode a string encoded using BASE64.


"dest" should point to a location where the decoded data is to
be written. This buffer should be at least the same size as
the input buffer. The decoded data may be binary in nature -
there is no guarantee that it will be a nul-terminated C
string.

"src" should point to the string to decode

"table" can point to a 128-byte character table used to convert


high-bit characters in the decoded data to an alternative
character set. This value will typically be NULL.

Returns: 1 on success
0 on failure (malformatted BASE64 data)

5.6 - Message composition routines

Mercury provides an easy way to create relatively complex mail messages


in MIME formats. You can create simple messages, messages with
attachments, multipart/alternative messages containing different versions
of the same data, and MIME digests containing other mail messages.

-------------------------------------------------------
void *om_create_message (UINT_32 mtype, UINT_32 flags);
-------------------------------------------------------

Create a message. The message type is set to "mtype" and the message
flags are set to "flags". The handle returned by this function must be
passed to all subsequent message building functions. Messages created
by Mercury are always valid MIME messages - non-MIME message types can
not be created (nor is it any longer desirable to do so).

"mtype" must be one of the following values:

OM_MT_PLAIN - A simple, single-part text/plain message


OM_MT_MULTIPART - A multipart/mixed message
OM_MT_ALTERNATIVE - A multipart/alternative message
OM_MT_DIGEST - A multipart/digest message

"flags" is a bitmap composed of the following possible values:

OM_M_8BIT - The message body contains 8-bit data

The "flags" field can be set later using the "om_add_field" function
and the OM_MF_FLAGS selector.

Returns: Non-NULL on success (message handle)


NULL on failure

------------------------------------------
INT_32 om_dispose_message (void *mhandle);
------------------------------------------

Release the storage used by a message. It is an error to use the


"mhandle" parameter after it has been passed to this routine. This
function must be called when the message is no longer needed, to
deallocate internal buffers.

It is the calling routine's responsibility to deal with the body file


and any files attached to the message.

Returns: > 0 on success


= 0 on failure (invalid handle)

------------------------------------------------------------------
INT_32 om_add_field (void *mhandle, UINT_32 selector, char *data);
------------------------------------------------------------------

Add a field to a message.

"selector" can be any one of the following values:

OM_MF_TO - set the master recipient of the message


OM_MF_SUBJECT - set the subject field for the message
OM_MF_CC - set the secondary recipients of the message
OM_MF_FROM - set the originator of the message.
OM_MF_BODY - set the filename containing the message body
OM_MF_RAW - add a raw header for the message.
OM_MF_FLAGS - set the message's global flags

If the "OM_MF_FROM" field is not set for a message, it will default


to the postmaster address for the system. It is an error to attempt
to send a message for which "OM_MF_TO" has not been set; all other
fields are optional.

The "OM_MF_RAW" selector allows the calling process to add any header
it wishes to the message. The header should be passed complete,
including the header keyword, but without a terminating CRLF pair.
Mercury guarantees that headers added to a message will be written to
the message in the order in which they are added, so you can add
headers with continuations if you wish. You cannot add any of the
following headers using "OM_MF_RAW": "To", "Cc", "Subject", "BCC",
"Content-type", "Content-transfer-encoding", "MIME-Version", or
"Content-disposition". You can reset all custom headers added using
OM_MF_RAW by using OM_MF_RAW as a selector and passing NULL for the
"data" parameter.

It is explicitly legal to add a field you have already added to a


message. This allows you to create a message then send it to multiple
recipients in separate jobs, for example.

Returns: > 0 on success


= 0 on error

-----------------------------------------------------
INT_32 om_add_attachment (void *mhandle, char *fname,
char *ftype, char *description, UINT_32 encoding,
UINT_32 flags, void *reserved);
-----------------------------------------------------

Add an attachment or part to a message.


"fname" - the fully-qualified path to the file to attach
"ftype" - a content-type string - see below
"description" - optional free text description string
"encoding" - encoding method - see below
"flags" - special settings for the attachment - see below
"reserved" - must be 0

The "ftype" string is a free text atom and can have any value, provided
it does not contain spaces, tabs or commas.

"encoding" can have the following values

OM_AE_DEFAULT - default encoding (MIME BASE64 encoding)


OM_AE_TEXT - simple textual data, unencoded
OM_AE_UUENCODE - uuencoding
OM_AE_BINHEX - Macintosh Binhex format (data fork only)

"flags" can have the following values

OM_AF_INLINE - write the file as a simple textual section


OM_AF_MESSAGE - write the message as a Message/RFC822 part

The file need not exist at the time it is attached.

Mercury/32 guarantees that attachments will appear in the message in


the order in which they were attached, so you can create messages
that depend on the sequence of attachments with confidence.

Returns: > 0 on success


= 0 on failure.

-----------------------------------------------------
INT_32 om_write_message (void *mhandle, char *fname);
-----------------------------------------------------

Given a message handle that has been populated with fields, body and
attachments, create a simple disk file containing the final form of
the mail message.

Note that this function does *not* create a job, nor does it send
the message. It simply renders the message structures into their
final form.

This routine is called internally by "om_send_message" to create the


final form of the Mercury queue job. It is guaranteed that calling
this function then calling "om_send_message" will generate a file
and a job that are identical (so, this routine can be used to create
archive copies of outgoing messages).

Returns: > 0 on success


= 0 on failure

------------------------------------------------------
void *om_send_message (void *mhandle, char *envelope);
------------------------------------------------------
Given a message handle that has been populated with fields, body
and attachments, create a job in the Mercury queue containing the
final form of the mail message.

"envelope" is an optional envelope ("Return-path") address for the


created message. If NULL or 0-length, the envelope will default to
the From field, or the postmaster address.

The return from this job is an open Mercury job handle. It is the
responsibility of the calling process to make any last-minute job
settings, then to close or discard the job.

Returns: Open Mercury job handle on success


0 on failure

Notes on composing messages:

1: To create a MIME digest, you must ensure that all the files you attach
to your message are validly-formatted RFC822 mail messages, and that each
has the OM_AF_MESSAGE bit set in its "flags" field. The message body is
ignored for MIME digests, so you may wish to consider creating a "dummy"
first message containing a summary, subscription information or other
information useful to the recipient.

2: The message body file and any attachments must be closed when either
"om_write_message" or "om_send_message" is called.

3: If the "OM_M_8BIT" flag is set for the message, the body of the
message will be sent using the MIME "quoted-printable" encoding scheme.
At present, only the ISO-8859-1 character set (which is the same as the
default MS-Windows character set) is supported for 8-bit data. It is
safe to set the OM_M_8BIT flag for a message that contains only 7-bit
data, but it is an error not to set it for a message that *does* contain
8-bit character data.

4: Address fields ("To:" and "Cc:") can be up to 32000 bytes in length,


and may contain multiple addresses separated by commas. Mercury/32 will
wrap the address fields according to RFC822 line folding rules. Mercury
will *not* expand partial addresses to fully-qualified domain name forms.
It is up to your Daemon to ensure that all addresses are legal and
fully-qualified.

5.7 - Statistics and logging interface

Mercury incorporates a comprehensive Statistics Manager that can keep


running statistics and information about the throughput, performance and
operation of the system. The entire Statistics Manager interface is
available to Daemons and protocol modules. Mercury also provides a System
Messages window into which console-type messages can be written at
various priority levels.

----------------------------------------------
INT_32 st_register_module (char *module_name);
----------------------------------------------
Register a module with the statistics interface. "Module name" is the
name that should be presented in the statistics manager list for this
item. This function creates a top-level statistics category in the
Statistics Manager's list.

Returns: Module handle > 0 on success


0 on failure (very rare)

---------------------------------------------
INT_32 st_unregister_module (INT_32 mhandle);
---------------------------------------------

"Unregister" a module and remove its entries from the statistics list.

Returns 1 on success
0 on failure (not registered)

----------------------------------------------------------
INT_32 st_create_category (INT_32 mhandle, char *cname,
INT_32 ctag, INT_32 ctype, INT_32 dlen, UINT_32 flags);
----------------------------------------------------------

Create a single statistical category within a specified module.

"mhandle" module's registered handle

"cname" display name for this category

"ctag" module's reference handle for this item; this is an


arbitrary value supplied by the module that identifies
this category, and it must be unique within the items
created within a single module.

"ctype" type of data represented


- STC_INTEGER for integral data
- STC_STRING for string data
- STC_DATE for time/date data

"dlen" maximum size of data (for strings)

"flags" attributes of this category


- STF_CUMULATIVE data accumulates
- STF_PEAK record only the highest value
- STF_UNIQUE allow only one named instance

Returns: > 0 (category handle) on success


0 on failure

---------------------------------------------------------
INT_32 st_remove_category (INT_32 mhandle, UINT_32 ctag);
---------------------------------------------------------

Remove a statistical category and delete it from any lists currently


displayed. "ctag" should be the category tag handle passed when the
category was created.
Returns 1 on success
0 on failure (category not found)

-------------------------------------------------------
INT_32 st_set_hcategory (INT_32 chandle, UINT_32 data);
-------------------------------------------------------

Set the data for a category given its category handle only. Note that
a "category handle" is the return from st_create_category, not the
ctag value passed to that function. The system guarantees that
category handles will always be globally unique, and may change from
session to session.

"data" must be of a type appropriate for the registered category. If


the category has the "STF_CUMULATIVE" attribute set, then the data are
accumulated to any data already existing for the category.

Returns: > 0 on success


0 on failure

-------------------------------------------------------------------
INT_32 st_set_category (INT_32 mhandle, INT_32 ctag, UINT_32 data);
-------------------------------------------------------------------

Set the data for a category given its module handle and the module's
internal category handle. "data" must be of a type appropriate for the
registered category. If the category has the "STF_CUMULATIVE" attribute
set, then the data are accumulated to any data that already exist for
the category.

Returns: > 0 on success


0 on failure

----------------------------------------------------------
INT_32 st_get_next_module (INT_32 mhandle, char *modname);
----------------------------------------------------------

Return the next module in the statistics list. To enumerate the modules
in the list, set "mhandle" to -1 on the first call, then pass the return
value in subsequent calls until -1 is returned.

The module's name is copied into "modname", which should be at least 128
characters in length.

Returns: > 0 on success


-1 on no more modules.

-------------------------------------------------------------
INT_32 st_get_next_category (INT_32 mhandle, INT_32 chandle,
char *cname, INT_32 *ctype, INT_32 *clen, INT_32 *cflags);
-------------------------------------------------------------

Return the next category belonging to the module "mhandle" in the


statistics list. To enumerate all the categories in the category list,
pass the module handle (returned by "st_get_next_module", set "chandle"
to -1 on the first call then pass the return value in subsequent calls.
Repeat until the function returns -1.

"cname" receives the category's descriptive text; allocate


at least 128 characters

"ctype" receives the type of the category's data; possible


values are STC_INTEGER, STC_STRING or STC_DATE

"clen" receives the maximum length of the data type.

"cflags" receives the flags associated with the data item;


possible bit values are STF_CUMULATIVE, STF_PEAK and
STF_UNIQUE.

Returns: > 0 on success


-1 on no more categories

---------------------------------------------------------
INT_32 st_get_category_data (INT_32 chandle, void *data);
---------------------------------------------------------

Copy the current data value for the specified category into the
location pointed to by "data". It is the calling module's
responsibility to ensure that sufficient space is allocated. For
STC_INTEGER types, you should allocate 4 bytes; for STC_DATE types,
you should allocate 4 bytes (the returned value is a C "time_t"
type); for STC_STRING types, you should allocate the "clen" returned
by a call to st_get_next_category (which will always be correct for
integer and date types as well).

Returns: 1 on succes
0 on failure

--------------------------------------------------------------------
INT_32 st_export_stats (INT_32 mhandle, char *fname, UINT_32 flags);
--------------------------------------------------------------------

Export a single module's statistics, or all modules' stats


to the file named in "fname" in plain text format.

"mhandle" The module handle for which statistics should


be exported. If 0, export all modules.

"fname" Filename to receive data

"flags" If (flags & 1), append data to the file


If (flags & 2), omit 0 or undefined categories

Returns 1 on success
0 on failure

----------------------------------------------------------
void logstring (INT_16 ltype, INT_16 priority, char *str);
----------------------------------------------------------
Log a message in the System Messages window

"ltype" An arbitrary integer representing the "type" of


the message. It is a convention that "major" types
should be divisible by 10; other types of data
(minor types) will be indented.

"priority" The level of importance of the message. The user


can set the level they want displayed, and messages
with a lower priority than that will be discarded.
Possible values for this field are:
- LOG_DEBUG
- LOG_INFO
- LOG_NORMAL
- LOG_SIGNIFICANT
- LOG_URGENT
- LOG_NONE

Developers are strongly urged to use priority values correctly and


responbsibly, for the benefit of users.

-------------------------------------------------------------
void logdata (INT_16 ltype, INT_16 priority, char *fmt, ...);
-------------------------------------------------------------

The same as "logstring", but supporting "sprintf"-type formatting.


See the documentation for "sprintf" in your compiler manual for
information on valid formatting codes.

Note that because of the way macros are handled in the C language,
this function cannot be accessed using the convenience macros in
"daemon.h" - you must access the function directly from the protocol
block structure.

Appendix A - Technical issues

A.1 - Function names

When Mercury loads a Daemon, it attempts to locate the functions exported


by the Daemon using the function names. Different compilers may export
the names of your functions in different ways - even though your function
may be called "startup", the compiler may actually export it as
"_startup", for historical reasons.

Mercury always attempts to locate your functions in two ways: the first
attempt prepends a "_" to the function name, while the second searches
for the function name with no leading "_", all in uppercase. The first of
these methods will reliably detect functions exported by Borland
compilers, while the second mechanism will usually detect functions
exported by the Microsoft Visual C++ compiler family.

If you find that your functions do not seem to be getting called by


Mercury, check on the format of the name exported by your compiler - this
is the most likely source of the problem.

You might also like