/* $Cambridge: hermes/src/mailchk/imap.c,v 1.5 2003/08/14 08:37:51 dpc22 Exp $ */

#include "mailchk.h"

static BOOL (*imap_callback)(int exists) = NULL;

void
imap_set_callback(BOOL (*callback)(int exists))
{
    imap_callback = callback;
}

/* ====================================================================== */

/* Local helper functions */

static char *
imap_getline(struct iostream *stream)
{
    int c;
    static unsigned char *buf = NULL;
    static unsigned long size = 0;
    unsigned long len;

    if (buf == NULL) {
        size = IMAP_GETLINE_DELTA;
        buf  = xmalloc(size);
    }

    len = 0;

    while ((c=iogetc(stream)) != EOF) {
        if (c == '\r')
            continue;

        if (c == '\n') {
            buf[len] = '\0';
            return((char *)buf);
        }

        if (len == (size-1)) {
            size += IMAP_GETLINE_DELTA;
            buf  = xrealloc(buf, size);
        }
        buf[len++] = c;
    }
    return(NIL);
}

static BOOL
imap_nmatch(char *s1, char *s2)
{
    return((!strncmp(s1, s2, strlen(s2))) ? T : NIL);
}

static BOOL
process_unsolicited_line(char *s)
{
    char *t;

    if (!imap_nmatch(s, "* ") || !(t=strchr(s+2, ' ')))
        return(NIL);

    *t++ = '\0';
    s += 2;

    if (!strcmp(t, "EXISTS") && imap_callback)
        (*imap_callback)(atoi(s));

    return(T);
}

/* ====================================================================== */

struct iostream *
imap_connect(char *hostname, BOOL use_ssl)
{
    int fd;
    struct iostream *stream;
    unsigned long port = (use_ssl) ? 993 : 143;
    char *s;

    if ((fd=os_connect_inet_socket(hostname, port)) < 0)
        log_fatal("Unable to connect to %s: %s", hostname, strerror(errno));

    if (!(stream=iostream_create(fd, 0)))
        log_fatal("Unable to create iostream");

    if (use_ssl && !iostream_ssl_start_client(stream))
        log_fatal("Unable to start SSL on stream");

    if (!((s=imap_getline(stream)) && imap_nmatch(s, "* OK ")))
        log_fatal("Unable to read banner line from server");

    return(stream);
}

BOOL
imap_authenticate(struct iostream *stream, char *username, char *password)
{
    char *s;

    if (strchr(password, '"'))
        ioprintf(stream, ". LOGIN %s {%lu+}"CRLF"%s"CRLF,
                 username, strlen(password), password);
    else if (strchr(password, ' '))
        ioprintf(stream, ". LOGIN %s \"%s\""CRLF, username, password);
    else
        ioprintf(stream, ". LOGIN %s %s"CRLF, username, password);

    ioflush(stream);

    while ((s=imap_getline(stream)) && imap_nmatch(s, "* "))
        process_unsolicited_line(s);

    if (!imap_nmatch(s, ". "))
        log_fatal("Login failed");

    s += strlen(". ");

    if (imap_nmatch(s, "NO "))
        return(NIL);
    else if (!imap_nmatch(s, "OK "))
        log_fatal("Login failed");

    return(T);
}

BOOL
imap_examine(struct iostream *stream, char *foldername)
{
    char *s;

    if (strchr(foldername, '"'))
        ioprintf(stream, ". EXAMINE {%lu+}"CRLF"%s"CRLF,
                 strlen(foldername), foldername);
    else if (strchr(foldername, ' '))
        ioprintf(stream, ". EXAMINE \"%s\""CRLF, foldername);
    else
        ioprintf(stream, ". EXAMINE %s"CRLF, foldername);

    ioflush(stream);

    while ((s=imap_getline(stream)) && imap_nmatch(s, "* "))
        process_unsolicited_line(s);

    if (!imap_nmatch(s, ". OK "))
        log_fatal("Login failed");

    return(T);
}


BOOL
imap_noop(struct iostream *stream)
{
    char *s;

    ioputs(stream, ". NOOP"CRLF);
    ioflush(stream);

    while ((s=imap_getline(stream)) && imap_nmatch(s, "* "))
        process_unsolicited_line(s);

    return(imap_nmatch(s, ". OK ") ? T : NIL);
}


BOOL
imap_idle(struct iostream *stream)
{
    char *s;
    time_t start = time(NULL);
    time_t runtime;

    ioprintf(stream, ". IDLE"CRLF);
    ioflush(stream);

    if (!((s=imap_getline(stream)) && !strcmp(s, "+ idling")))
        log_fatal("IDLE failed");


    /* Server will timeout after 30 minutes time even if it has produced
     * some output: unsolicited responses don't restart the clock.
     * Following loop terminates after at most 29 mins (and at least 25
     * minutes) so that we can issue another idle and restart the clock */

    while (((runtime = time(NULL) - start) >= 0) && (runtime <= (25*60))) {
        iostream_set_timeout(stream, (29*60) - runtime);

        if (((s = imap_getline(stream)) == NULL) || !imap_nmatch(s, "* "))
            break;

        process_unsolicited_line(s);
    }

    iostream_set_timeout(stream, (29*60));
    
    if (iostream_is_eof(stream))
        return(NIL);

    ioprintf(stream, "DONE"CRLF);
    ioflush(stream);

    while ((s=imap_getline(stream)) && imap_nmatch(s, "* "))
        process_unsolicited_line(s);

    if (s && !imap_nmatch(s, ". OK"))
        return(NIL);

    return(T);
}
