/* This module was written by
 *
 * Eduardo Chappa (chappa@washington.edu)
 * http://alpine.x10host.com/alpine/
 *
 *  Original Version: November 1999
 *  Last Modified   : November 24, 2018
 *
 * Send bug reports about this module to the address above.
 */

#include "../pith/headers.h"
#include "../pith/state.h"
#include "../pith/conf.h"
#include "../pith/copyaddr.h"
#include "../pith/mailindx.h"
#include "../pith/rules.h"

#define CSEP_C	('\001')
#define CSEP_S	("\001")

/* Internal Prototypes */

int   test_condition  (CONDITION_S *, int, ENVELOPE *);
int   test_in         (CONDITION_S *, TOKEN_VALUE *, ENVELOPE *, int);
int   test_ni         (CONDITION_S *, TOKEN_VALUE *, ENVELOPE *, int);
int   test_not_in     (CONDITION_S *, TOKEN_VALUE *, ENVELOPE *, int);
int   test_not_ni     (CONDITION_S *, TOKEN_VALUE *, ENVELOPE *, int);
int   test_eq         (CONDITION_S *, TOKEN_VALUE *, ENVELOPE *, int);
int   test_not_eq     (CONDITION_S *, TOKEN_VALUE *, ENVELOPE *, int);
int   isolate_condition (char *, char **, int *);
int   sanity_check_condition (char *);
char  *test_rule      (RULELIST *, int, ENVELOPE *, int *);
char  *trim	      (RULEACTION_S *, int, ENVELOPE *);
char  *rextrim	      (RULEACTION_S *, int, ENVELOPE *);
char  *rexsub	      (RULEACTION_S *, int, ENVELOPE *);
char  *do_rextrim     (char *, TOKEN_VALUE *);
char  *do_rexsub      (char *, TOKEN_VALUE *);
char  *raw_value      (RULEACTION_S *, int, ENVELOPE *);
char  *extended_value (RULEACTION_S *, int, ENVELOPE *);
char  *exec_fcn	      (RULEACTION_S *, int, ENVELOPE *);
char  *expand         (char *, char *);
char  *get_name_token (char *);
char  *advance_to_char (char *, char, int, int *);
char  **functions_for_token (char *);
char  *canonicalize_condition (char *, int *);
void  free_rexsub (REXSUB_S **);
void  free_void_token_value (void **, int);
void  free_token_value (TOKEN_VALUE **);
void  free_condition  (CONDITION_S **);
void  free_condition_value (CONDVALUE_S **);
void  free_ruleaction (RULEACTION_S **);
void  free_rule       (RULE_S **);
void  free_rule_list  (RULELIST **);
void  *rule_alloc_mem (size_t);
void  add_rule        (int, int);
void  set_rule_list    (struct variable *);
void  free_parsed_value(TOKEN_VALUE **value);
RULE_S   *parse_rule  (char *, int);
RULELIST *get_rule_list (char **, int, int);
TOKEN_VALUE *parse_group_data (char *,int *);
TOKEN_VALUE *copy_parsed_value (TOKEN_VALUE *, int, ENVELOPE *);
TOKEN_VALUE *parse_action_to_char(char *);
TOKEN_VALUE *parse_rexsub_action(char *);
CONDVALUE_S *fill_condition_value (char *);
CONDITION_S *fill_condition (char *);
CONDITION_S *parse_condition (char *, int *);
PRULELIST_S *add_prule        (PRULELIST_S *, PRULELIST_S *);
RULEACTION_S *parse_action (char *, int);

REL_TOKEN rel_rules_test[] = {
   {EQ_REL,     Equal,          test_eq},
   {IN_REL,     Subset,         test_in},
   {NI_REL,     Includes,       test_ni},
   {NOT_EQ_REL, NotEqual,       test_not_eq},
   {NOT_IN_REL, NotSubset,      test_not_in},
   {NOT_NI_REL, NotIncludes,    test_not_ni},
   {NULL,       EndTypes,       NULL}
};

#define NREL  (sizeof(rel_rules_test)/sizeof(rel_rules_test[0]) - 1)

RULE_FCN rule_fcns[] = {
{COPY_FCN,	6,	1,	CHAR_TYPE,	parse_action_to_char,	extended_value,	FOR_SAVE|FOR_COMPOSE},
{REXTRIM_FCN,	9,	1,	CHAR_TYPE,	parse_action_to_char,	rextrim,	FOR_REPLACE|FOR_TRIM|FOR_RESUB|FOR_COMPOSE},
{REXSUB_FCN,	8,	1,	REXSUB_TYPE,	parse_rexsub_action,	rexsub,		FOR_REPLACE|FOR_TRIM|FOR_RESUB|FOR_COMPOSE},
{EXEC_FCN,	6,	1,	CHAR_TYPE,	parse_action_to_char,	exec_fcn,	FOR_REPLACE|FOR_TRIM|FOR_RESUB|FOR_COMPOSE},
{TRIM_FCN,	6,	1,	CHAR_TYPE,	parse_action_to_char,	trim,		FOR_REPLACE|FOR_TRIM|FOR_RESUB|FOR_COMPOSE},
{REPLACE_FCN,	9,	1,	CHAR_TYPE,	parse_action_to_char,	extended_value,	FOR_REPLACE},
{SAVE_FCN,	6,	0,	UNDEFINED_TYPE,	NULL,			extended_value,	FOR_SAVE},
{REPLY_FCN,	7,	0,	UNDEFINED_TYPE,	NULL,			extended_value,	FOR_REPLY_INTRO},
{SORT_FCN,	6,	0,	UNDEFINED_TYPE,	NULL,			raw_value,	FOR_SORT},
{INDEX_FCN,	7,	0,	UNDEFINED_TYPE,	NULL,			raw_value,	FOR_INDEX},
{COMMAND_FCN,	9,	0,	UNDEFINED_TYPE,	NULL,			raw_value,	FOR_KEY},
{REPLYSTR_FCN, 10,	0,	UNDEFINED_TYPE,	NULL,			raw_value,	FOR_COMPOSE},
{SIGNATURE_FCN,11,	0,	UNDEFINED_TYPE,	NULL,			raw_value,	FOR_COMPOSE},
{RESUB_FCN,	7,	0,	UNDEFINED_TYPE,	NULL,			extended_value,	FOR_RESUB},
{STARTUP_FCN,	9,	0,	UNDEFINED_TYPE,	NULL,			raw_value,	FOR_STARTUP},
{THRDSTYLE_FCN,11,	0,	UNDEFINED_TYPE,	NULL,			raw_value,	FOR_THREAD},
{THRDINDEX_FCN,11,	0,	UNDEFINED_TYPE,	NULL,			raw_value,	FOR_THREAD},
{SMTP_FCN,	6,	0,	UNDEFINED_TYPE,	NULL,			raw_value,	FOR_COMPOSE},
{NULL,		0,	0,	UNDEFINED_TYPE,	0,			0,		FOR_NOTHING}
};

char* token_rules[] = {
   FROM_TOKEN,
   NICK_TOKEN,
   FCCF_TOKEN,
   FCCS_TOKEN,
   OTEXT_TOKEN,
   OTEXTNQ_TOKEN,
   ROLE_TOKEN,
   FOLDER_TOKEN,
   SUBJ_TOKEN,
   PROCID_TOKEN,
   THDDSPSTY_TOKEN,
   THDNDXSTY_TOKEN,
   FLAG_TOKEN,
   COLLECT_TOKEN,
   THDDSPSTY_TOKEN,
   ADDR_TOKEN,
   TO_TOKEN,
   ADDTO_TOKEN,
   ADDCC_TOKEN,
   ADDRECIP_TOKEN,
   SCREEN_TOKEN,
   KEY_TOKEN,
   SEND_TOKEN,
   CC_TOKEN,
   LCC_TOKEN,
   BCC_TOKEN,
   FFROM_TOKEN,
   FADDRESS_TOKEN,
   NULL
};

#define NTOKENS  (sizeof(token_rules)/sizeof(token_rules[0]) - 1)
#define NFCN    (sizeof(rule_fcns)/sizeof(rule_fcns[0]) - 1)

char *subj_fcn[]    = {SUBJ_TOKEN,    REPLACE_FCN, TRIM_FCN, REXTRIM_FCN, REXSUB_FCN, EXEC_FCN};
char *from_fcn[]    = {FROM_TOKEN,    REPLACE_FCN, TRIM_FCN, REXTRIM_FCN, REXSUB_FCN, EXEC_FCN};
char *otext_fcn[]   = {OTEXT_TOKEN,   REPLACE_FCN, TRIM_FCN, REXTRIM_FCN, REXSUB_FCN, EXEC_FCN};
char *otextnq_fcn[] = {OTEXTNQ_TOKEN, REPLACE_FCN, TRIM_FCN, REXTRIM_FCN, REXSUB_FCN, EXEC_FCN};

char *adto_fcn[] = {ADDTO_TOKEN, EXEC_FCN, NULL, NULL, NULL};

char **fcns_for_index[] = {subj_fcn, from_fcn, otext_fcn, otextnq_fcn};

#define NFCNFI    (sizeof(fcns_for_index)/sizeof(fcns_for_index[0])) /*for idx*/
#define NFPT      (sizeof(fcns_for_index[0])) /* functions pert token */

SPAREP_S *
get_sparep_for_rule(char *value, int flag)
{
  SPAREP_S *rv;
  rv = (SPAREP_S *) rule_alloc_mem(sizeof(SPAREP_S));
  rv->flag = flag;
  rv->value = value ? cpystr(value) : NULL;
  return rv;
}

void
free_sparep_for_rule(void **sparep)
{
  SPAREP_S *spare = (SPAREP_S *) *sparep;
  if(!spare) return;
  if(spare->value)
     fs_give((void **)&spare->value);
  fs_give((void **)sparep);
}

int
context_for_function(char *name)
{
  int i;
  for (i = 0; i < NFCN && strcmp(rule_fcns[i].name, name); i++);
  return i == NFCN ? 0 : rule_fcns[i].what_for;

}

char **
functions_for_token(char *name)
{
  int i;
  for (i = 0; i < NFCNFI && strcmp(fcns_for_index[i][0], name); i++);
  return i == NFCNFI ? NULL : fcns_for_index[i];
}

void
free_rexsub (REXSUB_S **rexsub)
{
   if(rexsub == NULL || *rexsub == NULL)
      return;

   if((*rexsub)->pattern) fs_give((void **) &(*rexsub)->pattern);
   if((*rexsub)->text) fs_give((void **) &(*rexsub)->text);
   fs_give((void **) rexsub);
}

void 
free_void_token_value(void **voidptr, int voidtype)
{
  switch(voidtype){
      case CHAR_TYPE:	fs_give(voidptr);
			break;
      case REXSUB_TYPE: free_rexsub ((REXSUB_S **) voidptr);
			break;
      default: break;	/* do nothing */
  }
}

void 
free_token_value(TOKEN_VALUE **token)
{
   if(token && *token){
     if ((*token)->testxt)
	fs_give((void **)&(*token)->testxt);
     if ((*token)->voidtxt)
	free_void_token_value((void **) &(*token)->voidtxt, (*token)->voidtype);
     if((*token)->next)
	free_token_value(&(*token)->next);
     fs_give((void **)token);
   }
}

void
free_condition_value(CONDVALUE_S **cvalue)
{
  if(cvalue && *cvalue){
    if ((*cvalue)->tname)
	fs_give((void **)&(*cvalue)->tname);
    if ((*cvalue)->value)
	free_token_value(&(*cvalue)->value);
    fs_give((void **)cvalue);
  }
}

void 
free_condition(CONDITION_S **condition)
{
   if(condition && *condition){
     if((*condition)->cndtype ==  Condition)
	free_condition_value((CONDVALUE_S **)&(*condition)->cndrule);
     else if((*condition)->cndtype ==  ParOpen || (*condition)->cndtype ==  ParClose)
	fs_give(&(*condition)->cndrule);
     if((*condition)->next)
	free_condition(&(*condition)->next);
     fs_give((void **)condition);
   }
}

void
free_ruleaction(RULEACTION_S **raction)
{
   if(raction && *raction){
     if ((*raction)->token)
	fs_give((void **)&(*raction)->token);
     if ((*raction)->function)
	fs_give((void **)&(*raction)->function);
     if ((*raction)->value)
	free_token_value(&(*raction)->value);
     fs_give((void **)raction);
   }
}

void
free_rule(RULE_S **rule)
{
   if(rule && *rule){
     free_condition(&(*rule)->condition);
     free_ruleaction(&(*rule)->action);
     fs_give((void **)rule);
   }
}

void
free_rule_list(RULELIST **rule)
{
  if(!*rule)
    return;

  if((*rule)->next)
    free_rule_list(&(*rule)->next);

  if((*rule)->prule)
    free_rule(&(*rule)->prule);

  fs_give((void **)rule);
}

void
free_parsed_rule_list(PRULELIST_S **rule)
{
  if(!*rule)
    return;

  if((*rule)->next)
    free_parsed_rule_list(&(*rule)->next);

  if((*rule)->rlist)
    free_rule_list(&(*rule)->rlist);

  fs_give((void **)rule);
}

void *
rule_alloc_mem (size_t amount)
{
   void *genmem;
   memset(genmem = fs_get(amount), 0, amount);
   return genmem;
}

int
isolate_condition (char *data, char **cvalue, int  *len)
{
  char *p = data;
  int done = 0, error = 0, next_condition = 0, l;

  if(*p == '"' && p[strlen(p) - 1] == '"'){
    p[strlen(p) - 1] = '\0';
    p++;
  }
  *cvalue = NULL;
  while (*p && !done){
	switch (*p){
	   case '_': *cvalue = advance_to_char(p,'}', STRICTLY, NULL);
		     if(*cvalue){
			strcat(*cvalue,"}");
			p += strlen(*cvalue);
		     }
		     else
			error++;
		     done++;
	   case ' ': p++;
		     break;
	   case '&': 
	   case '|': if (*(p+1) == *p){	/* looking for && or ||*/
			p += 2;
			next_condition++;
		     }
		     else{
			error++;
			done++;
		     }
		     break;
	   case '=': /* looking for => or -> */
	   case '-': if (*(p+1) != '>' || next_condition)
			error++;
		     done++;
		     break;
	   default : done++;
		     error++;
		     break;
	}
  }
  *len = p - data;
  return error ? -1 : (*cvalue ? 1 : 0);
}

TOKEN_VALUE *
parse_group_data (char *data, int *error)
{
  TOKEN_VALUE *rvalue;
  char *p, *d;
  int offset, err = 0, freeme = 0;

  if(error)
    *error = 0;

  if (!data)
     return (TOKEN_VALUE *) NULL;

  if(*data == '_'){
    d = detoken_src(data, FOR_RULE, NULL, NULL, NULL, NULL);
    freeme++;
  }
  else
    d = data;

  rvalue = (TOKEN_VALUE *) rule_alloc_mem(sizeof(TOKEN_VALUE));
  if ((p = advance_to_char(d,';', STRICTLY, &offset)) != NULL){
      rvalue->testxt = p;
      rvalue->next   = parse_group_data(d + strlen(p) + 1 + offset, error);
  }
  else if ((p = advance_to_char(d,'}', STRICTLY, NULL)) != NULL)
      rvalue->testxt = p;
  else if (d && *d == '}')
      rvalue->testxt = cpystr("");
  else{
      err++;
      free_token_value(&rvalue);
  }
  if (error)
    *error += err;
  if(freeme != 0 && d != NULL)
    fs_give((void **)&d);
  return(rvalue);
}

CONDVALUE_S *
fill_condition_value(char *data)
{
  CONDVALUE_S *condition;
  int i, done, error = 0;
  char *group;

  for (i = 0, done = 0; done == 0 && token_rules[i] != NULL; i++)
      done = strncmp(data,token_rules[i], strlen(token_rules[i])) ? 0 : 1;
  if (done){
     condition = rule_alloc_mem(sizeof(CONDVALUE_S));
     condition->tname = cpystr(token_rules[--i]);
     data += strlen(token_rules[i]);
  }
  else if (*data == '_') {
      INDEX_PARSE_T *token;
      char *itokname;
      for (i = 0, done = 0; 
	   done == 0 && (token = itoken(i)) != NULL && (itokname = token->name) != NULL; i++)
	 done = strncmp(data+1, itokname, strlen(itokname))
			? 0 : data[strlen(itokname) + 1] == '_';
      if (done){
	 condition = (CONDVALUE_S *) rule_alloc_mem(sizeof(CONDVALUE_S));
	 condition->tname = fs_get(strlen(itokname) + 3);
	 sprintf(condition->tname, "_%s_", itokname);
	 data += strlen(itokname) + 2;
      }
      else 
	return NULL;
  } 
  else
     return NULL;

  for (; *data && *data == ' '; data++);
  if (*data){
     for (i = 0, done = 0; done == 0 && rel_rules_test[i].value != NULL; i++)
       done = strncmp(data, rel_rules_test[i].value, 2) ? 0 : 1;
     if (done)
       condition->ttype = rel_rules_test[--i].ttype;
     else{
	 free_condition_value(&condition);
	 return NULL;
     }
  }
  else{
    free_condition_value(&condition);
    return  NULL;
  }

  data += 2;
  for (; *data && *data == ' '; data++);
  if (*data++ != '{'){
     free_condition_value(&condition);
     return NULL;
  }
  group = advance_to_char(data,'}', STRICTLY, &error); 
  if (group || (!group &&  error < 0)){
     condition->value = parse_group_data(data, &error);
     if(group && error)
	free_condition_value(&condition);
     if(group)
        fs_give((void **) &group);
  }
  else
     free_condition_value(&condition);
  return condition;
}

char *
canonicalize_condition(char *data, int *eoc)
{
  char *p = data, *s, *t, c;
  char *q = fs_get((5*strlen(data)+1)*sizeof(char));
  char tmp[10];
  int level, done, error, i;

  if(eoc) *eoc = -1; 	/* assume error */
  *q = '\0';
  if(*p == '"'){
     if(p[strlen(p) - 1] == '"')
	p[strlen(p) - 1] = '\0';
     p++;
  }
  for(level = done = error = 0; *p && !done && !error; ){
     switch(*p){
	case ' ' : p++; break;
	case '(' : strcat(q, CSEP_S); strcat(q, "(");
		   sprintf(tmp, "%d ", level++);
		   strcat(q, tmp);
		   p++;
		   break;
	case ')' : strcat(q, CSEP_S); strcat(q, ")");
		   sprintf(tmp, "%d ", --level);
		   strcat(q, tmp);
		   p++;
		   if(level < 0) error++;
		   break;
	case '_' : for (s = p+1; *s >= 'A' && *s <= 'Z'; s++)
			;
		   for (i = 0; token_rules[i] != NULL; i++)
		      if(!strncmp(token_rules[i], p, s-p))
			break;
		   if(token_rules[i] == NULL)
		     error++;
		   else if(*s++ == '_'){
		     for(; *s == ' '; s++);
		     if(*s && *(s+1)){
			for(i = 0; rel_rules_test[i].value != NULL; i++)
			   if(!strncmp(rel_rules_test[i].value, s, 2))
			      break;
			if (rel_rules_test[i].value == NULL)
			   error++;
			else{
			   s += 2;
			   for(; *s == ' '; s++);
			   if(*s == '{'){
			     if(*(s+1) != '}')
			       t = advance_to_char(s+1,'}', STRICTLY, NULL);
			     else
			       t = cpystr("");
			     if(t != NULL){
			        for(i = 0; t[i] != '\0' && t[i] != CSEP_C; i++);
				if(t[i] == CSEP_C) error++;
			        if(error == 0){
				   strcat(q, CSEP_S); strcat(q, "C[");
				   s += strlen(t) + 1;	/* get past '{' */
				   *s = '\0';
				   strcat(q, p);
				   strcat(q, "}] ");
				   *s++ = '}';
				   p = s;
			        }
				fs_give((void **) &t);
			     }
			     else error++;
			   }
			   else
			     error++;
			}
		     }
		   }
		   else error++;
		   break;
	case '|':
	case '&': if((*(p+1) = *p) != '\0'){
			strcat(q, CSEP_S); strcat(q, *p == '|' ? "OR " : "AND ");
			p += 2;
		  } else error++;
		  break;
	case '-':
	case '=': if (*(p+1) == '>'){
		    if(eoc) *eoc = p - data;
		    done++;
		  }
		  else
		    error++;
		  break;
	default : error++;
		  break;
     }
  }
  if(error || level > 0)	/*simplistic approach by now */
    fs_give((void **)&q);
  else
    q[strlen(q)-1] = '\0';
  return q;
}

/* for a canonical condition, return if it is constructed according
 * to logical rules such as AND or OR between conditions, etc. We assume
 * we already canonicalized data, or else this will not work.
 */
int
sanity_check_condition(char *data)
{
  int i, error;
  char *s, *t, *d;

  if(data == NULL || *data == '\0')	/* no data in, no data out */
    return 0;

  d = fs_get((strlen(data)+1)*sizeof(char));
  for(s = data,i = 0; (t = strchr(s, CSEP_C))!= NULL && (d[i] = *(t+1)); s = t+1, i++);
  d[i] = '\0';
  for(i = 0, error = 0; d[i] != '\0' && error == 0; i++){
     switch(d[i]){
	case 'C': if((d[i+1] != '\0' && (d[i+1] == '(' || d[i+1] == 'C'))
			|| (i == 0 && d[1] != 'A' && d[1] != 'O' && d[1] != '\0'))
		     error++;
		  break;
	case ')': if(i == 0 || (d[i+1] != '\0' && (d[i+1] == 'C' || d[i+1] == '(')))
		     error++;
		  break;
	case '(': if(d[i+1] == '\0' || d[i+1] == ')' || d[i+1] == 'A' || d[i+1] == 'O')
		     error++;
		  break;
	case 'O':
	case 'A': if(i == 0 || d[i+1] == '\0' || d[i+1] == ')' || d[i+1] == 'A' || d[i+1] == 'O')
		     error++;
		  break;
	default : error++;
     }
  }
  if(d) fs_give((void **)&d);
  return error ? 0 : 1;
}

/* given a parsed data that satisfies sanity checks, parse it
 * into a condition we can check later on.
 */
CONDITION_S *
fill_condition(char *data)
{
  char *s, *t, *u;
  CONDITION_S *rv = NULL;
  CONDVALUE_S *cvalue;
  int *i;

  if(data == NULL || *data == '\0' || (s = strchr(data, CSEP_C)) == NULL)
    return NULL;

  rv = (CONDITION_S *) rule_alloc_mem(sizeof(CONDITION_S));
  switch(*++s){
     case ')':
     case '(':	 i = fs_get(sizeof(int));
		*i = atoi(s+1);
		rv->cndrule = (void *) i;
		rv->cndtype = *s == '(' ? ParOpen : ParClose;
		break;

     case 'C':	if((u = strchr(s+2, CSEP_C)) != NULL){
		   *u = '\0';
		   t = strrchr(s, ']');
		   t = '\0';
		   *u = CSEP_C;
		} else
		   s[strlen(s) - 1] = '\0';
		rv->cndrule = (void *) fill_condition_value(s+2);
		rv->cndtype = Condition;
		break;

     case 'A':
     case 'O':  rv->cndtype = *s == 'A' ? And : Or;
		break;

     default : fs_give((void **)&rv);
	       break;
  }
  rv->next = fill_condition(strchr(s, CSEP_C));

  return rv;
} 

/* eoc = end of condition, equal to -1 on error */
CONDITION_S *
parse_condition (char *data, int *eoc)
{
  CONDITION_S *condition = NULL;
  char *pvalue;
  
  if((pvalue = canonicalize_condition(data, eoc)) != NULL
	&& sanity_check_condition(pvalue) > 0)
    condition = fill_condition(pvalue);

  if(pvalue)
    fs_give((void **)&pvalue);

  if (condition == NULL && eoc)
    *eoc = -1;

  return condition;
}

RULEACTION_S *
parse_action (char *data, int context)
{
  int i, done, is_save;
  RULEACTION_S *raction = NULL;
  char *function, *p = data;

  if (p == NULL || *p == '\0')
     return NULL;

  is_save = *p == '-';
  p += 2;
  for (; *p == ' '; p++);

  if (is_save){	/* got "->", a save-rule separator */
     raction = (RULEACTION_S *) rule_alloc_mem(sizeof(RULEACTION_S));
     raction->function = cpystr("_SAVE_");
     raction->value    = (TOKEN_VALUE *) rule_alloc_mem(sizeof(TOKEN_VALUE));
     raction->context |= FOR_SAVE;
     raction->exec     = extended_value;
     raction->value->testxt = cpystr(p);
     return raction;
  }
  for (i = 0, done = 0; !done && (i < NFCN); i++)
       done = (strstr(p,rule_fcns[i].name) == p);
  p += done ? strlen(rule_fcns[--i].name) + 1 : 0;
  if(!*p || (rule_fcns[i].what_for && !(rule_fcns[i].what_for & context)))
     return NULL;
  if (done){
     raction = rule_alloc_mem(sizeof(RULEACTION_S));
	/* We assign raction->token to be subject. This is not necessary for
	   most rules. It is done only for rules that need it and will not
	   make any difference in rules that do not need it. It will hopefully
	   reduce complexity in the language
	 */
     raction->token    = cpystr(SUBJ_TOKEN);
     raction->function = cpystr(rule_fcns[i].name);
     raction->context  = rule_fcns[i].what_for;
     raction->exec     = rule_fcns[i].execute;
     raction->value    = (TOKEN_VALUE *) rule_alloc_mem(sizeof(TOKEN_VALUE));
     raction->value->testxt = advance_to_char(p,'}', STRICTLY, NULL);
     if(!raction->value->testxt)
       free_ruleaction(&raction);
     return raction;
  }

  raction = (RULEACTION_S *) rule_alloc_mem(sizeof(RULEACTION_S));
  raction->token = get_name_token(p);

  p += strlen(raction->token) + 1;
  for (; *p && *p == ' '; p++);
  if (!strncmp(p, ":=", 2))
      p += 2;
  else{
      free_ruleaction(&raction);
      return NULL;
  }

  for (; *p && *p == ' '; p++);

  for(i = 0; i < NFCN && strncmp(p, rule_fcns[i].name, rule_fcns[i].len); i++);

  if (!rule_fcns[i].is_assignment){
     free_ruleaction(&raction);
     return NULL;
  }

  raction->function = cpystr(rule_fcns[i].name);
  raction->context  = rule_fcns[i].what_for;
  raction->exec     = rule_fcns[i].execute;
  p += rule_fcns[i].len;

  if(*p++ != '{'){
     free_ruleaction(&raction);
     return NULL;
  }

  if(rule_fcns[i].parse_action_value){
    raction->value = (rule_fcns[i].parse_action_value)(p);
    if(raction->value)
       raction->value->voidtype = rule_fcns[i].return_type;
  }

  if(raction->value == NULL
	|| (raction->value->testxt == NULL && raction->value->voidtxt == NULL))
      free_ruleaction(&raction);

  return raction;
}

TOKEN_VALUE *
parse_action_to_char(char *p)
{
  return parse_group_data(p, NULL);
}

TOKEN_VALUE *
parse_rexsub_action(char *data)
{
  TOKEN_VALUE *rv;
  REXSUB_S *rsv = NULL;		/* rexsub value */
  char *d = NULL, *p, *t, *n;	/* data, pattern, text, number */
  long number;
  int offset, error;

  if (data == NULL || *data == '\0')
     return NULL;

  rv  = (TOKEN_VALUE *) rule_alloc_mem(sizeof(TOKEN_VALUE));

  number = 0;
  p = t = n = NULL;

  p = advance_to_char(data,'}', STRICTLY, NULL);

  error = (p == NULL) ? 1 : 0;
  if(!error){
     offset = strlen(p) + 1;
     d = data + offset;
     if(*d++ != '{') error++;
  }

  if(!error){
     n = advance_to_char(d,'}', STRICTLY, NULL);
     if(n == NULL && *d == '}')
	n = cpystr("");
     if(n == NULL) error++;
  }

  if(!error){
     t = detoken_src(n, FOR_RULE, NULL, NULL, NULL, NULL);
     offset += strlen(n) + 2;
     d = data + offset;
     if(*d == '\0') number = 1;
     else if(*d++ != '{') error++;
  }

  if(!error && number == 0){
     fs_give((void **) &n);
     n = advance_to_char(d,'}', STRICTLY, NULL);
     if(n == NULL){
	if(*d == '\0')
	   number = 1;
	else
	   error++;
     }
     else if(strcmp(n, "g") == 0)
        number = -1;
     else{
        char *eon;
	number = strtol(n, &eon, 10);
	if((eon != NULL && *eon != '\0') || number <= 0) error++;
     }
  }

  if(error){
     if (p) fs_give((void **) &p);
     if (t) fs_give((void **) &t);
  }
  if (n) fs_give((void **) &n);

  if(!error){
     rsv = fs_get(sizeof(REXSUB_S));
     rsv->pattern = p;
     rsv->text    = t;
     rsv->times   = number;
  }

  rv->voidtxt = (void *) rsv;
  return rv;
}

RULE_S *
parse_rule (char *data, int context)
{
  RULE_S *prule;	/*parsed rule */
  int len = 0;
  
  if (!(prule = (RULE_S *) rule_alloc_mem(sizeof(RULE_S))) ||
	!(prule->condition = parse_condition(data, &len)) ||
	!(prule->action = parse_action(data+len, context)))
      free_rule(&prule);

  return prule;
}

RULELIST *
get_rule_list(char **list, int context, int i)
{
  RULE_S *rule;
  RULELIST *trulelist = NULL;

  if (list[i] && *list[i]){
     if((rule = parse_rule(list[i], context)) != NULL){
	trulelist  = (RULELIST *) rule_alloc_mem(sizeof(RULELIST));
	trulelist->prule = rule;
	trulelist->next = get_rule_list(list, context, i+1);
     }
     else
	trulelist = get_rule_list(list, context, i+1);
  }
  return trulelist;
}

/* add parsed rule */
PRULELIST_S *
add_prule(PRULELIST_S *rule_list, PRULELIST_S *rule)
{
   PRULELIST_S *rlist;

   for(rlist = rule_list; rlist && rlist->next; rlist = rlist->next);

   if(rlist)
      rlist->next = rule;
   else
      rule_list = rule;

   return rule_list;
}  

void
add_rule(int code, int context)
{
  char **list = ps_global->vars[code].current_val.l;
  PRULELIST_S *prulelist, *trulelist, *orulelist;

  if (list && *list && **list){
     trulelist = (PRULELIST_S *) rule_alloc_mem(sizeof(PRULELIST_S));
     trulelist->varnum = code;
     if ((trulelist->rlist = get_rule_list(list, context, 0)) != NULL)
        ps_global->rule_list = add_prule(ps_global->rule_list, trulelist);
     else
	free_parsed_rule_list(&trulelist);
  }
}

/* see create_rule_list below */
void
set_rule_list(struct variable *vars)
{
    set_current_val(&vars[V_THREAD_DISP_STYLE_RULES], TRUE, TRUE);
    set_current_val(&vars[V_THREAD_INDEX_STYLE_RULES], TRUE, TRUE);
    set_current_val(&vars[V_COMPOSE_RULES], TRUE, TRUE);
    set_current_val(&vars[V_FORWARD_RULES], TRUE, TRUE);
    set_current_val(&vars[V_INDEX_RULES], TRUE, TRUE);
    set_current_val(&vars[V_KEY_RULES], FALSE, TRUE);
    set_current_val(&vars[V_REPLACE_RULES], TRUE, TRUE);
    set_current_val(&vars[V_REPLY_INDENT_RULES], TRUE, TRUE);
    set_current_val(&vars[V_REPLY_LEADIN_RULES], TRUE, TRUE);
    set_current_val(&vars[V_RESUB_RULES], TRUE, TRUE);
    set_current_val(&vars[V_SAVE_RULES], TRUE, TRUE);
    set_current_val(&vars[V_SMTP_RULES], TRUE, TRUE);
    set_current_val(&vars[V_SORT_RULES], TRUE, TRUE);
    set_current_val(&vars[V_STARTUP_RULES], TRUE, TRUE);
}

/* see set_rule_list above */
void
create_rule_list(struct variable *vars)
{
  set_rule_list(vars);
  add_rule(V_THREAD_DISP_STYLE_RULES, FOR_THREAD);
  add_rule(V_THREAD_INDEX_STYLE_RULES, FOR_THREAD);
  add_rule(V_COMPOSE_RULES, FOR_COMPOSE);
  add_rule(V_FORWARD_RULES, FOR_COMPOSE);
  add_rule(V_INDEX_RULES, FOR_INDEX);
  add_rule(V_KEY_RULES, FOR_KEY);
  add_rule(V_REPLACE_RULES, FOR_REPLACE);
  add_rule(V_REPLY_INDENT_RULES, FOR_COMPOSE);
  add_rule(V_REPLY_LEADIN_RULES, FOR_REPLY_INTRO);
  add_rule(V_RESUB_RULES, FOR_RESUB|FOR_TRIM);
  add_rule(V_SAVE_RULES, FOR_SAVE);
  add_rule(V_SMTP_RULES, FOR_COMPOSE);
  add_rule(V_SORT_RULES, FOR_SORT);
  add_rule(V_STARTUP_RULES, FOR_STARTUP);
}

int
condition_contains_token(CONDITION_S *condition, char *token)
{
  while(condition && condition->cndtype != Condition)
      condition =  condition->next;

  return condition 
	  ? (!strcmp(COND(condition)->tname, token) 
		? 1
		: condition_contains_token(condition->next, token)) 
	  : 0;
}

RULELIST *
get_rulelist_from_code(int code, PRULELIST_S *list)
{
  return list ? (list->varnum == code ? list->rlist 
			      : get_rulelist_from_code(code, list->next))
	      : (RULELIST *) NULL;
}   

char *
test_rule(RULELIST *rlist, int ctxt, ENVELOPE *env, int *n)
{
  char *result;

  if(!rlist)
     return NULL;

  if ((result = process_rule(rlist->prule, ctxt, env)) != NULL)
      return result;
  else{
       (*n)++;
       return test_rule(rlist->next, ctxt, env, n);
  } 
}

RULE_S *
get_rule (RULELIST *rule, int n)
{
  return rule ? (n ? get_rule(rule->next, n-1) : rule->prule) 
	      : NULL;
}

/* get_result_rule:
 * Parameters: list: the list of rules to be passed to the function to check
 *             rule_context: context of the rule
 *             env : envelope used to check the rule, if needed.
 *
 * Returns: The value of the first rule that is satisfied in the list, or
 *          NULL if not. This function should be called in the following 
 *          way (notice that memory is freed by caller).
 *
 * You should use this function to obtain the result of a rule. You can
 * also call directly "process_rule", but I advice to use this function if
 * there's no difference on which function to call.

   RULE_RESULT *rule;

   rule = (RULE_RESULT *) 
           get_result_rule(V_SOME_RULE, context, envelope);

   if (rule){ 
       assign the value of rule->result;
       if (rule->result)
          fs_give((void **)&rule->result);
       fs_give((void **)&rule);
   }
 */

RULE_RESULT *
get_result_rule(int code, int rule_context, ENVELOPE *env)
{
    char  *rule_result;
    RULE_RESULT *rule = NULL;
    RULELIST *rlist;
    int n = 0;

    if(!(rule_context & FOR_RULE))
      rule_context |= FOR_RULE;
    rlist = get_rulelist_from_code(code, ps_global->rule_list);
    if (rlist){
       rule_result = test_rule(rlist, rule_context, env, &n);
       if (rule_result && *rule_result){
          rule = (RULE_RESULT *) fs_get (sizeof(RULE_RESULT));
          rule->result = rule_result;
          rule->number = n;
       }
    }
    return rule;
}

char *
get_rule_result(int rule_context, char *newfolder, int code)
{   
    char        *rule_result = NULL;
    ENVELOPE    *news_envelope;
    RULE_RESULT *rule;

    if (IS_NEWS(ps_global->mail_stream)){
       news_envelope = mail_newenvelope();
       news_envelope->newsgroups = cpystr(newfolder);
    }
    else
       news_envelope = NULL;

    rule = get_result_rule(code, rule_context, news_envelope);

    if (news_envelope)
        mail_free_envelope (&news_envelope);

    if (rule){
        rule_result = cpystr(rule->result);
        if (rule->result)
          fs_give((void **)&rule->result);
        fs_give((void **)&rule);
    }
    return rule_result;
}

/* process_rule:
   Parameters:  prule, a processed rule, ready to be tested
		rule_context: context of the rule, and
		env: An envelope if needed.

   Returns   :  The value of the processed rule_data if the processing was 
		successful and matches context and possibly the envelope, or
		NULL if there's no match
 */

char *
process_rule (RULE_S *prule, int rule_context, ENVELOPE *env)
{
   if(!prule)
     return NULL;

   if(!(rule_context & FOR_RULE))
      rule_context |= FOR_RULE;

   return test_condition(prule->condition, rule_context, env)
	    ? (prule->action->exec)(prule->action, rule_context, env)
	    : NULL;
}

TOKEN_VALUE *
copy_parsed_value(TOKEN_VALUE *value, int ctxt, ENVELOPE *env)
{
   TOKEN_VALUE *tval = NULL;

   if(!value)
      return NULL;

   if(value->testxt){
     tval = (TOKEN_VALUE *) rule_alloc_mem(sizeof(TOKEN_VALUE));
     tval->testxt = detoken_src(value->testxt, ctxt, env, NULL, NULL, NULL);
     tval->voidtxt = value->voidtxt;
     tval->codefcn = value->codefcn;     
   }
   if(value->next)
     tval->next = copy_parsed_value(value->next, ctxt, env);

   return tval;
}

void
free_parsed_value(TOKEN_VALUE **value)
{
   TOKEN_VALUE *tval = NULL;

   if(!*value)
      return;

   if((*value)->testxt)
     fs_give((void **)&(*value)->testxt);

   if((*value)->next)
     free_parsed_value(&(*value)->next);

    fs_give((void **)value);
}

int
test_condition_work(CONDITION_S *bc, CONDITION_S *ec, int rcntxt, ENVELOPE *env)
{
   int rv,level;
   TOKEN_VALUE *group;
   CONDITION_S *cend;

   switch(bc->cndtype){
	case Condition:	group = copy_parsed_value(COND(bc)->value, rcntxt, env);
			rv = (*rel_rules_test[COND(bc)->ttype].execute)(bc, group, env, rcntxt);
			free_parsed_value(&group);
			if(bc == ec)
			  return rv;
			if(bc->next == NULL)
			  return rv;
			else
			  switch(bc->next->cndtype){
			    case And: return rv ? test_condition_work(bc->next->next, ec, rcntxt, env) : 0;
				  break;
			    case Or : return rv ? 1 : test_condition_work(bc->next->next, ec, rcntxt, env);
				  break;
			    case ParClose: return rv;
			    default : rv = 0; break;  /* fail, we should not be here */
			  }
			break;

	case ParOpen:	level = ((int *)bc->cndrule)[0];
			for(cend = bc; cend->next && (cend->next->cndtype != ParClose
				  	|| ((int *)cend->next->cndrule)[0] != level); 
					cend = cend->next);
			rv = test_condition_work(bc->next, cend, rcntxt, env);
			cend = cend->next;	/* here we are at ')' */
			if(cend->next == NULL)
			  return rv;
			else{
			  switch(cend->next->cndtype){
			    case And: return rv ? test_condition_work(cend->next->next, ec, rcntxt, env) : 0;
				  break;
			    case Or : return rv ? 1 : test_condition_work(cend->next->next, ec, rcntxt, env);
				  break;
			    default : rv = 0; break;	/* fail, we should not be here */
			  }
			}
			break;
	     default:	rv = 0; break; 	/* fail, we should not be here */
   }
   return rv;  /* we never ever get here */
}


int
test_condition(CONDITION_S *condition, int rcntxt, ENVELOPE *env)
{
   return test_condition_work(condition, NULL, rcntxt, env);
}

/* returns the name of the token it found or NULL if there is no token, the
 * real value of the token is obtained by calling the detoken_src function.
 */ 
char *
get_name_token (char *condition)
{
  char *p, *q, c, *rv;

  if (condition == NULL)
     return NULL;

  for(p = condition; *p != '\0' && (*p == ' '|| *p == '\t'); p++);

  if (*p != '_') return NULL;

  for (q = p+1; *q != '\0' && *q != '_'; q++);

  if (*q != '_') return NULL;

  c = *(q+1);
  *(q+1) = '\0';
  rv = cpystr(p);
  *(q+1) = c;
  return rv;
}

/* This function tests if a string contained in the variable "group" is
 * in the "condition"
 */
int
test_in (CONDITION_S *condition, TOKEN_VALUE *group, ENVELOPE *env, 
		int context)
{
 int rv = 0;
 char *test;
 TOKEN_VALUE *test_group = group;

 test = env && env->sparep && (((SPAREP_S *)env->sparep)->flag & USE_RAW_SP)
	? cpystr(((SPAREP_S *)env->sparep)->value)
	: detoken_src(COND(condition)->tname, context, env, NULL, NULL, NULL);
 if (test){
    while (rv == 0 && test_group){
       if(!*test || strstr(test_group->testxt, test))
	  rv++;
       else
	  test_group = test_group->next;
    }
    fs_give((void **)&test);
 }
 return rv;
}

int
test_ni (CONDITION_S *condition, TOKEN_VALUE *group, 
		ENVELOPE *env, int context)
{
 int rv = 0;
 char *test;
 TOKEN_VALUE *test_group = group;

 test = env && env->sparep && (((SPAREP_S *)env->sparep)->flag & USE_RAW_SP)
	? cpystr(((SPAREP_S *)env->sparep)->value)
	: detoken_src(COND(condition)->tname, context, env, NULL, NULL, NULL);
 if (test){
    if(!test_group)
      rv++;
    while (rv == 0 && test_group){
       if(!*test_group->testxt || strstr(test, test_group->testxt))
	  rv++;
       else
	  test_group = test_group->next;
    }
    fs_give((void **)&test);
 }
 return rv;
}

int
test_not_in (CONDITION_S *condition, TOKEN_VALUE *group, 
		ENVELOPE *env, int context)
{
 return !test_in(condition, group, env, context);
}

int
test_not_ni (CONDITION_S *condition, TOKEN_VALUE *group, 
		ENVELOPE *env, int context)
{
 return !test_ni(condition, group, env, context);
}

int
test_eq (CONDITION_S *condition, TOKEN_VALUE *group, 
		ENVELOPE *env, int context)
{
 int rv = 0;
 char *test;
 TOKEN_VALUE *test_group = group;

 test = env && env->sparep && (((SPAREP_S *)env->sparep)->flag & USE_RAW_SP)
	? cpystr(((SPAREP_S *)env->sparep)->value)
	: detoken_src(COND(condition)->tname, context, env, NULL, NULL, NULL);
 if (test){
    while (rv == 0 && test_group){
       if((!*test && !*test_group->testxt) || !strcmp(test_group->testxt, test))
	  rv++;
       else
	  test_group = test_group->next;
    }
    fs_give((void **)&test);
 }
 return rv;
}

int
test_not_eq (CONDITION_S *condition, TOKEN_VALUE *group, 
		ENVELOPE *env, int context)
{
 return !test_eq(condition, group, env, context);
}

char *
do_trim (char *test, TOKEN_VALUE *tval)
{
   char *begin_text;
   int offset = 0;

   if (!tval)
      return test;

   while((begin_text = strstr(test+offset,tval->testxt)) != NULL){
      memmove(begin_text, begin_text+strlen(tval->testxt), strlen(begin_text) - strlen(tval->testxt) + 1);
      offset = begin_text - test;
   }

   return do_trim(test, tval->next);
}

char *
trim (RULEACTION_S *action, int context, ENVELOPE *env)
{
 char *begin_text, *test;
 RULEACTION_S *taction = action;
 int offset;

 if (taction->context & context){
    if ((test = detoken_src(taction->token, context, env, NULL, NULL, NULL)) != NULL)
       test = do_trim(test, taction->value);
    return test;
 }
 return NULL;
}

char *
do_rextrim (char *test, TOKEN_VALUE *tval)
{
   char *begin_text, *trim_text;
   int i, offset = 0;
   size_t len;

   if (!tval)
      return test;

   if((trim_text = expand(test, tval->testxt)) != NULL){
      while((begin_text = strstr(test+offset, trim_text)) != NULL){
          memmove(begin_text, begin_text+strlen(trim_text), strlen(begin_text) - strlen(trim_text) + 1);
          offset = begin_text - test;
      }
   }

   return do_rextrim(test, tval->next);
}

char *
rextrim (RULEACTION_S *action, int context, ENVELOPE *env)
{
 char *test = NULL;
 RULEACTION_S *taction = action;

 if ((taction->context & context) &&
    (test = detoken_src(taction->token, context, env, NULL, NULL, NULL)))
	test = do_rextrim(test, taction->value);
 return test;
}

char *
do_rexsub (char *test, TOKEN_VALUE *tval)
{
   REXSUB_S *rsv;
   regmatch_t pmatch;
   regex_t preg;
   char *ret_string = cpystr(test ? test : "");

   if (!tval)
      return ret_string;

   rsv = (REXSUB_S *) tval->voidtxt;

   if (rsv == NULL 		/* this should not happen */
	|| rsv->pattern == NULL /* neither should this */
	|| rsv->text == NULL    /* neither should this */
	|| regcomp(&preg, rsv->pattern, REG_EXTENDED) != 0) /* but this could */
      return ret_string;

   /* can't use expand() to do the job here. This is because there
    * is a variable number of hits in rsv->times. */

   if(regexec((regex_t *) &preg, test, 1, &pmatch, 0) == 0){
     int offset = 0, n;

     n = rsv->times > 0 ? rsv->times : strlen(test);
     fs_resize((void **) &ret_string, strlen(test) + n*strlen(rsv->text) + 1);
     *ret_string = '\0';

     do {
	strncat(ret_string, test + offset, pmatch.rm_so);
	strcat(ret_string, rsv->text);
	offset += pmatch.rm_eo;
     } while (--n && regexec(&preg, test + offset, 1, &pmatch, REG_NOTBOL) == 0);
     strcat(ret_string, test + offset);
   }
   fs_resize((void **) &ret_string, strlen(ret_string) + 1);
   regfree(&preg);

   return ret_string;
}

char *
rexsub (RULEACTION_S *action, int context, ENVELOPE *env)
{
 char *test = NULL, *sub = NULL;
 RULEACTION_S *taction = action;

 if ((taction->context & context) &&
    (test = detoken_src(taction->token, context, env, NULL, NULL, NULL)))
	sub = do_rexsub(test, taction->value);
 if (test) fs_give((void **) &test);
 return sub;
}

char *
raw_value (RULEACTION_S *action, int context, ENVELOPE *env)
{
return (action->context & context) ? cpystr(action->value->testxt) : NULL;
}

char *
extended_value (RULEACTION_S *action, int ctxt, ENVELOPE *env)
{
return (action->context & ctxt) 
	? detoken_src(action->value->testxt, ctxt, env, NULL, NULL, NULL)
	: NULL;
}

/* advances given_string until it finds given_char, memory freed by caller  */
char *
advance_to_char(char *given_string, char given_char, int flag, int *error) 
{
   char *b, *s, c;
   int i, err  = 0, quoted ;

   if (error)
      *error = 0;

   if (!given_string || !*given_string)
       return NULL;

   b = s = cpystr(given_string);
   for(i = 0, quoted = 0, c = *s; c ; c = *++s){
      if(c == '\\'){
	 quoted++;
	 continue;
      }
      if(quoted){
	quoted = 0;
	if (c == given_char){
	  err += (flag & STRICTLY) ? 0 : 1;
	  err++;
	  break;
	}
	b[i++] = '\\';
      }
      if(c == given_char){
	 err += (flag & STRICTLY) ? 0 : 1;
	 break;
      }
      b[i++] = c;
   }
   b[i] = '\0';
   if (b && (strlen(b) == strlen(given_string)) && (flag & STRICTLY)){
      fs_give((void **)&b);
      return NULL;   /* character not found */
   }

   if(b && !*b){
     fs_give((void **)&b);
     err = -1;
   }

   if (error)
      *error = err;

   return b;
}

/* Regular Expressions Support */
char *
expand (char *string, char *pattern)
{
  char c, *ret_string = NULL;
  regmatch_t pmatch;
  regex_t preg;

  if(pattern == NULL || regcomp(&preg, pattern, REG_EXTENDED) != 0)
     return NULL;

  if(regexec((regex_t *) &preg, string , 1, &pmatch, 0) == 0 
	&& pmatch.rm_so < pmatch.rm_eo){
      c = string[pmatch.rm_eo];
      string[pmatch.rm_eo] = '\0';
      ret_string = cpystr(string+pmatch.rm_so);
      string[pmatch.rm_eo] = c;
  }
  regfree(&preg);

  return ret_string;
}

char *
exec_fcn (RULEACTION_S *action, int ctxt, ENVELOPE *env)
{
  STORE_S *output_so;
  gf_io_t	gc, pc;
  char *status, *rv, *cmd, *test;

  if(!(action->context & ctxt))
    return NULL;

  if((test = detoken_src(action->token, ctxt, env, NULL, NULL, NULL)) != NULL)
    gf_set_readc(&gc, test, (unsigned long)strlen(test), CharStar, 0);

  if((output_so = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL)
     gf_set_so_writec(&pc, output_so);

  cmd = (char *)fs_get((strlen(action->value->testxt) + strlen("_TMPFILE_") + 2)*sizeof(char));
  sprintf(cmd,"%s _TMPFILE_", action->value->testxt);
  status = (*ps_global->tools.exec_rule)(cmd, gc, pc);

  so_seek(output_so, 0L, 0);
  rv = cpystr(output_so->dp);
  gf_clear_so_writec(output_so);
  so_give(&output_so);
  if(test)
    fs_give((void **)&test);

  return status ? NULL : rv;
}

ENVELOPE *
rules_fetchenvelope(INDEXDATA_S *idata, int *we_clear)
{
  ENVELOPE *env;

  if (idata->no_fetch){
    if (we_clear) *we_clear = 1;
    env = mail_newenvelope();
    env->from     = copyaddrlist(idata->from);
    env->to       = copyaddrlist(idata->to);
    env->cc       = copyaddrlist(idata->cc);
    env->sender   = copyaddrlist(idata->sender);
    env->subject  = cpystr(idata->subject);
    env->date     = cpystr((unsigned char *) idata->date);
    env->newsgroups = cpystr(idata->newsgroups);
    return env;
  }
  if (we_clear) *we_clear = 0;
  env = pine_mail_fetchenvelope(idata->stream, idata->rawno);
  return env;
}
