]> Pileus Git - ~andy/fetchmail/blobdiff - pop3.c
Credit John Beck's fixes.
[~andy/fetchmail] / pop3.c
diff --git a/pop3.c b/pop3.c
index 0570a3cc9c9902258e9e66defcf5690d69b6baec..5549dc9ac5a56ff41e3efbbb7bbb6a61e581cd99 100644 (file)
--- a/pop3.c
+++ b/pop3.c
 #include  "i18n.h"
 
 #ifdef OPIE_ENABLE
+#ifdef __cplusplus
+extern "C" {
+#endif
 #include <opie.h>
+#ifdef __cplusplus
+}
+#endif
 #endif /* OPIE_ENABLE */
 
 /* global variables: please reinitialize them explicitly for proper
@@ -35,6 +41,7 @@ static char lastok[POPBUFSIZE+1];
 #endif /* OPIE_ENABLE */
 
 /* session variables initialized in capa_probe() or pop3_getauth() */
+flag done_capa = FALSE;
 #if defined(GSSAPI)
 flag has_gssapi = FALSE;
 #endif /* defined(GSSAPI) */
@@ -45,8 +52,11 @@ static flag has_cram = FALSE;
 #ifdef OPIE_ENABLE
 flag has_otp = FALSE;
 #endif /* OPIE_ENABLE */
+#ifdef NTLM_ENABLE
+flag has_ntlm = FALSE;
+#endif /* NTLM_ENABLE */
 #ifdef SSL_ENABLE
-static flag has_ssl = FALSE;
+static flag has_stls = FALSE;
 #endif /* SSL_ENABLE */
 
 /* mailbox variables initialized in pop3_getrange() */
@@ -75,60 +85,17 @@ char *sdps_envto;
 static int do_pop3_ntlm(int sock, struct query *ctl,
        int msn_instead /** if true, send AUTH MSN, else send AUTH NTLM */)
 {
-    tSmbNtlmAuthRequest request;
-    tSmbNtlmAuthChallenge challenge;
-    tSmbNtlmAuthResponse response;
+    char msgbuf[POPBUFSIZE+1];
+    int result;
 
-    char msgbuf[2048];
-    int result,len;
-  
     gen_send(sock, msn_instead ? "AUTH MSN" : "AUTH NTLM");
 
-    if ((result = gen_recv(sock, msgbuf, sizeof msgbuf)))
-       return result;
-  
-    if (msgbuf[0] != '+')
-       return PS_AUTHFAIL;
-  
-    buildSmbNtlmAuthRequest(&request,ctl->remotename,NULL);
-
-    if (outlevel >= O_DEBUG)
-       dumpSmbNtlmAuthRequest(stdout, &request);
-
-    memset(msgbuf,0,sizeof msgbuf);
-    to64frombits (msgbuf, (unsigned char*)&request, SmbLength(&request));
-  
-    if (outlevel >= O_MONITOR)
-       report(stdout, "POP3> %s\n", msgbuf);
-  
-    strcat(msgbuf,"\r\n");
-    SockWrite (sock, msgbuf, strlen (msgbuf));
-
-    if ((gen_recv(sock, msgbuf, sizeof msgbuf)))
+    if ((result = ntlm_helper(sock, ctl, "POP3")))
        return result;
-  
-    len = from64tobits ((unsigned char*)&challenge, msgbuf, sizeof(msgbuf));
-    
-    if (outlevel >= O_DEBUG)
-       dumpSmbNtlmAuthChallenge(stdout, &challenge);
-    
-    buildSmbNtlmAuthResponse(&challenge, &response,ctl->remotename,ctl->password);
-  
-    if (outlevel >= O_DEBUG)
-       dumpSmbNtlmAuthResponse(stdout, &response);
-  
-    memset(msgbuf,0,sizeof msgbuf);
-    to64frombits (msgbuf, (unsigned char*)&response, SmbLength(&response));
-
-    if (outlevel >= O_MONITOR)
-       report(stdout, "POP3> %s\n", msgbuf);
-      
-    strcat(msgbuf,"\r\n");
-    SockWrite (sock, msgbuf, strlen (msgbuf));
-  
+
     if ((result = gen_recv (sock, msgbuf, sizeof msgbuf)))
        return result;
-  
+
     if (strstr (msgbuf, "OK"))
        return PS_SUCCESS;
     else
@@ -232,6 +199,9 @@ static int capa_probe(int sock)
 {
     int        ok;
 
+    if (done_capa) {
+       return PS_SUCCESS;
+    }
 #if defined(GSSAPI)
     has_gssapi = FALSE;
 #endif /* defined(GSSAPI) */
@@ -242,6 +212,9 @@ static int capa_probe(int sock)
 #ifdef OPIE_ENABLE
     has_otp = FALSE;
 #endif /* OPIE_ENABLE */
+#ifdef NTLM_ENABLE
+    has_ntlm = FALSE;
+#endif /* NTLM_ENABLE */
 
     ok = gen_transact(sock, "CAPA");
     if (ok == PS_SUCCESS)
@@ -253,26 +226,37 @@ static int capa_probe(int sock)
        {
            if (DOTLINE(buffer))
                break;
+
 #ifdef SSL_ENABLE
            if (strstr(buffer, "STLS"))
-               has_ssl = TRUE;
+               has_stls = TRUE;
 #endif /* SSL_ENABLE */
+
 #if defined(GSSAPI)
            if (strstr(buffer, "GSSAPI"))
                has_gssapi = TRUE;
 #endif /* defined(GSSAPI) */
+
 #if defined(KERBEROS_V4)
            if (strstr(buffer, "KERBEROS_V4"))
                has_kerberos = TRUE;
 #endif /* defined(KERBEROS_V4)  */
+
 #ifdef OPIE_ENABLE
            if (strstr(buffer, "X-OTP"))
                has_otp = TRUE;
 #endif /* OPIE_ENABLE */
+
+#ifdef NTLM_ENABLE
+           if (strstr(buffer, "NTLM"))
+               has_ntlm = TRUE;
+#endif /* NTLM_ENABLE */
+
            if (strstr(buffer, "CRAM-MD5"))
                has_cram = TRUE;
        }
     }
+    done_capa = TRUE;
     return(ok);
 }
 
@@ -296,9 +280,10 @@ static int pop3_getauth(int sock, struct query *ctl, char *greeting)
     char *challenge;
 #endif /* OPIE_ENABLE */
 #ifdef SSL_ENABLE
-    flag did_stls = FALSE;
+    flag connection_may_have_tls_errors = FALSE;
 #endif /* SSL_ENABLE */
 
+    done_capa = FALSE;
 #if defined(GSSAPI)
     has_gssapi = FALSE;
 #endif /* defined(GSSAPI) */
@@ -310,11 +295,17 @@ static int pop3_getauth(int sock, struct query *ctl, char *greeting)
     has_otp = FALSE;
 #endif /* OPIE_ENABLE */
 #ifdef SSL_ENABLE
-    has_ssl = FALSE;
+    has_stls = FALSE;
 #endif /* SSL_ENABLE */
 
     /* Set this up before authentication quits early. */
     set_peek_capable(ctl);
+
+    /* Hack: allow user to force RETR. */
+    if (peek_capable && getenv("FETCHMAIL_POP3_FORCE_RETR")) {
+       peek_capable = 0;
+    }
+
     /*
      * The "Maillennium POP3/PROXY server" deliberately truncates
      * TOP replies after c. 64 or 80 kByte (we have varying reports), so
@@ -331,9 +322,9 @@ static int pop3_getauth(int sock, struct query *ctl, char *greeting)
      *
      * Matthias Andree
      */
-    if (peek_capable && strstr(greeting, "Maillennium POP3/PROXY server")) {
+    if (peek_capable && strstr(greeting, "Maillennium POP3")) {
        if ((ctl->server.workarounds & WKA_TOP) == 0) {
-           report(stdout, GT_("Warning: \"Maillennium POP3/PROXY server\" found, using RETR command instead of TOP.\n"));
+           report(stdout, GT_("Warning: \"Maillennium POP3\" found, using RETR command instead of TOP.\n"));
            ctl->server.workarounds |= WKA_TOP;
        }
        peek_capable = 0;
@@ -352,22 +343,7 @@ static int pop3_getauth(int sock, struct query *ctl, char *greeting)
         ctl->server.sdps = TRUE;
 #endif /* SDPS_ENABLE */
 
-#ifdef NTLM_ENABLE
-    /* MSN servers require the use of NTLM (MSN) authentication */
-    if (!strcasecmp(ctl->server.pollname, "pop3.email.msn.com") ||
-           ctl->server.authenticate == A_MSN)
-       return (do_pop3_ntlm(sock, ctl, 1) == 0) ? PS_SUCCESS : PS_AUTHFAIL;
-    if (ctl->server.authenticate == A_NTLM)
-       return (do_pop3_ntlm(sock, ctl, 0) == 0) ? PS_SUCCESS : PS_AUTHFAIL;
-#else
-    if (ctl->server.authenticate == A_NTLM || ctl->server.authenticate == A_MSN)
-    {
-       report(stderr,
-          GT_("Required NTLM capability not compiled into fetchmail\n"));
-    }
-#endif
-
-    switch (ctl->server.protocol) {
+   switch (ctl->server.protocol) {
     case P_POP3:
 #ifdef RPA_ENABLE
        /* XXX FIXME: AUTH probing (RFC1734) should become global */
@@ -398,25 +374,29 @@ static int pop3_getauth(int sock, struct query *ctl, char *greeting)
 
        /*
         * CAPA command may return a list including available
-        * authentication mechanisms.  if it doesn't, no harm done, we
-        * just fall back to a plain login.  Note that this code 
-        * latches the server's authentication type, so that in daemon mode
-        * the CAPA check only needs to be done once at start of run.
+        * authentication mechanisms and STLS capability.
         *
-        * If CAPA fails, then force the authentication method to PASSORD
-        * and repoll immediately.
+        * If it doesn't, no harm done, we just fall back to a plain
+        * login -- if the user allows it.
         *
-        * These authentication methods are blessed by RFC1734,
-        * describing the POP3 AUTHentication command.
+        * Note that this code latches the server's authentication type,
+        * so that in daemon mode the CAPA check only needs to be done
+        * once at start of run.
+        *
+        * If CAPA fails, then force the authentication method to
+        * PASSWORD, switch off opportunistic and repoll immediately.
+        * If TLS is mandatory, fail up front.
         */
        if ((ctl->server.authenticate == A_ANY) ||
-           (ctl->server.authenticate == A_GSSAPI) ||
-           (ctl->server.authenticate == A_KERBEROS_V4) ||
-           (ctl->server.authenticate == A_OTP) ||
-           (ctl->server.authenticate == A_CRAM_MD5))
+               (ctl->server.authenticate == A_GSSAPI) ||
+               (ctl->server.authenticate == A_KERBEROS_V4) ||
+               (ctl->server.authenticate == A_KERBEROS_V5) ||
+               (ctl->server.authenticate == A_OTP) ||
+               (ctl->server.authenticate == A_CRAM_MD5) ||
+               maybe_tls(ctl))
        {
            if ((ok = capa_probe(sock)) != PS_SUCCESS)
-           /* we are in STAGE_GETAUTH! */
+               /* we are in STAGE_GETAUTH => failure is PS_AUTHFAIL! */
                if (ok == PS_AUTHFAIL ||
                    /* Some servers directly close the socket. However, if we
                     * have already authenticated before, then a previous CAPA
@@ -425,53 +405,97 @@ static int pop3_getauth(int sock, struct query *ctl, char *greeting)
                     */
                    (ok == PS_SOCKET && !ctl->wehaveauthed))
                {
-                   ctl->server.authenticate = A_PASSWORD;
-                   /* repoll immediately */
-                   ok = PS_REPOLL;
-                   break;
+#ifdef SSL_ENABLE
+                   if (must_tls(ctl)) {
+                       /* fail with mandatory STLS without repoll */
+                       report(stderr, GT_("TLS is mandatory for this session, but server refused CAPA command.\n"));
+                       report(stderr, GT_("The CAPA command is however necessary for TLS.\n"));
+                       return ok;
+                   } else if (maybe_tls(ctl)) {
+                       /* defeat opportunistic STLS */
+                       xfree(ctl->sslproto);
+                       ctl->sslproto = xstrdup("");
+                   }
+#endif
+                   /* If strong authentication was opportunistic, retry without, else fail. */
+                   switch (ctl->server.authenticate) {
+                       case A_ANY:
+                           ctl->server.authenticate = A_PASSWORD;
+                           /* FALLTHROUGH */
+                       case A_PASSWORD: /* this should only happen with TLS enabled */
+                           return PS_REPOLL;
+                       default:
+                           return PS_AUTHFAIL;
+                   }
                }
        }
 
 #ifdef SSL_ENABLE
-       if (has_ssl
-           && !ctl->use_ssl
-           && (!ctl->sslproto || !strcmp(ctl->sslproto,"tls1")))
-       {
-           char *realhost;
+       if (maybe_tls(ctl)) {
+           char *commonname;
 
-          realhost = ctl->server.via ? ctl->server.via : ctl->server.pollname;
-           ok = gen_transact(sock, "STLS");
+           commonname = ctl->server.pollname;
+           if (ctl->server.via)
+               commonname = ctl->server.via;
+           if (ctl->sslcommonname)
+               commonname = ctl->sslcommonname;
 
-           /* We use "tls1" instead of ctl->sslproto, as we want STLS,
-            * not other SSL protocols
-            */
-          if (ok == PS_SUCCESS &&
-              SSLOpen(sock,ctl->sslcert,ctl->sslkey,"tls1",ctl->sslcertck, ctl->sslcertpath,ctl->sslfingerprint,realhost,ctl->server.pollname) == -1)
+          if (has_stls
+                  || must_tls(ctl)) /* if TLS is mandatory, ignore capabilities */
           {
-              if (!ctl->sslproto && !ctl->wehaveauthed)
+              /* Use "tls1" rather than ctl->sslproto because tls1 is the only
+               * protocol that will work with STARTTLS.  Don't need to worry
+               * whether TLS is mandatory or opportunistic unless SSLOpen() fails
+               * (see below). */
+              if (gen_transact(sock, "STLS") == PS_SUCCESS
+                      && (set_timeout(mytimeout), SSLOpen(sock, ctl->sslcert, ctl->sslkey, "tls1", ctl->sslcertck,
+                          ctl->sslcertfile, ctl->sslcertpath, ctl->sslfingerprint, commonname,
+                          ctl->server.pollname, &ctl->remotename)) != -1)
               {
-                  ctl->sslproto = xstrdup("");
-                  /* repoll immediately */
-                  return(PS_REPOLL);
+                  /*
+                   * RFC 2595 says this:
+                   *
+                   * "Once TLS has been started, the client MUST discard cached
+                   * information about server capabilities and SHOULD re-issue the
+                   * CAPABILITY command.  This is necessary to protect against
+                   * man-in-the-middle attacks which alter the capabilities list prior
+                   * to STARTTLS.  The server MAY advertise different capabilities
+                   * after STARTTLS."
+                   *
+                   * Now that we're confident in our TLS connection we can
+                   * guarantee a secure capability re-probe.
+                   */
+                  set_timeout(0);
+                  done_capa = FALSE;
+                  ok = capa_probe(sock);
+                  if (ok != PS_SUCCESS) {
+                      return ok;
+                  }
+                  if (outlevel >= O_VERBOSE)
+                  {
+                      report(stdout, GT_("%s: upgrade to TLS succeeded.\n"), commonname);
+                  }
+              } else if (must_tls(ctl)) {
+                  /* Config required TLS but we couldn't guarantee it, so we must
+                   * stop. */
+                  set_timeout(0);
+                  report(stderr, GT_("%s: upgrade to TLS failed.\n"), commonname);
+                  return PS_SOCKET;
+              } else {
+                  /* We don't know whether the connection is usable, and there's
+                   * no command we can reasonably issue to test it (NOOP isn't
+                   * allowed til post-authentication), so leave it in an unknown
+                   * state, mark it as such, and check more carefully if things
+                   * go wrong when we try to authenticate. */
+                  set_timeout(0);
+                  connection_may_have_tls_errors = TRUE;
+                  if (outlevel >= O_VERBOSE)
+                  {
+                      report(stdout, GT_("%s: opportunistic upgrade to TLS failed, trying to continue.\n"), commonname);
+                  }
               }
-              report(stderr,
-                      GT_("SSL connection failed.\n"));
-               return PS_SOCKET;
-           }
-          did_stls = TRUE;
-
-          /*
-           * RFC 2595 says this:
-           *
-           * "Once TLS has been started, the client MUST discard cached
-           * information about server capabilities and SHOULD re-issue the
-           * CAPABILITY command.  This is necessary to protect against
-           * man-in-the-middle attacks which alter the capabilities list prior
-           * to STARTTLS.  The server MAY advertise different capabilities
-           * after STARTTLS."
-           */
-          capa_probe(sock);
-       }
+          }
+       } /* maybe_tls() */
 #endif /* SSL_ENABLE */
 
        /*
@@ -497,7 +521,8 @@ static int pop3_getauth(int sock, struct query *ctl, char *greeting)
 #if defined(GSSAPI)
        if (has_gssapi &&
            (ctl->server.authenticate == A_GSSAPI ||
-            ctl->server.authenticate == A_ANY))
+           (ctl->server.authenticate == A_ANY
+            && check_gss_creds("pop", ctl->server.truename) == PS_SUCCESS)))
        {
            ok = do_gssauth(sock,"AUTH","pop",ctl->server.truename,ctl->remotename);
            if (ok == PS_SUCCESS || ctl->server.authenticate != A_ANY)
@@ -516,7 +541,25 @@ static int pop3_getauth(int sock, struct query *ctl, char *greeting)
        }
 #endif /* OPIE_ENABLE */
 
-       if (ctl->server.authenticate == A_CRAM_MD5 || 
+#ifdef NTLM_ENABLE
+    /* MSN servers require the use of NTLM (MSN) authentication */
+    if (!strcasecmp(ctl->server.pollname, "pop3.email.msn.com") ||
+           ctl->server.authenticate == A_MSN)
+       return (do_pop3_ntlm(sock, ctl, 1) == 0) ? PS_SUCCESS : PS_AUTHFAIL;
+    if (ctl->server.authenticate == A_NTLM || (has_ntlm && ctl->server.authenticate == A_ANY)) {
+       ok = do_pop3_ntlm(sock, ctl, 0);
+        if (ok == 0 || ctl->server.authenticate != A_ANY)
+           break;
+    }
+#else
+    if (ctl->server.authenticate == A_NTLM || ctl->server.authenticate == A_MSN)
+    {
+       report(stderr,
+          GT_("Required NTLM capability not compiled into fetchmail\n"));
+    }
+#endif
+
+       if (ctl->server.authenticate == A_CRAM_MD5 || 
            (has_cram && ctl->server.authenticate == A_ANY))
        {
            ok = do_cram_md5(sock, "AUTH", ctl, NULL);
@@ -533,8 +576,10 @@ static int pop3_getauth(int sock, struct query *ctl, char *greeting)
        if ((challenge = strstr(lastok, "otp-"))) {
          char response[OPIE_RESPONSE_MAX+1];
          int i;
+         char *n = xstrdup("");
 
-         i = opiegenerator(challenge, !strcmp(ctl->password, "opie") ? "" : ctl->password, response);
+         i = opiegenerator(challenge, !strcmp(ctl->password, "opie") ? n : ctl->password, response);
+         free(n);
          if ((i == -2) && !run.poll_interval) {
            char secret[OPIE_SECRET_MAX+1];
            fprintf(stderr, GT_("Secret pass phrase: "));
@@ -553,19 +598,31 @@ static int pop3_getauth(int sock, struct query *ctl, char *greeting)
        }
 #endif /* OPIE_ENABLE */
 
-       strlcpy(shroud, ctl->password, sizeof(shroud));
-       ok = gen_transact(sock, "PASS %s", ctl->password);
-       shroud[0] = '\0';
-#ifdef SSL_ENABLE
-       /* this is for servers which claim to support TLS, but actually
-        * don't! */
-       if (did_stls && ok == PS_SOCKET && !ctl->sslproto && !ctl->wehaveauthed)
+       /* KPOP uses out-of-band authentication and does not check what
+        * we send here, so send some random fixed string, to avoid
+        * users switching *to* KPOP accidentally revealing their
+        * password */
+       if ((ctl->server.authenticate == A_ANY
+                   || ctl->server.authenticate == A_KERBEROS_V4
+                   || ctl->server.authenticate == A_KERBEROS_V5)
+               && (ctl->server.service != NULL
+                   && strcmp(ctl->server.service, KPOP_PORT) == 0))
        {
-           ctl->sslproto = xstrdup("");
-           /* repoll immediately */
-           ok = PS_REPOLL;
+           ok = gen_transact(sock, "PASS krb_ticket");
+           break;
        }
-#endif
+
+       /* check if we are actually allowed to send the password */
+       if (ctl->server.authenticate == A_ANY
+               || ctl->server.authenticate == A_PASSWORD) {
+           strlcpy(shroud, ctl->password, sizeof(shroud));
+           ok = gen_transact(sock, "PASS %s", ctl->password);
+       } else {
+           report(stderr, GT_("We've run out of allowed authenticators and cannot continue.\n"));
+           ok = PS_AUTHFAIL;
+       }
+       memset(shroud, 0x55, sizeof(shroud));
+       shroud[0] = '\0';
        break;
 
     case P_APOP:
@@ -590,19 +647,37 @@ static int pop3_getauth(int sock, struct query *ctl, char *greeting)
        else
            *++end = '\0';
 
+       /* SECURITY: 2007-03-17
+        * Strictly validating the presented challenge for RFC-822
+        * conformity (it must be a msg-id in terms of that standard) is
+        * supposed to make attacks against the MD5 implementation
+        * harder[1]
+        *
+        * [1] "Security vulnerability in APOP authentication",
+        *     GaĆ«tan Leurent, fetchmail-devel, 2007-03-17 */
+       if (!rfc822_valid_msgid((unsigned char *)start)) {
+           report(stderr,
+                   GT_("Invalid APOP timestamp.\n"));
+           return PS_AUTHFAIL;
+       }
+
        /* copy timestamp and password into digestion buffer */
-       msg = xmalloc((end-start+1) + strlen(ctl->password) + 1);
+       msg = (char *)xmalloc((end-start+1) + strlen(ctl->password) + 1);
        strcpy(msg,start);
        strcat(msg,ctl->password);
-       strcpy(ctl->digest, MD5Digest((unsigned char *)msg));
+       strcpy((char *)ctl->digest, MD5Digest((unsigned char *)msg));
        free(msg);
 
-       ok = gen_transact(sock, "APOP %s %s", ctl->remotename, ctl->digest);
+       ok = gen_transact(sock, "APOP %s %s", ctl->remotename, (char *)ctl->digest);
        break;
 
     case P_RPOP:
-       if ((ok = gen_transact(sock,"USER %s", ctl->remotename)) == 0)
+       if ((ok = gen_transact(sock,"USER %s", ctl->remotename)) == 0) {
+           strlcpy(shroud, ctl->password, sizeof(shroud));
            ok = gen_transact(sock, "RPOP %s", ctl->password);
+           memset(shroud, 0x55, sizeof(shroud));
+           shroud[0] = '\0';
+       }
        break;
 
     default:
@@ -610,6 +685,19 @@ static int pop3_getauth(int sock, struct query *ctl, char *greeting)
        ok = PS_ERROR;
     }
 
+#ifdef SSL_ENABLE
+    /* this is for servers which claim to support TLS, but actually
+     * don't! */
+    if (connection_may_have_tls_errors
+                   && (ok == PS_SOCKET || ok == PS_PROTOCOL))
+    {
+       xfree(ctl->sslproto);
+       ctl->sslproto = xstrdup("");
+       /* repoll immediately without TLS */
+       ok = PS_REPOLL;
+    }
+#endif
+
     if (ok != 0)
     {
        /* maybe we detected a lock-busy condition? */
@@ -645,16 +733,23 @@ static void trim(char *s) {
     s[0] = '\0';
 }
 
+/* XXX FIXME: using the Message-ID is unsafe, some messages (spam,
+ * broken messages) do not have Message-ID headers, and messages without
+ * those appear to break this code and cause fetchmail (at least version
+ * 6.2.3) to not delete such messages properly after retrieval.
+ * See Sourceforge Bug #780933.
+ *
+ * The other problem is that the TOP command itself is optional, too... */
 static int pop3_gettopid(int sock, int num , char *id, size_t idsize)
 {
     int ok;
     int got_it;
     char buf [POPBUFSIZE+1];
     snprintf(buf, sizeof(buf), "TOP %d 1", num);
-    if ((ok = gen_transact(sock, buf )) != 0)
+    if ((ok = gen_transact(sock, "%s", buf)) != 0)
        return ok;
     got_it = 0;
-    while ((ok = gen_recv(sock, buf, sizeof(buf))) == 0) 
+    while (gen_recv(sock, buf, sizeof(buf)) == 0)
     {
        if (DOTLINE(buf))
            break;
@@ -666,6 +761,8 @@ static int pop3_gettopid(int sock, int num , char *id, size_t idsize)
            trim(id);
        }
     }
+    /* XXX FIXME: do not return success here if no Message-ID header was
+     * found. */
     return 0;
 }
 
@@ -706,7 +803,7 @@ static int pop3_getuidl(int sock, int num, char *id /** output */, size_t idsize
        return(ok);
     if ((ok = parseuid(buf, &gotnum, id, idsize)))
        return ok;
-    if (gotnum != num) {
+    if (gotnum != (unsigned long)num) {
        report(stderr, GT_("Server responded with UID for wrong message.\n"));
        return PS_PROTOCOL;
     }
@@ -718,6 +815,7 @@ static int pop3_fastuidl( int sock,  struct query *ctl, unsigned int count, int
     int ok;
     unsigned int first_nr, last_nr, try_nr;
     char id [IDLEN+1];
+    struct idlist *savep = NULL; /** pointer to cache save_str result, speeds up saves */
 
     first_nr = 0;
     last_nr = count + 1;
@@ -734,7 +832,7 @@ static int pop3_fastuidl( int sock,  struct query *ctl, unsigned int count, int
            if (mark == UID_DELETED || mark == UID_EXPUNGED)
            {
                if (outlevel >= O_VERBOSE)
-                   report(stderr, GT_("id=%s (num=%d) was deleted, but is still present!\n"), id, try_nr);
+                   report(stderr, GT_("id=%s (num=%u) was deleted, but is still present!\n"), id, try_nr);
                /* just mark it as seen now! */
                newl->val.status.mark = mark = UID_SEEN;
            }
@@ -759,8 +857,8 @@ static int pop3_fastuidl( int sock,  struct query *ctl, unsigned int count, int
            last_nr = try_nr;
 
            /* save it */
-           newl = save_str(&ctl->oldsaved, id, UID_UNSEEN);
-           newl->val.status.num = try_nr;
+           savep = save_str(savep ? &savep : &ctl->oldsaved, id, UID_UNSEEN);
+           savep->val.status.num = try_nr;
        }
     }
     if (outlevel >= O_DEBUG && last_nr <= count)
@@ -774,6 +872,10 @@ static int pop3_fastuidl( int sock,  struct query *ctl, unsigned int count, int
 
 static int pop3_slowuidl( int sock,  struct query *ctl, int *countp, int *newp)
 {
+    /* XXX FIXME: this code is severely broken. A Cc:d mailing list
+     * message will arrive twice with the same Message-ID, so this
+     * slowuidl code will break. Same goes for messages without
+     * Message-ID headers at all. This code would best be removed. */
     /* This approach tries to get the message headers from the
      * remote hosts and compares the message-id to the already known
      * ones:
@@ -869,6 +971,7 @@ static int pop3_getrange(int sock,
     int ok;
     char buf [POPBUFSIZE+1];
 
+    (void)folder;
     /* Ensure that the new list is properly empty */
     ctl->newsaved = (struct idlist *)NULL;
 
@@ -882,14 +985,18 @@ static int pop3_getrange(int sock,
     /* get the total message count */
     gen_send(sock, "STAT");
     ok = pop3_ok(sock, buf);
-    if (ok == 0)
-       sscanf(buf,"%d %d", countp, bytes);
-    else
+    if (ok == 0) {
+       int asgn;
+
+       asgn = sscanf(buf,"%d %d", countp, bytes);
+       if (asgn != 2)
+               return PS_PROTOCOL;
+    } else
        return(ok);
 
     /*
-     * Newer, RFC-1725/1939-conformant POP servers may not have the LAST command.
-     * We work as hard as possible to hide this, but it makes
+     * Newer, RFC-1725/1939-conformant POP servers may not have the LAST
+     * command.  We work as hard as possible to hide this, but it makes
      * counting new messages intrinsically quadratic in the worst case.
      */
     last = 0;
@@ -938,10 +1045,10 @@ static int pop3_getrange(int sock,
            if (dofastuidl)
                return(pop3_fastuidl( sock, ctl, *countp, newp));
            /* grab the mailbox's UID list */
-           if ((ok = gen_transact(sock, "UIDL")) != 0)
+           if (gen_transact(sock, "UIDL") != 0)
            {
                /* don't worry, yet! do it the slow way */
-               if ((ok = pop3_slowuidl(sock, ctl, countp, newp)))
+               if (pop3_slowuidl(sock, ctl, countp, newp))
                {
                    report(stderr, GT_("protocol error while fetching UIDLs\n"));
                    return(PS_ERROR);
@@ -951,18 +1058,19 @@ static int pop3_getrange(int sock,
            {
                /* UIDL worked - parse reply */
                unsigned long unum;
+               struct idlist *newl = NULL;
 
                *newp = 0;
-               while ((ok = gen_recv(sock, buf, sizeof(buf))) == PS_SUCCESS)
+               while (gen_recv(sock, buf, sizeof(buf)) == PS_SUCCESS)
                {
                    if (DOTLINE(buf))
                        break;
 
                    if (parseuid(buf, &unum, id, sizeof(id)) == PS_SUCCESS)
                    {
-                       struct idlist   *old, *newl;
+                       struct idlist   *old;
 
-                       newl = save_str(&ctl->newsaved, id, UID_UNSEEN);
+                       newl = save_str(newl ? &newl : &ctl->newsaved, id, UID_UNSEEN);
                        newl->val.status.num = unum;
 
                        if ((old = str_in_list(&ctl->oldsaved, id, FALSE)))
@@ -997,8 +1105,9 @@ static int pop3_getrange(int sock,
                             * the same mail will not be downloaded again.
                             */
                            old = save_str(&ctl->oldsaved, id, UID_UNSEEN);
-                           old->val.status.num = unum;
                        }
+                       /* save the number */
+                       old->val.status.num = unum;
                    } else
                        return PS_ERROR;
                } /* multi-line loop for UIDL reply */
@@ -1012,15 +1121,15 @@ static int pop3_getrange(int sock,
 static int pop3_getpartialsizes(int sock, int first, int last, int *sizes)
 /* capture the size of message #first */
 {
-    int        ok = 0, i;
+    int        ok = 0, i, num;
     char buf [POPBUFSIZE+1];
-    unsigned int num, size;
+    unsigned int size;
 
     for (i = first; i <= last; i++) {
        gen_send(sock, "LIST %d", i);
        if ((ok = pop3_ok(sock, buf)) != 0)
            return(ok);
-       if (sscanf(buf, "%u %u", &num, &size) == 2) {
+       if (sscanf(buf, "%d %u", &num, &size) == 2) {
            if (num == i)
                sizes[i - first] = size;
            else
@@ -1052,7 +1161,7 @@ static int pop3_getsizes(int sock, int count, int *sizes)
            if (DOTLINE(buf))
                break;
            else if (sscanf(buf, "%u %u", &num, &size) == 2) {
-               if (num > 0 && num <= count)
+               if (num > 0 && num <= (unsigned)count)
                    sizes[num - 1] = size;
                else
                    /* warn about possible attempt to induce buffer overrun */
@@ -1161,18 +1270,20 @@ static int pop3_fetch(int sock, struct query *ctl, int number, int *lenp)
                 * as a workaround. */
                if (strspn(buf, " \t") == strlen(buf))
                    strcpy(buf, "<>");
-               sdps_envfrom = xmalloc(strlen(buf)+1);
+               sdps_envfrom = (char *)xmalloc(strlen(buf)+1);
                strcpy(sdps_envfrom,buf);
                break;
            case 5:
                 /* Wrap address with To: <> so nxtaddr() likes it */
-                sdps_envto = xmalloc(strlen(buf)+7);
+                sdps_envto = (char *)xmalloc(strlen(buf)+7);
                 sprintf(sdps_envto,"To: <%s>",buf);
                break;
             }
        } while
            (!(buf[0] == '.' && (buf[1] == '\r' || buf[1] == '\n' || buf[1] == '\0')));
     }
+#else
+    (void)ctl;
 #endif /* SDPS_ENABLE */
 
     /*
@@ -1247,6 +1358,7 @@ static int pop3_delete(int sock, struct query *ctl, int number)
 static int pop3_mark_seen(int sock, struct query *ctl, int number)
 /* mark a given message as seen */
 {
+    (void)sock;
     mark_uid_seen(ctl, number);
     return(PS_SUCCESS);
 }
@@ -1284,8 +1396,8 @@ static int pop3_logout(int sock, struct query *ctl)
 static const struct method pop3 =
 {
     "POP3",            /* Post Office Protocol v3 */
-    "pop3",            /* standard POP3 port */
-    "pop3s",           /* ssl POP3 port */
+    "pop3",            /* port for plain and TLS POP3 */
+    "pop3s",           /* port for SSL POP3 */
     FALSE,             /* this is not a tagged protocol */
     TRUE,              /* this uses a message delimiter */
     pop3_ok,           /* parse command response */