]> 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 3acc592360455a778251e21064b08ed2642be172..5549dc9ac5a56ff41e3efbbb7bbb6a61e581cd99 100644 (file)
--- a/pop3.c
+++ b/pop3.c
@@ -52,6 +52,9 @@ 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_stls = FALSE;
 #endif /* SSL_ENABLE */
@@ -82,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, &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 (&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, &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
@@ -252,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)
@@ -263,22 +226,32 @@ static int capa_probe(int sock)
        {
            if (DOTLINE(buffer))
                break;
+
 #ifdef SSL_ENABLE
            if (strstr(buffer, "STLS"))
                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;
        }
@@ -307,9 +280,7 @@ static int pop3_getauth(int sock, struct query *ctl, char *greeting)
     char *challenge;
 #endif /* OPIE_ENABLE */
 #ifdef SSL_ENABLE
-    char *realhost = ctl->server.via ? ctl->server.via : ctl->server.pollname;
     flag connection_may_have_tls_errors = FALSE;
-    flag got_tls = FALSE;
 #endif /* SSL_ENABLE */
 
     done_capa = FALSE;
@@ -329,6 +300,12 @@ static int pop3_getauth(int sock, struct query *ctl, char *greeting)
 
     /* 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
@@ -345,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;
@@ -366,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 */
@@ -449,7 +411,7 @@ static int pop3_getauth(int sock, struct query *ctl, char *greeting)
                        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 {
+                   } else if (maybe_tls(ctl)) {
                        /* defeat opportunistic STLS */
                        xfree(ctl->sslproto);
                        ctl->sslproto = xstrdup("");
@@ -470,16 +432,25 @@ static int pop3_getauth(int sock, struct query *ctl, char *greeting)
 
 #ifdef SSL_ENABLE
        if (maybe_tls(ctl)) {
-          if (has_stls)
+           char *commonname;
+
+           commonname = ctl->server.pollname;
+           if (ctl->server.via)
+               commonname = ctl->server.via;
+           if (ctl->sslcommonname)
+               commonname = ctl->sslcommonname;
+
+          if (has_stls
+                  || must_tls(ctl)) /* if TLS is mandatory, ignore capabilities */
           {
               /* 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
-                      && SSLOpen(sock, ctl->sslcert, ctl->sslkey, "tls1", ctl->sslcertck,
-                          ctl->sslcertpath, ctl->sslfingerprint, realhost,
-                          ctl->server.pollname, &ctl->remotename) != -1)
+                      && (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)
               {
                   /*
                    * RFC 2595 says this:
@@ -494,7 +465,7 @@ static int pop3_getauth(int sock, struct query *ctl, char *greeting)
                    * Now that we're confident in our TLS connection we can
                    * guarantee a secure capability re-probe.
                    */
-                  got_tls = TRUE;
+                  set_timeout(0);
                   done_capa = FALSE;
                   ok = capa_probe(sock);
                   if (ok != PS_SUCCESS) {
@@ -502,16 +473,13 @@ static int pop3_getauth(int sock, struct query *ctl, char *greeting)
                   }
                   if (outlevel >= O_VERBOSE)
                   {
-                      report(stdout, GT_("%s: upgrade to TLS succeeded.\n"), realhost);
+                      report(stdout, GT_("%s: upgrade to TLS succeeded.\n"), commonname);
                   }
-              }
-          }
-
-          if (!got_tls) {
-              if (must_tls(ctl)) {
+              } else if (must_tls(ctl)) {
                   /* Config required TLS but we couldn't guarantee it, so we must
                    * stop. */
-                  report(stderr, GT_("%s: upgrade to TLS failed.\n"), realhost);
+                  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
@@ -519,14 +487,15 @@ static int pop3_getauth(int sock, struct query *ctl, char *greeting)
                    * 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"), realhost);
+                      report(stdout, GT_("%s: opportunistic upgrade to TLS failed, trying to continue.\n"), commonname);
                   }
               }
           }
-       }
+       } /* maybe_tls() */
 #endif /* SSL_ENABLE */
 
        /*
@@ -552,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)
@@ -571,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);
@@ -610,6 +598,20 @@ static int pop3_getauth(int sock, struct query *ctl, char *greeting)
        }
 #endif /* OPIE_ENABLE */
 
+       /* 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))
+       {
+           ok = gen_transact(sock, "PASS krb_ticket");
+           break;
+       }
+
        /* check if we are actually allowed to send the password */
        if (ctl->server.authenticate == A_ANY
                || ctl->server.authenticate == A_PASSWORD) {
@@ -621,17 +623,6 @@ static int pop3_getauth(int sock, struct query *ctl, char *greeting)
        }
        memset(shroud, 0x55, sizeof(shroud));
        shroud[0] = '\0';
-#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)
-       {
-           xfree(ctl->sslproto);
-           ctl->sslproto = xstrdup("");
-           /* repoll immediately without TLS */
-           ok = PS_REPOLL;
-       }
-#endif
        break;
 
     case P_APOP:
@@ -656,14 +647,28 @@ 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:
@@ -680,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? */
@@ -728,10 +746,10 @@ static int pop3_gettopid(int sock, int num , char *id, size_t idsize)
     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;
@@ -797,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;
@@ -813,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;
            }
@@ -838,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)
@@ -966,9 +985,13 @@ 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);
 
     /*
@@ -1022,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);
@@ -1035,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)))
@@ -1246,12 +1270,12 @@ 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;
             }