Professional Documents
Culture Documents
Daemon
Daemon
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
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
-----------------------------------------------------------------------
1: Introduction
-----------------------------------------------------------------------
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.
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
-----------------------------------------------------------------------
"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.
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.
daemon_address@host.domain == daemon:path_to_dll[;parameter]
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
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
-----------------------------------------------------------------------
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
char *str
str = (char *) (m->get_variable (GV_QUEUENAME));
-----------------------------------------------------------------------
4: Advanced topics
-----------------------------------------------------------------------
4.1 - DAEMON.INI
[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:
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.
[Daemons]
Cookie Daemon = c:\mercury\daemons\cookie.dll;autocookie
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.
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.
[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:
[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:
[Daemon_Config]
Cookie Daemon = c:\mercury\daemons\cookiecf.dll;autoconfig
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.
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.
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
-----------------------------------------------------------------------
-------------------------------
DWORD get_variable (int index);
-------------------------------
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);
--------------------------------------------------------------
---------------------------------------------------------------
int is_group (char *address, char *host, char *gname);
---------------------------------------------------------------
Example
"David Harris" (Pegasus Mail Author) <david@pmail.gen.nz>
would be reduced by this function to
david@pmail.gen.nz
---------------------------------------------------------------
int extract_one_address (char *dest, char *source, int offset);
---------------------------------------------------------------
"offset" 0 on the first call, the return from the previous call
to the function on subsequent calls.
--------------------------------------------------------
void extract_cqtext (char *dest, char *source, int len);
--------------------------------------------------------
Example:
"David Harris" <david@pmail.gen.nz>
will be reduced by this function to
David Harris
"dest" receives the reduced textual form
Returns: Nothing.
------------------------------------------------------
int dlist_info (DLIST *dlist, char *lname, int number,
char *address, char *errbuf, LIST *modlist);
------------------------------------------------------
"lname" points to the simple name of the list (no domain part).
Returns: 0 on success
-1 on failure
-------------------------------------------------------------------
void send_notification (char *username, char *host, char *message);
-------------------------------------------------------------------
Returns Nothing
---------------------------------------------------------------
int get_delivery_path (char *path, char *username, char *host);
---------------------------------------------------------------
---------------------------------
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:
This function will obtain the date and time from the local workstation
if no server connection is available to provide it.
-----------------------------------------------
int write_profile (char *section, char *fname);
-----------------------------------------------
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:
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);
----------------------------------------------------------
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.
----------------------------------
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.
----------------------------------
int ji_close_job (void *jobhandle);
----------------------------------
Returns: 1 on success
0 on failure (job invalid).
------------------------------------------------
void ji_rewind_job (void *jobhandle, int flags);
------------------------------------------------
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);
-------------------------------------
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);
----------------------------------------------
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);
----------------------------------------------------------------------
The job handle returned by this function can be passed to any other
job management function in order to manipulate the queue job.
----------------------------------------------------
int ji_add_element (void *jobhandle, char *address);
----------------------------------------------------
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.
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.
-----------------------------------------------------
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:
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.
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.
------------------------------------------------------------------
void ji_increment_time (unsigned char *tm, unsigned int add_secs);
------------------------------------------------------------------
----------------------------------
void *ji_get_job_by_id (char *id);
----------------------------------
---------------------------------------------------------------------
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.
------------------------------------------------------------------
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);
------------------------------------------------------------------
--------------------------------------------------------------------
int is_valid_local_user (char *address, char *username, char *host);
--------------------------------------------------------------------
--------------------------------------------------------------------
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);
--------------------------------------------------------------------
--------------------------------------------------------------------
int get_user_details (char *host, char *match, char *username,
int ulen, char *address, int alen, char *fullname, int flen);
--------------------------------------------------------------------
Returns: 1 on success
0 on failure (no such user)
---------------------------------------------------------
void read_pmprop (char *userid, char *server, PMPROP *p);
---------------------------------------------------------
---------------------------------------------------------------
int change_ownership (char *fname, char *host, char *newowner);
---------------------------------------------------------------
-----------------------------------------------------------------
int begin_single_delivery (char *uic, char *server, void **data);
void end_single_delivery (void **data)
-----------------------------------------------------------------
-----------------------------------------------------------
INT_32 create_object (char *objectname, INT_32 objecttype,
char *id, INT_32 flags);
-----------------------------------------------------------
This routine will fail if a user already exists with the name "username",
or if the user's mail directory cannot be created.
Returns: 1 on success
0 on failure
----------------------------------------------------------------
int verify_password (char *username, char *host, char *password,
int select);
----------------------------------------------------------------
----------------------------------------------------------------
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
-----------------------------------------------------------------
DWORD mercury_command (DWORD selector, DWORD parm1, DWORD parm2);
-----------------------------------------------------------------
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);
----------------------------------------------------------------
Returns: buf
---------------------------------
char *rfc822_time (char *buffer);
char *rfc821_time (char *buffer);
---------------------------------
------------------------------------------------------
INT_32 select_printer (char *device_name, int maxlen);
------------------------------------------------------
---------------------------------------------------------------------
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);
---------------------------------------------------------------------
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.
-----------------------------------------------------
INT_32 fm_open_message (IMESSAGE *im, UINT_32 flags);
-----------------------------------------------------
---------------------------------
int fm_close_message (INT_32 id);
---------------------------------
Returns: 1 on success
0 on failure
--------------------------------------------------
char (*fm_gets (char *buf, INT_32 max, INT_32 id);
--------------------------------------------------
---------------------------
INT_16 fm_getc (INT_32 id);
---------------------------
-------------------------------------
void fm_ungetc (INT_16 c, INT_32 id);
-------------------------------------
---------------------------------------------------------
INT_32 fm_read (INT_32 id, char *buffer, INT_32 bufsize);
---------------------------------------------------------
------------------------------
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.
---------------------------------------------
INT_16 fm_setpos (INT_32 fil, INT_32 offset);
---------------------------------------------
--------------------------------------------------------------
INT_32 fm_get_folded_line (INT_32 fil, char *line, int limit);
--------------------------------------------------------------
-----------------------------------------------------------
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".
-------------------------------------------
int parse_header (INT_32 fil, IMESSAGE *m);
-------------------------------------------
Returns: 1 on success
0 on failure (very rare)
---------------------------------------------------------------
int mime_prep_message (INT_32 fil, char *fname, int headers);
---------------------------------------------------------------
----------------------------------------
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 "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 *".
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);
-----------------------------------------------------------------
Returns: 1 on success
0 on failure
-----------------------------------------------
int decode_mime_header (char *dest, char *src);
-----------------------------------------------
"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.
--------------------------------------------------------
int encode_mime_header (char *dest, char *src, int raw);
--------------------------------------------------------
"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.
----------------------------------------------------------
int encode_base64_str (char *dest, char *src, int srclen);
----------------------------------------------------------
"src" is the source string, which may contain any data (it is not
limited to textual or ASCII characters).
Returns 1 on success
0 on failure (very, very rare).
-----------------------------------------------------------
int decode_base64_str (char *dest, char *src, char *table);
-----------------------------------------------------------
Returns: 1 on success
0 on failure (malformatted BASE64 data)
-------------------------------------------------------
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).
The "flags" field can be set later using the "om_add_field" function
and the OM_MF_FLAGS selector.
------------------------------------------
INT_32 om_dispose_message (void *mhandle);
------------------------------------------
------------------------------------------------------------------
INT_32 om_add_field (void *mhandle, UINT_32 selector, char *data);
------------------------------------------------------------------
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.
-----------------------------------------------------
INT_32 om_add_attachment (void *mhandle, char *fname,
char *ftype, char *description, UINT_32 encoding,
UINT_32 flags, void *reserved);
-----------------------------------------------------
The "ftype" string is a free text atom and can have any value, provided
it does not contain spaces, tabs or commas.
-----------------------------------------------------
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.
------------------------------------------------------
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.
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.
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.
----------------------------------------------
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.
---------------------------------------------
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);
----------------------------------------------------------
---------------------------------------------------------
INT_32 st_remove_category (INT_32 mhandle, UINT_32 ctag);
---------------------------------------------------------
-------------------------------------------------------
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.
-------------------------------------------------------------------
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.
----------------------------------------------------------
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.
-------------------------------------------------------------
INT_32 st_get_next_category (INT_32 mhandle, INT_32 chandle,
char *cname, INT_32 *ctype, INT_32 *clen, INT_32 *cflags);
-------------------------------------------------------------
---------------------------------------------------------
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);
--------------------------------------------------------------------
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
-------------------------------------------------------------
void logdata (INT_16 ltype, INT_16 priority, char *fmt, ...);
-------------------------------------------------------------
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.
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.