Merge pull request #4045 from lambdageek/bug-47867
[mono.git] / mcs / class / System.Web / System.Web.Mail / SmtpClient.cs
1 //
2 // System.Web.Mail.SmtpClient.cs
3 //
4 // Author(s):
5 //   Per Arneng <pt99par@student.bth.se>
6 //   Sanjay Gupta <gsanjay@novell.com>
7 //   (C) 2004, Novell, Inc. (http://www.novell.com)
8 //
9
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30
31 using System.Net;
32 using System.IO;
33 using System.Text;
34 using System.Collections;
35 using System.Net.Sockets;
36 using System.Security.Permissions;
37 using System.Reflection;
38
39 namespace System.Web.Mail {
40
41         /// represents a conntection to a smtp server
42         internal class SmtpClient
43         {
44                 string server;
45                 TcpClient tcpConnection;
46                 SmtpStream smtp;
47                 string username;
48                 string password;
49                 int port = 25;
50                 bool usessl = false;
51                 short authenticate = 1;         
52         
53                 //Initialise the variables and connect
54                 public SmtpClient (string server)
55                 {
56                         this.server = server;
57                 }
58         
59                 // make the actual connection
60                 // and HELO handshaking
61                 void Connect ()
62                 {
63                         tcpConnection = new TcpClient (server, port);
64             
65                         NetworkStream stream = tcpConnection.GetStream ();
66                         smtp = new SmtpStream (stream);
67                 }
68                     
69                 void ChangeToSSLSocket ()
70                 {
71                         // Load Mono.Security.dll
72                         Assembly a;
73                         try {
74                                 a = Assembly.Load (Consts.AssemblyMono_Security);
75                         } catch (System.IO.FileNotFoundException) {
76                                 throw new SmtpException ("Cannot load Mono.Security.dll");
77                         }
78                         Type tSslClientStream = a.GetType ("Mono.Security.Protocol.Tls.SslClientStream");
79                         object[] consArgs = new object[4];
80                         consArgs[0] = smtp.Stream;
81                         consArgs[1] = server;
82                         consArgs[2] = true;
83                         Type tSecurityProtocolType = a.GetType ("Mono.Security.Protocol.Tls.SecurityProtocolType");
84                         int nSsl3Val = (int) Enum.Parse (tSecurityProtocolType, "Ssl3");
85                         int nTlsVal = (int) Enum.Parse (tSecurityProtocolType, "Tls");
86                         consArgs[3] = Enum.ToObject (tSecurityProtocolType, nSsl3Val | nTlsVal);
87
88                         object objSslClientStream = Activator.CreateInstance (tSslClientStream, consArgs); 
89
90                         if (objSslClientStream != null)
91                                 smtp = new SmtpStream ((Stream)objSslClientStream);
92                 }
93                 
94                 void ReadFields (MailMessageWrapper msg)
95                 {
96                         string tmp;
97                         username = msg.Fields.Data ["http://schemas.microsoft.com/cdo/configuration/sendusername"];
98                         password = msg.Fields.Data ["http://schemas.microsoft.com/cdo/configuration/sendpassword"]; 
99                         tmp = msg.Fields.Data ["http://schemas.microsoft.com/cdo/configuration/smtpauthenticate"]; 
100                         if (tmp != null)
101                                 authenticate = short.Parse (tmp);
102                         tmp = msg.Fields.Data ["http://schemas.microsoft.com/cdo/configuration/smtpusessl"];    
103                         if (tmp != null)
104                                 usessl = bool.Parse (tmp);
105                         tmp = msg.Fields.Data ["http://schemas.microsoft.com/cdo/configuration/smtpserverport"]; 
106                         if (tmp != null)
107                                 port = int.Parse (tmp);
108                 }
109
110                 void StartSend (MailMessageWrapper msg)
111                 {
112                         ReadFields (msg);
113                         Connect ();
114
115                         // read the server greeting
116                         smtp.ReadResponse ();
117                         smtp.CheckForStatusCode (220);
118
119                         if (usessl || (username != null && password != null && authenticate != 1)) 
120                         {
121                                 smtp.WriteEhlo (Dns.GetHostName ());
122
123                                 if (usessl) {
124                                         bool isSSL = smtp.WriteStartTLS ();
125                                         if (isSSL)
126                                                 ChangeToSSLSocket ();
127                                 }
128
129                                 if (username != null && password != null && authenticate != 1) {
130                                         smtp.WriteAuthLogin ();
131                                         if (smtp.LastResponse.StatusCode == 334) {
132                                                 smtp.WriteLine (Convert.ToBase64String (Encoding.ASCII.GetBytes (username)));
133                                                 smtp.ReadResponse ();
134                                                 smtp.CheckForStatusCode (334);
135                                                 smtp.WriteLine (Convert.ToBase64String (Encoding.ASCII.GetBytes (password)));
136                                                 smtp.ReadResponse ();
137                                                 smtp.CheckForStatusCode (235);
138                                         }
139                                 }
140                         } else  {
141                                 smtp.WriteHelo (Dns.GetHostName ());
142                         }
143                 }
144         
145                 public void Send (MailMessageWrapper msg)
146                 {
147                         if (msg.From == null)
148                                 throw new SmtpException ("From property must be set.");
149
150                         if (msg.To == null)
151                                 if (msg.To.Count < 1)
152                                         throw new SmtpException ("Atleast one recipient must be set.");
153             
154                         StartSend (msg);
155                         // start with a reset incase old data
156                         // is present at the server in this session
157                         smtp.WriteRset ();
158             
159                         // write the mail from command
160                         smtp.WriteMailFrom (msg.From.Address);
161             
162                         // write the rcpt to command for the To addresses
163                         foreach (MailAddress addr in msg.To)
164                                 smtp.WriteRcptTo (addr.Address);
165
166                         // write the rcpt to command for the Cc addresses
167                         foreach (MailAddress addr in msg.Cc)
168                                 smtp.WriteRcptTo (addr.Address);
169
170                         // write the rcpt to command for the Bcc addresses
171                         foreach (MailAddress addr in msg.Bcc)
172                                 smtp.WriteRcptTo (addr.Address);
173             
174                         // write the data command and then
175                         // send the email
176                         smtp.WriteData ();
177                 
178                         if (msg.Attachments.Count == 0)
179                                 SendSinglepartMail (msg);           
180                         else
181                                 SendMultipartMail (msg);
182
183                         // write the data end tag "."
184                         smtp.WriteDataEndTag ();
185                 }
186         
187                 // sends a single part mail to the server
188                 void SendSinglepartMail (MailMessageWrapper msg)
189                 {                           
190                         // write the header
191                         smtp.WriteHeader (msg.Header);
192             
193                         // send the mail body
194                         smtp.WriteBytes (msg.BodyEncoding.GetBytes (msg.Body));
195                 }
196
197                 // SECURITY-FIXME: lower assertion with imperative asserts      
198                 [FileIOPermission (SecurityAction.Assert, Unrestricted = true)]
199                 // sends a multipart mail to the server
200                 void SendMultipartMail (MailMessageWrapper msg)
201                 {    
202                         // generate the boundary between attachments
203                         string boundary = MailUtil.GenerateBoundary ();
204                 
205                         // set the Content-Type header to multipart/mixed
206                         string bodyContentType = msg.Header.ContentType;
207
208                         msg.Header.ContentType = String.Concat ("multipart/mixed;\r\n   boundary=", boundary);
209                 
210                         // write the header
211                         smtp.WriteHeader (msg.Header);
212                 
213                         // write the first part text part
214                         // before the attachments
215                         smtp.WriteBoundary (boundary);
216                 
217                         MailHeader partHeader = new MailHeader ();
218                         partHeader.ContentType = bodyContentType;               
219
220                         // Add all the custom headers to body part as specified in 
221                         //Fields property of MailMessageWrapper
222
223                         //Remove fields specific for authenticating to SMTP server.
224                         //Need to incorporate AUTH command in SmtpStream to handle  
225                         //Authorization info. Its a temporary fix for Bug no 68829.
226                         //Will dig some more on SMTP AUTH command, and then implement
227                         //Authorization. - Sanjay
228
229                         if (msg.Fields.Data ["http://schemas.microsoft.com/cdo/configuration/smtpauthenticate"] != null)
230                                 msg.Fields.Data.Remove ("http://schemas.microsoft.com/cdo/configuration/smtpauthenticate");
231                         if (msg.Fields.Data ["http://schemas.microsoft.com/cdo/configuration/sendusername"] != null)
232                                 msg.Fields.Data.Remove ("http://schemas.microsoft.com/cdo/configuration/sendusername");
233                         if (msg.Fields.Data ["http://schemas.microsoft.com/cdo/configuration/sendpassword"] != null)
234                                 msg.Fields.Data.Remove ("http://schemas.microsoft.com/cdo/configuration/sendpassword");
235                         partHeader.Data.Add (msg.Fields.Data);
236
237                         smtp.WriteHeader (partHeader);
238           
239                         // FIXME: probably need to use QP or Base64 on everything higher
240                         // then 8-bit .. like utf-16
241                         smtp.WriteBytes (msg.BodyEncoding.GetBytes (msg.Body) );
242
243                         smtp.WriteBoundary (boundary);
244
245                         // now start to write the attachments
246             
247                         for (int i=0; i< msg.Attachments.Count ; i++) {
248                                 MailAttachment a = (MailAttachment)msg.Attachments[ i ];
249                                 FileInfo fileInfo = new FileInfo (a.Filename);
250                                 MailHeader aHeader = new MailHeader ();
251                 
252                                 aHeader.ContentType = 
253                                         String.Concat (MimeTypes.GetMimeType (fileInfo.Name), "; name=\"", fileInfo.Name, "\"");
254                 
255                                 aHeader.ContentDisposition = String.Concat ("attachment; filename=\"", fileInfo.Name, "\"");
256                                 aHeader.ContentTransferEncoding = a.Encoding.ToString();
257                                 smtp.WriteHeader (aHeader);
258                    
259                                 // perform the actual writing of the file.
260                                 // read from the file stream and write to the tcp stream
261                                 FileStream ins = fileInfo.OpenRead ();
262                 
263                                 // create an apropriate encoder
264                                 IAttachmentEncoder encoder;
265                                 if (a.Encoding == MailEncoding.UUEncode)
266                                         encoder = new UUAttachmentEncoder (644, fileInfo.Name );
267                                 else
268                                         encoder = new Base64AttachmentEncoder ();
269                 
270                                 encoder.EncodeStream (ins, smtp.Stream);
271                 
272                                 ins.Close ();
273                                 smtp.WriteLine ("");
274                 
275                                 // if it is the last attachment write
276                                 // the final boundary otherwise write
277                                 // a normal one.
278                                 if (i < (msg.Attachments.Count - 1))
279                                         smtp.WriteBoundary (boundary);
280                                 else
281                                         smtp.WriteFinalBoundary (boundary);
282                         }
283                 }
284         
285                 // send quit command and
286                 // closes the connection
287                 public void Close()
288                 {
289                         smtp.WriteQuit();
290                         tcpConnection.Close();
291                 }
292         }
293 }