Merge pull request #347 from JamesB7/master
[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 #if TARGET_JVM
72                         java.lang.Class c = vmw.common.TypeUtils.ToClass (smtp.Stream);
73                         java.lang.reflect.Method m = c.getMethod ("ChangeToSSLSocket", null);
74                         m.invoke (smtp.Stream, new object[]{});
75 #else
76                         // Load Mono.Security.dll
77                         Assembly a;
78                         try {
79                                 a = Assembly.Load (Consts.AssemblyMono_Security);
80                         } catch (System.IO.FileNotFoundException) {
81                                 throw new SmtpException ("Cannot load Mono.Security.dll");
82                         }
83                         Type tSslClientStream = a.GetType ("Mono.Security.Protocol.Tls.SslClientStream");
84                         object[] consArgs = new object[4];
85                         consArgs[0] = smtp.Stream;
86                         consArgs[1] = server;
87                         consArgs[2] = true;
88                         Type tSecurityProtocolType = a.GetType ("Mono.Security.Protocol.Tls.SecurityProtocolType");
89                         int nSsl3Val = (int) Enum.Parse (tSecurityProtocolType, "Ssl3");
90                         int nTlsVal = (int) Enum.Parse (tSecurityProtocolType, "Tls");
91                         consArgs[3] = Enum.ToObject (tSecurityProtocolType, nSsl3Val | nTlsVal);
92
93                         object objSslClientStream = Activator.CreateInstance (tSslClientStream, consArgs); 
94
95                         if (objSslClientStream != null)
96                                 smtp = new SmtpStream ((Stream)objSslClientStream);
97 #endif
98                 }
99                 
100                 void ReadFields (MailMessageWrapper msg)
101                 {
102                         string tmp;
103                         username = msg.Fields.Data ["http://schemas.microsoft.com/cdo/configuration/sendusername"];
104                         password = msg.Fields.Data ["http://schemas.microsoft.com/cdo/configuration/sendpassword"]; 
105                         tmp = msg.Fields.Data ["http://schemas.microsoft.com/cdo/configuration/smtpauthenticate"]; 
106                         if (tmp != null)
107                                 authenticate = short.Parse (tmp);
108                         tmp = msg.Fields.Data ["http://schemas.microsoft.com/cdo/configuration/smtpusessl"];    
109                         if (tmp != null)
110                                 usessl = bool.Parse (tmp);
111                         tmp = msg.Fields.Data ["http://schemas.microsoft.com/cdo/configuration/smtpserverport"]; 
112                         if (tmp != null)
113                                 port = int.Parse (tmp);
114                 }
115
116                 void StartSend (MailMessageWrapper msg)
117                 {
118                         ReadFields (msg);
119                         Connect ();
120
121                         // read the server greeting
122                         smtp.ReadResponse ();
123                         smtp.CheckForStatusCode (220);
124
125                         if (usessl || (username != null && password != null && authenticate != 1)) 
126                         {
127                                 smtp.WriteEhlo (Dns.GetHostName ());
128
129                                 if (usessl) {
130                                         bool isSSL = smtp.WriteStartTLS ();
131                                         if (isSSL)
132                                                 ChangeToSSLSocket ();
133                                 }
134
135                                 if (username != null && password != null && authenticate != 1) {
136                                         smtp.WriteAuthLogin ();
137                                         if (smtp.LastResponse.StatusCode == 334) {
138                                                 smtp.WriteLine (Convert.ToBase64String (Encoding.ASCII.GetBytes (username)));
139                                                 smtp.ReadResponse ();
140                                                 smtp.CheckForStatusCode (334);
141                                                 smtp.WriteLine (Convert.ToBase64String (Encoding.ASCII.GetBytes (password)));
142                                                 smtp.ReadResponse ();
143                                                 smtp.CheckForStatusCode (235);
144                                         }
145                                 }
146                         } else  {
147                                 smtp.WriteHelo (Dns.GetHostName ());
148                         }
149                 }
150         
151                 public void Send (MailMessageWrapper msg)
152                 {
153                         if (msg.From == null)
154                                 throw new SmtpException ("From property must be set.");
155
156                         if (msg.To == null)
157                                 if (msg.To.Count < 1)
158                                         throw new SmtpException ("Atleast one recipient must be set.");
159             
160                         StartSend (msg);
161                         // start with a reset incase old data
162                         // is present at the server in this session
163                         smtp.WriteRset ();
164             
165                         // write the mail from command
166                         smtp.WriteMailFrom (msg.From.Address);
167             
168                         // write the rcpt to command for the To addresses
169                         foreach (MailAddress addr in msg.To)
170                                 smtp.WriteRcptTo (addr.Address);
171
172                         // write the rcpt to command for the Cc addresses
173                         foreach (MailAddress addr in msg.Cc)
174                                 smtp.WriteRcptTo (addr.Address);
175
176                         // write the rcpt to command for the Bcc addresses
177                         foreach (MailAddress addr in msg.Bcc)
178                                 smtp.WriteRcptTo (addr.Address);
179             
180                         // write the data command and then
181                         // send the email
182                         smtp.WriteData ();
183                 
184                         if (msg.Attachments.Count == 0)
185                                 SendSinglepartMail (msg);           
186                         else
187                                 SendMultipartMail (msg);
188
189                         // write the data end tag "."
190                         smtp.WriteDataEndTag ();
191                 }
192         
193                 // sends a single part mail to the server
194                 void SendSinglepartMail (MailMessageWrapper msg)
195                 {                           
196                         // write the header
197                         smtp.WriteHeader (msg.Header);
198             
199                         // send the mail body
200                         smtp.WriteBytes (msg.BodyEncoding.GetBytes (msg.Body));
201                 }
202
203                 // SECURITY-FIXME: lower assertion with imperative asserts      
204                 [FileIOPermission (SecurityAction.Assert, Unrestricted = true)]
205                 // sends a multipart mail to the server
206                 void SendMultipartMail (MailMessageWrapper msg)
207                 {    
208                         // generate the boundary between attachments
209                         string boundary = MailUtil.GenerateBoundary ();
210                 
211                         // set the Content-Type header to multipart/mixed
212                         string bodyContentType = msg.Header.ContentType;
213
214                         msg.Header.ContentType = String.Concat ("multipart/mixed;\r\n   boundary=", boundary);
215                 
216                         // write the header
217                         smtp.WriteHeader (msg.Header);
218                 
219                         // write the first part text part
220                         // before the attachments
221                         smtp.WriteBoundary (boundary);
222                 
223                         MailHeader partHeader = new MailHeader ();
224                         partHeader.ContentType = bodyContentType;               
225
226                         // Add all the custom headers to body part as specified in 
227                         //Fields property of MailMessageWrapper
228
229                         //Remove fields specific for authenticating to SMTP server.
230                         //Need to incorporate AUTH command in SmtpStream to handle  
231                         //Authorization info. Its a temporary fix for Bug no 68829.
232                         //Will dig some more on SMTP AUTH command, and then implement
233                         //Authorization. - Sanjay
234
235                         if (msg.Fields.Data ["http://schemas.microsoft.com/cdo/configuration/smtpauthenticate"] != null)
236                                 msg.Fields.Data.Remove ("http://schemas.microsoft.com/cdo/configuration/smtpauthenticate");
237                         if (msg.Fields.Data ["http://schemas.microsoft.com/cdo/configuration/sendusername"] != null)
238                                 msg.Fields.Data.Remove ("http://schemas.microsoft.com/cdo/configuration/sendusername");
239                         if (msg.Fields.Data ["http://schemas.microsoft.com/cdo/configuration/sendpassword"] != null)
240                                 msg.Fields.Data.Remove ("http://schemas.microsoft.com/cdo/configuration/sendpassword");
241                         partHeader.Data.Add (msg.Fields.Data);
242
243                         smtp.WriteHeader (partHeader);
244           
245                         // FIXME: probably need to use QP or Base64 on everything higher
246                         // then 8-bit .. like utf-16
247                         smtp.WriteBytes (msg.BodyEncoding.GetBytes (msg.Body) );
248
249                         smtp.WriteBoundary (boundary);
250
251                         // now start to write the attachments
252             
253                         for (int i=0; i< msg.Attachments.Count ; i++) {
254                                 MailAttachment a = (MailAttachment)msg.Attachments[ i ];
255                                 FileInfo fileInfo = new FileInfo (a.Filename);
256                                 MailHeader aHeader = new MailHeader ();
257                 
258                                 aHeader.ContentType = 
259                                         String.Concat (MimeTypes.GetMimeType (fileInfo.Name), "; name=\"", fileInfo.Name, "\"");
260                 
261                                 aHeader.ContentDisposition = String.Concat ("attachment; filename=\"", fileInfo.Name, "\"");
262                                 aHeader.ContentTransferEncoding = a.Encoding.ToString();
263                                 smtp.WriteHeader (aHeader);
264                    
265                                 // perform the actual writing of the file.
266                                 // read from the file stream and write to the tcp stream
267                                 FileStream ins = fileInfo.OpenRead ();
268                 
269                                 // create an apropriate encoder
270                                 IAttachmentEncoder encoder;
271                                 if (a.Encoding == MailEncoding.UUEncode)
272                                         encoder = new UUAttachmentEncoder (644, fileInfo.Name );
273                                 else
274                                         encoder = new Base64AttachmentEncoder ();
275                 
276                                 encoder.EncodeStream (ins, smtp.Stream);
277                 
278                                 ins.Close ();
279                                 smtp.WriteLine ("");
280                 
281                                 // if it is the last attachment write
282                                 // the final boundary otherwise write
283                                 // a normal one.
284                                 if (i < (msg.Attachments.Count - 1))
285                                         smtp.WriteBoundary (boundary);
286                                 else
287                                         smtp.WriteFinalBoundary (boundary);
288                         }
289                 }
290         
291                 // send quit command and
292                 // closes the connection
293                 public void Close()
294                 {
295                         smtp.WriteQuit();
296                         tcpConnection.Close();
297                 }
298         }
299 }