(DISTFILES): Comment out a few missing files.
[mono-project.git] / mcs / class / ByteFX.Data / mysqlclient / Driver.cs
blob7e51bd04da7157aeda1167c2491017816cf2d2f2
1 // ByteFX.Data data access components for .Net
2 // Copyright (C) 2002-2003 ByteFX, Inc.
3 //
4 // This library is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU Lesser General Public
6 // License as published by the Free Software Foundation; either
7 // version 2.1 of the License, or (at your option) any later version.
8 //
9 // This library is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 // Lesser General Public License for more details.
13 //
14 // You should have received a copy of the GNU Lesser General Public
15 // License along with this library; if not, write to the Free Software
16 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 using System;
19 using System.Net;
20 using System.Net.Sockets;
21 using System.IO;
22 using ICSharpCode.SharpZipLib.Zip.Compression;
23 using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
24 using System.Security.Cryptography;
25 using ByteFX.Data.Common;
26 using System.Collections;
27 using System.Text;
29 namespace ByteFX.Data.MySqlClient
31 /// <summary>
32 /// Summary description for Driver.
33 /// </summary>
34 internal class Driver
36 protected const int HEADER_LEN = 4;
37 protected const int MIN_COMPRESS_LENGTH = 50;
38 protected const int MAX_PACKET_SIZE = 256*256*256-1;
40 protected Stream stream;
41 protected BufferedStream writer;
42 protected Encoding encoding;
43 protected byte packetSeq;
44 protected long maxPacketSize;
45 protected DBVersion serverVersion;
46 protected bool isOpen;
47 protected string versionString;
48 protected Packet peekedPacket;
50 protected int protocol;
51 protected uint threadID;
52 protected String encryptionSeed;
53 protected int serverCaps;
54 protected bool useCompression = false;
57 public Driver()
59 packetSeq = 0;
60 encoding = System.Text.Encoding.Default;
61 isOpen = false;
64 public Encoding Encoding
66 get { return encoding; }
67 set { encoding = value; }
70 public long MaxPacketSize
72 get { return maxPacketSize; }
73 set { maxPacketSize = value; }
76 public string VersionString
78 get { return versionString; }
81 public DBVersion Version
83 get { return serverVersion; }
86 public void Open( MySqlConnectionString settings )
88 // connect to one of our specified hosts
89 try
91 StreamCreator sc = new StreamCreator( settings.Server, settings.Port, settings.PipeName );
92 stream = sc.GetStream( settings.ConnectionTimeout );
94 catch (Exception ex)
96 throw new MySqlException("Unable to connect to any of the specified MySQL hosts", ex);
99 if (stream == null)
100 throw new MySqlException("Unable to connect to any of the specified MySQL hosts");
102 writer = new BufferedStream( stream );
103 // read off the welcome packet and parse out it's values
104 Packet packet = ReadPacket();
105 protocol = packet.ReadByte();
106 versionString = packet.ReadString();
107 serverVersion = DBVersion.Parse( versionString );
108 threadID = (uint)packet.ReadInteger(4);
109 encryptionSeed = packet.ReadString();
111 // read in Server capabilities if they are provided
112 serverCaps = 0;
113 if (packet.HasMoreData)
114 serverCaps = (int)packet.ReadInteger(2);
116 Authenticate( settings.UserId, settings.Password, settings.UseCompression );
118 // if we are using compression, then we use our CompressedStream class
119 // to hide the ugliness of managing the compression
120 if (settings.UseCompression)
122 stream = new CompressedStream( stream );
123 writer = new BufferedStream( stream );
126 isOpen = true;
129 private Packet CreatePacket( byte[] buf )
131 if (buf == null)
132 return new Packet( serverVersion.isAtLeast(3, 22, 5) );
133 return new Packet( buf, serverVersion.isAtLeast(3, 22, 5 ));
136 private void Authenticate( String userid, String password, bool UseCompression )
138 ClientParam clientParam = ClientParam.CLIENT_FOUND_ROWS | ClientParam.CLIENT_LONG_FLAG;
140 if ((serverCaps & (int)ClientParam.CLIENT_COMPRESS) != 0 && UseCompression)
142 clientParam |= ClientParam.CLIENT_COMPRESS;
145 clientParam |= ClientParam.CLIENT_LONG_PASSWORD;
146 clientParam |= ClientParam.CLIENT_LOCAL_FILES;
147 // if (serverVersion.isAtLeast(4,1,0))
148 // clientParam |= ClientParam.CLIENT_PROTOCOL_41;
149 // if ( (serverCaps & (int)ClientParam.CLIENT_SECURE_CONNECTION ) != 0 && password.Length > 0 )
150 // clientParam |= ClientParam.CLIENT_SECURE_CONNECTION;
152 int packetLength = userid.Length + 16 + 6 + 4; // Passwords can be 16 chars long
154 Packet packet = CreatePacket(null);
156 if ((clientParam & ClientParam.CLIENT_PROTOCOL_41) != 0)
158 packet.WriteInteger( (int)clientParam, 4 );
159 packet.WriteInteger( (256*256*256)-1, 4 );
161 else
163 packet.WriteInteger( (int)clientParam, 2 );
164 packet.WriteInteger( 255*255*255, 3 );
167 packet.WriteString( userid, encoding );
168 if ( (clientParam & ClientParam.CLIENT_SECURE_CONNECTION ) != 0 )
170 // use the new authentication system
171 AuthenticateSecurely( packet, password );
173 else
175 // use old authentication system
176 packet.WriteString( EncryptPassword(password, encryptionSeed, protocol > 9), encoding );
177 // pad zeros out to packetLength for auth
178 for (int i=0; i < (packetLength-packet.Length); i++)
179 packet.WriteByte(0);
180 SendPacket(packet);
183 packet = ReadPacket();
184 if ((clientParam & ClientParam.CLIENT_COMPRESS) != 0)
185 useCompression = true;
188 /// <summary>
189 /// AuthenticateSecurity implements the new 4.1 authentication scheme
190 /// </summary>
191 /// <param name="packet">The in-progress packet we use to complete the authentication</param>
192 /// <param name="password">The password of the user to use</param>
193 private void AuthenticateSecurely( Packet packet, string password )
195 packet.WriteString("xxxxxxxx", encoding );
196 SendPacket(packet);
198 packet = ReadPacket();
200 // compute pass1 hash
201 string newPass = password.Replace(" ","").Replace("\t","");
202 SHA1 sha = new SHA1CryptoServiceProvider();
203 byte[] firstPassBytes = sha.ComputeHash( System.Text.Encoding.Default.GetBytes(newPass));
205 byte[] salt = packet.GetBuffer();
206 byte[] input = new byte[ firstPassBytes.Length + 4 ];
207 salt.CopyTo( input, 0 );
208 firstPassBytes.CopyTo( input, 4 );
209 byte[] outPass = new byte[100];
210 byte[] secondPassBytes = sha.ComputeHash( input );
212 byte[] cryptSalt = new byte[20];
213 Security.ArrayCrypt( salt, 4, cryptSalt, 0, secondPassBytes, 20 );
215 Security.ArrayCrypt( cryptSalt, 0, firstPassBytes, 0, firstPassBytes, 20 );
217 // send the packet
218 packet = CreatePacket(null);
219 packet.Write( firstPassBytes, 0, 20 );
220 SendPacket(packet);
224 /// <summary>
225 ///
226 /// </summary>
227 /// <returns></returns>
228 public Packet PeekPacket()
230 if (peekedPacket != null)
231 return peekedPacket;
233 peekedPacket = ReadPacket();
234 return peekedPacket;
237 /// <summary>
238 /// ReadBuffer continuously loops until it has read the entire
239 /// requested data
240 /// </summary>
241 /// <param name="buf">Buffer to read data into</param>
242 /// <param name="offset">Offset to place the data</param>
243 /// <param name="length">Number of bytes to read</param>
244 private void ReadBuffer( byte[] buf, int offset, int length )
246 while (length > 0)
248 int amountRead = stream.Read( buf, offset, length );
249 if (amountRead == 0)
250 throw new MySqlException("Unexpected end of data encountered");
251 length -= amountRead;
252 offset += amountRead;
256 private Packet ReadPacketFromServer()
258 int len = stream.ReadByte() + (stream.ReadByte() << 8) +
259 (stream.ReadByte() << 16);
260 byte seq = (byte)stream.ReadByte();
261 byte[] buf = new byte[ len ];
262 ReadBuffer( buf, 0, len );
264 if (seq != packetSeq)
265 throw new MySqlException("Unknown transmission status: sequence out of order");
266 packetSeq++;
268 Packet p = CreatePacket(buf);
269 p.Encoding = this.Encoding;
270 if (p.Length == MAX_PACKET_SIZE && serverVersion.isAtLeast(4,0,0))
271 p.Append( ReadPacketFromServer() );
272 return p;
275 /// <summary>
276 /// Reads a single packet off the stream
277 /// </summary>
278 /// <returns></returns>
279 public Packet ReadPacket()
281 // if we have peeked at a packet, then return it
282 if (peekedPacket != null)
284 Packet packet = peekedPacket;
285 peekedPacket = null;
286 return packet;
289 Packet p = ReadPacketFromServer();
291 // if this is an error packet, then throw the exception
292 if (p[0] == 0xff)
294 p.ReadByte();
295 int errorCode = (int)p.ReadInteger(2);
296 string msg = p.ReadString();
297 throw new MySqlException( msg, errorCode );
300 return p;
303 protected MemoryStream CompressBuffer(byte[] buf, int index, int length)
306 if (length < MIN_COMPRESS_LENGTH) return null;
308 MemoryStream ms = new MemoryStream(buf.Length);
309 DeflaterOutputStream dos = new DeflaterOutputStream(ms);
311 dos.WriteByte( (byte)(length & 0xff ));
312 dos.WriteByte( (byte)((length >> 8) & 0xff ));
313 dos.WriteByte( (byte)((length >> 16) & 0xff ));
314 dos.WriteByte( 0 );
316 dos.Write( buf, index, length );
317 dos.Finish();
318 if (ms.Length > length+4) return null;
319 return ms;
322 private void WriteInteger( int v, int numbytes )
324 int val = v;
326 if (numbytes < 1 || numbytes > 4)
327 throw new ArgumentOutOfRangeException("Wrong byte count for WriteInteger");
329 for (int x=0; x < numbytes; x++)
331 writer.WriteByte( (byte)(val&0xff) );
332 val >>= 8;
336 /// <summary>
337 /// Send a buffer to the server in a compressed form
338 /// </summary>
339 /// <param name="buf">Byte buffer to send</param>
340 /// <param name="index">Location in buffer to start sending</param>
341 /// <param name="length">Amount of data to send</param>
342 protected void SendCompressedBuffer(byte[] buf, int index, int length)
344 MemoryStream compressed_bytes = CompressBuffer(buf, index, length);
345 int comp_len = compressed_bytes == null ? length+HEADER_LEN : (int)compressed_bytes.Length;
346 int ucomp_len = compressed_bytes == null ? 0 : length+HEADER_LEN;
348 WriteInteger( comp_len, 3 );
349 writer.WriteByte( packetSeq++ );
350 WriteInteger( ucomp_len, 3 );
351 if (compressed_bytes != null)
352 writer.Write( compressed_bytes.GetBuffer(), 0, (int)compressed_bytes.Length );
353 else
355 WriteInteger( length, 3 );
356 writer.WriteByte( 0 );
357 writer.Write( buf, index, length );
359 stream.Flush();
362 protected void SendBuffer( byte[] buf, int offset, int length )
364 while (length > 0)
366 int amount = Math.Min( 1024, length );
367 writer.Write( buf, offset, amount );
368 writer.Flush();
369 offset += amount;
370 length -= amount;
374 /// <summary>
375 /// Send a single packet to the server.
376 /// </summary>
377 /// <param name="packet">Packet to send to the server</param>
378 /// <remarks>This method will send a single packet to the server
379 /// possibly breaking the packet up into smaller packets that are
380 /// smaller than max_allowed_packet. This method will always send at
381 /// least one packet to the server</remarks>
382 protected void SendPacket(Packet packet)
384 byte[] buf = packet.GetBuffer();
385 int len = packet.Length;
386 int index = 0;
387 bool oneSent = false;
389 // make sure we are not trying to send too much
390 if (packet.Length > maxPacketSize && maxPacketSize > 0)
391 throw new MySqlException("Packet size too large. This MySQL server cannot accept rows larger than " + maxPacketSize + " bytes.");
393 try
395 while (len > 0 || ! oneSent)
397 int lenToSend = Math.Min( len, MAX_PACKET_SIZE );
399 // send the data
400 if (useCompression)
401 SendCompressedBuffer( buf, index, lenToSend );
402 else
404 WriteInteger( lenToSend, 3 );
405 writer.WriteByte( packetSeq++ );
406 writer.Write( buf, index, lenToSend );
407 writer.Flush();
410 len -= lenToSend;
411 index += lenToSend;
412 oneSent = true;
414 writer.Flush();
416 catch (Exception ex)
418 Console.WriteLine( ex.Message );
423 public void Close()
425 if (stream != null)
426 stream.Close();
430 /// <summary>
431 /// Sends the specified command to the database
432 /// </summary>
433 /// <param name="command">Command to execute</param>
434 /// <param name="text">Text attribute of command</param>
435 /// <returns>Result packet returned from database server</returns>
436 public void Send( DBCmd command, String text )
438 CommandResult result = Send( command, this.Encoding.GetBytes( text ) );
439 if (result.IsResultSet)
440 throw new MySqlException("SendCommand failed for command " + text );
443 public CommandResult Send( DBCmd cmd, byte[] bytes )
445 // string s = Encoding.GetString( bytes );
447 Packet packet = CreatePacket(null);
448 packetSeq = 0;
449 packet.WriteByte( (byte)cmd );
450 if (bytes != null)
451 packet.Write( bytes, 0, bytes.Length );
453 SendPacket( packet );
454 packet = ReadPacket();
456 // first check to see if this is a LOAD DATA LOCAL callback
457 // if so, send the file and then read the results
458 long fieldcount = packet.ReadLenInteger();
459 if (fieldcount == Packet.NULL_LEN)
461 string filename = packet.ReadString();
462 SendFileToServer( filename );
463 packet = ReadPacket();
465 else
466 packet.Position = 0;
468 return new CommandResult(packet, this);
471 /// <summary>
472 /// Sends the specified file to the server.
473 /// This supports the LOAD DATA LOCAL INFILE
474 /// </summary>
475 /// <param name="filename"></param>
476 private void SendFileToServer( string filename )
478 Packet p = CreatePacket(null);
479 byte[] buffer = new byte[4092];
480 FileStream fs = null;
481 try
483 fs = new FileStream( filename, FileMode.Open );
484 int count = fs.Read( buffer, 0, buffer.Length );
485 while (count != 0)
487 if ((p.Length + count) > MAX_PACKET_SIZE)
489 SendPacket( p );
490 p.Clear();
492 p.Write( buffer, 0, count );
493 count = fs.Read( buffer, 0, buffer.Length );
495 fs.Close();
497 // send any remaining data
498 if (p.Length > 0)
500 SendPacket(p);
501 p.Clear();
504 catch (Exception ex)
506 throw new MySqlException("Error during LOAD DATA LOCAL INFILE", ex);
508 finally
510 if (fs != null)
511 fs.Close();
512 // empty packet signals end of file
513 p.Clear();
514 SendPacket(p);
518 #region PasswordStuff
519 private static double rand(ref long seed1, ref long seed2)
521 seed1 = (seed1 * 3) + seed2;
522 seed1 %= 0x3fffffff;
523 seed2 = (seed1 + seed2 + 33) % 0x3fffffff;
524 return (seed1 / (double)0x3fffffff);
527 /// <summary>
528 /// Encrypts a password using the MySql encryption scheme
529 /// </summary>
530 /// <param name="password">The password to encrypt</param>
531 /// <param name="message">The encryption seed the server gave us</param>
532 /// <param name="new_ver">Indicates if we should use the old or new encryption scheme</param>
533 /// <returns></returns>
534 public static String EncryptPassword(String password, String message, bool new_ver)
536 if (password == null || password.Length == 0)
537 return password;
539 long[] hash_message = Hash(message);
540 long[] hash_pass = Hash(password);
542 long seed1 = (hash_message[0]^hash_pass[0]) % 0x3fffffff;
543 long seed2 = (hash_message[1]^hash_pass[1]) % 0x3fffffff;
545 char[] scrambled = new char[message.Length];
546 for (int x=0; x < message.Length; x++)
548 double r = rand(ref seed1, ref seed2);
549 scrambled[x] = (char)(Math.Floor(r*31) + 64);
552 if (new_ver)
553 { /* Make it harder to break */
554 char extra = (char)Math.Floor( rand(ref seed1, ref seed2) * 31 );
555 for (int x=0; x < scrambled.Length; x++)
556 scrambled[x] ^= extra;
559 return new string(scrambled);
562 /// <summary>
563 ///
564 /// </summary>
565 /// <param name="P"></param>
566 /// <returns></returns>
567 static long[] Hash(String P)
569 long val1 = 1345345333;
570 long val2 = 0x12345671;
571 long inc = 7;
573 for (int i=0; i < P.Length; i++)
575 if (P[i] == ' ' || P[i] == '\t') continue;
576 long temp = (long)(0xff & P[i]);
577 val1 ^= (((val1 & 63)+inc)*temp) + (val1 << 8);
578 val2 += (val2 << 8) ^ val1;
579 inc += temp;
582 long[] hash = new long[2];
583 hash[0] = val1 & 0x7fffffff;
584 hash[1] = val2 & 0x7fffffff;
585 return hash;
587 #endregion