#pragma implementation /* Generic IMAP server. It handles the protocol, the session, but does not care about networking and the message store. TODO: Messages flags auto-logout 30 minutes */ #include #include #include #include #include #include #include "imap.h" #include enum IMAP_STATE{ IMAP_IDLE, // Original connection, not logged in IMAP_AUTHSTEP, // We are running the authentication steps IMAP_AUTH, // We know the user ID IMAP_SELECT, // We are logged and a folder is selected }; class IMAP_CLIENT{ public: IMAP_INFO info; IMAP_STUFF stuff; IMAP_STATE state; int authstep; SSTRING authuser; SSTRING lasttag; SSTRINGS subs; // Folder subscribed IMAP_CLIENT(){ state = IMAP_IDLE; stuff.userid = -1; stuff.domainid = -1; stuff.data = NULL; info.sessiontime = time(NULL); } }; class _F_IMAP_private{ public: _F_IMAP *c; IMAP_CLIENT **tb; int maxclient; char hostname[1000]; int client; _F_IMAP_private(){ tb = NULL; maxclient = 0; if (gethostname(hostname,sizeof(hostname)-1)==-1){ hostname[0] = '\0'; } } }; int _F_IMAP::send (int client, const char *line, int len) { return write (client,line,len); } void _F_IMAP::sendf (const char *ctl, ...) { va_list list; va_start (list,ctl); char buf[10000]; int len = vsnprintf (buf,sizeof(buf)-1,ctl,list); va_end (list); send (priv->client,buf,len); } PUBLIC IMAP::IMAP(_F_IMAP &c) { priv = new _F_IMAP_private; c.priv = priv; priv->c = &c; } PUBLIC IMAP::~IMAP() { delete priv; } PRIVATE void IMAP::notsupported(int client, const char *tag) { sendf (client,"%s BAD Not implemented yet\r\n",tag); } PRIVATE bool IMAP::folder_exist(IMAP_CLIENT *cli, const char *folder) { SSTRINGS tb; priv->c->listfolders (cli->info,cli->stuff,tb); return tb.ilookup(folder)!=-1; } PRIVATE void IMAP::select ( int client, const char *tag, IMAP_CLIENT *cli, const char *line, bool examine) { SSTRING folder; folder.copyword(line); if (folder_exist(cli,folder.get())){ cli->info.folder.setfrom(folder); cli->info.readonly = examine; IMAP_STATUS rep; priv->c->status (cli->info,cli->stuff,rep); sendf (client,"* FLAGS %d\r\n",rep.flags); sendf (client,"* %d EXISTS\r\n",rep.exists); sendf (client,"* %d RECENT\r\n",rep.recent); sendf (client,"* OK [UIDVALIDITY %d]\r\n",rep.uid); sendf (client,"%s OK [READ-WRITE] SELECT completed\r\n",tag); cli->state = IMAP_SELECT; }else{ sendf (client,"%s NO SELECT failed\r\n",tag); cli->state = IMAP_AUTH; } } /* Parse a set (val val val) or simply val */ static const char *imap_parse_set (const char *line, SSTRINGS &tb) { line = str_skip(line); if (line[0] == '('){ line++; while (1){ line = str_skip(line); if (line[0] == '\0'){ break; }else if (line[0] == ')'){ line++; break; }else{ const char *start = line; while (*line > ' ' && *line != ')') line++; int len = (int)(line-start); if (len > 0){ SSTRING *s = new SSTRING; s->setfrom (start,len); tb.add (s); } } } }else{ SSTRING *s = new SSTRING; line = s->copyword (line); tb.add (s); } return line; } /* Return -1 if a critical error. The client must be disconnected. Or -1 is returned to mean, end of session. */ PUBLIC int IMAP::oneline (int client, const char *line) { int ret = 0; priv->client = client; IMAP_CLIENT *cli = priv->tb[client]; if (cli->state == IMAP_AUTHSTEP){ if (line[0] == '*'){ cli->state = IMAP_IDLE; sendf (client,"%s BAD AUTHENTICATE aborted\r\n",cli->lasttag.get()); }else{ char buf[100+1]; base64_decode (buf,100,line); if (cli->authstep == 0){ cli->authuser.setfrom (buf); sendf (client,"+ allo\r\n"); cli->authstep = 1; }else if (cli->authstep == 1){ if (priv->c->auth (cli->info,cli->stuff,cli->authuser.get() ,buf)){ sendf (client,"%s OK AUTHENTICATE completed\r\n",cli->lasttag.get()); cli->state = IMAP_AUTH; }else{ sendf (client,"%s NO AUTHTICATE failure\r\n",cli->lasttag.get()); cli->state = IMAP_IDLE; } } } }else{ SSTRING cmd; line = cli->lasttag.copyword (line); line = cmd.copyword (line); const char *pttag = cli->lasttag.get(); if (pttag[0] == '\0'){ sendf (client,"* BAD null command\r\n"); }else if (cmd.is_empty()){ sendf (client,"%s BAD missing command\r\n",pttag); }else if (cmd.icmp("logout")==0){ sendf (client,"* BYE IMAP4rev1 Server logging out\r\n"); sendf (client,"%s OK logout completed\r\n",pttag); ret = -1; }else if (cmd.icmp("capability")==0){ sendf (client,"* CAPABILITY IMAP4 IMAP4REV1 LOGIN-REFERRALS AUTH=LOGIN\r\n"); sendf (client,"%s OK CAPABILITY completed\r\n",pttag); }else if (cmd.icmp("noop")==0){ sendf (client,"%s OK NOOP completed\r\n",pttag); }else if (cli->state == IMAP_IDLE){ if (cmd.icmp("login")==0){ SSTRING user,pass; line = user.copyword(line); line = pass.copyword(line); if (priv->c->auth(cli->info,cli->stuff,user.get(),pass.get())){ sendf (client,"%s OK LOGIN completed\r\n",pttag); cli->state = IMAP_AUTH; }else{ sendf (client,"%s NO login failure\r\n",pttag); } }else if (cmd.icmp("authenticate")==0){ SSTRING method; method.copyword (line); if (method.icmp("login")==0){ cli->state = IMAP_AUTHSTEP; cli->authstep = 0; char buf[100]; strcpy (buf,"allo"); sendf (client,"+ %s\r\n",buf); //sendf (client,"%s OK AUTHENTICATE LOGIN\r\n",pttag); }else{ sendf (client,"%s NO AUTHENTICATE mechanism not supported\r\n",pttag); } }else{ sendf (client,"%s BAD unknown command\r\n",pttag); } }else if (cli->state == IMAP_AUTH || cli->state == IMAP_SELECT){ if (cmd.icmp("SELECT")==0){ select (client,pttag,cli,line,false); }else if (cmd.icmp("EXAMINE")==0){ select (client,pttag,cli,line,true); }else if (cmd.icmp("CREATE")==0){ SSTRING folder; folder.copyword (line); const char *ptfolder = folder.get(); if (ptfolder[0] == '\0' || folder_exist(cli,ptfolder)){ sendf (client,"%s NO CREATE failed, folder exist\r\n" ,pttag); }else if (priv->c->create (cli->info,cli->stuff,ptfolder)==-1){ sendf (client,"%s NO CREATE failed, server error\r\n" ,pttag); }else{ sendf (client,"%s OK CREATE completed\r\n" ,pttag); } }else if (cmd.icmp("DELETE")==0){ SSTRING folder; folder.copyword (line); const char *ptfolder = folder.get(); if (stricmp(ptfolder,"INBOX")==0){ sendf (client,"%s NO DELETE failed, Can't delete INBOX\r\n" ,pttag); }else if (ptfolder[0] == '\0' || !folder_exist(cli,ptfolder)){ sendf (client,"%s NO DELETE failed, folder does not exist\r\n" ,pttag); }else if (priv->c->delbox (cli->info,cli->stuff,ptfolder)==-1){ sendf (client,"%s NO DELETE failed, server error\r\n" ,pttag); }else{ sendf (client,"%s OK DELETE completed\r\n" ,pttag); } }else if (cmd.icmp("RENAME")==0){ SSTRING oldfolder,newfolder; line = oldfolder.copyword(line); newfolder.copyword (line); if (!folder_exist(cli,oldfolder.get())){ sendf (client,"%s NO RENAME failed, folder %s does exist\r\n",pttag,oldfolder.get()); }else if (folder_exist(cli,newfolder.get())){ sendf (client,"%s NO RENAME failed, folder %s exist\r\n",pttag,newfolder.get()); }else if (priv->c->rename(cli->info,cli->stuff,oldfolder.get() ,newfolder.get())==-1){ sendf (client,"%s NO RENAME failed, server error\r\n",pttag); }else{ sendf (client,"%s OK RENAME completed\r\n",pttag); } notsupported (client,pttag); }else if (cmd.icmp("SUBSCRIBE")==0){ SSTRING folder; folder.copyword(line); const char *ptfold = folder.get(); if (ptfold[0] == '\0'){ sendf (client,"%s BAD SUBSCRIBE failed, no folder specified\r\n",pttag); }else if (!folder_exist(cli,ptfold)){ sendf (client,"%s NO SUBSCRIBE failed, folder does not exist\r\n",pttag); }else{ if(cli->subs.ilookup(ptfold)==-1){ cli->subs.add (new SSTRING(ptfold)); } sendf (client,"%s OK SUBSCRIBE completed\r\n",pttag); } }else if (cmd.icmp("UNSUBSCRIBE")==0){ SSTRING folder; folder.copyword(line); const char *ptfold = folder.get(); if (ptfold[0] == '\0'){ sendf (client,"%s BAD UNSUBSCRIBE failed, no folder specified\r\n",pttag); }else if (!folder_exist(cli,ptfold)){ sendf (client,"%s NO UNSUBSCRIBE failed, folder does not exist\r\n",pttag); }else{ int no = cli->subs.ilookup(ptfold); if (no==-1){ sendf (client,"%s NO UNSUBSCRIBE failed, folder was not subscribed\r\n",pttag); }else{ cli->subs.remove_del(no); sendf (client,"%s OK SUBSCRIBE completed\r\n",pttag); } } }else if (cmd.icmp("LIST")==0){ SSTRING ref,folder; line = ref.copyword (line); folder.copyword (line); SSTRINGS tb; priv->c->listfolders (cli->info,cli->stuff,tb); for (int i=0; iget()); } sendf (client,"%s OK LIST completed\r\n",pttag); }else if (cmd.icmp("LSUB")==0){ notsupported (client,pttag); }else if (cmd.icmp("STATUS")==0){ notsupported (client,pttag); }else if (cmd.icmp("APPEND")==0){ notsupported (client,pttag); }else if (cli->state == IMAP_SELECT){ if (cmd.icmp("CHECK")==0){ sendf (client,"%s OK CHECK completed\r\n",pttag); }else if (cmd.icmp("CLOSE")==0){ if (!cli->info.readonly){ if (priv->c->expunge (cli->info,cli->stuff ,cli->info.folder.get(),false)==-1){ sendf (client,"%s NO CLOSE failed, server error\r\n",pttag); }else{ sendf (client,"%s OK CLOSE completed\r\n",pttag); } }else{ sendf (client,"%s OK CLOSE completed\r\n",pttag); } cli->state = IMAP_AUTH; }else if (cmd.icmp("EXPUNGE")==0){ if (priv->c->expunge (cli->info,cli->stuff ,cli->info.folder.get(),true)==-1){ sendf (client,"%s NO EXPUNGE failed, server error\r\n",pttag); }else{ sendf (client,"%s OK EXPUNGE completed\r\n",pttag); } }else if (cmd.icmp("SEARCH")==0){ while (*line != '\0'){ SSTRING f; line = f.copyword(line); // } IMAP_STATUS rep; priv->c->status (cli->info,cli->stuff,rep); for (int i=0; ic->getmsg (cli->info,cli->stuff,i); } sendf (client,"%s OK FETCH completed\r\n",pttag); }else if (cmd.icmp("STORE")==0){ notsupported (client,pttag); }else if (cmd.icmp("COPY")==0){ notsupported (client,pttag); }else if (cmd.icmp("UID")==0){ notsupported (client,pttag); }else{ sendf (client,"%s BAD unknown command\r\n",pttag); } }else{ sendf (client,"%s BAD unknown command\r\n",pttag); } } } return ret; } PUBLIC void IMAP::sendf (int client, const char *ctl, ...) { char buf[10000]; va_list list; va_start (list,ctl); int len = vsnprintf (buf,sizeof(buf)-1,ctl,list); va_end (list); priv->c->send (client,buf,len); } PUBLIC int IMAP::newclient (int client) { if (client >= priv->maxclient){ int oldmax = priv->maxclient; priv->maxclient += 1000; priv->tb = (IMAP_CLIENT**)realloc(priv->tb ,priv->maxclient*sizeof(IMAP_CLIENT*)); if (priv->tb == NULL){ syslog (LOG_CRIT,"Out of memory\n"); exit (-1); } for (int i=oldmax; imaxclient; i++) priv->tb[i] = NULL; } priv->tb[client] = new IMAP_CLIENT; sendf (client,"* OK [CAPABILITY IMAP4 IMAP4rev1 LOGIN-REFERRALS AUTH=LOGIN] %s vimap_sql\r\n" ,priv->hostname); return 0; } PUBLIC int IMAP::endclient (int client) { if (client >= 0 && client < priv->maxclient){ delete priv->tb[client]; priv->tb[client] = NULL; } return 0; }