/*****************************************************************************

  POPular -- A POP3 server and proxy for large mail systems

  $Id: pcontrol.c,v 1.40 2003/11/22 21:27:22 sqrt Exp $

  http://www.remote.org/jochen/mail/popular/

******************************************************************************

  Copyright (C) 1999-2003  Jochen Topf <jochen@remote.org>

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA

*****************************************************************************/

#if HAVE_CONFIG_H
# include <config.h>
#endif

#include "popular.h"


/* prompt for interactive use */
#define PROMPT_TEMPLATE "%s %s > "

/* timeout (in seconds) when waiting for answer from server */
#define TIMEOUT 10

/* program name for error messages */
static char *prgname;

/* file descriptor of control socket */
static int pcontrol_fd;

/* verbosity level 0=quiet, 1=normal, 2=verbose */
static int verbosity;

/* ignore errors when executing commands */
static int ignore;

/* UNIX domain socket name of server */
static char server_sockname[MAXBUF];

/* UNIX domain socket name of client */
static char client_sockname[MAXBUF];

/* server type as returned by id command */
static char server_type[MAXBUF];

int debug;


/*****************************************************************************

  help texts

*****************************************************************************/
struct help_text {
  char *id;
  char *name;
  char *text;
} help_text[] = {
  { "pproxy", "help", "pcontrol commands:\n\
  /h             this message\n\
  /h rc          help on return codes\n\
  /h <command>   help on command\n\
  /q             quit pcontrol\n\
\n\
pproxy commands:\n\
  debug      show/set debug modes\n\
  show       show settings\n\
  set        change settings\n\
  pdm        load/unload/configure POPular database modules (PDM)\n\
  capa       manipulate capability lists\n\
  vserv      virtual server configuration\n\
  backend    backend configuration\n\
  shutdown   shutdown server\n\
  prng       seed prng (SSL/TLS only)\n\
"}, { "pserv", "help", "pcontrol commands:\n\
  /h             this message\n\
  /h rc          help on return codes\n\
  /h <command>   help on command\n\
  /q             quit pcontrol\n\
\n\
pserv commands:\n\
  debug      show/set debug modes\n\
  show       show settings\n\
  set        change settings\n\
  capa       manipulate capability list\n\
  server     put server online and offline\n\
  shutdown   shutdown server\n\
"}, { "*", "rc", "return codes:\n\
  00 ok\n\
\n\
  10 unknown object\n\
  11 object not supported (because TLS support was not compiled in etc.)\n\
  12 object can't be changed (read-only)\n\
  13 invalid value for this object\n\
  15 unknown object or max number for this object already reached\n\
  16 missing data\n\
  17 command not allowed in this state\n\
  18 object in use\n\
\n\
  20 wrong argument count\n\
  21 unknown command\n\
  22 unknown subcommand\n\
  23 unknown keyword\n\
  24 not allowed with TLS protocol\n\
  25 not allowed with STARTTLS setting\n\
  26 setting not allowed because TLS not compiled in\n\
  29 parse error\n\
\n\
  31 bind error (maybe the port is already bound?)\n\
  32 out of memory error\n\
  33 file i/o error\n\
  34 operation failed\n\
  35 TLS error, see log file\n\
\n\
  40 module load error\n\
\n\
  99 internal error (If you see this, report it as a bug!)\n\
"}, { "pproxy", "debug", "Usage: debug [MODE [OPTION] ...]\n\
Set/clear/show debug options.\n\
\n\
modes:\n\
  +     set the following debug options\n\
  -     clear the following debug options\n\
  =     set exactly the following debug options and clear all others\n\
\n\
options:\n\
  MAIN     main program, general messages about what the program is doing\n\
  AUTH     authentication details (but without passwords)\n\
  PASS     passwords are logged (this might be a security risk!)\n\
  POP      POP3 dialogs are logged\n\
  CTRL     control requests and answers\n\
  NET      network errors etc.\n\
  TLS      TLS messages (pproxy only, only if compiled in)\n\
\n\
pseudo-options:\n\
  ALL      All options together\n\
  NONE     No options at all\n\
\n\
examples:\n\
  debug                  Show current debug options\n\
  debug + AUTH PASS      Add AUTH and PASS options to already existing options\n\
  debug = NET            Set only NET debug option\n\
  debug = ALL            Set all debug options\n\
  debug = NONE           Delete all debug options\n\
  debug =                Delete all debug options\n\
"}, { "pproxy", "show", "Usage: show [VARIABLE]\n\
Show settings of variable VARIABLE. If not argument is given, list all\n\
variable names.\n\
 \n\
read-only variables:\n\
  id              id (name) of server (always 'pproxy')\n\
  pid             process id of server\n\
  sessionlimit    maximum compiled in value for maxsession\n\
  rundir          directory for pidfile, control sockets etc.\n\
  version         version of server\n\
 \n\
config variables:\n\
  allowsslv2      Allow SSL v2 connections (default: off)\n\
  authtimeout     timeout for authorization state\n\
  backlog         listen(2) backlog value\n\
  capadir         directory with capability lists\n\
  checkport       UDP port where pcheckd listens\n\
  checktimeout    timeout while waiting for answer from pcheckd\n\
  defaultns       default namespace\n\
  fallback        fallback backend if user can't be authenticated locally\n\
  idletimeout     timeout on idle sessions\n\
  logfile         name of logfile\n\
  maxlocalload    if the system load is equal or higher than this, no new\n\
                  connections are accepted (0=check disabled)\n\
  maxsession      maximum number of proxy sessions\n\
  pdmdir          directory with databases modules (pdm_*.so)\n\
  proxytimeout    timeout while proxying data\n\
  sessiontimeout  timeout for whole session\n\
  tlsdir          directory with TLS key files\n\
"}, { "pserv", "show", "Usage: show [VARIABLE]\n\
Show settings of variable VARIABLE. If not argument is given, list all\n\
variable names.\n\
 \n\
read-only variables:\n\
  id              id (name) of server (always 'pserv')\n\
  pid             process id of server\n\
  rundir          directory for pidfile, control sockets etc.\n\
  sessionlimit    maximum compiled in value for maxsession\n\
  version         version of server\n\
 \n\
config variables:\n\
  backlog         listen(2) backlog value\n\
  capadir         directory with capability lists\n\
  idletimeout     timeout on idle sessions\n\
  localip         IP used to bind to listening port (or 'ANY' for INADDR_ANY)\n\
  logeachmsg      after each connection log status of every mail\n\
  logfile         name of logfile\n\
  maxlocalload    if the system load is equal or higher than this, no new\n\
                  connections are accepted (0=check disabled)\n\
  maxsession      maximum number of server sessions\n\
  popdir          directory with POP3 mailboxes\n\
  servport        TCP port where the server should listen\n\
  sessiontimeout  timeout for whole session\n\
  statusheader    whether to print a 'Status: RO' header line in read mail\n\
"}, { "pproxy", "set", "Usage: set VARIABLE CONTENT\n\
\n\
Set content of variable VARIABLE to CONTENT. See help for 'show' command for\n\
a list of variables.\n\
"}, { "pproxy", "prng", "Usage: prng FILENAME [BYTES]\n\
\n\
Seed the pseudo random number generator (PRNG) from a file. The filename\n\
is normally /dev/random or /dev/urandom. BYTES defaults to 1024.\n\
"}, { "pproxy", "pdm", "Usage: PDM SUBCOMMAND [ARG] ...\n\
\n\
subcommands:\n\
  pdm list                list all loaded modules\n\
  pdm flush               unload all modules\n\
  pdm add ID MOD ARG ...  add module to end of list\n\
  pdm del ID              unload module from list\n\
  pdm reload ID           reload module\n\
  pdm show ID             show module parameters\n\
\n\
"}, { "pproxy", "capa", "Usage: capa SUBCOMMAND [NAME]\n\
\n\
subcommands:\n\
  capa list			list all capability list names\n\
  capa load NAME		load the capability list named NAME\n\
  capa del NAME			delete the named capability list\n\
  capa show NAME		show capabilities if the named list\n\
\n\
If a named capability list is loaded and there already exists a capability\n\
list with that name, the new one will replace the old list. If something\n\
goes wrong while reading in a capability list, any existing capability list\n\
will be unchanged.\n\
"}, { "pserv", "capa", "Usage: capa SUBCOMMAND [NAME]\n\
\n\
subcommands:\n\
  capa show		        show current capability list setting\n\
  capa set NAME                 set capability list\n\
\n\
NAME can be one of: 'ERROR', 'NONE', 'DEFAULT' or any name of a file in\n\
capadir.\n\
"}, { "pproxy", "backend", "Usage: backend SUBCOMMAND [ARG] ...\n\
\n\
subcommands:\n\
  backend list                          list all configured backends\n\
  backend show BACKEND                  show config of BACKEND\n\
  backend del BACKEND                   delete BACKEND\n\
  backend conf BACKEND (ARG VALUE)...   configure backend\n\
  backend flush                         flush (delete) all backends\n\
\n\
arguments for 'conf' subcommand are:\n\
\n\
  host		host name or IP number of backend\n\
  port		TCP port where server listens on backend\n\
  prot		protocol used to talk to this backend (POP3, XPOP, CXPOP)\n\
  state		state of this backend (OFFLINE, FAKE, ONLINE)\n\
"}, { "pproxy", "vserv", "Usage: vserv SUBCOMMAND [ARG] ...\n\
\n\
subcommands:\n\
  vserv list                        list all configured virtual servers\n\
  vserv show VSERV                  show config of VSERV\n\
  vserv del VSERV                   delete VSERV\n\
  vserv conf VSERV (ARG VALUE)...   configure VSERV\n\
  vserv flush                       flush (delete) all virtual servers\n\
\n\
arguments for 'conf' subcommand are:\n\
\n\
  iface		host name or IP number of local interface for virtual server\n\
  port		TCP port where virtual server listens on\n\
  prot		protocol used to talk to client (POP3, POP3s)\n\
  starttls      set usage of STARTTLS (OFF, OPTIONAL, FORCE)\n\
  capa          capability list to use (ERROR, NONE, DEFAULT, <name>)\n\
  state		state of this virtual server (OFFLINE, DISABLED, FAKE, ONLINE)\n\
  namespace     namespace for connections on this virtual server\n\
  bannerok      greeting banner if virtual server is online\n\
  bannererr     greeting banner if virtual server if diabled\n\
"}, { "pproxy", "shutdown", "Usage: shutdown server|children|all|delayed\n\
shutdown server        Shut down parent. Children are left running\n\
shutdown children      Kill all children. Parent keeps running.\n\
shutdown all           Kill all children and exit parent.\n\
shutdown delayed       Stop accepting new connections and exit parent after\n\
                       all sessions have ended.\n\
"}, { "pserv", "shutdown", "Usage: shutdown server|children|all|delayed\n\
shutdown server        Shut down parent. Children are left running\n\
shutdown children      Kill all children. Parent keeps running.\n\
shutdown all           Kill all children and exit parent.\n\
shutdown delayed       Stop accepting new connections and exit parent after\n\
                       all sessions have ended.\n\
"}, { "pserv", "server", "Usage: server status|online|offline\n\
Set server status\n\
\n\
server status     show server status\n\
server online     take server online\n\
server offline    take server offline\n\
"}, { NULL, NULL, NULL }
};


/*****************************************************************************

  print_help()

  Prints help messages.

*****************************************************************************/
void
print_help(const char *id, const char *cmd)
{
  int i;
  for (i=0; help_text[i].name; i++) {
    if (strcasecmp(help_text[i].name, cmd)) continue;
    if (strcasecmp(help_text[i].id, id) && help_text[i].id[0] != '*') continue;
    puts(help_text[i].text);
    return;
  }

  printf("No help for '%s'\n", cmd);
}


/*****************************************************************************

  signal_handler()

*****************************************************************************/
void
signal_handler(int signum)
{
  (void) unlink(client_sockname);
  exit(0);
}


/*****************************************************************************

  cleanup()

  Is called after exit() to clean up UNIX domain socket file.

*****************************************************************************/
void
cleanup(void)
{
  (void) unlink(client_sockname);
}


/*****************************************************************************

  send_command()

  Send one command to the server and wait for a reply. Returns a code
  corresponding to the first char of the answer.

*****************************************************************************/
int
send_command(const char *cmd)
{
  int len;
  struct sockaddr_un server_addr;
  char answer[MAXBUF];

  if (verbosity >= 2) printf("Sending command: %s\n", cmd);

  /***************************************************************************
    Send command to server.
  ***************************************************************************/
  memset(&server_addr, 0, sizeof(server_addr));
  server_addr.sun_family = AF_UNIX;
  (void) strlcpy(server_addr.sun_path, server_sockname, sizeof(server_addr.sun_path));
  len = sendto(pcontrol_fd, cmd, strlen(cmd), 0, (struct sockaddr *) &server_addr, sizeof(server_addr));
  if (len != strlen(cmd)) {
    fprintf(stderr, "%s: sendto error: %s\n", prgname, strerror(errno));
    exit(RCODE_CERR);
  }


  /***************************************************************************
    Wait until server is ready.
  ***************************************************************************/
  {
    fd_set readset;
    int r;

    FD_ZERO(&readset);
    FD_SET(pcontrol_fd, &readset);

    r = rel_select(&readset, TIMEOUT, 0);
    if (r == 0) {
      fprintf(stderr, "%s: timeout while waiting for answer from server\n", prgname);
      exit(RCODE_CERR);
    } else if (r < 0) {
      fprintf(stderr, "%s: error while waiting for answer from server: %s\n", prgname, strerror(errno));
      exit(RCODE_CERR);
    }
  }


  /***************************************************************************
    Get answer from server.
  ***************************************************************************/
  len = recv(pcontrol_fd, answer, sizeof(answer)-1, 0);
  if (len < 0) {
    fprintf(stderr, "%s: recv error: %s\n", prgname, strerror(errno));
    exit(RCODE_CERR);
  }
  answer[len] = '\0';

  if (verbosity >= 1) printf("%s\n", answer);


  /***************************************************************************
    If this was an 'show id' command, save it for later reference.
  ***************************************************************************/
  if (! strcasecmp("show id", cmd)) {
    if (strncmp(answer, "00 set id \"", 11)) {
      fprintf(stderr, "%s: can't get id from server: got '%s'\n", prgname, answer);
      exit(RCODE_CERR);
    }
    (void) strlcpy(server_type, answer+11, sizeof(server_type));
    if (server_type[0] != '\0') server_type[strlen(server_type)-1] = '\0';
  }

  if (answer[0] == '0') return RCODE_OK;
  return RCODE_ERR;
}


/*****************************************************************************

  file_command_loop()

  Reads all lines from the given file (or stdin if filename == '-').

*****************************************************************************/
int
file_command_loop(const char *filename)
{
  char buf[MAXBUF];
  FILE *file;
  int rc=0;
  int offset=0;

  if (filename[0] == '-' && filename[1] == '\0') {
    file = stdin;
  } else {
    file = fopen(filename, "r");
    if (! file) {
      fprintf(stderr, "%s: can't open file %s: %s\n", prgname, filename, strerror(errno));
      exit(RCODE_CMDLINE);
    }
  }

  while (fgets(buf+offset, sizeof(buf)-offset, file)) {
    int len = strlen(buf);
    if (len > 1 && buf[len-2] == '\\') {
      offset = len-2;
      continue;
    }
    offset = 0;
    if (buf[0] == '\0' || buf[0] == '#' || buf[1] == '\0') continue;
    buf[len-1] = '\0';
    rc = send_command(buf);
    if (rc && !ignore) break;
  }

  return rc;
}


/*****************************************************************************

  tty_command_loop()

  Reads lines interactively from stdin. Uses readline library if compiled
  in. Commands starting with a / are interpreted locally, other commands
  are sent to the server.

*****************************************************************************/
#ifndef USE_READLINE
char *
readline(const char *prompt) {
  char *ret, *line = malloc(MAXBUF);
  printf(prompt);
  fflush(stdout);
  
  ret = fgets(line, MAXBUF, stdin);
  if (! ret) return NULL;

  if ((ret = strchr(line, '\n'))) *ret = '\0';
  return line;
}
#endif


void
tty_command_loop()
{
  char hostname[MAXBUF], *p;
  char prompt[MAXBUF];
  char *line;

#ifdef USE_READLINE
  rl_bind_key ('\t', rl_insert);
#endif

  (void)gethostname(hostname, sizeof(hostname)-1);
  p = strchr(hostname, '.');
  if (p) *p = '\0';

  snprintf(prompt, sizeof(prompt), PROMPT_TEMPLATE, hostname, server_type);

  printf("Connection to %s ready. Type '/h' for help, '/q' for quit.\n", server_type);

  while ((line = readline(prompt))) {
#ifdef USE_READLINE
    if (line[0] != '\0') add_history(line);
#endif

    if (line[0] == '/') {
      if ((tolower(line[1]) == 'q') && (line[2] == '\0')) {
        return;
      } else if ((tolower(line[1]) == 'h') && (line[2] == '\0')) {
        print_help(server_type, "help");
      } else if ((tolower(line[1]) == 'h') && (line[2] == ' ')) {
        print_help(server_type, line+3);
      } else {
        printf("Unknown command\n");
      }
    } else if ((line[0] != '\0') && (line[0] != '#')) {
      (void) send_command(line);
    }

    free(line);
  }
}


/*****************************************************************************

  print_usage()

*****************************************************************************/
void
print_usage(void)
{
  printf("Usage: %s [OPTION] ... [FILE] ...\n", prgname);
  printf("Send commands to running pproxy or pserv.\n");
  printf("Options:\n");
  printf("  -c, --command=CMD     send CMD to server\n");
  printf("  -s, --socket=FILE     open FILE as socket to communicate with server\n");
  printf("  -p, --program=PRG     open socket to program\n");
  printf("  -q, --quiet           don't print answer to command\n");
  printf("  -v, --verbose         print commands as they are sent\n");
  printf("  -i, --ignore-errors   ignore errors in commands\n");
  printf("      --help            print this help message\n");
  printf("      --version         print version information\n");
  printf("\n");
  printf("If no FILE or if FILE is `-', standard input is read.\n");
  printf("If neither the -s nor the -p option is given pcontrol will look for a\n");
  printf("socket from pproxy or pserv (in that order) in /var/run/popular.\n");
}


/*****************************************************************************

  main()

*****************************************************************************/
int
main(int argc, char *argv[])
{
  int c, rc=0, quiet=0, verbose=0;
  char *command=NULL;
  static struct option long_options[] = {
    { "help",          no_argument,       0, 0 },
    { "version",       no_argument,       0, 0 },
    { "command",       required_argument, 0, 'c' },
    { "socket",        required_argument, 0, 's' },
    { "program",       required_argument, 0, 'p' },
    { "quiet",         no_argument,       0, 'q' },
    { "verbose",       no_argument,       0, 'v' },
    { "ignore-errors", no_argument,       0, 'i' },
    { NULL,            0,                 0, 0 }
  };

  prgname = argv[0];		/* program name for error messages */


  /***************************************************************************
    This program should not be run as root and not be setuid or setgid.
  ***************************************************************************/
  if (!getuid() || !geteuid()) {
    fprintf(stderr, "%s: don't start this program as `root'.\n", prgname);
    exit(RCODE_CMDLINE);
  }

  if (getuid() != geteuid() || getgid() != getegid()) {
    fprintf(stderr, "%s: don't make this program setuid or setgid.\n", prgname);
    exit(RCODE_CMDLINE);
  }


  /***************************************************************************
    Read command line arguments.
  ***************************************************************************/
  while (1) {
    int option_index = 0;
    c = getopt_long(argc, argv, "s:c:p:vqi", long_options, &option_index);
 
    if (c == -1) break;

    switch (c) {
      case 0:
        if (! strcmp(long_options[option_index].name, "version")) {
          printf("pcontrol - POPular POP server suite %s\n", VERSION);
          exit(RCODE_OK);
        } else if (! strcmp(long_options[option_index].name, "help")) {
          print_usage();
          exit(RCODE_OK);
        } else {
          fprintf(stderr, "%s: unknown option: %s\n", prgname, long_options[option_index].name);
          exit(RCODE_INTERNAL);
        }
	break;
      case 'c':
        command = optarg;
        break;
      case 'p':
	if (server_sockname[0]) {
	  fprintf(stderr, "%s: you can't use --socket and --program at the same time (or twice)\n", prgname);
	  exit(RCODE_CMDLINE);
	}
        if (!strcmp(optarg, "pserv") || !strcmp(optarg, "serv")) {
          strlcpy(server_sockname, RUN_DIR "/" PSERV_SOCK_FILE, sizeof(server_sockname));
	} else if (!strcmp(optarg, "pproxy") || !strcmp(optarg, "proxy")) {
          strlcpy(server_sockname, RUN_DIR "/" PPROXY_SOCK_FILE, sizeof(server_sockname));
	} else {
	  fprintf(stderr, "%s: unknown program: %s\n", prgname, optarg);
	  exit(RCODE_CMDLINE);
	}
        break;
      case 's':
	if (server_sockname[0]) {
	  fprintf(stderr, "%s: you can't use --socket and --program at the same time (or twice)\n", prgname);
	  exit(RCODE_CMDLINE);
	}
        strlcpy(server_sockname, optarg, sizeof(server_sockname));
        break;
      case 'q':
        quiet = 1;
        break;
      case 'v':
        verbose = 1;
	debug = DG_ALL;
        break;
      case 'i':
        ignore = 1;
        break;
      default:
        fprintf(stderr, "Try `%s --help' for more information.\n", prgname);
        exit(RCODE_CMDLINE);
    }
  }

  if (quiet == 1 && verbose == 1) {
    fprintf(stderr, "%s: using options --quiet and --verbose together makes no sense.\n", prgname);
    fprintf(stderr, "Try `%s --help' for more information.\n", prgname);
    exit(RCODE_CMDLINE);
  }


  /***************************************************************************
    If no socket and no program name was given on the command line, try to
    figure it out automatically.
  ***************************************************************************/
  if (! server_sockname[0]) {
    struct stat sbuf;
    if (stat(RUN_DIR "/" PPROXY_SOCK_FILE, &sbuf) == 0) {
      strlcpy(server_sockname, RUN_DIR "/" PPROXY_SOCK_FILE, sizeof(server_sockname));
    } else if (stat(RUN_DIR "/" PSERV_SOCK_FILE, &sbuf) == 0) {
      strlcpy(server_sockname, RUN_DIR "/" PSERV_SOCK_FILE, sizeof(server_sockname));
    } else {
      fprintf(stderr, "%s: no socket file found (is server running? use --socket option?)\n", prgname);
      exit(RCODE_CMDLINE);
    }
  }


  /***************************************************************************
    Prepare socket.
  ***************************************************************************/
  snprintf(client_sockname, sizeof(client_sockname), RUN_DIR "/" PCONTROL_SOCK_FILE, getpid());
  pcontrol_fd = uds_socket(client_sockname, PCONTROL_SOCK_UMASK);
  if (pcontrol_fd < 0) {
    fprintf(stderr, "%s: socket open failed: %s\n", prgname, strerror(errno));
    exit(RCODE_CERR);
  }
  atexit(cleanup);


  /***************************************************************************
    Setup signal handling.
  ***************************************************************************/
  {
    struct sigaction sigAction;
    memset(&sigAction, 0, sizeof(sigAction));
    sigemptyset(&sigAction.sa_mask);
    sigAction.sa_handler = signal_handler;
    sigAction.sa_flags = SA_RESTART;
    if (sigaction(SIGINT, &sigAction, (struct sigaction *)0) != 0) {
      fprintf(stderr, "%s: sigaction failed", prgname);
      exit(RCODE_ERR);
    }
  }


  /***************************************************************************
    Get id from server.
  ***************************************************************************/
  verbosity = 0;
  send_command("show id");
  verbosity = quiet ? 0 : (verbose ? 2 : 1);


  /***************************************************************************
    Send commands.
  ***************************************************************************/
  if (command) {			/* --command */
    rc = send_command(command);
  } else if (optind < argc) {		/* files on command line */
    int i;
    for (i=optind; i < argc; i++) {
      rc = file_command_loop(argv[i]);
      if (rc && !ignore) break;
    }
  } else if (isatty(0)) {		/* interactive use */
    tty_command_loop();
  } else {				/* no file, read stdin */
    rc = file_command_loop("-");
  }

  exit(rc);
}


/** THE END *****************************************************************/


syntax highlighted by Code2HTML, v. 0.9.1