1 /* $Id: Pop3Client.c,v 1.23 2004/12/12 00:01:53 bluehal Exp $ */
2 /* Author : Scott Holden ( scotth@thezone.net )
3 Modified : Yong-iL Joh ( tolkien@mizi.com )
4 Modified : Jorge GarcĂa ( Jorge.Garcia@uv.es )
5 Modified ; Mark Hurley ( debian4tux@telocity.com )
6 Modified : Neil Spring ( nspring@cs.washington.edu )
10 * Last Updated : Tue Nov 13 13:45:23 PST 2001
20 #include "MessageList.h"
23 #include "passwordMgr.h"
31 static void ask_user_for_password( /*@notnull@ */ Pop3
*pc
, int bFlushCache
);
33 #define PCU (pc->u).pop_imap
34 #define POP_DM(pc, lvl, args...) DM(pc, lvl, "pop3: " args)
37 static struct connection_state
*authenticate_md5( /*@notnull@ */ Pop3
*pc
,
38 struct connection_state
*scs
,
40 static struct connection_state
*authenticate_apop( /*@notnull@ */ Pop3
*pc
,
41 struct connection_state
*scs
,
44 static struct connection_state
*authenticate_plaintext( /*@notnull@ */ Pop3
*pc
,
45 struct connection_state
*scs
,
48 void pop3_cacheHeaders( /*@notnull@ */ Pop3
*pc
);
50 extern void imap_releaseHeaders(Pop3
*pc
__attribute__((unused
)),
53 extern struct connection_state
*state_for_pcu(Pop3
*pc
);
55 static struct authentication_method
{
57 /* callback returns the connection state pointer if successful,
59 struct connection_state
*(*auth_callback
) (Pop3
*pc
,
60 struct connection_state
*scs
,
65 "cram-md5", authenticate_md5
}, {
66 "apop", authenticate_apop
}, {
68 "plaintext", authenticate_plaintext
}, {
73 struct connection_state
*pop3Login(Pop3
*pc
)
77 char apop_str
[BUF_SIZE
] = "";
79 struct authentication_method
*a
;
80 struct connection_state
*scs
;
81 char *connection_name
;
83 if ((fd
= sock_connect(PCU
.serverName
, PCU
.serverPort
)) == -1) {
84 POP_DM(pc
, DEBUG_ERROR
, "Not Connected To Server '%s:%d'\n",
85 PCU
.serverName
, PCU
.serverPort
);
89 connection_name
= malloc(strlen(PCU
.serverName
) + 20);
90 sprintf(connection_name
, "%s:%d", PCU
.serverName
, PCU
.serverPort
);
93 scs
= initialize_gnutls(fd
, connection_name
, pc
, PCU
.serverName
);
95 POP_DM(pc
, DEBUG_ERROR
, "Failed to initialize TLS\n");
99 scs
= initialize_unencrypted(fd
, connection_name
, pc
);
102 tlscomm_gets(buf
, BUF_SIZE
, scs
);
103 POP_DM(pc
, DEBUG_INFO
, "%s", buf
);
105 /* Detect APOP, copy challenge into apop_str */
106 for (ptr1
= buf
+ strlen(buf
), ptr2
= NULL
; ptr1
> buf
; --ptr1
) {
109 } else if (*ptr1
== '<') {
111 memcpy(apop_str
, ptr1
, 1 + ptr2
- ptr1
);
117 /* try each authentication method in turn. */
118 for (a
= auth_methods
; a
->name
!= NULL
; a
++) {
119 /* was it specified or did the user leave it up to us? */
120 if (PCU
.authList
[0] == '\0' || strstr(PCU
.authList
, a
->name
))
122 if ((a
->auth_callback(pc
, scs
, apop_str
)) != NULL
)
126 /* if authentication worked, we won't get here */
127 POP_DM(pc
, DEBUG_ERROR
,
128 "All Pop3 authentication methods failed for '%s@%s:%d'\n",
129 PCU
.userName
, PCU
.serverName
, PCU
.serverPort
);
130 tlscomm_printf(scs
, "QUIT\r\n");
136 int pop3CheckMail( /*@notnull@ */ Pop3
*pc
)
138 struct connection_state
*scs
;
146 tlscomm_printf(scs
, "STAT\r\n");
147 if( ! tlscomm_expect(scs
, "+", buf
, BUF_SIZE
) ) {
148 POP_DM(pc
, DEBUG_ERROR
,
149 "Error Receiving Stats '%s@%s:%d'\n",
150 PCU
.userName
, PCU
.serverName
, PCU
.serverPort
);
151 POP_DM(pc
, DEBUG_INFO
, "It said: %s\n", buf
);
154 sscanf(buf
, "+OK %d", &(pc
->TotalMsgs
));
157 /* - Updated - Mark Hurley - debian4tux@telocity.com
158 * In compliance with RFC 1725
159 * which removed the LAST command, any servers
160 * which follow this spec will return:
162 * We will leave it here for those servers which haven't
163 * caught up with the spec.
165 tlscomm_printf(scs
, "LAST\r\n");
166 tlscomm_gets(buf
, BUF_SIZE
, scs
);
168 /* it is not an error to receive this according to RFC 1725 */
169 /* no error should be returned */
170 pc
->UnreadMsgs
= pc
->TotalMsgs
;
171 // there's also a LIST command... not sure how to make use of it. */
173 sscanf(buf
, "+OK %d", &read
);
174 pc
->UnreadMsgs
= pc
->TotalMsgs
- read
;
177 tlscomm_printf(scs
, "QUIT\r\n");
184 struct msglst
*pop_getHeaders( /*@notnull@ */ Pop3
*pc
)
186 if (pc
->headerCache
== NULL
)
187 pop3_cacheHeaders(pc
);
188 if (pc
->headerCache
!= NULL
)
189 pc
->headerCache
->in_use
= 1;
190 return pc
->headerCache
;
195 int pop3Create(Pop3
*pc
, const char *str
)
197 /* POP3 format: pop3:user:password@server[:port] */
198 /* new POP3 format: pop3:user password server [port] */
199 /* If 'str' line is badly formatted, wmbiff won't display the mailbox. */
203 ([^@]+) or ([^ ]+) password
205 ([: ][0-9]+)? optional port
206 ' *' gobbles trailing whitespace before authentication types.
207 use separate regexes for old and new types to permit
208 use of '@' in passwords
210 const char *regexes
[] = {
211 "pop3s?:([^: ]{1,32}):([^@]{0,32})@([A-Za-z1-9][-A-Za-z0-9_.]+)(:[0-9]+)?( *([CcAaPp][-A-Za-z5 ]*))?$",
212 "pop3s?:([^: ]{1,32}) ([^ ]{1,32}) ([A-Za-z1-9][-A-Za-z0-9_.]+)( [0-9]+)?( *([CcAaPp][-A-Za-z5 ]*))?$",
213 // "pop3:([^: ]{1,32}) ([^ ]{1,32}) ([^: ]+)( [0-9]+)? *",
214 // "pop3:([^: ]{1,32}):([^@]{0,32})@([^: ]+)(:[0-9]+)? *",
217 struct regulo regulos
[] = {
218 {1, PCU
.userName
, regulo_strcpy
},
219 {2, PCU
.password
, regulo_strcpy
},
220 {3, PCU
.serverName
, regulo_strcpy
},
221 {4, &PCU
.serverPort
, regulo_atoi
},
222 {6, PCU
.authList
, regulo_strcpy_tolower
},
228 "pop3:([^: ]{1,32}):([^@]{0,32})@([^/: ]+)(:[0-9]+)?( *(.*))?$";
230 "pop3:([^: ]{1,32}) ([^ ]{1,32}) ([^/: ]+)( [0-9]+)?( *(.*))?$";
233 if (strncmp("pop3s:", str
, 6) == 0) {
234 #ifdef HAVE_GNUTLS_GNUTLS_H
237 printf("This copy of wmbiff was not compiled with gnutls;\n"
238 "imaps is unavailable. Exiting to protect your\n"
239 "passwords and privacy.\n");
248 PCU
.serverPort
= (PCU
.dossl
!= 0) ? 995 : 110;
249 PCU
.authList
[0] = '\0';
251 for (matchedchars
= 0, i
= 0;
252 regexes
[i
] != NULL
&& matchedchars
<= 0; i
++) {
253 matchedchars
= regulo_match(regexes
[i
], str
, regulos
);
256 /* failed to match either regex */
257 if (matchedchars
<= 0) {
259 POP_DM(pc
, DEBUG_ERROR
, "Couldn't parse line %s (%d)\n"
260 " If this used to work, run wmbiff with the -relax option, and\n "
261 " send mail to "PACKAGE_BUGREPORT
" with the hostname\n"
262 " of your mail server.\n", str
, matchedchars
);
265 // grab_authList(str + matchedchars, PCU.authList);
267 PCU
.password_len
= strlen(PCU
.password
);
268 if (PCU
.password
[0] == '\0') {
269 PCU
.interactive_password
= 1;
271 // ENFROB(PCU.password);
274 POP_DM(pc
, DEBUG_INFO
, "userName= '%s'\n", PCU
.userName
);
275 POP_DM(pc
, DEBUG_INFO
, "password is %ld chars long\n",
276 strlen(PCU
.password
));
277 POP_DM(pc
, DEBUG_INFO
, "serverName= '%s'\n", PCU
.serverName
);
278 POP_DM(pc
, DEBUG_INFO
, "serverPort= '%d'\n", PCU
.serverPort
);
279 POP_DM(pc
, DEBUG_INFO
, "authList= '%s'\n", PCU
.authList
);
281 pc
->checkMail
= pop3CheckMail
;
282 pc
->getHeaders
= pop_getHeaders
;
286 pc
->OldUnreadMsgs
= -1;
293 static struct connection_state
*authenticate_md5(Pop3
*pc
,
294 struct connection_state
*scs
,
295 char *apop_str
__attribute__((unused
)))
303 /* See if MD5 is supported */
304 tlscomm_printf(scs
, "AUTH CRAM-MD5\r\n");
305 tlscomm_gets(buf
, BUF_SIZE
, scs
);
306 POP_DM(pc
, DEBUG_INFO
, "%s", buf
);
308 if (buf
[0] != '+' || buf
[1] != ' ') {
309 /* nope, not supported. */
313 Decode_Base64(buf
+ 2, buf2
);
314 POP_DM(pc
, DEBUG_INFO
, "CRAM-MD5 challenge: %s\n", buf2
);
316 strcpy(buf
, PCU
.userName
);
320 rc
= gcry_md_open(&gmh
, GCRY_MD_MD5
, GCRY_MD_FLAG_HMAC
);
322 POP_DM(pc
, DEBUG_ERROR
, "unable to initialize gcrypt md5.\n");
325 gcry_md_setkey(gmh
, PCU
.password
, strlen(PCU
.password
));
326 gcry_md_write(gmh
, (unsigned char *) buf2
, strlen(buf2
));
328 md5
= gcry_md_read(gmh
, 0);
329 /* hmac_md5(buf2, strlen(buf2), PCU.password,
330 strlen(PCU.password), md5); */
331 Bin2Hex(md5
, 16, buf2
);
335 POP_DM(pc
, DEBUG_INFO
, "CRAM-MD5 response: %s\n", buf
);
336 Encode_Base64(buf
, buf2
);
338 tlscomm_printf(scs
, "%s\r\n", buf2
);
339 tlscomm_gets(buf
, BUF_SIZE
, scs
);
341 if (!strncmp(buf
, "+OK", 3))
342 return scs
; /* AUTH successful */
344 POP_DM(pc
, DEBUG_ERROR
,
345 "CRAM-MD5 AUTH failed for user '%s@%s:%d'\n",
346 PCU
.userName
, PCU
.serverName
, PCU
.serverPort
);
347 fprintf(stderr
, "It said %s", buf
);
352 static struct connection_state
*authenticate_apop(Pop3
*pc
,
353 struct connection_state
*scs
,
362 if (apop_str
[0] == '\0') {
363 /* server doesn't support apop. */
366 POP_DM(pc
, DEBUG_INFO
, "APOP challenge: %s\n", apop_str
);
367 strcat(apop_str
, PCU
.password
);
369 rc
= gcry_md_open(&gmh
, GCRY_MD_MD5
, 0);
371 POP_DM(pc
, DEBUG_ERROR
, "unable to initialize gcrypt md5.\n");
374 gcry_md_write(gmh
, (unsigned char *) apop_str
, strlen(apop_str
));
376 md5
= gcry_md_read(gmh
, 0);
377 Bin2Hex(md5
, 16, buf
);
380 POP_DM(pc
, DEBUG_INFO
, "APOP response: %s %s\n", PCU
.userName
, buf
);
381 tlscomm_printf(scs
, "APOP %s %s\r\n", PCU
.userName
, buf
);
382 tlscomm_gets(buf
, BUF_SIZE
, scs
);
384 if (!strncmp(buf
, "+OK", 3))
385 return scs
; /* AUTH successful */
387 POP_DM(pc
, DEBUG_ERROR
,
388 "APOP AUTH failed for user '%s@%s:%d'\n",
389 PCU
.userName
, PCU
.serverName
, PCU
.serverPort
);
390 POP_DM(pc
, DEBUG_INFO
, "It said %s", buf
);
394 #endif /* HAVE_GCRYPT_H */
397 static struct connection_state
*authenticate_plaintext( /*@notnull@ */ Pop3
*pc
,
398 struct connection_state
*scs
,
399 char *apop_str
__attribute__((unused
)))
403 tlscomm_printf(scs
, "USER %s\r\n", PCU
.userName
);
404 if (tlscomm_gets(buf
, BUF_SIZE
, scs
) == 0) {
405 POP_DM(pc
, DEBUG_ERROR
,
406 "Error reading from server authenticating '%s@%s:%d'\n",
407 PCU
.userName
, PCU
.serverName
, PCU
.serverPort
);
411 POP_DM(pc
, DEBUG_ERROR
,
412 "Failed user name when authenticating '%s@%s:%d'\n",
413 PCU
.userName
, PCU
.serverName
, PCU
.serverPort
);
414 /* deb #128863 might be easier if we printed: */
415 POP_DM(pc
, DEBUG_ERROR
, "The server's error message was: %s\n",
421 tlscomm_printf(scs
, "PASS %s\r\n", PCU
.password
);
422 if (tlscomm_gets(buf
, BUF_SIZE
, scs
) == 0) {
423 POP_DM(pc
, DEBUG_ERROR
,
424 "Error reading from server (2) authenticating '%s@%s:%d'\n",
425 PCU
.userName
, PCU
.serverName
, PCU
.serverPort
);
428 if (strncmp(buf
, "-ERR [AUTH] Password required", 20) == 0) {
429 if (PCU
.interactive_password
) {
430 PCU
.password
[0] = '\0';
431 ask_user_for_password(pc
, 1); /* 1=overwrite the cache */
432 tlscomm_printf(scs
, "PASS %s\r\n", PCU
.password
);
433 if (tlscomm_gets(buf
, BUF_SIZE
, scs
) == 0) {
434 POP_DM(pc
, DEBUG_ERROR
,
435 "Error reading from server (2) authenticating '%s@%s:%d'\n",
436 PCU
.userName
, PCU
.serverName
, PCU
.serverPort
);
442 POP_DM(pc
, DEBUG_ERROR
,
443 "Failed password when authenticating '%s@%s:%d'\n",
444 PCU
.userName
, PCU
.serverName
, PCU
.serverPort
);
445 POP_DM(pc
, DEBUG_ERROR
, "The server's error message was: %s\n",
453 void pop3_cacheHeaders( /*@notnull@ */ Pop3
*pc
)
456 struct connection_state
*scs
;
459 if (pc
->headerCache
!= NULL
) {
460 /* decrement the reference count, and free our version */
461 imap_releaseHeaders(pc
, pc
->headerCache
);
462 pc
->headerCache
= NULL
;
465 POP_DM(pc
, DEBUG_INFO
, "working headers\n");
466 /* login the server */
470 /* pc->UnreadMsgs = pc->TotalMsgs - read; */
471 pc
->headerCache
= NULL
;
472 for (i
= pc
->TotalMsgs
- pc
->UnreadMsgs
+ 1; i
<= pc
->TotalMsgs
; ++i
) {
474 m
= malloc(sizeof(struct msglst
));
478 POP_DM(pc
, DEBUG_INFO
, "search: %s", buf
);
480 tlscomm_printf(scs
, "TOP %i 0\r\n", i
);
481 while (tlscomm_gets(buf
, 256, scs
) && buf
[0] != '.') {
482 if (!strncasecmp(buf
, "From: ", 6)) {
483 /* manage the from in heads */
484 strncpy(m
->from
, buf
+ 6, FROM_LEN
- 1);
485 m
->from
[FROM_LEN
- 1] = '\0';
486 } else if (!strncasecmp(buf
, "Subject: ", 9)) {
488 strncpy(m
->subj
, buf
+ 9, SUBJ_LEN
- 1);
489 m
->subj
[SUBJ_LEN
- 1] = '\0';
492 strncpy(m
->subj
, "[NO SUBJECT]", 14);
495 strncpy(m
->from
, "[ANONYMOUS]", 14);
498 m
->next
= pc
->headerCache
;
500 pc
->headerCache
->in_use
= 0;
502 tlscomm_printf(scs
, "QUIT\r\n");
507 static void ask_user_for_password( /*@notnull@ */ Pop3
*pc
, int bFlushCache
)
509 /* see if we already have a password, as provided in the config file, or
510 already requested from the user. */
511 if (PCU
.interactive_password
) {
512 if (strlen(PCU
.password
) == 0) {
513 /* we need to grab the password from the user. */
515 POP_DM(pc
, DEBUG_INFO
, "asking for password %d\n",
518 passwordFor(PCU
.userName
, PCU
.serverName
, pc
, bFlushCache
);
519 if (password
!= NULL
) {
520 if (strlen(password
) + 1 > BUF_SMALL
) {
521 DMA(DEBUG_ERROR
, "Password is too long.\n");
522 memset(PCU
.password
, 0, BUF_SMALL
- 1);
524 strncpy(PCU
.password
, password
, BUF_SMALL
- 1);
525 PCU
.password_len
= strlen(PCU
.password
);
528 // ENFROB(PCU.password);