mutt stable branch with some hacks
at master 429 lines 10 kB view raw
1/* 2 * Copyright (C) 2000-2001 Vsevolod Volkov <vvv@mutt.org.ua> 3 * 4 * This program is free software; you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation; either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program; if not, write to the Free Software 16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 */ 18 19#if HAVE_CONFIG_H 20# include "config.h" 21#endif 22 23#include "mutt.h" 24#include "mx.h" 25#include "md5.h" 26#include "pop.h" 27 28#include <string.h> 29#include <unistd.h> 30 31#ifdef USE_SASL 32#include <sasl/sasl.h> 33#include <sasl/saslutil.h> 34 35#include "mutt_sasl.h" 36#endif 37 38#ifdef USE_SASL 39/* SASL authenticator */ 40static pop_auth_res_t pop_auth_sasl (POP_DATA *pop_data, const char *method) 41{ 42 sasl_conn_t *saslconn; 43 sasl_interact_t *interaction = NULL; 44 int rc; 45 char *buf = NULL; 46 size_t bufsize = 0; 47 char inbuf[LONG_STRING]; 48 const char* mech; 49 const char *pc = NULL; 50 unsigned int len, olen, client_start; 51 52 if (mutt_sasl_client_new (pop_data->conn, &saslconn) < 0) 53 { 54 dprint (1, (debugfile, "pop_auth_sasl: Error allocating SASL connection.\n")); 55 return POP_A_FAILURE; 56 } 57 58 if (!method) 59 method = pop_data->auth_list; 60 61 FOREVER 62 { 63 rc = sasl_client_start(saslconn, method, &interaction, &pc, &olen, &mech); 64 if (rc != SASL_INTERACT) 65 break; 66 mutt_sasl_interact (interaction); 67 } 68 69 if (rc != SASL_OK && rc != SASL_CONTINUE) 70 { 71 dprint (1, (debugfile, "pop_auth_sasl: Failure starting authentication exchange. No shared mechanisms?\n")); 72 73 /* SASL doesn't support suggested mechanisms, so fall back */ 74 return POP_A_UNAVAIL; 75 } 76 77 /* About client_start: If sasl_client_start() returns data via pc/olen, 78 * the client is expected to send this first (after the AUTH string is sent). 79 * sasl_client_start() may in fact return SASL_OK in this case. 80 */ 81 client_start = olen; 82 83 mutt_message _("Authenticating (SASL)..."); 84 85 bufsize = ((olen * 2) > LONG_STRING) ? (olen * 2) : LONG_STRING; 86 buf = safe_malloc (bufsize); 87 88 snprintf (buf, bufsize, "AUTH %s", mech); 89 olen = strlen (buf); 90 91 /* looping protocol */ 92 FOREVER 93 { 94 strfcpy (buf + olen, "\r\n", bufsize - olen); 95 mutt_socket_write (pop_data->conn, buf); 96 if (mutt_socket_readln (inbuf, sizeof (inbuf), pop_data->conn) < 0) 97 { 98 sasl_dispose (&saslconn); 99 pop_data->status = POP_DISCONNECTED; 100 FREE (&buf); 101 return POP_A_SOCKET; 102 } 103 104 /* Note we don't exit if rc==SASL_OK when client_start is true. 105 * This is because the first loop has only sent the AUTH string, we 106 * need to loop at least once more to send the pc/olen returned 107 * by sasl_client_start(). 108 */ 109 if (!client_start && rc != SASL_CONTINUE) 110 break; 111 112 if (!mutt_strncmp (inbuf, "+ ", 2) 113 && sasl_decode64 (inbuf+2, strlen (inbuf+2), buf, bufsize - 1, &len) != SASL_OK) 114 { 115 dprint (1, (debugfile, "pop_auth_sasl: error base64-decoding server response.\n")); 116 goto bail; 117 } 118 119 if (!client_start) 120 FOREVER 121 { 122 rc = sasl_client_step (saslconn, buf, len, &interaction, &pc, &olen); 123 if (rc != SASL_INTERACT) 124 break; 125 mutt_sasl_interact (interaction); 126 } 127 else 128 { 129 olen = client_start; 130 client_start = 0; 131 } 132 133 /* Even if sasl_client_step() returns SASL_OK, we should send at 134 * least one more line to the server. See #3862. 135 */ 136 if (rc != SASL_CONTINUE && rc != SASL_OK) 137 break; 138 139 /* send out response, or line break if none needed */ 140 if (pc) 141 { 142 if ((olen * 2) > bufsize) 143 { 144 bufsize = olen * 2; 145 safe_realloc (&buf, bufsize); 146 } 147 if (sasl_encode64 (pc, olen, buf, bufsize, &olen) != SASL_OK) 148 { 149 dprint (1, (debugfile, "pop_auth_sasl: error base64-encoding client response.\n")); 150 goto bail; 151 } 152 } 153 } 154 155 if (rc != SASL_OK) 156 goto bail; 157 158 if (!mutt_strncmp (inbuf, "+OK", 3)) 159 { 160 mutt_sasl_setup_conn (pop_data->conn, saslconn); 161 FREE (&buf); 162 return POP_A_SUCCESS; 163 } 164 165bail: 166 sasl_dispose (&saslconn); 167 168 /* terminate SASL session if the last response is not +OK nor -ERR */ 169 if (!mutt_strncmp (inbuf, "+ ", 2)) 170 { 171 snprintf (buf, bufsize, "*\r\n"); 172 if (pop_query (pop_data, buf, sizeof (buf)) == -1) 173 { 174 FREE (&buf); 175 return POP_A_SOCKET; 176 } 177 } 178 179 FREE (&buf); 180 mutt_error _("SASL authentication failed."); 181 mutt_sleep (2); 182 183 return POP_A_FAILURE; 184} 185#endif 186 187/* Get the server timestamp for APOP authentication */ 188void pop_apop_timestamp (POP_DATA *pop_data, char *buf) 189{ 190 char *p1, *p2; 191 192 FREE (&pop_data->timestamp); 193 194 if ((p1 = strchr (buf, '<')) && (p2 = strchr (p1, '>'))) 195 { 196 p2[1] = '\0'; 197 pop_data->timestamp = safe_strdup (p1); 198 } 199} 200 201/* APOP authenticator */ 202static pop_auth_res_t pop_auth_apop (POP_DATA *pop_data, const char *method) 203{ 204 struct md5_ctx ctx; 205 unsigned char digest[16]; 206 char hash[33]; 207 char buf[LONG_STRING]; 208 size_t i; 209 210 if (!pop_data->timestamp) 211 return POP_A_UNAVAIL; 212 213 if (rfc822_valid_msgid (pop_data->timestamp) < 0) 214 { 215 mutt_error _("POP timestamp is invalid!"); 216 mutt_sleep (2); 217 return POP_A_UNAVAIL; 218 } 219 220 mutt_message _("Authenticating (APOP)..."); 221 222 /* Compute the authentication hash to send to the server */ 223 md5_init_ctx (&ctx); 224 md5_process_bytes (pop_data->timestamp, strlen (pop_data->timestamp), &ctx); 225 md5_process_bytes (pop_data->conn->account.pass, 226 strlen (pop_data->conn->account.pass), &ctx); 227 md5_finish_ctx (&ctx, digest); 228 229 for (i = 0; i < sizeof (digest); i++) 230 sprintf (hash + 2 * i, "%02x", digest[i]); 231 232 /* Send APOP command to server */ 233 snprintf (buf, sizeof (buf), "APOP %s %s\r\n", pop_data->conn->account.user, hash); 234 235 switch (pop_query (pop_data, buf, sizeof (buf))) 236 { 237 case 0: 238 return POP_A_SUCCESS; 239 case -1: 240 return POP_A_SOCKET; 241 } 242 243 mutt_error _("APOP authentication failed."); 244 mutt_sleep (2); 245 246 return POP_A_FAILURE; 247} 248 249/* USER authenticator */ 250static pop_auth_res_t pop_auth_user (POP_DATA *pop_data, const char *method) 251{ 252 char buf[LONG_STRING]; 253 int ret; 254 255 if (!pop_data->cmd_user) 256 return POP_A_UNAVAIL; 257 258 mutt_message _("Logging in..."); 259 260 snprintf (buf, sizeof (buf), "USER %s\r\n", pop_data->conn->account.user); 261 ret = pop_query (pop_data, buf, sizeof (buf)); 262 263 if (pop_data->cmd_user == 2) 264 { 265 if (ret == 0) 266 { 267 pop_data->cmd_user = 1; 268 269 dprint (1, (debugfile, "pop_auth_user: set USER capability\n")); 270 } 271 272 if (ret == -2) 273 { 274 pop_data->cmd_user = 0; 275 276 dprint (1, (debugfile, "pop_auth_user: unset USER capability\n")); 277 snprintf (pop_data->err_msg, sizeof (pop_data->err_msg), 278 _("Command USER is not supported by server.")); 279 } 280 } 281 282 if (ret == 0) 283 { 284 snprintf (buf, sizeof (buf), "PASS %s\r\n", pop_data->conn->account.pass); 285 ret = pop_query_d (pop_data, buf, sizeof (buf), 286#ifdef DEBUG 287 /* don't print the password unless we're at the ungodly debugging level */ 288 debuglevel < MUTT_SOCK_LOG_FULL ? "PASS *\r\n" : 289#endif 290 NULL); 291 } 292 293 switch (ret) 294 { 295 case 0: 296 return POP_A_SUCCESS; 297 case -1: 298 return POP_A_SOCKET; 299 } 300 301 mutt_error ("%s %s", _("Login failed."), pop_data->err_msg); 302 mutt_sleep (2); 303 304 return POP_A_FAILURE; 305} 306 307static const pop_auth_t pop_authenticators[] = { 308#ifdef USE_SASL 309 { pop_auth_sasl, NULL }, 310#endif 311 { pop_auth_apop, "apop" }, 312 { pop_auth_user, "user" }, 313 { NULL, NULL } 314}; 315 316/* 317 * Authentication 318 * 0 - successful, 319 * -1 - connection lost, 320 * -2 - login failed, 321 * -3 - authentication canceled. 322*/ 323int pop_authenticate (POP_DATA* pop_data) 324{ 325 ACCOUNT *acct = &pop_data->conn->account; 326 const pop_auth_t* authenticator; 327 char* methods; 328 char* comma; 329 char* method; 330 int attempts = 0; 331 int ret = POP_A_UNAVAIL; 332 333 if (mutt_account_getuser (acct) || !acct->user[0] || 334 mutt_account_getpass (acct) || !acct->pass[0]) 335 return -3; 336 337 if (PopAuthenticators && *PopAuthenticators) 338 { 339 /* Try user-specified list of authentication methods */ 340 methods = safe_strdup (PopAuthenticators); 341 method = methods; 342 343 while (method) 344 { 345 comma = strchr (method, ':'); 346 if (comma) 347 *comma++ = '\0'; 348 dprint (2, (debugfile, "pop_authenticate: Trying method %s\n", method)); 349 authenticator = pop_authenticators; 350 351 while (authenticator->authenticate) 352 { 353 if (!authenticator->method || 354 !ascii_strcasecmp (authenticator->method, method)) 355 { 356 ret = authenticator->authenticate (pop_data, method); 357 if (ret == POP_A_SOCKET) 358 switch (pop_connect (pop_data)) 359 { 360 case 0: 361 { 362 ret = authenticator->authenticate (pop_data, method); 363 break; 364 } 365 case -2: 366 ret = POP_A_FAILURE; 367 } 368 369 if (ret != POP_A_UNAVAIL) 370 attempts++; 371 if (ret == POP_A_SUCCESS || ret == POP_A_SOCKET || 372 (ret == POP_A_FAILURE && !option (OPTPOPAUTHTRYALL))) 373 { 374 comma = NULL; 375 break; 376 } 377 } 378 authenticator++; 379 } 380 381 method = comma; 382 } 383 384 FREE (&methods); 385 } 386 else 387 { 388 /* Fall back to default: any authenticator */ 389 dprint (2, (debugfile, "pop_authenticate: Using any available method.\n")); 390 authenticator = pop_authenticators; 391 392 while (authenticator->authenticate) 393 { 394 ret = authenticator->authenticate (pop_data, authenticator->method); 395 if (ret == POP_A_SOCKET) 396 switch (pop_connect (pop_data)) 397 { 398 case 0: 399 { 400 ret = authenticator->authenticate (pop_data, authenticator->method); 401 break; 402 } 403 case -2: 404 ret = POP_A_FAILURE; 405 } 406 407 if (ret != POP_A_UNAVAIL) 408 attempts++; 409 if (ret == POP_A_SUCCESS || ret == POP_A_SOCKET || 410 (ret == POP_A_FAILURE && !option (OPTPOPAUTHTRYALL))) 411 break; 412 413 authenticator++; 414 } 415 } 416 417 switch (ret) 418 { 419 case POP_A_SUCCESS: 420 return 0; 421 case POP_A_SOCKET: 422 return -1; 423 case POP_A_UNAVAIL: 424 if (!attempts) 425 mutt_error (_("No authenticators available")); 426 } 427 428 return -2; 429}