1 // ByteFX.Data data access components for .Net
2 // Copyright (C) 2002-2003 ByteFX, Inc.
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.
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.
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
20 using System
.Net
.Sockets
;
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
;
29 namespace ByteFX
.Data
.MySqlClient
32 /// Summary description for 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;
60 encoding
= System
.Text
.Encoding
.Default
;
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
91 StreamCreator sc
= new StreamCreator( settings
.Server
, settings
.Port
, settings
.PipeName
);
92 stream
= sc
.GetStream( settings
.ConnectionTimeout
);
96 throw new MySqlException("Unable to connect to any of the specified MySQL hosts", ex
);
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
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
);
129 private Packet
CreatePacket( byte[] buf
)
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 );
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
);
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
++)
183 packet
= ReadPacket();
184 if ((clientParam
& ClientParam
.CLIENT_COMPRESS
) != 0)
185 useCompression
= true;
189 /// AuthenticateSecurity implements the new 4.1 authentication scheme
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
);
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 );
218 packet
= CreatePacket(null);
219 packet
.Write( firstPassBytes
, 0, 20 );
227 /// <returns></returns>
228 public Packet
PeekPacket()
230 if (peekedPacket
!= null)
233 peekedPacket
= ReadPacket();
238 /// ReadBuffer continuously loops until it has read the entire
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
)
248 int amountRead
= stream
.Read( buf
, offset
, length
);
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");
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() );
276 /// Reads a single packet off the stream
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
;
289 Packet p
= ReadPacketFromServer();
291 // if this is an error packet, then throw the exception
295 int errorCode
= (int)p
.ReadInteger(2);
296 string msg
= p
.ReadString();
297 throw new MySqlException( msg
, errorCode
);
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 ));
316 dos
.Write( buf
, index
, length
);
318 if (ms
.Length
> length
+4) return null;
322 private void WriteInteger( int v
, int numbytes
)
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) );
337 /// Send a buffer to the server in a compressed form
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
);
355 WriteInteger( length
, 3 );
356 writer
.WriteByte( 0 );
357 writer
.Write( buf
, index
, length
);
362 protected void SendBuffer( byte[] buf
, int offset
, int length
)
366 int amount
= Math
.Min( 1024, length
);
367 writer
.Write( buf
, offset
, amount
);
375 /// Send a single packet to the server.
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
;
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.");
395 while (len
> 0 || ! oneSent
)
397 int lenToSend
= Math
.Min( len
, MAX_PACKET_SIZE
);
401 SendCompressedBuffer( buf
, index
, lenToSend
);
404 WriteInteger( lenToSend
, 3 );
405 writer
.WriteByte( packetSeq
++ );
406 writer
.Write( buf
, index
, lenToSend
);
418 Console
.WriteLine( ex
.Message
);
431 /// Sends the specified command to the database
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);
449 packet
.WriteByte( (byte)cmd
);
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();
468 return new CommandResult(packet
, this);
472 /// Sends the specified file to the server.
473 /// This supports the LOAD DATA LOCAL INFILE
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;
483 fs
= new FileStream( filename
, FileMode
.Open
);
484 int count
= fs
.Read( buffer
, 0, buffer
.Length
);
487 if ((p
.Length
+ count
) > MAX_PACKET_SIZE
)
492 p
.Write( buffer
, 0, count
);
493 count
= fs
.Read( buffer
, 0, buffer
.Length
);
497 // send any remaining data
506 throw new MySqlException("Error during LOAD DATA LOCAL INFILE", ex
);
512 // empty packet signals end of file
518 #region PasswordStuff
519 private static double rand(ref long seed1
, ref long seed2
)
521 seed1
= (seed1
* 3) + seed2
;
523 seed2
= (seed1
+ seed2
+ 33) % 0x3fffffff;
524 return (seed1
/ (double)0x3fffffff);
528 /// Encrypts a password using the MySql encryption scheme
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)
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);
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
);
565 /// <param name="P"></param>
566 /// <returns></returns>
567 static long[] Hash(String P
)
569 long val1
= 1345345333;
570 long val2
= 0x12345671;
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
;
582 long[] hash
= new long[2];
583 hash
[0] = val1
& 0x7fffffff;
584 hash
[1] = val2
& 0x7fffffff;