/* pop.c: client routines for talking to a POP3-protocol post-office server
- Copyright (c) 1991, 1993, 1996, 1997 Free Software Foundation, Inc.
+ Copyright (C) 1991, 1993, 1996, 1997, 1999, 2001, 2002, 2003, 2004,
+ 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
Written by Jonathan Kamens, jik@security.ov.com.
This file is part of GNU Emacs.
GNU Emacs 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, or (at your option)
+the Free Software Foundation; either version 3, or (at your option)
any later version.
GNU Emacs is distributed in the hope that it will be useful,
You should have received a copy of the GNU General Public License
along with GNU Emacs; see the file COPYING. If not, write to
-the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-Boston, MA 02111-1307, USA. */
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+Boston, MA 02110-1301, USA. */
#ifdef HAVE_CONFIG_H
-#define NO_SHORTNAMES /* Tell config not to load remap.h */
-#include <../src/config.h>
+#include <config.h>
#else
#define MAIL_USE_POP
#endif
#ifdef MAIL_USE_POP
-#ifdef HAVE_CONFIG_H
-/* Cancel these substitutions made in config.h */
-#undef open
-#undef read
-#undef write
-#undef close
-#endif
-
#include <sys/types.h>
#ifdef WINDOWSNT
#include "ntlib.h"
#include <string.h>
#define index strchr
#endif
-#ifdef STDC_HEADERS
-#include <stdlib.h>
-#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
# ifdef HAVE_KRB5_H
# include <krb5.h>
# endif
-# ifdef HAVE_DES_H
-# include <des.h>
-# else
-# ifdef HAVE_KERBEROSIV_DES_H
-# include <kerberosIV/des.h>
-# else
-# ifdef HAVE_KERBEROS_DES_H
-# include <kerberos/des.h>
-# endif
-# endif
-# endif
# ifdef HAVE_KRB_H
# include <krb.h>
# else
#endif
#endif
-static int socket_connection (/* char *, int */);
-static char *getline (/* popserver */);
-static int sendline (/* popserver, char * */);
-static int fullwrite (/* int, char *, int */);
-static int getok (/* popserver */);
+#ifndef __P
+# ifdef __STDC__
+# define __P(a) a
+# else
+# define __P(a) ()
+# endif /* __STDC__ */
+#endif /* ! __P */
+
+static int socket_connection __P((char *, int));
+static int pop_getline __P((popserver, char **));
+static int sendline __P((popserver, char *));
+static int fullwrite __P((int, char *, int));
+static int getok __P((popserver));
#if 0
-static int gettermination (/* popserver */);
+static int gettermination __P((popserver));
#endif
-static void pop_trash (/* popserver */);
-static char *find_crlf (/* char * */);
+static void pop_trash __P((popserver));
+static char *find_crlf __P((char *, int));
-#define ERROR_MAX 80 /* a pretty arbitrary size */
+#define ERROR_MAX 160 /* a pretty arbitrary size, but needs
+ to be bigger than the original
+ value of 80 */
#define POP_PORT 110
#define KPOP_PORT 1109
-#ifdef WINDOWSNT
#define POP_SERVICE "pop3" /* we don't want the POP2 port! */
-#else
-#define POP_SERVICE "pop"
-#endif
#ifdef KERBEROS
-#define KPOP_SERVICE "kpop"
+#define KPOP_SERVICE "kpop" /* never used: look for 20060515 to see why */
#endif
char pop_error[ERROR_MAX];
#else
#define DONT_NEED_PASSWORD 0
#endif
-
+
if ((! password) && (! DONT_NEED_PASSWORD))
{
if (! (flags & POP_NO_GETPASS))
return (0);
}
}
- if (password)
+ if (password) /* always true, detected 20060515 */
flags |= POP_NO_KERBEROS;
else
- password = username;
+ password = username; /* dead code, detected 20060515 */
+ /** "kpop" service is never used: look for 20060515 to see why **/
sock = socket_connection (host, flags);
if (sock == -1)
free ((char *) server);
return (0);
}
-
+
server->file = sock;
server->data = 0;
server->buffer_index = 0;
int *size;
{
char *fromserver;
+ char *end_ptr;
if (server->in_multi)
{
strcpy (pop_error, "In multi-line query in pop_stat");
return (-1);
}
-
- if (sendline (server, "STAT") || (! (fromserver = getline (server))))
+
+ if (sendline (server, "STAT") || (pop_getline (server, &fromserver) < 0))
return (-1);
if (strncmp (fromserver, "+OK ", 4))
return (-1);
}
- *count = atoi (&fromserver[4]);
-
- fromserver = index (&fromserver[4], ' ');
- if (! fromserver)
+ errno = 0;
+ *count = strtol (&fromserver[4], &end_ptr, 10);
+ /* Check validity of string-to-integer conversion. */
+ if (fromserver + 4 == end_ptr || *end_ptr != ' ' || errno)
{
- strcpy (pop_error,
- "Badly formatted response from server in pop_stat");
+ strcpy (pop_error, "Unexpected response from POP server in pop_stat");
pop_trash (server);
return (-1);
}
- *size = atoi (fromserver + 1);
+ fromserver = end_ptr;
+
+ errno = 0;
+ *size = strtol (fromserver + 1, &end_ptr, 10);
+ if (fromserver + 1 == end_ptr || errno)
+ {
+ strcpy (pop_error, "Unexpected response from POP server in pop_stat");
+ pop_trash (server);
+ return (-1);
+ }
return (0);
}
free ((char *) *sizes);
return (-1);
}
- if (! (fromserver = getline (server)))
+ if (pop_getline (server, &fromserver) < 0)
{
free ((char *) *IDs);
free ((char *) *sizes);
}
for (i = 0; i < how_many; i++)
{
- if (pop_multi_next (server, &fromserver))
+ if (pop_multi_next (server, &fromserver) <= 0)
{
free ((char *) *IDs);
free ((char *) *sizes);
}
(*sizes)[i] = atoi (fromserver);
}
- if (pop_multi_next (server, &fromserver))
+ if (pop_multi_next (server, &fromserver) < 0)
{
free ((char *) *IDs);
free ((char *) *sizes);
* markfrom
* If true, then mark the string "From " at the beginning
* of lines with '>'.
- *
- * Return value: A string pointing to the message, if successful, or
- * null with pop_error set if not.
+ * msg_buf Output parameter to which a buffer containing the
+ * message is assigned.
+ *
+ * Return value: The number of bytes in msg_buf, which may contain
+ * embedded nulls, not including its final null, or -1 on error
+ * with pop_error set.
*
* Side effects: May kill connection on error.
*/
-char *
-pop_retrieve (server, message, markfrom)
+int
+pop_retrieve (server, message, markfrom, msg_buf)
popserver server;
int message;
int markfrom;
+ char **msg_buf;
{
int *IDs, *sizes, bufsize, fromcount = 0, cp = 0;
char *ptr, *fromserver;
if (server->in_multi)
{
strcpy (pop_error, "In multi-line query in pop_retrieve");
- return (0);
+ return (-1);
}
if (pop_list (server, message, &IDs, &sizes))
- return (0);
+ return (-1);
if (pop_retrieve_first (server, message, &fromserver))
{
- return (0);
+ return (-1);
}
/*
{
strcpy (pop_error, "Out of memory in pop_retrieve");
pop_retrieve_flush (server);
- return (0);
+ return (-1);
}
- while (! (ret = pop_retrieve_next (server, &fromserver)))
+ while ((ret = pop_retrieve_next (server, &fromserver)) >= 0)
{
- int linesize;
-
if (! fromserver)
{
ptr[cp] = '\0';
- return (ptr);
+ *msg_buf = ptr;
+ return (cp);
}
if (markfrom && fromserver[0] == 'F' && fromserver[1] == 'r' &&
fromserver[2] == 'o' && fromserver[3] == 'm' &&
{
strcpy (pop_error, "Out of memory in pop_retrieve");
pop_retrieve_flush (server);
- return (0);
+ return (-1);
}
fromcount = 0;
}
ptr[cp++] = '>';
}
- linesize = strlen (fromserver);
- bcopy (fromserver, &ptr[cp], linesize);
- cp += linesize;
+ bcopy (fromserver, &ptr[cp], ret);
+ cp += ret;
ptr[cp++] = '\n';
}
- if (ret)
- {
- free (ptr);
- return (0);
- }
-}
+ free (ptr);
+ return (-1);
+}
int
pop_retrieve_first (server, message, response)
return (pop_multi_first (server, pop_error, response));
}
+/*
+ Returns a negative number on error, 0 to indicate that the data has
+ all been read (i.e., the server has returned a "." termination
+ line), or a positive number indicating the number of bytes in the
+ returned buffer (which is null-terminated and may contain embedded
+ nulls, but the returned bytecount doesn't include the final null).
+ */
+
int
pop_retrieve_next (server, line)
popserver server;
return (pop_multi_first (server, pop_error, response));
}
+/*
+ Returns a negative number on error, 0 to indicate that the data has
+ all been read (i.e., the server has returned a "." termination
+ line), or a positive number indicating the number of bytes in the
+ returned buffer (which is null-terminated and may contain embedded
+ nulls, but the returned bytecount doesn't include the final null).
+ */
+
int
pop_top_next (server, line)
popserver server;
return (-1);
}
- if (sendline (server, command) || (! (*response = getline (server))))
+ if (sendline (server, command) || (pop_getline (server, response) < 0))
{
return (-1);
}
}
}
+/*
+ Read the next line of data from SERVER and place a pointer to it
+ into LINE. Return -1 on error, 0 if there are no more lines to read
+ (i.e., the server has returned a line containing only "."), or a
+ positive number indicating the number of bytes in the LINE buffer
+ (not including the final null). The data in that buffer may contain
+ embedded nulls, but does not contain the final CRLF. When returning
+ 0, LINE is set to null. */
+
int
pop_multi_next (server, line)
popserver server;
char **line;
{
char *fromserver;
+ int ret;
if (! server->in_multi)
{
return (-1);
}
- fromserver = getline (server);
- if (! fromserver)
+ if ((ret = pop_getline (server, &fromserver)) < 0)
{
return (-1);
}
else
{
*line = fromserver + 1;
- return (0);
+ return (ret - 1);
}
}
else
{
*line = fromserver;
- return (0);
+ return (ret);
}
}
popserver server;
{
char *line;
+ int ret;
if (! server->in_multi)
{
return (0);
}
- while (! pop_multi_next (server, &line))
+ while ((ret = pop_multi_next (server, &line)))
{
- if (! line)
- {
- return (0);
- }
+ if (ret < 0)
+ return (-1);
}
- return (-1);
+ return (0);
}
/* Function: pop_delete
popserver server;
{
char *fromserver;
-
+
if (server->in_multi)
{
strcpy (pop_error, "In multi-line query in pop_last");
if (sendline (server, "LAST"))
return (-1);
- if (! (fromserver = getline (server)))
+ if (pop_getline (server, &fromserver) < 0)
return (-1);
if (! strncmp (fromserver, "-ERR", 4))
}
else
{
- return (atoi (&fromserver[4]));
+ char *end_ptr;
+ int count;
+ errno = 0;
+ count = strtol (&fromserver[4], &end_ptr, 10);
+ if (fromserver + 4 == end_ptr || errno)
+ {
+ strcpy (pop_error, "Unexpected response from server in pop_last");
+ pop_trash (server);
+ return (-1);
+ }
+ return count;
}
}
* Arguments:
* host The host to which to connect.
* flags Option flags.
- *
+ *
* Return value: A file descriptor indicating the connection, or -1
* indicating failure, in which case an error has been copied
* into pop_error.
char *host;
int flags;
{
+#ifdef HAVE_GETADDRINFO
+ struct addrinfo *res, *it;
+ struct addrinfo hints;
+ int ret;
+#else /* !HAVE_GETADDRINFO */
struct hostent *hostent;
+#endif
struct servent *servent;
struct sockaddr_in addr;
char found_port = 0;
char *service;
int sock;
+ char *realhost;
#ifdef KERBEROS
#ifdef KERBEROS5
krb5_error_code rem;
CREDENTIALS cred;
Key_schedule schedule;
int rem;
- char *realhost;
#endif /* KERBEROS5 */
#endif /* KERBEROS */
int try_count = 0;
+ int connect_ok;
#ifdef WINDOWSNT
{
}
#endif
- do
- {
- hostent = gethostbyname (host);
- try_count++;
- if ((! hostent) && ((h_errno != TRY_AGAIN) || (try_count == 5)))
- {
- strcpy (pop_error, "Could not determine POP server's address");
- return (-1);
- }
- } while (! hostent);
-
bzero ((char *) &addr, sizeof (addr));
addr.sin_family = AF_INET;
+ /** "kpop" service is never used: look for 20060515 to see why **/
#ifdef KERBEROS
service = (flags & POP_NO_KERBEROS) ? POP_SERVICE : KPOP_SERVICE;
#else
}
else
{
+ /** "kpop" service is never used: look for 20060515 to see why **/
#ifdef KERBEROS
addr.sin_port = htons ((flags & POP_NO_KERBEROS) ?
POP_PORT : KPOP_PORT);
strncat (pop_error, strerror (errno),
ERROR_MAX - sizeof (POP_SOCKET_ERROR));
return (-1);
-
+
}
+#ifdef HAVE_GETADDRINFO
+ memset (&hints, 0, sizeof(hints));
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_CANONNAME;
+ hints.ai_family = AF_INET;
+ do
+ {
+ ret = getaddrinfo (host, service, &hints, &res);
+ try_count++;
+ if (ret != 0 && (ret != EAI_AGAIN || try_count == 5))
+ {
+ strcpy (pop_error, "Could not determine POP server's address");
+ return (-1);
+ }
+ } while (ret != 0);
+
+ if (ret == 0)
+ {
+ it = res;
+ while (it)
+ {
+ if (it->ai_addrlen == sizeof (addr))
+ {
+ struct sockaddr_in *in_a = (struct sockaddr_in *) it->ai_addr;
+ bcopy (&in_a->sin_addr, (char *) &addr.sin_addr,
+ sizeof (addr.sin_addr));
+ if (! connect (sock, (struct sockaddr *) &addr, sizeof (addr)))
+ break;
+ }
+ it = it->ai_next;
+ }
+ connect_ok = it != NULL;
+ if (connect_ok)
+ {
+ realhost = alloca (strlen (it->ai_canonname) + 1);
+ strcpy (realhost, it->ai_canonname);
+ }
+ freeaddrinfo (res);
+ }
+#else /* !HAVE_GETADDRINFO */
+ do
+ {
+ hostent = gethostbyname (host);
+ try_count++;
+ if ((! hostent) && ((h_errno != TRY_AGAIN) || (try_count == 5)))
+ {
+ strcpy (pop_error, "Could not determine POP server's address");
+ return (-1);
+ }
+ } while (! hostent);
+
while (*hostent->h_addr_list)
{
bcopy (*hostent->h_addr_list, (char *) &addr.sin_addr,
break;
hostent->h_addr_list++;
}
+ connect_ok = *hostent->h_addr_list != NULL;
+ if (! connect_ok)
+ {
+ realhost = alloca (strlen (hostent->h_name) + 1);
+ strcpy (realhost, hostent->h_name);
+ }
+
+#endif /* !HAVE_GETADDRINFO */
#define CONNECT_ERROR "Could not connect to POP server: "
-
- if (! *hostent->h_addr_list)
+
+ if (! connect_ok)
{
CLOSESOCKET (sock);
strcpy (pop_error, CONNECT_ERROR);
strncat (pop_error, strerror (errno),
ERROR_MAX - sizeof (CONNECT_ERROR));
return (-1);
-
+
}
#ifdef KERBEROS
+
#define KRB_ERROR "Kerberos error connecting to POP server: "
if (! (flags & POP_NO_KERBEROS))
{
if ((rem = krb5_auth_con_init (kcontext, &auth_context)))
goto krb5error;
-
+
if (rem = krb5_cc_default (kcontext, &ccdef))
goto krb5error;
if (rem = krb5_cc_get_principal (kcontext, ccdef, &client))
goto krb5error;
- for (cp = hostent->h_name; *cp; cp++)
+ for (cp = realhost; *cp; cp++)
{
if (isupper (*cp))
{
}
}
- if (rem = krb5_sname_to_principal (kcontext, hostent->h_name,
+ if (rem = krb5_sname_to_principal (kcontext, realhost,
POP_SERVICE, FALSE, &server))
goto krb5error;
CLOSESOCKET (sock);
return (-1);
}
-#else /* ! KERBEROS5 */
+#else /* ! KERBEROS5 */
ticket = (KTEXT) malloc (sizeof (KTEXT_ST));
- realhost = strdup (hostent->h_name);
rem = krb_sendauth (0L, sock, ticket, "pop", realhost,
(char *) krb_realmofhost (realhost),
(unsigned long) 0, &msg_data, &cred, schedule,
(struct sockaddr_in *) 0,
"KPOPV0.1");
free ((char *) ticket);
- free (realhost);
if (rem != KSUCCESS)
{
strcpy (pop_error, KRB_ERROR);
} /* socket_connection */
/*
- * Function: getline
+ * Function: pop_getline
*
* Purpose: Get a line of text from the connection and return a
* pointer to it. The carriage return and linefeed at the end of
* Arguments:
* server The server from which to get the line of text.
*
- * Returns: A non-null pointer if successful, or a null pointer on any
- * error, with an error message copied into pop_error.
+ * Returns: The number of characters in the line, which is returned in
+ * LINE, not including the final null. A return value of 0
+ * indicates a blank line. A negative return value indicates an
+ * error (in which case the contents of LINE are undefined. In
+ * case of error, an error message is copied into pop_error.
*
- * Notes: The line returned is overwritten with each call to getline.
+ * Notes: The line returned is overwritten with each call to pop_getline.
*
* Side effects: Closes the connection on error.
+ *
+ * THE RETURNED LINE MAY CONTAIN EMBEDDED NULLS!
*/
-static char *
-getline (server)
+static int
+pop_getline (server, line)
popserver server;
+ char **line;
{
#define GETLINE_ERROR "Error reading from server: "
if (server->data)
{
- char *cp = find_crlf (server->buffer + server->buffer_index);
+ char *cp = find_crlf (server->buffer + server->buffer_index,
+ server->data);
if (cp)
{
int found;
found = server->buffer_index;
data_used = (cp + 2) - server->buffer - found;
-
+
*cp = '\0'; /* terminate the string to be returned */
server->data -= data_used;
server->buffer_index += data_used;
if (pop_debug)
+ /* Embedded nulls will truncate this output prematurely,
+ but that's OK because it's just for debugging anyway. */
fprintf (stderr, "<<< %s\n", server->buffer + found);
- return (server->buffer + found);
+ *line = server->buffer + found;
+ return (data_used - 2);
}
else
{
server->buffer = (char *)realloc (server->buffer, server->buffer_size);
if (! server->buffer)
{
- strcpy (pop_error, "Out of memory in getline");
+ strcpy (pop_error, "Out of memory in pop_getline");
pop_trash (server);
- return (0);
+ return (-1);
}
}
ret = RECV (server->file, server->buffer + server->data,
strncat (pop_error, strerror (errno),
ERROR_MAX - sizeof (GETLINE_ERROR));
pop_trash (server);
- return (0);
+ return (-1);
}
else if (ret == 0)
{
- strcpy (pop_error, "Unexpected EOF from server in getline");
+ strcpy (pop_error, "Unexpected EOF from server in pop_getline");
pop_trash (server);
- return (0);
+ return (-1);
}
else
{
char *cp;
server->data += ret;
server->buffer[server->data] = '\0';
-
- cp = find_crlf (server->buffer + search_offset);
+
+ cp = find_crlf (server->buffer + search_offset,
+ server->data - search_offset);
if (cp)
{
int data_used = (cp + 2) - server->buffer;
if (pop_debug)
fprintf (stderr, "<<< %s\n", server->buffer);
- return (server->buffer);
+ *line = server->buffer;
+ return (data_used - 2);
}
/* As above, the "- 1" here is to account for the fact that
we may have read a CR without its accompanying LF. */
{
#define SENDLINE_ERROR "Error writing to POP server: "
int ret;
-
- ret = fullwrite (server->file, line, strlen (line));
- if (ret >= 0)
- { /* 0 indicates that a blank line was written */
- ret = fullwrite (server->file, "\r\n", 2);
- }
+ char *buf;
+
+ /* Combine the string and the CR-LF into one buffer. Otherwise, two
+ reasonable network stack optimizations, Nagle's algorithm and
+ delayed acks, combine to delay us a fraction of a second on every
+ message we send. (Movemail writes line without \r\n, client
+ kernel sends packet, server kernel delays the ack to see if it
+ can combine it with data, movemail writes \r\n, client kernel
+ waits because it has unacked data already in its outgoing queue,
+ client kernel eventually times out and sends.)
+
+ This can be something like 0.2s per command, which can add up
+ over a few dozen messages, and is a big chunk of the time we
+ spend fetching mail from a server close by. */
+ buf = alloca (strlen (line) + 3);
+ strcpy (buf, line);
+ strcat (buf, "\r\n");
+ ret = fullwrite (server->file, buf, strlen (buf));
if (ret < 0)
{
int nbytes;
{
char *cp;
- int ret;
+ int ret = 0;
cp = buf;
- while ((ret = SEND (fd, cp, nbytes, 0)) > 0)
+ while (nbytes && ((ret = SEND (fd, cp, nbytes, 0)) > 0))
{
cp += ret;
nbytes -= ret;
*
* Arguments:
* server The server to read from.
- *
+ *
* Returns: 0 for success, else for failure and puts error in pop_error.
*
* Side effects: On failure, may make the connection unusable.
{
char *fromline;
- if (! (fromline = getline (server)))
+ if (pop_getline (server, &fromline) < 0)
{
return (-1);
}
pop_trash (server);
return (-1);
}
-}
+}
#if 0
/*
{
char *fromserver;
- fromserver = getline (server);
- if (! fromserver)
+ if (pop_getline (server, &fromserver) < 0)
return (-1);
if (strcmp (fromserver, "."))
* Changes made to the maildrop since the session was started (or
* since the last pop_reset) may be lost.
*/
-void
+void
pop_close (server)
popserver server;
{
* Function: pop_trash
*
* Purpose: Like pop_close or pop_quit, but doesn't deallocate the
- * memory associated with the server. It is legal to call
+ * memory associated with the server. It is valid to call
* pop_close or pop_quit after this function has been called.
*/
static void
#endif
}
-/* Return a pointer to the first CRLF in IN_STRING,
- or 0 if it does not contain one. */
+/* Return a pointer to the first CRLF in IN_STRING, which can contain
+ embedded nulls and has LEN characters in it not including the final
+ null, or 0 if it does not contain one. */
static char *
-find_crlf (in_string)
+find_crlf (in_string, len)
char *in_string;
+ int len;
{
- while (1)
+ while (len--)
{
- if (! *in_string)
- return (0);
- else if (*in_string == '\r')
+ if (*in_string == '\r')
{
if (*++in_string == '\n')
return (in_string - 1);
else
in_string++;
}
- /* NOTREACHED */
+ return (0);
}
#endif /* MAIL_USE_POP */
+
+/* arch-tag: ceb37041-b7ad-49a8-a63d-286618b8367d
+ (do not change this comment) */