/*
 * WMMail - Window Maker Mail
 *
 * Copyright (c) 1996, 1997, 1998  Per Liden
 * Copyright (c) 1997, 1998  Bryan Chan
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * pop3.c: functions to handle POP3 mailboxes
 *
 * $Id: pop3.c,v 1.2 2000/07/03 08:49:24 bryan.chan Exp $
 *
 */

#include <stdio.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <time.h>
#include <proplist.h>
#include "wmmail.h"
#include "wmutil.h"
#include "net.h"

#ifdef POP3_SUPPORT

/*
 * Supported key(s) in the Options dictionary:
 *
 *  Hostname
 *  Port (optional)
 *  Username
 *  Password
 *
 * See mbox.c for information on states and transitions.
 *
 * For a local mailbox, errors encountered during an update usually
 * cause the program to give up monitoring the mailbox ("ignored").
 *
 * For remote mailboxes, network errors are ignored. Query will be
 * re-tried at the next update. Other types of errors (such as invalid
 * options) still cause the program to give up.
 *
 */

/* internal data structure */
typedef struct {
  char *folder_name;
  int socket;
} Connection;

/* internal functions */
static Connection *init_connection(Mailbox *);
static int         count_mail(Connection *, int *, int *);
static void        close_connection(Connection *);


#define MAX_STRING_SIZE  1024

#define GET_ENTRY(x,y,z) { proplist_t sub_key = PLMakeString(z); \
                           y = PLGetDictionaryEntry(x, sub_key); \
                           PLRelease(sub_key); }

int POP3_check(Mailbox *mailbox, int *beep, int *redraw, int *run)
{
  Connection *connection;
  int         prev_status,
              prev_new_mail_count;

  connection = init_connection(mailbox);
  mailbox->last_update = time(NULL);

  if (connection == NULL)
    return False;
  else if (connection == (Connection *) -1)
    return True;
  else
  {
    prev_status = mailbox->status;
    prev_new_mail_count = mailbox->new_mail_count;

    if (count_mail(connection,
                   &mailbox->total_mail_count, 
                   &mailbox->new_mail_count))
    {
      close_connection(connection);

      if (mailbox->total_mail_count == 0)
      {
        /* there is no mail in the mailbox */
        mailbox->status = NO_MAIL;
      }
      else if (mailbox->new_mail_count == 0)
      {
        /* there are no new mails */
        mailbox->status = OLD_MAIL;
      }
      else if (mailbox->new_mail_count > prev_new_mail_count)
      {
        /* new mails have arrived! */
        if (mailbox->status == NEW_MAIL && always_new_mail_exec)
          *run |= True;
        else if (mailbox->status == NEW_MAIL)
          *run |= False;
        else
          *run |= True;

        *beep = True;

        mailbox->status = NEW_MAIL;
      }
      /* else no change */
    }
    else
    {
      croak("cannot count messages in mailbox \"%s\"; ignored", mailbox->name);
      close_connection(connection);
      return False;
    }

    *redraw |= (prev_status != mailbox->status);

    return True;
  }
}


/* creates a connection to the POP3 server and logs in
 *
 * returns:  -1                     if recoverable error encountered
 *           NULL                   if unrecoverable error encountered
 *           pointer to Connection  if successfully initiated
 */
static Connection *init_connection(Mailbox *mailbox)
{
  Connection         *connection;
  struct sockaddr_in  remote_addr;
  struct hostent     *host;
  char                buf[MAX_STRING_SIZE];
  int                 sockfd,
                      port_no = -1;
  proplist_t          hostname,
                      port,
                      username,
                      password,
                      folder;

  GET_ENTRY(mailbox->options, hostname, "Hostname");
  GET_ENTRY(mailbox->options, port, "Port");
  GET_ENTRY(mailbox->options, username, "Username");
  GET_ENTRY(mailbox->options, password, "Password");

  if (!username || !password || !hostname)
  {
    croak("mailbox \"%s\" missing one of the options \"Username\", "
          "\"Password\", and \"Hostname\"; ignored", mailbox->name);
    return NULL;
  }
  else if ( !PLIsString(hostname) ||
            !PLIsString(username) ||
            !PLIsString(password) ||
            (port && (!PLIsString(port) ||
                      sscanf(PLGetString(port), "%i", &port_no) != 1)) )
  {
    croak("mailbox \"%s\" has invalid options; ignored", mailbox->name);
    return (Connection *) NULL;
  }

  if (( host = gethostbyname(PLGetString(hostname)) ) == NULL)
  {
    croak("gethostbyname() failed for mailbox \"%s\"; continuing",
          mailbox->name);
    return (Connection *) -1;
  }

  if (( sockfd = socket(AF_INET, SOCK_STREAM, 0) ) == -1)
  {
    croak("socket() failed for mailbox\"%s\"; continuing", mailbox->name);
    return (Connection *) -1;
  }

  remote_addr.sin_family = AF_INET;
  remote_addr.sin_port   = htons(port_no == -1 ? 110 : port_no);
  remote_addr.sin_addr   = *((struct in_addr *) host->h_addr);
  bzero(&remote_addr.sin_zero, 8);

  if (connect(sockfd, (struct sockaddr *) &remote_addr,
              sizeof(struct sockaddr)) == -1)
  {
    croak("connect() failed for mailbox\"%s\"; continuing", mailbox->name);
    close(sockfd);
    return (Connection *) -1;
  }

  /* are we welcome? */
  socket_read(sockfd, buf, MAX_STRING_SIZE);
  if (strncasecmp(buf, "+OK ", 4))
  {
    /* give up if not; we don't want to force open a door that won't */
    croak("no service from remote server for mailbox \"%s\"; ignored",
          mailbox->name);
    close(sockfd);
    return (Connection *) NULL;
  }

  sprintf(buf, "USER %s\n", PLGetString(username));
  socket_write(sockfd, buf);
  socket_read(sockfd, buf, MAX_STRING_SIZE);
  if (strncasecmp(buf, "+OK ", 4))
  {
    croak("Bad username specified for mailbox \"mailbox\"; ignored",
          mailbox->name);
    close(sockfd);
    return (Connection *) NULL;
  }

  sprintf(buf, "PASS %s\n", PLGetString(password));
  socket_write(sockfd, buf);
  socket_read(sockfd, buf, MAX_STRING_SIZE);
  if (strncasecmp(buf, "+OK ", 4))
  {
    /* if we can't login, then don't try any more */
    croak("login failed for mailbox \"%s\"; ignored", mailbox->name);
    close(sockfd);
    return NULL;
  }

  connection = wmalloc(sizeof(Connection));
  connection->socket = sockfd;

  return connection;
}


/* FIXME: socket errors are transparent! */
static int count_mail(connection, total_mail_count, new_mail_count)
    Connection *connection;
    int        *total_mail_count,
               *new_mail_count;
{
    int   i;
    char  buf[MAX_STRING_SIZE + 1];
    char  cmd[MAX_STRING_SIZE + 1];

    buf[MAX_STRING_SIZE] = 0;
    cmd[MAX_STRING_SIZE] = 0;

    sprintf(cmd, "STAT\n");

    if (socket_write(connection->socket, cmd) &&
        socket_read(connection->socket, buf, MAX_STRING_SIZE)) {
        if (!strncasecmp(buf, "+OK ", 4)) {
            sscanf(buf + 4, "%d", total_mail_count);
        }
    }

    for (i = 0, *new_mail_count = 0; i < *total_mail_count; i++) {
        sprintf(cmd, "TOP %d 0\n", i + 1);
        if (socket_write(connection->socket, cmd)) {
            int j = 1;

            while (socket_read(connection->socket, buf, MAX_STRING_SIZE) &&
                   strncmp(buf, ".", 1)) {
                if (!strncmp(buf, "Status:", 7) && index(buf + 7, 'R'))
                    j = 0;
            }

            (*new_mail_count) += j;
        }
    }

    return True;
}


static void close_connection(Connection *connection)
{
  char buf[MAX_STRING_SIZE];

  sprintf(buf, "QUIT\n");
  socket_write(connection->socket, buf);
  socket_read(connection->socket, buf, MAX_STRING_SIZE);
  close(connection->socket);
  wfree(connection);
}

#endif


syntax highlighted by Code2HTML, v. 0.9.1