Changed smtp for new worker classes
[fmail.git] / backends / protocol / smtp.cpp
bloba3a5280c2d2e7ff81ce8c85bff27b57ab2d47b8c
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 <time.h>
23 #include <libfmail.h>
24 #include <pcrecpp.h>
26 const char CRLF[2] = { 0x0D, 0x0A };
28 /* Currently supported SMTP commands */
29 const int CMD_EHLO = 1;
30 const int CMD_HELO = 2;
31 const int CMD_MAIL = 3;
32 const int CMD_RCPT = 4;
33 const int CMD_DATA = 5;
34 const int CMD_QUIT = 6;
35 const int CMD_RSET = 7;
36 const char KEY_END_DATA[6] = { 0x0D, 0x0A, '.', 0x0D, 0x0A, 0x00 };
38 /* Create a hash number from a character for search tree */
39 int knhash (char v) {
40 if ((v >= 'A') && (v <= 'Z'))
41 return v - 'A';
43 if (v == '_')
44 return 26;
46 if ((v >= 'a') && (v <= 'z'))
47 return v - 'a';
49 if (v == ' ')
50 return -1;
52 if (v == 0x0D)
53 return -1;
55 return 255;
58 int genPath(char *buffer){
59 time_t ctime;
60 struct tm * gtime;
61 int r;
63 /* Get current time*/
64 time(&ctime);
65 gtime = gmtime(&ctime);
67 /* Generate a Random number */
68 r = rand();
70 /* Construct a unique filename for the slot */
71 sprintf(buffer, "/tmp/fmail/queue/incoming/%i%i%i%i%i", gtime->tm_yday, gtime->tm_hour, gtime->tm_min, gtime->tm_sec, r);
72 return 1;
76 class SMTPHandler : public Job {
77 SearchTree < int, 27, &knhash > cmdtree;
78 std::string queue_ipc;
79 Socket *s;
80 public:
81 SMTPHandler (std::string qipc, Socket *sock) : Job() {
82 /* Queue IPC string */
83 queue_ipc = qipc;
85 s = sock;
87 /* Insert the SMTP commands to search tree */
88 cmdtree.Insert ("EHLO", 1);
89 cmdtree.Insert ("HELO", 2);
90 cmdtree.Insert ("MAIL", 3);
91 cmdtree.Insert ("RCPT", 4);
92 cmdtree.Insert ("DATA", 5);
93 cmdtree.Insert ("QUIT", 6);
94 cmdtree.Insert ("RSET", 7);
96 int Run () {
97 char buffer[255], * slotname;
98 int r, l, ret;
99 int onrun, cmd, state;
100 FILE * fd;
101 pcrecpp::RE reCmd ("\\w*:<(.*)>");
102 state = 0;
103 onrun = 1;
104 cmd = 0;
105 string mailfrom, rcpt;
107 IPC * ipc;
108 IPCMessage * msg;
110 /* Write out the SMTP server Greeting */
111 s->Write ("220 FancyMail v0.1", 18);
112 s->Write (CRLF, 2);
114 /* Connect to the Queue server */
115 //ipc = IPC::CreateIPC ((char *) queue_ipc.c_str ());
116 //ipc->RequestIPC ();
118 while (onrun) {
119 /* Wait for client input */
120 memset (buffer, 0, 255);
121 r = s->Read (buffer, 255);
123 /* Find the given command on our search tree */
124 try {
125 cmd = cmdtree.Lookup (buffer);
126 } catch (SearchNotFound) {
127 cmd = -1;
130 switch (cmd) {
131 case CMD_HELO:
132 /* We got normal SMTP HELO, write response */
133 s->Write ("250 ok localhost", 16);
134 s->Write (CRLF, 2);
136 /* Client presented itself, we can accept other
137 * commands, we pass to state 1 */
138 state = 1;
139 break;
140 case CMD_EHLO:
141 /* We still not support ESMTP, write not implemented */
142 s->Write ("502 Not implemented", 19);
143 s->Write (CRLF, 2);
144 break;
145 case CMD_MAIL:
146 /* Check if we had the proper greeting */
147 if (state != 1) {
148 s->Write ("502 Not implemented", 19);
149 s->Write (CRLF, 2);
150 break;
153 /* Extract the command mail from data */
154 ret = reCmd.PartialMatch (buffer, &mailfrom);
156 /* Write Response */
157 s->Write ("250 OK", 6);
158 s->Write (CRLF, 2);
160 /* We pass to next state */
161 state = 2;
162 break;
163 case CMD_RCPT:
164 /* Check that we have recieved the MAIL command first */
165 if (state != 2) {
166 s->Write ("502 Not implemented", 19);
167 s->Write (CRLF, 2);
168 break;
171 /* Extract the recipent */
172 reCmd.PartialMatch (buffer, &rcpt);
174 s->Write ("250 OK", 6);
175 s->Write (CRLF, 2);
177 state = 3;
178 break;
179 case CMD_DATA:
180 /* Check that we have recived the RCPT command first */
181 if (state != 3) {
182 s->Write ("502 Not implemented", 19);
183 s->Write (CRLF, 2);
184 break;
187 /* Give the client green light to send its data */
188 s->Write ("354 OK", 6);
189 s->Write (CRLF, 2);
191 genPath(buffer);
192 slotname = buffer;
194 /* Request a slot */
195 /* msg = new IPCMessage ("slot");
196 msg->PushParam ("incoming");
197 ipc->SendMessage (msg);
198 delete msg; */
200 /* Retrieve Response */
201 /* msg = ipc->ReciveMessage ();
202 if (!strcmp (msg->GetMessageName (), "ok")) {
203 slotname = msg->PopParam ();
204 } else {
205 break;
206 } */
208 /* Open Queue Slot File */
209 fd = NULL;
210 fd = fopen (slotname, "w");
213 if (fd == NULL) {
214 printf ("Error: Unable to open destination SLOT\n");
215 onrun = 0;
216 break;
219 /* Write our headers */
220 fprintf (fd, "MAIL FROM: %s\n", mailfrom.c_str ());
221 fprintf (fd, "RCPT: %s\n", rcpt.c_str ());
222 fprintf (fd, "\n");
224 r = 1;
225 while (r) {
226 /* Read incoming client data */
227 memset (buffer, 0, 255);
228 l = s->Read (buffer, 255);
230 /* Write to slot file */
231 fwrite (buffer, 1, l, fd);
233 /* Check for EOF */
234 if (strstr (buffer, KEY_END_DATA)) {
235 r = 0;
239 /* Close our slot file */
240 fclose (fd);
242 //delete msg;
244 /* Push the message to the Queue */
245 /* msg = new IPCMessage ("push");
246 msg->PushParam ("incoming");
247 msg->PushParam (slotname);
248 ipc->SendMessage (msg);*/
250 //delete msg;
251 //free (slotname);
253 s->Write ("250 OK", 6);
254 s->Write (CRLF, 2);
256 /* Go back to initial state */
257 state = 1;
258 break;
259 case CMD_RSET:
260 s->Write ("250 OK", 6);
261 s->Write (CRLF, 2);
263 /* Reset state */
264 state = 1;
265 break;
266 case CMD_QUIT:
267 s->Write ("221 Exiting", 11);
268 s->Write (CRLF, 2);
270 /* Stop main loop */
271 onrun = 0;
272 break;
273 default:
274 printf ("Not implemented [State: %i]: %s\n", state, buffer);
275 s->Write ("502 Not implemented", 19);
276 s->Write (CRLF, 2);
277 break;
282 /* Close Connection */
283 /*msg = new IPCMessage ("quit");
284 ipc->SendMessage (msg);
285 ipc->CloseIPC ();*/
287 s->Close ();
289 return 0;
293 class SimpleLoad : public LoadHandler {
294 public:
295 int Dispatch (Socket * sock, ProtocolHandler * ph) {
296 if (ph)
297 return ph->Handle (sock);
298 return 0;
302 int main () {
303 Socket * s, * cl;
304 Boss *boss;
305 int ret, port, tcount;
306 std::string conf_handler, qipc;
307 Configuration conf ("smtp.conf");
309 /* Get basic configuration options */
310 port = conf.getInt ("port", 25);
311 conf_handler = conf.getString ("loadhandler", "simple");
313 /* Create a Socket and bind it to SMTP port */
314 s = Socket::CreateSocket (SOCKET_INET, 0);
315 s->setAddress (NULL);
316 s->setPort (port);
318 if (s->Bind ()) {
319 printf ("Unable to bind to port\n");
320 return 0;
323 s->Listen (15);
325 /* Load queue connection string */
326 qipc = conf.getString ("queue", "socket://127.0.0.1:14003");
328 /* Create a Boss for Thread Worker based on configuration */
329 if (conf_handler == "thread") {
330 /* If we got threaded loadhandler check for max worker thread count */
331 tcount = conf.getInt ("thread.count", 10);
332 boss = new Boss(tcount);
333 } else {
334 boss = new Boss(1);
337 /* TODO: Don't loop forever, have a condition. Options:
338 * * We need to handle SIGTERM signal, but this may not be
339 * crossplatform, atexit may work on windows too.
340 * * Handle a EXIT ipc message (may lack security).
342 while (1) {
343 /* Poll for incoming connections */
344 ret = s->Poll (1000000, SOCKET_POLL_READ | SOCKET_POLL_ERROR);
345 if (ret & SOCKET_POLL_READ) {
346 /* Accept client and dispatch */
347 cl = s->Accept ();
348 boss->Dispatch(new SMTPHandler (qipc, cl));
349 } else if (ret & SOCKET_POLL_ERROR) { //error
350 return -1;
351 } else { //timeout
352 break;
356 return 0;