Eliminated searchtree debugging messages; Added UNIX socket (named sockets) support...
[fmail.git] / backends / protocol / smtp.cpp
blobfaadfe59a7a836e040e724b14d7f1f60fc58dffc
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>
22 #include <signal.h>
24 #include <libfmail.h>
25 #include <pcrecpp.h>
27 const char CRLF[2] = { 0x0D, 0x0A };
29 static bool sRun;
31 /* Currently supported SMTP commands */
32 const int CMD_EHLO = 1;
33 const int CMD_HELO = 2;
34 const int CMD_MAIL = 3;
35 const int CMD_RCPT = 4;
36 const int CMD_DATA = 5;
37 const int CMD_QUIT = 6;
38 const int CMD_RSET = 7;
39 const char KEY_END_DATA[6] = { 0x0D, 0x0A, '.', 0x0D, 0x0A, 0x00 };
41 /* Create a hash number from a character for search tree */
42 int knhash (char v) {
43 if ((v >= 'A') && (v <= 'Z'))
44 return v - 'A';
46 if (v == '_')
47 return 26;
49 if ((v >= 'a') && (v <= 'z'))
50 return v - 'a';
52 if (v == ' ')
53 return -1;
55 if (v == 0x0D)
56 return -1;
58 return 255;
61 int genPath(char *buffer){
62 time_t ctime;
63 struct tm * gtime;
64 int r;
66 /* Get current time*/
67 time(&ctime);
68 gtime = gmtime(&ctime);
70 /* Generate a Random number */
71 r = rand();
73 /* Construct a unique filename for the slot */
74 sprintf(buffer, "/tmp/fmail/queue/incoming/%i%i%i%i%i", gtime->tm_yday, gtime->tm_hour, gtime->tm_min, gtime->tm_sec, r);
75 return 1;
79 class SMTPHandler : public Job {
80 SearchTree < int, 27, &knhash > cmdtree;
81 std::string queue_ipc;
82 Socket *s;
83 public:
84 SMTPHandler (std::string qipc, Socket *sock) : Job() {
85 /* Queue IPC string */
86 //queue_ipc = qipc;
88 s = sock;
90 /* Insert the SMTP commands to search tree */
91 cmdtree.Insert ("EHLO", 1);
92 cmdtree.Insert ("HELO", 2);
93 cmdtree.Insert ("MAIL", 3);
94 cmdtree.Insert ("RCPT", 4);
95 cmdtree.Insert ("DATA", 5);
96 cmdtree.Insert ("QUIT", 6);
97 cmdtree.Insert ("RSET", 7);
99 ~SMTPHandler(){
100 delete s;
102 int Run () {
103 char buffer[255], * slotname;
104 int r, l, ret;
105 int onrun, cmd, state;
106 FILE * fd;
107 pcrecpp::RE reCmd ("\\w*:<(.*)>");
108 state = 0;
109 onrun = 1;
110 cmd = 0;
111 string mailfrom, rcpt;
113 /* Write out the SMTP server Greeting */
114 s->Write ("220 FancyMail v0.1", 18);
115 s->Write (CRLF, 2);
117 while (onrun) {
118 /* Wait for client input */
119 memset (buffer, 0, 255);
120 r = s->Read (buffer, 255);
122 /* Find the given command on our search tree */
123 try {
124 cmd = cmdtree.Lookup (buffer);
125 } catch (SearchNotFound) {
126 cmd = -1;
129 switch (cmd) {
130 case CMD_HELO:
131 /* We got normal SMTP HELO, write response */
132 s->Write ("250 ok localhost", 16);
133 s->Write (CRLF, 2);
135 /* Client presented itself, we can accept other
136 * commands, we pass to state 1 */
137 state = 1;
138 break;
139 case CMD_EHLO:
140 /* We still not support ESMTP, write not implemented */
141 s->Write ("502 Not implemented", 19);
142 s->Write (CRLF, 2);
143 break;
144 case CMD_MAIL:
145 /* Check if we had the proper greeting */
146 if (state != 1) {
147 s->Write ("502 Not implemented", 19);
148 s->Write (CRLF, 2);
149 break;
152 /* Extract the command mail from data */
153 ret = reCmd.PartialMatch (buffer, &mailfrom);
155 /* Write Response */
156 s->Write ("250signal(SIGTERM, SIG_IGN); OK", 6);
157 s->Write (CRLF, 2);
159 /* We pass to next state */
160 state = 2;
161 break;
162 case CMD_RCPT:
163 /* Check that we have recieved the MAIL command first */
164 if (state != 2) {
165 s->Write ("502 Not implemented", 19);
166 s->Write (CRLF, 2);
167 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 /* Open Queue Slot File */
195 fd = NULL;
196 fd = fopen (slotname, "w");
199 if (fd == NULL) {
200 printf ("Error: Unable to open destination SLOT\n");
201 onrun = 0;
202 break;
205 /* Write our headers */
206 fprintf (fd, "MAIL FROM: %s\n", mailfrom.c_str ());
207 fprintf (fd, "RCPT: %s\n", rcpt.c_str ());
208 fprintf (fd, "\n");
210 r = 1;
211 while (r) {
212 /* Read incoming client data */
213 memset (buffer, 0, 255);
214 l = s->Read (buffer, 255);
216 /* Write to slot file */
217 fwrite (buffer, 1, l, fd);
219 /* Check for EOF */
220 if (strstr (buffer, KEY_END_DATA)) {
221 r = 0;
225 /* Close our slot file */
226 fclose (fd);
228 s->Write ("250 OK", 6);
229 s->Write (CRLF, 2);
231 /* Go back to initial state */
232 state = 1;
233 break;
234 case CMD_RSET:
235 s->Write ("250 OK", 6);
236 s->Write (CRLF, 2);
238 /* Reset state */
239 state = 1;
240 break;
241 case CMD_QUIT:
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 s->Close ();
259 return 0;
263 class SimpleLoad : public LoadHandler {
264 public:
265 int Dispatch (Socket * sock, ProtocolHandler * ph) {
266 if (ph)
267 return ph->Handle (sock);
268 return 0;
272 void onExit(int sig){
273 printf("Got Signal %i\n", sig);
274 sRun = false;
275 signal(SIGTERM, SIG_IGN);
276 signal(SIGINT, SIG_IGN);
279 int main () {
280 Socket * s, * cl;
281 Boss *boss;
282 int ret, port, tcount;
283 std::string conf_handler, qipc;
284 Configuration conf ("smtp.conf");
285 struct sigaction act;
287 act.sa_handler = onExit;
288 sigemptyset(&act.sa_mask);
289 act.sa_flags = 0;
291 sigaction(SIGINT, &act, 0);
293 /* Get basic configuration options */
294 port = conf.getInt ("port", 25);
295 conf_handler = conf.getString ("loadhandler", "simple");
297 /* Create a Socket and bind it to SMTP port */
298 s = Socket::CreateSocket (SOCKET_INET, 0);
299 s->setAddress (NULL);
300 s->setPort (port);
302 if (s->Bind ()) {
303 printf ("Unable to bind to port\n");
304 return 0;
307 s->Listen (15);
309 /* Load queue connection string */
310 qipc = conf.getString ("queue", "socket://127.0.0.1:14003");
312 /* Create a Boss for Thread Worker based on configuration */
313 if (conf_handler == "thread") {
314 /* If we got threaded loadhandler check for max worker thread count */
315 tcount = conf.getInt ("thread.count", 10);
316 boss = new Boss(tcount);
317 } else {
318 boss = new Boss(1);
321 sRun = true;
323 while (sRun) {
324 /* Poll for incoming connections */
325 ret = s->Poll (1000000, SOCKET_POLL_READ | SOCKET_POLL_ERROR);
326 if (ret & SOCKET_POLL_READ) {
327 /* Accept client and dispatch */
328 cl = s->Accept ();
329 boss->Dispatch(new SMTPHandler (qipc, cl));
330 } else if (ret & SOCKET_POLL_ERROR) { //error
331 return -1;
332 } else { //timeout
333 break;
337 printf("fmail-smtp shutting down\n");
338 s->Close();
340 delete s;
341 delete boss;
343 return 0;