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.
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 */
38 if ((v
>= 65) && (v
<= 90))
44 if ((v
>= 97) && (v
<= 122))
56 class SMTPHandler
: public ProtocolHandler
{
57 SearchTree
<int, 27, &knhash
> cmdtree
;
58 std::string queue_ipc
;
60 SMTPHandler(std::string qipc
){
61 /* Queue IPC string */
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
;
76 int onrun
, cmd
, state
;
78 pcrecpp::RE
reCmd("\\w*:<(.*)>");
82 string mailfrom
, rcpt
;
87 /* Write out the SMTP server Greeting */
88 s
->Write("220 FancyMail v0.1", 18);
91 /* Connect to the Queue server */
92 ipc
= IPC::CreateIPC((char*)queue_ipc
.c_str());
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 */
102 cmd
= cmdtree
.Lookup(buffer
);
103 }catch (SearchNotFound
){
109 /* We got normal SMTP HELO, write response */
110 s
->Write("250 ok localhost", 16);
113 /* Client presented itself, we can accept other
114 * commands, we pass to state 1 */
118 /* We still not support ESMTP, write not implemented */
119 s
->Write( "502 Not implemented", 19);
123 /* Check if we had the proper greeting */
125 s
->Write( "502 Not implemented", 19);
130 /* Extract the command mail from data */
131 ret
= reCmd
.PartialMatch(buffer
, &mailfrom
);
134 s
->Write( "250 OK", 6);
137 /* We pass to next state */
141 /* Check that we have recieved the MAIL command first */
143 s
->Write( "502 Not implemented", 19);
148 /* Extract the recipent */
149 reCmd
.PartialMatch(buffer
, &rcpt
);
151 s
->Write( "250 OK", 6);
157 /* Check that we have recived the RCPT command first */
159 s
->Write( "502 Not implemented", 19);
164 /* Give the client green light to send its data */
165 s
->Write( "354 OK", 6);
170 msg
= new IPCMessage("slot");
171 msg
->PushParam("incoming");
172 ipc
->SendMessage(msg
);
174 /* Retrieve Response */
175 msg
= ipc
->ReciveMessage();
176 if (!strcmp(msg
->GetMessageName(), "ok")){
177 slotname
= msg
->PopParam();
182 /* Open Queue Slot File */
184 fd
= fopen(slotname
, "w");
187 printf("Error: Unable to open destination SLOT\n");
192 /* Write our headers */
193 fprintf(fd
, "MAIL FROM: %s\n", mailfrom
.c_str());
194 fprintf(fd
, "RCPT: %s\n", rcpt
.c_str());
199 /* Read incoming client data */
200 memset(buffer
, 0, 255);
201 l
= s
->Read( buffer
, 255);
203 /* Write to slot file */
204 fwrite(buffer
, 1, l
, fd
);
207 if (strstr(buffer
, KEY_END_DATA
)){
212 /* Close our slot file */
215 /* Push the message to the Queue */
216 msg
= new IPCMessage("push");
217 msg
->PushParam("incoming");
218 msg
->PushParam(slotname
);
219 ipc
->SendMessage(msg
);
222 s
->Write( "250 OK", 6);
225 /* Go back to initial state */
229 s
->Write( "250 OK", 6);
236 s
->Write( "221 Exiting", 11);
243 printf("Not implemented [State: %i]: %s\n", state
, buffer
);
244 s
->Write( "502 Not implemented", 19);
251 /* Close Connection */
252 msg
= new IPCMessage("quit");
253 ipc
->SendMessage(msg
);
259 class SimpleLoad
: public LoadHandler
{
261 int Dispatch(Socket
*sock
, ProtocolHandler
*ph
){
263 return ph
->Handle(sock
);
270 SMTPHandler
*handler
;
272 int ret
, port
, tcount
;
273 std::string conf_handler
, qipc
;
274 Configuration
conf("smtp.conf");
276 /* Get basic configuration options */
277 port
= conf
.getInt("port", 25);
278 conf_handler
= conf
.getString("loadhandler", "simple");
280 /* Create a Socket and bind it to SMTP port */
281 s
= Socket::CreateSocket(SOCKET_INET
, 0);
286 printf("Unable to bind to port\n");
292 /* Load queue connection string */
293 qipc
= conf
.getString("queue", "socket://127.0.0.1:14003");
295 /* Create a handler object */
296 handler
= new SMTPHandler(qipc
);
298 /* Create a LoadHandler Object based on configuration */
299 if (conf_handler
== "thread"){
300 /* If we got threaded loadhandler check for max worker thread count */
301 tcount
= conf
.getInt("thread.count", 10);
302 lh
= new ThreadLoad(handler
, tcount
);
304 lh
= new SimpleLoad();
307 /* TODO: Don't loop forever, have a condition. Options:
308 * * We need to handle SIGTERM signal, but this may not be
309 * crossplatform, atexit may work on windows too.
310 * * Handle a EXIT ipc message (may lack security).
313 /* Poll for incoming connections */
314 ret
= s
->Poll(1000000, SOCKET_POLL_READ
);
315 if (ret
== SOCKET_POLL_READ
){
316 /* Accept client and dispatch */
318 lh
->Dispatch(cl
, handler
);