imap._pike
Go to the documentation of this file.
1 /* Copyright (C) 2002-2004 Christian Schmidt
2  *
3  * This program is free software; you can redistribute it and/or modify
4  * it under the terms of the GNU General Public License as published by
5  * the Free Software Foundation; either version 2 of the License, or
6  * (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, write to the Free Software
15  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16  */
17 inherit "/net/coal/login";
18 inherit "/net/base/line";
19  inherit Events.Listener;
20 #include <macros.h>
21 #include <config.h>
22 #include <database.h>
23 #include <events.h>
24 #include <client.h>
25 #include <attributes.h>
26 #include <classes.h>
27 #include <mail.h>
28 class imap : public login,line{
29 public:
30 
31 
32 /**
33  * implements a imap4-server, see rfc3501 (http://www.ietf.org/rfc/rfc3501.txt)
34  * sTeam-documents are converted using the messaging-module (/libraries/messaging.pmod)
35  *
36  * NOTE: this server uses '/' as the hierarchy-delimiter (see rfc for details)
37  * do NOT change this, it's hardcoded in _many_ places and WILL cause trouble!!
38  */
39 
40 import Messaging;
41 
42 
43 
44 
45 
46 //#define DEBUG_IMAP
47 
48 #ifdef DEBUG_IMAP
49 #define LOG_IMAP(s, args...) werror("imap: "+s+"\n", args)
50 #else
51 #define LOG_IMAP
52 #endif
53 
54 
55  int _state = STATE_NONAUTHENTICATED;
56  Messaging.BaseMailBox oMailBox; //stores the current selected mailbox
57  Messaging.BaseMailBox oInbox; //keeps the inbox, for performance...
58  Messaging.BaseMailBox oWorkarea;
59  array aSubscribedFolders=({}); //folders a user has subscribed to
60  int iUIDValidity=0;
61  mapping (int:int) mMessageNums=([]);
62 int iContinue=0; //for command continuation request
63 int iBytes=0;
64 string sData="";
65 string sCurrentCommand="";
66 array(IMAPListener) alEnter, alLeave;
67 
68 //the following maps commands to functions
69 //depending on the state of the server
70 protected:
71  mapping mCmd = ([
72  STATE_NONAUTHENTICATED: ([
73  "CAPABILITY": capability,
74  "NOOP": noop,
75  "LOGOUT": logout,
76  "AUTHENTICATE": authenticate,
77  "LOGIN": login,
78  "STARTTLS": starttls,
79  ]),
80  STATE_AUTHENTICATED: ([
81  "CAPABILITY": capability,
82  "NOOP": noop,
83  "LOGOUT": logout,
84  "SELECT": select,
85  "EXAMINE": examine,
86  "CREATE": do_create,
87  "DELETE": delete,
88  "RENAME": rename,
89  "SUBSCRIBE": subscribe,
90  "UNSUBSCRIBE": unsubscribe,
91  "LIST": list,
92  "LSUB": lsub,
93  "STATUS": status,
94  "APPEND": append,
95  ]),
96  STATE_SELECTED: ([
97  "CAPABILITY": capability,
98  "NOOP": noop,
99 
100  "LOGOUT": logout,
101  "SELECT": select,
102  "EXAMINE": examine,
103  "CREATE": do_create,
104  "DELETE": delete,
105  "RENAME": rename,
106  "SUBSCRIBE": subscribe,
107  "UNSUBSCRIBE": unsubscribe,
108  "LIST": list,
109  "LSUB": lsub,
110  "STATUS": status,
111  "APPEND": append,
112  "CHECK": check,
113  "CLOSE": close,
114  "EXPUNGE": expunge,
115  "SEARCH": do_search,
116  "FETCH": fetch,
117  "STORE": store,
118  "COPY": copy,
119  "UID": uid,
120  ]),
121 ]);
122 
123 
124 
125 /**********************************************************
126  * conversion, parser...
127  */
128 
129 //converts a timestamp to a human-readable form
130 protected:
131  string time_to_string(int timestamp)
132 {
133  array month=({"Jan","Feb","Mar","Apr","May","Jun",
134  "Jul","Aug","Sep","Oct","Nov","Dec"});
135 
136  mapping(string:int) parts=localtime(timestamp);
137  parts["year"]+=1900;
138  string result;
139  if(parts["mday"]<10) result=" "+parts["mday"];
140  else result=(string)parts["mday"];
141  result=result+"-"+month[parts["mon"]]+"-"+parts["year"]+" ";
142  if(parts["hour"]<10) result+="0"+parts["hour"];
143  else result+=parts["hour"];
144  result+=":";
145 
146  if(parts["min"]<10) result+="0"+parts["min"];
147  else result+=parts["min"];
148  result+=":";
149 
150  if(parts["sec"]<10) result+="0"+parts["sec"];
151  else result+=parts["sec"];
152  result+=" ";
153 
154  int timezone=parts["timezone"]/-3600;
155  if(timezone<0)
156  {
157  timezone=0-timezone;
158  result+="-";
159  }
160  else result+="+";
161  if(timezone<10) result=result+"0"+timezone+"00";
162  else result=result+timezone+"00";
163  return result;
164 }
165 
166 public:
167 
168 //convert a flag-pattern to a string
169 protected:
170  string flags_to_string(int flags)
171 {
172  string t="";
173 
174  if (flags==0) return t;
175 
176  if (flags & SEEN) t=t+"\\Seen ";
177  if (flags & ANSWERED) t=t+"\\Answered ";
178  if (flags & FLAGGED) t=t+"\\Flagged ";
179  if (flags & DELETED) t=t+"\\Deleted ";
180  if (flags & DRAFT) t=t+"\\Draft ";
181 
182  t=String.trim_whites(t);
183 
184  return t;
185 }
186 
187 public:
188 
189 //convert a flag-string to a number
190 protected:
191  int string_to_flags(string flags)
192 {
193  int t=0;
194  if(flags=="") return 0;
195 
196  array parts = flags/" ";
197  int err=0;
198 
199  for (int i=0;i<sizeof(parts);i++) //parse flags
200  {
201  string tmp=upper_case(parts[i]);
202  tmp=String.trim_whites(tmp); //remove trailing whitespace
203  switch(tmp)
204  {
205  case "\\SEEN":
206  t=t|SEEN;
207  break;
208  case "\\ANSWERED":
209  t=t|ANSWERED;
210  break;
211  case "\\FLAGGED":
212  t=t|FLAGGED;
213  break;
214  case "\\DELETED":
215  t=t|DELETED;
216  break;
217  case "\\DRAFT":
218  t=t|DRAFT;
219  break;
220  default: //unsupported flag -> error!
221  LOG_IMAP("Unknown flag in STORE: "+tmp);
222  err=1;
223  }
224  }
225 
226  if(err) t=-1;
227 
228  return t;
229 }
230 
231 public:
232 
233 
234 //convert a range-string ("4:7") to array (4,5,6,7)
235 //changed to array ({ 4, 7 }) (min, max) now
236 #if 0
237 protected:
238  array parse_range(string range)
239 {
240  array set=({});
241 
242  if(sscanf(range,"%d:%d", int minrange, int maxrange)==2)
243  {
244  //for(int i=min;i<=max;i++) set=set+({i});
245  return ({ minrange, maxrange });
246  }
247  else if(sscanf(range,"%d",int val)==1) set=set+({val});
248  //if range can't be parsed, an empty array is returned
249 
250  return set;
251 }
252 
253 public:
254 #else
255 protected:
256  array parse_range(string range)
257 {
258  if(sscanf(range,"%d:%d", int minrange, int maxrange)==2)
259  {
260  return ({ ({ minrange, maxrange }) });
261  }
262  else if(sscanf(range,"%d",int val)==1)
263  return ({ val });
264  return ({ });
265 }
266 
267 public:
268 #endif
269 
270 //convert a set ("2,4:7,12") to array (2,4,5,6,7,12);
271 //now its ( 2,(4,7),12 )
272 protected:
273  array parse_set(string range)
274 {
275  array set=({});
276 
277  array parts=range/","; //split range into single ranges/numbers
278  foreach(parts,string tmp) {set=set+parse_range(tmp);}
279 
280  return set;
281 }
282 
283 public:
284 
285 //split a quoted string into its arguments
286 protected:
287  array parse_quoted_string(string data)
288 {
289  array result=({});
290 
291  if(search(data,"\"")!=-1)
292  {
293  //process string
294  int i=0,j=0;
295  while(i<sizeof(data))
296  {
297  switch (data[i])
298  {
299  case '\"':
300  j=search(data,"\"",i+1); //search for matching "
301  if (j==-1) return ({}); //syntax error
302  else result=result+({data[i+1..j-1]});
303  i=j+1;
304  break;
305  case ' ':
306  i=i+1;
307  break;
308  default:
309  j=search(data," ",i); //unquoted string mixed with quoted string
310  if (j==-1)
311  {
312  result=result+({data[i..sizeof(data)-1]});
313  i=sizeof(data);
314  }
315  else
316  {
317  result=result+({data[i..j-1]});
318  i=j+1;
319  }
320  break;
321  }
322  }
323  }
324  else result=data/" "; //data had no ", just split at spaces
325 
326  return result;
327 }
328 
329 public:
330 
331 //remove the quoting "..." from a string
332 protected:
333  string unquote_string(string data)
334 {
335  if(search(data,"\"")==-1)
336  return data;
337  else
338  return data[1..sizeof(data)-2];
339 }
340 
341 public:
342 
343 string mimetype(object obj)
344 {
345  mapping header=obj->query_attribute(MAIL_MIMEHEADERS);
346  string tmp;
347  if(mappingp(header))
348  {
349  tmp=header["content-type"];
350  if(!zero_type(tmp))
351  sscanf(tmp,"%s;",tmp);
352  else tmp=obj->query_attribute(DOC_MIME_TYPE);
353  }
354  else tmp=obj->query_attribute(DOC_MIME_TYPE);
355  return upper_case(tmp);
356 }
357 
358 //parse the parameter of a fetch-command
359 //see rfc3501 for details
360 protected:
361  array parse_fetch_string(string data)
362 {
363  array result=({});
364  array tmp;
365 
366  if(data[0]=='(')
367  {
368  if(data[sizeof(data)-1]==')')
369  {
370  data=data[1..sizeof(data)-2]; //remove ()
371  tmp=parse_quoted_string(data);
372  }
373  }
374  else tmp=({data}); //parameter has only one argument
375 
376  int i=0;
377  while(i<sizeof(tmp))
378  {
379  switch(upper_case(tmp[i]))
380  {
381  case "ENVELOPE":
382  case "FLAGS":
383  case "INTERNALDATE":
384  case "RFC822":
385  case "RFC822.HEADER":
386  case "RFC822.SIZE":
387  case "RFC822.TEXT":
388  case "BODY":
389  case "BODYSTRUCTURE":
390  case "UID":
391  string t=upper_case(tmp[i]);
392  result=({t})+result;
393  i++;
394  break;
395  case "ALL":
396  result=({"FLAGS","INTERNALDATE","RFC822.SIZE","ENVELOPE"})+result;
397  i++;
398  break;
399  case "FAST":
400  result=({"FLAGS","INTERNALDATE","RFC822.SIZE"})+result;
401  i++;
402  break;
403  case "FULL":
404  result=({"FLAGS","INTERNALDATE","RFC822.SIZE","ENVELOPE","BODY"})+
405  result;
406  i++;
407  break;
408  default:
409  if(search(upper_case(tmp[i]),"BODY")!=-1) //"BODY..." has special syntax
410  {
411  string t="";
412  int j=i+1;
413  if(j==sizeof(tmp)) //last argument, no further processing needed
414  {
415  result+=({upper_case(tmp[i])});
416  return result;
417  }
418  if(search(tmp[i],"]")==-1)
419  {
420  while(search(tmp[j],"]")==-1 && j<sizeof(tmp))
421  j++; //search for closing ]
422  if(j<sizeof(tmp))
423  for(int a=i;a<=j;a++) t+=tmp[a]+" ";
424  //copy the whole thing as one string
425  else
426  {
427  LOG_IMAP("unexpected end of string while parsing BODY...");
428  return ({}); //syntax error
429  }
430 
431  t=t[0..sizeof(t)-2];
432  result+=({t});
433  i=j+1;
434  }
435  else
436  {
437  result+=({upper_case(tmp[i])});
438  i++;
439  }
440  }
441  else
442  {
443  LOG_IMAP("unknown argument to FETCH found: "+upper_case(tmp[i]));
444  return ({}); //syntax error
445  }
446  }//switch
447  }//while
448  return result;
449 }
450 
451 public:
452 
453 //reformat a mail-adress, see rfc3501
454 string adress_structure(string data)
455 {
456  data-="\"";
457  string result="(";
458 
459  array parts=data/",";
460  for(int i=0;i<sizeof(parts);i++)
461  {
462  string name,box,host;
463  int res=sscanf(parts[i],"%s<%s@%s>",name,box,host);
464  if(res!=3)
465  {
466  res=sscanf(parts[i],"%s@%s",box,host);
467  if (res!=2)
468  {
469  LOG_IMAP("parse error in adress_structure() !");
470  return ""; //parse error
471  }
472  name="NIL";
473  }
474  if(sizeof(name)==0) name="NIL";
475  else
476  {
477  name=String.trim_whites(name);
478  name="\""+name+"\"";
479  }
480  result+="("+name+" NIL \""+box+"\" \""+host+"\")";
481  }
482 
483  result+=")";
484  return result;
485 }
486 
487 //convert header-informations to structured envelope-data
488 string get_envelope_data(int num)
489 {
490  mapping(string:string) headers=oMailBox->get_message(num)->header();
491  string t,result="(\"";
492 
493  t=headers["date"];
494  if(t==0) t=time_to_string(oMailBox->get_message(num)->internal_date());
495  result=result+t+"\" ";
496 
497  t=headers["subject"];
498  if(t==0) t="";
499  result=result+"\""+t+"\" ";
500 
501  string from=headers["from"];
502  if(from==0) from="NIL";
503  else from=adress_structure(from);
504  result=result+from+" ";
505 
506  t=headers["sender"];
507  if(t==0) t=from;
508  else t=adress_structure(t);
509  result=result+t+" ";
510 
511  t=headers["reply-to"];
512  if(t==0) t=from;
513  else t=adress_structure(t);
514  result=result+t+" ";
515 
516  t=headers["to"];
517  if(t==0) t="NIL";
518  else t=adress_structure(t);
519  result=result+t+" ";
520 
521  t=headers["cc"];
522  if(t==0) t="NIL";
523  else t=adress_structure(t);
524  result=result+t+" ";
525 
526  t=headers["bcc"];
527  if(t==0) t="NIL";
528  else t=adress_structure(t);
529  result=result+t+" ";
530 
531  t=headers["in-reply-to"];
532  if(t==0) t="NIL";
533  else t="\""+t+"\"";
534  result=result+t+" ";
535 
536  t=headers["message-id"];
537  if(t==0) t="NIL";
538  else t="\""+t+"\"";
539  result=result+t;
540 
541  result+=")";
542  return result;
543 }
544 
545 //combine all headers of a message to one string
546 string headers_to_string(mapping headers)
547 {
548  string result="";
549 
550  foreach(indices(headers),string key)
551  result+=String.capitalize(key)+": "+headers[key]+"\r\n";
552 
553  return result+"\r\n"; //header and body are seperated by newline
554 }
555 
556 //parse & process the "BODY..." part of a fetch-command
557 //see rfc3501 for complete syntax of "BODY..."
558 string process_body_command(Message msg, string data)
559 {
560  string result,tmp,dummy,cmd,arg;
561  mapping(string:string) headers;
562  int i=0;
563 
564  data-=".PEEK"; //already processed in fetch(...)
565  while(data[i]!='[' && i<sizeof(data)) i++;
566  if(i==sizeof(data)) return ""; //parse error
567  result=data[0..i];
568  tmp=data[i+1..sizeof(data)-2];
569  if(sscanf(tmp,"%s(%s)", cmd, arg)==0)
570  cmd=tmp;
571  cmd-=" ";
572  switch(cmd)
573  {
574  case "HEADER":
575  headers=msg->header();
576  dummy=headers_to_string(headers);
577  result+="HEADER] {"+sizeof(dummy)+"}\r\n"+dummy;
578  break;
579  case "TEXT":
580  dummy=msg->body()+"\r\n";
581  result+="TEXT] {"+sizeof(dummy)+"}\r\n"+dummy;
582  break;
583  case "HEADER.FIELDS":
584  dummy="";
585  headers=msg->header();
586  array wanted=arg/" ";
587  foreach(wanted,string key)
588  if(headers[lower_case(key)]!=0)
589  dummy+=String.capitalize(lower_case(key))+
590  ": "+headers[lower_case(key)]+"\r\n";
591  dummy+="\r\n";
592  result+="HEADER] {"+sizeof(dummy)+"}\r\n"+dummy;
593  break;
594  default:
595  int part;
596  if(sscanf(cmd,"%d",part)==1)
597  {
598  object target;
599  if(msg->has_attachments())
600  {
601  target=msg->attachments()[part-1];
602  dummy=target->body()+"\r\n";
603  }
604  else
605  dummy=msg->body();
606 
607  result+=part+"] {"+sizeof(dummy)+"}\r\n"+dummy;
608  }
609  else
610  {
611  dummy=msg->complete_text()+"\r\n";
612  result+="] {"+sizeof(dummy)+"}\r\n"+dummy;
613  }
614  break;
615  }
616 
617  return result;
618 }
619 
620 string get_bodystructure_msg(Messaging.Message obj)
621 {
622  mapping header;
623  string type,subtype,result,tmp;
624 
625  type=obj->type();
626  subtype=obj->subtype();
627  result="(\""+type+"\" \""+subtype+"\" ";
628  header=obj->header();
629  tmp=header["content-type"];
630  LOG_IMAP("content-type header:%O",tmp);
631  if(!zero_type(tmp) && (search(tmp,";")!=-1))
632  {
633  sscanf(tmp,"%*s; %s",tmp);
634  array parts=tmp/";";
635  LOG_IMAP("parts=%O",parts);
636  result+="(";
637  foreach(parts, string part)
638  {
639  part = String.trim_whites(part);
640  sscanf(part,"%s=%s",string left, string right);
641  right-="\"";
642  result+="\""+upper_case(left)+"\" \""+right+"\" ";
643  }
644  result = String.trim_whites(result) + ") ";
645  }
646  else result+="NIL ";
647 
648  tmp=header["content-id"];
649  if(!zero_type(tmp))
650  result+="\""+tmp+"\" ";
651  else result+="NIL ";
652  tmp=header["content-description"];
653  if(!zero_type(tmp))
654  result+="\""+tmp+"\" ";
655  else result+="NIL ";
656  tmp=header["content-transfer-encoding"];
657  if(!zero_type(tmp))
658  result+="\""+tmp+"\" ";
659  else result+="\"8BIT\" ";
660 
661  int size=obj->body_size();
662  if(obj->is_attachment()) size+=2;
663  result+=size+" ";
664  result+=sizeof(obj->body()/"\n")+")";
665 
666  return result;
667 }
668 
669 //get the imap-bodystructure of a message
670 string get_bodystructure(Message msg)
671 {
672  string result="";
673 
674  int iAttch=msg->has_attachments();
675  if(iAttch)
676  {
677  array(Message) elems=msg->attachments();
678  result="(";
679  for(int i=0;i<sizeof(elems);i++)
680  result+=get_bodystructure_msg(elems[i]);
681  result+=" \"MIXED\")";
682  }
683  else
684  result+=get_bodystructure_msg(msg);
685  return result;
686 }
687 
688 protected:
689  void send_reply_untagged(string msg)
690 {
691  send_message("* "+msg+"\r\n");
692 }
693 
694 public:
695 
696 protected:
697  void send_reply(string tag, string msg)
698 {
699  call(send_message, 0, tag+" "+msg+"\r\n");
700 }
701 
702 public:
703 
704 protected:
705  void send_reply_continue(string msg)
706 {
707  send_message("+ "+msg+"\r\n");
708 }
709 
710 public:
711 
712 void create(object f)
713 {
714  ::create(f);
715 
716  string sTime=ctime(time());
717  sTime=sTime-"\n"; //remove trailing LF
718  send_reply_untagged("OK IMAP4rev1 Service Ready on "+_Server->get_server_name()+", "+sTime);
719 }
720 
721 //called automatic for selected events
722 void notify_enter(int event, mixed ... args)
723 {
724  if(args[0]->get_object_id()!=iUIDValidity) return;
725  //target object is not the mailbox -> ignore this event
726  object what;
727  if(event & EVENTS_MONITORED) what=args[3];
728  else
729  {
730  if(event & EVENT_ANNOTATE) what=args[2];
731  else what=args[1];
732  }
733  if(what->get_object_class() & oMailBox->allowed_types())
734  { //only update if new object can be converted to mail
735  int id=what->get_object_id();
736  LOG_IMAP(oUser->get_identifier()+" recieved new mail #"+id);
737  if(!zero_type(mMessageNums[id]))
738  LOG_IMAP("ignored - mail is not new...");
739  else
740  {
741  int num=oMailBox->get_num_messages();
742  send_reply_untagged(num+" EXISTS");
743  mMessageNums+=([id:num]); //new message added, update mapping of uids to msns
744  }
745  }
746 }
747 
748 void notify_leave(int event, mixed ... args)
749 {
750  if(args[0]->get_object_id()!=iUIDValidity) return;
751  //target object is not the mailbox -> ignore this event
752  object what;
753  if(event & EVENTS_MONITORED) what=args[3];
754  else
755  {
756  if(event & EVENT_REMOVE_ANNOTATION) what=args[2];
757  else what=args[1];
758  }
759  if(what->get_object_class() & oMailBox->allowed_types())
760  { //only update if removed object can be converted to mail
761  int id=what->get_object_id();
762  LOG_IMAP("Mail #"+id+
763  " removed from mailbox of "+oUser->get_identifier());
764  if(zero_type(mMessageNums[id]))
765  LOG_IMAP("ignored - mail is already removed...");
766  else
767  {
768  send_reply_untagged(mMessageNums[id]+" EXPUNGE");
769  m_delete(mMessageNums,id);
770  //message deleted, remove its record from mapping of uids to msns
771  }
772  }
773 }
774 
775 class IMAPListener {
776 public:
777 
778  function fCallback; //stores the callback function
779  void create(int events, object obj, function callback) {
780  ::create(events, PHASE_NOTIFY, obj, 0);
781  fCallback = callback;
782  obj->listen_event(this_object());
783  }
784 
785  void notify(int event, mixed args, object eObject) {
786  if ( functionp(fCallback) )
787  fCallback(event, @args);
788  }
789 
790  mapping save() { return 0; }
791 
792  string describe() {
793  return "IMAPListener()";
794  }
795 }
796 
797 void reset_listeners()
798 {
799  if(arrayp(alEnter))
800  foreach(alEnter,object tmp) destruct(tmp);
801  alEnter=({});
802  if(arrayp(alLeave))
803  foreach(alLeave,object tmp) destruct(tmp);
804  alLeave=({});
805 }
806 
807 /***************************************************************************
808  * IMAP commands
809  */
810 
811 
812 protected:
813  void capability(string tag, string params)
814 {
815  if ( sizeof(params)>0 ) send_reply(tag,"BAD arguments invalid");
816  else
817  {
818  send_reply_untagged("CAPABILITY IMAP4rev1");
819  send_reply(tag,"OK CAPABILITY completed");
820  }
821 }
822 
823 public:
824 
825 protected:
826  void noop(string tag, string params)
827 {
828  send_reply(tag,"OK NOOP completed");
829 }
830 
831 public:
832 
833 protected:
834  void logout(string tag, string params)
835 {
836  _state = STATE_LOGOUT;
837  reset_listeners();
838  if(objectp(oMailBox)) destruct(oMailBox);
839  if(objectp(oWorkarea)) destruct(oWorkarea);
840  send_reply_untagged("BYE server closing connection");
841  send_reply(tag,"OK LOGOUT complete");
842 
843  if( objectp(oUser) )
844  oUser->disconnect();
845 
846  close_connection();
847 }
848 
849 public:
850 
851 protected:
852  void authenticate(string tag, string params)
853 {
854  send_reply(tag,"NO AUTHENTICATE command not supported - use LOGIN instead");
855 }
856 
857 public:
858 
859 protected:
860  void starttls(string tag, string params)
861 {
862  send_reply(tag,"NO [ALERT] STARTTLS is not supported by this server");
863 }
864 
865 public:
866 
867 protected:
868  void login(string tag, string params)
869 {
870  array parts = parse_quoted_string(params);
871  if( sizeof(parts)==2 )
872  {
873  oUser = _Persistence->lookup_user(parts[0]);
874  if ( objectp(oUser) )
875  {
876  if ( oUser->check_user_password(parts[1]) ) //passwd ok, continue
877  {
878  login_user(oUser);
879  aSubscribedFolders=oUser->query_attribute(MAIL_SUBSCRIBED_FOLDERS);
880  if(!arrayp(aSubscribedFolders))
881  {
882  aSubscribedFolders=({});
883  oUser->set_attribute(MAIL_SUBSCRIBED_FOLDERS,aSubscribedFolders);
884  }
885  _state = STATE_AUTHENTICATED;
886  send_reply(tag,"OK LOGIN completed");
887 
888  LOG_IMAP("user "+oUser->get_identifier()+
889  " logged in, subscribed folders:%O",aSubscribedFolders);
890  }
891  else send_reply(tag,"NO LOGIN failed");
892  }
893  else send_reply(tag,"NO LOGIN failed");
894  }
895  else send_reply(tag,"BAD arguments invalid");
896 }
897 
898 public:
899 
900 protected:
901  void select(string tag, string params)
902 {
903  //deselect any selected mailbox
904  _state = STATE_AUTHENTICATED;
905  iUIDValidity=0;
906  reset_listeners();
907 
908  params=decode_mutf7(unquote_string(params));
909  array folders=params/"/";
910 
911  if ( upper_case(folders[0])=="INBOX" || folders[0]=="workarea" )
912  {
913  if (upper_case(folders[0])=="INBOX")
914  {
915  if(!objectp(oInbox))
916  oInbox = Messaging.get_mailbox(oUser);
917  oMailBox = oInbox;
918  }
919  else //path starts with "workarea"...
920  {
921  if(!objectp(oWorkarea))
922  oWorkarea = Messaging.get_mailbox(oUser->query_attribute(USER_WORKROOM));
923  oMailBox = oWorkarea;
924  }
925  if(sizeof(folders)>1) //subfolder of inbox or workarea
926  {
927  Messaging.BaseMailBox tmp = oMailBox;
928  tmp=tmp->get_subfolder(folders[1..sizeof(folders)-1]*"/");
929  if(objectp(tmp)) oMailBox = tmp;
930  else oMailBox=0; //subfolder doesn't exist
931  }
932 
933  if(objectp(oMailBox))
934  {
935  _state = STATE_SELECTED;
936  iUIDValidity=oMailBox->get_object_id();
937  mMessageNums=oMailBox->get_uid2msn_mapping();
938 // LOG_IMAP("selected mailbox #"+iUIDValidity);
939 // LOG_IMAP("mapping uid<->msn is: %O",mMessageNums);
940 
941  array events = oMailBox->enter_event();
942  foreach(events, int event)
943  events = oMailBox->leave_event();
944  foreach(events, int event)
945 
946  int num = oMailBox->get_num_messages();
947 
948  send_reply_untagged("FLAGS (\\Answered \\Deleted \\Seen \\Flagged \\Draft)");
949  send_reply_untagged("OK [PERMANENTFLAGS (\\Answered \\Deleted \\Seen \\Flagged \\Draft)]");
950  send_reply_untagged(num+" EXISTS");
951  send_reply_untagged("0 RECENT"); //"recent"-flag is not supported yet
952  send_reply_untagged("OK [UIDVALIDITY "+iUIDValidity+"] UIDs valid");
953 
954  send_reply(tag,"OK [READ-WRITE] SELECT completed");
955  }
956  else send_reply(tag,"NO SELECT failed, Mailbox does not exist");
957  }
958  else send_reply(tag,"NO SELECT failed, Mailbox does not exist");
959 }
960 
961 public:
962 
963 protected:
964  void examine(string tag, string params)
965 {
966  //deselect any selected mailbox
967  _state = STATE_AUTHENTICATED;
968  iUIDValidity=0;
969  reset_listeners();
970 
971  //TODO: support subfolders of inbox
972  if ( params=="INBOX" )
973  {
974  _state = STATE_SELECTED;
975  oMailBox = Messaging.get_mailbox(oUser);
976  iUIDValidity=oMailBox->get_object_id();
977 
978  int num = oMailBox->get_num_messages();
979 
980  send_reply_untagged("FLAGS (\\Answered \\Deleted \\Seen \\Flagged \\Draft)");
981  send_reply_untagged(num+" EXISTS");
982  send_reply_untagged("0 RECENT");
983  send_reply_untagged("OK [UIDVALIDITY "+iUIDValidity+"] UIDs valid");
984 
985  send_reply(tag,"OK [READ-ONLY] EXAMINE completed");
986  }
987  else send_reply(tag,"NO EXAMINE failed, Mailbox does not exist");
988 }
989 
990 public:
991 
992 protected:
993  void do_create(string tag, string params)
994 {
995  array parts = parse_quoted_string(decode_mutf7(params));
996 
997  if(sizeof(parts)==1)
998  {
999  array folders = parts[0]/"/"; //separate hierarchy-levels
1000  Messaging.BaseMailBox tmp;
1001  LOG_IMAP("CREATE: " +parts[0]);
1002  if(upper_case(folders[0])=="INBOX" || folders[0]=="workarea")
1003  {
1004  if(upper_case(folders[0])=="INBOX")
1005  {
1006  if(!objectp(oInbox))
1007  oInbox = Messaging.get_mailbox(oUser);
1008  tmp=oInbox;
1009  }
1010  else
1011  {
1012  if(!objectp(oWorkarea))
1013  oWorkarea = Messaging.get_mailbox(oUser->query_attribute(USER_WORKROOM));
1014  tmp=oWorkarea;
1015  }
1016  }
1017  else //try to create subfolder outside inbox or workarea
1018  {
1019  send_reply(tag,"NO [ALERT] cannot create top-level mailboxes");
1020  return;
1021  }
1022 
1023  int i=1;
1024  //skip folders in hierarchy that already exist
1025  while(i<sizeof(folders) && objectp(tmp->get_subfolder(folders[i])))
1026  {
1027  tmp=tmp->get_subfolder(folders[i]);
1028  i++;
1029  }
1030 
1031  //all subfolders listed in 'params' exist -> nothing to do...
1032  if(i==sizeof(folders))
1033  {
1034  send_reply(tag, "NO CREATE failed, folder already exists!");
1035  return;
1036  }
1037 
1038  //create ALL subfolders given in 'params' that do not exist
1039  while(i<sizeof(folders))
1040  {
1041  if(folders[i]!="")
1042  {
1043  LOG_IMAP("about to create folder "+folders[i]);
1044  int result=tmp->create_subfolder(folders[i]);
1045  if(result==0)
1046  {
1047  send_reply(tag,"NO CREATE unable to create that folder");
1048  return;
1049  }
1050  tmp=tmp->get_subfolder(folders[i]);
1051  LOG_IMAP("created folder "+folders[i]+"["+tmp->get_object_id()+"]");
1052  i++;
1053  }
1054  else
1055  {
1056  if(i==sizeof(folders)-1)
1057  {
1058  send_reply(tag,"OK CREATE completed");
1059  return;
1060  }
1061  LOG_IMAP("illegal call to CREATE: "+parts[0]);
1062  send_reply(tag,"NO CREATE unable to create that folder");
1063  return;
1064  }
1065  }
1066  send_reply(tag,"OK CREATE completed");
1067  }
1068  else send_reply(tag,"BAD arguments invalid");
1069 }
1070 
1071 public:
1072 
1073 protected:
1074  void delete(string tag, string params)
1075 {
1076  array parts = parse_quoted_string(decode_mutf7(params));
1077 
1078  if(sizeof(parts)==1)
1079  {
1080  LOG_IMAP("DELETE called for "+parts[0]);
1081  if(upper_case(parts[0])=="INBOX")
1082  {
1083  send_reply(tag,"NO cannot delete inbox");
1084  return;
1085  }
1086  array folders = parts[0]/"/";
1087  int i=0;
1088  Messaging.BaseMailBox tmp;
1089  if(upper_case(folders[0])=="INBOX")
1090  {
1091  i++;
1092  if(!objectp(oInbox))
1093  oInbox = Messaging.get_mailbox(oUser);
1094 
1095  tmp=oInbox;
1096  }
1097  int success=1;
1098 
1099  while(i<sizeof(folders) && success && objectp(tmp))
1100  {
1101  LOG_IMAP("searching for "+folders[i]+" in "+tmp->get_identifier()+"["+tmp->get_object_id()+"]");
1102  object tt=tmp->get_subfolder(folders[i]);
1103  if( !objectp(tt) )
1104  {
1105  success=0;
1106  LOG_IMAP("not found...");
1107  }
1108  else tmp=tt;
1109  i++;
1110  }
1111  if(!objectp(tmp)) success=0;
1112  if(success)
1113  {
1114  //delete folder folders[i] if empty
1115  LOG_IMAP("DELETE found mailbox "+parts[0]+": "+tmp->get_identifier()+"["+tmp->get_object_id()+"]");
1116  if(tmp->has_subfolders()==0)
1117  {
1118  int id=-1;
1119  if(objectp(oMailBox)) id=oMailBox->get_object_id();
1120  if(tmp->get_object_id()!=id)
1121  {
1122  LOG_IMAP("deleting mailbox "+parts[0]);
1123  tmp->delete();
1124  send_reply(tag,"OK DELETE succeded");
1125  }
1126  else send_reply(tag,"NO cannot delete selected folder, please deselect first");
1127  }
1128  else send_reply(tag,"NO folder has subfolders, delete them first");
1129  }
1130  else send_reply(tag,"NO folder does not exist");
1131  }
1132  else send_reply(tag,"BAD arguments invalid");
1133 }
1134 
1135 public:
1136 
1137 protected:
1138  void rename(string tag, string params)
1139 {
1140  array parts = parse_quoted_string(params);
1141 
1142  if(sizeof(parts)==2) send_reply(tag,"NO RENAME Permission denied");
1143  else send_reply(tag,"BAD arguments invalid");
1144 }
1145 
1146 public:
1147 
1148 protected:
1149  void subscribe(string tag, string params)
1150 {
1151  array parts = parse_quoted_string(decode_mutf7(params));
1152 
1153  if(sizeof(parts)==1)
1154  {
1155  array folders=parts[0]/"/"; //split mailbox-name at hierarchy-delimiter "/"
1156  if(!(upper_case(folders[0])=="INBOX" || folders[0]=="workarea"))
1157  {
1158  send_reply(tag,"NO SUBSCRIBE can't subscribe to that name");
1159  return;
1160  }
1161  Messaging.BaseMailBox tmp;
1162  if(upper_case(folders[0])=="INBOX")
1163  {
1164  folders[0]="INBOX";
1165  if(!objectp(oInbox))
1166  oInbox = Messaging.get_mailbox(oUser);
1167  tmp=oInbox;
1168  }
1169  else
1170  {
1171  if(!objectp(oWorkarea))
1172  oWorkarea = Messaging.get_mailbox(oUser->query_attribute(USER_WORKROOM));
1173  tmp=oWorkarea;
1174  }
1175  if(sizeof(folders)>1) tmp=tmp->get_subfolder(folders[1..sizeof(folders)-1]*"/");
1176  if(objectp(tmp))
1177  {
1178  string res=folders*"/";
1179  LOG_IMAP("subscribed to folder "+res);
1180  aSubscribedFolders+=({res});
1181  aSubscribedFolders=sort(Array.uniq(aSubscribedFolders));
1182  oUser->set_attribute(MAIL_SUBSCRIBED_FOLDERS,aSubscribedFolders);
1183  send_reply(tag,"OK SUBSCRIBE completed");
1184  }
1185  else send_reply(tag,"NO SUBSCRIBE can't subscribe to that name");
1186  }
1187  else send_reply(tag,"BAD arguments invalid");
1188 
1189  return;
1190 }
1191 
1192 public:
1193 
1194 protected:
1195  void unsubscribe(string tag, string params)
1196 {
1197  array parts = parse_quoted_string(decode_mutf7(params));
1198 
1199  if(sizeof(parts)==1)
1200  {
1201  if(search(aSubscribedFolders,parts[0])!=-1)
1202  {
1203  aSubscribedFolders-=({parts[0]});
1204  oUser->set_attribute(MAIL_SUBSCRIBED_FOLDERS,aSubscribedFolders);
1205  send_reply(tag,"OK UNSUBSCRIBE completed");
1206  }
1207  else send_reply(tag,"NO UNSUBSCRIBE can't unsubscribe that name");
1208  }
1209  else send_reply(tag,"BAD arguments invalid");
1210 
1211  return;
1212 }
1213 
1214 public:
1215 
1216 object validate_reference_name(string refname)
1217 {
1218  array parts=refname/"/"; parts-=({""});
1219  object startbox;
1220  if(upper_case(parts[0])=="INBOX") // "inbox" is case-insensitive
1221  {
1222  parts[0]=="INBOX";
1223  if(!objectp(oInbox))
1224  oInbox = Messaging.get_mailbox(oUser);
1225  startbox=oInbox;
1226  }
1227  else if(parts[0]=="workarea")
1228  {
1229  if(!objectp(oWorkarea))
1230  oWorkarea = Messaging.get_mailbox(oUser->query_attribute(USER_WORKROOM));
1231  startbox=oWorkarea;
1232  }
1233  else //start of reference name is invalid (not "inbox" or "workarea")
1234  return 0;
1235 
1236  object tmp;
1237  if(sizeof(parts)>1)
1238  tmp=startbox->get_subfolder(parts[1..sizeof(parts)-1]*"/");
1239  else tmp=startbox;
1240  if(objectp(tmp)) return tmp;
1241  else return 0;
1242 }
1243 
1244 protected:
1245  void list_all_folders(int depth)
1246 {
1247  array folders;
1248  int i;
1249  if(!objectp(oInbox))
1250  oInbox = Messaging.get_mailbox(oUser);
1251  if(!objectp(oWorkarea))
1252  oWorkarea = Messaging.get_mailbox(oUser->query_attribute(USER_WORKROOM));
1253  //list all subfolders of inbox
1254  folders=oInbox->list_subfolders(depth-1);
1255  send_reply_untagged("LIST () \"/\" \"INBOX\"");
1256  for(i=0;i<sizeof(folders);i++)
1257  send_reply_untagged("LIST () \"/\" \"INBOX/"+encode_mutf7(folders[i])+"\"");
1258  //list all subfolders of workarea
1259  folders=oWorkarea->list_subfolders(depth-1);
1260  send_reply_untagged("LIST () \"/\" \"workarea\"");
1261  for(i=0;i<sizeof(folders);i++)
1262  send_reply_untagged("LIST () \"/\" \"workarea/"+encode_mutf7(folders[i])+"\"");
1263 }
1264 
1265 public:
1266 
1267 protected:
1268  void list(string tag, string params)
1269 {
1270  array (string) args=parse_quoted_string(params);
1271 
1272  //first argument contains reference-name ("root" of mailbox-path in 2nd arg)
1273  //second argument contains mailbox name (wildcards allowed)
1274  //for further details see rfc3501, Section 6.3.8.
1275 
1276  if(sizeof(args)!=2)
1277  {
1278  send_reply(tag,"BAD LIST arguments invalid");
1279  return;
1280  }
1281 
1282  string refname = decode_mutf7(args[0]);
1283  string boxname = decode_mutf7(args[1]);
1284  string start;
1285  array parts;
1286  object startbox;
1287  LOG_IMAP("LIST called with reference: "+refname+" and mailbox: "+boxname);
1288  int result=0;
1289 
1290  if(refname!="")
1291  {
1292  startbox = validate_reference_name(refname);
1293  if(objectp(startbox)) result=1;
1294  parts=refname/"/"; parts-=({""});
1295  if(upper_case(parts[0])=="INBOX") parts[0]="INBOX";
1296  start=parts*"/" + "/"; //add hierarchy-delimiter at end of starting path
1297  LOG_IMAP("result of validate_reference_name: "+result);
1298 // LOG_IMAP("startbox: %O",startbox);
1299 // LOG_IMAP("start: "+start);
1300 // LOG_IMAP("parts: %O",parts);
1301  }
1302  if(boxname=="")
1303  {
1304  if(refname!="")
1305  {
1306  if(result==0)
1307  {
1308  send_reply(tag,"OK LIST completed");
1309  return;
1310  }
1311  }
1312  else //special case: boxname AND refname are empty
1313  {
1314  send_reply_untagged("LIST (\\Noselect) \"/\" \"\"");
1315  send_reply(tag,"OK LIST completed");
1316  return;
1317  }
1318  }
1319  else //boxname!=""
1320  {
1321  if(refname!="" && result==0)
1322  {
1323  send_reply(tag,"OK LIST completed");
1324  return;
1325  }
1326  }
1327  if(refname=="" && (boxname[0]=='%' || boxname=="*" ))
1328  {
1329  if(boxname=="*") list_all_folders(-1);
1330  else //boxname=="%"
1331  {
1332  int depth=sizeof(boxname/"/"-({}));
1333  list_all_folders(depth);
1334  }
1335  send_reply(tag,"OK LIST completed");
1336  return;
1337  }
1338  int i=0;
1339  if(upper_case(boxname)=="INBOX*")
1340  {
1341  boxname="*"; refname="INBOX/"; start="INBOX/";
1342  if(!objectp(oInbox))
1343  oInbox=Messaging.get_mailbox(oUser);
1344  startbox=oInbox;
1345  send_reply_untagged("LIST () \"/\" \"INBOX\"");
1346  }
1347  if(boxname=="workarea*")
1348  {
1349  boxname="*"; refname="workarea/"; start="workarea/";
1350  if(!objectp(oWorkarea))
1351  oWorkarea = Messaging.get_mailbox(oUser->query_attribute(USER_WORKROOM));
1352  startbox=oWorkarea;
1353  send_reply_untagged("LIST () \"/\" \"workarea\"");
1354  }
1355  parts=boxname/"/";
1356  if(upper_case(parts[0])=="INBOX") parts[0]="INBOX";
1357  if(refname=="")
1358  {
1359  if(parts[0]=="INBOX")
1360  {
1361  if(!objectp(oInbox))
1362  oInbox=Messaging.get_mailbox(oUser);
1363  startbox=oInbox;
1364  start="INBOX/";
1365  if(sizeof(parts)==1) send_reply_untagged("LIST () \"/\" \"INBOX\"");
1366  }
1367  else if(parts[0]=="workarea")
1368  {
1369  if(!objectp(oWorkarea))
1370  oWorkarea = Messaging.get_mailbox(oUser->query_attribute(USER_WORKROOM));
1371  startbox=oWorkarea;
1372  start="workarea/";
1373  if(sizeof(parts)==1) send_reply_untagged("LIST () \"/\" \"workarea\"");
1374  }
1375  if(sizeof(parts)==1)
1376  {
1377  send_reply(tag,"OK LIST completed");
1378  return;
1379  }
1380  }
1381  parts-=({"INBOX"});
1382  parts-=({"workarea"});
1383  while(i<sizeof(parts) && parts[i]!="%" && parts[i]!="*")
1384  i++; //search for first wildcard in boxname
1385  if(i==sizeof(parts) && parts[i-1]!="%" && parts[i-1]!="*") //no wildcard, test if folder exists
1386  {
1387  string path = parts[0..i-1]*"/";
1388  object tmp=startbox->get_subfolder(path);
1389  if(objectp(tmp))
1390  {
1391  start+=path;
1392  send_reply_untagged("LIST () \"/\" \""+encode_mutf7(start)+"\"");
1393  }
1394  send_reply(tag,"OK LIST completed");
1395  return;
1396  }
1397  if(i>0)
1398  {
1399  string path = parts[0..i-1]*"/"; //path until the first wildcard
1400  object tmp=startbox->get_subfolder(path);
1401  if(objectp(tmp))
1402  {
1403  startbox=tmp;
1404  start+=path+"/";
1405  }
1406  else //refname + boxname (without wildcards) is invalid
1407  {
1408  send_reply(tag,"OK LIST completed");
1409  return;
1410  }
1411  }
1412  int depth;
1413  if(parts[i]=="*") depth=-1; //get _all_ subfolders
1414  else // parts[i]=="%", "count" # of %'s to get depth
1415  {
1416  depth=1;
1417  if(i<sizeof(parts)-1) //current "%" is not the last part
1418  for(int j=i+1;j<sizeof(parts);j++)
1419  {
1420  if(parts[j]=="%") depth++;
1421  else
1422  {
1423  LOG_IMAP("error in LIST-Command: "+refname+" "+boxname);
1424  send_reply(tag,"NO LIST cannot list that reference or name");
1425  return;
1426  }
1427  }
1428  }
1429  array folders = startbox->list_subfolders(depth);
1430  if(arrayp(folders))
1431  for(int j=0;j<sizeof(folders);j++)
1432  send_reply_untagged("LIST () \"/\" \""+start+encode_mutf7(folders[j])+"\"");
1433  send_reply(tag,"OK LIST completed");
1434  return;
1435 }
1436 
1437 public:
1438 
1439 protected:
1440  void lsub(string tag, string params)
1441 {
1442  array args=parse_quoted_string(params);
1443  if(sizeof(args)==2)
1444  {
1445  args[0]=decode_mutf7(args[0]);
1446  args[1]=decode_mutf7(args[1]);
1447  if(args[0]=="" && (args[1]=="*" || upper_case(args[1])=="INBOX*"))
1448  {
1449  aSubscribedFolders=oUser->query_attribute(MAIL_SUBSCRIBED_FOLDERS);
1450  for(int i=0;i<sizeof(aSubscribedFolders);i++)
1451  send_reply_untagged("LSUB () \"/\" \""+encode_mutf7(aSubscribedFolders[i])+"\"");
1452  send_reply(tag,"OK LSUB completed");
1453  }
1454  else send_reply(tag,"OK LSUB completed");
1455  }
1456  else send_reply(tag,"BAD arguments invalid");
1457 }
1458 
1459 public:
1460 
1461 protected:
1462  void status(string tag, string params)
1463 {
1464  string mailbox, what;
1465  if(sscanf(params,"%s (%s)", mailbox, what)!=2)
1466  {
1467  send_reply(tag,"BAD arguments invalid");
1468  return;
1469  }
1470  Messaging.BaseMailBox mbox;
1471  array parts = decode_mutf7(unquote_string(mailbox))/"/";
1472  if(upper_case(parts[0])=="INBOX") //lookup mailbox
1473  {
1474  parts[0]="INBOX";
1475  if(!objectp(oInbox))
1476  oInbox = Messaging.get_mailbox(oUser);
1477  mbox = oInbox;
1478  }
1479  else if(parts[0]=="workarea")
1480  {
1481  if(!objectp(oWorkarea))
1482  oWorkarea = Messaging.get_mailbox(oUser->query_attribute(USER_WORKROOM));
1483  mbox = oWorkarea;
1484  }
1485  if(sizeof(parts)>1) mbox=mbox->get_subfolder(parts[1..sizeof(parts)-1]*"/");
1486  if(!objectp(mbox))
1487  {
1488  send_reply(tag,"NO mailbox does not exist");
1489  return;
1490  }
1491  mailbox=encode_mutf7(parts*"/");
1492  array items=what/" ";
1493  string result="";
1494  foreach(items, string tmp)
1495  {
1496  switch (upper_case(tmp))
1497  {
1498  case "MESSAGES":
1499  result+=" MESSAGES "+mbox->get_num_messages();
1500  break;
1501  case "RECENT":
1502  result+=" RECENT 0"; // recent-flag is not supported
1503  break;
1504  case "UIDNEXT":
1505  result+=" UIDNEXT 12345"; //TODO: return correct value
1506  break;
1507  case "UIDVALIDITY":
1508  result+=" UIDVALIDITY "+iUIDValidity;
1509  break;
1510  case "UNSEEN":
1511  int max=mbox->get_num_messages();
1512  int unseen=max;
1513  for(int i=0;i<max;i++)
1514  if(mbox->get_message(i)->flag()->has(SEEN)) unseen--;
1515  result+=" UNSEEN "+unseen;
1516  break;
1517  default:
1518  send_reply(tag,"BAD arguments invalid");
1519  return;
1520  }
1521  }
1522  result="("+String.trim_whites(result)+")";
1523  send_reply_untagged("STATUS \""+mailbox+"\" "+result);
1524  send_reply(tag,"OK STATUS completed");
1525 }
1526 
1527 public:
1528 
1529 protected:
1530  void append(string tag, string params)
1531 {
1532 // LOG_IMAP("APPEND called:%O",params);
1533  string sFolder, sData;
1534  if(sscanf(params,"%s %s",sFolder,sData)!=2)
1535  {
1536  send_reply(tag,"BAD Arguments invalid");
1537  return;
1538  }
1539  array parts=decode_mutf7(sFolder)/"/";
1540  if(upper_case(parts[0])=="INBOX" || parts[0]=="workarea")
1541  {
1542  Messaging.BaseMailBox tmp;
1543  if(upper_case(parts[0])=="INBOX")
1544  {
1545  if(!objectp(oInbox))
1546  oInbox=Messaging.get_mailbox(oUser);
1547  tmp=oInbox;
1548  }
1549  else
1550  {
1551  if(!objectp(oWorkarea))
1552  oWorkarea=Messaging.get_mailbox(oUser->query_attribute(USER_WORKROOM));
1553  tmp=oWorkarea;
1554  }
1555  tmp=tmp->get_subfolder(parts[1..sizeof(parts)-1]*"/");
1556  if(objectp(tmp))
1557  {
1558  Messaging.Message msg = Messaging.MIME2Message(sData);
1559  if(objectp(msg))
1560  {
1561  tmp->add_message(msg);
1562  send_reply(tag,"OK APPEND completed");
1563  }
1564  else send_reply(tag,"NO Syntax-error in data");
1565  }
1566  else send_reply(tag,"NO cannot append to non-existent folder");
1567  }
1568  else send_reply(tag,"NO cannot append to non-existent folder");
1569 }
1570 
1571 public:
1572 
1573 protected:
1574  void check(string tag, string params)
1575 {
1576  send_reply(tag,"OK CHECK completed");
1577 }
1578 
1579 public:
1580 
1581 protected:
1582  void close(string tag, string params)
1583 {
1584  _state = STATE_AUTHENTICATED;
1585 
1586  reset_listeners();
1587  oMailBox->delete_mails();
1588 
1589  send_reply(tag,"OK CLOSE completed");
1590 }
1591 
1592 public:
1593 
1594 protected:
1595  void expunge(string tag, string params)
1596 {
1597  oMailBox->delete_mails();
1598  /* This causes the mailbox-module to delete all mails, which have the
1599  * deleted-flag set. The notify-function of this socket is called then
1600  * with a suitable "leave"-event, which sends the required "* #msn EXPUNGE"
1601  * message(s) to the connected mailclient.
1602  */
1603 
1604  send_reply(tag,"OK EXPUNGE completed");
1605 }
1606 
1607 public:
1608 
1609 protected:
1610  void do_search(string tag, string params)
1611 {
1612  array parts = parse_quoted_string(params);
1613  int i=0,err=0;
1614  int not=0, or=0;
1615  int num=oMailBox->get_num_messages();
1616 
1617  array result=({});
1618  array tmp=({});
1619 
1620  while (i<sizeof(parts))
1621  {
1622  tmp=({});
1623  switch(parts[i])
1624  { //not all search-parameters are supported yet
1625  case "ALL":
1626  for(int j=0;j<num;j++) tmp=tmp+({j+1});
1627  result=tmp;
1628  i++;
1629  break;
1630  case "ANSWERED":
1631  for (int j=0;j<num;j++)
1632  if(oMailBox->get_message(j)->flag()->has(ANSWERED)) tmp=tmp+({j+1});
1633  result=result&tmp;
1634  i++;
1635  break;
1636  case "BCC":
1637  i+=2;
1638  break;
1639  case "BEFORE":
1640  i+=2;
1641  break;
1642  case "BODY":
1643  i+=2;
1644  break;
1645  case "CC":
1646  i+=2;
1647  break;
1648  case "DELETED":
1649  for (int j=0;j<num;j++)
1650  if(oMailBox->get_message(j)->flag()->has(DELETED)) tmp=tmp+({j+1});
1651  result=result&tmp;
1652  i++;
1653  break;
1654  case "FLAGGED":
1655  for (int j=0;j<num;j++)
1656  if(oMailBox->get_message(j)->flag()->has(FLAGGED)) tmp=tmp+({j+1});
1657  result=result&tmp;
1658  i++;
1659  break;
1660  case "FROM":
1661  i+=2;
1662  break;
1663  case "KEYWORD":
1664  i+=2;
1665  break;
1666  case "NEW":
1667  break;
1668  case "OLD":
1669  break;
1670  case "ON":
1671  i+=2;
1672  break;
1673  case "RECENT":
1674  i++;
1675  break;
1676  case "SEEN":
1677  for (int j=0;j<num;j++)
1678  if(oMailBox->get_message(j)->flag()->has(SEEN)) tmp=tmp+({j+1});
1679  result=result&tmp;
1680  i++;
1681  break;
1682  case "SINCE":
1683  i+=2;
1684  break;
1685  case "SUBJECT":
1686  i+=2;
1687  break;
1688  case "TEXT":
1689  i+=2;
1690  break;
1691  case "TO":
1692  i+=2;
1693  break;
1694  case "UNANSWERED":
1695  for (int j=0;j<num;j++)
1696  if(!oMailBox->get_message(j)->flag()->has(ANSWERED)) tmp=tmp+({j+1});
1697  result=result&tmp;
1698  i++;
1699  break;
1700  case "UNDELETED":
1701  for (int j=0;j<num;j++)
1702  if(!oMailBox->get_message(j)->flag()->has(DELETED)) tmp=tmp+({j+1});
1703  result=result&tmp;
1704  i++;
1705  break;
1706  case "UNFLAGGED":
1707  for (int j=0;j<num;j++)
1708  if(!oMailBox->get_message(j)->flag()->has(FLAGGED)) tmp=tmp+({j+1});
1709  result=result&tmp;
1710  i++;
1711  break;
1712  case "UNKEYWORD":
1713  i+=2;
1714  break;
1715  case "UNSEEN":
1716  for (int j=0;j<num;j++)
1717  if(!oMailBox->get_message(j)->flag()->has(SEEN)) tmp=tmp+({j+1});
1718  result=result&tmp;
1719  i++;
1720  break;
1721  case "DRAFT":
1722  for (int j=0;j<num;j++)
1723  if(oMailBox->get_message(j)->flag()->has(DRAFT)) tmp=tmp+({j+1});
1724  result=result&tmp;
1725  i++;
1726  break;
1727  case "HEADER":
1728  i+=3;
1729  break;
1730  case "LARGER":
1731  i+=2;
1732  break;
1733  case "NOT":
1734  not=1; i++;
1735  break;
1736  case "OR":
1737  or=1; i++;
1738  break;
1739  case "SENTBEFORE":
1740  i+=2;
1741  break;
1742  case "SENTON":
1743  i+=2;
1744  break;
1745  case "SENTSINCE":
1746  i+=2;
1747  break;
1748  case "SMALLER":
1749  i+=2;
1750  break;
1751  case "UID":
1752  i+=2;
1753  break;
1754  case "UNDRAFT":
1755  for (int j=0;j<num;j++)
1756  if(!oMailBox->get_message(j)->flag()->has(DRAFT)) tmp=tmp+({j+1});
1757  result=result&tmp;
1758  i++;
1759  break;
1760  default:
1761  //todo: support "(...)"
1762  tmp=parse_set(parts[i]);
1763  if (tmp!=({}))
1764  {
1765  result=result&tmp;
1766  i++;
1767  }
1768  else
1769  {
1770  send_reply(tag,"BAD arguments invalid");
1771  return;
1772  }
1773  break;
1774  }
1775  }//while
1776 
1777  if(!err)
1778  {
1779  string final_result="";
1780  for(i=0;i<sizeof(result);i++) final_result=final_result+" "+result[i];
1781  send_reply_untagged("SEARCH"+final_result);
1782  send_reply(tag,"OK SEARCH completed");
1783  }
1784  else send_reply(tag,"BAD arguments invalid");
1785 }
1786 
1787 public:
1788 
1789 
1790 protected:
1791  string fetch_result(int i, array parts, function getMessageFunc,void|int uid_mode)
1792 {
1793  string res=i+" FETCH (";
1794  if ( !functionp(getMessageFunc) )
1795  return "";
1796  Message msg = getMessageFunc(i);
1797  if ( !objectp(msg) )
1798  return "";
1799  if(uid_mode) res+="UID "+msg->get_object_id()+" ";
1800  for(int j=0;j<sizeof(parts);j++) {
1801  switch(parts[j]) {
1802  case "FLAGS":
1803  string tmp=flags_to_string(msg->flag()->get());
1804  res+="FLAGS ("+tmp+") ";
1805  break;
1806  case "UID":
1807  if(uid_mode) break; //UID is already in response string
1808  int uid=msg->get_object_id();
1809  res+="UID "+uid+" ";
1810  break;
1811  case "INTERNALDATE":
1812  res+="INTERNALDATE \""+
1813  time_to_string(msg->internal_date())+
1814  "\" ";
1815  break;
1816  case "ENVELOPE":
1817  res+="ENVELOPE "+
1818  get_envelope_data(i)+" ";
1819  break;
1820  case "RFC822.SIZE":
1821  res+="RFC822.SIZE "+
1822  msg->size()+" ";
1823  break;
1824  case "RFC822.HEADER":
1825  string dummy=headers_to_string(msg->header());
1826  res+="RFC822.HEADER {"+sizeof(dummy)+"}\r\n"+dummy;
1827  break;
1828  case "RFC822":
1829  string t=msg->complete_text();
1830  res+="RFC 822 {"+sizeof(t)+"}\r\n"+t;
1831  break;
1832  case "BODYSTRUCTURE":
1833  case "BODY":
1834  res+="BODY "+get_bodystructure(msg)+" ";
1835  break;
1836  default:
1837  if(search(upper_case(parts[j]),"BODY")!=-1) {
1838  if(search(upper_case(parts[j]),"PEEK")==-1
1839  && !msg->flag()->has(SEEN)) {
1840  msg->flag()->add(SEEN);
1841  msg->update();
1842  res+="FLAGS ("+
1843  flags_to_string(msg->flag()->get())+") ";
1844  }
1845  res+=process_body_command(msg,parts[j]);
1846  }
1847  else {
1848  return 0;
1849  }
1850  break;
1851  }
1852  }
1853  return res;
1854 }
1855 
1856 public:
1857 
1858 protected:
1859  void fetch(string tag, string params, int|void uid_mode)
1860 {
1861  int num=sscanf(params,"%s %s",string range, string what);
1862  if(num!=2)
1863  {
1864  send_reply(tag,"BAD arguments invalid");
1865  return;
1866  }
1867 
1868  int err=0;
1869  array nums=({});
1870  function getMessageFunc = oMailBox->get_message;
1871 
1872  if(uid_mode)
1873  {
1874  LOG_IMAP("starting FETCH in uid-mode: "+range);
1875 
1876  if(search(range,"*")!=-1)
1877  {
1878  if(range=="*" || range=="1:*")
1879  {
1880  LOG_IMAP("range selects ALL messages");
1881  range="1:"+oMailBox->get_num_messages();
1882  nums=parse_set(range);
1883  if( nums==({}) ) err=1;
1884  }
1885  else
1886  {
1887  int start;
1888  sscanf(range,"%d:*",start);
1889  LOG_IMAP("starting uid is "+start);
1890  if(zero_type(mMessageNums[start])==1)
1891  {
1892  //search for following uid
1893  int maximum=0xFFFFFFFF;
1894  foreach(indices(mMessageNums),int t)
1895  if(t>start && t<maximum)
1896  maximum=t;
1897  start=maximum;
1898  LOG_IMAP("uid not present, next fitting is "+start);
1899  }
1900  if(start<0xFFFFFFFF) start=mMessageNums[start];
1901  else start=oMailBox->get_num_messages()+1;
1902  LOG_IMAP("starting msn is "+start);
1903  nums=parse_set(start+":"+oMailBox->get_num_messages());
1904  if( nums==({}) ) err=1;
1905  }
1906  }
1907  else
1908  {
1909  nums=parse_set(range);
1910  if( nums==({}) ) err=1;
1911  getMessageFunc = oMailBox->get_message_by_oid;
1912  nums=oMailBox->filter_uids(nums);
1913  LOG_IMAP("filtered UIDS = %O\n", nums);
1914  }
1915  }
1916  else
1917  {
1918  if(range=="*") range="1:*";
1919  range=replace(range,"*",(string)oMailBox->get_num_messages());
1920  nums=parse_set(range);
1921  if( nums==({}) ) err=1;
1922  }
1923 
1924  array parts=parse_fetch_string(what);
1925  if( parts==({}) ) err=1;
1926  LOG_IMAP("fetch attributes parsed, result:\n"+sprintf("%O",parts));
1927 
1928  if(!err)
1929  {
1930  mixed res;
1931  foreach(nums, mixed i)
1932  {
1933  if ( arrayp(i) ) {
1934  // min/max notation
1935  for ( int j=i[0]; j < i[1]; j++ ) {
1936  res = fetch_result(j, parts, getMessageFunc, uid_mode);
1937  if ( !stringp(res) )
1938  send_reply(tag,"BAD arguments invalid");
1939  else if ( strlen(res) > 0 ) {
1940  res=String.trim_whites(res)+")";
1941  send_reply_untagged(res);
1942  }
1943  }
1944  }
1945  else {
1946  res = fetch_result(i, parts, getMessageFunc, uid_mode);
1947  if ( !stringp(res) )
1948  send_reply(tag,"BAD arguments invalid");
1949  else {
1950  res=String.trim_whites(res)+")";
1951  send_reply_untagged(res);
1952  }
1953  }
1954  }
1955  send_reply(tag,"OK FETCH completed");
1956  }
1957  else
1958  {
1959  if(nums==({})) send_reply(tag,"OK FETCH completed"); //empty or invalid numbers
1960  else send_reply_untagged("BAD arguments invalid"); //parse error
1961  }
1962 }
1963 
1964 public:
1965 
1966 protected:
1967  void store(string tag, string params, int|void uid_mode)
1968 {
1969  werror("STORE %O\n", params);
1970  int num=sscanf(params,"%s %s (%s)",string range,string cmd, string tflags);
1971 
1972  if(num!=3)
1973  {
1974  send_reply(tag,"BAD arguments invalid");
1975  return;
1976  }
1977 
1978  int err=0;
1979 
1980  array nums=({});
1981 
1982  function getMessageFunc = oMailBox->get_message;
1983  if(uid_mode)
1984  {
1985  if(range=="*" || range=="1:*")
1986  {
1987  range=replace(range,"*",(string)oMailBox->get_num_messages());
1988  nums=parse_set(range);
1989  if( nums==({}) ) err=1;
1990  }
1991  else
1992  {
1993  nums=parse_set(range);
1994  if( nums==({}) ) err=1;
1995  getMessageFunc = oMailBox->get_message_by_oid;
1996  nums = oMailBox->filter_uids(nums);
1997  }
1998  }
1999  else
2000  {
2001  if(range=="*") range="1:*";
2002  range=replace(range,"*",(string)oMailBox->get_num_messages());
2003  nums=parse_set(range);
2004  if( nums==({}) ) err=1;
2005  }
2006 
2007  int flags=string_to_flags(tflags);
2008  if (flags==-1) err=1; //can't parse flags
2009 
2010  werror("STORE %O, flags=%O nums=%O\n", cmd, flags, nums);
2011 
2012  if(err==0)
2013  {
2014  int silent=0;
2015  string tmp;
2016  cmd=upper_case(cmd);
2017 
2018  switch(cmd)
2019  {
2020  case "FLAGS.SILENT":
2021  silent=1;
2022  case "FLAGS":
2023  foreach(nums,mixed i) {
2024  if ( !arrayp(i) )
2025  i = ({ i, i });
2026  for (int j = i[0]; j <= i[1]; j++) {
2027  getMessageFunc(j)->flag()->set(flags);
2028  getMessageFunc(j)->update();
2029  if (!silent) {
2030  tmp=flags_to_string(getMessageFunc(j)->flag()->get());
2031  send_reply_untagged(i+" FETCH (FLAGS ("+tmp+"))");
2032  }
2033  }
2034  }
2035  break;
2036  case "+FLAGS.SILENT":
2037  silent=1;
2038  case "+FLAGS":
2039  foreach(nums,mixed i)
2040  {
2041  if ( !arrayp(i) )
2042  i = ({ i, i });
2043  for (int j = i[0]; j <= i[1]; j++) {
2044  Messaging.Message msg = getMessageFunc(j);
2045  werror("Adding %O flag to %O\n", flags, msg);
2046  msg->flag()->add(flags);
2047  msg->update();
2048  if (!silent)
2049  {
2050  tmp=flags_to_string(msg->flag()->get());
2051  send_reply_untagged(j+" FETCH (FLAGS ("+tmp+"))");
2052  }
2053  }
2054  }
2055  break;
2056  case "-FLAGS.SILENT":
2057  silent=1;
2058  case "-FLAGS":
2059  foreach(nums,mixed i)
2060  {
2061  if ( !arrayp(i) )
2062  i = ({ i, i });
2063  for (int j = i[0]; j <= i[1]; j++) {
2064  Messaging.Message msg = getMessageFunc(j);
2065  msg->flag()->del(flags);
2066  msg->update();
2067  if (!silent)
2068  {
2069  tmp=flags_to_string(msg->flag()->get());
2070  send_reply_untagged(j+" FETCH (FLAGS ("+tmp+"))");
2071  }
2072  }
2073  }
2074  break;
2075  default:
2076  send_reply(tag,"BAD arguments invalid");
2077  return;
2078  }
2079  send_reply(tag,"OK STORE completed");
2080  }
2081  else send_reply(tag,"BAD arguments invalid");
2082 }
2083 
2084 public:
2085 
2086 protected:
2087  void copy(string tag, string params, int|void uid_mode)
2088 {
2089  int num=sscanf(params,"%s %s", string range, string targetbox);
2090  if(num!=2)
2091  {
2092  send_reply(tag,"BAD arguments invalid");
2093  return;
2094  }
2095 
2096  int err=0;
2097  array nums = ({});
2098  function getMessageFunc = oMailBox->get_message;
2099 
2100  if(uid_mode)
2101  {
2102  if(range=="*" || range=="1:*")
2103  range=replace(range,"*",(string)oMailBox->get_num_messages());
2104  nums=parse_set(range);
2105  if( nums==({}) ) err=1;
2106  getMessageFunc = oMailBox->get_message_by_oid;
2107  nums = oMailBox->filter_uids(nums);
2108  }
2109  else
2110  {
2111  if(range=="*") range="1:*";
2112  range=replace(range,"*",(string)oMailBox->get_num_messages());
2113  nums=parse_set(range);
2114  if( nums==({}) ) err=1;
2115  }
2116 
2117  if(err)
2118  {
2119  send_reply(tag,"OK COPY completed");
2120  return;
2121  }
2122 
2123  array parts = decode_mutf7(unquote_string(targetbox))/"/";
2124  if(upper_case(parts[0])=="INBOX" || parts[0]=="workarea")
2125  {
2126  Messaging.BaseMailBox tmp;
2127  if(upper_case(parts[0])=="INBOX")
2128  {
2129  if(!objectp(oInbox))
2130  oInbox = Messaging.get_mailbox(oUser);
2131  tmp = oInbox;
2132  }
2133  else
2134  {
2135  if(!objectp(oWorkarea))
2136  oWorkarea = Messaging.get_mailbox(oUser->query_attribute(USER_WORKROOM));
2137  tmp = oWorkarea;
2138  }
2139  tmp = tmp->get_subfolder(parts[1..sizeof(parts)-1]*"/");
2140  if(!objectp(tmp))
2141  {
2142  send_reply(tag, "NO [TRYCREATE] target mailbox does not exist");
2143  return;
2144  }
2145  LOG_IMAP("COPY found target mailbox: "+tmp->get_identifier());
2146  for(int i=0;i<sizeof(nums);i++)
2147  {
2148  if ( !arrayp(nums[i]) )
2149  nums[i] = ({ nums[i], nums[i] });
2150  for ( int j = nums[i][0]; j < nums[i][1]; j++ ) {
2151  LOG_IMAP("COPY processing mail #"+j);
2152  Message copy = getMessageFunc(j)->duplicate();
2153  LOG_IMAP("COPY duplicated mail #"+j);
2154  tmp->add_message(copy);
2155  LOG_IMAP("COPY stored mail #"+j+" to target "+
2156  tmp->get_identifier());
2157  }
2158  }
2159  send_reply(tag,"OK COPY completed");
2160  }
2161  else send_reply(tag,"NO COPY cannot copy to that mailbox");
2162 }
2163 
2164 public:
2165 
2166 protected:
2167  void uid(string tag, string params)
2168 {
2169  sscanf(params,"%s %s",string cmd,string args);
2170  args=String.trim_whites(args);
2171 
2172  switch(upper_case(cmd))
2173  {
2174  case "COPY":
2175  copy(tag, args, 1);
2176  break;
2177  case "FETCH":
2178  fetch(tag, args, 1);
2179  break;
2180  case "SEARCH":
2181  send_reply(tag,"NO command is not implemented yet!");
2182  break;
2183  case "STORE":
2184  store(tag, args, 1);
2185  break;
2186  default:
2187  send_reply(tag,"BAD arguments invalid");
2188  break;
2189  }
2190  //completion reply is already sent in called funtion
2191  //no further send_reply() is needed!
2192 }
2193 
2194 public:
2195 
2196 protected:
2197  void call_function(function f, mixed ... params)
2198 {
2199  get_module("tasks")->add_task(0, this_object(), f, params, ([ ]));
2200 }
2201 
2202 public:
2203 
2204 protected:
2205  void process_command(string _cmd)
2206 {
2207  string sTag, sCommand, sParams;
2208  string cmd;
2209  if(iContinue) //processing command continuation request
2210  {
2211  if(sizeof(_cmd)<iBytes-2)
2212  {
2213  LOG_IMAP("received "+sizeof(_cmd)+ "bytes (+2 for CRLF)");
2214  sData+=_cmd+"\r\n";
2215  iBytes-=sizeof(_cmd);
2216  iBytes-=2; //CRLF at end of line counts too
2217  LOG_IMAP(""+iBytes+" bytes remaining");
2218  return;
2219  }
2220  else
2221  {
2222  sData+=_cmd+"\r\n";
2223  iBytes=0;
2224  iContinue=0;
2225  sscanf(sCurrentCommand,"%s %s %s",sTag, sCommand, sParams);
2226  sCurrentCommand="";
2227  sParams+=" "+sData;
2228  function f = mCmd[_state][upper_case(sCommand)];
2229  if(functionp(f)) call_function(f,sTag,sParams);
2230  else send_reply(sTag,"BAD unknown error");
2231  LOG_IMAP("command continuation received "+sizeof(sData)+" bytes of data");
2232  return;
2233  }
2234  }
2235  if(_cmd=="") return; //ignore empty command lines
2236  int length;
2237  if(sscanf(_cmd,"%s {%d}",cmd,length)==2)
2238  {
2239  iContinue=1;
2240  iBytes=length;
2241  sCurrentCommand=cmd;
2242  LOG_IMAP("command continuation request accepted for "+length+" bytes");
2243  }
2244  else cmd=_cmd;
2245 
2246  array tcmd = cmd/" ";
2247 
2248  if(sizeof(tcmd)>1) //tag + command
2249  {
2250  if(sizeof(tcmd)==2) //command without parameter(s)
2251  {
2252  sTag=tcmd[0];
2253  sCommand=tcmd[1];
2254  sParams="";
2255  }
2256  else sscanf(cmd,"%s %s %s", sTag, sCommand, sParams);
2257 
2258  sCommand = upper_case(sCommand);
2259 
2260 // LOG_IMAP("Tag: "+sTag+" ; Command: "+sCommand+" ; Params: "+sParams);
2261 
2262  function f = mCmd[_state][sCommand];
2263  if ( functionp(f) )
2264  {
2265  if(!iContinue) call_function(f, sTag, sParams);
2266  else send_reply_continue("ready for literal data");
2267  }
2268  else
2269  {
2270  send_reply(sTag,"BAD command not recognized");
2271  if(iContinue) iContinue=0;
2272  }
2273  }
2274  else send_reply(cmd,"BAD command not recognized");
2275 }
2276 
2277 public:
2278 
2279 void close_connection()
2280 {
2281  reset_listeners();
2282  if(objectp(oMailBox)) destruct(oMailBox);
2283  if(objectp(oWorkarea)) destruct(oWorkarea);
2284 
2285  if(_state!=STATE_LOGOUT) //we got called by idle-timeout
2286  catch(send_reply_untagged("BYE Autologout; idle for too long"));
2287  ::close_connection();
2288 }
2289 
2290 string get_socket_name() { return "imap4"; }
2291 
2292 int get_client_features() { return CLIENT_FEATURES_EVENTS; }
2293 
2294 
2295 };