/*
elmo - ELectronic Mail Operator
Copyright (C) 2002, 2003, 2004 rzyjontko
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
----------------------------------------------------------------------
operations on mail array
*/
#define _GNU_SOURCE 1
/****************************************************************************
* IMPLEMENTATION HEADERS
****************************************************************************/
#include <string.h>
#include <errno.h>
#include "xmalloc.h"
#include "mail.h"
#include "hash.h"
#include "multree.h"
#include "misc.h"
#include "memchunk.h"
#include "error.h"
#include "gettext.h"
#include "clock.h"
#include "debug.h"
/****************************************************************************
* IMPLEMENTATION PRIVATE DEFINITIONS / ENUMERATIONS / SIMPLE TYPEDEFS
****************************************************************************/
#define INITIAL_MAIL_COUNT 100
#define CHUNK_SIZE 1000
#define INFO_MAGIC 0x1D10A1DA
#define INFO_VERSION 11
/****************************************************************************
* IMPLEMENTATION PRIVATE CLASS PROTOTYPES / EXTERNAL CLASS REFERENCES
****************************************************************************/
/****************************************************************************
* IMPLEMENTATION PRIVATE STRUCTURES / UTILITY CLASSES
****************************************************************************/
struct mail_array_info {
unsigned magic; /* magic number = INFO_MAGIC */
int version; /* version = INFO_VERSION */
int size; /* messages count */
};
/****************************************************************************
* IMPLEMENTATION REQUIRED EXTERNAL REFERENCES (AVOID)
****************************************************************************/
/****************************************************************************
* IMPLEMENTATION PRIVATE DATA
****************************************************************************/
/****************************************************************************
* INTERFACE DATA
****************************************************************************/
/****************************************************************************
* IMPLEMENTATION PRIVATE FUNCTION PROTOTYPES
****************************************************************************/
/****************************************************************************
* IMPLEMENTATION PRIVATE FUNCTIONS
****************************************************************************/
static void
find_parent (mail_array_t *marray, htable_t *table, int index)
{
int i;
mail_t *parent = NULL;
mail_t *mail = mail_array_get (marray, index);
entry_t *entry = NULL;
if (mail == NULL)
return;
if (mail->in_reply_to){
for (i = mail->in_reply_to->count; i; i--){
entry = htable_lookup (table, mail->in_reply_to->array[i - 1]);
if (entry)
break;
}
}
if (entry){
parent = mail_array_get (marray, (int) entry->content);
mail->parent = (int) entry->content;
parent->child_count++;
}
else {
mail->parent = -1;
}
}
static void
put_msgid_into_hash (mail_array_t *marray, htable_t *table, int index)
{
mail_t *mail = mail_array_get (marray, index);
entry_t *entry = NULL;
if (mail == NULL)
return;
if (! mail->msg_id)
return;
entry = htable_insert (table, mail->msg_id, (void *) index);
if (entry->content != (void *) index)
mail->flags |= FLAG_DUPLICATE;
}
static int
is_not_reply (mail_t *mail)
{
return mail->parent == -1;
}
static int
is_old (mail_t *mail)
{
return (mail->flags & FLAG_OLD) && ! (mail->flags & FLAG_READ);
}
static int
is_read (mail_t *mail)
{
return mail->flags & FLAG_READ;
}
static int
is_not_read (mail_t *mail)
{
return ! (mail->flags & FLAG_READ);
}
static int
count_true (mail_array_t *marray, int (*is_true)(mail_t *))
{
int i;
int result = 0;
mail_t *mail;
for (i = 0; i < marray->count; i++){
mail = mail_array_get (marray, i);
if (is_true (mail))
result++;
}
return result;
}
static int
first_true (mail_array_t *marray, int (*is_true)(mail_t *), int start)
{
int i;
mail_t *mail;
for (i = start; i < marray->count; i++){
mail = mail_array_get (marray, i);
if (is_true (mail))
return i;
}
return -1;
}
static int
first_true_backward (mail_array_t *marray, int (*is_true)(mail_t *), int start)
{
int i;
mail_t *mail;
for (i = start; i >= 0; i--){
mail = mail_array_get (marray, i);
if (is_true (mail))
return i;
}
return -1;
}
/****************************************************************************
* MAIL ARRAY DUMPING FUNCTIONS
****************************************************************************/
static void
dump_address (memchunk_t *chunk, address_t *addr)
{
if (addr == NULL)
memchunk_intdump (chunk, -1);
else
memchunk_intdump (chunk, addr->index);
}
static void
dump_raddress (memchunk_t *chunk, raddress_t *raddr)
{
int i;
if (raddr == NULL){
memchunk_intdump (chunk, 0);
return;
}
memchunk_intdump (chunk, raddr->count);
for (i = 0; i < raddr->count; i++){
dump_address (chunk, raddr->array[i]);
}
}
static void
dump_mail_but_place (memchunk_t *chunk, mail_t *mail)
{
dump_address (chunk, mail->from);
dump_address (chunk, mail->reply_to);
memchunk_strdump (chunk, mail->subject);
memchunk_strdump (chunk, mail->date_str);
dump_raddress (chunk, mail->to);
dump_raddress (chunk, mail->cc);
dump_raddress (chunk, mail->bcc);
dump_address (chunk, mail->recv_for);
memchunk_strdump (chunk, mail->msg_id);
rstring_dump (chunk, mail->in_reply_to);
memchunk_intdump (chunk, mail->flags);
memchunk_intdump (chunk, mail->date);
memchunk_strdump (chunk, mail->mua);
mime_dump (chunk, mail->mime->mime);
rstring_dump (chunk, mail->headers);
memchunk_strdump (chunk, mail->smtp);
}
static int
index_address (raddress_t *table, address_t *addr, int index)
{
if (addr == NULL)
return index;
if (addr->index == -1){
raddress_add (table, addr);
addr->index = index++;
}
return index;
}
static int
index_raddress (raddress_t *table, raddress_t *raddr, int index)
{
int i;
if (raddr == NULL)
return index;
for (i = 0; i < raddr->count; i++)
index = index_address (table, raddr->array[i], index);
return index;
}
static int
index_mail (raddress_t *table, mail_t *mail, int index)
{
index = index_address (table, mail->from, index);
index = index_address (table, mail->reply_to, index);
index = index_raddress (table, mail->to, index);
index = index_raddress (table, mail->cc, index);
index = index_raddress (table, mail->bcc, index);
index = index_address (table, mail->recv_for, index);
return index;
}
static void
dump_address_table (memchunk_t *chunk, mail_array_t *marray)
{
int i;
int index = 0;
raddress_t *table = raddress_create ();
address_reset_indexes ();
for (i = 0; i < marray->count; i++)
index = index_mail (table, marray->array + i, index);
raddress_dump (table, chunk);
raddress_destroy (table);
}
/****************************************************************************
* MAIL ARRAY READING FUNCTIONS
****************************************************************************/
static address_t *
address_from_table (memchunk_t *chunk, raddress_t *table)
{
int index = memchunk_intget (chunk);
if (table == NULL || index == -1)
return NULL;
if (index >= table->count){
debug_msg (DEBUG_ERROR, "index %d > than table->count %d",
index, table->count);
return NULL;
}
return table->array[index];
}
static raddress_t *
raddress_from_table (memchunk_t *chunk, raddress_t *table)
{
int i;
int cnt = memchunk_intget (chunk);
raddress_t *result;
if (cnt <= 0)
return NULL;
result = raddress_create_size (cnt + 1);
for (i = 0; i < cnt; i++){
raddress_add (result, address_from_table (chunk, table));
}
return result;
}
static void
read_mail_but_place (memchunk_t *chunk, mail_t *mail, raddress_t *table)
{
mail->from = address_from_table (chunk, table);
mail->reply_to = address_from_table (chunk, table);
mail->subject = memchunk_strget (chunk);
mail->date_str = memchunk_strget (chunk);
mail->to = raddress_from_table (chunk, table);
mail->cc = raddress_from_table (chunk, table);
mail->bcc = raddress_from_table (chunk, table);
mail->recv_for = address_from_table (chunk, table);
mail->msg_id = memchunk_strget (chunk);
mail->in_reply_to = rstring_read (chunk);
mail->flags = memchunk_intget (chunk);
mail->date = memchunk_intget (chunk);
mail->mua = memchunk_strget (chunk);
mail->mime = mime_info_read (chunk);
mail->headers = rstring_read (chunk);
mail->smtp = memchunk_strget (chunk);
}
/****************************************************************************
* INTERFACE FUNCTIONS
****************************************************************************/
void
mail_clear (mail_t *mail)
{
mail->subject = NULL;
mail->from = NULL;
mail->to = NULL;
mail->cc = NULL;
mail->bcc = NULL;
mail->recv_for = NULL;
mail->reply_to = NULL;
mail->msg_id = NULL;
mail->in_reply_to = NULL;
mail->tree = NULL;
mail->date_str = NULL;
mail->tree_len = 0;
mail->flags = 0;
mail->parent = -1;
mail->child_count = 0;
mail->is_last_child = 0;
mail->place.generic = NULL;
mail->date = 0;
mail->mua = NULL;
mail->type = BOX_INVALID;
mail->mime = NULL;
mail->headers = NULL;
mail->smtp = NULL;
}
void
mail_destroy (mail_t *mail, box_type_t type)
{
if (mail->subject)
xfree (mail->subject);
if (mail->msg_id)
xfree (mail->msg_id);
if (mail->in_reply_to){
rstring_delete (mail->in_reply_to);
}
if (mail->date_str)
xfree (mail->date_str);
if (mail->tree)
xfree (mail->tree);
if (type == BOX_MAILDIR || type == BOX_SENDER){
if (mail->place.file_name)
xfree (mail->place.file_name);
mail->place.file_name = NULL;
}
if (mail->mua)
xfree (mail->mua);
if (mail->mime)
mime_info_destroy (mail->mime);
if (mail->to)
raddress_destroy (mail->to);
if (mail->cc)
raddress_destroy (mail->cc);
if (mail->bcc)
raddress_destroy (mail->bcc);
if (mail->headers)
rstring_delete (mail->headers);
if (mail->smtp)
xfree (mail->smtp);
}
mail_array_t *
mail_array_create_size (int size, box_type_t type, const char *path)
{
mail_array_t *result = xmalloc (sizeof (mail_array_t));
result->count = 0;
result->not_replies_count = -1;
result->old_count = -1;
result->read_count = -1;
result->have_parents = 0;
result->order = ORDER_NO;
result->size = size;
result->type = type;
result->path = xstrdup (path);
result->array = xmalloc (size * sizeof (mail_t));
return result;
}
mail_array_t *
mail_array_create (box_type_t type, const char *path)
{
return mail_array_create_size (INITIAL_MAIL_COUNT, type, path);
}
void
mail_array_destroy (mail_array_t *marray)
{
int i;
for (i = 0; i < marray->count; i++){
mail_destroy (marray->array + i, marray->type);
}
if (marray->path)
xfree (marray->path);
if (marray->array)
xfree (marray->array);
xfree (marray);
}
void
mail_array_substitute (mail_array_t *marray, mail_array_t *with)
{
if (marray->path)
xfree (marray->path);
if (marray->array)
xfree (marray->array);
marray->path = with->path;
marray->array = with->array;
marray->size = with->size;
marray->count = with->count;
marray->type = with->type;
marray->not_replies_count = -1;
marray->old_count = -1;
marray->read_count = -1;
marray->have_parents = 0;
marray->order = ORDER_NO;
xfree (with);
}
void
mail_array_insert (mail_array_t *marray, mail_t *mail)
{
if (marray->count == marray->size){
marray->size = (marray->size + 1) * 2;
marray->array = xrealloc (marray->array,
marray->size * sizeof (mail_t));
}
memcpy (marray->array + marray->count, mail, sizeof (mail_t));
marray->not_replies_count = -1;
marray->old_count = -1;
marray->read_count = -1;
marray->have_parents = 0;
marray->order = ORDER_NO;
marray->count++;
}
mail_t *
mail_array_get (mail_array_t *marray, int index)
{
if (marray == NULL)
return NULL;
if (index < 0 || index >= marray->count)
return NULL;
return marray->array + index;
}
void
mail_array_find_parents (mail_array_t *marray)
{
int i;
htable_t *id_table;
id_table = htable_create (misc_logarithm (marray->count));
for (i = 0; i < marray->count; i++){
put_msgid_into_hash (marray, id_table, i);
}
for (i = 0; i < marray->count; i++){
find_parent (marray, id_table, i);
}
htable_destroy (id_table, NULL);
marray->have_parents = 1;
}
int
mail_array_not_replies (mail_array_t *marray)
{
if (marray->not_replies_count != -1)
return marray->not_replies_count;
return count_true (marray, is_not_reply);
}
int
mail_array_old (mail_array_t *marray)
{
if (marray->old_count != -1)
return marray->old_count;
return count_true (marray, is_old);
}
int
mail_array_read (mail_array_t *marray)
{
if (marray->read_count != -1)
return marray->read_count;
return count_true (marray, is_read);
}
int
mail_array_first_unread_index (mail_array_t *marray)
{
return first_true (marray, is_not_read, 0);
}
int
mail_array_dump (mail_array_t *marray, FILE *fp,
void (*dump_place)(memchunk_t *, mail_t *))
{
struct mail_array_info info;
memchunk_t *chunk = memchunk_create_size (CHUNK_SIZE);
int i;
info.magic = INFO_MAGIC;
info.version = INFO_VERSION;
info.size = marray->count;
if (fwrite (&info, sizeof (info), 1, fp) != 1){
error_ (errno, "fwrite");
memchunk_destroy (chunk);
return 1;
}
memchunk_reset (chunk);
dump_address_table (chunk, marray);
if (memchunk_dump (chunk, fp)){
memchunk_destroy (chunk);
return 1;
}
for (i = 0; i < marray->count; i++){
memchunk_reset (chunk);
dump_mail_but_place (chunk, marray->array + i);
dump_place (chunk, marray->array + i);
if (memchunk_dump (chunk, fp)){
memchunk_destroy (chunk);
return 1;
}
}
memchunk_destroy (chunk);
return 0;
}
mail_array_t *
mail_array_read_from_file (FILE *fp, box_type_t type, const char *path,
void (*read_place)(memchunk_t *, mail_t *))
{
struct mail_array_info info;
int i;
mail_array_t *result;
memchunk_t *chunk;
int p;
raddress_t *table;
if (fread (&info, sizeof (info), 1, fp) != 1){
error_ (errno, "fread");
fclose (fp);
return NULL;
}
if (info.magic != INFO_MAGIC){
error_ (0, _("cache file has invalid format"));
fclose (fp);
return NULL;
}
if (info.version != INFO_VERSION){
error_ (0, _("cache file format changed since last version"));
fclose (fp);
return NULL;
}
chunk = memchunk_create_size (CHUNK_SIZE);
result = mail_array_create_size (info.size, type, path);
if (memchunk_read (chunk, fp)){
memchunk_destroy (chunk);
return NULL;
}
table = raddress_read (chunk);
p = progress_setup (info.size, _("reading box (messages)"));
for (i = 0; i < info.size; i++){
memchunk_reset (chunk);
if (memchunk_read (chunk, fp)){
memchunk_destroy (chunk);
mail_array_destroy (result);
return NULL;
}
memchunk_rewind (chunk);
mail_clear (result->array + i);
read_mail_but_place (chunk, result->array + i, table);
read_place (chunk, result->array + i);
progress_advance (p, 1);
}
result->count = info.size;
if (table)
raddress_destroy (table);
progress_close (p);
memchunk_destroy (chunk);
return result;
}
/****************************************************************************
* SORTING PRIVATE DATA
****************************************************************************/
/**
* threading requires having another array
*/
static mail_array_t *thread_array = NULL;
static mail_array_t *sorted_array = NULL;
/**
* this is used in threading
*/
static multree_t *root = NULL;
static multree_t **nodes_array = NULL;
/****************************************************************************
* SORTING FUNCTIONS (IMPLEMENTATION PRIVATE)
****************************************************************************/
static int
date_cmp (const void *a, const void *b)
{
mail_t *aa = (mail_t *) a;
mail_t *bb = (mail_t *) b;
return (aa->date > bb->date) - (aa->date < bb->date);
}
static int
date_cmp_rev (const void *a, const void *b)
{
return - date_cmp (a, b);
}
static int
from_cmp (const void *a, const void *b)
{
mail_t *aa = (mail_t *) a;
mail_t *bb = (mail_t *) b;
if (aa->from == NULL || aa->from->name == NULL)
return 1;
if (bb->from == NULL || bb->from->name == NULL)
return -1;
return strcoll (aa->from->name, bb->from->name);
}
static int
from_cmp_rev (const void *a, const void *b)
{
return - from_cmp (a, b);
}
static int
subject_cmp (const void *a, const void *b)
{
mail_t *aa = (mail_t *) a;
mail_t *bb = (mail_t *) b;
if (aa->subject == NULL)
return 1;
if (bb->subject == NULL)
return -1;
return strcoll (aa->subject, bb->subject);
}
static int
subject_cmp_rev (const void *a, const void *b)
{
return - subject_cmp (a, b);
}
static int
smtp_cmp (const void *a, const void *b)
{
mail_t *aa = (mail_t *) a;
mail_t *bb = (mail_t *) b;
if (aa->smtp == NULL)
return 1;
if (bb->smtp == NULL)
return -1;
return strcmp (aa->smtp, bb->smtp);
}
static void
prepare_nodes_array (mail_array_t *marray)
{
int i;
mail_t *mail;
for (i = 0; i < marray->count; i++){
mail = mail_array_get (marray, i);
nodes_array[i] = multree_create (mail->child_count);
nodes_array[i]->data = (void *) i;
}
}
static void
insert_mail (multree_t *node)
{
mail_t *mail = mail_array_get (sorted_array, (int) node->data);
multree_t *parent_node;
if (node == root)
return;
if (mail->parent != -1){
parent_node = nodes_array[mail->parent];
if (parent_node->children[parent_node->children_filled - 1] == node)
mail->is_last_child = 1;
else
mail->is_last_child = 0;
}
mail_array_insert (thread_array, mail);
/**
* it's a nasty hack: we don't need any information in an old array
* (where mail is still stored) so we can put in mail->date it's index
* in a new array
*/
mail->date = thread_array->count - 1;
}
/**
* this function uses the nasty hack in insert_mail, it finds parents
* from the information stored in mail->date
*
* I know, it's terrible, but see how efficiently it works :)
*/
static void
refind_parents (void)
{
int i;
mail_t *new_mail;
mail_t *old_mail;
mail_t *old_parent;
for (i = 0; i < sorted_array->count; i++){
old_mail = mail_array_get (sorted_array, i);
old_parent = mail_array_get (sorted_array, old_mail->parent);
new_mail = mail_array_get (thread_array, old_mail->date);
if (new_mail == NULL)
continue;
if (old_parent)
new_mail->parent = old_parent->date;
else
new_mail->parent = -1;
}
}
/****************************************************************************
* SORTING FUNCTIONS (INTERFACE)
****************************************************************************/
void
mail_array_sort_external (mail_array_t *marray,
int (*cmp)(const void *, const void *))
{
qsort (marray->array, marray->count, sizeof (mail_t), cmp);
marray->have_parents = 0;
marray->order = ORDER_EXTERNAL;
}
void
mail_array_sort_date (mail_array_t *marray)
{
marray->have_parents = 0;
if (marray->order != ORDER_DATE){
qsort (marray->array, marray->count, sizeof (mail_t), date_cmp);
marray->order = ORDER_DATE;
}
else {
qsort (marray->array, marray->count, sizeof (mail_t), date_cmp_rev);
marray->order = ORDER_DATE_REV;
}
}
void
mail_array_sort_from (mail_array_t *marray)
{
marray->have_parents = 0;
if (marray->order != ORDER_FROM){
qsort (marray->array, marray->count, sizeof (mail_t), from_cmp);
marray->order = ORDER_FROM;
}
else {
qsort (marray->array, marray->count, sizeof (mail_t), from_cmp_rev);
marray->order = ORDER_FROM_REV;
}
}
void
mail_array_sort_subject (mail_array_t *marray)
{
marray->have_parents = 0;
if (marray->order != ORDER_SUBJECT){
qsort (marray->array, marray->count, sizeof (mail_t), subject_cmp);
marray->order = ORDER_SUBJECT;
}
else {
qsort (marray->array, marray->count, sizeof (mail_t), subject_cmp_rev);
marray->order = ORDER_SUBJECT_REV;
}
}
void
mail_array_sort_smtp (mail_array_t *marray)
{
marray->have_parents = 0;
if (marray->order == ORDER_SMTP)
return;
qsort (marray->array, marray->count, sizeof (mail_t), smtp_cmp);
marray->order = ORDER_SMTP;
}
void
mail_array_sort_threads (mail_array_t *marray)
{
int i;
int not_replies = mail_array_not_replies (marray);
mail_t *mail;
multree_t *parent;
if (marray->order == ORDER_THREAD)
return;
/**
* step 0: find parents (if necessary)
*/
if (! marray->have_parents)
mail_array_find_parents (marray);
/**
* step 1: prepare new mail array and other important data structures
*/
thread_array = mail_array_create_size (marray->size, marray->type, NULL);
sorted_array = marray;
root = multree_create (not_replies);
root->data = (void *) -1;
nodes_array = xcalloc (marray->count, sizeof (multree_t *));
prepare_nodes_array (marray);
/**
* step 2: create the tree
*/
for (i = 0; i < marray->count; i++){
mail = mail_array_get (marray, i);
if (mail->parent == -1)
parent = root;
else
parent = nodes_array[mail->parent];
multree_add_child (parent, nodes_array[i]);
}
/**
* step 3: sorting
*/
multree_preorder (root, insert_mail);
multree_postorder (root, multree_node_destroy);
/**
* step 4: switch to the new array
*/
refind_parents ();
if (marray->array)
xfree (marray->array);
marray->array = thread_array->array;
marray->order = ORDER_THREAD;
xfree (thread_array);
/**
* step 5: free resources
*/
if (nodes_array)
xfree (nodes_array);
root = NULL;
nodes_array = NULL;
thread_array = NULL;
sorted_array = NULL;
}
/****************************************************************************
* SEARCHING PRIVATE DATA
****************************************************************************/
static void *search_ptr = NULL;
static const void *search_subject = NULL;
static const void *search_from = NULL;
/****************************************************************************
* SEARCHING FUNCTIONS (IMPLEMENTATION PRIVATE)
****************************************************************************/
static int
search_for_place (mail_t *mail)
{
return mail->place.generic == search_ptr;
}
static int
search_for_subject (mail_t *mail)
{
if (mail->subject == NULL)
return 0;
if (strstr (mail->subject, search_subject))
return 1;
return 0;
}
static int
search_for_from (mail_t *mail)
{
if (mail->from == NULL)
return 0;
if (strstr (mail->from->full, search_from))
return 1;
return 0;
}
static int
search_unread (mail_t *mail)
{
if (mail->flags & FLAG_READ)
return 0;
return 1;
}
/****************************************************************************
* SEARCHING FUNCTIONS (INTERFACE)
****************************************************************************/
int
mail_array_find_place (mail_array_t *marray, void *ptr)
{
int result;
search_ptr = ptr;
result = first_true (marray, search_for_place, 0);
search_ptr = NULL;
return result;
}
int
mail_array_find (mail_array_t *marray, int (*fun)(mail_t *), int start)
{
return first_true (marray, fun, start);
}
int
mail_array_find_back (mail_array_t *marray, int (*fun)(mail_t *), int start)
{
return first_true_backward (marray, fun, start);
}
int
mail_array_find_subject (mail_array_t *marray, const char *subject, int start)
{
int result;
search_subject = subject;
result = first_true (marray, search_for_subject, start);
search_subject = NULL;
return result;
}
int
mail_array_find_from (mail_array_t *marray, const char *from, int start)
{
int result;
search_from = from;
result = first_true (marray, search_for_from, start);
search_from = NULL;
return result;
}
int
mail_array_find_unread (mail_array_t *marray, int start)
{
int result;
result = first_true (marray, search_unread, start);
if (result != -1)
return result;
return first_true (marray, search_unread, 0);
}
int
mail_array_find_unread_back (mail_array_t *marray, int start)
{
int result;
result = first_true_backward (marray, search_unread, start);
if (result != -1)
return result;
return first_true_backward (marray, search_unread, marray->count - 1);
}
/****************************************************************************
* INTERFACE CLASS BODIES
****************************************************************************/
/****************************************************************************
*
* END MODULE mail.c
*
****************************************************************************/
syntax highlighted by Code2HTML, v. 0.9.1