/* -*- mode: C; mode: fold; -*- */
/* Copyright (c) 1998 John E. Davis (davis@space.mit.edu)
 *
 * This file is part of slrn.
 *
 * Slrn 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, or (at your option) any
 * later version.
 * 
 * Slrn 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 Slrn; see the file COPYING.  If not, write to the Free
 * Software Foundation, 59 Temple Place - Suite 330, 
 * Boston, MA  02111-1307, USA.
 */

#include "config.h"
#include "slrnfeat.h"

/*{{{ Include Files */

#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <signal.h>
#include <ctype.h>
#include <errno.h>
#include <time.h>

#ifdef HAVE_STDLIB_H
# include <stdlib.h>
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#if !defined(VMS) && !defined(__WIN32__) && !defined(__NT__)
# define HAS_PASSWORD_CODE	1
# include <pwd.h>
#endif

#ifdef VMS
# include "vms.h"
#else
# include <sys/types.h>
# include <sys/stat.h>
#endif

#if defined(VMS) && defined(MULTINET)
# include "multinet_root:[multinet.include]netdb.h"
#else
# if defined(__NT__)
#  include <winsock.h>
# else
#  if defined(__WIN32__)
#   define Win32_Winsock
#   include <windows.h>
#  else
#   include <netdb.h>
#   ifndef h_errno
extern int h_errno;
#   endif
#  endif 
# endif 
#endif 

#ifdef HAVE_SYS_WAIT_H
# include <sys/wait.h>
#endif

#ifdef NeXT
# undef WIFEXITED
# undef WEXITSTATUS
#endif

#ifndef WEXITSTATUS
# define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
#endif
#ifndef WIFEXITED
# define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
#endif

#include <slang.h>
#include "jdmacros.h"

#include "misc.h"
#include "group.h"
#include "slrn.h"
#include "post.h"
#include "server.h"
#include "util.h"
#include "ttymsg.h"
#include "chmap.h"

#if SLRN_HAS_MIME
#include "mime.h"
#endif

#ifdef VMS
/* valid filname chars for unix equiv of vms filename */
# define VALID_FILENAME_CHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789$_-/"
# include "vms.h"
#endif
/*}}}*/

/*{{{ Global Variables */
int Slrn_Full_Screen_Update = 1; 
int Slrn_User_Wants_Confirmation = 1;
int Slrn_Message_Present = 0;

#ifndef VMS
char *Slrn_SendMail_Command;
#endif

char *Slrn_Editor;
char *Slrn_Editor_Post;
char *Slrn_Editor_Score;
char *Slrn_Editor_Mail;

Slrn_User_Info_Type Slrn_User_Info;
SLKeyMap_List_Type *Slrn_RLine_Keymap;

/*}}}*/
/*{{{ Static Variables */

static int Error_Present;
static SLang_RLine_Info_Type *Slrn_Keymap_RLI;
static char *Input_String;
static char *Input_String_Ptr;
static char *Input_Chars_Ptr;
static char *Input_Chars;
static int Beep_Pending;
/*}}}*/

static void redraw_message (void);
static void redraw_mini_buffer (void);

/*{{{ Screen Update Functions */

void slrn_smg_refresh (void)
{
   if (Slrn_TT_Initialized & SLRN_SMG_INIT)
     {
	slrn_push_suspension (0);
	if (Beep_Pending)
	  SLtt_beep ();
	Beep_Pending = 0;
	SLsmg_refresh ();
	slrn_pop_suspension ();
     }
}


void slrn_set_color (int color) /*{{{*/
{
   SLsmg_set_color (color);
}

/*}}}*/

void slrn_redraw (void) /*{{{*/
{
   if (Slrn_Batch) return;
   
   slrn_push_suspension (0);
   
   SLsmg_cls ();
   Slrn_Full_Screen_Update = 1;
   
   redraw_message ();
   slrn_update_screen ();
   redraw_mini_buffer ();

   slrn_smg_refresh ();
   slrn_pop_suspension ();
}

/*}}}*/

void slrn_print_percent (int row, int col, SLscroll_Window_Type *w) /*{{{*/
{
   int bot_showing;
   unsigned int bot_number;
   
   SLsmg_erase_eol ();
   SLsmg_gotorc (row, col);
   SLsmg_printf ("-- %d/%d", w->line_num, w->num_lines);

   bot_number = w->line_num + (w->nrows - w->window_row) - 1;
   
   bot_showing = ((w->bot_window_line == NULL)
		  || (w->num_lines == bot_number));

   if (w->line_num == w->window_row + 1)
     {
	SLsmg_write_string (bot_showing ? "  (All)" : "  (Top)" );
     }
   else if (bot_showing) SLsmg_write_string("  (Bot)");
   else SLsmg_printf("  (%d%%)", (100 * bot_number) / w->num_lines);
   SLsmg_erase_eol ();
}

/*}}}*/

void slrn_update_top_status_line (void) /*{{{*/
{
   if (Slrn_Full_Screen_Update == 0) return;
   SLsmg_gotorc (0, 0);
   slrn_set_color (MENU_COLOR);
   SLsmg_printf ("\
slrn %s ** Press '?' for help, 'q' to quit. ** Server: %s",
		 Slrn_Version,
		 Slrn_Server_Obj->sv_name);
   SLsmg_erase_eol ();
   slrn_set_color (0);
}

/*}}}*/

/*}}}*/
/*{{{ Message/Error Functions */

/* The first character is the color */
static char Message_Buffer[1024];

static void redraw_message (void)
{
   int color;
   char *m, *mmax;

   if (Slrn_Batch) return;
   
   if (Slrn_Message_Present == 0)
     return;
   
   slrn_push_suspension (0);

   SLsmg_gotorc (SLtt_Screen_Rows - 1, 0);
   
   color = Message_Buffer [0];
   m = Message_Buffer + 1;
   
   while (1)
     {
	mmax = slrn_strchr (m, 1);
	if (mmax == NULL)
	  mmax = m + strlen(m);
	
	slrn_set_color (color);
	SLsmg_write_nchars (m, (unsigned int) (mmax - m));
	if (*mmax == 0)
	  break;
	mmax++;
	if (*mmax == 0)
	  break;
	
	slrn_set_color (RESPONSE_CHAR_COLOR);
	SLsmg_write_nchars (mmax, 1);
	m = mmax + 1;
     }
   
   SLsmg_erase_eol ();
   slrn_set_color (0);
   
   slrn_pop_suspension ();
}

     
static void vmessage_1 (int color, char *fmt, va_list ap)
{
   vsprintf (Message_Buffer + 1, fmt, ap);
   Message_Buffer[0] = (char) color;
   Slrn_Message_Present = 1;
   redraw_message ();
}

static void vmessage (FILE *fp, char *fmt, va_list ap)
{
   if (Slrn_TT_Initialized & SLRN_SMG_INIT)
     vmessage_1 (0, fmt, ap);
   else
     slrn_tty_vmessage (fp, fmt, ap);
}

static void verror (char *fmt, va_list ap)
{
   if ((Slrn_TT_Initialized & SLRN_SMG_INIT) == 0)
     {
	slrn_tty_vmessage (stderr, fmt, ap);
     }
   else if (Error_Present == 0)
     {
	slrn_clear_message ();
	Error_Present = 1;
	Beep_Pending = 1;
	vmessage_1 (ERROR_COLOR, fmt, ap);
	SLang_flush_input ();
     }
   
   if (SLang_Error == 0) SLang_Error = INTRINSIC_ERROR;
}

/*}}}*/
void slrn_clear_message (void) /*{{{*/
{
   Slrn_Message_Present = Error_Present = 0;
   /* SLang_Error = 0; */
   Beep_Pending = 0;
   SLKeyBoard_Quit = 0;
   
   if ((Slrn_TT_Initialized & SLRN_SMG_INIT) == 0)
     return;
   
   slrn_push_suspension (0);
   SLsmg_gotorc (SLtt_Screen_Rows - 1, 0);
   SLsmg_erase_eol ();
   *Message_Buffer = 0;
   slrn_pop_suspension ();
}

/*}}}*/

void slrn_va_message (char *fmt, va_list ap)
{
   if (Error_Present == 0)
     vmessage (stderr, fmt, ap);
}

int slrn_message (char *fmt, ...) /*{{{*/
{
   va_list ap;
   
   if (Error_Present) return -1;
   va_start(ap, fmt);
   vmessage (stdout, fmt, ap);
   va_end (ap);
   return 0;
}

/*}}}*/

int slrn_message_now (char *fmt, ...) /*{{{*/
{
   va_list ap;
   
   if (Error_Present) return -1;
   va_start(ap, fmt);
   vmessage (stdout, fmt, ap);
   va_end (ap);
   slrn_smg_refresh ();
   Slrn_Message_Present = 0;
   return 0;
}

/*}}}*/

void slrn_error (char *fmt, ...) /*{{{*/
{
   va_list ap;

   va_start(ap, fmt);
   verror (fmt, ap);
   va_end (ap);
}

/*}}}*/

void slrn_error_now (unsigned secs, char *fmt, ...) /*{{{*/
{
   va_list ap;

   if (fmt != NULL)
     {
	va_start(ap, fmt);
	verror (fmt, ap);
	va_end (ap);
     }
   slrn_smg_refresh ();
   Slrn_Message_Present = 0;
   if (secs) sleep (secs);
}

/*}}}*/

int slrn_check_batch (void)
{
   if (Slrn_Batch == 0) return 0;
   slrn_error ("This function is not available in batch mode.");
   return -1;
}

/*}}}*/

/*{{{ File Related Functions */
   

#ifdef VMS
/*{{{ VMS Filename fixup functions */

static void vms_fix_name(char *name)
{
   int idx, pos;
   
   pos = strspn(name, VALID_FILENAME_CHARS);
   if (pos == strlen(name))
     return;
   for(idx=pos;idx<strlen(name);idx++)
     if (!(isdigit(name[idx]) || isalpha(name[idx]) || (name[idx] == '$') || (name[idx] == '_') || (name[idx] == '-')
	   || (name[idx] == '/')))
       name[idx] = '-';
}

static char Copystr[SLRN_MAX_PATH_LEN];
static int vms_copyname1 (char *name)
{
   strcpy(Copystr, name);
   return(1);
}

static int vms_copyname2 (char *name, int type)
{
   strcpy(Copystr, name);
   return(1);
}

/*}}}*/
#endif

void slrn_make_home_filename (char *name, char *file) /*{{{*/
{
   char *home;
#ifndef VMS
   if (slrn_is_absolute_path (name)
       || ((name[0] == '.') && (name[1] == SLRN_PATH_SLASH_CHAR))
#if defined(IBMPC_SYSTEM)
       || ((name[0] == '.') && (name[1] == '/'))
#endif
       )
     {
	strcpy (file, name);
#if defined(IBMPC_SYSTEM)
	slrn_os2_convert_path (file);
#endif
	return;
     }
   
   if (NULL == (home = getenv ("SLRNHOME")))
     home = getenv ("HOME");
   
   *file = 0;
   slrn_dircat (home, name, file);
#else
   char *cp, *cp1;
   static char fname[SLRN_MAX_PATH_LEN];
   char fn[SLRN_MAX_PATH_LEN], fn1[SLRN_MAX_PATH_LEN];
   int rc, idx;
   
   strcpy (fn1, name);
   if (NULL != slrn_strchr (name, ':'))
     {
	strcpy (file, name);
	return;
     }
   
   if (NULL == (home = getenv ("SLRNHOME")))
     home = getenv ("HOME");
   
   *file = 0;
   if (NULL != (cp = slrn_strchr (fn1, '/')))
     {
# ifdef __DECC
	*cp = '\0'; cp++;
	cp1 = decc$translate_vms(home);
	if (cp1 == 0 || (int)cp1 == -1)
	  { /* error translating */ }
	else
	  {
	     strcpy(fname, cp1);
	     strcat(cp1, "/");
 	  }
	strcat (cp1, fn1);
	
	vms_fix_name (cp1);
 	
	rc = decc$to_vms(cp1, vms_copyname2, 0, 2);
 	if (rc > 0)
 	  {
 	     strcpy(fname, Copystr);
 	     rc = mkdir(fname, 0755);
 	  }
	strcat(fname, cp);
# else
	*cp = '\0'; cp++;
	cp1 = shell$translate_vms(home);
	if (cp1 == 0 || (int)cp1 == -1)
	  { /* error translating */ }
	else
 	  {
	     strcpy(fname, cp1);
	     strcat(cp1, "/");
	  }
	strcat (cp1, fn1);
	
	vms_fix_name (cp1);
 	
	rc = shell$to_vms(cp1, vms_copyname2, 0, 2);
	if (rc > 0)
	  {
	     strcpy(fname, Copystr);
	     rc = mkdir(fname, 0755);
	  }
	strcat(fname, cp);
# endif
	strcpy(file,fname);
     }
   else
     {
	if (home != NULL) strcpy(file, home);
	strcat(file, name);
     }
#endif /* VMS */
}

/*}}}*/

int slrn_make_home_dirname (char *name, char *dir) /*{{{*/
{
   /* This needs modified to deal with VMS directory syntax */
#ifndef VMS
   slrn_make_home_filename (name, dir);
#else
   char *home, *cp;
   char fn[SLRN_MAX_PATH_LEN];
   static char fname[SLRN_MAX_PATH_LEN];
   int rc, idx, len;
   
   if (NULL != slrn_strchr (name, ':'))
     {
	strcpy (dir, name);
	return 0;
     }
   home = getenv ("HOME");
   *dir = 0;
   if (cp = strchr(name,'/'))
     {
#ifdef __DECC
	cp = decc$translate_vms(home);
	if (cp == 0 || (int)cp == -1)
	  { /* error translating */ }
	else
	  {
	     strcpy(fname, cp);
	     strcat(cp, "/");
	  }
	strcat (cp, name);
	vms_fix_name (cp);
	
	rc = decc$to_vms(cp, vms_copyname2, 0, 2);
	if (rc > 0)
	  {
	     strcpy(fname, Copystr);
	     rc = mkdir(fname, 0755);
	  }
#else
	if (shell$from_vms(home, vms_copyname1, 0))
	  {
	     if (Copystr != NULL) strcpy (fn, Copystr);
	     strcat(fn, "/");
	  }
	strcat (fn, name);
	vms_fix_name(fn);
	if (shell$to_vms(fn, vms_copyname1, 0))
	  strcpy(fname, Copystr);
#endif
	strcpy(dir,fname);
     }
   else
     {
	if (home != NULL) 
	 {
	  strcpy(dir, home);
	  len = strlen(dir) - 1;
	  if (dir[len] == ']')
	   {
	    dir[len] = '.';
	    strcat(dir, name);
	    strcat(dir, "]");
	   }
	  else
	    strcat(dir, name);
	 }
	else
	  strcat(dir, name);
     }
#endif /* VMS */
   
   return 0;
}

/*}}}*/

static unsigned int make_random (void)
{
   static unsigned long s;
   static int init;
   
   if (init == 0)
     {
	s = (unsigned long) time (NULL) + (unsigned long) getpid ();
	init = 1;
     }

   s = s * 69069UL + 1013904243UL;

   /* The time has been added to make this number somewhat unpredictable
    * based on the previous number.  The whole point of this is to foil
    * any attempt of a hacker to determine the name of the _next_ temp
    * file that slrn will create.
    */
   return (unsigned int) (s + (unsigned long) time (NULL));
}
   
/* Note: This function should not create a file that is deleted when it
 * is closed.
 */

FILE *slrn_open_tmpfile_in_dir (char *dir, char *file, char *mode)
{
   FILE *fp;
   unsigned int len;
   unsigned int i;
   char buf[80];
   
#ifndef VMS
   if (2 != slrn_file_exists (dir))
     return NULL;
#endif

#if defined(IBMPC_SYSTEM)
   sprintf (buf, "SLRN%04u", make_random ());
#else
   sprintf (buf, "SLRN%X", make_random ());
#endif
   
   if (-1 == slrn_dircat (dir, buf, file))
     return NULL;
   
#if defined(IBMPC_SYSTEM)
# define MAX_TMP_FILE_NUMBER 999
#else
# define MAX_TMP_FILE_NUMBER 1024
#endif
   
   len = strlen (file);
   for (i = 0; i < MAX_TMP_FILE_NUMBER; i++)
     {
	sprintf (file + len, ".%u", i);
	
	/* I wish this were an atomic operation but I do not know of a
	 * portable way to achieve that using standard C.
	 */
	if ((0 == slrn_file_exists (file))	    
	    && (NULL != (fp = fopen (file, mode))))
	  {
	     return fp;
	  }
     }
   return NULL;
}

	  

FILE *slrn_open_tmpfile (char *file, char *mode) /*{{{*/
{
   char *dir;
   
   dir = getenv ("TMP");
   if ((dir == NULL) || (2 != slrn_file_exists (dir)))
     dir = getenv ("TMPDIR");

   if ((dir == NULL) || (2 != slrn_file_exists (dir)))
     {
#ifdef VMS
	dir = "SYS$LOGIN:";
#else
# if defined(IBMPC_SYSTEM)
	dir = ".";
# else
	dir = "/tmp";
# endif
#endif
     }
   
   return slrn_open_tmpfile_in_dir (dir, file, mode);
}

/*}}}*/

FILE *slrn_open_home_file (char *name, char *mode, char *file, int create_flag) /*{{{*/
{
   slrn_make_home_filename (name, file);
   
#ifdef VMS
   if (create_flag)
     {
	FILE *fp = fopen (file, mode, "fop=cif");
	if (fp == NULL) perror ("fopen");
	return fp;
     }
#else
   (void) create_flag;
#endif	

   return fopen (file, mode);
}

/*}}}*/

int slrn_mail_file (char *file, int edit, unsigned int editline, char *to, char *subject) /*{{{*/
{
   char buf [256 + 2 * SLRN_MAX_PATH_LEN];
#if defined(IBMPC_SYSTEM)
   char outfile [SLRN_MAX_PATH_LEN];
#endif
   
   if (edit && (Slrn_Batch == 0))
     {
	if (slrn_edit_file (Slrn_Editor_Mail, file, editline) < 0) return -1;
	
	while (1)
	  {
	     char rsp;
	     
	     rsp = slrn_get_response ("yYnNeE", "Mail the message? \001Yes, \001No, \001Edit");
	     rsp |= 0x20;
	     if (rsp == 'n') return -1;
	     if (rsp == 'y') break;
	     if (slrn_edit_file (Slrn_Editor_Mail, file, 1) < 0) return -1;
	  }
     }
   slrn_message_now ("Sending ...");

   if (-1 == slrn_save_file_to_mail_file (file, Slrn_Save_Replies_File, NULL))
     return -1;

   slrn_chmap_fix_file (file);

#ifdef VMS
   sprintf(buf, "%s\"%s\"", MAIL_PROTOCOL, to);
   vms_send_mail( buf, subject, file );
#else
   (void) to; (void) subject;

   /* What I need to do is to open the file and feed it line by line to the
    * sendmail program.  This way I can strip out blank headers.
    */
   if (Slrn_Use_Mime)
     {
	FILE *fp, *pp;
	int header = 1;
	char line[1024];
	
	fp = fopen (file, "r");
	if (fp == NULL) return (-1);
	
	slrn_mime_scan_file (fp);
# if defined(IBMPC_SYSTEM)
	pp = slrn_open_tmpfile (outfile, "w");
# else
	pp = slrn_popen (Slrn_SendMail_Command, "w");
# endif
	if (pp == NULL)
	  {
	     slrn_fclose (fp);
	     return (-1);
	  }
	
	while (fgets (line, sizeof(line), fp) != NULL)
	  {
	     unsigned int len = strlen (line);
	     
	     if (len == 0) continue;
	     len--;
	     
	     if (line [len] == '\n') line [len] = 0;
	     
	     if (header)
	       {
		  if (line[0] == 0)
		    {
		       header = 0;
		       
		       slrn_mime_add_headers (pp);
		       fp = slrn_mime_encode (fp);
		    }
		  slrn_mime_header_encode (line, sizeof(line));
	       }
	     fputs (line, pp);
	     putc('\n', pp);
	  }
	slrn_fclose (fp);
#if defined(IBMPC_SYSTEM)
	slrn_fclose (pp);
	sprintf (buf, "%s %s", Slrn_SendMail_Command, outfile);
	slrn_posix_system (buf, 0);
#else
	slrn_pclose (pp);
#endif
     }
   else
     {
#if defined(IBMPC_SYSTEM)
	sprintf (buf, "%s %s", Slrn_SendMail_Command, file);
#else
	sprintf (buf, "%s < %s", Slrn_SendMail_Command, file);
#endif
	slrn_posix_system (buf, 0);
     }
#endif /* NOT VMS */
   slrn_message ("Sending...done");
   return 0;
}

/*}}}*/

#if SLRN_HAS_PIPING   
int _slrn_pclose (FILE *fp)
{
   int ret;

   ret = pclose (fp);
   if (ret == 0) return ret;

#if defined(WIFEXITED) && defined(WEXITSTATUS)
   if ((ret != -1) && WIFEXITED(ret))
     {
	ret = WEXITSTATUS(ret);
     }
#endif
   return ret;
}
#endif				       /* SLRN_HAS_PIPING */

#if SLRN_HAS_PIPING
/* There appears to be no simply way of getting the command assocated with
 * a pipe file descriptor.  Therefore, I will simply store them in a table.
 */
typedef struct _Pipe_Cmd_Table_Type
{
   char *cmd;
   FILE *fp;
   struct _Pipe_Cmd_Table_Type *next;
}
Pipe_Cmd_Table_Type;

Pipe_Cmd_Table_Type *Pipe_Cmd_Table;
   
static int store_pipe_cmd (char *cmd, FILE *fp)
{
   Pipe_Cmd_Table_Type *p;

   if (NULL == (cmd = SLang_create_slstring (cmd)))
     return -1;
   
   p = (Pipe_Cmd_Table_Type *)slrn_malloc (sizeof (Pipe_Cmd_Table_Type), 1, 0);
   if (p == NULL)
     {
	SLang_free_slstring (cmd);
	return -1;
     }
   p->cmd = cmd;
   p->fp = fp;
   p->next = Pipe_Cmd_Table;
   Pipe_Cmd_Table = p;
   return 0;
}

static void delete_pipe_cmd (FILE *fp)
{   
   Pipe_Cmd_Table_Type *last, *next;
   
   last = NULL;
   next = Pipe_Cmd_Table;
   while (next != NULL)
     {
	if (next->fp == fp)
	  {
	     if (last != NULL)
	       last->next = next->next;
	     else 
	       Pipe_Cmd_Table = next->next;
	     
	     SLang_free_slstring (next->cmd);
	     slrn_free ((char *) next);
	     return;
	  }
	
	last = next;
	next = next->next;
     }
   
   /* should not be reached */
}

static char *get_pipe_cmd (FILE *fp)
{
   Pipe_Cmd_Table_Type *p;
   
   p = Pipe_Cmd_Table;
   while (p != NULL)
     {
	if (p->fp == fp)
	  return p->cmd;
	
	p = p->next;
     }
   
   return "**UNKNOWN**";
}
#endif 				       /* SLRN_HAS_PIPING */
int slrn_pclose (FILE *fp) /*{{{*/
{
#if SLRN_HAS_PIPING
   int ret;
   if (fp == NULL) return -1;
   
   ret = _slrn_pclose (fp);
   if (ret)
     {
	char buf[SLRN_MAX_PATH_LEN];
	fprintf (stderr, "Command %s returned exit status %d.  Press RETURN.\n", 
		 get_pipe_cmd (fp), ret);
	fgets (buf, sizeof(buf), stdin);
     }

   delete_pipe_cmd (fp);

   slrn_set_display_state (SLRN_TTY_INIT | SLRN_SMG_INIT);
   return 0;
#else
   return -1;
#endif
}

/*}}}*/

FILE *slrn_popen (char *cmd, char *mode) /*{{{*/
{
#if SLRN_HAS_PIPING
   FILE *fp;
   
   slrn_set_display_state (0);
   fp = popen (cmd, mode);
   
   if (fp == NULL)
     {
	char buf[256];
	fprintf (stderr, "Command %s failed to run.  Press RETURN.\n", cmd);
	fgets (buf, sizeof(buf), stdin);
	slrn_set_display_state (SLRN_TTY_INIT | SLRN_SMG_INIT);
     }
   else (void) store_pipe_cmd (cmd, fp);

   return fp;
#else
   return NULL;
#endif
}

/*}}}*/


/*}}}*/

static int create_edit_command (char *edit, char *cmd, char *file, unsigned int line) /*{{{*/
{
   int d, s;
   char ch, *p = edit;
   /* Look for %d and %s */
   
   d = s = 0;
   
   while (0 != (ch = *p++))
     {
	if (ch != '%') continue;
	ch = *p;
	if (!d && (ch == 'd'))
	  {
	     *p = 'u';		       /* map %d to %u (unsigned) */
	     if (s == 0) d = 1; else d = 2;
	  }
	else if (!s && (ch == 's'))
	  {
	     if (d == 0) s = 1; else s = 2;
	  }
	else
	  {
	     slrn_error ("Invalid Editor definition.");
	     return 0;
	  }
	p++;
     }

#if defined(IBMPC_SYSTEM)
   /* Convert editor pathnames from / form to \\ form.  I wonder what 
    * happens when the pathname contains a space.  Hmmm...
    */
   p = edit;
   while ((*p != ' ') || (*p != '\t') || (*p != 0))
     {
	if (*p == '/') *p = '\\';
	p++;
     }
#endif
   
   /* No %d, %s */
   
   if ((d == 0) && (s == 0))
     {
	sprintf (cmd, "%s %s", edit, file);
     }
   else if (d == 0)
     {
	sprintf (cmd, edit, file);
     }
   else if (s == 0)
     {
	sprintf (cmd, edit, (int) line);
	strcat (edit, " ");
	strcat (edit, file);
     }
   else /* d and s */
     {
	if (d == 1)
	  sprintf (cmd, edit, line, file);
	else sprintf (cmd, edit, file, line);
     }
   return 1;
}

/*}}}*/
int slrn_edit_file (char *editor, char *file, unsigned int line) /*{{{*/
{
   char buf[1024];
   char editbuf[1024];
   int ret;
   
   if (editor == NULL)
     editor = Slrn_Editor;

   if ((editor == NULL)
       && (NULL == (editor = getenv("SLRN_EDITOR")))
       && (NULL == (editor = getenv("SLANG_EDITOR")))
       && (NULL == (editor = getenv("EDITOR")))
       && (NULL == (editor = getenv("VISUAL"))))
     {
#if defined(VMS) || defined(__NT__) || defined(__MINGW32__)
	editor = "edit";
#else
# if defined(__os2__)
	editor = "e";
# else
#  ifdef __unix__
	editor = "vi";
#  endif
# endif
#endif
     }
   
   strcpy (editbuf, editor);
   if (0 == create_edit_command(editbuf, buf, file, line)) return -1;
   
   ret = slrn_posix_system (buf, 1);
   
   /* Am I the only one who thinks this is a good idea?? */
   if (Slrn_TT_Initialized) while (SLang_input_pending (5))
     {
	SLang_flush_input ();
     }
   
   /* slrn_redraw (); */
   return ret;
}

/*}}}*/

/*{{{ Get Input From User Related Functions */

static void rline_update (unsigned char *buf, int len, int col) /*{{{*/
{
   slrn_push_suspension (0);
   SLsmg_gotorc (SLtt_Screen_Rows - 1, 0);
   SLsmg_write_nchars ((char *) buf, len);
   SLsmg_erase_eol ();
   SLsmg_gotorc (SLtt_Screen_Rows - 1, col);
   slrn_smg_refresh ();
   slrn_pop_suspension ();
}

/*}}}*/

/* If 1, redraw read_line.  If 2, redraw message */
static int Reading_Input;

static void redraw_mini_buffer (void) /*{{{*/
{
   if (Reading_Input == 1)
     SLrline_redraw (Slrn_Keymap_RLI);
   else if (Reading_Input == 2)
     redraw_message ();
}

/*}}}*/

static SLang_RLine_Info_Type *init_readline (void) /*{{{*/
{
   unsigned char *buf;
   SLang_RLine_Info_Type *rli;
   
   rli = (SLang_RLine_Info_Type *) slrn_malloc (sizeof(SLang_RLine_Info_Type),
						1, 1);
   if (rli == NULL)
     return NULL;
   
   if (NULL == (buf = (unsigned char *) slrn_malloc (256, 0, 1)))
     {
	SLFREE (rli);
	return NULL;
     }

   rli->buf = buf;
   rli->buf_len = 255;
   rli->tab = 8;
   rli->dhscroll = 20;
   rli->getkey = SLang_getkey;
   rli->tt_goto_column = NULL;
   rli->update_hook = rline_update;
#ifdef SL_RLINE_BLINK_MATCH
   rli->flags |= SL_RLINE_BLINK_MATCH;
   rli->input_pending = SLang_input_pending;
#endif
   if (SLang_init_readline (rli) < 0)
     {
	SLFREE (rli);
	SLFREE (buf);
	rli = NULL;
     }
   
   return rli;
}

/*}}}*/

int slrn_init_readline (void) /*{{{*/
{
   if ((Slrn_Keymap_RLI == NULL) 
       && (NULL == (Slrn_Keymap_RLI = init_readline ())))
     return -1;
   
   Slrn_RLine_Keymap = Slrn_Keymap_RLI->keymap;

   return 0;
}   

/*}}}*/

static int read_from_input_string (char *str)
{
   char *s;
   
   if (Input_String == NULL) return -1;
   
   s = slrn_strchr (Input_String_Ptr, '\n');
   if (s != NULL) 
     *s = 0;
   
   strncpy (str, Input_String_Ptr, 255);
   str[255] = 0;
   
   if (s == NULL)
     {
	SLFREE (Input_String);
	Input_String_Ptr = Input_String = NULL;
     }
   else Input_String_Ptr = s + 1;
   
   return strlen (str);
}

/* s could be NULL.  If so, input string is cleared. */
void slrn_set_input_string (char *s)
{
   slrn_free (Input_String);
   Input_String = s;
   Input_String_Ptr = s;
}

static char read_from_input_char (void)
{
   if (Input_Chars_Ptr == NULL)
     return 0;
   
   if (*Input_Chars_Ptr == 0)
     {
	slrn_set_input_chars (NULL);
	return 0;
     }
   
   return *Input_Chars_Ptr++;
}

void slrn_set_input_chars (char *s)
{
   slrn_free (Input_Chars);
   Input_Chars = s;
   Input_Chars_Ptr = s;
}

static int generic_read_input (char *prompt, char *dfl, char *str, int trim_flag, 
			       int no_echo, int point) /*{{{*/
{
   int i;
   int tt_init_state;
   char prompt_buf[256];
   unsigned int len;
   int save_slang_error;
   
   Slrn_Full_Screen_Update = 1;

   strncpy (prompt_buf, prompt, sizeof (prompt_buf));
   prompt_buf[sizeof(prompt_buf) - 1] = 0;
   len = strlen (prompt);
   if (len + 3 < sizeof (prompt_buf))
     {
	if (len && (prompt[len - 1] != '?'))
	  {
	     strcpy (prompt_buf + len, ": ");
	     len += 2;
	  }
	else 
	  {
	     strcpy (prompt_buf + len, " ");
	     len++;
	  }
     }
   
   if ((dfl != NULL) && *dfl)
     {
	if (len + 13 + strlen (dfl) < sizeof (prompt_buf))
	  {
	     sprintf (prompt_buf + len,
		      "(default: %s) ", dfl);
	  }
	else dfl = NULL;
     }
   prompt = prompt_buf;

   if ((str == NULL) && (dfl == NULL)) return -1;
   
   Slrn_Keymap_RLI->edit_width = SLtt_Screen_Cols - 1;
   Slrn_Keymap_RLI->prompt = prompt;
   *Slrn_Keymap_RLI->buf = 0;

   /* slrn_set_suspension (1); */   

   if ((str != NULL) && *str)
     {
	strcpy ((char *) Slrn_Keymap_RLI->buf, str);
	if (point == 0)
	  Slrn_Keymap_RLI->point = 0;
	else
	  Slrn_Keymap_RLI->point = strlen (str);

	*str = 0;
     }
   if (str == NULL) str = dfl;

   i = read_from_input_string (str);
   if (i >= 0) return i;
   
   tt_init_state = Slrn_TT_Initialized;
   
   slrn_set_display_state (Slrn_TT_Initialized | SLRN_TTY_INIT);
   
   if (no_echo)
     {
#ifdef SL_RLINE_NO_ECHO
	Slrn_Keymap_RLI->flags |= SL_RLINE_NO_ECHO;
#endif
     }
   else
     {
#ifdef SL_RLINE_NO_ECHO
	Slrn_Keymap_RLI->flags &= ~SL_RLINE_NO_ECHO;
#endif
     }

   if (tt_init_state & SLRN_SMG_INIT)
     Slrn_Keymap_RLI->update_hook = rline_update;
   else
     Slrn_Keymap_RLI->update_hook = NULL;

   slrn_enable_mouse (0);
   
   
   save_slang_error = SLang_Error;
   SLang_Error = 0;

   Reading_Input = 1;
   i = SLang_read_line (Slrn_Keymap_RLI);
   Reading_Input = 0;
   
   slrn_enable_mouse (1);
   
   if ((i >= 0) && !SLang_Error && !SLKeyBoard_Quit)
     {
	char *b = (char *) Slrn_Keymap_RLI->buf;
	
	if (*b) 
	  {
	     if (no_echo == 0) SLang_rline_save_line (Slrn_Keymap_RLI);
	     if (trim_flag) 
	       {
		  slrn_trim_string (b);
		  b = slrn_skip_whitespace (b);
	       }
	  }
	else if (dfl != NULL) b = dfl;

	/* b could be equal to dfl and dfl could be equal to str.  If this is
	 * the case, there is no need to perform the strcpy */
	if (b != str) strcpy (str, b);
	i = strlen (str);
     }
   
   if (SLKeyBoard_Quit) i = -1;
   SLKeyBoard_Quit = 0;
   SLang_Error = save_slang_error;
   
   slrn_set_display_state (tt_init_state);

   if (tt_init_state & SLRN_SMG_INIT)
     {
	/* put cursor at edge of screen to comfort user */
	SLsmg_gotorc (SLtt_Screen_Rows - 1, 0);
	slrn_smg_refresh ();
     }
   else
     {
	putc ('\n', stdout);
	fflush (stdout);
     }

   /* slrn_set_suspension (0); */
   return i;
}

/*}}}*/

int slrn_read_input (char *prompt, char *dfl, char *str, int trim_flag, int point)
{
   return generic_read_input (prompt, dfl, str, trim_flag, 0, point);
}

int slrn_read_input_no_echo (char *prompt, char *dfl, char *str, int trim_flag, int point)
{
   return generic_read_input (prompt, dfl, str, trim_flag, 1, point);
}

int slrn_read_integer (char *prompt, int *dflt, int *np) /*{{{*/
{
   char sdfl_buf[32];
   char *sdfl = NULL;
   char str[256];
   int n;
   
   if (dflt != NULL)
     {
	sprintf (sdfl_buf, "%d", *dflt);
	sdfl = sdfl_buf;
     }
   
   *str = 0;
   if (-1 == (n = slrn_read_input (prompt, sdfl, str, 1, 0)))
     {
	slrn_error ("Abort!");
	return -1;
     }
   
   if (1 != sscanf(str, "%d", &n))
     {
	slrn_error ("Integer expected.");
	return -1;
     }
   *np = n;
   return 0;
}

/*}}}*/

char slrn_get_response (char *valid_chars, char *str, ...) /*{{{*/
{
   char ch;
   va_list ap;
   char *v;
   
   /* if (SLang_Error) return -1; */
   if (Error_Present)
     slrn_error_now (2, NULL);

   while (1)
     {
	Slrn_Full_Screen_Update = 1;

	ch = read_from_input_char ();
	
	if (ch == 0)
	  {
	     if (Slrn_TT_Initialized == 0)
	       {
		  char buf[256];
	  
		  va_start(ap, str);
		  slrn_tty_vmessage (stdout, str, ap);
		  va_end(ap);
		  
		  *buf = 0;
		  (void) fgets (buf, sizeof(buf), stdin);
		  ch = *buf;
	       }
	     else 
	       {
		  SLang_flush_input ();
		  slrn_clear_message ();
		  
		  va_start(ap, str);
		  vmessage_1 (0, str, ap);
		  va_end(ap);
		  
		  slrn_smg_refresh ();
		  
		  Reading_Input = 2;
		  ch = SLang_getkey ();
		  Reading_Input = 0;
	     
		  slrn_clear_message ();
		  SLang_Error = SLKeyBoard_Quit = 0;
	       }
	  }

	v = valid_chars;
	while (*v)
	  {
	     if (*v == ch) return ch;
	     v++;
	  }
	
	slrn_error_now (0, "Invalid response! Try again.");
	if (Slrn_TT_Initialized & SLRN_TTY_INIT)
	  {
	     (void) SLang_input_pending (15);
	  }
     }
}

/*}}}*/
int slrn_get_yesno (int dflt, char *str, ...) /*{{{*/
{
   va_list ap;
   char buf[512];
   char ch, rsp;
   char *fmt;
   
   /* if (SLang_Error) return -1; */
   
   va_start(ap, str);
   (void) vsprintf(buf, str, ap);
   va_end(ap);
   
   if (dflt)
     {
	ch = 'y';
	fmt = "? [\001Y]es, \001No";
     }
   else
     {
	ch = 'n';
	fmt = "? \001Yes, [\001N]o";
     }
   
   strcat (buf, fmt);
   rsp = slrn_get_response ("yYnN\r", buf);
   if (rsp == '\r') rsp = ch;
   else rsp |= 0x20;
   
   if (rsp == 'n') return 0;
   return 1;
}

/*}}}*/
int slrn_get_yesno_cancel (char *str, ...) /*{{{*/
{
   va_list ap;
   char buf[512];
   
   if (SLang_Error) return -1;
   
   if (strlen (str) >= sizeof (buf) + 25)
     return -1;
   
   va_start(ap, str);
   (void) vsprintf(buf, str, ap);
   va_end(ap);
   
   strcat (buf, "? [\001Y]es, \001No, \001Cancel");
   
   switch (slrn_get_response ("\007yYnNcC\r", buf))
     {
      case '\r':
      case 'Y':
      case 'y':
	return 1;
	
      case 'n':
      case 'N':
	return 0;
	
      default:
	return -1;
     }
}

/*}}}*/

void slrn_get_mouse_rc (int *rp, int *cp) /*{{{*/
{
   int r, c;
   
   c = (unsigned char) SLang_getkey () - 32;
   r = (unsigned char) SLang_getkey () - 32;
   if (cp != NULL) *cp = c;
   if (rp != NULL) *rp = r;
}

/*}}}*/

/*}}}*/

/*{{{ Misc Regexp Utility Functions */

SLRegexp_Type *slrn_compile_regexp_pattern (char *pat) /*{{{*/
{
   static unsigned char compiled_pattern_buf [512];
   static SLRegexp_Type re;
   
   re.pat = (unsigned char *) pat;
   re.buf = compiled_pattern_buf;
   re.buf_len = sizeof (compiled_pattern_buf);
   re.case_sensitive = 0;
   
   if (0 != SLang_regexp_compile (&re))
     {
	slrn_error ("Invalid regular expression or expression too long.");
	return NULL;
     }
   return &re;
}

/*}}}*/

unsigned char *slrn_regexp_match (SLRegexp_Type *re, char *str) /*{{{*/
{
   unsigned int len;
   
   if ((str == NULL)
       || (re->min_length > (len = strlen (str))))
     return NULL;
   
   return SLang_regexp_match ((unsigned char *)str, len, re);
}

/*}}}*/

/*}}}*/

int slrn_is_fqdn (char *h) /*{{{*/
{
   char *p;
   
   /* Believe it or not, I have come across one system with a '(' character
    * as part of the hostname!!!  I suppose that I should also check for
    * other strange characters as well.  This is an issue since a message
    * id will be composed from the fqdn.  For that reason, such names will
    * be rejected.  Sigh.
    */
   if (NULL != slrn_strbrk (h, "~`!@#$%^&*()=+|\\[]{}/?;"))
     return 0;
   
   p = slrn_strchr (h, '.');
   return ((p != NULL) && (p != h));
}

/*}}}*/
void slrn_get_user_info (void) /*{{{*/
{
   struct hostent *host_entry;
   char *name, *host;
#ifdef HAS_PASSWORD_CODE
   struct passwd *pw;
#endif
   
   /* Fill in what is assumed to be non-NULL by rest of program. */
   Slrn_User_Info.followup_string = slrn_safe_strmalloc ("In article %m, %r wrote:");
   Slrn_User_Info.reply_string = slrn_safe_strmalloc ("In %n, you wrote:");

   Slrn_Courtesy_CC_Message = slrn_safe_strmalloc ("[This message has also been posted.]");
   
   /* Now get default values for rest. */
   
   host = Slrn_User_Info.host;
   
   /* gethostname may not provide the full name so use gethostbyname
    * to get more information.  Why isn't there a simplified interface to
    * get the FQDN!!!!
    */
   host_entry = NULL;
   if (-1 != gethostname (host, MAX_HOST_NAME_LEN))
     host_entry = gethostbyname (host);
#if defined(TRY_AGAIN) && !defined(MULTINET)
   if ((host_entry == NULL) && (h_errno == TRY_AGAIN))
     {
	sleep (2);
	host_entry = gethostbyname (host);
     }
#endif
   
   if ((host_entry == NULL) || (host_entry->h_name == NULL))
     *host = 0;
   else
     {
	if (slrn_is_fqdn ((char *)host_entry->h_name))
	  {
	     Slrn_User_Info.posting_host = slrn_safe_strmalloc ((char *)host_entry->h_name);
	  }
	else
	  {
	     char **aliases;
	     aliases = host_entry->h_aliases;
	     if (aliases != NULL) while (*aliases != NULL)
	       {
		  if (slrn_is_fqdn (*aliases))
		    {
		       Slrn_User_Info.posting_host = slrn_safe_strmalloc (*aliases);
		       break;
		    }
		  aliases++;
	       }
	  }
	
	if (Slrn_User_Info.posting_host == NULL)
	  {
	     strcpy (host, (char *) host_entry->h_name);
	  }
	else strcpy (host, Slrn_User_Info.posting_host);
     }
   
#if defined(USE_DOMAIN_NAME) && defined(MY_DOMAIN_NAME)
   if (*host && (0 == slrn_is_fqdn (host)))
     {
	char *my_domain_name;
	unsigned int len;
	
	len = strlen (host);
	my_domain_name = MY_DOMAIN_NAME;
	if (*my_domain_name != '.')
	  {
	     host[len++] = '.';
	  }
	strcpy (host + len, my_domain_name);
     }
#endif
   /* If a value was compiled in, use it. */
#ifdef OUR_HOSTNAME
# ifdef __unix__
   /* If the value is a file, read the file for the FQDN */
   if (slrn_is_absolute_path (OUR_HOSTNAME))
     {
	FILE *fp;
	
	if (NULL != (fp = fopen(OUR_HOSTNAME, "r")))
	  {
	   char tmphost[MAX_HOST_NAME_LEN];
	     
	     if (fgets(tmphost, MAX_HOST_NAME_LEN , fp) != NULL)
	       {
		  char *p;
		  
		  if ((p = slrn_strchr (tmphost, '\n')) != NULL)
		    *p = '\0';
	       /* If the entry is valid, we're done */
		  if (slrn_is_fqdn(tmphost))
		    strcpy(host, tmphost);
	       }
	     fclose(fp);
	  }
     }
   else
# endif
     if (slrn_is_fqdn (OUR_HOSTNAME)
	 && strlen(OUR_HOSTNAME) < MAX_HOST_NAME_LEN)
       strcpy (host, OUR_HOSTNAME);
#endif

   /* Allow user chance to over ride this.  Until now, host was a pointer
    * to Slrn_User_Info.host.
    */
   if ((NULL != (host = getenv ("HOSTNAME")))
       && slrn_is_fqdn (host))
     {
	strncpy (Slrn_User_Info.host, host, MAX_HOST_NAME_LEN);
	Slrn_User_Info.host[MAX_HOST_NAME_LEN - 1] = 0;
     }
   
#ifdef VMS
   name = slrn_vms_getlogin();
#else
   name = NULL;
# ifdef HAS_PASSWORD_CODE
   /* I cannot use getlogin under Unix because some implementations 
    * truncate the username to 8 characters.  Besides, I suspect that
    * it is equivalent to the following line.
    */
   pw = getpwuid (getuid ());
   if (pw != NULL)
     name = pw->pw_name;
# endif
#endif
   
   if (((name == NULL) || (*name == 0))
       && ((name = getenv("USER")) == NULL)
       && ((name = getenv("LOGNAME")) == NULL))
     name = "";

   Slrn_User_Info.username = slrn_safe_strmalloc (name);
   Slrn_User_Info.login_name = slrn_safe_strmalloc (name);

   if ((Slrn_User_Info.replyto = getenv ("REPLYTO")) == NULL)
     Slrn_User_Info.replyto = "";
   Slrn_User_Info.replyto = slrn_safe_strmalloc (Slrn_User_Info.replyto);
   
#ifdef VMS
   Slrn_User_Info.realname = slrn_vms_fix_fullname(slrn_vms_get_uaf_fullname());
#else
   if (((Slrn_User_Info.realname = getenv ("NAME")) == NULL)
       )
     {
	Slrn_User_Info.realname = "";
     }
#endif

   Slrn_User_Info.realname = slrn_safe_strmalloc (Slrn_User_Info.realname);
   
   /* truncate at character used to delineate extra gecos fields */
   name = Slrn_User_Info.realname;
   while (*name && (*name != ',')) name++;
   *name = 0;
   
   Slrn_User_Info.org = getenv ("ORGANIZATION");
#ifdef OUR_ORGANIZATION
   if (Slrn_User_Info.org == NULL) Slrn_User_Info.org = OUR_ORGANIZATION;
#endif
   if (Slrn_User_Info.org != NULL)
     {
	/* Check to see if this is an organization file. */
	char orgbuf[512];
	if (slrn_is_absolute_path (Slrn_User_Info.org))
	  {
	     FILE *fporg;
	     if (NULL != (fporg = fopen (Slrn_User_Info.org, "r")))
	       {
		  if (NULL != fgets (orgbuf, sizeof (orgbuf) - 1, fporg))
		    {
		       unsigned int orglen = strlen (orgbuf);
		       if (orglen && (orgbuf[orglen - 1] == '\n'))
			 orgbuf[orglen - 1] = 0;
		       Slrn_User_Info.org = orgbuf;
		    }
		  slrn_fclose (fporg);
	       }
	  }
	Slrn_User_Info.org = slrn_safe_strmalloc (Slrn_User_Info.org);
     }
   
   Slrn_User_Info.signature = slrn_safe_strmalloc (SLRN_SIGNATURE_FILE);
   
#if SLRN_HAS_MIME
   Slrn_Mime_Display_Charset = slrn_safe_strmalloc ("iso-8859-1");
#endif
   
#ifdef SLRN_SENDMAIL_COMMAND
   Slrn_SendMail_Command = slrn_safe_strmalloc (SLRN_SENDMAIL_COMMAND);
#endif
}

/*}}}*/
