/*
 *  OpenVPN -- An application to securely tunnel IP networks
 *             over a single TCP/UDP port, with support for SSL/TLS-based
 *             session authentication and key exchange,
 *             packet encryption, packet authentication, and
 *             packet compression.
 *
 *  Copyright (C) 2002-2010 OpenVPN Technologies, Inc. <sales@openvpn.net>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2
 *  as published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program (see the file COPYING included with this
 *  distribution); if not, write to the Free Software Foundation, Inc.,
 *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#elif defined(_MSC_VER)
#include "config-msvc.h"
#endif

#include "syshead.h"

#include "error.h"
#include "buffer.h"
#include "misc.h"
#include "win32.h"
#include "socket.h"
#include "tun.h"
#include "otime.h"
#include "perf.h"
#include "status.h"
#include "integer.h"
#include "ps.h"
#include "mstats.h"

#ifdef ENABLE_CRYPTO
#ifdef ENABLE_CRYPTO_OPENSSL
#include <openssl/err.h>
#endif
#endif

#include "memdbg.h"

#if SYSLOG_CAPABILITY
#ifndef LOG_OPENVPN
#define LOG_OPENVPN LOG_DAEMON
#endif
#endif

/* Globals */
unsigned int x_debug_level; /* GLOBAL */

/* Mute state */
static int mute_cutoff;     /* GLOBAL */
static int mute_count;      /* GLOBAL */
static int mute_category;   /* GLOBAL */

/*
 * Output mode priorities are as follows:
 *
 *  (1) --log-x overrides everything
 *  (2) syslog is used if --daemon or --inetd is defined and not --log-x
 *  (3) if OPENVPN_DEBUG_COMMAND_LINE is defined, output
 *      to constant logfile name.
 *  (4) Output to stdout.
 */

/* If true, indicates that stdin/stdout/stderr
   have been redirected due to --log */
static bool std_redir;      /* GLOBAL */

/* Should messages be written to the syslog? */
static bool use_syslog;     /* GLOBAL */

/* Should timestamps be included on messages to stdout/stderr? */
static bool suppress_timestamps; /* GLOBAL */

/* The program name passed to syslog */
#if SYSLOG_CAPABILITY
static char *pgmname_syslog;  /* GLOBAL */
#endif

/* If non-null, messages should be written here (used for debugging only) */
static FILE *msgfp;         /* GLOBAL */

/* If true, we forked from main OpenVPN process */
static bool forked;         /* GLOBAL */

/* our default output targets */
static FILE *default_out; /* GLOBAL */
static FILE *default_err; /* GLOBAL */

void
msg_forked (void)
{
  forked = true;
}

bool
set_debug_level (const int level, const unsigned int flags)
{
  const int ceiling = 15;

  if (level >= 0 && level <= ceiling)
    {
      x_debug_level = level;
      return true;
    }
  else if (flags & SDL_CONSTRAIN)
    {
      x_debug_level = constrain_int (level, 0, ceiling);
      return true;
    }
  return false;
}

bool
set_mute_cutoff (const int cutoff)
{
  if (cutoff >= 0)
    {
      mute_cutoff = cutoff;
      return true;
    }
  else
    return false;
}

int
get_debug_level (void)
{
  return x_debug_level;
}

int
get_mute_cutoff (void)
{
  return mute_cutoff;
}

void
set_suppress_timestamps (bool suppressed)
{
  suppress_timestamps = suppressed;
}

void
error_reset ()
{
  use_syslog = std_redir = false;
  suppress_timestamps = false;
  x_debug_level = 1;
  mute_cutoff = 0;
  mute_count = 0;
  mute_category = 0;
  default_out = OPENVPN_MSG_FP;
  default_err = OPENVPN_MSG_FP;

#ifdef OPENVPN_DEBUG_COMMAND_LINE
  msgfp = fopen (OPENVPN_DEBUG_FILE, "w");
  if (!msgfp)
    openvpn_exit (OPENVPN_EXIT_STATUS_CANNOT_OPEN_DEBUG_FILE); /* exit point */
#else
  msgfp = NULL;
#endif
}

void
errors_to_stderr (void)
{
  default_err = OPENVPN_ERROR_FP;  
}

/*
 * Return a file to print messages to before syslog is opened.
 */
FILE *
msg_fp(const unsigned int flags)
{
  FILE *fp = msgfp;
  if (!fp)
    fp = (flags & (M_FATAL|M_USAGE_SMALL)) ? default_err : default_out;
  if (!fp)
    openvpn_exit (OPENVPN_EXIT_STATUS_CANNOT_OPEN_DEBUG_FILE); /* exit point */
  return fp;
}

#define SWAP { tmp = m1; m1 = m2; m2 = tmp; }

int x_msg_line_num; /* GLOBAL */

void x_msg (const unsigned int flags, const char *format, ...)
{
  va_list arglist;
  va_start (arglist, format);
  x_msg_va (flags, format, arglist);
  va_end (arglist);
}

void x_msg_va (const unsigned int flags, const char *format, va_list arglist)
{
  struct gc_arena gc;
#if SYSLOG_CAPABILITY
  int level;
#endif
  char *m1;
  char *m2;
  char *tmp;
  int e;
  const char *prefix;
  const char *prefix_sep;

  void usage_small (void);

#ifndef HAVE_VARARG_MACROS
  /* the macro has checked this otherwise */
  if (!MSG_TEST (flags))
    return;
#endif

  e = openvpn_errno ();

  /*
   * Apply muting filter.
   */
#ifndef HAVE_VARARG_MACROS
  /* the macro has checked this otherwise */
  if (!dont_mute (flags))
    return;
#endif

  gc_init (&gc);

  m1 = (char *) gc_malloc (ERR_BUF_SIZE, false, &gc);
  m2 = (char *) gc_malloc (ERR_BUF_SIZE, false, &gc);

  vsnprintf (m1, ERR_BUF_SIZE, format, arglist);
  m1[ERR_BUF_SIZE - 1] = 0; /* windows vsnprintf needs this */

  if ((flags & M_ERRNO) && e)
    {
      openvpn_snprintf (m2, ERR_BUF_SIZE, "%s: %s (errno=%d)",
			m1, strerror_ts (e, &gc), e);
      SWAP;
    }

#ifdef ENABLE_CRYPTO
#ifdef ENABLE_CRYPTO_OPENSSL
  if (flags & M_SSL)
    {
      int nerrs = 0;
      int err;
      while ((err = ERR_get_error ()))
	{
	  openvpn_snprintf (m2, ERR_BUF_SIZE, "%s: %s",
			    m1, ERR_error_string (err, NULL));
	  SWAP;
	  ++nerrs;
	}
      if (!nerrs)
	{
	  openvpn_snprintf (m2, ERR_BUF_SIZE, "%s (OpenSSL)", m1);
	  SWAP;
	}
    }
#endif
#endif

  if (flags & M_OPTERR)
    {
      openvpn_snprintf (m2, ERR_BUF_SIZE, "Options error: %s", m1);
      SWAP;
    }

#if SYSLOG_CAPABILITY
  if (flags & (M_FATAL|M_NONFATAL|M_USAGE_SMALL))
    level = LOG_ERR;
  else if (flags & M_WARN)
    level = LOG_WARNING;
  else
    level = LOG_NOTICE;
#endif

  /* set up client prefix */
  if (flags & M_NOIPREFIX)
    prefix = NULL;
  else
    prefix = msg_get_prefix ();
  prefix_sep = " ";
  if (!prefix)
    prefix_sep = prefix = "";

  /* virtual output capability used to copy output to management subsystem */
  if (!forked)
    {
      const struct virtual_output *vo = msg_get_virtual_output ();
      if (vo)
	{
	  openvpn_snprintf (m2, ERR_BUF_SIZE, "%s%s%s",
			    prefix,
			    prefix_sep,
			    m1);
	  virtual_output_print (vo, flags, m2);
	}
    }

  if (!(flags & M_MSG_VIRT_OUT))
    {
      if (use_syslog && !std_redir && !forked)
	{
#if SYSLOG_CAPABILITY
	  syslog (level, "%s%s%s",
		  prefix,
		  prefix_sep,
		  m1);
#endif
	}
      else
	{
	  FILE *fp = msg_fp(flags);
	  const bool show_usec = check_debug_level (DEBUG_LEVEL_USEC_TIME);

	  if ((flags & M_NOPREFIX) || suppress_timestamps)
	    {
	      fprintf (fp, "%s%s%s%s",
		       prefix,
		       prefix_sep,
		       m1,
		       (flags&M_NOLF) ? "" : "\n");
	    }
	  else
	    {
	      fprintf (fp, "%s %s%s%s%s",
		       time_string (0, 0, show_usec, &gc),
		       prefix,
		       prefix_sep,
		       m1,
		       (flags&M_NOLF) ? "" : "\n");
	    }
	  fflush(fp);
	  ++x_msg_line_num;
	}
    }

  if (flags & M_FATAL)
    msg (M_INFO, "Exiting due to fatal error");

  if (flags & M_FATAL)
    openvpn_exit (OPENVPN_EXIT_STATUS_ERROR); /* exit point */

  if (flags & M_USAGE_SMALL)
    usage_small ();

  gc_free (&gc);
}

/*
 * Apply muting filter.
 */
bool
dont_mute (unsigned int flags)
{
  bool ret = true;
  if (mute_cutoff > 0 && !(flags & M_NOMUTE))
    {
      const int mute_level = DECODE_MUTE_LEVEL (flags);
      if (mute_level > 0 && mute_level == mute_category)
	{
	  if (mute_count == mute_cutoff)
	    msg (M_INFO | M_NOMUTE, "NOTE: --mute triggered...");
	  if (++mute_count > mute_cutoff)
	    ret = false;
	}
      else
	{
	  const int suppressed = mute_count - mute_cutoff;
	  if (suppressed > 0)
	    msg (M_INFO | M_NOMUTE,
		 "%d variation(s) on previous %d message(s) suppressed by --mute",
		 suppressed,
		 mute_cutoff);
	  mute_count = 1;
	  mute_category = mute_level;
	}
    }
  return ret;
}

void
assert_failed (const char *filename, int line)
{
  msg (M_FATAL, "Assertion failed at %s:%d", filename, line);
}

/*
 * Fail memory allocation.  Don't use msg() because it tries
 * to allocate memory as part of its operation.
 */
void
out_of_memory (void)
{
  fprintf (stderr, PACKAGE_NAME ": Out of Memory\n");
  exit (1);
}

void
open_syslog (const char *pgmname, bool stdio_to_null)
{
#if SYSLOG_CAPABILITY
  if (!msgfp && !std_redir)
    {
      if (!use_syslog)
	{
	  pgmname_syslog = string_alloc (pgmname ? pgmname : PACKAGE, NULL);
	  openlog (pgmname_syslog, LOG_PID, LOG_OPENVPN);
	  use_syslog = true;

	  /* Better idea: somehow pipe stdout/stderr output to msg() */
	  if (stdio_to_null)
	    set_std_files_to_null (false);
	}
    }
#else
  msg (M_WARN, "Warning on use of --daemon/--inetd: this operating system lacks daemon logging features, therefore when I become a daemon, I won't be able to log status or error messages");
#endif
}

void
close_syslog ()
{
#if SYSLOG_CAPABILITY
  if (use_syslog)
    {
      closelog();
      use_syslog = false;
      if (pgmname_syslog)
	{
	  free (pgmname_syslog);
	  pgmname_syslog = NULL;
	}
    }
#endif
}

#ifdef WIN32

static HANDLE orig_stderr;

HANDLE
get_orig_stderr (void)
{
  if (orig_stderr)
    return orig_stderr;
  else
    return GetStdHandle (STD_ERROR_HANDLE);
}

#endif

void
redirect_stdout_stderr (const char *file, bool append)
{
#if defined(WIN32)
  if (!std_redir)
    {
      struct gc_arena gc = gc_new ();
      HANDLE log_handle;
      int log_fd;

      SECURITY_ATTRIBUTES saAttr; 
      saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 
      saAttr.bInheritHandle = TRUE; 
      saAttr.lpSecurityDescriptor = NULL; 

      log_handle = CreateFileW (wide_string (file, &gc),
                                GENERIC_WRITE,
                                FILE_SHARE_READ,
                                &saAttr,
                                append ? OPEN_ALWAYS : CREATE_ALWAYS,
                                FILE_ATTRIBUTE_NORMAL,
                                NULL);

      gc_free (&gc);

      if (log_handle == INVALID_HANDLE_VALUE)
	{
	  msg (M_WARN|M_ERRNO, "Warning: cannot open --log file: %s", file);
	  return;
	}

      /* append to logfile? */
      if (append)
	{
	  if (SetFilePointer (log_handle, 0, NULL, FILE_END) == INVALID_SET_FILE_POINTER)
	    msg (M_ERR, "Error: cannot seek to end of --log file: %s", file);
	}
      
      /* save original stderr for password prompts */
      orig_stderr = GetStdHandle (STD_ERROR_HANDLE);

#if 0 /* seems not be necessary with stdout/stderr redirection below*/
      /* set up for redirection */
      if (!SetStdHandle (STD_OUTPUT_HANDLE, log_handle)
	  || !SetStdHandle (STD_ERROR_HANDLE, log_handle))
	msg (M_ERR, "Error: cannot redirect stdout/stderr to --log file: %s", file);
#endif

      /* direct stdout/stderr to point to log_handle */
      log_fd = _open_osfhandle ((intptr_t)log_handle, _O_TEXT);
      if (log_fd == -1)
	msg (M_ERR, "Error: --log redirect failed due to _open_osfhandle failure");
      
      /* open log_handle as FILE stream */
      ASSERT (msgfp == NULL);
      msgfp = _fdopen (log_fd, "wt");
      if (msgfp == NULL)
	msg (M_ERR, "Error: --log redirect failed due to _fdopen");

      /* redirect C-library stdout/stderr to log file */
      if (_dup2 (log_fd, 1) == -1 || _dup2 (log_fd, 2) == -1)
	msg (M_WARN, "Error: --log redirect of stdout/stderr failed");

      std_redir = true;
    }
#elif defined(HAVE_DUP2)
  if (!std_redir)
    {
      int out = open (file,
		      O_CREAT | O_WRONLY | (append ? O_APPEND : O_TRUNC),
		      S_IRUSR | S_IWUSR);

      if (out < 0)
	{
	  msg (M_WARN|M_ERRNO, "Warning: Error redirecting stdout/stderr to --log file: %s", file);
	  return;
	}

      if (dup2 (out, 1) == -1)
	msg (M_ERR, "--log file redirection error on stdout");
      if (dup2 (out, 2) == -1)
	msg (M_ERR, "--log file redirection error on stderr");

      if (out > 2)
	close (out);

      std_redir = true;
    }

#else
  msg (M_WARN, "WARNING: The --log option is not supported on this OS because it lacks the dup2 function");
#endif
}

/*
 * Functions used to check return status
 * of I/O operations.
 */

unsigned int x_cs_info_level;    /* GLOBAL */
unsigned int x_cs_verbose_level; /* GLOBAL */
unsigned int x_cs_err_delay_ms;  /* GLOBAL */

void
reset_check_status ()
{
  x_cs_info_level = 0;
  x_cs_verbose_level = 0;
}

void
set_check_status (unsigned int info_level, unsigned int verbose_level)
{
  x_cs_info_level = info_level;
  x_cs_verbose_level = verbose_level;
}

/*
 * Called after most socket or tun/tap operations, via the inline
 * function check_status().
 *
 * Decide if we should print an error message, and see if we can
 * extract any useful info from the error, such as a Path MTU hint
 * from the OS.
 */
void
x_check_status (int status,
		const char *description,
		struct link_socket *sock,
		struct tuntap *tt)
{
  const int my_errno = openvpn_errno ();
  const char *extended_msg = NULL;

  msg (x_cs_verbose_level, "%s %s returned %d",
       sock ? proto2ascii (sock->info.proto, true) : "",
       description,
       status);

  if (status < 0)
    {
      struct gc_arena gc = gc_new ();
#if EXTENDED_SOCKET_ERROR_CAPABILITY
      /* get extended socket error message and possible PMTU hint from OS */
      if (sock)
	{
	  int mtu;
	  extended_msg = format_extended_socket_error (sock->sd, &mtu, &gc);
	  if (mtu > 0 && sock->mtu != mtu)
	    {
	      sock->mtu = mtu;
	      sock->info.mtu_changed = true;
	    }
	}
#elif defined(WIN32)
      /* get possible driver error from TAP-Windows driver */
      extended_msg = tap_win_getinfo (tt, &gc);
#endif
      if (!ignore_sys_error (my_errno))
	{
	  if (extended_msg)
	    msg (x_cs_info_level, "%s %s [%s]: %s (code=%d)",
		 description,
		 sock ? proto2ascii (sock->info.proto, true) : "",
		 extended_msg,
		 strerror_ts (my_errno, &gc),
		 my_errno);
	  else
	    msg (x_cs_info_level, "%s %s: %s (code=%d)",
		 description,
		 sock ? proto2ascii (sock->info.proto, true) : "",
		 strerror_ts (my_errno, &gc),
		 my_errno);

	  if (x_cs_err_delay_ms)
	    platform_sleep_milliseconds (x_cs_err_delay_ms);
	}
      gc_free (&gc);
    }
}

/*
 * In multiclient mode, put a client-specific prefix
 * before each message.
 */
const char *x_msg_prefix; /* GLOBAL */

/*
 * Allow MSG to be redirected through a virtual_output object
 */

const struct virtual_output *x_msg_virtual_output; /* GLOBAL */

/*
 * Exiting.
 */

void
openvpn_exit (const int status)
{
  if (!forked)
    {
      void tun_abort();
#ifdef ENABLE_PLUGIN
      void plugin_abort (void);
#endif

      tun_abort();

#ifdef WIN32
      uninit_win32 ();
#endif

      close_syslog ();

#ifdef ENABLE_PLUGIN
      plugin_abort ();
#endif

#if PORT_SHARE
      if (port_share)
	port_share_abort (port_share);
#endif

#ifdef ENABLE_MEMSTATS
      mstats_close();
#endif

#ifdef ABORT_ON_ERROR
      if (status == OPENVPN_EXIT_STATUS_ERROR)
	abort ();
#endif

      if (status == OPENVPN_EXIT_STATUS_GOOD)
	perf_output_results ();
    }

  exit (status);
}

/*
 * Translate msg flags into a string
 */
const char *
msg_flags_string (const unsigned int flags, struct gc_arena *gc)
{
  struct buffer out = alloc_buf_gc (16, gc);
  if (flags == M_INFO)
    buf_printf (&out, "I");
  if (flags & M_FATAL)
    buf_printf (&out, "F");
  if (flags & M_NONFATAL)
    buf_printf (&out, "N");
  if (flags & M_WARN)
    buf_printf (&out, "W");
  if (flags & M_DEBUG)
    buf_printf (&out, "D");
  return BSTR (&out);
}

#ifdef ENABLE_DEBUG
void
crash (void)
{
  char *null = NULL;
  *null = 0;
}
#endif

#ifdef WIN32

const char *
strerror_win32 (DWORD errnum, struct gc_arena *gc)
{
  /*
   * This code can be omitted, though often the Windows
   * WSA error messages are less informative than the
   * Posix equivalents.
   */
#if 1
  switch (errnum) {
    /*
     * When the TAP-Windows driver returns STATUS_UNSUCCESSFUL, this code
     * gets returned to user space.
     */
  case ERROR_GEN_FAILURE:
    return "General failure (ERROR_GEN_FAILURE)";
  case ERROR_IO_PENDING:
    return "I/O Operation in progress (ERROR_IO_PENDING)";
  case WSA_IO_INCOMPLETE:
    return "I/O Operation in progress (WSA_IO_INCOMPLETE)";
  case WSAEINTR:
    return "Interrupted system call (WSAEINTR)";
  case WSAEBADF:
    return "Bad file number (WSAEBADF)";
  case WSAEACCES:
    return "Permission denied (WSAEACCES)";
  case WSAEFAULT:
    return "Bad address (WSAEFAULT)";
  case WSAEINVAL:
    return "Invalid argument (WSAEINVAL)";
  case WSAEMFILE:
    return "Too many open files (WSAEMFILE)";
  case WSAEWOULDBLOCK:
    return "Operation would block (WSAEWOULDBLOCK)";
  case WSAEINPROGRESS:
    return "Operation now in progress (WSAEINPROGRESS)";
  case WSAEALREADY:
    return "Operation already in progress (WSAEALREADY)";
  case WSAEDESTADDRREQ:
    return "Destination address required (WSAEDESTADDRREQ)";
  case WSAEMSGSIZE:
    return "Message too long (WSAEMSGSIZE)";
  case WSAEPROTOTYPE:
    return "Protocol wrong type for socket (WSAEPROTOTYPE)";
  case WSAENOPROTOOPT:
    return "Bad protocol option (WSAENOPROTOOPT)";
  case WSAEPROTONOSUPPORT:
    return "Protocol not supported (WSAEPROTONOSUPPORT)";
  case WSAESOCKTNOSUPPORT:
    return "Socket type not supported (WSAESOCKTNOSUPPORT)";
  case WSAEOPNOTSUPP:
    return "Operation not supported on socket (WSAEOPNOTSUPP)";
  case WSAEPFNOSUPPORT:
    return "Protocol family not supported (WSAEPFNOSUPPORT)";
  case WSAEAFNOSUPPORT:
    return "Address family not supported by protocol family (WSAEAFNOSUPPORT)";
  case WSAEADDRINUSE:
    return "Address already in use (WSAEADDRINUSE)";
  case WSAENETDOWN:
    return "Network is down (WSAENETDOWN)";
  case WSAENETUNREACH:
    return "Network is unreachable (WSAENETUNREACH)";
  case WSAENETRESET:
    return "Net dropped connection or reset (WSAENETRESET)";
  case WSAECONNABORTED:
    return "Software caused connection abort (WSAECONNABORTED)";
  case WSAECONNRESET:
    return "Connection reset by peer (WSAECONNRESET)";
  case WSAENOBUFS:
    return "No buffer space available (WSAENOBUFS)";
  case WSAEISCONN:
    return "Socket is already connected (WSAEISCONN)";
  case WSAENOTCONN:
    return "Socket is not connected (WSAENOTCONN)";
  case WSAETIMEDOUT:
    return "Connection timed out (WSAETIMEDOUT)";
  case WSAECONNREFUSED:
    return "Connection refused (WSAECONNREFUSED)";
  case WSAELOOP:
    return "Too many levels of symbolic links (WSAELOOP)";
  case WSAENAMETOOLONG:
    return "File name too long (WSAENAMETOOLONG)";
  case WSAEHOSTDOWN:
    return "Host is down (WSAEHOSTDOWN)";
  case WSAEHOSTUNREACH:
    return "No Route to Host (WSAEHOSTUNREACH)";
  case WSAENOTEMPTY:
    return "Directory not empty (WSAENOTEMPTY)";
  case WSAEPROCLIM:
    return "Too many processes (WSAEPROCLIM)";
  case WSAEUSERS:
    return "Too many users (WSAEUSERS)";
  case WSAEDQUOT:
    return "Disc Quota Exceeded (WSAEDQUOT)";
  case WSAESTALE:
    return "Stale NFS file handle (WSAESTALE)";
  case WSASYSNOTREADY:
    return "Network SubSystem is unavailable (WSASYSNOTREADY)";
  case WSAVERNOTSUPPORTED:
    return "WINSOCK DLL Version out of range (WSAVERNOTSUPPORTED)";
  case WSANOTINITIALISED:
    return "Successful WSASTARTUP not yet performed (WSANOTINITIALISED)";
  case WSAEREMOTE:
    return "Too many levels of remote in path (WSAEREMOTE)";
  case WSAHOST_NOT_FOUND:
    return "Host not found (WSAHOST_NOT_FOUND)";
  default:
    break;
  }
#endif

  /* format a windows error message */
  {
    char message[256];
    struct buffer out = alloc_buf_gc (256, gc);
    const int status =  FormatMessage (
				       FORMAT_MESSAGE_IGNORE_INSERTS
				       | FORMAT_MESSAGE_FROM_SYSTEM
				       | FORMAT_MESSAGE_ARGUMENT_ARRAY,
				       NULL,
				       errnum,
				       0,
				       message,
				       sizeof (message),
				       NULL);
    if (!status)
      {
	buf_printf (&out, "[Unknown Win32 Error]");
      }
    else
      {
	char *cp;
	for (cp = message; *cp != '\0'; ++cp)
	  {
	    if (*cp == '\n' || *cp == '\r')
	      *cp = ' ';
	  }
	
	buf_printf(&out, "%s", message);
      }
    
    return BSTR (&out);
  }
}

#endif
