1 /*
2  * Copyright (C) 2002-2003 Fhg Fokus
3  *
4  * This file is part of SEMS, a free SIP media server.
5  *
6  * SEMS 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. This program is released under
10  * the GPL with the additional exemption that compiling, linking,
11  * and/or using OpenSSL is allowed.
12  *
13  * For a license to use the SEMS software under conditions
14  * other than those described here, or to purchase support for this
15  * software, please contact iptel.org by e-mail at the following addresses:
16  *    info@iptel.org
17  *
18  * SEMS is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software
25  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
26  */
27 
28 #include <stdio.h>
29 #include <unistd.h>
30 #include <errno.h>
31 #include <sys/socket.h>
32 #include <netinet/in.h>
33 #include <arpa/inet.h>
34 #include <time.h>
35 #include <string.h>
36 
37 #include "AmSmtpClient.h"
38 #include "AmUtils.h"
39 #include "log.h"
40 
41 #include "sip/resolver.h"
42 
43 #define B64_FRAME_LINE_SIZE    15
44 #define B64_MAX_BUF_LINES      45
45 
46 #define SEND_LINE(l) \
47     { \
48 	if(send_line(l)) \
49 	    return true; \
50     }
51 
AmSmtpClient()52 AmSmtpClient::AmSmtpClient()
53   : server_ip(), server_port(0),
54     sd(0)
55 {
56 }
57 
~AmSmtpClient()58 AmSmtpClient::~AmSmtpClient()
59 {
60   if(sd){
61     close();
62   }
63 }
64 
connect(const string & _server_ip,unsigned short _server_port)65 bool AmSmtpClient::connect(const string& _server_ip, unsigned short _server_port)
66 {
67   if(sd && close())
68     return true;
69 
70   server_ip = _server_ip;
71   server_port = _server_port;
72 
73   if(server_ip.empty())
74     return true;
75 
76   if(!server_port)
77     server_port = 25; /* Not present on FreeBSD IPPORT_SMTP; */
78 
79   struct sockaddr_in addr;
80 
81   addr.sin_family = AF_INET;
82   addr.sin_port = htons(server_port);
83 
84   {
85     sockaddr_storage _sa;
86     dns_handle       _dh;
87 
88     if(resolver::instance()->resolve_name(server_ip.c_str(),
89 					&_dh,&_sa,IPv4) < 0) {
90       ERROR("address not valid (smtp server: %s)\n",server_ip.c_str());
91       return false;
92     }
93 
94     memcpy(&addr.sin_addr,&((sockaddr_in*)&_sa)->sin_addr,sizeof(in_addr));
95   }
96 
97   sd = socket(PF_INET, SOCK_STREAM, 0);
98   if(::connect(sd,(struct sockaddr *)&addr,sizeof(addr)) == -1) {
99     ERROR("%s\n",strerror(errno));
100     return false;
101   }
102 
103   INFO("connected to: %s\n",server_ip.c_str());
104   bool cont = !get_response(); // server's welcome
105 
106   if(cont){
107     INFO("%s welcomes us\n",server_ip.c_str());
108     return send_command("HELO " + server_ip);
109   }
110   else
111     return true;
112 }
113 
114 // returns:  0 if succeded
115 //          -1 if failed
send(const AmMail & mail)116 bool AmSmtpClient::send(const AmMail& mail)
117 {
118   string mail_from = "mail from: <" + mail.from + ">";
119   string rcpt_to = "rcpt to: <" + mail.to + ">";
120 
121   vector<string> headers;
122 
123   if (!mail.header.empty()) headers.push_back(mail.header);
124   headers.push_back("From: " + mail.from);
125   headers.push_back("To: " + mail.to);
126   headers.push_back("Subject: " + mail.subject);
127 
128   if ( send_command(mail_from)
129        || send_command(rcpt_to)
130        || send_body(headers,mail) )
131     return true;
132 
133   return false;
134 }
135 
disconnect()136 bool AmSmtpClient::disconnect()
137 {
138   return send_command("quit");
139 }
140 
close()141 bool AmSmtpClient::close()
142 {
143   ::close(sd);
144   sd = 0;
145   INFO("We are now deconnected from server\n");
146   return false;
147 }
148 
149 
read_line()150 bool AmSmtpClient::read_line()
151 {
152   received=0;
153   int s = read(sd,lbuf,SMTP_LINE_BUFFER);
154   if(s == -1)
155     ERROR("AmSmtpClient::read_line(): %s\n",strerror(errno));
156   else if(s > 0){
157     received = s;
158     DBG("RECEIVED: %.*s\n",s,lbuf);
159     lbuf[s] = '\0';
160   }
161   else if(!s)
162     DBG("AmSmtpClient::read_line(): EoF reached!\n");
163 
164   return (s<=0);
165 }
166 
send_line(const string & cmd)167 bool AmSmtpClient::send_line(const string& cmd)
168 {
169   string snd_buf = cmd;
170 
171   string::size_type pos = 0;
172   while( (pos = snd_buf.find('\n',pos)) != string::npos ){
173     if( (pos == 0) || ((pos > 0) && (snd_buf[pos-1] != '\r')) ){
174       snd_buf.insert(pos,1,'\r');
175       pos += 2;
176     }
177   }
178 
179   snd_buf += "\r\n";
180   int ssize = write(sd,snd_buf.c_str(),snd_buf.length());
181   if(ssize == -1){
182     ERROR("AmSmtpClient::send_line(): %s\n",strerror(errno));
183     return true;
184   }
185 
186   DBG("SENT: %.*s",(int)snd_buf.length(),snd_buf.c_str());
187 
188   return false;
189 }
190 
get_response()191 bool AmSmtpClient::get_response()
192 {
193   return (read_line() || parse_response());
194 }
195 
send_command(const string & cmd)196 bool AmSmtpClient::send_command(const string& cmd)
197 {
198   if( send_line(cmd) || get_response()){
199     status = st_Error;
200     return true;
201   }
202 
203   if(res_code >= 200 && res_code < 400) {
204     status = st_Ok;
205   }
206   else if(res_code < 600) {
207     ERROR("smtp server answered: %i %s (cmd was '%s')\n",
208 	  res_code,res_msg.c_str(),cmd.c_str());
209     status = st_Error;
210   }
211   else {
212     WARN("unknown response from smtp server: %i %s (cmd was '%s')\n",
213 	 res_code,res_msg.c_str(),cmd.c_str());
214     status = st_Unknown;
215   }
216 
217   return (status != st_Ok);
218 }
219 
220 
221 
parse_response()222 bool AmSmtpClient::parse_response()
223 {
224   if(parse_return_code(lbuf,res_code,res_msg)==-1){
225 
226     ERROR("AmSmtpClient::parse_response(): while parsing response\n");
227     return true;
228   }
229 
230   return false;
231 }
232 
send_body(const vector<string> & hdrs,const AmMail & mail)233 bool AmSmtpClient::send_body(const vector<string>& hdrs, const AmMail& mail)
234 {
235   return send_command("data")
236     || send_data(hdrs,mail)
237     || send_command(".");
238 }
239 
240 static void base64_encode(unsigned char* in, unsigned char* out, unsigned int in_size);
241 static int base64_encode_file(FILE* in, int out);
242 
send_data(const vector<string> & hdrs,const AmMail & mail)243 bool AmSmtpClient::send_data(const vector<string>& hdrs, const AmMail& mail)
244 {
245   string part_delim = "----=_NextPart_"
246     + int2str(int(time(NULL)))
247     + "_" + int2str(int(getpid()));
248 
249   for( vector<string>::const_iterator hdr_it = hdrs.begin();
250        hdr_it != hdrs.end(); ++hdr_it )
251     SEND_LINE(*hdr_it);
252 
253   SEND_LINE("MIME-Version: 1.0");
254 
255   if(!mail.attachements.empty()){
256     SEND_LINE("Content-Type: multipart/mixed; ");
257     SEND_LINE("      boundary=\"" + part_delim + "\"");
258     SEND_LINE(""); // EoH
259     SEND_LINE("--" + part_delim);
260   }
261 
262   if(mail.charset.empty()){
263     SEND_LINE("Content-Type: text/plain");
264   }
265   else {
266     SEND_LINE("Content-Type: text/plain; ");
267     SEND_LINE("      charset=\"" + mail.charset + "\"");
268   }
269   SEND_LINE(""); //EoH
270   SEND_LINE(mail.body);
271 
272 
273   for( Attachements::const_iterator att_it = mail.attachements.begin();
274        att_it != mail.attachements.end(); ++att_it	) {
275 
276     SEND_LINE("--" + part_delim );
277     if(!att_it->content_type.empty()){
278       SEND_LINE("Content-Type: " + att_it->content_type);
279     }
280     else {
281       SEND_LINE("Content-Type: application/octet-stream");
282     }
283     SEND_LINE("Content-Transfer-Encoding: base64");
284 
285     if(att_it->filename.empty()) {
286       SEND_LINE("Content-Disposition: inline"); // | "attachement"
287     }
288     else {
289       SEND_LINE("Content-Disposition: inline; "); // | "attachement"
290       SEND_LINE("      filename=\"" + att_it->filename + "\"");
291     }
292     SEND_LINE(""); // EoH
293 
294     base64_encode_file(att_it->fp,sd);
295     SEND_LINE(""); // base64_encode_file() doesn't generate any EoL
296   }
297 
298   if(!mail.attachements.empty()){
299     SEND_LINE("--" + part_delim + "--");
300   }
301 
302   return false;
303 }
304 
305 // !! do not touch !! configure only B64_FRAME_LINE_SIZE & B64_MAX_BUF_LINES
306 
307 #define B64_IN_LINE_SIZE       (3 * B64_FRAME_LINE_SIZE)
308 #define B64_OUT_LINE_SIZE      (4 * B64_FRAME_LINE_SIZE)
309 
310 #define B64_INPUT_BUFFER_SIZE  (B64_IN_LINE_SIZE * B64_MAX_BUF_LINES)
311 #define B64_OUTPUT_BUFFER_SIZE (B64_OUT_LINE_SIZE * B64_MAX_BUF_LINES)
312 
313 
314 char base64_table[] = {
315   'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
316   'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
317   'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
318   'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
319 };
320 
base64_encode(unsigned char * in,unsigned char * out,unsigned int in_size)321 static void base64_encode(unsigned char* in, unsigned char* out, unsigned int in_size)
322 {
323   unsigned int dw;
324 
325   switch(in_size){
326   case 3:
327     dw = (((unsigned int)in[0]) << 16)
328       | (((unsigned int)in[1]) << 8)
329       | ((unsigned int)in[2]);
330     break;
331   case 2:
332     dw = (((unsigned int)in[0]) << 16)
333       | (((unsigned int)in[1]) << 8);
334     break;
335   case 1:
336     dw = (((unsigned int)in[0]) << 16);
337     break;
338   default:
339     return;
340   }
341 
342   unsigned int i=0;
343   for(; i<(in_size+1); i++)
344     out[i] = base64_table[(dw >> (18-6*i)) & ((1<<6)-1)];
345 
346   for(; i<4; i++)
347     out[i] = '=';
348 }
349 
base64_encode_file(FILE * in,int out_fd)350 static int base64_encode_file(FILE* in, int out_fd)
351 {
352   unsigned char ibuf[B64_INPUT_BUFFER_SIZE];
353   unsigned char obuf[B64_OUTPUT_BUFFER_SIZE]={' '};
354   int s;
355 
356   FILE* out = fdopen(out_fd,"w");
357 
358   if(!out){
359     ERROR("base64_encode_file: out file == NULL\n");
360     return -1;
361   }
362 
363   rewind(in);
364   //     FILE* in  = fopen(filename,"rb");
365   //     if(!in){
366   // 	ERROR("%s\n",strerror(errno));
367   // 	return -1;
368   //     }
369 
370   int bytes_written=0;
371   while((s = fread(ibuf,1,B64_INPUT_BUFFER_SIZE,in))){
372 
373     unsigned int ioff=0;
374     unsigned int ooff=0;
375     while(s>=3){
376       base64_encode(ibuf+ioff,obuf+ooff,3);
377       ioff += 3;
378       ooff += 4;
379       s -= 3;
380     }
381     if(s){
382       base64_encode(ibuf+ioff,obuf+ooff,s);
383       ooff += 4;
384     }
385 
386     unsigned int off=0;
387     while(ooff >= 60){
388       fprintf(out,"%.*s\r\n",60,obuf + off);
389       off  += 60;
390       ooff -= 60;
391     }
392 
393     if(ooff){
394       fprintf(out,"%.*s\r\n",int(ooff),obuf + off);
395       off += ooff;
396     }
397 
398     bytes_written += off;
399   };
400 
401   fflush(out);
402   //fclose(in);
403   DBG("%i bytes written\n",bytes_written);
404   return 0;
405 }
406 
407