1 /* ========================================================================
2 * Copyright 1988-2008 University of Washington
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
11 * ========================================================================
15 * Program: IPOP2D - IMAP to POP2 conversion server
17 * Author: Mark Crispin
19 * University of Washington
21 * Internet: MRC@Washington.EDU
23 * Date: 28 October 1990
24 * Last Edited: 13 February 2008
33 extern int errno
; /* just in case */
39 /* Autologout timer */
40 #define KODTIMEOUT 60*5
41 #define LOGINTIMEOUT 60*3
45 /* Size of temporary buffers */
60 char *version
= "75"; /* edit number of this server */
61 short state
= LISN
; /* server state */
62 short critical
= NIL
; /* non-zero if in critical code */
63 MAILSTREAM
*stream
= NIL
; /* mailbox stream */
64 time_t idletime
= 0; /* time we went idle */
65 unsigned long nmsgs
= 0; /* number of messages */
66 unsigned long current
= 1; /* current message number */
67 unsigned long size
= 0; /* size of current message */
68 char status
[MAILTMPLEN
]; /* space for status string */
69 char *user
= ""; /* user name */
70 char *pass
= ""; /* password */
71 unsigned long *msg
= NIL
; /* message translation vector */
72 char *logout
= "Logout";
73 char *goodbye
= "+ Sayonara\015\012";
76 /* Function prototypes */
78 int main (int argc
,char *argv
[]);
79 void sayonara (int status
);
84 short c_helo (char *t
,int argc
,char *argv
[]);
85 short c_fold (char *t
);
86 short c_read (char *t
);
87 short c_retr (char *t
);
88 short c_acks (char *t
);
89 short c_ackd (char *t
);
90 short c_nack (char *t
);
94 int main (int argc
,char *argv
[])
98 char *pgmname
= (argc
&& argv
[0]) ?
99 (((s
= strrchr (argv
[0],'/')) || (s
= strrchr (argv
[0],'\\'))) ?
100 s
+1 : argv
[0]) : "ipop2d";
101 /* set service name before linkage */
102 mail_parameters (NIL
,SET_SERVICENAME
,(void *) "pop");
104 if (mail_parameters (NIL
,GET_DISABLEPLAINTEXT
,NIL
)) {
105 goodbye
= "- POP2 server disabled on this system\015\012";
108 /* initialize server */
109 server_init (pgmname
,"pop",NIL
,clkint
,kodint
,hupint
,trmint
,NIL
);
110 /* There are reports of POP2 clients which get upset if anything appears
111 * between the "+" and the "POP2" in the greeting.
113 printf ("+ POP2 %s %s.%s server ready\015\012",tcp_serverhost (),
114 CCLIENTVERSION
,version
);
115 fflush (stdout
); /* dump output buffer */
116 state
= AUTH
; /* initial server state */
117 while (state
!= DONE
) { /* command processing loop */
118 idletime
= time (0); /* get a command under timeout */
119 alarm ((state
!= AUTH
) ? TIMEOUT
: LOGINTIMEOUT
);
120 clearerr (stdin
); /* clear stdin errors */
121 while (!fgets (cmdbuf
,TMPLEN
-1,stdin
)) {
122 if (ferror (stdin
) && (errno
== EINTR
)) clearerr (stdin
);
124 char *e
= ferror (stdin
) ?
125 strerror (errno
) : "Unexpected client disconnect";
126 alarm (0); /* disable all interrupts */
127 server_init (NIL
,NIL
,NIL
,SIG_IGN
,SIG_IGN
,SIG_IGN
,SIG_IGN
,SIG_IGN
);
128 sprintf (logout
= cmdbuf
,"%.80s while reading line",e
);
130 stream
= mail_close (stream
);
135 alarm (0); /* make sure timeout disabled */
136 idletime
= 0; /* no longer idle */
137 /* find end of line */
138 if (!strchr (cmdbuf
,'\012')) {
139 server_init (NIL
,NIL
,NIL
,SIG_IGN
,SIG_IGN
,SIG_IGN
,SIG_IGN
,SIG_IGN
);
140 logout
= "- Command line too long\015\012";
143 else if (!(s
= strtok (cmdbuf
," \015\012"))) {
144 server_init (NIL
,NIL
,NIL
,SIG_IGN
,SIG_IGN
,SIG_IGN
,SIG_IGN
,SIG_IGN
);
145 goodbye
= "- Missing or null command\015\012";
148 else { /* dispatch based on command */
149 ucase (s
); /* canonicalize case */
151 t
= strtok (NIL
,"\015\012");
152 if ((state
== AUTH
) && !strcmp (s
,"HELO")) state
= c_helo (t
,argc
,argv
);
153 else if ((state
== MBOX
|| state
== ITEM
) && !strcmp (s
,"FOLD"))
155 else if ((state
== MBOX
|| state
== ITEM
) && !strcmp (s
,"READ"))
157 else if ((state
== ITEM
) && !strcmp (s
,"RETR")) state
= c_retr (t
);
158 else if ((state
== NEXT
) && !strcmp (s
,"ACKS")) state
= c_acks (t
);
159 else if ((state
== NEXT
) && !strcmp (s
,"ACKD")) state
= c_ackd (t
);
160 else if ((state
== NEXT
) && !strcmp (s
,"NACK")) state
= c_nack (t
);
161 else if ((state
== AUTH
|| state
== MBOX
|| state
== ITEM
) &&
162 !strcmp (s
,"QUIT")) {
163 server_init (NIL
,NIL
,NIL
,SIG_IGN
,SIG_IGN
,SIG_IGN
,SIG_IGN
,SIG_IGN
);
164 state
= DONE
; /* done in either case */
165 if (t
) goodbye
= "- Bogus argument given to QUIT\015\012";
166 else { /* expunge the stream */
167 if (stream
&& nmsgs
) stream
= mail_close_full (stream
,CL_EXPUNGE
);
168 stream
= NIL
; /* don't repeat it */
171 else { /* some other or inappropriate command */
172 server_init (NIL
,NIL
,NIL
,SIG_IGN
,SIG_IGN
,SIG_IGN
,SIG_IGN
,SIG_IGN
);
173 goodbye
= "- Bogus or out of sequence command\015\012";
177 fflush (stdout
); /* make sure output blatted */
179 /* clean up the stream */
180 if (stream
) mail_close (stream
);
182 return 0; /* stupid compilers */
187 * Accepts: exit status
192 void sayonara (int status
)
194 logouthook_t lgoh
= (logouthook_t
) mail_parameters (NIL
,GET_LOGOUTHOOK
,NIL
);
195 if (goodbye
) { /* have a goodbye message? */
196 fputs (goodbye
,stdout
);
197 fflush (stdout
); /* make sure blatted */
199 syslog (LOG_INFO
,"%s user=%.80s host=%.80s",logout
,
200 user
? (char *) user
: "???",tcp_clienthost ());
201 /* do logout hook if needed */
202 if (lgoh
) (*lgoh
) (mail_parameters (NIL
,GET_LOGOUTDATA
,NIL
));
203 _exit (status
); /* all done */
211 alarm (0); /* disable all interrupts */
212 server_init (NIL
,NIL
,NIL
,SIG_IGN
,SIG_IGN
,SIG_IGN
,SIG_IGN
,SIG_IGN
);
213 goodbye
= "- Autologout; idle for too long\015\012";
214 logout
= "Autologout";
215 state
= DONE
; /* mark state done in either case */
216 if (!critical
) { /* badly host if in critical code */
217 if (stream
&& !stream
->lock
) mail_close (stream
);
219 sayonara (1); /* die die die */
224 /* Kiss Of Death interrupt
229 /* only if in command wait */
230 if (idletime
&& ((time (0) - idletime
) > KODTIMEOUT
)) {
231 alarm (0); /* disable all interrupts */
232 server_init (NIL
,NIL
,NIL
,SIG_IGN
,SIG_IGN
,SIG_IGN
,SIG_IGN
,SIG_IGN
);
233 goodbye
= "- Killed (lost mailbox lock)\015\012";
234 logout
= "Killed (lost mailbox lock)";
235 state
= DONE
; /* mark state done in either case */
236 if (!critical
) { /* badly host if in critical code */
237 if (stream
&& !stream
->lock
) mail_close (stream
);
239 sayonara (1); /* die die die */
250 alarm (0); /* disable all interrupts */
251 server_init (NIL
,NIL
,NIL
,SIG_IGN
,SIG_IGN
,SIG_IGN
,SIG_IGN
,SIG_IGN
);
254 state
= DONE
; /* mark state done in either case */
255 if (!critical
) { /* badly host if in critical code */
256 if (stream
&& !stream
->lock
) mail_close (stream
);
258 sayonara (1); /* die die die */
263 /* Termination interrupt
268 alarm (0); /* disable all interrupts */
269 server_init (NIL
,NIL
,NIL
,SIG_IGN
,SIG_IGN
,SIG_IGN
,SIG_IGN
,SIG_IGN
);
270 goodbye
= "- Killed (terminated)\015\012";
271 logout
= "Killed (terminated)";
272 if (critical
) state
= DONE
; /* mark state done in either case */
273 /* Make no attempt at graceful closure since a shutdown may be in
274 * progress, and we won't have any time to do mail_close() actions.
276 else sayonara (1); /* die die die */
279 /* Parse HELO command
280 * Accepts: pointer to command argument
284 short c_helo (char *t
,int argc
,char *argv
[])
288 if ((!(t
&& *t
&& (u
= strtok (t
," ")) && (p
= strtok (NIL
,"\015\012")))) ||
289 (strlen (p
) >= TMPLEN
)) { /* get user name and password */
290 fputs ("- Missing user or password\015\012",stdout
);
293 /* copy password, handle quoting */
294 for (s
= tmp
; *p
; p
++) *s
++ = (*p
== '\\') ? *++p
: *p
;
295 *s
= '\0'; /* tie off string */
297 if (!(s
= strchr (u
,':'))) { /* want remote mailbox? */
298 /* no, delimit user from possible admin */
299 if ((s
= strchr (u
,'*')) != NULL
) *s
++ = '\0';
300 if (server_login (user
= cpystr (u
),pass
,s
,argc
,argv
)) {
301 syslog (LOG_INFO
,"%sLogin user=%.80s host=%.80s",s
? "Admin " : "",
302 user
,tcp_clienthost ());
303 return c_fold ("INBOX"); /* local; select INBOX */
306 #ifndef DISABLE_POP_PROXY
307 /* can't do if can't log in as anonymous */
308 else if (anonymous_login (argc
,argv
)) {
309 *s
++ = '\0'; /* separate host name from user name */
310 user
= cpystr (s
); /* note user name */
311 syslog (LOG_INFO
,"IMAP login to host=%.80s user=%.80s host=%.80s",u
,user
,
313 /* initially remote INBOX */
314 sprintf (tmp
,"{%.128s/user=%.128s}INBOX",u
,user
);
315 /* disable rimap just in case */
316 mail_parameters (NIL
,SET_RSHTIMEOUT
,0);
320 fputs ("- Bad login\015\012",stdout
);
324 /* Parse FOLD command
325 * Accepts: pointer to command argument
329 short c_fold (char *t
)
331 unsigned long i
,j
,flags
;
332 char *s
= NIL
,tmp
[2*TMPLEN
];
334 if (!(t
&& *t
)) { /* make sure there's an argument */
335 fputs ("- Missing mailbox name\015\012",stdout
);
338 myusername_full (&flags
); /* get user type flags */
339 /* expunge old stream */
340 if (stream
&& nmsgs
) mail_expunge (stream
);
341 nmsgs
= 0; /* no more messages */
342 if (msg
) fs_give ((void **) &msg
);
343 #ifndef DISABLE_POP_PROXY
344 if (flags
== MU_ANONYMOUS
) { /* don't permit proxy to leave IMAP */
345 if (stream
) { /* not first time */
346 if (!(stream
->mailbox
&& (s
= strchr (stream
->mailbox
,'}'))))
347 fatal ("bad previous mailbox name");
348 strncpy (tmp
,stream
->mailbox
,i
= (++s
- stream
->mailbox
));
349 if (i
>= TMPLEN
) fatal ("ridiculous network prefix");
350 strcpy (tmp
+i
,t
); /* append mailbox to initial spec */
353 /* must be net name first time */
354 else if (!mail_valid_net_parse (t
,&mb
)) fatal ("anonymous folder bogon");
357 /* open mailbox, note # of messages */
358 if ((j
= (stream
= mail_open (stream
,t
,NIL
)) ? stream
->nmsgs
: 0) != 0L){
359 sprintf (tmp
,"1:%lu",j
); /* fetch fast information for all messages */
360 mail_fetch_fast (stream
,tmp
,NIL
);
361 msg
= (unsigned long *) fs_get ((stream
->nmsgs
+ 1) *
362 sizeof (unsigned long));
363 for (i
= 1; i
<= j
; i
++) /* find undeleted messages, add to vector */
364 if (!mail_elt (stream
,i
)->deleted
) msg
[++nmsgs
] = i
;
366 #ifndef DISABLE_POP_PROXY
367 if (!stream
&& (flags
== MU_ANONYMOUS
)) {
368 fputs ("- Bad login\015\012",stdout
);
372 printf ("#%lu messages in %s\015\012",nmsgs
,stream
? stream
->mailbox
:
377 /* Parse READ command
378 * Accepts: pointer to command argument
382 short c_read (char *t
)
384 MESSAGECACHE
*elt
= NIL
;
385 if (t
&& *t
) { /* have a message number argument? */
386 /* validity check message number */
387 if (((current
= strtoul (t
,NIL
,10)) < 1) || (current
> nmsgs
)) {
388 fputs ("- Invalid message number given to READ\015\012",stdout
);
392 else if (current
> nmsgs
) { /* at end of mailbox? */
393 fputs ("=0 No more messages\015\012",stdout
);
396 /* set size if message valid and exists */
397 size
= msg
[current
] ? (elt
= mail_elt(stream
,msg
[current
]))->rfc822_size
: 0;
398 if (elt
) sprintf (status
,"Status: %s%s\015\012",
399 elt
->seen
? "R" : " ",elt
->recent
? " " : "O");
400 else status
[0] = '\0'; /* no status */
401 size
+= strlen (status
); /* update size to reflect status */
402 /* display results */
403 printf ("=%lu characters in message %lu\015\012",size
+ 2,current
);
408 /* Parse RETR command
409 * Accepts: pointer to command argument
413 short c_retr (char *t
)
417 if (t
) { /* disallow argument */
418 fputs ("- Bogus argument given to RETR\015\012",stdout
);
421 if (size
) { /* message size valid? */
422 t
= mail_fetch_header (stream
,msg
[current
],NIL
,NIL
,&i
,FT_PEEK
);
423 if (i
> 2) { /* only if there is something */
424 i
-= 2; /* lop off last two octets */
425 while (i
) { /* blat the header */
426 if (!(j
= fwrite (t
,sizeof (char),i
,stdout
))) return DONE
;
427 if (i
-= j
) t
+= j
; /* advance to incomplete data */
430 fputs (status
,stdout
); /* yes, output message */
431 fputs ("\015\012",stdout
); /* delimit header from text */
432 if ((t
= mail_fetch_text (stream
,msg
[current
],NIL
,&i
,FT_RETURNSTRINGSTRUCT
)) != NULL
)
433 while (i
) { /* blat the text */
434 if (!(j
= fwrite (t
,sizeof (char),i
,stdout
))) return DONE
;
435 if (i
-= j
) t
+= j
; /* advance to incomplete data */
437 else for (bs
= &stream
->private.string
; i
--; )
438 if (putc (SNX (bs
),stdout
) == EOF
) return DONE
;
439 fputs ("\015\012",stdout
); /* trailer to coddle PCNFS' NFSMAIL */
441 else return DONE
; /* otherwise go away */
445 /* Parse ACKS command
446 * Accepts: pointer to command argument
450 short c_acks (char *t
)
453 if (t
) { /* disallow argument */
454 fputs ("- Bogus argument given to ACKS\015\012",stdout
);
457 /* mark message as seen */
458 sprintf (tmp
,"%lu",msg
[current
++]);
459 mail_setflag (stream
,tmp
,"\\Seen");
460 return c_read (NIL
); /* end message reading transaction */
464 /* Parse ACKD command
465 * Accepts: pointer to command argument
469 short c_ackd (char *t
)
472 if (t
) { /* disallow argument */
473 fputs ("- Bogus argument given to ACKD\015\012",stdout
);
476 /* mark message as seen and deleted */
477 sprintf (tmp
,"%lu",msg
[current
]);
478 mail_setflag (stream
,tmp
,"\\Seen \\Deleted");
479 msg
[current
++] = 0; /* mark message as deleted */
480 return c_read (NIL
); /* end message reading transaction */
484 /* Parse NACK command
485 * Accepts: pointer to command argument
489 short c_nack (char *t
)
491 if (t
) { /* disallow argument */
492 fputs ("- Bogus argument given to NACK\015\012",stdout
);
495 return c_read (NIL
); /* end message reading transaction */
498 /* Co-routines from MAIL library */
501 /* Message matches a search
502 * Accepts: MAIL stream
506 void mm_searched (MAILSTREAM
*stream
,unsigned long msgno
)
512 /* Message exists (i.e. there are that many messages in the mailbox)
513 * Accepts: MAIL stream
517 void mm_exists (MAILSTREAM
*stream
,unsigned long number
)
519 /* Can't use this mechanism. POP has no means of notifying the client of
520 new mail during the session. */
525 * Accepts: MAIL stream
529 void mm_expunged (MAILSTREAM
*stream
,unsigned long number
)
531 if (state
!= DONE
) { /* ignore if closing */
532 /* someone else screwed us */
533 goodbye
= "- Mailbox expunged from under me!\015\012";
534 if (stream
&& !stream
->lock
) mail_close (stream
);
541 /* Message status changed
542 * Accepts: MAIL stream
546 void mm_flags (MAILSTREAM
*stream
,unsigned long number
)
548 /* This isn't used */
553 * Accepts: MAIL stream
554 * hierarchy delimiter
559 void mm_list (MAILSTREAM
*stream
,int delimiter
,char *name
,long attributes
)
561 /* This isn't used */
565 /* Subscribe mailbox found
566 * Accepts: MAIL stream
567 * hierarchy delimiter
572 void mm_lsub (MAILSTREAM
*stream
,int delimiter
,char *name
,long attributes
)
574 /* This isn't used */
579 * Accepts: MAIL stream
584 void mm_status (MAILSTREAM
*stream
,char *mailbox
,MAILSTATUS
*status
)
586 /* This isn't used */
589 /* Notification event
590 * Accepts: MAIL stream
595 void mm_notify (MAILSTREAM
*stream
,char *string
,long errflg
)
597 mm_log (string
,errflg
); /* just do mm_log action */
601 /* Log an event for the user to see
602 * Accepts: string to log
606 void mm_log (char *string
,long errflg
)
609 case NIL
: /* information message */
610 case PARSE
: /* parse glitch */
611 break; /* too many of these to log */
612 case WARN
: /* warning */
613 syslog (LOG_DEBUG
,"%s",string
);
615 case BYE
: /* driver broke connection */
617 char tmp
[MAILTMPLEN
];
618 alarm (0); /* disable all interrupts */
619 server_init (NIL
,NIL
,NIL
,SIG_IGN
,SIG_IGN
,SIG_IGN
,SIG_IGN
,SIG_IGN
);
620 sprintf (logout
= tmp
,"Mailbox closed (%.80s)",string
);
624 case ERROR
: /* error that broke command */
625 default: /* default should never happen */
626 syslog (LOG_NOTICE
,"%s",string
);
632 /* Log an event to debugging telemetry
633 * Accepts: string to log
636 void mm_dlog (char *string
)
638 /* Not doing anything here for now */
642 /* Get user name and password for this host
643 * Accepts: parse of network mailbox name
644 * where to return user name
645 * where to return password
649 void mm_login (NETMBX
*mb
,char *username
,char *password
,long trial
)
652 strncpy (username
,*mb
->user
? mb
->user
: user
,NETMAXUSER
-1);
653 strncpy (password
,pass
,255); /* and password */
654 username
[NETMAXUSER
] = password
[255] = '\0';
657 /* About to enter critical code
661 void mm_critical (MAILSTREAM
*stream
)
667 /* About to exit critical code
671 void mm_nocritical (MAILSTREAM
*stream
)
680 * flag indicating that mailbox may be clobbered
681 * Returns: abort flag
684 long mm_diskerror (MAILSTREAM
*stream
,long errcode
,long serious
)
686 if (serious
) { /* try your damnest if clobberage likely */
688 "Retrying after disk error user=%.80s host=%.80s mbx=%.80s: %.80s",
689 user
,tcp_clienthost (),
690 (stream
&& stream
->mailbox
) ? stream
->mailbox
: "???",
692 alarm (0); /* make damn sure timeout disabled */
693 sleep (60); /* give it some time to clear up */
696 syslog (LOG_ALERT
,"Fatal disk error user=%.80s host=%.80s mbx=%.80s: %.80s",
697 user
,tcp_clienthost (),
698 (stream
&& stream
->mailbox
) ? stream
->mailbox
: "???",
704 /* Log a fatal error event
705 * Accepts: string to log
708 void mm_fatal (char *string
)
710 mm_log (string
,ERROR
); /* shouldn't happen normally */