/*
* IMC2 - an inter-mud communications protocol
*
* imc-mail.c: IMC mailer functions
*
* Copyright (C) 1996,1997 Oliver Jowett <oliver@jowett.manawatu.planet.co.nz>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program (see the file COPYING); if not, write to the
* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include "imc.h"
/*
* general stuff
*/
/* escape a string for writing to a file */
static const char *escape(const char *data)
{
char *buf=imc_getsbuf(IMC_DATA_LENGTH);
char *p;
for (p=buf; *data && (p-buf < IMC_DATA_LENGTH-1); data++, p++)
{
if (*data == '\n')
{
*p++='\\';
*p='n';
}
else if (*data == '\r')
{
*p++='\\';
*p='r';
}
else if (*data == '\\')
{
*p++='\\';
*p='\\';
}
else if (*data == '"')
{
*p++='\\';
*p='"';
}
else
*p=*data;
}
*p=0;
imc_shrinksbuf(buf);
return buf;
}
/* unescape: reverse escape */
static const char *unescape(const char *data)
{
char *buf=imc_getsbuf(IMC_DATA_LENGTH);
char *p;
char ch;
for (p=buf; *data && (p-buf < IMC_DATA_LENGTH-1); data++, p++)
{
if (*data == '\\')
{
ch = *(++data);
switch (ch)
{
case 'n':
*p='\n';
break;
case 'r':
*p='\r';
break;
case '\\':
*p='\\';
break;
default:
*p=ch;
break;
}
}
else
*p=*data;
}
*p=0;
imc_shrinksbuf(buf);
return buf;
}
/*
* mail
*/
/* new_mail: create a new mail structure */
static imc_mail *new_mail(void)
{
imc_mail *p;
p=imc_malloc(sizeof(*p));
p->from = NULL;
p->to = NULL;
p->subject = NULL;
p->date = NULL;
p->text = NULL;
p->id = NULL;
p->received = 0;
p->usage = 0;
p->next = NULL;
return p;
}
/* free_mail: free a mail structure */
static void free_mail(imc_mail *p)
{
if (!p)
{
imc_logerror("BUG: free_mail: freeing NULL pointer");
return;
}
if (p->usage)
{
imc_logerror("BUG: free_mail: freeing mail at %p with usage=%d",
p, p->usage);
return;
}
if (p->from)
imc_strfree(p->from);
if (p->to)
imc_strfree(p->to);
if (p->id)
imc_strfree(p->id);
if (p->text)
imc_strfree(p->text);
if (p->subject)
imc_strfree(p->subject);
if (p->date)
imc_strfree(p->date);
imc_cancel_event(NULL, p);
imc_free(p, sizeof(*p));
}
/* write_mail: write a single mail to a file */
static void write_mail(imc_mail *p, FILE *out)
{
if (!p)
{
imc_logerror("BUG: write_mail: NULL pointer");
return;
}
fprintf(out, "From %s\n", escape(p->from));
fprintf(out, "To %s\n", escape(p->to));
fprintf(out, "Subject %s\n", escape(p->subject));
fprintf(out, "Date %s\n", escape(p->date));
fprintf(out, "Text %s\n", escape(p->text));
fprintf(out, "ID %s\n", escape(p->id));
fprintf(out, "Received %ld\n", p->received);
}
/* read_mail: read a single mail from a file, NULL on EOF */
static imc_mail *read_mail(FILE *in)
{
imc_mail *p;
char line[IMC_DATA_LENGTH];
char temp[IMC_DATA_LENGTH];
fgets(line, IMC_DATA_LENGTH, in);
if (ferror(in) || feof(in))
return NULL;
p=new_mail();
sscanf(line, "From %[^\n]", temp);
p->from=imc_strdup(unescape(temp));
fgets(line, IMC_DATA_LENGTH, in);
sscanf(line, "To %[^\n]", temp);
p->to=imc_strdup(unescape(temp));
fgets(line, IMC_DATA_LENGTH, in);
sscanf(line, "Subject %[^\n]", temp);
p->subject=imc_strdup(unescape(temp));
fgets(line, IMC_DATA_LENGTH, in);
sscanf(line, "Date %[^\n]", temp);
p->date=imc_strdup(unescape(temp));
fgets(line, IMC_DATA_LENGTH, in);
sscanf(line, "Text %[^\n]", temp);
p->text=imc_strdup(unescape(temp));
fgets(line, IMC_DATA_LENGTH, in);
sscanf(line, "ID %[^\n]", temp);
p->id=imc_strdup(unescape(temp));
fgets(line, IMC_DATA_LENGTH, in);
sscanf(line, "Received %ld", &p->received);
p->usage=0;
return p;
}
/*
* maillist - a list of imc_mail entries
*/
imc_mail *imc_ml_head;
/* init_ml: init the maillist */
static void init_ml(void)
{
imc_ml_head = new_mail();
imc_ml_head->to = imc_strdup("");
imc_ml_head->from = imc_strdup("");
imc_ml_head->date = imc_strdup("");
imc_ml_head->subject = imc_strdup("");
imc_ml_head->text = imc_strdup("");
imc_ml_head->id = imc_strdup("");
}
/* free_ml: free the maillist */
static void free_ml(void)
{
imc_mail *p, *p_next;
for (p=imc_ml_head; p; p=p_next)
{
p_next=p->next;
p->usage=0; /* suppress warnings */
free_mail(p);
}
imc_ml_head=NULL;
}
/* add_ml: add an item to the maillist */
static void add_ml(imc_mail *p)
{
if (!p)
{
imc_logerror("BUG: add_ml: adding NULL pointer");
return;
}
p->next=imc_ml_head->next;
imc_ml_head->next=p;
}
/* find_ml: find a given ID in the mail list */
static imc_mail *find_ml(const char *id)
{
imc_mail *p;
if (!id)
{
imc_logerror("BUG: find_ml: NULL id");
return NULL;
}
for (p=imc_ml_head->next; p; p=p->next)
if (!strcasecmp(p->id, id))
return p;
return NULL;
}
/* delete_ml: delete a given node in the mail list */
static void delete_ml(imc_mail *node)
{
imc_mail *last, *p;
if (!node)
{
imc_logerror("BUG: delete_ml: NULL node");
return;
}
for (last=imc_ml_head, p=last->next; p && p != node; p=p->next)
;
if (p)
{
last->next = p->next;
free_mail(p);
}
else
imc_logerror("BUG: delete_ml: node at %p not on list", node);
}
/* save_ml: save maillist */
static void save_ml(void)
{
FILE *out;
imc_mail *p;
char name[200];
imc_sncpy(name, imc_prefix, 190);
strcat(name, "mail-list");
out=fopen(name, "w");
if (!out)
{
imc_lerror("save_ml: fopen");
return;
}
for (p=imc_ml_head->next; p; p=p->next)
write_mail(p, out);
fclose(out);
}
/* load_ml: load maillist, assumes init_ml done */
static void load_ml(void)
{
FILE *in;
imc_mail *p;
char name[200];
imc_sncpy(name, imc_prefix, 190);
strcat(name, "mail-list");
in=fopen(name, "r");
if (!in)
return;
p=read_mail(in);
while (p)
{
add_ml(p);
p=read_mail(in);
}
fclose(in);
}
/*
* qnode - an entry in the 'mail to send' queue, referencing a particular
* piece of mail, and the mud that this entry needs to send to
*/
/* new_qnode: get a new qnode */
static imc_qnode *new_qnode(void)
{
imc_qnode *p;
p=imc_malloc(sizeof(*p));
p->data = NULL;
p->next = NULL;
p->tomud = NULL;
return p;
}
/* free_qnode: free a qnode */
static void free_qnode(imc_qnode *q)
{
if (!q)
{
imc_logerror("BUG: free_qnode: freeing NULL pointer");
return;
}
if (q->tomud)
imc_strfree(q->tomud);
if (q->data && !--q->data->usage)
delete_ml(q->data);
imc_cancel_event(NULL, q);
imc_free(q, sizeof(*q));
}
/* write_qnode: write a qnode to a file */
static void write_qnode(imc_qnode *q, FILE *out)
{
if (!q)
{
imc_logerror("BUG: write_qnode: NULL pointer");
return;
}
fprintf(out, "%s %s\n", q->data->id, q->tomud);
}
/* read_qnode: read a qnode from a file */
static imc_qnode *read_qnode(FILE *in)
{
imc_qnode *p;
imc_mail *m;
char line[IMC_DATA_LENGTH];
char temp1[IMC_DATA_LENGTH], temp2[IMC_DATA_LENGTH];
fgets(line, IMC_DATA_LENGTH, in);
if (ferror(in) || feof(in))
return NULL;
sscanf(line, "%[^ ] %[^\n]", temp1, temp2);
m=find_ml(temp1);
if (!m)
{
imc_logerror("read_qnode: ID %s not in mail queue", temp1);
return NULL;
}
p=new_qnode();
m->usage++;
p->data = m;
p->tomud = imc_strdup(temp2);
return p;
}
/*
* mailqueue - a list of active qnodes
*/
imc_qnode *imc_mq_head, *imc_mq_tail;
/* init_mq: init mailqueue */
static void init_mq(void)
{
imc_mq_head = new_qnode();
imc_mq_tail = imc_mq_head;
imc_mq_head->data = NULL;
imc_mq_head->next = NULL;
}
/* free_mq: delete mailqueue */
static void free_mq(void)
{
imc_qnode *p, *p_next;
for (p=imc_mq_head; p; p=p_next)
{
p_next=p->next;
free_qnode(p);
}
imc_mq_head=imc_mq_tail=NULL;
}
/* add_mq: add a queue of items to the tail of the mq */
static void add_mq(imc_qnode *p)
{
imc_mq_tail->next=p;
while (p->next)
p=p->next;
imc_mq_tail=p;
}
#if 0
/* get_mq: extract the head of the mail queue */
static imc_qnode *get_mq(void)
{
imc_qnode *p;
if (imc_mq_head == imc_mq_tail) /* empty queue */
return NULL;
p=imc_mq_head->next;
imc_mq_head->next=p->next;
if (p == imc_mq_tail)
imc_mq_tail=imc_mq_head;
p->next=NULL; /* Just In Case */
return p;
}
#endif
/* find_mq: find the item with the given ID/tomud values */
static imc_qnode *find_mq(const char *id, const char *tomud)
{
imc_qnode *p;
for (p=imc_mq_head->next; p; p=p->next)
if (!strcmp(id, p->data->id) && !strcasecmp(tomud, p->tomud))
return p;
return NULL;
}
/* delete_mq: delete the item with the given ID/tomud values */
static void delete_mq(const char *id, const char *tomud)
{
imc_qnode *p, *last;
for (last=imc_mq_head, p=last->next; p; last=p, p=p->next)
if (!strcmp(id, p->data->id) && !strcasecmp(tomud, p->tomud))
{
last->next=p->next;
if (p == imc_mq_tail)
imc_mq_tail=last;
free_qnode(p);
return;
}
}
/* save mailqueue */
static void save_mq(void)
{
FILE *out;
imc_qnode *p;
char name[200];
imc_sncpy(name, imc_prefix, 189);
strcat(name, "mail-queue");
out=fopen(name, "w");
if (!out)
{
imc_lerror("save_mq: fopen");
return;
}
for (p=imc_mq_head->next; p; p=p->next)
write_qnode(p, out);
fclose(out);
}
/* load mailqueue, assumes init_mq done */
static void load_mq(void)
{
FILE *in;
imc_qnode *p;
char name[200];
int when=10;
imc_sncpy(name, imc_prefix, 189);
strcat(name, "mail-queue");
in=fopen(name, "r");
if (!in)
return;
p=read_qnode(in);
while (!feof(in) && !ferror(in))
{
if (p)
{
add_mq(p);
imc_add_event(when, ev_qnode_send, p, 1);
when += rand()%30+30;
}
p=read_qnode(in);
}
fclose(in);
}
/*
* mailid - a single mail ID that has been received
*/
/* new_mailid: get a new mailid */
static imc_mailid *new_mailid(void)
{
imc_mailid *p;
p=imc_malloc(sizeof(*p));
p->id = NULL;
p->received = 0;
p->next = NULL;
return p;
}
/* free_mailid: free a mailid */
static void free_mailid(imc_mailid *p)
{
if (!p)
{
imc_logerror("BUG: free_mailid: freeing NULL pointer");
return;
}
if (p->id)
imc_strfree(p->id);
imc_cancel_event(NULL, p);
imc_free(p, sizeof(*p));
}
/* generate_mailid: generate a new mailid (string) */
static char *generate_mailid(void)
{
char *buffer=imc_getsbuf(200);
sprintf(buffer, "%d-%ld@%s", rand(), imc_sequencenumber++, imc_name);
imc_shrinksbuf(buffer);
return buffer;
}
/* write_mailid: write a mailid to a file */
static void write_mailid(imc_mailid *p, FILE * out)
{
fprintf(out, "%s %ld\n", p->id, p->received);
}
/* read_mailid: read a mailid from a file, NULL on EOF */
static imc_mailid *read_mailid(FILE *in)
{
imc_mailid *p;
char line[IMC_DATA_LENGTH];
char temp[IMC_DATA_LENGTH];
time_t r;
fgets(line, IMC_DATA_LENGTH, in);
if (ferror(in) || feof(in))
return NULL;
sscanf(line, "%[^ ] %ld", temp, &r);
p=new_mailid();
p->id = imc_strdup(temp);
p->received = r;
return p;
}
/*
* idlist - a list of mail IDs received over the last 24 hours
*/
imc_mailid *imc_idlist;
/* init_idlist: init the ID list */
static void init_idlist(void)
{
imc_idlist=new_mailid();
}
/* free_idlist: free the idlist */
static void free_idlist(void)
{
imc_mailid *p, *p_next;
for (p=imc_idlist; p; p=p_next)
{
p_next=p->next;
free_mailid(p);
}
imc_idlist=NULL;
}
/* add_idlist: add an ID to the idlist */
static void add_idlist(imc_mailid *p)
{
p->next=imc_idlist->next;
imc_idlist->next=p;
}
/* find_id: check if an ID is in the ID list */
static imc_mailid *find_id(const char *id)
{
imc_mailid *p;
for (p=imc_idlist->next; p; p=p->next)
if (!strcmp(p->id, id))
return p;
return NULL;
}
/* flush_idlist: flush old entries from the mailseen list */
static void flush_idlist(time_t at)
{
imc_mailid *p, *last;
for (last=imc_idlist, p=last->next; p; p=p->next)
if (p->received < at) /* delete this entry */
{
last->next=p->next;
free_mailid(p);
p=last;
}
}
/* save_idlist: save idlist */
static void save_idlist(void)
{
FILE *out;
imc_mailid *p;
char name[200];
imc_sncpy(name, imc_prefix, 191);
strcat(name, "mail-ids");
out=fopen(name, "w");
if (!out)
{
imc_lerror("save_idlist: fopen");
return;
}
for (p=imc_idlist->next; p; p=p->next)
write_mailid(p, out);
fclose(out);
}
/* load_idlist: load idlist, assumes init_idlist done */
static void load_idlist(void)
{
FILE *in;
imc_mailid *p;
char name[200];
imc_sncpy(name, imc_prefix, 191);
strcat(name, "mail-ids");
in=fopen(name, "r");
if (!in)
return;
p=read_mailid(in);
while (p)
{
add_idlist(p);
p=read_mailid(in);
}
fclose(in);
}
/* datestring: generate a date string for the current time */
static char *datestring(void)
{
char *buf=imc_getsbuf(100);
strcpy(buf, ctime(&imc_now));
buf[strlen(buf)-1]=0;
imc_shrinksbuf(buf);
return buf;
}
/* bounce: generate a local bounce note */
static void bounce(imc_mail *item, const char *source, const char *reason)
{
char temp[IMC_DATA_LENGTH];
sprintf(temp,
"Your mail of %s:\n\r"
" to: %s\n\r"
" re: %s\n\r"
"was undeliverable for the following reason:\n\r"
"\n\r"
"%s: %s\n\r",
item->date,
item->to,
item->subject,
source,
reason);
imc_mail_arrived("Mail-daemon", imc_nameof(item->from), datestring(),
"Bounced mail", temp);
}
/* expire old entries in the mailid list; called once an hour */
void ev_mailid_expire(void *data)
{
flush_idlist(imc_now + 24*3600);
imc_add_event(3600, ev_mailid_expire, NULL, 1);
}
/* give up sending a given qnode */
void ev_qnode_expire(void *data)
{
char temp[200];
imc_qnode *p=(imc_qnode *)data;
sprintf(temp, "Unable to send to %s after 12 hours, giving up", p->tomud);
bounce(p->data, imc_name, temp);
delete_mq(p->data->id, p->tomud);
save_ml();
save_mq();
}
/* try sending a qnode */
void ev_qnode_send(void *data)
{
imc_qnode *p=(imc_qnode *)data;
imc_packet out;
save_ml();
save_mq();
/* send it.. */
imc_initdata(&out.data);
sprintf(out.to, "Mail-daemon@%s", p->tomud);
strcpy(out.from, "Mail-daemon");
strcpy(out.type, "mail");
imc_addkey(&out.data, "to", p->data->to);
imc_addkey(&out.data, "from", p->data->from);
imc_addkey(&out.data, "subject", p->data->subject);
imc_addkey(&out.data, "date", p->data->date);
imc_addkey(&out.data, "text", p->data->text);
imc_addkey(&out.data, "id", p->data->id);
imc_send(&out);
imc_freedata(&out.data);
/* try resending it in an hour */
imc_add_event(3600, ev_qnode_send, data, 1);
}
/* imc_recv_mailok: a mail-ok packet was received */
void imc_recv_mailok(const char *from, const char *id)
{
delete_mq(id, imc_mudof(from));
save_mq();
save_ml(); /* we might have removed the mail if usage==0 */
}
/* imc_recv_mailrej: a mail-reject packet was received */
void imc_recv_mailrej(const char *from, const char *id, const char *reason)
{
imc_qnode *p;
p = find_mq(id, imc_mudof(from));
if (!p)
return;
bounce(p->data, from, reason);
delete_mq(id, imc_mudof(from));
save_mq();
save_ml();
}
/* addrtomud: convert a 'to' list to the local mud format (ie strip @mudname
* when it matches imc_name)
*/
static void addrtomud(const char *list, char *output)
{
char arg[IMC_NAME_LENGTH];
output[0]=0;
list=imc_getarg(list, arg, IMC_NAME_LENGTH);
while (*arg)
{
if (!strcasecmp(imc_name, imc_mudof(arg)))
sprintf(output + strlen(output), "%s ", imc_nameof(arg));
else
sprintf(output + strlen(output), "%s ", arg);
list=imc_getarg(list, arg, IMC_NAME_LENGTH);
}
}
/* mudtoaddr: add the @mudname to a 'to' list for unqualified names */
static void mudtoaddr(const char *list, char *output)
{
char arg[IMC_NAME_LENGTH];
output[0]=0;
list=imc_getarg(list, arg, IMC_NAME_LENGTH);
while (*arg)
{
if (strchr(arg, '@') == NULL)
sprintf(output + strlen(output), "%s@%s ", arg, imc_name);
else
sprintf(output + strlen(output), "%s ", arg);
list=imc_getarg(list, arg, IMC_NAME_LENGTH);
}
/* chop final space */
if (arg[0] && arg[strlen(arg) - 1] == ' ')
arg[strlen(arg) - 1] = 0;
}
/* imc_recv_mail: a mail packet was received */
void imc_recv_mail(const char *from, const char *to, const char *date,
const char *subject, const char *id, const char *text)
{
imc_mailid *mid;
imc_packet out;
char *reason;
char temp[IMC_DATA_LENGTH];
imc_initdata(&out.data);
sprintf(out.to, "Mail-daemon@%s", imc_mudof(from));
strcpy(out.from, "Mail-daemon");
/* check if we've already seen it */
mid=find_id(id);
if (mid)
{
strcpy(out.type, "mail-ok");
imc_addkey(&out.data, "id", id);
imc_send(&out);
imc_freedata(&out.data);
mid->received = imc_now;
return;
}
/* check for rignores */
if (imc_isignored(from))
{
strcpy(out.type, "mail-reject");
imc_addkey(&out.data, "id", id);
imc_addkey(&out.data, "reason", "You are being ignored.");
imc_send(&out);
imc_freedata(&out.data);
return;
}
/* forward it to the mud */
addrtomud(to, temp);
if ((reason=imc_mail_arrived(from, temp, date, subject, text)) == NULL)
{
/* it was OK */
strcpy(out.type, "mail-ok");
imc_addkey(&out.data, "id", id);
imc_send(&out);
imc_freedata(&out.data);
mid=new_mailid();
mid->id=imc_strdup(id);
mid->received=imc_now;
add_idlist(mid);
save_idlist();
return;
}
/* mud rejected the mail */
strcpy(out.type, "mail-reject");
imc_addkey(&out.data, "id", id);
imc_addkey(&out.data, "reason", reason);
imc_send(&out);
imc_freedata(&out.data);
}
/* imc_send_mail: called by the mud to add a piece of mail to the queue */
void imc_send_mail(const char *from, const char *to, const char *date,
const char *subject, const char *text)
{
char temp[IMC_DATA_LENGTH];
imc_mail *m;
imc_qnode *qroot, *q;
char arg[IMC_NAME_LENGTH];
const char *mud;
int when=10;
/* set up the entry for the mail list */
m=new_mail();
mudtoaddr(to, temp); /* qualify local addresses */
m->to = imc_strdup(temp);
sprintf(temp, "%s@%s", from, imc_name); /* qualify sender */
m->from = imc_strdup(temp);
m->date = imc_strdup(date);
m->subject = imc_strdup(subject);
m->id = imc_strdup(generate_mailid());
m->text = imc_strdup(text);
m->received = imc_now;
qroot=NULL; /* initialise the local list */
to=imc_getarg(to, arg, IMC_NAME_LENGTH);
while (*arg)
{
/* get a mudname and check if we've already added a queue entry for that
* mud. If not, add it
*/
if (strchr(arg, '@') != NULL && (mud = imc_mudof(arg))[0] != 0 &&
strcasecmp(mud, imc_name))
{
if (!strcmp(mud, "*"))
q=NULL; /* catch the @* case - not yet implemented */
else
for (q=qroot; q; q=q->next)
if (!strcasecmp(q->tomud, mud))
break;
if (!q) /* not seen yet */
{ /* add to the top of our mini-queue */
q=new_qnode();
q->tomud=imc_strdup(mud);
q->data=m;
q->next=qroot;
m->usage++;
qroot=q;
imc_add_event(when, ev_qnode_send, q, 1);
when += rand()%30+30;
}
}
/* get the next address */
to=imc_getarg(to, arg, IMC_NAME_LENGTH);
}
if (!qroot) /* boggle, no foreign addresses?? */
{
free_mail(m);
return;
}
/* add mail to mail list, add mini-queue to mail queue */
add_ml(m);
add_mq(qroot);
save_ml();
save_mq();
}
/* imc_mail_startup: start up the mail subsystem */
void imc_mail_startup(void)
{
init_mq();
init_ml();
init_idlist();
/* order is important here: we need the maillist to resolve the ID refs in
* the mailqueue
*/
load_ml();
load_mq();
load_idlist();
/* queue an expiry event */
imc_add_event(24*3600, ev_mailid_expire, NULL, 0);
}
/* imc_mail_shutdown: shut down the mailer */
void imc_mail_shutdown(void)
{
save_mq();
save_ml();
save_idlist();
free_mq();
free_ml();
free_idlist();
imc_cancel_event(ev_mailid_expire, NULL);
}
/* imc_mail_showqueue: returns the current mail queue
* buffer handling here is pretty ugly, oh well
*/
char *imc_mail_showqueue(void)
{
char *buf=imc_getsbuf(IMC_DATA_LENGTH);
char temp[100];
imc_qnode *p;
int left = IMC_DATA_LENGTH;
sprintf(buf, "%-15s %-45s %-10s %s\n\r", "From", "To", "Via", "Time");
for (p=imc_mq_head->next; p && left > 160; p = p->next)
{
int m, s;
m=imc_next_event(ev_qnode_send, p);
if (m<0)
sprintf(temp, "%-15.15s %-45.45s %-10.10s --:--\n\r",
p->data->from,
p->data->to,
p->tomud);
else
{
s=m%60;
m/=60;
sprintf(temp, "%-15.15s %-45.45s %-10.10s %2d:%02d\n\r",
p->data->from,
p->data->to,
p->tomud,
m, s);
}
left -= strlen(temp);
strcat(buf, temp);
}
if (p)
{
int count;
for (count=0; p; p=p->next, count++)
;
sprintf(temp, "[%d further entries omitted]\n\r", count);
strcat(buf, temp);
}
imc_shrinksbuf(buf);
return buf;
}