/* * Function: pop_stat * * Purpose: Issue the STAT command to the server and return (in the * value parameters) the number of messages in the maildrop and * the total size of the maildrop. * * Return value: 0 on success, or non-zero with an error in pop_error * in failure. * * Side effects: On failure, may make further operations on the * connection impossible. */ int pop_stat (popserver server, int *count, 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") || (pop_getline (server, &fromserver) < 0)) return (-1); if (strncmp (fromserver, "+OK ", 4)) { if (0 == strncmp (fromserver, "-ERR", 4)) { strncpy (pop_error, fromserver, ERROR_MAX); } else { strcpy (pop_error, "Unexpected response from POP server in pop_stat"); pop_trash (server); } return (-1); } 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, "Unexpected response from POP server in pop_stat"); pop_trash (server); return (-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); }
/* * Procedure getok * * Purpose: Reads a line from the server. If the return indicator is * positive, return with a zero exit status. If not, return with * a negative exit status. * * 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. */ static int getok (popserver server) { char *fromline; if (pop_getline (server, &fromline) < 0) { return (-1); } if (! strncmp (fromline, "+OK", 3)) return (0); else if (! strncmp (fromline, "-ERR", 4)) { snprintf (pop_error, ERROR_MAX, "%s", fromline); return (-1); } else { strcpy (pop_error, "Unexpected response from server; expecting +OK or -ERR"); pop_trash (server); return (-1); } }
/* * Function: sendline * * Purpose: Sends a line of text to the POP server. The line of text * passed into this function should NOT have the carriage return * and linefeed on the end of it. Periods at beginnings of lines * will NOT be treated specially by this function. * * Arguments: * server The server to which to send the text. * line The line of text to send. * * Return value: Upon successful completion, a value of 0 will be * returned. Otherwise, a non-zero value will be returned, and * an error will be copied into pop_error. * * Side effects: Closes the connection on error. */ static int sendline (popserver server, const char *line) { #define SENDLINE_ERROR "Error writing to POP server: " int ret; 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 (stpcpy (buf, line), "\r\n"); ret = fullwrite (server->file, buf, strlen (buf)); if (ret < 0) { pop_trash (server); snprintf (pop_error, ERROR_MAX, "%s%s", SENDLINE_ERROR, strerror (errno)); return (ret); } if (pop_debug) fprintf (stderr, ">>> %s\n", line); return (0); }
/* * Function: pop_last * * Purpose: Find out the highest seen message from the server. * * Arguments: * server The server. * * Return value: If successful, the highest seen message, which is * greater than or equal to 0. Otherwise, a negative number with * the error explained in pop_error. * * Side effects: Closes the connection on error. */ int pop_last (popserver server) { char *fromserver; if (server->in_multi) { strcpy (pop_error, "In multi-line query in pop_last"); return (-1); } if (sendline (server, "LAST")) return (-1); if (pop_getline (server, &fromserver) < 0) return (-1); if (! strncmp (fromserver, "-ERR", 4)) { snprintf (pop_error, ERROR_MAX, "%s", fromserver); return (-1); } else if (strncmp (fromserver, "+OK ", 4)) { strcpy (pop_error, "Unexpected response from server in pop_last"); pop_trash (server); return (-1); } else { 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; } }
/* * Function: pop_list * * Purpose: Performs the POP "list" command and returns (in value * parameters) two malloc'd zero-terminated arrays -- one of * message IDs, and a parallel one of sizes. * * Arguments: * server The pop connection to talk to. * message The number of the one message about which to get * information, or 0 to get information about all * messages. * * Return value: 0 on success, non-zero with error in pop_error on * failure. * * Side effects: On failure, may make further operations on the * connection impossible. */ int pop_list (popserver server, int message, int **IDs, int **sizes) { int how_many, i; char *fromserver; if (server->in_multi) { strcpy (pop_error, "In multi-line query in pop_list"); return (-1); } if (message) how_many = 1; else { int count, size; if (pop_stat (server, &count, &size)) return (-1); how_many = count; } *IDs = (int *) malloc ((how_many + 1) * sizeof (int)); *sizes = (int *) malloc ((how_many + 1) * sizeof (int)); if (! (*IDs && *sizes)) { strcpy (pop_error, "Out of memory in pop_list"); return (-1); } if (message) { sprintf (pop_error, "LIST %d", message); if (sendline (server, pop_error)) { free ((char *) *IDs); free ((char *) *sizes); return (-1); } if (pop_getline (server, &fromserver) < 0) { free ((char *) *IDs); free ((char *) *sizes); return (-1); } if (strncmp (fromserver, "+OK ", 4)) { if (! strncmp (fromserver, "-ERR", 4)) snprintf (pop_error, ERROR_MAX, "%s", fromserver); else { strcpy (pop_error, "Unexpected response from server in pop_list"); pop_trash (server); } free ((char *) *IDs); free ((char *) *sizes); return (-1); } (*IDs)[0] = atoi (&fromserver[4]); fromserver = strchr (&fromserver[4], ' '); if (! fromserver) { strcpy (pop_error, "Badly formatted response from server in pop_list"); pop_trash (server); free ((char *) *IDs); free ((char *) *sizes); return (-1); } (*sizes)[0] = atoi (fromserver); (*IDs)[1] = (*sizes)[1] = 0; return (0); } else { if (pop_multi_first (server, "LIST", &fromserver)) { free ((char *) *IDs); free ((char *) *sizes); return (-1); } for (i = 0; i < how_many; i++) { if (pop_multi_next (server, &fromserver) <= 0) { free ((char *) *IDs); free ((char *) *sizes); return (-1); } (*IDs)[i] = atoi (fromserver); fromserver = strchr (fromserver, ' '); if (! fromserver) { strcpy (pop_error, "Badly formatted response from server in pop_list"); free ((char *) *IDs); free ((char *) *sizes); pop_trash (server); return (-1); } (*sizes)[i] = atoi (fromserver); } if (pop_multi_next (server, &fromserver) < 0) { free ((char *) *IDs); free ((char *) *sizes); return (-1); } else if (fromserver) { strcpy (pop_error, "Too many response lines from server in pop_list"); free ((char *) *IDs); free ((char *) *sizes); return (-1); } (*IDs)[i] = (*sizes)[i] = 0; return (0); } }
/* * 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 * the line are stripped, but periods at the beginnings of lines * are NOT dealt with in any special way. * * Arguments: * server The server from which to get the line of text. * * 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 pop_getline. * * Side effects: Closes the connection on error. * * THE RETURNED LINE MAY CONTAIN EMBEDDED NULLS! */ static int pop_getline (popserver server, char **line) { #define GETLINE_ERROR "Error reading from server: " int ret; int search_offset = 0; if (server->data) { char *cp = find_crlf (server->buffer + server->buffer_index, server->data); if (cp) { int found; int data_used; 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); *line = server->buffer + found; return (data_used - 2); } else { memmove (server->buffer, server->buffer + server->buffer_index, server->data); /* Record the fact that we've searched the data already in the buffer for a CRLF, so that when we search below, we don't have to search the same data twice. There's a "- 1" here to account for the fact that the last character of the data we have may be the CR of a CRLF pair, of which we haven't read the second half yet, so we may have to search it again when we read more data. */ search_offset = server->data - 1; server->buffer_index = 0; } } else { server->buffer_index = 0; } while (1) { /* There's a "- 1" here to leave room for the null that we put at the end of the read data below. We put the null there so that find_crlf knows where to stop when we call it. */ if (server->data == server->buffer_size - 1) { server->buffer_size += GETLINE_INCR; server->buffer = (char *)realloc (server->buffer, server->buffer_size); if (! server->buffer) { strcpy (pop_error, "Out of memory in pop_getline"); pop_trash (server); return (-1); } } ret = RECV (server->file, server->buffer + server->data, server->buffer_size - server->data - 1, 0); if (ret < 0) { snprintf (pop_error, ERROR_MAX, "%s%s", GETLINE_ERROR, strerror (errno)); pop_trash (server); return (-1); } else if (ret == 0) { strcpy (pop_error, "Unexpected EOF from server in pop_getline"); pop_trash (server); return (-1); } else { char *cp; server->data += ret; server->buffer[server->data] = '\0'; cp = find_crlf (server->buffer + search_offset, server->data - search_offset); if (cp) { int data_used = (cp + 2) - server->buffer; *cp = '\0'; server->data -= data_used; server->buffer_index = data_used; if (pop_debug) fprintf (stderr, "<<< %s\n", 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. */ search_offset += ret - 1; } } /* NOTREACHED */ }