Logo Search packages:      
Sourcecode: pan version File versions  Download package

nntp.c

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * Pan - A Newsreader for Gtk+
 * Copyright (C) 2002  Charles Kerr <charles@rebelbase.com>
 *
 * 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; version 2 of the License.
 *
 * 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
 */

#include <config.h>

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <glib.h>

#include <gmime/gmime-utils.h>

#include <pan/base/acache.h>
#include <pan/base/debug.h>
#include <pan/base/log.h>
#include <pan/base/pan-glib-extensions.h>
#include <pan/base/pan-i18n.h>
#include <pan/base/serverlist.h>
#include <pan/base/util-mime.h>

#include <pan/nntp.h>
#include <pan/prefs.h>
#include <pan/task.h>
#include <pan/auth-spa.h>

/** deprecated, use nntp_command */
extern const char * sockwrite_err_msg;
extern const char * sockread_err_msg;

enum
{
      OK_GROUP                      = 211,
      OK_AUTH                             = 281,
      NEED_AUTHDATA                       = 381,
      ERR_NOAUTH                    = 480,
      ERR_AUTHREJ                   = 482,
      ERR_ACCESS                    = 502
};

enum
{
      AUTH_UNKNOWN,
      AUTH_MSN
};

/**
***
**/

static TaskStateEnum
nntp_get_response (PanSocket     * socket,
                   const char   ** setme_response,
                   int           * setme_response_number)
{
      TaskStateEnum retval;
      const char * msg = NULL;
      debug_enter ("nntp_get_response");

      /* sanity clause */
      g_return_val_if_fail (socket!=NULL, TASK_FAIL);
      g_return_val_if_fail (setme_response!=NULL, TASK_FAIL);
      g_return_val_if_fail (setme_response_number!=NULL, TASK_FAIL);

      retval = pan_socket_getline (socket, &msg);
      if (retval == TASK_OK)
      {
            *setme_response = msg;
            *setme_response_number = atoi (msg);
      }
      else
      {
            *setme_response = msg ? msg : sockread_err_msg;
            *setme_response_number = -1;
      }

      debug_exit ("nntp_get_response");
      return retval;
}

TaskStateEnum
nntp_command (StatusItem           * status,
              PanSocket            * sock,
              const gchar         ** setme_response,
              int                  * setme_response_number,
              const gchar          * command)
{
      TaskStateEnum val;
      size_t len;

      /* sanity clause */
      g_return_val_if_fail (sock!=NULL, TASK_FAIL);
      g_return_val_if_fail (is_nonempty_string(command), TASK_FAIL);
      g_return_val_if_fail (setme_response!=NULL, TASK_FAIL);
      g_return_val_if_fail (setme_response_number!=NULL, TASK_FAIL);
      g_return_val_if_fail (is_nonempty_string(command), TASK_FAIL);

      /**
      ***  Send a command...
      **/

      len = strlen (command);
      if (len>=2 && command[len-2]=='\r' && command[len-1]=='\n')
      {
            val = pan_socket_putline (sock, command);
      }
      else /* not terminated in \r\n, so add */
      {
            gchar * tmp = g_strdup_printf ("%s\r\n", command);
            val = pan_socket_putline (sock, tmp);
            g_free (tmp);
      }
      if (val != TASK_OK)
      {
            *setme_response = sockwrite_err_msg;
            *setme_response_number = -1;
            return val;
      }

      /**
      ***  Get a response...
      **/

      val = nntp_get_response (sock, setme_response, setme_response_number);

      if (*setme_response_number != ERR_NOAUTH)
            return val;

      val = nntp_authenticate (status, sock);

      if (val == TASK_OK)
            return nntp_command (status, sock, setme_response, setme_response_number, command);
      else
            return val;
}
 
TaskStateEnum
nntp_command_va (StatusItem           * status,
                 PanSocket            * sock,
                 const char          ** setme_result,
                 int                  * setme_result_number,
                 const char           * command_va,
                 ...)
{
      va_list args;
      char* line;
      TaskStateEnum retval;

      g_return_val_if_fail (sock!=NULL, TASK_FAIL);
      g_return_val_if_fail (setme_result!=NULL, TASK_FAIL);
      g_return_val_if_fail (setme_result_number!=NULL, TASK_FAIL);
      g_return_val_if_fail (is_nonempty_string(command_va), TASK_FAIL);

      va_start (args, command_va);
      line = g_strdup_vprintf (command_va, args);
      va_end (args);

      retval = nntp_command (status, sock, setme_result, setme_result_number, line);

      g_free (line);
      return retval;
}

/**
***
**/

void
nntp_disconnect (StatusItem   * status,
                 PanSocket    * socket)
{
      debug_enter ("nntp_disconnect");

      /* entry assertions */
      g_return_if_fail (socket!=NULL);

      /* try to disconnect.  We don't look for the response, since we don't
       * know what's in this socket.  It could be dead, or it could still have
       * crap in the read buffer, mangling the response number. */
      pan_socket_putline (socket, "QUIT\r\n");

      /* Flush the socket. This ensures any remaining data (like TCP closing 
       * packets) are read between sending the NNTP command (which makes the 
       * server close the socket), and us closing our end of the socket.
       * This prevents the socket going into TIME_WAIT state (or worse, 
       * FIN_WAIT2)
       */
      pan_socket_flush (socket);

      /* disconnected successfully */
      debug_exit ("nntp_disconnect");
}

/**
***
**/

TaskStateEnum
nntp_handshake (StatusItem  * status,
                PanSocket   * socket,
                gboolean    * setme_posting_ok)
{
      TaskStateEnum val;
      int response_number;
      const char * response = NULL;

      /* sanity checks */
      g_return_val_if_fail (status!=NULL, TASK_FAIL);
      g_return_val_if_fail (socket!=NULL, TASK_FAIL);
      g_return_val_if_fail (setme_posting_ok!=NULL, TASK_FAIL);
      debug_enter ("nntp_handshake");

      /* get the server's handshake message */
      val = nntp_get_response (socket, &response, &response_number);
      *setme_posting_ok = response_number == 200;
      if (response_number!=200 && response_number!=201) {
            status_item_emit_error_va (status, _("NNTP handshake failed: %s"), response);
            log_add_va (LOG_ERROR, _("Handshake failed: %s"), response);
            return val;
      }

      log_add_va (LOG_INFO, _("Handshake: %s"), response);

      /* yes, we also support authentication on demand, but it appears
         many servers are happier if we ask for authentication up-front. */
      if (pan_socket_needs_auth (socket))
            val = nntp_authenticate (status, socket);

      /* Achim Settelmeir reports that his local INN doesn't accept GROUP
         without MODE READER first */
      nntp_can_post (NULL, socket, setme_posting_ok);

      debug_exit ("nntp_handshake");
      return val;
}

static TaskStateEnum
nntp_spa_authenticate (StatusItem     * status,
                        PanSocket      * sock)
{
      /* Authentication for SPA scheme (msn.com, and others) */
      /* Added by Marc Prud'hommeaux <marc@apocalypse.org>, mostly */
      /* taken from the ftechmail SPA authentication, which, in */
      /* turn, was ripped out of the SAMBA project. */

      TaskStateEnum val;
      int response_number = 0;
      const char * response = NULL;
      const gchar * response_trim = NULL;
      gchar msgbuf[2048];
      SPAAuthRequest   spa_request;
      SPAAuthChallenge spa_challenge;
      SPAAuthResponse  spa_response;

      debug_enter ("nntp_spa_authenticate");

      memset (msgbuf, 0, sizeof (msgbuf));

      val = nntp_command (status, sock, &response, &response_number, "AUTHINFO GENERIC MSN");
      if (response_number != NEED_AUTHDATA) {
            status_item_emit_error_va (status, _("Authentication failed: bad handshake for SPA password"));
            return val;
      }
      
      spa_build_auth_request (&spa_request, pan_socket_get_username(sock)->str, NULL);
      spa_bits_to_base64 (msgbuf, (unsigned char*)&spa_request, spa_request_length (&spa_request));

      val = nntp_command_va (status, sock, &response, &response_number, "AUTHINFO GENERIC %s", msgbuf);
      if (response_number != NEED_AUTHDATA) {
            status_item_emit_error_va (status, _("Bad SPA handshake: %s"), response);
            return val;
      }

      if (!pstring_is_set (pan_socket_get_password(sock))) {
            if (status != NULL)
                  status_item_emit_error_va (status, _(
                        "Authentication failed: need a password"));
            return TASK_FAIL;
      }

      /* response is now something like:
         381 TlRMTVNTUAACAAAAGAAYACAAAAABAg [...]
         Trim off the first 4 bytes in our response. */
      response_trim = response + 4;
      spa_base64_to_bits ((unsigned char*)&spa_challenge, response_trim);
      spa_build_auth_response (&spa_challenge, &spa_response, 
                               pan_socket_get_username(sock)->str,
                               pan_socket_get_password(sock)->str);
      spa_bits_to_base64 (msgbuf, (unsigned char*)&spa_response, spa_request_length (&spa_response));

      val = nntp_command_va (status, sock, &response, &response_number, "AUTHINFO GENERIC %s", msgbuf);
      if (response_number != OK_AUTH) {
            status_item_emit_error_va (status, _("Authentication failed: %s"), response);
            debug_exit ("nntp_spa_authenticate");
            return val;
      }

      debug_exit ("nntp_spa_authenticate");
      return TASK_OK;
}

static void
log_handshake_error (StatusItem  * status,
                     PanSocket   * sock,
                     const char  * response)
{
      char buf [1024];
      const PString * name = pan_socket_get_server_name (sock);

      g_snprintf (buf, sizeof(buf),
                  _("%*.*s handshake failed: %s"), 
                  name->len, name->len, (name->str ? name->str : ""),
                  response);

      log_add (LOG_ERROR|LOG_URGENT, buf);
      if (status != NULL)
            status_item_emit_error_va (status, buf);
}

TaskStateEnum
nntp_authenticate_types (StatusItem     * status,
                         PanSocket      * sock,
                         gboolean         check_auth_types)
{
      TaskStateEnum val;
      int authtype = AUTH_UNKNOWN;
      int response_number = 0;
      const char * response = NULL;
      debug_enter ("nntp_authenticate");

      /* entry assertions */
      g_return_val_if_fail (status!=NULL, TASK_FAIL);
      g_return_val_if_fail (sock!=NULL, TASK_FAIL);
      g_return_val_if_fail (pan_socket_get_username(sock)!=NULL, TASK_FAIL);

      /* check for authentication schemes */
      /* see: http://www.mibsoftware.com/userkt/nntpext/0035.htm */
      if (check_auth_types)
      {
            val = nntp_command_va (status, sock, &response, &response_number, "AUTHINFO GENERIC");
            if (response_number == OK_AUTH)
            {
                  /* now we should be getting a list of valid authentication */
                  /* types terminated with a "." */
                  for (;;) {
                        val = nntp_get_response (sock, &response, &response_number);
                        if (val == TASK_OK && !pan_strcmp(response, "MSN\r\n"))
                              authtype = AUTH_MSN;
                        if (val != TASK_OK || !pan_strcmp(response, ".\r\n"))
                              break;
                  }
      
                  switch (authtype)
                  {
                        case AUTH_MSN:
                              return nntp_spa_authenticate (status, sock);
      
                        /* Unknown authentication type. We will fall through */
                        /* and try the normal authentication method anyway. */
                        case AUTH_UNKNOWN:
                        default:
                              status_item_emit_error_va (status, _(
                                    "No supported authentication mechanism"));
                  }
            }
      }

      /* set the username */
      val = nntp_command_va (status, sock, &response, &response_number, "AUTHINFO USER %s", pan_socket_get_username(sock)->str);

      /* On servers that require SPA authentication, sending */
      /* "AUTHINFO USER ..." seems to result in a 502 error. If we      */
      /* have not yet tried other authentication methods, do so now.    */
      /* Ideally, we would not need to do this, but since some news`    */
      /* servers disconnect the user if they send "AUTHINFO GENERIC",   */
      /* we need to first try simple authentication. */
      if (response_number==ERR_ACCESS && !check_auth_types)
            return nntp_authenticate_types (status, sock, TRUE);

      if (response_number!=OK_AUTH && response_number!=NEED_AUTHDATA) {
            log_handshake_error (status, sock, response);
            return val!=TASK_OK ? val : TASK_FAIL;
      }

      /* set the password, if required */
      if (response_number==NEED_AUTHDATA)
      {
            const PString * password = pan_socket_get_password (sock);
            if (!pstring_is_set (password)) {
                  log_handshake_error (status, sock, _("No password found!"));
                  return TASK_FAIL;
            }

            val = nntp_command_va (status, sock, &response, &response_number,
                                   "AUTHINFO PASS %*.*s",
                                   password->len, password->len, password->str);
      }

      /* if we failed, emit an error */   
      if (response_number != OK_AUTH)
      {
            log_handshake_error (status, sock, response);
            if (val == TASK_OK)
                  val = TASK_FAIL;
      }

      debug_exit ("nntp_authenticate");
      return val;
}

TaskStateEnum
nntp_authenticate (StatusItem     * status,
                   PanSocket      * sock)
{
      return nntp_authenticate_types (status, sock, FALSE);
}

TaskStateEnum
nntp_can_post (StatusItem   * status,
               PanSocket    * sock,
               gboolean     * setme_can_post)
{
      TaskStateEnum val;
      int response_number;
      const char * response = NULL;
      debug_enter ("nntp_can_post");

      /* entry assertions */
      g_return_val_if_fail (sock!=NULL, TASK_FAIL);
      g_return_val_if_fail (setme_can_post!=NULL, TASK_FAIL);

      /* ask the server if we can post or not */
      *setme_can_post = FALSE;
      val = nntp_command (status, sock, &response, &response_number, "MODE READER");

      if (val==TASK_OK)
      {
            *setme_can_post = response_number == 200;

            if (status!=NULL && response_number!=200 && response_number!=201)
                  log_add_va (LOG_ERROR, _("MODE READER check failed: %s"),  response);
      }

      debug_exit ("nntp_can_post");
      return val;
}

/**
***
**/

TaskStateEnum
nntp_set_group (StatusItem      * status,
                PanSocket       * sock,
                const PString   * group_name)
{
      TaskStateEnum val;
      int response_number = 0;
      const char * response = NULL;
      debug_enter ("nntp_set_group");

      /* sanity checks */
      g_return_val_if_fail (sock!=NULL, TASK_FAIL);
      g_return_val_if_fail (status!=NULL, TASK_FAIL);
      g_return_val_if_fail (pstring_is_set (group_name), TASK_FAIL);

      /* we're already there */
      if (pstring_equal (group_name, pan_socket_get_current_group(sock)))
            return TASK_OK;

      /* change to that group */
      val = nntp_command_va (status, sock, &response, &response_number,
                             "GROUP %*.*s",
                             group_name->len, group_name->len, group_name->str);
      if (response_number != OK_GROUP) {
            status_item_emit_error_va (status, _("Unable to set group \"%*.*s\": %s"),
                                       group_name->len, group_name->len, group_name->str,
                                       response);
            return val;
      }

      /* update this socket's current group */
      pan_socket_set_current_group (sock, group_name);
      return TASK_OK;
}

/**
***
**/

TaskStateEnum
nntp_post (StatusItem    * status,
           PanSocket     * sock,
           const gchar   * msg)
{
      TaskStateEnum val;
      int response_number;
      const char * response;
      debug_enter ("nntp_post");

      /* entry assertions */
      g_return_val_if_fail (sock!=NULL, TASK_FAIL);
      g_return_val_if_fail (status!=NULL, TASK_FAIL);
      g_return_val_if_fail (is_nonempty_string(msg), TASK_FAIL);

      /* if we're in mute mode, don't post */
      if (pan_mute)
      {
            fprintf (stderr, "Mute: Your Message will not actually be posted.");
            fprintf (stderr, "\n\n\nYour message\n%s<end of message>\n\n\n", msg);
            fflush (NULL);
      }
      else
      {
            /* tell the server we want to post */
            val = nntp_command (status, sock, &response, &response_number, "POST");
            if (val != TASK_OK)
                  return val;
            if (response_number != 340) {
                  status_item_emit_error_va (status, _("Posting failed.  Server said: %s"), response);
                  return TASK_FAIL;
            }

            /* post the article */
            val = nntp_command_va (status, sock, &response, &response_number, "%s\r\n.\r\n", msg);
            if (val != TASK_OK)
                  return val;
            if (response_number != 240) {
                  status_item_emit_error_va (status, "Posting failed.  Server said: %s", response);
                  return TASK_FAIL;
            }

            log_add_va (LOG_INFO, _("Posting complete.  Server said: %s"), response);
      }

      /* if we made it this far, we succeeded! */
      debug_exit ("nntp_post");
      return TASK_OK;
}



TaskStateEnum
nntp_noop (StatusItem    * status,
           PanSocket     * socket)
{
      const PString * server;
      gboolean can_post = FALSE;
      debug_enter ("nntp_noop");

      /* entry assertions */
      g_return_val_if_fail (status!=NULL, TASK_FAIL);
      g_return_val_if_fail (socket!=NULL, TASK_FAIL);

      /* do work */
      server = pan_socket_get_server_name (socket);
      status_item_emit_status_va (status, _("\"stay connected\" sent to %*.*s"),
                                  server->len, server->len, server->str);
      return nntp_can_post (status, socket, &can_post);
}

/**
***
**/

TaskStateEnum
nntp_article_download (StatusItem         * status,
                       PanSocket          * sock,
                       MessageIdentifier  * mid,
                       const gboolean     * abort,
                       int                  verbose)
{
      const PString * servername;
      const char * response;
      gboolean getting_headers;
      MessageSource * source;
      int response_number;
      int retval;
      TaskStateEnum val;
      GString * line;
      GString * setme;
      debug_enter ("nntp_article_download");

      /* sanity clause */
      g_return_val_if_fail (status!=NULL, TASK_FAIL);
      g_return_val_if_fail (sock!=NULL, TASK_FAIL);
      g_return_val_if_fail (message_identifier_is_valid(mid), TASK_FAIL);
      g_return_val_if_fail (abort!=NULL, TASK_FAIL);

      /* let the user know what we're doing */
      if (verbose & NNTP_VERBOSE_TEXT)
            status_item_emit_status (status, mid->message_id.str);

      /* change to the group we want */
      servername = pan_socket_get_server_name(sock);
      source = message_identifier_get_source_for_server (mid, servername);
      if (*abort || !source)
            return TASK_FAIL;
      val = nntp_set_group (status, sock, &source->group_name);
      if (val != TASK_OK)
            return val;

      /* request the article from the server */
      val = nntp_command_va (status, sock, &response, &response_number, "ARTICLE %lu", source->number);
      if (val != TASK_OK)
            return val;
      if (response_number != 220)
      {
            /* log the error */
            char * pch = g_strdup_printf (_("Getting article \"%s\" body failed: %s"), mid->readable_name, response);
            status_item_emit_error (status, pch);
            g_free (pch);

            /* mark the article as `error' */
            if (1) {
                  Server * server = serverlist_get_named_server (&source->server_name);
                  Group * group = server_get_named_group (server, &source->group_name);
                  if (group_ref_articles_if_loaded (group)) {
                        Article * a = group_get_article_by_message_id (group, &mid->message_id);
                        if (a != NULL)
                              article_set_error_flag (a, TRUE);
                        group_unref_articles (group, NULL);
                  }
            }

            return TASK_FAIL;
      }

      /* try to read the article... */
      getting_headers = TRUE;
      line = g_string_sized_new (80);

      setme = g_string_sized_new (80 * 1000);
      if (verbose & NNTP_VERBOSE_INIT_STEPS)
            status_item_emit_init_steps (status, mid->line_qty);

      for (;;)
      {
            const gchar * pch;

            /* is someone aborting this task? */
            if (*abort) {
                  retval = TASK_FAIL;
                  break;
            }

            /* read the next line */
            val = pan_socket_getline (sock, &response);
            if (val != TASK_OK) {
                  retval = val;
                  break;
            }

            /* does this actually ever happen? */
            if (!is_nonempty_string(response))
                  continue;

            /* end of the message */
            if (!strncmp(response, ".\r\n", 3)) {
                  retval = TASK_OK;
                  break;
            }

            /* strip out the \r */
            g_string_assign (line, response);
            while ((pch = strstr (line->str, "\r\n")))
                  g_string_erase (line, pch-line->str, 1);

            /* rfc 977: 2.4.1 */
            if (line->len>=2 && line->str[0]=='.' && line->str[1]=='.')
                  g_string_erase (line, 0, 1);

            /* save the line */
            g_string_append (setme, line->str);
            if (verbose & NNTP_VERBOSE_NEXT_STEP)
                  status_item_emit_next_step (status);


            /* save the headers that we want to save */
            if (getting_headers) {
                  if (line->len==1 && !strcmp(line->str, "\n")) /* header/body separator */
                        getting_headers = FALSE;
            }
      }

      if (verbose & NNTP_VERBOSE_NEXT_STEP)
            status_item_emit_next_step (status);

      /* save the message */
      if (retval == TASK_OK)
            acache_set_message (ACACHE_DEFAULT_KEY, &mid->message_id, setme->str, setme->len);

      g_string_free (line, TRUE);
      g_string_free (setme, TRUE);
      debug_exit ("nntp_article_download");
      return retval;
}


/**
***
**/

TaskStateEnum
nntp_get_group_info (StatusItem       * status,
                     PanSocket        * sock,
                     const PString    * group_name,
                     gulong           * article_qty,
                     gulong           * low_num,
                     gulong           * high_num,
                     const gboolean   * abort)
{
      TaskStateEnum val;
      int response_number;
      const char * response;
      debug_enter ("nntp_get_group_info");

      /* entry assertions */
      g_return_val_if_fail (status!=NULL, TASK_FAIL);
      g_return_val_if_fail (sock!=NULL, TASK_FAIL);
      g_return_val_if_fail (pstring_is_set (group_name), TASK_FAIL);
      g_return_val_if_fail (article_qty!=NULL, TASK_FAIL);
      g_return_val_if_fail (low_num!=NULL, TASK_FAIL);
      g_return_val_if_fail (high_num!=NULL, TASK_FAIL);
      g_return_val_if_fail (abort!=NULL, TASK_FAIL);

      /* change to that group */
      val = nntp_command_va (status, sock, &response, &response_number,
                             "GROUP %*.*s",
                             group_name->len, group_name->len, group_name->str);
      if (val != TASK_OK)
            return val;
      if (response_number != OK_GROUP) {
            status_item_emit_error_va (status, _("Unable to set group \"%*.*s\": %s"),
                                       group_name->len, group_name->len, group_name->str,
                                       response);
            return TASK_FAIL;
      }

      /* success; parse the results */
      sscanf (response, "%*d %lu %lu %lu", article_qty, low_num, high_num);
      pan_socket_set_current_group (sock, group_name);
      return TASK_OK;
}

/**
***
**/

TaskStateEnum
nntp_download_headers (StatusItem       * status,
                       PanSocket        * sock,
                       Group            * group,
                       gulong             low,
                       gulong             high,
                       const gboolean   * abort,
                       const char       * progress_fmt,
                       gulong           * setme_high_reached,
                       GPtrArray        * setme_articles)
{
      const char * buffer;
      const char * response;
      const char * charset;
      int response_number;
      TaskStateEnum val;
      gulong total;
      gulong first;
      gulong last;
      gulong high_reached = 0ul;
      GString * buf;
      debug_enter ("nntp_download_headers");

      /* sanity checks */
      g_return_val_if_fail (status!=NULL, TASK_FAIL);
      g_return_val_if_fail (sock!=NULL, TASK_FAIL);
      g_return_val_if_fail (group_is_valid(group), TASK_FAIL);
      g_return_val_if_fail (abort!=NULL, TASK_FAIL);
      g_return_val_if_fail (is_nonempty_string(progress_fmt), TASK_FAIL);
      g_return_val_if_fail (setme_high_reached!=NULL, TASK_FAIL);
      g_return_val_if_fail (setme_articles!=NULL, TASK_FAIL);

      /**
      ***  Get up-to-date information about this group
      **/

      total = first = last = 0;
      val = nntp_get_group_info (status, sock, &group->name, &total, &first, &last, abort);
      if (val != TASK_OK)
            return val;

      /**
      ***  If no articles to get, then we're already done
      **/

      if (total == 0) {
            const gchar * n = group_get_name (group);
            status_item_emit_status_va (status, _("No articles found for group \"%s\""), n);
            return TASK_OK;
      }

      /**
      ***  Tell the server that we want a series of article headers...
      **/

      if (*abort)
            return TASK_FAIL;
      val = nntp_command_va (status, sock, &response, &response_number, "XOVER %lu-%lu", low, high);
      if (val != TASK_OK)
            return val;
      if (response_number != 224) {
            status_item_emit_status_va (status, _("Getting header list failed: %s"), response);
            return TASK_FAIL;
      }

      /**
      ***  Walk through all the articles headers that the server spits back
      **/

      charset = group_get_default_charset (group);

      buf = g_string_new (NULL);
      pan_g_ptr_array_reserve (setme_articles, setme_articles->len + (high-low));
      for (;;)
      {
            int part = 0;
              int parts = 0;
            const char * pch;
            const char * s;
            Article * a;
            const char * march = NULL;
            PString tok = PSTRING_INIT;

            /* read the next line */
            if (*abort)
                  break;
            val = pan_socket_getline (sock, &buffer);
            if (val != TASK_OK)
                  break;

            /* handle end-of-list */
            if (!strncmp(buffer, ".\r\n", 3))
                  break;

            /* create the article data */
            a = article_new (group);
            a->is_new = TRUE;

            /* setup for parsing */
            g_string_assign (buf, buffer);
            march = buf->str;

            /* get article number */
            a->number = get_next_token_ulong (march, '\t', &march);
            high_reached = MAX (high_reached, a->number);

            /* get subject */
            if (get_next_token_pstring (march, '\t', &march, &tok)) {
                  tok = pstring_strstrip_shallow (&tok);
                  tok = pstring_shallow (pan_header_to_utf8 (tok.str, tok.len, charset), -1);
                  article_set_subject (a, &tok);
                  pstring_clear (&tok);
            }

            /* get author */
            if (get_next_token_pstring (march, '\t', &march, &tok)) {
                  tok = pstring_strstrip_shallow (&tok);
                  article_set_author (a, &tok);
            }

            /* get date */
            if (get_next_token_pstring (march, '\t', &march, &tok)) {
                  tok = pstring_strstrip_shallow (&tok);
                  a->date = pstring_is_set (&tok)
                        ? g_mime_utils_header_decode_date (tok.str, NULL)
                        : (time_t)0;
            }

            /* get message id */
            if (get_next_token_pstring (march, '\t', &march, &tok)) {
                  tok = pstring_strstrip_shallow (&tok);
                  if (pstring_is_set(&tok) && tok.str[0]=='<')
                        article_set_message_id (a, &tok);
            }

            /* get references */
            if (get_next_token_pstring (march, '\t', &march, &tok)) {
                  tok = pstring_strstrip_shallow (&tok);
                  if (pstring_is_set(&tok) && tok.str[0]=='<')
                        article_set_references (a, &tok);
            }

            /* get byte qty */
            a->byte_qty = get_next_token_ulong (march, '\t', &march);

            /* get line qty */
            a->linecount = get_next_token_int (march, '\t', &march);

            /* get crossref */
            if (get_next_token_pstring (march, '\t', &march, &tok)) {
                  pstring_strstrip_shallow (&tok);
                  if (tok.len>6 && !strncmp(tok.str,"Xref: ", 6)) {
                        PString header = pstring_substr_shallow (&tok, tok.str+6, NULL);
                        header = pstring_strstrip_shallow (&header); /* remove trailing linefeed */
                        article_set_xref (a, &header);
                  }
            }

            /**
            ***  Validity Checking
            **/

            if (!article_is_valid (a)) {
                  status_item_emit_error_va (status, 
                        _("Corrupt header skipped: %s"), buffer);
                  continue;
            }


            /* Look for the (n/N) or [n/N] construct in subject lines,
             * starting at the end of the string and working backwards */
            part = 0;
            parts = 0;
            s = a->subject.str;
            pch = s + a->subject.len - 1;
            while (pch != s)
            {
                  /* find the ']' of [n/N] */
                  --pch;
                  if ((pch[1]!=')' && pch[1]!=']') || !isdigit((guchar)*pch))
                        continue;

                  /* find the '/' of [n/N] */
                  while (s!=pch && isdigit((guchar)*pch))
                        --pch;
                  if (s==pch || (*pch!='/' && *pch!='|'))
                        continue;

                  /* N -> parts */
                  parts = atoi (pch+1);
                  --pch;

                  /* find the '[' of [n/N] */
                  while (s!=pch && isdigit((guchar)*pch))
                        --pch;
                  if (s==pch || (*pch!='(' && *pch!='[')) {
                        parts = 0;
                        continue;
                  }

                  /* n -> part */
                  part = atoi (pch+1);
                  break;
            }

            /* if not a multipart yet, AND if it's a big message, AND
               it's either in one of the pictures/fan/sex groups or it
               has commonly-used image names in the subject, guess it's
               a single-part binary */
            if (!parts
                  && a->linecount>400
                  && (((g_strstr_len (group->name.str, group->name.len, "binaries")
                              || g_strstr_len (group->name.str, group->name.len, "fan")
                              || g_strstr_len (group->name.str, group->name.len, "mag")
                              || g_strstr_len (group->name.str, group->name.len, "sex")))
                        || ((pan_strstr(s,".jpg") || pan_strstr(s,".JPG")
                              || pan_strstr(s,".jpeg") || pan_strstr(s,".JPEG")
                              || pan_strstr(s,".gif")  || pan_strstr(s,".GIF")
                              || pan_strstr(s,".tiff") || pan_strstr(s,".TIFF")))))
                  part = parts = 1;

            /* but if it's starting the subject with "Re:" and doesn't
               have many lines, it's probably a followup to a part, rather
               than an actual part. */
            if (a->subject.len>3 && !strncmp (a->subject.str, "Re:", 3) && a->linecount<100)
                  part = parts = 0;

            /* Subjects containing (0/N) aren't part of an N-part binary;
               they're text description articles that accompany the binary. */
            if (part == 0)
                  parts = 0;

            /* Verify Multipart info */
            if ((parts>=1) && (part<=parts)) {
                  a->parts = parts;
                  a->part = part;
            }
            else {
                  a->parts = 0;
                  a->part = 0;
            }

            /* Add the article to the article list */
            g_ptr_array_add (setme_articles, a);

            /* update the count & progress ui */
            status_item_emit_next_step (status);
            if (!(setme_articles->len % 100))
                  status_item_emit_status_va (status, progress_fmt, setme_articles->len, (high-low+1));
      }

      *setme_high_reached = high_reached;
      status_item_emit_status_va (status, progress_fmt, setme_articles->len, (high-low+1));
      g_string_free (buf, TRUE);

      return *abort ? TASK_FAIL : val;
}

/**
***
**/

TaskStateEnum
nntp_download_bodies (StatusItem                * status,
                      PanSocket                 * sock,
                      const gboolean            * abort,
                      MessageIdentifier        ** mids,
                      int                         mid_qty,
                      int                       * index,
                      gboolean                    abort_if_one_fails)
{
      int i;
      TaskStateEnum val = TASK_OK;
      debug_enter ("nntp_download_bodies");

      /* sanity checks pt 1 */
      g_return_val_if_fail (mids!=NULL, TASK_FAIL);
      g_return_val_if_fail (mid_qty>0, TASK_FAIL);
      g_return_val_if_fail (index!=NULL, TASK_FAIL);

      /**
      ***  If we have the bodies, then just unflag 'em and maybe check them out
      **/

      /* find the first message we don't have cached */
      for (i=*index; i<mid_qty; ++i)
            if (!acache_has_message (ACACHE_DEFAULT_KEY, &mids[i]->message_id))
                  break;

      /* are we done? */
      if (i==mid_qty)
            return TASK_OK;

      /* sanity checks pt 2 */
      g_return_val_if_fail (status!=NULL, TASK_FAIL);
      g_return_val_if_fail (sock!=NULL, TASK_FAIL);
      g_return_val_if_fail (abort!=NULL, TASK_FAIL);

      /**
      ***  Phooey, we have to download some of the bodies
      **/

      /* status item */
      if (1) {
            gulong line_qty = 0ul;
            gulong line_qty_so_far = 0ul;
            for (i=0; i<mid_qty; ++i) {
                  line_qty += mids[i]->line_qty;
                  if (i<*index)
                        line_qty_so_far += mids[i]->line_qty;
            }
            status_item_emit_init_steps (status, line_qty);
            status_item_emit_set_step (status, line_qty_so_far);
      }

      /* download the articles */
      for (; *index<mid_qty && !*abort; ++(*index))
      {
            MessageIdentifier * mid = mids[*index];

            if (mid_qty == 1)
                  status_item_emit_status_va (status, _("Getting \"%s\""),
                                                mid->readable_name);
            else
                  status_item_emit_status_va (status, _("Getting %d of %d"),
                                                1+*index, mid_qty);

            /* download if we have to. */
            if (acache_has_message (ACACHE_DEFAULT_KEY, &mid->message_id))
            {
                  val = TASK_OK;
                  status_item_emit_inc_step (status, mid->line_qty);
            }
            else
            {
                  val = nntp_article_download (status,
                                               sock,
                                               mid,
                                               abort,
                                               NNTP_VERBOSE_NEXT_STEP);

                  if (val!=TASK_OK && abort_if_one_fails)
                        return val;
            }
      }

      return val;
}


TaskStateEnum
nntp_cancel (StatusItem       * status,
             const PString    * message_id,
             PanSocket        * sock)
{
      const char * newsgroups;
      const char * author;
      TaskStateEnum val;
      GMimeMessage * message;
      debug_enter ("nntp_cancel");

      /* sanity checks */
      g_return_val_if_fail (sock != NULL, TASK_FAIL);
      g_return_val_if_fail (status != NULL, TASK_FAIL);
      g_return_val_if_fail (pstring_is_set (message_id), TASK_FAIL);
      g_return_val_if_fail (acache_has_message(ACACHE_DEFAULT_KEY, message_id), TASK_FAIL);

      /* get info */
      val = TASK_FAIL;
      message = acache_get_message (ACACHE_DEFAULT_KEY, &message_id, 1);
      newsgroups = g_mime_message_get_header (message, HEADER_NEWSGROUPS);
      author = g_mime_message_get_sender (message);
      if (is_nonempty_string(newsgroups) && is_nonempty_string(author))
      {
            GString * msg;

            /* let the user know what we're doing */
            status_item_emit_status_va (status, _("Canceling article"));

            /* build the message to post */
            msg = g_string_sized_new (1024);
            g_string_append_printf (msg, "From: %s\r\n", author);
            g_string_append_printf (msg, "Newsgroups: %s\r\n", newsgroups);
            g_string_append_printf (msg, "Subject: cancel %s\r\n", message_id->str);
            g_string_append_printf (msg, "Control: cancel %s\r\n", message_id->str);
            g_string_append (msg, "\r\n");
            g_string_append_printf (msg, "Ignore\r\nArticle canceled by Pan %s\r\n", VERSION);

            /* post the cancel message */
            val = nntp_post (status, sock, msg->str);

            /* cleanup */
            g_string_free (msg, TRUE);
      }

      /* cleanup */
      g_object_unref (message);
      debug_exit ("nntp_cancel");
      return val;
}

Generated by  Doxygen 1.6.0   Back to index