1 /* Copyright (C) 2002-2004 Christian Schmidt
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.
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.
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
17 inherit "/net/coal/login";
18 inherit "/net/base/line";
19 inherit Events.Listener;
25 #include <attributes.h>
28 class imap : public login,line{
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)
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!!
49 #define LOG_IMAP(s, args...) werror("imap: "+s+"\n", args)
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
61 mapping (int:int) mMessageNums=([]);
62 int iContinue=0; //for command continuation request
65 string sCurrentCommand="";
66 array(IMAPListener) alEnter, alLeave;
68 //the following maps commands to functions
69 //depending on the state of the server
72 STATE_NONAUTHENTICATED: ([
73 "CAPABILITY": capability,
76 "AUTHENTICATE": authenticate,
80 STATE_AUTHENTICATED: ([
81 "CAPABILITY": capability,
89 "SUBSCRIBE": subscribe,
90 "UNSUBSCRIBE": unsubscribe,
97 "CAPABILITY": capability,
106 "SUBSCRIBE": subscribe,
107 "UNSUBSCRIBE": unsubscribe,
125 /**********************************************************
126 * conversion, parser...
129 //converts a timestamp to a human-readable form
131 string time_to_string(int timestamp)
133 array month=({"Jan","Feb","Mar","Apr","May","Jun",
134 "Jul","Aug","Sep","Oct","Nov","Dec"});
136 mapping(string:int) parts=localtime(timestamp);
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"];
146 if(parts["min"]<10) result+="0"+parts["min"];
147 else result+=parts["min"];
150 if(parts["sec"]<10) result+="0"+parts["sec"];
151 else result+=parts["sec"];
154 int timezone=parts["timezone"]/-3600;
161 if(timezone<10) result=result+"0"+timezone+"00";
162 else result=result+timezone+"00";
168 //convert a flag-pattern to a string
170 string flags_to_string(int flags)
174 if (flags==0) return t;
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 ";
182 t=String.trim_whites(t);
189 //convert a flag-string to a number
191 int string_to_flags(string flags)
194 if(flags=="") return 0;
196 array parts = flags/" ";
199 for (int i=0;i<sizeof(parts);i++) //parse flags
201 string tmp=upper_case(parts[i]);
202 tmp=String.trim_whites(tmp); //remove trailing whitespace
220 default: //unsupported flag -> error!
221 LOG_IMAP("Unknown flag in STORE: "+tmp);
234 //convert a range-string ("4:7") to array (4,5,6,7)
235 //changed to array ({ 4, 7 }) (min, max) now
238 array parse_range(string range)
242 if(sscanf(range,"%d:%d", int minrange, int maxrange)==2)
244 //for(int i=min;i<=max;i++) set=set+({i});
245 return ({ minrange, maxrange });
247 else if(sscanf(range,"%d",int val)==1) set=set+({val});
248 //if range can't be parsed, an empty array is returned
256 array parse_range(string range)
258 if(sscanf(range,"%d:%d", int minrange, int maxrange)==2)
260 return ({ ({ minrange, maxrange }) });
262 else if(sscanf(range,"%d",int val)==1)
270 //convert a set ("2,4:7,12") to array (2,4,5,6,7,12);
271 //now its ( 2,(4,7),12 )
273 array parse_set(string range)
277 array parts=range/","; //split range into single ranges/numbers
278 foreach(parts,string tmp) {set=set+parse_range(tmp);}
285 //split a quoted string into its arguments
287 array parse_quoted_string(string data)
291 if(search(data,"\"")!=-1)
295 while(i<sizeof(data))
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]});
309 j=search(data," ",i); //unquoted string mixed with quoted string
312 result=result+({data[i..sizeof(data)-1]});
317 result=result+({data[i..j-1]});
324 else result=data/" "; //data had no ", just split at spaces
331 //remove the quoting "..." from a string
333 string unquote_string(string data)
335 if(search(data,"\"")==-1)
338 return data[1..sizeof(data)-2];
343 string mimetype(object obj)
345 mapping header=obj->query_attribute(MAIL_MIMEHEADERS);
349 tmp=header["content-type"];
351 sscanf(tmp,"%s;",tmp);
352 else tmp=obj->query_attribute(DOC_MIME_TYPE);
354 else tmp=obj->query_attribute(DOC_MIME_TYPE);
355 return upper_case(tmp);
358 //parse the parameter of a fetch-command
359 //see rfc3501 for details
361 array parse_fetch_string(string data)
368 if(data[sizeof(data)-1]==')')
370 data=data[1..sizeof(data)-2]; //remove ()
371 tmp=parse_quoted_string(data);
374 else tmp=({data}); //parameter has only one argument
379 switch(upper_case(tmp[i]))
385 case "RFC822.HEADER":
389 case "BODYSTRUCTURE":
391 string t=upper_case(tmp[i]);
396 result=({"FLAGS","INTERNALDATE","RFC822.SIZE","ENVELOPE"})+result;
400 result=({"FLAGS","INTERNALDATE","RFC822.SIZE"})+result;
404 result=({"FLAGS","INTERNALDATE","RFC822.SIZE","ENVELOPE","BODY"})+
409 if(search(upper_case(tmp[i]),"BODY")!=-1) //"BODY..." has special syntax
413 if(j==sizeof(tmp)) //last argument, no further processing needed
415 result+=({upper_case(tmp[i])});
418 if(search(tmp[i],"]")==-1)
420 while(search(tmp[j],"]")==-1 && j<sizeof(tmp))
421 j++; //search for closing ]
423 for(int a=i;a<=j;a++) t+=tmp[a]+" ";
424 //copy the whole thing as one string
427 LOG_IMAP("unexpected end of string while parsing BODY...");
428 return ({}); //syntax error
437 result+=({upper_case(tmp[i])});
443 LOG_IMAP("unknown argument to FETCH found: "+upper_case(tmp[i]));
444 return ({}); //syntax error
453 //reformat a mail-adress, see rfc3501
454 string adress_structure(string data)
459 array parts=data/",";
460 for(int i=0;i<sizeof(parts);i++)
462 string name,box,host;
463 int res=sscanf(parts[i],"%s<%s@%s>",name,box,host);
466 res=sscanf(parts[i],"%s@%s",box,host);
469 LOG_IMAP("parse error in adress_structure() !");
470 return ""; //parse error
474 if(sizeof(name)==0) name="NIL";
477 name=String.trim_whites(name);
480 result+="("+name+" NIL \""+box+"\" \""+host+"\")";
487 //convert header-informations to structured envelope-data
488 string get_envelope_data(int num)
490 mapping(string:string) headers=oMailBox->get_message(num)->header();
491 string t,result="(\"";
494 if(t==0) t=time_to_string(oMailBox->get_message(num)->internal_date());
495 result=result+t+"\" ";
497 t=headers["subject"];
499 result=result+"\""+t+"\" ";
501 string from=headers["from"];
502 if(from==0) from="NIL";
503 else from=adress_structure(from);
504 result=result+from+" ";
508 else t=adress_structure(t);
511 t=headers["reply-to"];
513 else t=adress_structure(t);
518 else t=adress_structure(t);
523 else t=adress_structure(t);
528 else t=adress_structure(t);
531 t=headers["in-reply-to"];
536 t=headers["message-id"];
545 //combine all headers of a message to one string
546 string headers_to_string(mapping headers)
550 foreach(indices(headers),string key)
551 result+=String.capitalize(key)+": "+headers[key]+"\r\n";
553 return result+"\r\n"; //header and body are seperated by newline
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)
560 string result,tmp,dummy,cmd,arg;
561 mapping(string:string) headers;
564 data-=".PEEK"; //already processed in fetch(...)
565 while(data[i]!='[' && i<sizeof(data)) i++;
566 if(i==sizeof(data)) return ""; //parse error
568 tmp=data[i+1..sizeof(data)-2];
569 if(sscanf(tmp,"%s(%s)", cmd, arg)==0)
575 headers=msg->header();
576 dummy=headers_to_string(headers);
577 result+="HEADER] {"+sizeof(dummy)+"}\r\n"+dummy;
580 dummy=msg->body()+"\r\n";
581 result+="TEXT] {"+sizeof(dummy)+"}\r\n"+dummy;
583 case "HEADER.FIELDS":
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";
592 result+="HEADER] {"+sizeof(dummy)+"}\r\n"+dummy;
596 if(sscanf(cmd,"%d",part)==1)
599 if(msg->has_attachments())
601 target=msg->attachments()[part-1];
602 dummy=target->body()+"\r\n";
607 result+=part+"] {"+sizeof(dummy)+"}\r\n"+dummy;
611 dummy=msg->complete_text()+"\r\n";
612 result+="] {"+sizeof(dummy)+"}\r\n"+dummy;
620 string get_bodystructure_msg(Messaging.Message obj)
623 string type,subtype,result,tmp;
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))
633 sscanf(tmp,"%*s; %s",tmp);
635 LOG_IMAP("parts=%O",parts);
637 foreach(parts, string part)
639 part = String.trim_whites(part);
640 sscanf(part,"%s=%s",string left, string right);
642 result+="\""+upper_case(left)+"\" \""+right+"\" ";
644 result = String.trim_whites(result) + ") ";
648 tmp=header["content-id"];
650 result+="\""+tmp+"\" ";
652 tmp=header["content-description"];
654 result+="\""+tmp+"\" ";
656 tmp=header["content-transfer-encoding"];
658 result+="\""+tmp+"\" ";
659 else result+="\"8BIT\" ";
661 int size=obj->body_size();
662 if(obj->is_attachment()) size+=2;
664 result+=sizeof(obj->body()/"\n")+")";
669 //get the imap-bodystructure of a message
670 string get_bodystructure(Message msg)
674 int iAttch=msg->has_attachments();
677 array(Message) elems=msg->attachments();
679 for(int i=0;i<sizeof(elems);i++)
680 result+=get_bodystructure_msg(elems[i]);
681 result+=" \"MIXED\")";
684 result+=get_bodystructure_msg(msg);
689 void send_reply_untagged(string msg)
691 send_message("* "+msg+"\r\n");
697 void send_reply(string tag, string msg)
699 call(send_message, 0, tag+" "+msg+"\r\n");
705 void send_reply_continue(string msg)
707 send_message("+ "+msg+"\r\n");
712 void create(object f)
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);
721 //called automatic for selected events
722 void notify_enter(int event, mixed ... args)
724 if(args[0]->get_object_id()!=iUIDValidity) return;
725 //target object is not the mailbox -> ignore this event
727 if(event & EVENTS_MONITORED) what=args[3];
730 if(event & EVENT_ANNOTATE) what=args[2];
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...");
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
748 void notify_leave(int event, mixed ... args)
750 if(args[0]->get_object_id()!=iUIDValidity) return;
751 //target object is not the mailbox -> ignore this event
753 if(event & EVENTS_MONITORED) what=args[3];
756 if(event & EVENT_REMOVE_ANNOTATION) what=args[2];
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...");
768 send_reply_untagged(mMessageNums[id]+" EXPUNGE");
769 m_delete(mMessageNums,id);
770 //message deleted, remove its record from mapping of uids to msns
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());
785 void notify(int event, mixed args, object eObject) {
786 if ( functionp(fCallback) )
787 fCallback(event, @args);
790 mapping save() { return 0; }
793 return "IMAPListener()";
797 void reset_listeners()
800 foreach(alEnter,object tmp) destruct(tmp);
803 foreach(alLeave,object tmp) destruct(tmp);
807 /***************************************************************************
813 void capability(string tag, string params)
815 if ( sizeof(params)>0 ) send_reply(tag,"BAD arguments invalid");
818 send_reply_untagged("CAPABILITY IMAP4rev1");
819 send_reply(tag,"OK CAPABILITY completed");
826 void noop(string tag, string params)
828 send_reply(tag,"OK NOOP completed");
834 void logout(string tag, string params)
836 _state = STATE_LOGOUT;
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");
852 void authenticate(string tag, string params)
854 send_reply(tag,"NO AUTHENTICATE command not supported - use LOGIN instead");
860 void starttls(string tag, string params)
862 send_reply(tag,"NO [ALERT] STARTTLS is not supported by this server");
868 void login(string tag, string params)
870 array parts = parse_quoted_string(params);
871 if( sizeof(parts)==2 )
873 oUser = _Persistence->lookup_user(parts[0]);
874 if ( objectp(oUser) )
876 if ( oUser->check_user_password(parts[1]) ) //passwd ok, continue
879 aSubscribedFolders=oUser->query_attribute(MAIL_SUBSCRIBED_FOLDERS);
880 if(!arrayp(aSubscribedFolders))
882 aSubscribedFolders=({});
883 oUser->set_attribute(MAIL_SUBSCRIBED_FOLDERS,aSubscribedFolders);
885 _state = STATE_AUTHENTICATED;
886 send_reply(tag,"OK LOGIN completed");
888 LOG_IMAP("user "+oUser->get_identifier()+
889 " logged in, subscribed folders:%O",aSubscribedFolders);
891 else send_reply(tag,"NO LOGIN failed");
893 else send_reply(tag,"NO LOGIN failed");
895 else send_reply(tag,"BAD arguments invalid");
901 void select(string tag, string params)
903 //deselect any selected mailbox
904 _state = STATE_AUTHENTICATED;
908 params=decode_mutf7(unquote_string(params));
909 array folders=params/"/";
911 if ( upper_case(folders[0])=="INBOX" || folders[0]=="workarea" )
913 if (upper_case(folders[0])=="INBOX")
916 oInbox = Messaging.get_mailbox(oUser);
919 else //path starts with "workarea"...
921 if(!objectp(oWorkarea))
922 oWorkarea = Messaging.get_mailbox(oUser->query_attribute(USER_WORKROOM));
923 oMailBox = oWorkarea;
925 if(sizeof(folders)>1) //subfolder of inbox or workarea
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
933 if(objectp(oMailBox))
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);
941 array events = oMailBox->enter_event();
942 foreach(events, int event)
943 events = oMailBox->leave_event();
944 foreach(events, int event)
946 int num = oMailBox->get_num_messages();
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");
954 send_reply(tag,"OK [READ-WRITE] SELECT completed");
956 else send_reply(tag,"NO SELECT failed, Mailbox does not exist");
958 else send_reply(tag,"NO SELECT failed, Mailbox does not exist");
964 void examine(string tag, string params)
966 //deselect any selected mailbox
967 _state = STATE_AUTHENTICATED;
971 //TODO: support subfolders of inbox
972 if ( params=="INBOX" )
974 _state = STATE_SELECTED;
975 oMailBox = Messaging.get_mailbox(oUser);
976 iUIDValidity=oMailBox->get_object_id();
978 int num = oMailBox->get_num_messages();
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");
985 send_reply(tag,"OK [READ-ONLY] EXAMINE completed");
987 else send_reply(tag,"NO EXAMINE failed, Mailbox does not exist");
993 void do_create(string tag, string params)
995 array parts = parse_quoted_string(decode_mutf7(params));
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")
1004 if(upper_case(folders[0])=="INBOX")
1006 if(!objectp(oInbox))
1007 oInbox = Messaging.get_mailbox(oUser);
1012 if(!objectp(oWorkarea))
1013 oWorkarea = Messaging.get_mailbox(oUser->query_attribute(USER_WORKROOM));
1017 else //try to create subfolder outside inbox or workarea
1019 send_reply(tag,"NO [ALERT] cannot create top-level mailboxes");
1024 //skip folders in hierarchy that already exist
1025 while(i<sizeof(folders) && objectp(tmp->get_subfolder(folders[i])))
1027 tmp=tmp->get_subfolder(folders[i]);
1031 //all subfolders listed in 'params' exist -> nothing to do...
1032 if(i==sizeof(folders))
1034 send_reply(tag, "NO CREATE failed, folder already exists!");
1038 //create ALL subfolders given in 'params' that do not exist
1039 while(i<sizeof(folders))
1043 LOG_IMAP("about to create folder "+folders[i]);
1044 int result=tmp->create_subfolder(folders[i]);
1047 send_reply(tag,"NO CREATE unable to create that folder");
1050 tmp=tmp->get_subfolder(folders[i]);
1051 LOG_IMAP("created folder "+folders[i]+"["+tmp->get_object_id()+"]");
1056 if(i==sizeof(folders)-1)
1058 send_reply(tag,"OK CREATE completed");
1061 LOG_IMAP("illegal call to CREATE: "+parts[0]);
1062 send_reply(tag,"NO CREATE unable to create that folder");
1066 send_reply(tag,"OK CREATE completed");
1068 else send_reply(tag,"BAD arguments invalid");
1074 void delete(string tag, string params)
1076 array parts = parse_quoted_string(decode_mutf7(params));
1078 if(sizeof(parts)==1)
1080 LOG_IMAP("DELETE called for "+parts[0]);
1081 if(upper_case(parts[0])=="INBOX")
1083 send_reply(tag,"NO cannot delete inbox");
1086 array folders = parts[0]/"/";
1088 Messaging.BaseMailBox tmp;
1089 if(upper_case(folders[0])=="INBOX")
1092 if(!objectp(oInbox))
1093 oInbox = Messaging.get_mailbox(oUser);
1099 while(i<sizeof(folders) && success && objectp(tmp))
1101 LOG_IMAP("searching for "+folders[i]+" in "+tmp->get_identifier()+"["+tmp->get_object_id()+"]");
1102 object tt=tmp->get_subfolder(folders[i]);
1106 LOG_IMAP("not found...");
1111 if(!objectp(tmp)) success=0;
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)
1119 if(objectp(oMailBox)) id=oMailBox->get_object_id();
1120 if(tmp->get_object_id()!=id)
1122 LOG_IMAP("deleting mailbox "+parts[0]);
1124 send_reply(tag,"OK DELETE succeded");
1126 else send_reply(tag,"NO cannot delete selected folder, please deselect first");
1128 else send_reply(tag,"NO folder has subfolders, delete them first");
1130 else send_reply(tag,"NO folder does not exist");
1132 else send_reply(tag,"BAD arguments invalid");
1138 void rename(string tag, string params)
1140 array parts = parse_quoted_string(params);
1142 if(sizeof(parts)==2) send_reply(tag,"NO RENAME Permission denied");
1143 else send_reply(tag,"BAD arguments invalid");
1149 void subscribe(string tag, string params)
1151 array parts = parse_quoted_string(decode_mutf7(params));
1153 if(sizeof(parts)==1)
1155 array folders=parts[0]/"/"; //split mailbox-name at hierarchy-delimiter "/"
1156 if(!(upper_case(folders[0])=="INBOX" || folders[0]=="workarea"))
1158 send_reply(tag,"NO SUBSCRIBE can't subscribe to that name");
1161 Messaging.BaseMailBox tmp;
1162 if(upper_case(folders[0])=="INBOX")
1165 if(!objectp(oInbox))
1166 oInbox = Messaging.get_mailbox(oUser);
1171 if(!objectp(oWorkarea))
1172 oWorkarea = Messaging.get_mailbox(oUser->query_attribute(USER_WORKROOM));
1175 if(sizeof(folders)>1) tmp=tmp->get_subfolder(folders[1..sizeof(folders)-1]*"/");
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");
1185 else send_reply(tag,"NO SUBSCRIBE can't subscribe to that name");
1187 else send_reply(tag,"BAD arguments invalid");
1195 void unsubscribe(string tag, string params)
1197 array parts = parse_quoted_string(decode_mutf7(params));
1199 if(sizeof(parts)==1)
1201 if(search(aSubscribedFolders,parts[0])!=-1)
1203 aSubscribedFolders-=({parts[0]});
1204 oUser->set_attribute(MAIL_SUBSCRIBED_FOLDERS,aSubscribedFolders);
1205 send_reply(tag,"OK UNSUBSCRIBE completed");
1207 else send_reply(tag,"NO UNSUBSCRIBE can't unsubscribe that name");
1209 else send_reply(tag,"BAD arguments invalid");
1216 object validate_reference_name(string refname)
1218 array parts=refname/"/"; parts-=({""});
1220 if(upper_case(parts[0])=="INBOX") // "inbox" is case-insensitive
1223 if(!objectp(oInbox))
1224 oInbox = Messaging.get_mailbox(oUser);
1227 else if(parts[0]=="workarea")
1229 if(!objectp(oWorkarea))
1230 oWorkarea = Messaging.get_mailbox(oUser->query_attribute(USER_WORKROOM));
1233 else //start of reference name is invalid (not "inbox" or "workarea")
1238 tmp=startbox->get_subfolder(parts[1..sizeof(parts)-1]*"/");
1240 if(objectp(tmp)) return tmp;
1245 void list_all_folders(int depth)
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])+"\"");
1268 void list(string tag, string params)
1270 array (string) args=parse_quoted_string(params);
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.
1278 send_reply(tag,"BAD LIST arguments invalid");
1282 string refname = decode_mutf7(args[0]);
1283 string boxname = decode_mutf7(args[1]);
1287 LOG_IMAP("LIST called with reference: "+refname+" and mailbox: "+boxname);
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);
1308 send_reply(tag,"OK LIST completed");
1312 else //special case: boxname AND refname are empty
1314 send_reply_untagged("LIST (\\Noselect) \"/\" \"\"");
1315 send_reply(tag,"OK LIST completed");
1321 if(refname!="" && result==0)
1323 send_reply(tag,"OK LIST completed");
1327 if(refname=="" && (boxname[0]=='%' || boxname=="*" ))
1329 if(boxname=="*") list_all_folders(-1);
1332 int depth=sizeof(boxname/"/"-({}));
1333 list_all_folders(depth);
1335 send_reply(tag,"OK LIST completed");
1339 if(upper_case(boxname)=="INBOX*")
1341 boxname="*"; refname="INBOX/"; start="INBOX/";
1342 if(!objectp(oInbox))
1343 oInbox=Messaging.get_mailbox(oUser);
1345 send_reply_untagged("LIST () \"/\" \"INBOX\"");
1347 if(boxname=="workarea*")
1349 boxname="*"; refname="workarea/"; start="workarea/";
1350 if(!objectp(oWorkarea))
1351 oWorkarea = Messaging.get_mailbox(oUser->query_attribute(USER_WORKROOM));
1353 send_reply_untagged("LIST () \"/\" \"workarea\"");
1356 if(upper_case(parts[0])=="INBOX") parts[0]="INBOX";
1359 if(parts[0]=="INBOX")
1361 if(!objectp(oInbox))
1362 oInbox=Messaging.get_mailbox(oUser);
1365 if(sizeof(parts)==1) send_reply_untagged("LIST () \"/\" \"INBOX\"");
1367 else if(parts[0]=="workarea")
1369 if(!objectp(oWorkarea))
1370 oWorkarea = Messaging.get_mailbox(oUser->query_attribute(USER_WORKROOM));
1373 if(sizeof(parts)==1) send_reply_untagged("LIST () \"/\" \"workarea\"");
1375 if(sizeof(parts)==1)
1377 send_reply(tag,"OK LIST completed");
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
1387 string path = parts[0..i-1]*"/";
1388 object tmp=startbox->get_subfolder(path);
1392 send_reply_untagged("LIST () \"/\" \""+encode_mutf7(start)+"\"");
1394 send_reply(tag,"OK LIST completed");
1399 string path = parts[0..i-1]*"/"; //path until the first wildcard
1400 object tmp=startbox->get_subfolder(path);
1406 else //refname + boxname (without wildcards) is invalid
1408 send_reply(tag,"OK LIST completed");
1413 if(parts[i]=="*") depth=-1; //get _all_ subfolders
1414 else // parts[i]=="%", "count" # of %'s to get depth
1417 if(i<sizeof(parts)-1) //current "%" is not the last part
1418 for(int j=i+1;j<sizeof(parts);j++)
1420 if(parts[j]=="%") depth++;
1423 LOG_IMAP("error in LIST-Command: "+refname+" "+boxname);
1424 send_reply(tag,"NO LIST cannot list that reference or name");
1429 array folders = startbox->list_subfolders(depth);
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");
1440 void lsub(string tag, string params)
1442 array args=parse_quoted_string(params);
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*"))
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");
1454 else send_reply(tag,"OK LSUB completed");
1456 else send_reply(tag,"BAD arguments invalid");
1462 void status(string tag, string params)
1464 string mailbox, what;
1465 if(sscanf(params,"%s (%s)", mailbox, what)!=2)
1467 send_reply(tag,"BAD arguments invalid");
1470 Messaging.BaseMailBox mbox;
1471 array parts = decode_mutf7(unquote_string(mailbox))/"/";
1472 if(upper_case(parts[0])=="INBOX") //lookup mailbox
1475 if(!objectp(oInbox))
1476 oInbox = Messaging.get_mailbox(oUser);
1479 else if(parts[0]=="workarea")
1481 if(!objectp(oWorkarea))
1482 oWorkarea = Messaging.get_mailbox(oUser->query_attribute(USER_WORKROOM));
1485 if(sizeof(parts)>1) mbox=mbox->get_subfolder(parts[1..sizeof(parts)-1]*"/");
1488 send_reply(tag,"NO mailbox does not exist");
1491 mailbox=encode_mutf7(parts*"/");
1492 array items=what/" ";
1494 foreach(items, string tmp)
1496 switch (upper_case(tmp))
1499 result+=" MESSAGES "+mbox->get_num_messages();
1502 result+=" RECENT 0"; // recent-flag is not supported
1505 result+=" UIDNEXT 12345"; //TODO: return correct value
1508 result+=" UIDVALIDITY "+iUIDValidity;
1511 int max=mbox->get_num_messages();
1513 for(int i=0;i<max;i++)
1514 if(mbox->get_message(i)->flag()->has(SEEN)) unseen--;
1515 result+=" UNSEEN "+unseen;
1518 send_reply(tag,"BAD arguments invalid");
1522 result="("+String.trim_whites(result)+")";
1523 send_reply_untagged("STATUS \""+mailbox+"\" "+result);
1524 send_reply(tag,"OK STATUS completed");
1530 void append(string tag, string params)
1532 // LOG_IMAP("APPEND called:%O",params);
1533 string sFolder, sData;
1534 if(sscanf(params,"%s %s",sFolder,sData)!=2)
1536 send_reply(tag,"BAD Arguments invalid");
1539 array parts=decode_mutf7(sFolder)/"/";
1540 if(upper_case(parts[0])=="INBOX" || parts[0]=="workarea")
1542 Messaging.BaseMailBox tmp;
1543 if(upper_case(parts[0])=="INBOX")
1545 if(!objectp(oInbox))
1546 oInbox=Messaging.get_mailbox(oUser);
1551 if(!objectp(oWorkarea))
1552 oWorkarea=Messaging.get_mailbox(oUser->query_attribute(USER_WORKROOM));
1555 tmp=tmp->get_subfolder(parts[1..sizeof(parts)-1]*"/");
1558 Messaging.Message msg = Messaging.MIME2Message(sData);
1561 tmp->add_message(msg);
1562 send_reply(tag,"OK APPEND completed");
1564 else send_reply(tag,"NO Syntax-error in data");
1566 else send_reply(tag,"NO cannot append to non-existent folder");
1568 else send_reply(tag,"NO cannot append to non-existent folder");
1574 void check(string tag, string params)
1576 send_reply(tag,"OK CHECK completed");
1582 void close(string tag, string params)
1584 _state = STATE_AUTHENTICATED;
1587 oMailBox->delete_mails();
1589 send_reply(tag,"OK CLOSE completed");
1595 void expunge(string tag, string params)
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.
1604 send_reply(tag,"OK EXPUNGE completed");
1610 void do_search(string tag, string params)
1612 array parts = parse_quoted_string(params);
1615 int num=oMailBox->get_num_messages();
1620 while (i<sizeof(parts))
1624 { //not all search-parameters are supported yet
1626 for(int j=0;j<num;j++) tmp=tmp+({j+1});
1631 for (int j=0;j<num;j++)
1632 if(oMailBox->get_message(j)->flag()->has(ANSWERED)) tmp=tmp+({j+1});
1649 for (int j=0;j<num;j++)
1650 if(oMailBox->get_message(j)->flag()->has(DELETED)) tmp=tmp+({j+1});
1655 for (int j=0;j<num;j++)
1656 if(oMailBox->get_message(j)->flag()->has(FLAGGED)) tmp=tmp+({j+1});
1677 for (int j=0;j<num;j++)
1678 if(oMailBox->get_message(j)->flag()->has(SEEN)) tmp=tmp+({j+1});
1695 for (int j=0;j<num;j++)
1696 if(!oMailBox->get_message(j)->flag()->has(ANSWERED)) tmp=tmp+({j+1});
1701 for (int j=0;j<num;j++)
1702 if(!oMailBox->get_message(j)->flag()->has(DELETED)) tmp=tmp+({j+1});
1707 for (int j=0;j<num;j++)
1708 if(!oMailBox->get_message(j)->flag()->has(FLAGGED)) tmp=tmp+({j+1});
1716 for (int j=0;j<num;j++)
1717 if(!oMailBox->get_message(j)->flag()->has(SEEN)) tmp=tmp+({j+1});
1722 for (int j=0;j<num;j++)
1723 if(oMailBox->get_message(j)->flag()->has(DRAFT)) tmp=tmp+({j+1});
1755 for (int j=0;j<num;j++)
1756 if(!oMailBox->get_message(j)->flag()->has(DRAFT)) tmp=tmp+({j+1});
1761 //todo: support "(...)"
1762 tmp=parse_set(parts[i]);
1770 send_reply(tag,"BAD arguments invalid");
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");
1784 else send_reply(tag,"BAD arguments invalid");
1791 string fetch_result(int i, array parts, function getMessageFunc,void|int uid_mode)
1793 string res=i+" FETCH (";
1794 if ( !functionp(getMessageFunc) )
1796 Message msg = getMessageFunc(i);
1797 if ( !objectp(msg) )
1799 if(uid_mode) res+="UID "+msg->get_object_id()+" ";
1800 for(int j=0;j<sizeof(parts);j++) {
1803 string tmp=flags_to_string(msg->flag()->get());
1804 res+="FLAGS ("+tmp+") ";
1807 if(uid_mode) break; //UID is already in response string
1808 int uid=msg->get_object_id();
1809 res+="UID "+uid+" ";
1811 case "INTERNALDATE":
1812 res+="INTERNALDATE \""+
1813 time_to_string(msg->internal_date())+
1818 get_envelope_data(i)+" ";
1821 res+="RFC822.SIZE "+
1824 case "RFC822.HEADER":
1825 string dummy=headers_to_string(msg->header());
1826 res+="RFC822.HEADER {"+sizeof(dummy)+"}\r\n"+dummy;
1829 string t=msg->complete_text();
1830 res+="RFC 822 {"+sizeof(t)+"}\r\n"+t;
1832 case "BODYSTRUCTURE":
1834 res+="BODY "+get_bodystructure(msg)+" ";
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);
1843 flags_to_string(msg->flag()->get())+") ";
1845 res+=process_body_command(msg,parts[j]);
1859 void fetch(string tag, string params, int|void uid_mode)
1861 int num=sscanf(params,"%s %s",string range, string what);
1864 send_reply(tag,"BAD arguments invalid");
1870 function getMessageFunc = oMailBox->get_message;
1874 LOG_IMAP("starting FETCH in uid-mode: "+range);
1876 if(search(range,"*")!=-1)
1878 if(range=="*" || range=="1:*")
1880 LOG_IMAP("range selects ALL messages");
1881 range="1:"+oMailBox->get_num_messages();
1882 nums=parse_set(range);
1883 if( nums==({}) ) err=1;
1888 sscanf(range,"%d:*",start);
1889 LOG_IMAP("starting uid is "+start);
1890 if(zero_type(mMessageNums[start])==1)
1892 //search for following uid
1893 int maximum=0xFFFFFFFF;
1894 foreach(indices(mMessageNums),int t)
1895 if(t>start && t<maximum)
1898 LOG_IMAP("uid not present, next fitting is "+start);
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;
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);
1918 if(range=="*") range="1:*";
1919 range=replace(range,"*",(string)oMailBox->get_num_messages());
1920 nums=parse_set(range);
1921 if( nums==({}) ) err=1;
1924 array parts=parse_fetch_string(what);
1925 if( parts==({}) ) err=1;
1926 LOG_IMAP("fetch attributes parsed, result:\n"+sprintf("%O",parts));
1931 foreach(nums, mixed i)
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);
1946 res = fetch_result(i, parts, getMessageFunc, uid_mode);
1947 if ( !stringp(res) )
1948 send_reply(tag,"BAD arguments invalid");
1950 res=String.trim_whites(res)+")";
1951 send_reply_untagged(res);
1955 send_reply(tag,"OK FETCH completed");
1959 if(nums==({})) send_reply(tag,"OK FETCH completed"); //empty or invalid numbers
1960 else send_reply_untagged("BAD arguments invalid"); //parse error
1967 void store(string tag, string params, int|void uid_mode)
1969 werror("STORE %O\n", params);
1970 int num=sscanf(params,"%s %s (%s)",string range,string cmd, string tflags);
1974 send_reply(tag,"BAD arguments invalid");
1982 function getMessageFunc = oMailBox->get_message;
1985 if(range=="*" || range=="1:*")
1987 range=replace(range,"*",(string)oMailBox->get_num_messages());
1988 nums=parse_set(range);
1989 if( nums==({}) ) err=1;
1993 nums=parse_set(range);
1994 if( nums==({}) ) err=1;
1995 getMessageFunc = oMailBox->get_message_by_oid;
1996 nums = oMailBox->filter_uids(nums);
2001 if(range=="*") range="1:*";
2002 range=replace(range,"*",(string)oMailBox->get_num_messages());
2003 nums=parse_set(range);
2004 if( nums==({}) ) err=1;
2007 int flags=string_to_flags(tflags);
2008 if (flags==-1) err=1; //can't parse flags
2010 werror("STORE %O, flags=%O nums=%O\n", cmd, flags, nums);
2016 cmd=upper_case(cmd);
2020 case "FLAGS.SILENT":
2023 foreach(nums,mixed i) {
2026 for (int j = i[0]; j <= i[1]; j++) {
2027 getMessageFunc(j)->flag()->set(flags);
2028 getMessageFunc(j)->update();
2030 tmp=flags_to_string(getMessageFunc(j)->flag()->get());
2031 send_reply_untagged(i+" FETCH (FLAGS ("+tmp+"))");
2036 case "+FLAGS.SILENT":
2039 foreach(nums,mixed 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);
2050 tmp=flags_to_string(msg->flag()->get());
2051 send_reply_untagged(j+" FETCH (FLAGS ("+tmp+"))");
2056 case "-FLAGS.SILENT":
2059 foreach(nums,mixed i)
2063 for (int j = i[0]; j <= i[1]; j++) {
2064 Messaging.Message msg = getMessageFunc(j);
2065 msg->flag()->del(flags);
2069 tmp=flags_to_string(msg->flag()->get());
2070 send_reply_untagged(j+" FETCH (FLAGS ("+tmp+"))");
2076 send_reply(tag,"BAD arguments invalid");
2079 send_reply(tag,"OK STORE completed");
2081 else send_reply(tag,"BAD arguments invalid");
2087 void copy(string tag, string params, int|void uid_mode)
2089 int num=sscanf(params,"%s %s", string range, string targetbox);
2092 send_reply(tag,"BAD arguments invalid");
2098 function getMessageFunc = oMailBox->get_message;
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);
2111 if(range=="*") range="1:*";
2112 range=replace(range,"*",(string)oMailBox->get_num_messages());
2113 nums=parse_set(range);
2114 if( nums==({}) ) err=1;
2119 send_reply(tag,"OK COPY completed");
2123 array parts = decode_mutf7(unquote_string(targetbox))/"/";
2124 if(upper_case(parts[0])=="INBOX" || parts[0]=="workarea")
2126 Messaging.BaseMailBox tmp;
2127 if(upper_case(parts[0])=="INBOX")
2129 if(!objectp(oInbox))
2130 oInbox = Messaging.get_mailbox(oUser);
2135 if(!objectp(oWorkarea))
2136 oWorkarea = Messaging.get_mailbox(oUser->query_attribute(USER_WORKROOM));
2139 tmp = tmp->get_subfolder(parts[1..sizeof(parts)-1]*"/");
2142 send_reply(tag, "NO [TRYCREATE] target mailbox does not exist");
2145 LOG_IMAP("COPY found target mailbox: "+tmp->get_identifier());
2146 for(int i=0;i<sizeof(nums);i++)
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());
2159 send_reply(tag,"OK COPY completed");
2161 else send_reply(tag,"NO COPY cannot copy to that mailbox");
2167 void uid(string tag, string params)
2169 sscanf(params,"%s %s",string cmd,string args);
2170 args=String.trim_whites(args);
2172 switch(upper_case(cmd))
2178 fetch(tag, args, 1);
2181 send_reply(tag,"NO command is not implemented yet!");
2184 store(tag, args, 1);
2187 send_reply(tag,"BAD arguments invalid");
2190 //completion reply is already sent in called funtion
2191 //no further send_reply() is needed!
2197 void call_function(function f, mixed ... params)
2199 get_module("tasks")->add_task(0, this_object(), f, params, ([ ]));
2205 void process_command(string _cmd)
2207 string sTag, sCommand, sParams;
2209 if(iContinue) //processing command continuation request
2211 if(sizeof(_cmd)<iBytes-2)
2213 LOG_IMAP("received "+sizeof(_cmd)+ "bytes (+2 for CRLF)");
2215 iBytes-=sizeof(_cmd);
2216 iBytes-=2; //CRLF at end of line counts too
2217 LOG_IMAP(""+iBytes+" bytes remaining");
2225 sscanf(sCurrentCommand,"%s %s %s",sTag, sCommand, sParams);
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");
2235 if(_cmd=="") return; //ignore empty command lines
2237 if(sscanf(_cmd,"%s {%d}",cmd,length)==2)
2241 sCurrentCommand=cmd;
2242 LOG_IMAP("command continuation request accepted for "+length+" bytes");
2246 array tcmd = cmd/" ";
2248 if(sizeof(tcmd)>1) //tag + command
2250 if(sizeof(tcmd)==2) //command without parameter(s)
2256 else sscanf(cmd,"%s %s %s", sTag, sCommand, sParams);
2258 sCommand = upper_case(sCommand);
2260 // LOG_IMAP("Tag: "+sTag+" ; Command: "+sCommand+" ; Params: "+sParams);
2262 function f = mCmd[_state][sCommand];
2265 if(!iContinue) call_function(f, sTag, sParams);
2266 else send_reply_continue("ready for literal data");
2270 send_reply(sTag,"BAD command not recognized");
2271 if(iContinue) iContinue=0;
2274 else send_reply(cmd,"BAD command not recognized");
2279 void close_connection()
2282 if(objectp(oMailBox)) destruct(oMailBox);
2283 if(objectp(oWorkarea)) destruct(oWorkarea);
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();
2290 string get_socket_name() { return "imap4"; }
2292 int get_client_features() { return CLIENT_FEATURES_EVENTS; }