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
, struct connection_state
* scs
,
39 static struct connection_state
*authenticate_apop( /*@notnull@ */ Pop3 pc
, struct connection_state
* scs
,
42 static struct connection_state
*authenticate_plaintext( /*@notnull@ */ Pop3 pc
, struct connection_state
* scs
,
45 void pop3_cacheHeaders( /*@notnull@ */ Pop3 pc
);
47 extern void imap_releaseHeaders(Pop3 pc
48 __attribute__ ((unused
)),
51 extern struct connection_state
*state_for_pcu(Pop3 pc
);
53 static struct authentication_method
{
55 /* callback returns the connection state pointer if successful,
57 struct connection_state
*(*auth_callback
) (Pop3 pc
, struct connection_state
* scs
, char *apop_str
);
61 "cram-md5", authenticate_md5
}, {
62 "apop", authenticate_apop
}, {
64 "plaintext", authenticate_plaintext
}, {
69 struct connection_state
*pop3Login(Pop3 pc
)
73 char apop_str
[BUF_SIZE
];
75 struct authentication_method
*a
;
76 struct connection_state
*scs
;
77 char *connection_name
;
80 apop_str
[0] = '\0'; /* if defined, server supports apop */
82 if ((fd
= sock_connect(PCU
.serverName
, PCU
.serverPort
)) == -1) {
83 POP_DM(pc
, DEBUG_ERROR
, "Not Connected To Server '%s:%d'\n",
84 PCU
.serverName
, PCU
.serverPort
);
88 connection_name
= malloc(strlen(PCU
.serverName
) + 20);
89 sprintf(connection_name
, "%s:%d", PCU
.serverName
, PCU
.serverPort
);
92 scs
= initialize_gnutls(fd
, connection_name
, pc
, PCU
.serverName
);
94 POP_DM(pc
, DEBUG_ERROR
, "Failed to initialize TLS\n");
98 scs
= initialize_unencrypted(fd
, connection_name
, pc
);
101 tlscomm_gets(buf
, BUF_SIZE
, scs
);
102 POP_DM(pc
, DEBUG_INFO
, "%s", buf
);
104 /* Detect APOP, copy challenge into apop_str */
105 for (ptr1
= buf
+ strlen(buf
), ptr2
= NULL
; ptr1
> buf
; --ptr1
) {
108 } else if (*ptr1
== '<') {
111 strncpy(apop_str
, ptr1
, BUF_SIZE
);
118 /* try each authentication method in turn. */
119 for (a
= auth_methods
; a
->name
!= NULL
; a
++) {
120 /* was it specified or did the user leave it up to us? */
121 if (PCU
.authList
[0] == '\0' || strstr(PCU
.authList
, a
->name
))
123 if ((a
->auth_callback(pc
, scs
, apop_str
)) != NULL
)
127 /* if authentication worked, we won't get here */
128 POP_DM(pc
, DEBUG_ERROR
,
129 "All Pop3 authentication methods failed for '%s@%s:%d'\n",
130 PCU
.userName
, PCU
.serverName
, PCU
.serverPort
);
131 tlscomm_printf(scs
, "QUIT\r\n");
137 int pop3CheckMail( /*@notnull@ */ Pop3 pc
)
139 struct connection_state
*scs
;
147 tlscomm_printf(scs
, "STAT\r\n");
148 if( ! tlscomm_expect(scs
, "+", buf
, BUF_SIZE
) ) {
149 POP_DM(pc
, DEBUG_ERROR
,
150 "Error Receiving Stats '%s@%s:%d'\n",
151 PCU
.userName
, PCU
.serverName
, PCU
.serverPort
);
152 POP_DM(pc
, DEBUG_INFO
, "It said: %s\n", buf
);
155 sscanf(buf
, "+OK %d", &(pc
->TotalMsgs
));
158 /* - Updated - Mark Hurley - debian4tux@telocity.com
159 * In compliance with RFC 1725
160 * which removed the LAST command, any servers
161 * which follow this spec will return:
163 * We will leave it here for those servers which haven't
164 * caught up with the spec.
166 tlscomm_printf(scs
, "LAST\r\n");
167 tlscomm_gets(buf
, BUF_SIZE
, scs
);
169 /* it is not an error to receive this according to RFC 1725 */
170 /* no error should be returned */
171 pc
->UnreadMsgs
= pc
->TotalMsgs
;
172 // there's also a LIST command... not sure how to make use of it. */
174 sscanf(buf
, "+OK %d", &read
);
175 pc
->UnreadMsgs
= pc
->TotalMsgs
- read
;
178 tlscomm_printf(scs
, "QUIT\r\n");
185 struct msglst
*pop_getHeaders( /*@notnull@ */ Pop3 pc
)
187 if (pc
->headerCache
== NULL
)
188 pop3_cacheHeaders(pc
);
189 if (pc
->headerCache
!= NULL
)
190 pc
->headerCache
->in_use
= 1;
191 return pc
->headerCache
;
196 int pop3Create(Pop3 pc
, const char *str
)
198 /* POP3 format: pop3:user:password@server[:port] */
199 /* new POP3 format: pop3:user password server [port] */
200 /* If 'str' line is badly formatted, wmbiff won't display the mailbox. */
204 ([^@]+) or ([^ ]+) password
206 ([: ][0-9]+)? optional port
207 ' *' gobbles trailing whitespace before authentication types.
208 use separate regexes for old and new types to permit
209 use of '@' in passwords
211 const char *regexes
[] = {
212 "pop3s?:([^: ]{1,32}):([^@]{0,32})@([A-Za-z1-9][-A-Za-z0-9_.]+)(:[0-9]+)?( *([CcAaPp][-A-Za-z5 ]*))?$",
213 "pop3s?:([^: ]{1,32}) ([^ ]{1,32}) ([A-Za-z1-9][-A-Za-z0-9_.]+)( [0-9]+)?( *([CcAaPp][-A-Za-z5 ]*))?$",
214 // "pop3:([^: ]{1,32}) ([^ ]{1,32}) ([^: ]+)( [0-9]+)? *",
215 // "pop3:([^: ]{1,32}):([^@]{0,32})@([^: ]+)(:[0-9]+)? *",
218 struct regulo regulos
[] = {
219 {1, PCU
.userName
, regulo_strcpy
},
220 {2, PCU
.password
, regulo_strcpy
},
221 {3, PCU
.serverName
, regulo_strcpy
},
222 {4, &PCU
.serverPort
, regulo_atoi
},
223 {6, PCU
.authList
, regulo_strcpy_tolower
},
229 "pop3:([^: ]{1,32}):([^@]{0,32})@([^/: ]+)(:[0-9]+)?( *(.*))?$";
231 "pop3:([^: ]{1,32}) ([^ ]{1,32}) ([^/: ]+)( [0-9]+)?( *(.*))?$";
234 if (strncmp("pop3s:", str
, 6) == 0) {
235 #ifdef HAVE_GNUTLS_GNUTLS_H
238 printf("This copy of wmbiff was not compiled with gnutls;\n"
239 "imaps is unavailable. Exiting to protect your\n"
240 "passwords and privacy.\n");
249 PCU
.serverPort
= (PCU
.dossl
!= 0) ? 995 : 110;
250 PCU
.authList
[0] = '\0';
252 for (matchedchars
= 0, i
= 0;
253 regexes
[i
] != NULL
&& matchedchars
<= 0; i
++) {
254 matchedchars
= regulo_match(regexes
[i
], str
, regulos
);
257 /* failed to match either regex */
258 if (matchedchars
<= 0) {
260 POP_DM(pc
, DEBUG_ERROR
, "Couldn't parse line %s (%d)\n"
261 " If this used to work, run wmbiff with the -relax option, and\n "
262 " send mail to "PACKAGE_BUGREPORT
" with the hostname\n"
263 " of your mail server.\n", str
, matchedchars
);
266 // grab_authList(str + matchedchars, PCU.authList);
268 PCU
.password_len
= strlen(PCU
.password
);
269 if (PCU
.password
[0] == '\0') {
270 PCU
.interactive_password
= 1;
272 // ENFROB(PCU.password);
275 POP_DM(pc
, DEBUG_INFO
, "userName= '%s'\n", PCU
.userName
);
276 POP_DM(pc
, DEBUG_INFO
, "password is %ld chars long\n",
277 strlen(PCU
.password
));
278 POP_DM(pc
, DEBUG_INFO
, "serverName= '%s'\n", PCU
.serverName
);
279 POP_DM(pc
, DEBUG_INFO
, "serverPort= '%d'\n", PCU
.serverPort
);
280 POP_DM(pc
, DEBUG_INFO
, "authList= '%s'\n", PCU
.authList
);
282 pc
->checkMail
= pop3CheckMail
;
283 pc
->getHeaders
= pop_getHeaders
;
287 pc
->OldUnreadMsgs
= -1;
294 static struct connection_state
*authenticate_md5(Pop3 pc
, struct connection_state
* scs
, char *apop_str
295 __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
, struct connection_state
* scs
, char *apop_str
)
360 if (apop_str
[0] == '\0') {
361 /* server doesn't support apop. */
364 POP_DM(pc
, DEBUG_INFO
, "APOP challenge: %s\n", apop_str
);
365 strcat(apop_str
, PCU
.password
);
367 rc
= gcry_md_open(&gmh
, GCRY_MD_MD5
, 0);
369 POP_DM(pc
, DEBUG_ERROR
, "unable to initialize gcrypt md5.\n");
372 gcry_md_write(gmh
, (unsigned char *) apop_str
, strlen(apop_str
));
374 md5
= gcry_md_read(gmh
, 0);
375 Bin2Hex(md5
, 16, buf
);
378 POP_DM(pc
, DEBUG_INFO
, "APOP response: %s %s\n", PCU
.userName
, buf
);
379 tlscomm_printf(scs
, "APOP %s %s\r\n", PCU
.userName
, buf
);
380 tlscomm_gets(buf
, BUF_SIZE
, scs
);
382 if (!strncmp(buf
, "+OK", 3))
383 return scs
; /* AUTH successful */
385 POP_DM(pc
, DEBUG_ERROR
,
386 "APOP AUTH failed for user '%s@%s:%d'\n",
387 PCU
.userName
, PCU
.serverName
, PCU
.serverPort
);
388 POP_DM(pc
, DEBUG_INFO
, "It said %s", buf
);
392 #endif /* HAVE_GCRYPT_H */
395 static struct connection_state
*authenticate_plaintext( /*@notnull@ */ Pop3 pc
,
396 struct connection_state
* scs
, char *apop_str
397 __attribute__ ((unused
)))
401 tlscomm_printf(scs
, "USER %s\r\n", PCU
.userName
);
402 if (tlscomm_gets(buf
, BUF_SIZE
, scs
) == 0) {
403 POP_DM(pc
, DEBUG_ERROR
,
404 "Error reading from server authenticating '%s@%s:%d'\n",
405 PCU
.userName
, PCU
.serverName
, PCU
.serverPort
);
409 POP_DM(pc
, DEBUG_ERROR
,
410 "Failed user name when authenticating '%s@%s:%d'\n",
411 PCU
.userName
, PCU
.serverName
, PCU
.serverPort
);
412 /* deb #128863 might be easier if we printed: */
413 POP_DM(pc
, DEBUG_ERROR
, "The server's error message was: %s\n",
419 tlscomm_printf(scs
, "PASS %s\r\n", PCU
.password
);
420 if (tlscomm_gets(buf
, BUF_SIZE
, scs
) == 0) {
421 POP_DM(pc
, DEBUG_ERROR
,
422 "Error reading from server (2) authenticating '%s@%s:%d'\n",
423 PCU
.userName
, PCU
.serverName
, PCU
.serverPort
);
426 if (strncmp(buf
, "-ERR [AUTH] Password required", 20) == 0) {
427 if (PCU
.interactive_password
) {
428 PCU
.password
[0] = '\0';
429 ask_user_for_password(pc
, 1); /* 1=overwrite the cache */
430 tlscomm_printf(scs
, "PASS %s\r\n", PCU
.password
);
431 if (tlscomm_gets(buf
, BUF_SIZE
, scs
) == 0) {
432 POP_DM(pc
, DEBUG_ERROR
,
433 "Error reading from server (2) authenticating '%s@%s:%d'\n",
434 PCU
.userName
, PCU
.serverName
, PCU
.serverPort
);
440 POP_DM(pc
, DEBUG_ERROR
,
441 "Failed password when authenticating '%s@%s:%d'\n",
442 PCU
.userName
, PCU
.serverName
, PCU
.serverPort
);
443 POP_DM(pc
, DEBUG_ERROR
, "The server's error message was: %s\n",
451 void pop3_cacheHeaders( /*@notnull@ */ Pop3 pc
)
454 struct connection_state
*scs
;
457 if (pc
->headerCache
!= NULL
) {
458 /* decrement the reference count, and free our version */
459 imap_releaseHeaders(pc
, pc
->headerCache
);
460 pc
->headerCache
= NULL
;
463 POP_DM(pc
, DEBUG_INFO
, "working headers\n");
464 /* login the server */
468 /* pc->UnreadMsgs = pc->TotalMsgs - read; */
469 pc
->headerCache
= NULL
;
470 for (i
= pc
->TotalMsgs
- pc
->UnreadMsgs
+ 1; i
<= pc
->TotalMsgs
; ++i
) {
472 m
= malloc(sizeof(struct msglst
));
476 POP_DM(pc
, DEBUG_INFO
, "search: %s", buf
);
478 tlscomm_printf(scs
, "TOP %i 0\r\n", i
);
479 while (tlscomm_gets(buf
, 256, scs
) && buf
[0] != '.') {
480 if (!strncasecmp(buf
, "From: ", 6)) {
481 /* manage the from in heads */
482 strncpy(m
->from
, buf
+ 6, FROM_LEN
- 1);
483 m
->from
[FROM_LEN
- 1] = '\0';
484 } else if (!strncasecmp(buf
, "Subject: ", 9)) {
486 strncpy(m
->subj
, buf
+ 9, SUBJ_LEN
- 1);
487 m
->subj
[SUBJ_LEN
- 1] = '\0';
490 strncpy(m
->subj
, "[NO SUBJECT]", 14);
493 strncpy(m
->from
, "[ANONYMOUS]", 14);
496 m
->next
= pc
->headerCache
;
498 pc
->headerCache
->in_use
= 0;
500 tlscomm_printf(scs
, "QUIT\r\n");
505 static void ask_user_for_password( /*@notnull@ */ Pop3 pc
, int bFlushCache
)
507 /* see if we already have a password, as provided in the config file, or
508 already requested from the user. */
509 if (PCU
.interactive_password
) {
510 if (strlen(PCU
.password
) == 0) {
511 /* we need to grab the password from the user. */
513 POP_DM(pc
, DEBUG_INFO
, "asking for password %d\n",
516 passwordFor(PCU
.userName
, PCU
.serverName
, pc
, bFlushCache
);
517 if (password
!= NULL
) {
518 if (strlen(password
) + 1 > BUF_SMALL
) {
519 DMA(DEBUG_ERROR
, "Password is too long.\n");
520 memset(PCU
.password
, 0, BUF_SMALL
- 1);
522 strncpy(PCU
.password
, password
, BUF_SMALL
- 1);
523 PCU
.password_len
= strlen(PCU
.password
);
526 // ENFROB(PCU.password);