Added new configuration variables for SMTP and Queue servers
[fmail.git] / backends / protocol / smtp.cpp
blob277b25f960c46ed33cdb0c1a137e410a5e6e04d3
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 >= 65) && (v <= 90))
39 return v - 65;
41 if (v == 95)
42 return 26;
44 if ((v >= 97) && (v <= 122))
45 return v - 97;
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);
169 /* Request a slot */
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();
178 }else{
179 break;
182 /* Open Queue Slot File */
183 fd = NULL;
184 fd = fopen(slotname, "w");
186 if (fd == NULL){
187 printf("Error: Unable to open destination SLOT\n");
188 onrun = 0;
189 break;
192 /* Write our headers */
193 fprintf(fd, "MAIL FROM: %s\n", mailfrom.c_str());
194 fprintf(fd, "RCPT: %s\n", rcpt.c_str());
195 fprintf(fd, "\n");
197 r = 1;
198 while (r){
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);
206 /* Check for EOF */
207 if (strstr(buffer, KEY_END_DATA)){
208 r = 0;
212 /* Close our slot file */
213 fclose(fd);
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);
223 s->Write( CRLF, 2);
225 /* Go back to initial state */
226 state = 1;
227 break;
228 case CMD_RSET:
229 s->Write( "250 OK", 6);
230 s->Write( CRLF, 2);
232 /* Reset state */
233 state = 1;
234 break;
235 case CMD_QUIT:
236 s->Write( "221 Exiting", 11);
237 s->Write(CRLF, 2);
239 /* Stop main loop */
240 onrun = 0;
241 break;
242 default:
243 printf("Not implemented [State: %i]: %s\n", state, buffer);
244 s->Write( "502 Not implemented", 19);
245 s->Write( CRLF, 2);
246 break;
251 /* Close Connection */
252 msg = new IPCMessage("quit");
253 ipc->SendMessage(msg);
254 ipc->CloseIPC();
255 return 0;
259 class SimpleLoad : public LoadHandler {
260 public:
261 int Dispatch(Socket *sock, ProtocolHandler *ph){
262 if (ph)
263 return ph->Handle(sock);
264 return 0;
268 int main(){
269 LoadHandler *lh;
270 SMTPHandler *handler;
271 Socket *s, *cl;
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);
282 s->setAddress(NULL);
283 s->setPort(port);
285 if (s->Bind()){
286 printf("Unable to bind to port\n");
287 return 0;
290 s->Listen(15);
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);
303 }else{
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).
312 while(1){
313 /* Poll for incoming connections */
314 ret = s->Poll(1000000, SOCKET_POLL_READ);
315 if (ret == SOCKET_POLL_READ){
316 /* Accept client and dispatch */
317 cl = s->Accept();
318 lh->Dispatch(cl, handler);
322 return 0;