Fixed missing closing of client socket, minor cleanups
[fmail.git] / backends / protocol / smtp.cpp
blobbc6a1cf1bb764d4bd980675c81c35d2ce527ba38
1 /*
2 SMTP Protocol Handler
4 Copyright (C) 2007 Carlos Daniel Ruvalcaba Valenzuela <clsdaniel@gmail.com>
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License along
17 with this program; if not, write to the Free Software Foundation, Inc.,
18 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include <libfmail.h>
22 #include <pcrecpp.h>
24 const char CRLF[2] = {0x0D, 0x0A};
26 /* Currently supported SMTP commands */
27 const int CMD_EHLO = 1;
28 const int CMD_HELO = 2;
29 const int CMD_MAIL = 3;
30 const int CMD_RCPT = 4;
31 const int CMD_DATA = 5;
32 const int CMD_QUIT = 6;
33 const int CMD_RSET = 7;
34 const char KEY_END_DATA[6] = {0x0D, 0x0A, '.', 0x0D, 0x0A, 0x00};
36 /* Create a hash number from a character for search tree */
37 int knhash(char v){
38 if ((v >= 'A') && (v <= 'Z'))
39 return v - 'A';
41 if (v == '_')
42 return 26;
44 if ((v >= 'a') && (v <= 'z'))
45 return v - 'a';
47 if (v == ' ')
48 return -1;
50 if (v == 0x0D)
51 return -1;
53 return 255;
56 class SMTPHandler : public ProtocolHandler{
57 SearchTree<int, 27, &knhash> cmdtree;
58 std::string queue_ipc;
59 public:
60 SMTPHandler(std::string qipc){
61 /* Queue IPC string */
62 queue_ipc = qipc;
64 /* Insert the SMTP commands to search tree */
65 cmdtree.Insert("EHLO", 1);
66 cmdtree.Insert("HELO", 2);
67 cmdtree.Insert("MAIL", 3);
68 cmdtree.Insert("RCPT", 4);
69 cmdtree.Insert("DATA", 5);
70 cmdtree.Insert("QUIT", 6);
71 cmdtree.Insert("RSET", 7);
73 int Handle(Socket *s){
74 char buffer[255], *slotname;
75 int r, l, ret;
76 int onrun, cmd, state;
77 FILE *fd;
78 pcrecpp::RE reCmd("\\w*:<(.*)>");
79 state = 0;
80 onrun = 1;
81 cmd = 0;
82 string mailfrom, rcpt;
84 IPC *ipc;
85 IPCMessage *msg;
87 /* Write out the SMTP server Greeting */
88 s->Write("220 FancyMail v0.1", 18);
89 s->Write( CRLF, 2);
91 /* Connect to the Queue server */
92 ipc = IPC::CreateIPC((char*)queue_ipc.c_str());
93 ipc->RequestIPC();
95 while (onrun){
96 /* Wait for client input */
97 memset(buffer, 0, 255);
98 r = s->Read(buffer, 255);
100 /* Find the given command on our search tree */
101 try{
102 cmd = cmdtree.Lookup(buffer);
103 }catch (SearchNotFound){
104 cmd = -1;
107 switch(cmd){
108 case CMD_HELO:
109 /* We got normal SMTP HELO, write response */
110 s->Write("250 ok localhost", 16);
111 s->Write(CRLF, 2);
113 /* Client presented itself, we can accept other
114 * commands, we pass to state 1 */
115 state = 1;
116 break;
117 case CMD_EHLO:
118 /* We still not support ESMTP, write not implemented */
119 s->Write( "502 Not implemented", 19);
120 s->Write( CRLF, 2);
121 break;
122 case CMD_MAIL:
123 /* Check if we had the proper greeting */
124 if (state != 1){
125 s->Write( "502 Not implemented", 19);
126 s->Write( CRLF, 2);
127 break;
130 /* Extract the command mail from data */
131 ret = reCmd.PartialMatch(buffer, &mailfrom);
133 /* Write Response */
134 s->Write( "250 OK", 6);
135 s->Write( CRLF, 2);
137 /* We pass to next state */
138 state = 2;
139 break;
140 case CMD_RCPT:
141 /* Check that we have recieved the MAIL command first */
142 if (state != 2){
143 s->Write( "502 Not implemented", 19);
144 s->Write( CRLF, 2);
145 break;
148 /* Extract the recipent */
149 reCmd.PartialMatch(buffer, &rcpt);
151 s->Write( "250 OK", 6);
152 s->Write( CRLF, 2);
154 state = 3;
155 break;
156 case CMD_DATA:
157 /* Check that we have recived the RCPT command first */
158 if (state != 3){
159 s->Write( "502 Not implemented", 19);
160 s->Write( CRLF, 2);
161 break;
164 /* Give the client green light to send its data */
165 s->Write( "354 OK", 6);
166 s->Write( CRLF, 2);
168 /* Request a slot */
169 msg = new IPCMessage("slot");
170 msg->PushParam("incoming");
171 ipc->SendMessage(msg);
172 delete msg;
174 /* Retrieve Response */
175 msg = ipc->ReciveMessage();
176 if (!strcmp(msg->GetMessageName(), "ok")){
177 slotname = msg->PopParam();
178 }else{
179 break;
182 /* Open Queue Slot File */
183 fd = NULL;
184 fd = fopen(slotname, "w");
187 if (fd == NULL){
188 printf("Error: Unable to open destination SLOT\n");
189 onrun = 0;
190 break;
193 /* Write our headers */
194 fprintf(fd, "MAIL FROM: %s\n", mailfrom.c_str());
195 fprintf(fd, "RCPT: %s\n", rcpt.c_str());
196 fprintf(fd, "\n");
198 r = 1;
199 while (r){
200 /* Read incoming client data */
201 memset(buffer, 0, 255);
202 l = s->Read( buffer, 255);
204 /* Write to slot file */
205 fwrite(buffer, 1, l, fd);
207 /* Check for EOF */
208 if (strstr(buffer, KEY_END_DATA)){
209 r = 0;
213 /* Close our slot file */
214 fclose(fd);
216 delete msg;
218 /* Push the message to the Queue */
219 msg = new IPCMessage("push");
220 msg->PushParam("incoming");
221 msg->PushParam(slotname);
222 ipc->SendMessage(msg);
224 delete msg;
225 free(slotname);
227 s->Write( "250 OK", 6);
228 s->Write( CRLF, 2);
230 /* Go back to initial state */
231 state = 1;
232 break;
233 case CMD_RSET:
234 s->Write( "250 OK", 6);
235 s->Write( CRLF, 2);
237 /* Reset state */
238 state = 1;
239 break;
240 case CMD_QUIT:
241 printf("GOT QUIT\n");
242 s->Write( "221 Exiting", 11);
243 s->Write(CRLF, 2);
245 /* Stop main loop */
246 onrun = 0;
247 break;
248 default:
249 printf("Not implemented [State: %i]: %s\n", state, buffer);
250 s->Write( "502 Not implemented", 19);
251 s->Write( CRLF, 2);
252 break;
257 /* Close Connection */
258 msg = new IPCMessage("quit");
259 ipc->SendMessage(msg);
260 ipc->CloseIPC();
262 s->Close();
264 return 0;
268 class SimpleLoad : public LoadHandler {
269 public:
270 int Dispatch(Socket *sock, ProtocolHandler *ph){
271 if (ph)
272 return ph->Handle(sock);
273 return 0;
277 int main(){
278 LoadHandler *lh;
279 SMTPHandler *handler;
280 Socket *s, *cl;
281 int ret, port, tcount;
282 std::string conf_handler, qipc;
283 Configuration conf("smtp.conf");
285 /* Get basic configuration options */
286 port = conf.getInt("port", 25);
287 conf_handler = conf.getString("loadhandler", "simple");
289 /* Create a Socket and bind it to SMTP port */
290 s = Socket::CreateSocket(SOCKET_INET, 0);
291 s->setAddress(NULL);
292 s->setPort(port);
294 if (s->Bind()){
295 printf("Unable to bind to port\n");
296 return 0;
299 s->Listen(15);
301 /* Load queue connection string */
302 qipc = conf.getString("queue", "socket://127.0.0.1:14003");
304 /* Create a handler object */
305 handler = new SMTPHandler(qipc);
307 /* Create a LoadHandler Object based on configuration */
308 if (conf_handler == "thread"){
309 /* If we got threaded loadhandler check for max worker thread count */
310 tcount = conf.getInt("thread.count", 10);
311 lh = new ThreadLoad(handler, tcount);
312 }else{
313 lh = new SimpleLoad();
316 /* TODO: Don't loop forever, have a condition. Options:
317 * * We need to handle SIGTERM signal, but this may not be
318 * crossplatform, atexit may work on windows too.
319 * * Handle a EXIT ipc message (may lack security).
321 while(1){
322 /* Poll for incoming connections */
323 ret = s->Poll(1000000, SOCKET_POLL_READ | SOCKET_POLL_ERROR);
324 if (ret & SOCKET_POLL_READ){
325 /* Accept client and dispatch */
326 printf("Dispatching Client\n");
327 cl = s->Accept();
328 lh->Dispatch(cl, handler);
329 printf("On to next client!\n");
330 }else if(ret & SOCKET_POLL_ERROR){ //error
331 return -1;
332 }else{ //timeout
333 break;
337 return 0;