static char rcsid[] = "@(#)$Id: reply.c,v 1.46 2006/04/09 07:37:19 hurtta Exp $";
/******************************************************************************
* The Elm (ME+) Mail System - $Revision: 1.46 $ $State: Exp $
*
* Modified by: Kari Hurtta <hurtta+elm@posti.FMI.FI>
* (was hurtta+elm@ozone.FMI.FI)
******************************************************************************
* The Elm Mail System
*
* Copyright (c) 1988-1992 USENET Community Trust
* Copyright (c) 1986,1987 Dave Taylor
*****************************************************************************/
/*** routine allows replying to the sender of the current message
***/
#include "def_elm.h"
#include "s_elm.h"
DEBUG_VAR(Debug,__FILE__,"mail");
static char *us2s P_((unsigned char *str));
static char *us2s(str)
unsigned char *str;
{
return (char *)str;
}
/** Note that this routine generates automatic header information
for the subject and (obviously) to lines, but that these can
be altered while in the editor composing the reply message!
**/
#include <errno.h>
#ifndef ANSI_C
extern int errno;
#endif
/* Prototype */
static void get_reply_subj P_((char *, char *, char *, int));
/* Determine the subject to use for a reply. */
static void get_reply_subj(out_subj,in_subj,dflt_subj, size)
char *out_subj; /* store the resulting subject here */
char *in_subj; /* subject of the original message */
char *dflt_subj; /* default to use if "in_subj" is empty */
int size;
{
if ( *in_subj == '\0' ) {
strfcpy(out_subj,dflt_subj, size);
return;
}
if (
( in_subj[0] == 'r' || in_subj[0] == 'R' ) &&
( in_subj[1] == 'e' || in_subj[1] == 'E' ) &&
( in_subj[2] == ':' )
) {
for ( in_subj += 3 ; whitespace(*in_subj) ; ++in_subj ) ;
}
strfcat( strfcpy( out_subj, "Re: ", size ), in_subj, size);
}
static int optimize_and_add P_((CONST struct addr_item * new_address,
struct addr_item **full_address));
static int optimize_and_add(new_address, full_address)
CONST struct addr_item * new_address;
struct addr_item ** full_address;
{
int len;
struct addr_item * result = *full_address;
/* This routine will add the new address to the list of addresses
* in the full address buffer IFF it doesn't already occur.
*
* full_address buffer is realloced if needed.
*/
for (len = 0;
result &&
result[len].addr && result[len].fullname && result[len].comment;
len++) {
if (0 == strcmp(new_address->addr,result[len].addr)) {
/* same address */
if (!string_len(result[len].fullname)) {
free_string(&result[len].fullname);
result[len].fullname = dup_string(new_address->fullname);
}
DPRINT(Debug,15,(&Debug, "optimize_and_add = 1:\n"));
DPRINT(Debug,15,(&Debug, " result[%d].addr=%s\n",
len,result[len].addr));
return(1); /* duplicate address */
}
}
result = safe_realloc(result,(len+2) * sizeof (struct addr_item));
result[len].addr = safe_strdup(new_address->addr);
result[len].fullname = dup_string(new_address->fullname);
result[len].comment = dup_string(new_address->comment);
result[len+1].addr = NULL;
result[len+1].fullname = NULL;
result[len+1].comment = NULL;
DPRINT(Debug,15,(&Debug, "optimize_and_add = 0:\n"));
DPRINT(Debug,15,(&Debug,
" result[%d].fullname=%S\n",
len,result[len].fullname));
DPRINT(Debug,15,(&Debug, " result[%d].addr=%s\n",
len,result[len].addr));
DPRINT(Debug,15,(&Debug,
" result[%d].comment=%S\n",
len,result[len].comment));
DPRINT(Debug,15,(&Debug,
" *full_address=result=%p\n",result));
*full_address = result;
return(0);
}
void append_addresses_to_addr_list (target,list)
struct addr_item **target;
CONST struct addr_item *list;
{
CONST struct addr_item *ptr;
for (ptr = list;
ptr && ptr -> addr && ptr->fullname && ptr->comment;
ptr++) {
optimize_and_add(ptr, target);
}
}
struct addr_item * handle_reply_to(current_header, infile)
struct header_rec * current_header;
FILE *infile;
{
struct addr_item * result = NULL;
struct addr_item * return_address = current_header->from;
int count=0;
header_list_ptr all_headers;
/** First off, get to the first line of the message desired **/
/* skip_envelope() does seek */
if (skip_envelope(current_header, infile) == -1) {
int err = errno;
DPRINT(Debug,1,(&Debug,
"Error: seek %ld resulted in errno %s (%s)\n",
current_header->offset, error_description(err),
"handle_reply_to"));
lib_error(CATGETS(elm_msg_cat, ElmSet, ElmSeekFailedFile,
"ELM [seek] couldn't read %d bytes into file (%s)."),
current_header->offset, error_description(err));
return NULL;
}
/** now let's parse the actual message! **/
/* read_header_line reads also continuation lines ...
* 2 == check that this is header line...
*/
all_headers = file_read_headers(infile,RHL_CHECK_HEADER);
if (all_headers) {
header_list_ptr walk;
for (walk = locate_header_by_name(all_headers,"Reply-To");
walk;
walk = walk->next_this_header) {
struct addr_item * addrs,*ptr;
addrs = break_down_address(walk->body,
!(current_header -> status & NOHDRENCODING) &&
is_rfc1522(walk->body),
display_charset /* ????????? */
);
/* go through all addresses in this line */
for (ptr = addrs;
ptr && ptr -> addr && ptr->fullname && ptr->comment;
ptr++) {
/* do NOT add sender's domain in here ! It was MTA's task
and if it is not added then there is good reason.
(adding of senders domain to unqualified addresses
BREAKS my local mailing lists!!!)
That is: It is possible that Reply-to is written by some
other site than From !!!
*/
if (!optimize_and_add(ptr, &result)) {
count++;
DPRINT(Debug,3,(&Debug,
"handle_reply_to: #%d: addr=%s\n",
count,ptr -> addr));
}
}
if (addrs)
free_addr_items(addrs);
}
delete_headers(&all_headers);
}
/* if there wasn't reply-to header */
if (!count) {
struct addr_item *ptr;
for (ptr = return_address;
ptr && ptr -> addr && ptr->fullname && ptr->comment;
ptr++) {
if (!optimize_and_add(ptr, &result)) {
DPRINT(Debug,3,(&Debug,
"handle_reply_to: RETADR: addr=%s\n",
ptr -> addr));
}
}
}
if (!result) {
result = safe_realloc(result,2 * sizeof (struct addr_item));
result[0].addr = safe_strdup(current_header->env_from);
result[0].fullname = new_string(display_charset);
result[0].comment = new_string(display_charset);
result[1].addr = NULL;
result[1].fullname = NULL;
result[1].comment = NULL;
DPRINT(Debug,3,(&Debug,
"handle_reply_to: ENV: addr=%s\n",
result[0].addr));
}
return result;
}
struct addr_item * get_and_expand_everyone(current_header,
return_address)
struct header_rec * current_header;
struct addr_item * return_address;
{
enum { h_to = 0, h_cc = 1, h_done = 2 } count;
struct addr_item * result = NULL;
char * env_host = qstrpbrk(current_header->env_from,":");
/* skip route */
if (env_host)
env_host = qstrpbrk(env_host,"@");
else
env_host = qstrpbrk(current_header->env_from,"@");
/** As each address is taken, ensure that it
isn't to the author of the message NOR to us. If neither,
prepend with current return address and append to result
**/
for (count = h_to ; count < h_done; count++) {
struct addr_item * addrs = NULL, *ptr;
switch (count) {
case h_to: addrs = current_header->to; break;
case h_cc: addrs = current_header->cc; break;
}
/* go through all addresses in this line */
for (ptr = addrs;
ptr && ptr -> addr && ptr->fullname && ptr->comment;
ptr++) {
if (0 == strcmp(ptr->addr,current_header->env_from))
continue;
if (okay_address_l(ptr, return_address)) {
/**
Some mailers can emit unqualified addresses in the
headers, e.g. a Cc to a local user might appear as
just "user" and not "user@dom.ain". We do a real
low-rent check here. If it looks like a domain
address then we will pass it through. Otherwise we
tag domain from envelope sender address.
**/
if (qchloc(ptr->addr, '@') <= 0 && env_host)
ptr->addr = strmcat(ptr->addr,env_host);
if (!optimize_and_add(ptr,&result)) {
DPRINT(Debug,3,(&Debug,
"get_and_expand_everyone: got: addr=%s, fullname=%S, comment=%S\n",
ptr -> addr, ptr -> fullname, ptr->comment));
}
}
}
}
return result;
}
void reply(index, mailbox, aview, page, prompt_area, header_area)
int index;
struct MailboxView *mailbox;
struct AliasView *aview;
struct menu_context *page;
struct menu_context *prompt_area;
struct menu_context *header_area;
{
/** Reply to the current message.
If screen nedd to redraw, use menu_trigger_redraw(page)
**/
char subject[SLEN];
int form_letter;
struct addr_item * rt = NULL;
struct string * temp1 = NULL;
char * temp2 = NULL;
FILE *F = NULL;
struct header_rec *current_header = NULL;
if (!give_message_data(mailbox,index,
¤t_header,&F,NULL,
NO_mime_parse))
return;
if (! current_header || !F)
return;
rt = handle_reply_to(current_header,F);
if (current_header->subject)
temp1 = convert_string(display_charset,current_header->subject,1);
else
temp1 = new_string(display_charset);
temp2 = us2s(stream_from_string(temp1,1,NULL));
form_letter = (current_header->status & FORM_LETTER);
/* TODO: Fix this mess... */
get_reply_subj( subject, temp2,
( form_letter ?
catgets(elm_msg_cat, ElmSet, ElmFilledInForm,
"Filled in form") :
catgets(elm_msg_cat, ElmSet, ElmReYourMail,
"Re: your mail") ),
sizeof subject);
if (form_letter) {
mail_filled_in_form(index,rt, subject,
mailbox, aview,
page); /* Generates always new page ... */
} else {
int r = send_msg_l(index,rt, NULL, subject,
MAIL_EDIT_MSG | MAIL_REPLYING,
NO, mailbox, aview,
page, prompt_area);
if (r) {
if (me_retcode) {
DPRINT(Debug,5,(&Debug,
"reply: Setting replied flag\n"));
current_header->status |= REPLIED;
current_header->status_chgd = TRUE;
}
}
}
if (rt)
free_addr_items(rt);
free_string(&temp1);
free(temp2);
/* XXX Is this needed? */
if (header_area) {
int vis;
struct menu_common MENU;
set_mcommon_from_mbxview(&MENU,mailbox);
vis = compute_visible(index+1, &MENU);
menu_header_status_update(header_area,vis-1);
}
return;
}
void reply_to_everyone(index,mailbox, aview, page, prompt_area, header_area)
int index;
struct MailboxView *mailbox /* for save_copy */;
struct AliasView *aview;
struct menu_context *page;
struct menu_context *prompt_area;
struct menu_context *header_area;
{
/** Reply to everyone who received the current message.
This includes other people in the 'To:' line and people
in the 'Cc:' line too. Returns non-zero iff the screen
has to be rewritten. **/
char subject[SLEN];
struct addr_item * rt;
struct addr_item * cc;
struct string * temp1 = NULL;
char * temp2 = NULL;
FILE *F = NULL;
struct header_rec *current_header = NULL;
int r;
if (!give_message_data(mailbox,index,
¤t_header,&F,NULL,
NO_mime_parse))
return;
if (! current_header || !F)
return;
rt = handle_reply_to(current_header,F);
cc = get_and_expand_everyone(current_header,rt);
if (current_header->subject)
temp1 = convert_string(display_charset,current_header->subject,1);
else
temp1 = new_string(display_charset);
temp2 = us2s(stream_from_string(temp1,1,NULL));
/* TODO: Fix this mess ... */
get_reply_subj( subject, temp2,
catgets(elm_msg_cat, ElmSet, ElmReYourMail,
"Re: your mail"),
sizeof subject);
r = send_msg_l(index,rt, cc, subject,
MAIL_EDIT_MSG | MAIL_REPLYING,
NO, mailbox, aview,
page, prompt_area);
if (r) {
if (me_retcode) {
DPRINT(Debug,5,(&Debug,
"reply_to_everyone: Setting replied flag\n"));
current_header->status |= REPLIED;
current_header->status_chgd = TRUE;
}
}
if (rt)
free_addr_items(rt);
if (cc)
free_addr_items(cc);
free_string(&temp1);
free(temp2);
/* XXX Is this needed? */
if (header_area) {
int vis;
struct menu_common MENU;
set_mcommon_from_mbxview(&MENU,mailbox);
vis = compute_visible(index+1, &MENU);
menu_header_status_update(header_area,vis-1);
}
return;
}
void forward(index,mailbox,aview, page, prompt_area,header_area)
int index;
struct MailboxView *mailbox;
struct AliasView *aview;
struct menu_context *page;
struct menu_context *prompt_area;
struct menu_context *header_area;
{
/** Forward the current message. What this actually does is
to temporarily set forwarding to true, then call 'send' to
get the address and route the mail. Modified to also set
'noheader' to FALSE also, so that the original headers
of the message sent are included in the message body also.
if the main part of the screen has been changed
use menu_trigger_redraw()
**/
char subject[SLEN];
int edit_msg = FALSE;
int li, co;
struct header_rec *current_header = give_header(mailbox,index);
menu_get_sizes(prompt_area, &li, &co);
if (! current_header)
return;
if (current_header->status & FORM_LETTER)
menu_PutLineX(prompt_area,0,co-40,
CATGETS(elm_msg_cat, ElmSet,
ElmNoEditingAllowed,
"<No editing allowed.>"));
else {
/* NOTICE: prompt_letter may return EOF */
int X1;
int delay_redraw = 0;
again:
X1 = prompt_letter(0,"",*def_ans_yes,
PROMPT_yesno|PROMPT_redraw_mark|PROMPT_ctrlL|
PROMPT_cancel,prompt_area,
CATGETS(elm_msg_cat, ElmSet, ElmEditOutgoingMessage,
"Edit outgoing message? (%c/%c) "),
*def_ans_yes, *def_ans_no);
if (('L'&31) == X1 ||
REDRAW_MARK == X1) {
menu_ClearScreen(page); /* Reset possible redraw flag */
/* Call refresh routines of children */
menu_redraw_children(page);
if (menu_need_redraw(prompt_area))
menu_ClearScreen(prompt_area); /* clear redraw flag */
delay_redraw++; /* Can't trigger redraw yet... */
goto again;
}
if (delay_redraw)
menu_trigger_redraw(page);
if (TERMCH_interrupt_char == X1)
goto out;
if (EOF == X1)
goto out;
edit_msg = (X1 != *def_ans_no);
}
if (current_header-> subject &&
string_len(current_header->subject) > 0) {
struct string * temp1 = NULL;
char * temp2 = NULL;
temp1 = convert_string(display_charset,
current_header->subject,1);
temp2 = us2s(stream_from_string(temp1,1,NULL));
strfcpy(subject, temp2, sizeof subject);
/* this next strange compare is to see if the last few chars are
already '(fwd)' before we tack another on */
if (strlen(subject) < 6 || (strcmp(subject+strlen(subject)-5,
"(fwd)") != 0))
strfcat(subject, " (fwd)", sizeof subject);
send_msg_l(index,
NULL, NULL, subject,
(edit_msg ? MAIL_EDIT_MSG : 0 ) |
MAIL_FORWARDING,
current_header->status & FORM_LETTER ?
PREFORMATTED : allow_forms,
mailbox, aview,
page, prompt_area);
free_string(&temp1);
free(temp2);
}
else
send_msg_l(index,
NULL, NULL,
/* TODO: Fix this mess... */
catgets(elm_msg_cat, ElmSet, ElmForwardedMail,
"Forwarded mail..."),
(edit_msg ? MAIL_EDIT_MSG : 0 ) |
MAIL_FORWARDING,
current_header->status & FORM_LETTER ?
PREFORMATTED : allow_forms,
mailbox, aview,
page, prompt_area);
out:
menu_trigger_redraw(prompt_area);
if (header_area) {
int vis;
struct menu_common MENU;
set_mcommon_from_mbxview(&MENU,mailbox);
vis = compute_visible(index+1, &MENU);
menu_header_status_update(header_area,vis-1);
}
return;
}
void get_return_name(address, name, trans_to_lowercase, size)
char *address, *name;
int trans_to_lowercase;
int size;
{
/** Given the address (either a single address or a combined list
of addresses) extract the login name of the first person on
the list and return it as 'name'. Modified to stop at
any non-alphanumeric character. **/
/** An important note to remember is that it isn't vital that this
always returns just the login name, but rather that it always
returns the SAME name. If the persons' login happens to be,
for example, joe.richards, then it's arguable if the name
should be joe, or the full login. It's really immaterial, as
indicated before, so long as we ALWAYS return the same name! **/
/** Another note: modified to return the argument as all lowercase
always, unless trans_to_lowercase is FALSE... **/
/**
* Yet another note: Modified to return a reasonable name
* even when double quoted addresses and DecNet addresses
* are embedded in a domain style address.
**/
char single_address[SLEN], *sa;
register int i, loc, iindex = 0,
end, first = 0;
register char *c;
DPRINT(Debug,6,(&Debug,
"get_return_name called with (%s, <>, shift=%s, size=%d)\n",
address, onoff(trans_to_lowercase), size));
/* First step - copy address up to a comma, space, or EOLN */
for (sa = single_address; *address &&
sa - single_address < sizeof single_address -1 ; ) {
i = len_next_part(address);
if (i > 1) {
while (--i >= 0 &&
sa - single_address < sizeof single_address -1)
*sa++ = *address++;
} else if (*address == ',' || whitespace(*address))
break;
else
*sa++ = *address++;
}
*sa = '\0';
/* Now is it an Internet address?? */
if ((loc = qchloc(single_address, '@')) != -1) { /* Yes */
/*
* Is it a double quoted address?
*/
if (single_address[0] == '"') {
first = 1;
/*
* Notice `end' will really be the index of
* the last char in a double quoted address.
*/
loc = ((end = chloc (&single_address[1], '"')) == -1)
? loc
: end;
}
else {
first = 0;
}
/*
* Hope it is not one of those weird X.400
* addresses formatted like
* /G=Jukka/S=Ukkonen/O=CSC/@fumail.fi
*/
if (single_address[first] == '/') {
/* OK then, let's assume it is one of them. */
iindex = 0;
if ((c = strstr (&single_address[first], "/G"))
|| (c = strstr (&single_address[first], "/g"))) {
for (c += 2; *c && (*c++ != '='); );
for ( ;*c && (*c != '/') && iindex < size-1; c++) {
name[iindex++] = trans_to_lowercase
#ifdef ASCII_CTYPE
&& isascii(*c)
#endif
? tolower ((unsigned char)*c) : *c;
}
if (iindex > 0 && iindex < size-1) {
name[iindex++] = '.';
}
}
if ((c = strstr (&single_address[first], "/S"))
|| (c = strstr (&single_address[first], "/s"))) {
for (c += 2; *c && (*c++ != '='); );
for ( ;*c && (*c != '/') && iindex < size-1; c++) {
name[iindex++] = trans_to_lowercase
#ifdef ASCII_CTYPE
&& isascii(*c)
#endif
? tolower ((unsigned char)*c) : *c;
}
}
name[iindex] = '\0';
for (c = name; *c; c++) {
*c = ((*c == '.') || (*c == '-') || isalnum (*c))
? *c : '_';
}
if (iindex == 0) {
strfcpy (name, "X.400.John.Doe", size);
}
return;
}
/*
* Is it an embedded DecNet address?
*/
while (NULL != (c =
strstr (&single_address[first], "::"))) {
first = c - single_address + 2;
}
/*
* At this point the algorithm is to keep shifting our
* copy window left until we hit a '!'. The login name
* is then located between the '!' and the first meta-
* character to it's right (ie '%', ':', '/' or '@').
*/
for (i=loc; single_address[i] != '!' && i > first-1; i--)
if (single_address[i] == '%' ||
single_address[i] == ':' ||
single_address[i] == '/' ||
single_address[i] == '@') loc = i-1;
if (i < first || single_address[i] == '!') i++;
for (iindex = 0; iindex < loc - i + 1 && iindex < size-1; iindex++)
if (trans_to_lowercase
#ifdef ASCII_CTYPE
&& isascii(single_address[iindex+i])
#endif
)
name[iindex] = tolower((unsigned char)
single_address[iindex+i]);
else
name[iindex] = single_address[iindex+i];
name[iindex] = '\0';
}
else { /* easier - standard USENET address */
/*
* This really is easier - we just cruise left from
* the end of the string until we hit either a '!'
* or the beginning of the line. No sweat.
*/
loc = strlen(single_address)-1; /* last char */
for (i = loc;
i > -1 && single_address[i] != '!'
&& single_address[i] != '.' && iindex < size-1;
i--) {
if (trans_to_lowercase
#ifdef ASCII_CTYPE
&& isascii(single_address[iindex+i])
#endif
)
name[iindex++] = tolower((unsigned char)single_address[i]);
else
name[iindex++] = single_address[i];
}
name[iindex] = '\0';
reverse(name);
}
return;
}
/*
* Local Variables:
* mode:c
* c-basic-offset:4
* buffer-file-coding-system: iso-8859-1
* End:
*/
syntax highlighted by Code2HTML, v. 0.9.1