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
236 static int haveBeenWarned
;
238 if (!haveBeenWarned
) {
239 printf("wmbiff uses gnutls for TLS/SSL encryption support:\n"
240 " If you distribute software that uses gnutls, don't forget\n"
241 " to warn the users of your software that gnutls is at a\n"
242 " testing phase and may be totally insecure.\n"
243 "\nConsider yourself warned.\n");
247 printf("This copy of wmbiff was not compiled with gnutls;\n"
248 "imaps is unavailable. Exiting to protect your\n"
249 "passwords and privacy.\n");
258 PCU
.serverPort
= (PCU
.dossl
!= 0) ? 995 : 110;
259 PCU
.authList
[0] = '\0';
261 for (matchedchars
= 0, i
= 0;
262 regexes
[i
] != NULL
&& matchedchars
<= 0; i
++) {
263 matchedchars
= regulo_match(regexes
[i
], str
, regulos
);
266 /* failed to match either regex */
267 if (matchedchars
<= 0) {
269 POP_DM(pc
, DEBUG_ERROR
, "Couldn't parse line %s (%d)\n"
270 " If this used to work, run wmbiff with the -relax option, and\n "
271 " send mail to wmbiff-devel@lists.sourceforge.net with the hostname\n"
272 " of your mail server.\n", str
, matchedchars
);
275 // grab_authList(str + matchedchars, PCU.authList);
277 PCU
.password_len
= strlen(PCU
.password
);
278 if (PCU
.password
[0] == '\0') {
279 PCU
.interactive_password
= 1;
281 // ENFROB(PCU.password);
284 POP_DM(pc
, DEBUG_INFO
, "userName= '%s'\n", PCU
.userName
);
285 POP_DM(pc
, DEBUG_INFO
, "password is %ld chars long\n",
286 strlen(PCU
.password
));
287 POP_DM(pc
, DEBUG_INFO
, "serverName= '%s'\n", PCU
.serverName
);
288 POP_DM(pc
, DEBUG_INFO
, "serverPort= '%d'\n", PCU
.serverPort
);
289 POP_DM(pc
, DEBUG_INFO
, "authList= '%s'\n", PCU
.authList
);
291 pc
->checkMail
= pop3CheckMail
;
292 pc
->getHeaders
= pop_getHeaders
;
296 pc
->OldUnreadMsgs
= -1;
303 static struct connection_state
*authenticate_md5(Pop3 pc
, struct connection_state
* scs
, char *apop_str
304 __attribute__ ((unused
)))
312 /* See if MD5 is supported */
313 tlscomm_printf(scs
, "AUTH CRAM-MD5\r\n");
314 tlscomm_gets(buf
, BUF_SIZE
, scs
);
315 POP_DM(pc
, DEBUG_INFO
, "%s", buf
);
317 if (buf
[0] != '+' || buf
[1] != ' ') {
318 /* nope, not supported. */
322 Decode_Base64(buf
+ 2, buf2
);
323 POP_DM(pc
, DEBUG_INFO
, "CRAM-MD5 challenge: %s\n", buf2
);
325 strcpy(buf
, PCU
.userName
);
329 rc
= gcry_md_open(&gmh
, GCRY_MD_MD5
, GCRY_MD_FLAG_HMAC
);
331 POP_DM(pc
, DEBUG_ERROR
, "unable to initialize gcrypt md5.\n");
334 gcry_md_setkey(gmh
, PCU
.password
, strlen(PCU
.password
));
335 gcry_md_write(gmh
, (unsigned char *) buf2
, strlen(buf2
));
337 md5
= gcry_md_read(gmh
, 0);
338 /* hmac_md5(buf2, strlen(buf2), PCU.password,
339 strlen(PCU.password), md5); */
340 Bin2Hex(md5
, 16, buf2
);
344 POP_DM(pc
, DEBUG_INFO
, "CRAM-MD5 response: %s\n", buf
);
345 Encode_Base64(buf
, buf2
);
347 tlscomm_printf(scs
, "%s\r\n", buf2
);
348 tlscomm_gets(buf
, BUF_SIZE
, scs
);
350 if (!strncmp(buf
, "+OK", 3))
351 return scs
; /* AUTH successful */
353 POP_DM(pc
, DEBUG_ERROR
,
354 "CRAM-MD5 AUTH failed for user '%s@%s:%d'\n",
355 PCU
.userName
, PCU
.serverName
, PCU
.serverPort
);
356 fprintf(stderr
, "It said %s", buf
);
361 static struct connection_state
*authenticate_apop(Pop3 pc
, struct connection_state
* scs
, char *apop_str
)
369 if (apop_str
[0] == '\0') {
370 /* server doesn't support apop. */
373 POP_DM(pc
, DEBUG_INFO
, "APOP challenge: %s\n", apop_str
);
374 strcat(apop_str
, PCU
.password
);
376 rc
= gcry_md_open(&gmh
, GCRY_MD_MD5
, 0);
378 POP_DM(pc
, DEBUG_ERROR
, "unable to initialize gcrypt md5.\n");
381 gcry_md_write(gmh
, (unsigned char *) apop_str
, strlen(apop_str
));
383 md5
= gcry_md_read(gmh
, 0);
384 Bin2Hex(md5
, 16, buf
);
387 POP_DM(pc
, DEBUG_INFO
, "APOP response: %s %s\n", PCU
.userName
, buf
);
388 tlscomm_printf(scs
, "APOP %s %s\r\n", PCU
.userName
, buf
);
389 tlscomm_gets(buf
, BUF_SIZE
, scs
);
391 if (!strncmp(buf
, "+OK", 3))
392 return scs
; /* AUTH successful */
394 POP_DM(pc
, DEBUG_ERROR
,
395 "APOP AUTH failed for user '%s@%s:%d'\n",
396 PCU
.userName
, PCU
.serverName
, PCU
.serverPort
);
397 POP_DM(pc
, DEBUG_INFO
, "It said %s", buf
);
401 #endif /* HAVE_GCRYPT_H */
404 static struct connection_state
*authenticate_plaintext( /*@notnull@ */ Pop3 pc
,
405 struct connection_state
* scs
, char *apop_str
406 __attribute__ ((unused
)))
410 tlscomm_printf(scs
, "USER %s\r\n", PCU
.userName
);
411 if (tlscomm_gets(buf
, BUF_SIZE
, scs
) == 0) {
412 POP_DM(pc
, DEBUG_ERROR
,
413 "Error reading from server authenticating '%s@%s:%d'\n",
414 PCU
.userName
, PCU
.serverName
, PCU
.serverPort
);
418 POP_DM(pc
, DEBUG_ERROR
,
419 "Failed user name when authenticating '%s@%s:%d'\n",
420 PCU
.userName
, PCU
.serverName
, PCU
.serverPort
);
421 /* deb #128863 might be easier if we printed: */
422 POP_DM(pc
, DEBUG_ERROR
, "The server's error message was: %s\n",
428 tlscomm_printf(scs
, "PASS %s\r\n", PCU
.password
);
429 if (tlscomm_gets(buf
, BUF_SIZE
, scs
) == 0) {
430 POP_DM(pc
, DEBUG_ERROR
,
431 "Error reading from server (2) authenticating '%s@%s:%d'\n",
432 PCU
.userName
, PCU
.serverName
, PCU
.serverPort
);
435 if (strncmp(buf
, "-ERR [AUTH] Password required", 20) == 0) {
436 if (PCU
.interactive_password
) {
437 PCU
.password
[0] = '\0';
438 ask_user_for_password(pc
, 1); /* 1=overwrite the cache */
439 tlscomm_printf(scs
, "PASS %s\r\n", PCU
.password
);
440 if (tlscomm_gets(buf
, BUF_SIZE
, scs
) == 0) {
441 POP_DM(pc
, DEBUG_ERROR
,
442 "Error reading from server (2) authenticating '%s@%s:%d'\n",
443 PCU
.userName
, PCU
.serverName
, PCU
.serverPort
);
449 POP_DM(pc
, DEBUG_ERROR
,
450 "Failed password when authenticating '%s@%s:%d'\n",
451 PCU
.userName
, PCU
.serverName
, PCU
.serverPort
);
452 POP_DM(pc
, DEBUG_ERROR
, "The server's error message was: %s\n",
460 void pop3_cacheHeaders( /*@notnull@ */ Pop3 pc
)
463 struct connection_state
*scs
;
466 if (pc
->headerCache
!= NULL
) {
467 /* decrement the reference count, and free our version */
468 imap_releaseHeaders(pc
, pc
->headerCache
);
469 pc
->headerCache
= NULL
;
472 POP_DM(pc
, DEBUG_INFO
, "working headers\n");
473 /* login the server */
477 /* pc->UnreadMsgs = pc->TotalMsgs - read; */
478 pc
->headerCache
= NULL
;
479 for (i
= pc
->TotalMsgs
- pc
->UnreadMsgs
+ 1; i
<= pc
->TotalMsgs
; ++i
) {
481 m
= malloc(sizeof(struct msglst
));
485 POP_DM(pc
, DEBUG_INFO
, "search: %s", buf
);
487 tlscomm_printf(scs
, "TOP %i 0\r\n", i
);
488 while (tlscomm_gets(buf
, 256, scs
) && buf
[0] != '.') {
489 if (!strncasecmp(buf
, "From: ", 6)) {
490 /* manage the from in heads */
491 strncpy(m
->from
, buf
+ 6, FROM_LEN
- 1);
492 m
->from
[FROM_LEN
- 1] = '\0';
493 } else if (!strncasecmp(buf
, "Subject: ", 9)) {
495 strncpy(m
->subj
, buf
+ 9, SUBJ_LEN
- 1);
496 m
->subj
[SUBJ_LEN
- 1] = '\0';
499 strncpy(m
->subj
, "[NO SUBJECT]", 14);
502 strncpy(m
->from
, "[ANONYMOUS]", 14);
505 m
->next
= pc
->headerCache
;
507 pc
->headerCache
->in_use
= 0;
509 tlscomm_printf(scs
, "QUIT\r\n");
514 static void ask_user_for_password( /*@notnull@ */ Pop3 pc
, int bFlushCache
)
516 /* see if we already have a password, as provided in the config file, or
517 already requested from the user. */
518 if (PCU
.interactive_password
) {
519 if (strlen(PCU
.password
) == 0) {
520 /* we need to grab the password from the user. */
522 POP_DM(pc
, DEBUG_INFO
, "asking for password %d\n",
525 passwordFor(PCU
.userName
, PCU
.serverName
, pc
, bFlushCache
);
526 if (password
!= NULL
) {
527 if (strlen(password
) + 1 > BUF_SMALL
) {
528 DMA(DEBUG_ERROR
, "Password is too long.\n");
529 memset(PCU
.password
, 0, BUF_SMALL
- 1);
531 strncpy(PCU
.password
, password
, BUF_SMALL
- 1);
532 PCU
.password_len
= strlen(PCU
.password
);
535 // ENFROB(PCU.password);