/*-----------------------------------------------------------------------------

    DSPAM plugin for CommuniGate Pro 4.x, 5.x

    Installation and usage instructions can be found at
    http://kocmuk.ru/2009/11/28/dspam-cgp/


    Copyright (C) 2009 kocmuk.ru
    Version 1.0.2   

  ---------------------------------------------------------------------------

    Build:

    AIX:        cc_r dspam-cgp.c -o dspam-cgp
    Linux:      gcc -Wall -pthread dspam-cgp.c -o dspam-cgp
    FreeBSD:    gcc -DFREEBSD -pthread dspam-cgp.c -o dspam-cgp
    Solaris:    gcc -threads dspam-cgp.c -o dspam-cgp -lsocket -lnsl

  ---------------------------------------------------------------------------

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions
  are met:

    1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.
    2. Redistributions in binary form must reproduce the above copyright
       notice, this list of conditions and the following disclaimer in
       the documentation and/or other materials provided with the
       distribution.
    3. The name of the author may not be used to endorse or promote
       products derived from this software without specific prior written
       permission. 

  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
  OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
  OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

-----------------------------------------------------------------------------*/


#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <pwd.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>                                                         
#include <sys/un.h>                                                             

#ifdef FREEBSD
#include <netinet/in.h>
#endif

#define CGP_API_VERSION     2
#define CGP_CMD_LEN         16
#define CGP_CMDARG_LEN      128
#define CGP_ANSWER_LEN      4096
#define CGP_DIR             "/var/CommuniGate"

/* def config */
#define DSPAM_CMD_LEN	    128
#define DSPAM_TIMEOUT       30
#define DSPAM_PORT          24
#define DSPAM_MSIZE 	    256 /* in Kylobytes */

#define TRANSPORT_LOCALHOST 0x01    /* TCP to localhost only */
#define TRANSPORT_TCP       0x02    /* standard TCP socket   */
#define TRANSPORT_UNIX      0x03    /* UNIX domain socket    */
#define TRANSPORT_MAX_HOSTS 1

/* Defines the bar score ranges. By default the following ratios are used:
 * digital    Bar score
 * 0          []
 * 1-49       [X]
 * 50-70      [XX]
 * 71-89      [XXX]
 * 90-94      [XXXX]
 * 95-98      [XXXXX]
 * 99-100     [XXXXXX]
*/
int BARSCORERANGES[] = {0,49,70,89,94,98,100,      -1};
/*  Bar score here      ^^^^^^^^^^^^^^^^^^^^ */ 

typedef struct transport
{
    int            type;
    const char     *socketpath;    /* for UNIX domain socket */
    const char     *hostname;      /* for TCP sockets        */
    unsigned short port;           /* for TCP sockets        */
    struct in_addr hosts[TRANSPORT_MAX_HOSTS];
    int            nhosts;
    int            flags;
} transport_t;

typedef struct req 
{
	unsigned int	seqNum;
	char		cmdArg[CGP_CMDARG_LEN];
	transport_t	*trans;
	int		timeout;
        char		duser[DSPAM_CMD_LEN];
	char		dauth[DSPAM_CMD_LEN];
        int		classify;
	int		quiet;
	long		dmsize;
} req_t;

void        *request_FILE(void *arg);
void	    chomp (char *string);

static int  ds_lmtp(FILE *fds, char *buf, size_t buflen, const char *cmd, int wait, int timeout, int fd);
static int  getline(FILE *fds, char *buf, size_t buflen, int timeout, int fd);
static int  ds_stream(req_t *req);
static void print_usage(void);
static void putline(char *f, ...);
static int  read_args(int argc, char **argv, int *timeout, transport_t *trans, 
	int *dmsize, char *dauth, char *duser, int *classify, int *quiet);
static int  transport_connect(req_t *req);
static void transport_init(transport_t *trans);
static int  transport_setup(transport_t *trans);
static char *replace_spec_chars(char *msg, int msg_len, char *newmsg);
static char *dspam_level_calculate(char *msg);
static int select_fd (int fd, int maxtime, int writep);
char *ifgets(char *s, int size, FILE *stream, int timeout, int fd);

/* --------------------- */
int
main(int argc, char **argv)
{
    char            request[CGP_CMDARG_LEN+CGP_CMD_LEN];
    char            cmd[CGP_CMD_LEN], cmdArg[CGP_CMDARG_LEN];
    char	    duser[DSPAM_CMD_LEN];
    char	    dauth[DSPAM_CMD_LEN];
    unsigned int    seqNum;
    transport_t     trans;
    pthread_t       req_thread;
    pthread_attr_t  p_attr;
    req_t           *req;
    int             timeout = DSPAM_TIMEOUT;
    int             rc;
    int		    classify = 0;
    int		    quiet = 0;
    int		    dmsize = DSPAM_MSIZE;

    transport_init(&trans);
    read_args(argc, argv, &timeout, &trans, &dmsize, dauth, duser, &classify, &quiet);

    if ( (strlen(dauth) == 0) || (strlen(duser) == 0)) {
	putline("* dspam-cgp: Unable to determine the destination user or client ident");
	return 1;
    }

    if( transport_setup(&trans) ) {
        putline("* dspam-cgp: Can't transport_setup()");
        return 1;
    }

    pthread_attr_init(&p_attr);
    pthread_attr_setdetachstate(&p_attr, PTHREAD_CREATE_DETACHED);

    while( fgets(request, sizeof(request), stdin) != NULL ) {
        if( sscanf(request, "%u %s %s\n", &seqNum, cmd, cmdArg) != 3 ) {
            putline("* dspam-cgp: CGP API error: Can't get command");
            return 1;
        }

        if( strncasecmp(cmd, "FILE", 4) == 0 ) {        /* FILE command  */
            if( (req = (req_t *)calloc(1, sizeof(req_t))) == NULL ) {
		putline("* dspam-cgp: Can't calloc(): %s", strerror(errno));
                putline("%u FAILURE", seqNum);
                continue;
            }

            strncpy(req->cmdArg, cmdArg, sizeof(cmdArg)-1);
            req->seqNum  = seqNum;
            req->trans   = &trans;
            req->timeout = timeout;
 	    strncpy(req->duser, duser, sizeof(duser)-1);
	    strncpy(req->dauth, dauth, sizeof(dauth)-1);
	    req->classify = classify;
	    req->quiet = quiet;
	    req->dmsize = dmsize * 1024;

            req_thread = (pthread_t)NULL;

            if( (rc = pthread_create(&req_thread, &p_attr, request_FILE, (void*)req)) ) {
		putline("* dspam-cgp: Can't pthread_create(): %s", strerror(errno));
                putline("%u FAILURE", seqNum);
                free(req);
                continue;
            }

        } else if( strncasecmp(cmd, "INTF", 4) == 0 ) { /* INTF command  */

            putline("%u INTF %u", seqNum, CGP_API_VERSION);

        } else {                                        /* Default */

	    putline("* dspam-cgp: bad command: %s", cmd);
            putline("%u FAILURE", seqNum);

        }
    }

    if( ferror(stdin) && errno != EINTR )
        putline("* dspam-cgp: %s", strerror(errno));

    return 0;
}


/* ----------------------------------------------------------------- */
void *
request_FILE(void *arg)
{
    req_t       *req = (req_t *) arg;
    int         rc;

    if ((rc = ds_stream(req)) <= 0 ) {
	 putline("%u FAILURE",  req->seqNum);
    }

    free(req);
    return NULL;
}

/* ----------------------------------------------------------------- */
static int 
ds_lmtp(FILE *fds, char *buf, size_t buf_len, const char *cmd, int wait, int timeout, int fd)
{
    char    ecmd[CGP_CMDARG_LEN];
    char    rstr;
    int     rc, len;

    if( strlen(cmd) > 0 ) {
 	snprintf(ecmd, sizeof(ecmd), "%s\n", cmd);
    	if( fputs(ecmd, fds) <= 0 ) {
            putline("* dsapm-cgp: ds_lmtp: Can't fputs(): %s", strerror(errno));
	    return -1;
        }
	fflush(fds);
/*	putline("* LMTP DEBUG  put: '%s'", cmd); */
    }

    while( (len = getline(fds, buf, buf_len, timeout, fd)) >=0 ) {
	chomp(buf);
	if ( sscanf(buf,"%03d%c",&rc, &rstr) <= 0 ) {
	     putline("* dsapm-cgp: ds_lmtp: abnormal answer: %s", buf);
	     return -1;
	}
/*	
	putline("* LMTP DEBUG  get: '%s'", buf);
	putline("* LMTP DEBUG wait: '%d'", wait);
        putline("* LMTP DEBUG  ret: '%d' - '%c'", rc, rstr);
*/
        if ( rstr != ' ' )
             continue;

        if ( rc == wait )
             return 1;
             
        putline("* dsapm-cgp: ds_lmtp: error: wait='%d', but rc='%d', str='%s'", wait, rc, buf);
        return -1;
    }
    return -1;
}

/* ----------------------------------------------------------------- */
static int
getline(FILE *fds, char *buf, size_t buflen, int timeout, int fd)
{
  /* If the file isn't there or at the end, can't do anything */
  if ( (NULL == fds) || feof(fds) || ferror(fds) ) {
	putline("* dspam-cgp: getline: file isn't there or at the end, can't do anything: %s", strerror(errno));
	return -1;
  }

  if (ifgets(buf, buflen, fds, timeout, fd))
	return strlen(buf);
  if (errno == ETIMEDOUT)
	 putline("* dspam-cgp: getline: Socket timeout after %d seconds", timeout);
  return (-1);
}

/* ----------------------------------------------------------------- */
static int
ds_stream(req_t *req)
{
    char                buf[CGP_ANSWER_LEN+2];
    char		headers[CGP_ANSWER_LEN+2];
    char		dspam_cmd[DSPAM_CMD_LEN];
    int                 buf_len = sizeof(buf) - 2, len;
    int                 fd = -1, fdi = -1, domsg = 0, fcut = 0;
    FILE		*fds, *fdis;
    long		fsize = 0, psize = 0;

    if( (fd = transport_connect(req)) == -1 )
        return -1;
    if( (fds =  fdopen(fd, "w+")) == NULL ) {
	putline("* dspam-cgp[%u]: ds_stream: Can't fdopen(): %s", req->seqNum, strerror(errno));
	close(fd);
	return -1;
    }
    setvbuf(fds, (char *) NULL, _IONBF, 0);

/* --- INIT --- */
    if (ds_lmtp(fds, buf, buf_len, "", 220, req->timeout, fd) < 0) {
        putline("* dspam-cgp[%u]: LMTP INIT error", req->seqNum);
        fclose(fds);
	close(fd);
        return -1;
    }

/* --- LHLO --- */
    if (ds_lmtp(fds, buf, buf_len, "LHLO localhost.localdomain", 250, req->timeout, fd) < 0) {
	putline("* dspam-cgp[%u]: LMTP LHLO error", req->seqNum);
	fclose(fds);
	close(fd);
	return -1;
    }	

/* --- MAIL FROM --- */
    if (req->classify) {
        snprintf(dspam_cmd, sizeof(dspam_cmd), 
		"MAIL FROM: <%s> DSPAMPROCESSMODE=\"--client --classify --stdout\"", req->dauth);
    } else {
	snprintf(dspam_cmd, sizeof(dspam_cmd), 
		"MAIL FROM: <%s> DSPAMPROCESSMODE=\"--client --process --deliver=innocent,spam --stdout\"", req->dauth);
    }
    if (ds_lmtp(fds, buf, buf_len, dspam_cmd, 250, req->timeout, fd) < 0) {
        putline("* dspam-cgp[%u]: LMTP MAIL FROM error", req->seqNum);
        fclose(fds);
	close(fd);
        return -1;
    }

/* --- RCPT TO --- */
    snprintf(dspam_cmd, sizeof(dspam_cmd), "RCPT TO: <%s>", req->duser);
    if (ds_lmtp(fds, buf, buf_len, dspam_cmd, 250, req->timeout, fd) < 0) {
        putline("* dspam-cgp[%u]: LMTP RCPT TO error", req->seqNum);
        fclose(fds);
	close(fd);
        return -1;
    }

/* --- DATA --- */
    if (ds_lmtp(fds, buf, buf_len, "DATA", 354, req->timeout, fd) < 0) {
        putline("* dspam-cgp[%u]: LMTP DATA error", req->seqNum);
        fclose(fds);
	close(fd);
        return -1;
    }

/* --- open file --- */
    if( (fdis = fopen(req->cmdArg, "r")) == NULL ) {
        putline("* dspam-cgp[%u]: Can't open file(%s): %s", req->seqNum, req->cmdArg, strerror(errno));
        fclose(fds);
	close(fd);
        return -1;
    }

    /* Check file size and set fcut flag */
    fseek(fdis, 0L, SEEK_END);
    fsize = ftell(fdis);
    fseek(fdis, 0L, SEEK_SET);   
    if (fsize > (req->dmsize + 2*1024))
	fcut = 1;


    setvbuf(fdis, (char *) NULL, _IONBF, 0);
    fdi = fileno(fdis);

/* --- put file to dspam --- */
    while( (len = getline(fdis, buf, buf_len, req->timeout, fdi)) > 0 ) {

	/* Skip CGP headers */
	if (( strncmp(buf, "\n", 1) == 0 ) && (len == 1) && (!domsg)) { 
	    domsg = 1;
	    continue;
	}

	if (domsg) {
	    if (fcut) {
		if (psize > req->dmsize)
			break;
	    	psize = psize + len;
	    }
            if( fputs(buf, fds) <= 0 ) {
                putline("* dspam-cgp[%u]: Can't fputs(): %s", req->seqNum, strerror(errno));
                fclose(fds);
		close(fd);
                fclose(fdis);
                return -1;
            }
	    fflush(fds);
	}
    }
    fclose(fdis);

/* --- put end (.) --- */
    if( fputs(".\n", fds) <= 0 ) {
	putline("* dspam-cgp[%u]: LMTP (.) error", req->seqNum);
        fclose(fds);
	close(fd);
        return -1;
    }
    fflush(fds);

/* --- READ answer --- */
    if( (len = getline(fds, buf, buf_len, req->timeout, fd)) <= 0 ) {
        putline("* dspam-cgp[%u]: Can't read(): %s", req->seqNum, strerror(errno));
        fclose(fds);
	close(fd);
        return -1;
    }

/* --- QUIT --- */
    if( fputs("QUIT\n", fds) <= 0 ) {
        putline("* dspam-cgp[%u]: LMTP (QUIT) error", req->seqNum);
        fclose(fds);
        close(fd);
        return -1;
    }
    fflush(fds);

    fclose(fds);
    close(fd);

/* --- Check  answer --- */

    buf[len] = '\0';
    chomp(buf);

    if( strcasecmp(buf, "X-DSPAM-Result:") >= 0 ) {
	putline("%u ADDHEADER \"%s\"", req->seqNum, 
		dspam_level_calculate(replace_spec_chars(buf, strlen(buf), headers)));
        return 1;

    } else if (strcasecmp(buf, ".") == 0 ) {
	if (req->quiet) {
		putline("* dspam-cgp[%u]: DSPAM Skip the message", req->seqNum);
		putline("%u OK",  req->seqNum);
	} else {
		putline("%u ADDHEADER \"X-DSPAM-Result: Skip\"", req->seqNum);
	}
	return 1;
    } else {
        putline("* dspam-cgp[%u]: DSPAM reply error: %s", req->seqNum, buf);
    	return -1;
    }
}

/* ----------------------------------------------------------------- */
static void
print_usage(void)
{
    printf("Usage: dspam-cgp [options]\n\n");
    printf("  -d <ip addr>    :  specify ip address to connect to DSPAM\n");
    printf("  -h              :  print this help message\n");
    printf("  -p <port>       :  specify port for connection. default: %u\n", DSPAM_PORT);
    printf("  -t <sec>        :  timeout in seconds to read from DSPAM.\n");
    printf("                  :  0 disables, default: %u sec\n", DSPAM_TIMEOUT);
    printf("  -U <path>       :  use UNIX domain socket with path\n");
    printf("  -a <sec@Relay1> :  set ClientIdent for DSPAM mode\n");
    printf("  -u <user>       :  specifies the destination users\n");
    printf("  -c              :  tells DSPAM to only classify the message\n");
    printf("  -S <size>       :  checks the first Kylobytes of a message\n");
    printf("                  :  if message size is more, default: %dK\n", DSPAM_MSIZE);
    printf("  -q              :  don't add any header on Skip messages\n\n");
}

/* ----------------------------------------------------------------- */
static void
putline(char *f, ...)
{
    char    answer[CGP_ANSWER_LEN];
    char    eanswer[CGP_ANSWER_LEN];
    va_list ap;

    va_start(ap, f);
    vsnprintf(answer, sizeof(answer)-1, f, ap);
    va_end(ap);

    snprintf(eanswer, sizeof(eanswer), "%s\n", answer);

    write(STDOUT_FILENO, eanswer, strlen(eanswer));
}

/* ----------------------------------------------------------------- */
static int
read_args(int argc, char **argv, int *timeout, transport_t *trans, 
	int *dmsize, char *dauth, char *duser, int *classify, int *quiet)
{
    int opt;

    while( (opt = getopt(argc,argv,"?hcqd:p:t:U:u:a:S:")) != -1 ) {
        switch(opt) {
            case 'U':
                trans->type       = TRANSPORT_UNIX;
                trans->socketpath = optarg;
                break;
            case 'u':
		chomp(optarg);
		strcpy(duser, optarg);
                break;
            case 'a':
		chomp(optarg);
		strcpy(dauth, optarg);
                break;
            case 'd':
                trans->type       = TRANSPORT_TCP;
                trans->hostname   = optarg;
                break;
            case 'p':
                trans->port = atoi(optarg);
                break;
            case 't':
                *timeout = atoi(optarg);
                break;
            case 'S':
		*dmsize = atoi(optarg);
		break;
            case 'c':
                *classify = 1;
                break;
            case 'q':
                *quiet = 1;
                break;
            case '?':
            case 'h':
                print_usage();
                exit(1);
        }
    }
    return 0;
}

/* ----------------------------------------------------------------- */
static int
transport_connect(req_t *req)
{
    struct sockaddr_in  srv_i;
    struct sockaddr_un  srv_u;
    transport_t         *trans = req->trans;
    int                 fd = -1;

    switch( trans->type ) {
        case TRANSPORT_TCP:
            if( (fd = socket(PF_INET, SOCK_STREAM, 0)) < 0 ) {
                putline("* dspam-cgp[%u]: Can't socket(): %s", req->seqNum, strerror(errno));
                return -1;
            }

            memset(&srv_i, 0, sizeof(srv_i));
            srv_i.sin_family = AF_INET;
            srv_i.sin_port = htons(trans->port);
            srv_i.sin_addr = trans->hosts[0];

            if( connect(fd, (struct sockaddr *) &srv_i, sizeof(srv_i)) < 0 ) {
                putline("* dspam-cgp[%u]: Can't connect(): %s", req->seqNum, strerror(errno));
                close(fd);
                return -1;
            }
            break;

        case TRANSPORT_UNIX:
            if( (fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0 ) {
                putline("* dspam-cgp[%u]: Can't socket(): %s", req->seqNum, strerror(errno));
                return -1;
            }

            memset(&srv_u, 0, sizeof(srv_u));
            srv_u.sun_family = AF_UNIX;
            strncpy(srv_u.sun_path, trans->socketpath, sizeof(srv_u.sun_path) - 1);

            if( connect(fd, (struct sockaddr *) &srv_u, sizeof(srv_u)) < 0 ) {
                putline("* dspam-cgp[%u]: Can't connect(): %s", req->seqNum, strerror(errno));
                close(fd);
                return -1;
            }
            break;

        default:
            break;
    }

    return fd;
}

/* ----------------------------------------------------------------- */
static void
transport_init(transport_t *trans)
{
    memset(trans, 0, sizeof(transport_t));
    trans->port = DSPAM_PORT;
}

/* ----------------------------------------------------------------- */
static int
transport_setup(transport_t *trans)
{
    switch( trans->type ) {
        case TRANSPORT_TCP:
            trans->hosts[0].s_addr = inet_addr(trans->hostname);
            trans->nhosts = 1;
            break;
        case TRANSPORT_UNIX:
            break;
        default:
            putline("* dspam-cgp: unknown transport type: %d", trans->type);
            return -1;
            break;
    }
    return 0;
}

/* ----------------------------------------------------------------- */
void
chomp (char *string)
{
  int len;
  if (string == NULL)
    return;
  len = strlen (string);
  if (len && string[len - 1] == 10)
  {
    string[len - 1] = 0;
    len--;
  }
  if (len && string[len - 1] == 13)
    string[len - 1] = 0;
  return;
}

/* ----------------------------------------------------------------- */
static char*
replace_spec_chars(char *msg, int msg_len, char *newmsg)
{
        char    *p = newmsg;
        int             i;

        for(i = 0; i < msg_len && (newmsg - p) < CGP_ANSWER_LEN-32; i++) {
                switch( msg[i] ) {
                        case '\0':
                        case '\r':
                        case '\n':
                                /* replace \r or \n or \0 -> \\e (CGP End-of-Line) */
                                *newmsg = '\\';
                                ++newmsg;
                                *newmsg = 'e';
                                break;
                        case '\t':
                                /* replace \t -> \\t (CGP Tab) */
                                *newmsg = '\\';
                                ++newmsg;
                                *newmsg = 't';
                                break;
                        case '"':
                                /* replace \" -> \\" (CGP quote) */
                                *newmsg = '\\';
                                ++newmsg;
                                *newmsg = '"';
                                break;
                        default:
                                /* just copy other chars */
                                *newmsg = msg[i];
                                break;
                }
                ++newmsg;
        }
        *newmsg = '\0';
        return p;
}

/* ----------------------------------------------------------------- */
static char*
dspam_level_calculate(char *msg)
{
	float 	conf=0.00, prob=0.00, tprob=0.00, out=0.00;
	char 	level[128]="", score[28]="", result[128]="";
	char 	*p;
	int 	i,rv=0;

        if ((p = strstr(msg, "X-DSPAM-Result: "))) {
                if ( sscanf(p,"X-DSPAM-Result: %s", result) != 1 ) {
                        putline("* dspam-cgp: dspam_level_calculate(): 'X-DSPAM-Result' parse error");
                        return msg;
                } else {
			if( strncasecmp(result, "Spam", 4) != 0 ) {
				return msg;
			}
		}
        } else {
                putline("* dspam-cgp: dspam_level_calculate(): 'X-DSPAM-Result' not found");
                return msg;
        }


	if ((p = strstr(msg, "X-DSPAM-Confidence: "))) {
		if ( sscanf(p,"X-DSPAM-Confidence: %f", &conf) != 1 ) {
			putline("* dspam-cgp: dspam_level_calculate(): 'X-DSPAM-Confidence' parse error");
			return msg;
		}
	} else {
		putline("* dspam-cgp: dspam_level_calculate(): 'X-DSPAM-Confidence' not found");
		return msg;
	}
        if ((p = strstr(msg, "X-DSPAM-Probability: "))) {
                if ( sscanf(p,"X-DSPAM-Probability: %f", &prob) != 1 ) {
                        putline("* dspam-cgp: dspam_level_calculate(): 'X-DSPAM-Probability' parse error");
			return msg;
                }
        } else {
		putline("* dspam-cgp dspam_level_calculate(): 'X-DSPAM-Probability' not found");
		return msg;
	}

/*	putline("* dspam-cgp: Confidence=%f, Probability=%f", conf, prob); */

	tprob = (((prob - 0.5) * 2) * 100);
	if (tprob <= 0) {
		putline("* dspam-cgp: dspam_level_calculate(): tprob=%f that <= 0", tprob); 
		return msg; 
	}

	out = ((tprob + (conf*100)) / 2);
	if (out > 0 && out < 101 ) {
		if (out > 3 && out < 7) {
			rv = 5;
		} else {
			rv = (abs((out / 10) + 0.49) * 10);
			if (out > 93)
				rv -= out > 97 ? 1:5;
		}

		for (i=0; BARSCORERANGES[i] >= 0; i++) {
			if (i==0) {
				if (rv<=BARSCORERANGES[i]) { 
					break; 
				}
			} else {
				if (rv>BARSCORERANGES[i-1] && rv<=BARSCORERANGES[i]) {
					while(i--) 
						strcat(score,"X");
					break; 
				}
			}
		}
		sprintf(level, "X-Junk-Score: %d [%s]\\n", rv, score);
		strcat(msg, level);
	} else {
		putline("* dspam-cgp: dspam_level_calculate(): Outcome='%f' calculate error.", out);
		return msg;
	}

return msg;

}

/* ----------------------------------------------------------------- */
char *
ifgets(char *s, int size, FILE *stream, int timeout, int fd)
{
  int res;

  if (timeout) {
      do {
          res = select_fd (fd, timeout, 0);
      } while (res == -1 && errno == EINTR);
      if (res <= 0) {
      	if (res == 0) {
		errno = ETIMEDOUT;
		return NULL;
	}
      return NULL;
      }
  }
  if(fgets(s, size, stream)) {
	return s;
  }
  return NULL;
}

/* ----------------------------------------------------------------- */
/* Wait for file descriptor FD to be readable, MAXTIME being the
   timeout in seconds.  If WRITEP is non-zero, checks for FD being
   writable instead.

   Returns 1 if FD is accessible, 0 for timeout and -1 for error in
   select().  */
static int
select_fd (int fd, int maxtime, int writep)
{
  fd_set fds, exceptfds;
  struct timeval timeout;

  FD_ZERO (&fds);
  FD_SET (fd, &fds);
  FD_ZERO (&exceptfds);
  FD_SET (fd, &exceptfds);
  timeout.tv_sec = maxtime;
  timeout.tv_usec = 0;
  /* HPUX reportedly warns here.  What is the correct incantation?  */
  return select (fd + 1, writep ? NULL : &fds, writep ? &fds : NULL,
                 &exceptfds, &timeout);
}

