1 /**
2  * @file
3  * POP authentication
4  *
5  * @authors
6  * Copyright (C) 2000-2001 Vsevolod Volkov <vvv@mutt.org.ua>
7  * Copyright (C) 2018 Richard Russon <rich@flatcap.org>
8  *
9  * @copyright
10  * This program is free software: you can redistribute it and/or modify it under
11  * the terms of the GNU General Public License as published by the Free Software
12  * Foundation, either version 2 of the License, or (at your option) any later
13  * version.
14  *
15  * This program is distributed in the hope that it will be useful, but WITHOUT
16  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License along with
21  * this program.  If not, see <http://www.gnu.org/licenses/>.
22  */
23 
24 /**
25  * @page pop_auth POP authentication
26  *
27  * POP authentication
28  */
29 
30 #include "config.h"
31 #include <stdbool.h>
32 #include <stdio.h>
33 #include <string.h>
34 #include "private.h"
35 #include "mutt/lib.h"
36 #include "address/lib.h"
37 #include "config/lib.h"
38 #include "core/lib.h"
39 #include "conn/lib.h"
40 #include "adata.h"
41 #include "mutt_socket.h"
42 #ifdef USE_SASL
43 #include <sasl/sasl.h>
44 #include <sasl/saslutil.h>
45 #endif
46 
47 #ifdef USE_SASL
48 /**
49  * pop_auth_sasl - POP SASL authenticator - Implements PopAuth::authenticate()
50  */
pop_auth_sasl(struct PopAccountData * adata,const char * method)51 static enum PopAuthRes pop_auth_sasl(struct PopAccountData *adata, const char *method)
52 {
53   sasl_conn_t *saslconn = NULL;
54   sasl_interact_t *interaction = NULL;
55   int rc;
56   char inbuf[1024];
57   const char *mech = NULL;
58   const char *pc = NULL;
59   unsigned int len = 0, olen = 0;
60 
61   if (mutt_account_getpass(&adata->conn->account) || !adata->conn->account.pass[0])
62     return POP_A_FAILURE;
63 
64   if (mutt_sasl_client_new(adata->conn, &saslconn) < 0)
65   {
66     mutt_debug(LL_DEBUG1, "Error allocating SASL connection\n");
67     return POP_A_FAILURE;
68   }
69 
70   if (!method)
71     method = adata->auth_list.data;
72 
73   while (true)
74   {
75     rc = sasl_client_start(saslconn, method, &interaction, &pc, &olen, &mech);
76     if (rc != SASL_INTERACT)
77       break;
78     mutt_sasl_interact(interaction);
79   }
80 
81   if ((rc != SASL_OK) && (rc != SASL_CONTINUE))
82   {
83     mutt_debug(
84         LL_DEBUG1,
85         "Failure starting authentication exchange. No shared mechanisms?\n");
86 
87     /* SASL doesn't support suggested mechanisms, so fall back */
88     sasl_dispose(&saslconn);
89     return POP_A_UNAVAIL;
90   }
91 
92   /* About client_start:  If sasl_client_start() returns data via pc/olen,
93    * the client is expected to send this first (after the AUTH string is sent).
94    * sasl_client_start() may in fact return SASL_OK in this case.  */
95   unsigned int client_start = olen;
96 
97   // L10N: (%s) is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
98   mutt_message(_("Authenticating (%s)..."), "SASL");
99 
100   size_t bufsize = MAX((olen * 2), 1024);
101   char *buf = mutt_mem_malloc(bufsize);
102 
103   snprintf(buf, bufsize, "AUTH %s", mech);
104   olen = strlen(buf);
105 
106   /* looping protocol */
107   while (true)
108   {
109     mutt_str_copy(buf + olen, "\r\n", bufsize - olen);
110     mutt_socket_send(adata->conn, buf);
111     if (mutt_socket_readln_d(inbuf, sizeof(inbuf), adata->conn, MUTT_SOCK_LOG_FULL) < 0)
112     {
113       sasl_dispose(&saslconn);
114       adata->status = POP_DISCONNECTED;
115       FREE(&buf);
116       return POP_A_SOCKET;
117     }
118 
119     /* Note we don't exit if rc==SASL_OK when client_start is true.
120      * This is because the first loop has only sent the AUTH string, we
121      * need to loop at least once more to send the pc/olen returned
122      * by sasl_client_start().  */
123     if (!client_start && (rc != SASL_CONTINUE))
124       break;
125 
126     if (mutt_str_startswith(inbuf, "+ ") &&
127         (sasl_decode64(inbuf + 2, strlen(inbuf + 2), buf, bufsize - 1, &len) != SASL_OK))
128     {
129       mutt_debug(LL_DEBUG1, "error base64-decoding server response\n");
130       goto bail;
131     }
132 
133     if (!client_start)
134     {
135       while (true)
136       {
137         rc = sasl_client_step(saslconn, buf, len, &interaction, &pc, &olen);
138         if (rc != SASL_INTERACT)
139           break;
140         mutt_sasl_interact(interaction);
141       }
142     }
143     else
144     {
145       olen = client_start;
146       client_start = 0;
147     }
148 
149     /* Even if sasl_client_step() returns SASL_OK, we should send at
150      * least one more line to the server.  */
151     if ((rc != SASL_CONTINUE) && (rc != SASL_OK))
152       break;
153 
154     /* send out response, or line break if none needed */
155     if (pc)
156     {
157       if ((olen * 2) > bufsize)
158       {
159         bufsize = olen * 2;
160         mutt_mem_realloc(&buf, bufsize);
161       }
162       if (sasl_encode64(pc, olen, buf, bufsize, &olen) != SASL_OK)
163       {
164         mutt_debug(LL_DEBUG1, "error base64-encoding client response\n");
165         goto bail;
166       }
167     }
168   }
169 
170   if (rc != SASL_OK)
171     goto bail;
172 
173   if (mutt_str_startswith(inbuf, "+OK"))
174   {
175     mutt_sasl_setup_conn(adata->conn, saslconn);
176     FREE(&buf);
177     return POP_A_SUCCESS;
178   }
179 
180 bail:
181   sasl_dispose(&saslconn);
182 
183   /* terminate SASL session if the last response is not +OK nor -ERR */
184   if (mutt_str_startswith(inbuf, "+ "))
185   {
186     snprintf(buf, bufsize, "*\r\n");
187     if (pop_query(adata, buf, bufsize) == -1)
188     {
189       FREE(&buf);
190       return POP_A_SOCKET;
191     }
192   }
193 
194   FREE(&buf);
195   // L10N: %s is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
196   mutt_error(_("%s authentication failed"), "SASL");
197 
198   return POP_A_FAILURE;
199 }
200 #endif
201 
202 /**
203  * pop_apop_timestamp - Get the server timestamp for APOP authentication
204  * @param adata POP Account data
205  * @param buf   Timestamp string
206  */
pop_apop_timestamp(struct PopAccountData * adata,char * buf)207 void pop_apop_timestamp(struct PopAccountData *adata, char *buf)
208 {
209   char *p1 = NULL, *p2 = NULL;
210 
211   FREE(&adata->timestamp);
212 
213   if ((p1 = strchr(buf, '<')) && (p2 = strchr(p1, '>')))
214   {
215     p2[1] = '\0';
216     adata->timestamp = mutt_str_dup(p1);
217   }
218 }
219 
220 /**
221  * pop_auth_apop - APOP authenticator - Implements PopAuth::authenticate()
222  */
pop_auth_apop(struct PopAccountData * adata,const char * method)223 static enum PopAuthRes pop_auth_apop(struct PopAccountData *adata, const char *method)
224 {
225   struct Md5Ctx md5ctx;
226   unsigned char digest[16];
227   char hash[33];
228   char buf[1024];
229 
230   if (mutt_account_getpass(&adata->conn->account) || !adata->conn->account.pass[0])
231     return POP_A_FAILURE;
232 
233   if (!adata->timestamp)
234     return POP_A_UNAVAIL;
235 
236   if (!mutt_addr_valid_msgid(adata->timestamp))
237   {
238     mutt_error(_("POP timestamp is invalid"));
239     return POP_A_UNAVAIL;
240   }
241 
242   // L10N: (%s) is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
243   mutt_message(_("Authenticating (%s)..."), "APOP");
244 
245   /* Compute the authentication hash to send to the server */
246   mutt_md5_init_ctx(&md5ctx);
247   mutt_md5_process(adata->timestamp, &md5ctx);
248   mutt_md5_process(adata->conn->account.pass, &md5ctx);
249   mutt_md5_finish_ctx(&md5ctx, digest);
250   mutt_md5_toascii(digest, hash);
251 
252   /* Send APOP command to server */
253   snprintf(buf, sizeof(buf), "APOP %s %s\r\n", adata->conn->account.user, hash);
254 
255   switch (pop_query(adata, buf, sizeof(buf)))
256   {
257     case 0:
258       return POP_A_SUCCESS;
259     case -1:
260       return POP_A_SOCKET;
261   }
262 
263   // L10N: %s is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
264   mutt_error(_("%s authentication failed"), "APOP");
265 
266   return POP_A_FAILURE;
267 }
268 
269 /**
270  * pop_auth_user - USER authenticator - Implements PopAuth::authenticate()
271  */
pop_auth_user(struct PopAccountData * adata,const char * method)272 static enum PopAuthRes pop_auth_user(struct PopAccountData *adata, const char *method)
273 {
274   if (!adata->cmd_user)
275     return POP_A_UNAVAIL;
276 
277   if (mutt_account_getpass(&adata->conn->account) || !adata->conn->account.pass[0])
278     return POP_A_FAILURE;
279 
280   mutt_message(_("Logging in..."));
281 
282   char buf[1024];
283   snprintf(buf, sizeof(buf), "USER %s\r\n", adata->conn->account.user);
284   int ret = pop_query(adata, buf, sizeof(buf));
285 
286   if (adata->cmd_user == 2)
287   {
288     if (ret == 0)
289     {
290       adata->cmd_user = 1;
291 
292       mutt_debug(LL_DEBUG1, "set USER capability\n");
293     }
294 
295     if (ret == -2)
296     {
297       adata->cmd_user = 0;
298 
299       mutt_debug(LL_DEBUG1, "unset USER capability\n");
300       snprintf(adata->err_msg, sizeof(adata->err_msg), "%s",
301                _("Command USER is not supported by server"));
302     }
303   }
304 
305   if (ret == 0)
306   {
307     snprintf(buf, sizeof(buf), "PASS %s\r\n", adata->conn->account.pass);
308     const short c_debug_level = cs_subset_number(NeoMutt->sub, "debug_level");
309     ret = pop_query_d(adata, buf, sizeof(buf),
310                       /* don't print the password unless we're at the ungodly debugging level */
311                       (c_debug_level < MUTT_SOCK_LOG_FULL) ? "PASS *\r\n" : NULL);
312   }
313 
314   switch (ret)
315   {
316     case 0:
317       return POP_A_SUCCESS;
318     case -1:
319       return POP_A_SOCKET;
320   }
321 
322   mutt_error("%s %s", _("Login failed"), adata->err_msg);
323 
324   return POP_A_FAILURE;
325 }
326 
327 /**
328  * pop_auth_oauth - Authenticate a POP connection using OAUTHBEARER - Implements PopAuth::authenticate()
329  */
pop_auth_oauth(struct PopAccountData * adata,const char * method)330 static enum PopAuthRes pop_auth_oauth(struct PopAccountData *adata, const char *method)
331 {
332   /* If they did not explicitly request or configure oauth then fail quietly */
333   const char *const c_pop_oauth_refresh_command =
334       cs_subset_string(NeoMutt->sub, "pop_oauth_refresh_command");
335   if (!method && !c_pop_oauth_refresh_command)
336     return POP_A_UNAVAIL;
337 
338   // L10N: (%s) is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
339   mutt_message(_("Authenticating (%s)..."), "OAUTHBEARER");
340 
341   char *oauthbearer = mutt_account_getoauthbearer(&adata->conn->account, false);
342   if (!oauthbearer)
343     return POP_A_FAILURE;
344 
345   size_t auth_cmd_len = strlen(oauthbearer) + 30;
346   char *auth_cmd = mutt_mem_malloc(auth_cmd_len);
347   snprintf(auth_cmd, auth_cmd_len, "AUTH OAUTHBEARER %s\r\n", oauthbearer);
348   FREE(&oauthbearer);
349 
350   int ret = pop_query_d(adata, auth_cmd, strlen(auth_cmd),
351 #ifdef DEBUG
352                         /* don't print the bearer token unless we're at the ungodly debugging level */
353                         (cs_subset_number(NeoMutt->sub, "debug_level") < MUTT_SOCK_LOG_FULL) ?
354                             "AUTH OAUTHBEARER *\r\n" :
355 #endif
356                             NULL);
357   FREE(&auth_cmd);
358 
359   switch (ret)
360   {
361     case 0:
362       return POP_A_SUCCESS;
363     case -1:
364       return POP_A_SOCKET;
365   }
366 
367   /* The error response was a SASL continuation, so "continue" it.
368    * See RFC7628 3.2.3 */
369   mutt_socket_send(adata->conn, "\001");
370 
371   char *err = adata->err_msg;
372   char decoded_err[1024];
373   int len = mutt_b64_decode(adata->err_msg, decoded_err, sizeof(decoded_err) - 1);
374   if (len >= 0)
375   {
376     decoded_err[len] = '\0';
377     err = decoded_err;
378   }
379   mutt_error("%s %s", _("Authentication failed"), err);
380 
381   return POP_A_FAILURE;
382 }
383 
384 /**
385  * PopAuthenticators - Accepted authentication methods
386  */
387 static const struct PopAuth PopAuthenticators[] = {
388   // clang-format off
389   { pop_auth_oauth, "oauthbearer" },
390 #ifdef USE_SASL
391   { pop_auth_sasl, NULL },
392 #endif
393   { pop_auth_apop, "apop" },
394   { pop_auth_user, "user" },
395   { NULL, NULL },
396   // clang-format on
397 };
398 
399 /**
400  * pop_auth_is_valid - Check if string is a valid pop authentication method
401  * @param authenticator Authenticator string to check
402  * @retval true Argument is a valid auth method
403  *
404  * Validate whether an input string is an accepted pop authentication method as
405  * defined by #PopAuthenticators.
406  */
pop_auth_is_valid(const char * authenticator)407 bool pop_auth_is_valid(const char *authenticator)
408 {
409   for (size_t i = 0; i < mutt_array_size(PopAuthenticators); i++)
410   {
411     const struct PopAuth *auth = &PopAuthenticators[i];
412     if (auth->method && mutt_istr_equal(auth->method, authenticator))
413       return true;
414   }
415 
416   return false;
417 }
418 
419 /**
420  * pop_authenticate - Authenticate with a POP server
421  * @param adata POP Account data
422  * @retval num Result, e.g. #POP_A_SUCCESS
423  * @retval  0 Successful
424  * @retval -1 Connection lost
425  * @retval -2 Login failed
426  * @retval -3 Authentication cancelled
427  */
pop_authenticate(struct PopAccountData * adata)428 int pop_authenticate(struct PopAccountData *adata)
429 {
430   struct ConnAccount *cac = &adata->conn->account;
431   const struct PopAuth *authenticator = NULL;
432   int attempts = 0;
433   int ret = POP_A_UNAVAIL;
434 
435   if ((mutt_account_getuser(cac) < 0) || (cac->user[0] == '\0'))
436   {
437     return -3;
438   }
439 
440   const struct Slist *c_pop_authenticators =
441       cs_subset_slist(NeoMutt->sub, "pop_authenticators");
442   const bool c_pop_auth_try_all =
443       cs_subset_bool(NeoMutt->sub, "pop_auth_try_all");
444   if (c_pop_authenticators && (c_pop_authenticators->count > 0))
445   {
446     /* Try user-specified list of authentication methods */
447     struct ListNode *np = NULL;
448     STAILQ_FOREACH(np, &c_pop_authenticators->head, entries)
449     {
450       mutt_debug(LL_DEBUG2, "Trying method %s\n", np->data);
451       authenticator = PopAuthenticators;
452 
453       while (authenticator->authenticate)
454       {
455         if (!authenticator->method || mutt_istr_equal(authenticator->method, np->data))
456         {
457           ret = authenticator->authenticate(adata, np->data);
458           if (ret == POP_A_SOCKET)
459           {
460             switch (pop_connect(adata))
461             {
462               case 0:
463               {
464                 ret = authenticator->authenticate(adata, np->data);
465                 break;
466               }
467               case -2:
468                 ret = POP_A_FAILURE;
469             }
470           }
471 
472           if (ret != POP_A_UNAVAIL)
473             attempts++;
474           if ((ret == POP_A_SUCCESS) || (ret == POP_A_SOCKET) ||
475               ((ret == POP_A_FAILURE) && !c_pop_auth_try_all))
476           {
477             break;
478           }
479         }
480         authenticator++;
481       }
482     }
483   }
484   else
485   {
486     /* Fall back to default: any authenticator */
487     mutt_debug(LL_DEBUG2, "Using any available method\n");
488     authenticator = PopAuthenticators;
489 
490     while (authenticator->authenticate)
491     {
492       ret = authenticator->authenticate(adata, NULL);
493       if (ret == POP_A_SOCKET)
494       {
495         switch (pop_connect(adata))
496         {
497           case 0:
498           {
499             ret = authenticator->authenticate(adata, NULL);
500             break;
501           }
502           case -2:
503             ret = POP_A_FAILURE;
504         }
505       }
506 
507       if (ret != POP_A_UNAVAIL)
508         attempts++;
509       if ((ret == POP_A_SUCCESS) || (ret == POP_A_SOCKET) ||
510           ((ret == POP_A_FAILURE) && !c_pop_auth_try_all))
511       {
512         break;
513       }
514 
515       authenticator++;
516     }
517   }
518 
519   switch (ret)
520   {
521     case POP_A_SUCCESS:
522       return 0;
523     case POP_A_SOCKET:
524       return -1;
525     case POP_A_UNAVAIL:
526       if (attempts == 0)
527         mutt_error(_("No authenticators available"));
528   }
529 
530   return -2;
531 }
532