/*****************************************************************************
 * $Id: proxydemo.c
 *
 * Example application source code using the libcurl multi socket interface 
 * with a glib event loop to create a basic http proxy server.
 *
 * Written by Jeff Pohlmeyer
 *
 * Tested with libcurl-7.16.0 and glib-2.12.4 on SuSE-10.1
 *
 * This demo implements a very crude local-proxy server, it supports only
 * http GET and simple form POST  (NOT multipart/formdata or chunked!).
 * It also does NOT support https, ftp, content-encoded data, etc...
 *
 * NOTE: The request-header parser is fairly primitive, it is NOT intended
 * to be used as a public proxy for untrusted clients!
 *
 * Compile with:
 * gcc -g -Wall `pkg-config glib-2.0 libcurl --libs --cflags` \
 *    proxydemo.c -o proxydemo
 *
 * To test, start the application with the desired [port] or [address:port],
 * and point your browser's http-proxy setting to it.
 * For instance:
 *     proxydemo 127.0.0.1:8080
 */


#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <glib.h>
#include <curl/curl.h>


#ifdef __WIN32__
# define IO_CHANNEL_NEW g_io_channel_win32_new_socket
#else
# include <netinet/in.h>
# include <arpa/inet.h>
# define IO_CHANNEL_NEW g_io_channel_unix_new
#endif


#define MAX_PENDING_CONNECTIONS 256

/* Global information, common to all connections */
typedef struct _GlobalInfo {
  CURLM *multi;
  guint timer_event;
  int prev_running;
  int still_running;
} GlobalInfo;


/* Information associated with a specific easy handle */
typedef struct _ConnInfo {
  CURL *easy;
  char *url;
  GlobalInfo *global;
  char error[CURL_ERROR_SIZE];
  int client_fd;
  GIOChannel *client_ch;
  struct curl_slist* request_hdr;
  gsize post_len;
  gsize post_left;
} ConnInfo;


/* Information associated with a specific socket */
typedef struct _SockInfo {
  GIOChannel *ch;
  guint watch_id;
  gboolean watching;
  GlobalInfo *global;
} SockInfo;



/* Sends data to the client */
guint write_client(int sock, char *buf,  guint size)
{
  int rv;
  guint rem=size;
  guint Result=0;
  char *p=buf;

  do {
    rv=send(sock, p, rem, 0);
    if ( rv >= 0 ) {
      Result+=rv;
      if (Result>=size) { return Result; }
      p += rv;
      rem -= rv;
    } else {
      g_printerr("write_client(): error on socket #%d:%s\n",
      sock, strerror(errno));
      return Result;
    }
  } while(1);
}


/* Sends a null-terminated string to client */
static gboolean write_client_str(int sock, char *str)
{
  size_t len;
  if (!str) { return FALSE;}
  len = strlen(str);
  return (write_client(sock,str,len) == len)?TRUE:FALSE;
}


/* Sends a brief error page to the client */
static gboolean send_error_page(int sock, char *url, char *err){
  return (
    write_client_str(sock,
     "<html><head><title>Proxy Error</title></head>\
     <body><hr><h3>Proxy Error</h3><hr> \
     While attempting to retrieve: &nbsp;<a href=\"" )
    && write_client_str(sock, url)
    && write_client_str(sock, "\">")
    && write_client_str(sock, url)
    && write_client_str(sock,
      "</a><br>The following error was encountered:<br><b>&nbsp; ")
    && write_client_str(sock, err)
    && write_client_str(sock,
      "</b><hr><i>libcurl proxy demo program</i></body></html>\n")
  );
}



/* g_slist_foreach callback to free element data */
static void free_header_cb(gpointer data, gpointer user_data)
{
  if (data) g_free(data);
}


/* Shutdown the channel, report any error, and close the socket */
static void shutdown_channel(GIOChannel*ch)
{
  GError *err=NULL;
  int fd;
  if (!ch) return;
  fd=g_io_channel_unix_get_fd(ch);
  g_io_channel_shutdown(ch, TRUE, &err);
  if ( (err) && (err->message) ) { 
    g_critical("Error on channel shutdown:%s\n", err->message);
  }
  g_io_channel_unref(ch);
  if (fd >= 0) close(fd);
}


/* Free all data in a ConnInfo struct */
static void conn_info_free(ConnInfo *conn)
{
  if (!conn) return;
  shutdown_channel(conn->client_ch);
  if (conn->url) { g_free(conn->url);}
  g_slist_foreach((GSList*)(conn->request_hdr), free_header_cb, NULL);
  g_slist_free((GSList*)(conn->request_hdr));
  if (conn->easy) { curl_easy_cleanup(conn->easy); }
  g_free(conn);
}


/* Check for completed transfers, and remove their easy handles */
static void check_run_count(GlobalInfo *gi)
{
  if ((gi->prev_running > gi->still_running)||(0==gi->still_running)){
    char *eff_url=NULL;
    CURLMsg *msg;
    int msgs_left;
    ConnInfo *conn=NULL;
    CURL*easy;
    CURLcode res;

    do {
      easy=NULL;
      while ((msg = curl_multi_info_read(gi->multi, &msgs_left))) {
        if (msg->msg == CURLMSG_DONE) {
          easy=msg->easy_handle;
          res=msg->data.result;
          break;
        }
      }
      if (easy) {
        curl_easy_getinfo(easy, CURLINFO_PRIVATE, &conn);
        curl_easy_getinfo(easy, CURLINFO_EFFECTIVE_URL, &eff_url);
        g_print("DONE: %s => (%d) %s\n", eff_url, res, conn->error);
        curl_multi_remove_handle(gi->multi, easy);
        switch (res) {
          case CURLE_OK: break;
          case CURLE_WRITE_ERROR: break; /* Pipe broke, can't talk to client */
          case CURLE_ABORTED_BY_CALLBACK: break;/* Probably also broken pipe */
          case CURLE_COULDNT_RESOLVE_PROXY:
          case CURLE_COULDNT_RESOLVE_HOST:
          case CURLE_COULDNT_CONNECT:
            /* Upstream connection failed */
            write_client_str(conn->client_fd, 
               "HTTP/1.1 502 Bad Gateway\r\n\r\n");
        default:
          send_error_page(conn->client_fd, conn->url, conn->error);
        }
        conn_info_free(conn);
      }
    } while (easy);
    g_print( "Remaining:%d\n", gi->still_running );
  }
  gi->prev_running = gi->still_running;
}


/* Die if we get a bad CURLMcode somewhere */
void mcode_or_die(CURLMcode code, char *where) {
  if ( CURLM_OK != code ) {
    char *s;
    switch (code) {
      case     CURLM_CALL_MULTI_PERFORM: s="CURLM_CALL_MULTI_PERFORM"; break;
      case     CURLM_OK:                 s="CURLM_OK";                 break;
      case     CURLM_BAD_HANDLE:         s="CURLM_BAD_HANDLE";         break;
      case     CURLM_BAD_EASY_HANDLE:    s="CURLM_BAD_EASY_HANDLE";    break;
      case     CURLM_OUT_OF_MEMORY:      s="CURLM_OUT_OF_MEMORY";      break;
      case     CURLM_INTERNAL_ERROR:     s="CURLM_INTERNAL_ERROR";     break;
      case     CURLM_BAD_SOCKET:         s="CURLM_BAD_SOCKET";         break;
      case     CURLM_UNKNOWN_OPTION:     s="CURLM_UNKNOWN_OPTION";     break;
      case     CURLM_LAST:               s="CURLM_LAST";               break;
      default: s="CURLM_unknown";
    }
    g_printerr("ERROR: %s returns %s\n", where, s);
    exit(code);
  }
}



/* Called by glib when our timeout expires */
static gboolean timer_cb(gpointer data)
{
  GlobalInfo *gi = (GlobalInfo *)data;
  CURLMcode rc;

  do {
    rc = curl_multi_socket(gi->multi, CURL_SOCKET_TIMEOUT, &gi->still_running);
  } while (rc == CURLM_CALL_MULTI_PERFORM);
  mcode_or_die(rc, "timer_cb");
  check_run_count(gi);
  return FALSE;
}



/* CURLMOPT_TIMERFUNCTION */
static int update_timeout_cb(CURLM *multi, long timeout_ms, void *userp)
{
  GlobalInfo *gi=(GlobalInfo *)userp;
  gi->timer_event = g_timeout_add(timeout_ms, timer_cb, gi);
  return 0;
}



/* Called by glib when we get action on a multi socket */
static gboolean event_cb(GIOChannel *ch, GIOCondition cond, gpointer data)
{
  GlobalInfo *gi = (GlobalInfo*) data;
  CURLMcode rc;
  int fd=g_io_channel_unix_get_fd(ch);
  (void) cond;
  do {
    rc = curl_multi_socket(gi->multi, fd, &gi->still_running);
  } while (rc == CURLM_CALL_MULTI_PERFORM);
  mcode_or_die(rc, "event_cb");
  check_run_count(gi);
  if(gi->still_running) {
    return TRUE;
  } else {
    g_print("Last transfer done, kill timeout\n");
    if (gi->timer_event) { g_source_remove(gi->timer_event); }
    return FALSE;
  }
}





/* Assign information to a SockInfo structure */
static void setsock(SockInfo*si,curl_socket_t s,CURL*e,int act,GlobalInfo*gi)
{
  GIOCondition kind =
     (act&CURL_POLL_IN?G_IO_IN:0)|(act&CURL_POLL_OUT?G_IO_OUT:0);

  if (si->watching) { g_source_remove(si->watch_id); }
  si->watch_id=g_io_add_watch(si->ch, kind, event_cb, gi);
  si->watching=TRUE;
}



/* Initialize a new SockInfo structure */
static void addsock(curl_socket_t s, CURL *easy, int action, GlobalInfo *gi)
{
  SockInfo *si = g_malloc0(sizeof(SockInfo));

  si->global = gi;
  si->ch=IO_CHANNEL_NEW(s);
  setsock(si, s, easy, action, gi);
  curl_multi_assign(gi->multi, s, si);
}



/* CURLMOPT_SOCKETFUNCTION */
static int sock_cb(CURL*e, curl_socket_t s, int what, void*cbp, void*sockp)
{
  GlobalInfo *gi = (GlobalInfo*) cbp;
  SockInfo *si = (SockInfo*) sockp;

  if (CURL_POLL_REMOVE == what) { 
    if (si) {
      if (si->watching) { g_source_remove(si->watch_id); }
      g_free(si);
    }
  } else {
    if (!si) { addsock(s, e, what, gi); }
    else { setsock(si, s, e, what, gi); }
  }
  return 0;
}



/* CURLOPT_WRITEFUNCTION */
static size_t write_cb(void *ptr, size_t size, size_t nmemb, void *user_data)
{
  size_t Result = size * nmemb;
  ConnInfo *conn = (ConnInfo*) user_data;
  return write_client(conn->client_fd, ptr, Result);
}



#define MatchHeader(s1,s2,n)((strncasecmp(s1,s2,n)==0)&&(s1[n]==':'))



/* CURLOPT_HEADERFUNCTION */
size_t header_cb(char *hdr, size_t size,  size_t nmemb,  void *data)
{
  size_t Result = size * nmemb;
  ConnInfo *conn = (ConnInfo*) data;
  /* Curl handles the encodings, so don't report them to the client... */
  if ( MatchHeader(hdr, "Content-Encoding",  16) ||
       MatchHeader(hdr, "Transfer-Encoding", 17) ) { return Result; }
  return write_client(conn->client_fd, hdr, Result);
}



/* CURLOPT_READFUNCTION (for POST data only) */
static  size_t read_cb(void*outbuf, size_t  size, size_t nmemb,  void *userp){
  ConnInfo *conn = (ConnInfo*)userp;
  GError *err=NULL;
  size_t buf_avail=size*nmemb;
  size_t ask_for=(conn->post_left>buf_avail)?buf_avail:conn->post_left;
  size_t got_from_client=0;
  if (ask_for) {
    GIOStatus rv;
    rv=g_io_channel_read_chars( conn->client_ch,
                outbuf, ask_for, &got_from_client, &err);
    if (G_IO_STATUS_NORMAL != rv) {
      if (err && err->message) {
        g_printerr("*** Warning: Reading POST data: %s\n", err->message);
        return CURL_READFUNC_ABORT;
      }
    }
    conn->post_left -= got_from_client;
  }
  return got_from_client;
}



/* Create a new easy handle, and add it to the global curl_multi */
void new_conn( char*url, GSList*hdr, gsize postlen,
               char meth, GIOChannel*ch,GlobalInfo*gi)
{
  ConnInfo *conn = g_malloc0(sizeof(ConnInfo));
  CURLMcode rc;

  conn->post_len=postlen;
  conn->post_left=postlen;
  conn->client_ch=ch;
  conn->client_fd=g_io_channel_unix_get_fd(ch);
  conn->error[0]='\0';
  conn->global = gi;
  conn->url = url;
  conn->request_hdr = (struct curl_slist *)hdr;
  conn->easy = curl_easy_init();
  g_assert(conn->easy);
  curl_easy_setopt(conn->easy, CURLOPT_VERBOSE, 0);
  curl_easy_setopt(conn->easy, CURLOPT_URL, conn->url);
  curl_easy_setopt(conn->easy, CURLOPT_FOLLOWLOCATION, 0);
  curl_easy_setopt(conn->easy, CURLOPT_HTTPHEADER, conn->request_hdr);
  curl_easy_setopt(conn->easy, CURLOPT_ERRORBUFFER, conn->error);
  curl_easy_setopt(conn->easy, CURLOPT_PRIVATE, conn);
  curl_easy_setopt(conn->easy, CURLOPT_HEADERFUNCTION, header_cb);
  curl_easy_setopt(conn->easy, CURLOPT_HEADERDATA, conn);
  if ('H'==meth) {
      curl_easy_setopt(conn->easy, CURLOPT_NOBODY, TRUE);
  } else {
    curl_easy_setopt(conn->easy, CURLOPT_WRITEFUNCTION, write_cb);
    curl_easy_setopt(conn->easy, CURLOPT_WRITEDATA, conn);
    if ('P'==meth) {
      curl_easy_setopt(conn->easy, CURLOPT_POST, 1);
      curl_easy_setopt(conn->easy, CURLOPT_POSTFIELDSIZE, conn->post_len);
      curl_easy_setopt(conn->easy, CURLOPT_READFUNCTION, read_cb);
      curl_easy_setopt(conn->easy, CURLOPT_READDATA, conn);
      g_print("POST: len=%d\n",postlen);
    }
  }
  curl_easy_setopt(conn->easy, CURLOPT_CONNECTTIMEOUT, 120);
  curl_easy_setopt(conn->easy, CURLOPT_LOW_SPEED_LIMIT, 1);
  curl_easy_setopt(conn->easy, CURLOPT_LOW_SPEED_TIME, 120);
  g_print("START: %s\n", url);
  rc =curl_multi_add_handle(gi->multi, conn->easy);
  mcode_or_die(rc, "new_conn::add_handle");
  do {
    rc = curl_multi_socket_all(gi->multi, &gi->still_running);
  } while (CURLM_CALL_MULTI_PERFORM == rc);
  mcode_or_die(rc, "new_conn::socket_all");  
  check_run_count(gi);
}



/* g_slist_find_custom() callback to find matching header name */
gint find_header_cb(char *list_data, char *user_data){
  if (list_data && user_data){
    return MatchHeader(list_data, user_data, strlen(user_data))?0:1;
  } else {
    return -1;
  }
}

/* Search header list for the given name and return a pointer
   to the value on the right of the colon and white space.
   Returns NULL if empty or not found */
static char*get_header_value(GSList *HeaderList, char*name)
{
  char *value=NULL;
  GSList*hdr=g_slist_find_custom(
             HeaderList, name, (GCompareFunc)find_header_cb );
  if (hdr && hdr->data) {
    value=strchr(hdr->data, ':');
    if (value) { 
      value++;
      while (' '==*value) { value++; }
      if (!*value) { value=NULL; }
    }
  }
  return value;
}


/* Clean up after bad client request headers.*/
static gboolean request_failed(GIOChannel *ch, GSList *hdr)
{
  shutdown_channel(ch);
  if (hdr) { 
    g_slist_foreach(hdr, free_header_cb, NULL); 
    g_slist_free(hdr);
  }
  return TRUE;
}


/* This gets called by glib when a client tries to connect.
   Note that this creates a copy of the headers, and it will keep on
   allocating until it finds the expected double CRLF or runs completely
   out of memory, so a DoS would probably be trivial. You have been warned! */
static gboolean listen_cb(GIOChannel*ch, GIOCondition condition, gpointer data)
{
  struct sockaddr_in ClientAddr;
  size_t size = sizeof(ClientAddr);
  int ClientSocket;
  GIOChannel *ClientChannel;
  GIOStatus rv;
  GError *err=NULL;
  GSList *HeaderList=NULL, *ThisHeader;
  gsize ReadLen, tp, PostLength=0;
  char *ReadBuf, *MethodLine, *Url, *HeaderValue, *EndPtr;
  char MethodID;

  ClientSocket = accept( g_io_channel_unix_get_fd(ch),
                         (struct sockaddr*)&ClientAddr, &size);
  ClientChannel = IO_CHANNEL_NEW(ClientSocket);
  do {
    rv = g_io_channel_read_line(ClientChannel,&ReadBuf,&ReadLen,&tp,&err);
    if (ReadBuf) {
      if (&ReadBuf[tp]>ReadBuf) {
        ReadBuf[tp]='\0';
        HeaderList=g_slist_append(HeaderList, ReadBuf);
      } else { 
     break;
      }
    }
    if (err) {
      g_printerr("listen_cb: %s\n", err->message);
      g_free(err);
      break;
    }
  } while ( (ReadLen&&(G_IO_STATUS_NORMAL==rv))||(G_IO_STATUS_AGAIN==rv) );
  if ( (!HeaderList) || (!HeaderList->data) ) {
    write_client_str(ClientSocket, "HTTP/1.1 400 Bad Request\r\n\r\n");
    send_error_page(ClientSocket, "[Unknown]", "400: Empty request.");
    return request_failed(ClientChannel,HeaderList);
  }
  MethodLine=(char*)HeaderList->data;       /* Save first line of request and */
  HeaderList = g_slist_remove(HeaderList, MethodLine); /* remove it from list */
  if ( (strncmp(MethodLine,"GET " , 4) != 0) &&
       (strncmp(MethodLine,"POST ", 5) != 0) &&
       (strncmp(MethodLine,"HEAD ", 5) != 0) )
  {
    write_client_str(ClientSocket,"HTTP/1.1 501 Method Not Implemented\r\n");
    write_client_str(ClientSocket,"Allow: GET, HEAD, POST\r\n\r\n");
    send_error_page(ClientSocket, MethodLine, 
        "501: Request method not supported.");
    g_free(MethodLine);
    return request_failed(ClientChannel,HeaderList);
  }
  /* Uppercased first char of method is our "nmemonic" (G|P|H) */
  MethodID=(MethodLine[0]<='Z')?MethodLine[0]:MethodLine[0]+0x20;
  Url=strchr(MethodLine, ' ');
  if (!Url) {
    write_client_str(ClientSocket, "HTTP/1.1 400 Bad Request\r\n\r\n");
    send_error_page(ClientSocket, 
      "[Unknown]", "400: Unable to determine the URL.");
    g_free(MethodLine);
    return request_failed(ClientChannel,HeaderList);
  }
  while ( ' ' == *Url ) { Url++; }
  EndPtr=strchr(Url, ' ');
  if (EndPtr){ *EndPtr='\0'; }
  /* We hope the client specifies a complete URL when talking to a proxy. */
  if ( 0 == strncasecmp(Url, "http://", 7) ) { 
    Url=g_strdup(Url);  
  } else {
    /* Our resource didn't start with "http://", so we try a little harder */
    char *ProtoSep=strstr(Url,"://");
    if (ProtoSep && (ProtoSep<strchr(Url, '/'))) {
      /* It looks like something besides http, so bail out now */
      write_client_str(ClientSocket,
        "HTTP/1.1 501 Unsupported Protocol\r\n\r\n");
      send_error_page(ClientSocket, Url, 
        "501: The proxy only supports http:// requests.");
      g_free(MethodLine);
      return request_failed(ClientChannel,HeaderList);
    }
    /* We didn't get a complete URL, so we will try to build one... */
    HeaderValue=get_header_value(HeaderList, "Host");
    if ((!HeaderValue) || (!*Url) ){
      write_client_str(ClientSocket, "HTTP/1.1 400 Bad Request\r\n\r\n");
      send_error_page(ClientSocket, Url, 
         "400: Unable to determine hostname from request.");
      g_free(MethodLine);
      return request_failed(ClientChannel,HeaderList);
    }
    Url=g_strdup_printf("http://%s%s%s", HeaderValue,('/'==Url[0])?"":"/",Url);
  }
  g_free(MethodLine);
  /* If this is a POST, we need the content-length... */
  if ('P'==MethodID) {
    HeaderValue=get_header_value(HeaderList, "Content-Length");
    if (!HeaderValue){
      write_client_str(ClientSocket, "HTTP/1.1 411 Length Required\r\n\r\n");
      send_error_page(ClientSocket, Url,
         "411: This proxy requires a Content-Length header for POST data.");
      g_free(Url);
      return request_failed(ClientChannel,HeaderList);
    }
    PostLength=g_ascii_strtoull(HeaderValue, &EndPtr, 0);
    if ((!EndPtr) || (*EndPtr != '\0')) {
      write_client_str(ClientSocket, "HTTP/1.1 411 Length Required\r\n\r\n");
      send_error_page(ClientSocket, Url, 
        "411: The Content-Length header could not be parsed.");
      g_free(Url);
      return request_failed(ClientChannel,HeaderList);
    }
  }
  /* For simplicity, it's better if we don't accept content-encodings at all */
  ThisHeader = g_slist_find_custom(HeaderList, "Accept-Encoding",
                                    (GCompareFunc)find_header_cb );
  if (ThisHeader) {
    HeaderList = g_slist_remove_link(HeaderList, ThisHeader);
    g_free(ThisHeader->data);
    g_slist_free(ThisHeader);
  }
  /* If we made it this far, go get it! */
  new_conn(Url, HeaderList, PostLength, MethodID, ClientChannel, data);
  return TRUE;
}



#ifndef __WIN32__
/* Unix apps get a SIGPIPE if the client closes a connection
   unexpectedly, so we need to trap it here... */
static void trap_sigpipe()
{
  struct sigaction sa;
  sa.sa_handler = SIG_IGN;
  sigemptyset (&sa.sa_mask);
  sa.sa_flags = 0;
  sigaction (SIGPIPE, &sa, NULL);
}
#endif




/* Check all curl multi sockets ocassionally, to avoid stalling. */
#define UGLY_STALLING_HACK 0

#if UGLY_STALLING_HACK
static gboolean check_timer_cb(void *user_data)
{
  GlobalInfo*gi = (GlobalInfo*)user_data;
  CURLMcode rc;

  if (gi->still_running>0) {
    do {
      rc = curl_multi_socket_all(gi->multi, &gi->still_running);
    } while (CURLM_CALL_MULTI_PERFORM == rc);
    mcode_or_die(rc, "check_timer_cb");
    check_run_count(gi);
  }
  return TRUE;
}
#else
#define check_timer_cb NULL
#endif



/* Show usage syntax and example and exit */
static void usage(char *AppName){
    g_printerr("Usage:\n  %s [address:]<port>\n", AppName );
    g_printerr("Examples:\n");
    g_printerr("  %s 127.0.0.1:8080   <= specify interface\n", AppName );
    g_printerr("  %s 3128             <= use any interface\n", AppName );
    exit(1);
}


/**** MAIN ****/
int main(int argc, char **argv)
{
  GlobalInfo *gi;
  GMainLoop*gmain;
  char *AppName, *PortStr, *EndPtr, *ListenIntf = NULL;
  int ListenPort = 0;
  int ListenSocket;
  struct sockaddr_in ListenAddr;
  GIOChannel* ListenChannel;


  gi=g_malloc0(sizeof(GlobalInfo));
  AppName=(char *)g_basename(argv[0]);
  curl_global_init(CURL_GLOBAL_ALL); /* Needed for winsock init */

#ifndef __WIN32__
  trap_sigpipe(); /* Don't let a SIGPIPE from the client kill us */
#endif

  if ((argc!=2) || (argv[1] && ('-'==argv[1][0])) ) { usage(AppName); }
  PortStr=strchr(argv[1], ':');
  if (PortStr) {
    if (PortStr>argv[1]){ListenIntf=g_strndup(argv[1], PortStr-argv[1]);}
  PortStr++;
  } else {PortStr=argv[1];}
  ListenPort = g_ascii_strtoll(PortStr, &EndPtr, 0);
  if ( (!EndPtr) || (*EndPtr !='\0') ) {
    g_printerr("%s: PORT must be a numeric value, not \"%s\"\n",
    AppName, PortStr);
    exit(1);
  }
  if ( ( ListenPort < 1 ) || ( ListenPort > 65535 ) ) {
    g_printerr("%s: Error: PORT must be between 1 and 65535, got %u\n", 
    AppName, ListenPort);
    exit(1);
  }
  ListenSocket=socket(AF_INET, SOCK_STREAM, 0);
  if (ListenSocket < 0) {
    g_printerr("%s: socket() failed: %s.\n", AppName, strerror(errno));
    exit(1);
  }
  ListenAddr.sin_family= AF_INET;
  ListenAddr.sin_port=htons(ListenPort);
  if (ListenIntf) {
    ListenAddr.sin_addr.s_addr=inet_addr(ListenIntf);
    if (strcmp(ListenIntf, "255.255.255.255") != 0 ) {
      if ( INADDR_NONE == ListenAddr.sin_addr.s_addr ) {
        g_print( "%s: failed to convert \"%s\" to a valid address.\n",
                  AppName, ListenIntf);
        exit(1);
      }
    }
   } else {
    ListenAddr.sin_addr.s_addr = htonl(INADDR_ANY);
  }
  if (bind(ListenSocket, (struct sockaddr*)&ListenAddr, sizeof(ListenAddr))<0) {
     g_printerr("%s: Can't bind to port %d on %s (%s)\n",
               AppName, ListenPort, ListenIntf?ListenIntf:"", strerror(errno));
    exit(1);
  }
  if ( listen(ListenSocket,MAX_PENDING_CONNECTIONS) < 0 ) {
    g_printerr("%s: Unable to listen on socket #%d (%s)\n",
               AppName, ListenSocket, strerror(errno) );
    exit(1);
  }
  ListenChannel=IO_CHANNEL_NEW(ListenSocket);
  if ( !ListenChannel ) {
    g_printerr("%s: Failed to create listener channel.\n", AppName);
    exit(1);
  }
  g_io_add_watch(ListenChannel,G_IO_IN,listen_cb,gi);
  if ( ListenIntf && ListenIntf[0] ) {
    g_print("%s: Listening on %s port %d\n",
      AppName, ListenIntf, ListenPort);
  } else {
    g_print("%s: Listening on port %d (promiscuous mode)\n",
      AppName, ListenPort);
  }
  gmain=g_main_loop_new(NULL,FALSE);
  gi->multi = curl_multi_init();
  curl_multi_setopt(gi->multi, CURLMOPT_SOCKETFUNCTION, sock_cb);
  curl_multi_setopt(gi->multi, CURLMOPT_SOCKETDATA, gi);
  curl_multi_setopt(gi->multi, CURLMOPT_TIMERFUNCTION, update_timeout_cb);
  curl_multi_setopt(gi->multi, CURLMOPT_TIMERDATA, gi);
  if (check_timer_cb) g_timeout_add(5000, check_timer_cb, gi);
  g_main_loop_run(gmain);
  return 0;
}
