MySQLGridData.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. /*
  2. * Copyright (c) Contributors, http://opensimulator.org/
  3. * See CONTRIBUTORS.TXT for a full list of copyright holders.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. * * Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * * Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. * * Neither the name of the OpenSim Project nor the
  13. * names of its contributors may be used to endorse or promote products
  14. * derived from this software without specific prior written permission.
  15. *
  16. * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
  17. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  18. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19. * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
  20. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  21. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  22. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  23. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  25. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. */
  27. using System;
  28. using System.Collections.Generic;
  29. using System.Data;
  30. using System.Reflection;
  31. using OpenMetaverse;
  32. using log4net;
  33. using OpenSim.Framework;
  34. namespace OpenSim.Data.MySQL
  35. {
  36. /// <summary>
  37. /// A MySQL Interface for the Grid Server
  38. /// </summary>
  39. public class MySQLGridData : GridDataBase
  40. {
  41. private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  42. /// <summary>
  43. /// MySQL Database Manager
  44. /// </summary>
  45. private MySQLManager database;
  46. /// <summary>
  47. /// Better DB manager. Swap-in replacement too.
  48. /// </summary>
  49. public Dictionary<int, MySQLSuperManager> m_dbconnections = new Dictionary<int, MySQLSuperManager>();
  50. public int m_maxConnections = 10;
  51. public int m_lastConnect;
  52. public MySQLSuperManager GetLockedConnection()
  53. {
  54. int lockedCons = 0;
  55. while (true)
  56. {
  57. m_lastConnect++;
  58. // Overflow protection
  59. if (m_lastConnect == int.MaxValue)
  60. m_lastConnect = 0;
  61. MySQLSuperManager x = m_dbconnections[m_lastConnect % m_maxConnections];
  62. if (!x.Locked)
  63. {
  64. x.GetLock();
  65. return x;
  66. }
  67. lockedCons++;
  68. if (lockedCons > m_maxConnections)
  69. {
  70. lockedCons = 0;
  71. System.Threading.Thread.Sleep(1000); // Wait some time before searching them again.
  72. m_log.Debug(
  73. "WARNING: All threads are in use. Probable cause: Something didnt release a mutex properly, or high volume of requests inbound.");
  74. }
  75. }
  76. }
  77. override public void Initialise()
  78. {
  79. m_log.Info("[MySQLGridData]: " + Name + " cannot be default-initialized!");
  80. throw new PluginNotInitialisedException (Name);
  81. }
  82. /// <summary>
  83. /// <para>Initialises Grid interface</para>
  84. /// <para>
  85. /// <list type="bullet">
  86. /// <item>Loads and initialises the MySQL storage plugin</item>
  87. /// <item>Warns and uses the obsolete mysql_connection.ini if connect string is empty.</item>
  88. /// <item>Check for migration</item>
  89. /// </list>
  90. /// </para>
  91. /// </summary>
  92. /// <param name="connect">connect string.</param>
  93. override public void Initialise(string connect)
  94. {
  95. if (connect != String.Empty)
  96. {
  97. database = new MySQLManager(connect);
  98. m_log.Info("Creating " + m_maxConnections + " DB connections...");
  99. for (int i = 0; i < m_maxConnections; i++)
  100. {
  101. m_log.Info("Connecting to DB... [" + i + "]");
  102. MySQLSuperManager msm = new MySQLSuperManager();
  103. msm.Manager = new MySQLManager(connect);
  104. m_dbconnections.Add(i, msm);
  105. }
  106. }
  107. else
  108. {
  109. m_log.Warn("Using deprecated mysql_connection.ini. Please update database_connect in GridServer_Config.xml and we'll use that instead");
  110. IniFile GridDataMySqlFile = new IniFile("mysql_connection.ini");
  111. string settingHostname = GridDataMySqlFile.ParseFileReadValue("hostname");
  112. string settingDatabase = GridDataMySqlFile.ParseFileReadValue("database");
  113. string settingUsername = GridDataMySqlFile.ParseFileReadValue("username");
  114. string settingPassword = GridDataMySqlFile.ParseFileReadValue("password");
  115. string settingPooling = GridDataMySqlFile.ParseFileReadValue("pooling");
  116. string settingPort = GridDataMySqlFile.ParseFileReadValue("port");
  117. database = new MySQLManager(settingHostname, settingDatabase, settingUsername, settingPassword,
  118. settingPooling, settingPort);
  119. m_log.Info("Creating " + m_maxConnections + " DB connections...");
  120. for (int i = 0; i < m_maxConnections; i++)
  121. {
  122. m_log.Info("Connecting to DB... [" + i + "]");
  123. MySQLSuperManager msm = new MySQLSuperManager();
  124. msm.Manager = new MySQLManager(settingHostname, settingDatabase, settingUsername, settingPassword,
  125. settingPooling, settingPort);
  126. m_dbconnections.Add(i, msm);
  127. }
  128. }
  129. // This actually does the roll forward assembly stuff
  130. Assembly assem = GetType().Assembly;
  131. Migration m = new Migration(database.Connection, assem, "GridStore");
  132. // TODO: After rev 6000, remove this. People should have
  133. // been rolled onto the new migration code by then.
  134. TestTables(m);
  135. m.Update();
  136. }
  137. #region Test and initialization code
  138. /// <summary>
  139. /// Ensure that the user related tables exists and are at the latest version
  140. /// </summary>
  141. private void TestTables(Migration m)
  142. {
  143. // we already have migrations, get out of here
  144. if (m.Version > 0)
  145. return;
  146. Dictionary<string, string> tableList = new Dictionary<string, string>();
  147. tableList["regions"] = null;
  148. database.GetTableVersion(tableList);
  149. UpgradeRegionsTable(tableList["regions"]);
  150. // we have tables, but not a migration model yet
  151. if (m.Version == 0)
  152. m.Version = 1;
  153. }
  154. /// <summary>
  155. /// Create or upgrade the table if necessary
  156. /// </summary>
  157. /// <param name="oldVersion">A null indicates that the table does not
  158. /// currently exist</param>
  159. private void UpgradeRegionsTable(string oldVersion)
  160. {
  161. // null as the version, indicates that the table didn't exist
  162. if (oldVersion == null)
  163. {
  164. database.ExecuteResourceSql("CreateRegionsTable.sql");
  165. return;
  166. }
  167. if (oldVersion.Contains("Rev. 1"))
  168. {
  169. database.ExecuteResourceSql("UpgradeRegionsTableToVersion2.sql");
  170. return;
  171. }
  172. if (oldVersion.Contains("Rev. 2"))
  173. {
  174. database.ExecuteResourceSql("UpgradeRegionsTableToVersion3.sql");
  175. return;
  176. }
  177. }
  178. #endregion
  179. /// <summary>
  180. /// Shuts down the grid interface
  181. /// </summary>
  182. override public void Dispose()
  183. {
  184. database.Close();
  185. }
  186. /// <summary>
  187. /// Returns the plugin name
  188. /// </summary>
  189. /// <returns>Plugin name</returns>
  190. override public string Name
  191. {
  192. get { return "MySql OpenGridData"; }
  193. }
  194. /// <summary>
  195. /// Returns the plugin version
  196. /// </summary>
  197. /// <returns>Plugin version</returns>
  198. override public string Version
  199. {
  200. get { return "0.1"; }
  201. }
  202. /// <summary>
  203. /// Returns all the specified region profiles within coordates -- coordinates are inclusive
  204. /// </summary>
  205. /// <param name="xmin">Minimum X coordinate</param>
  206. /// <param name="ymin">Minimum Y coordinate</param>
  207. /// <param name="xmax">Maximum X coordinate</param>
  208. /// <param name="ymax">Maximum Y coordinate</param>
  209. /// <returns>Array of sim profiles</returns>
  210. override public RegionProfileData[] GetProfilesInRange(uint xmin, uint ymin, uint xmax, uint ymax)
  211. {
  212. MySQLSuperManager dbm = GetLockedConnection();
  213. try
  214. {
  215. Dictionary<string, object> param = new Dictionary<string, object>();
  216. param["?xmin"] = xmin.ToString();
  217. param["?ymin"] = ymin.ToString();
  218. param["?xmax"] = xmax.ToString();
  219. param["?ymax"] = ymax.ToString();
  220. IDbCommand result =
  221. dbm.Manager.Query(
  222. "SELECT * FROM regions WHERE locX >= ?xmin AND locX <= ?xmax AND locY >= ?ymin AND locY <= ?ymax",
  223. param);
  224. IDataReader reader = result.ExecuteReader();
  225. RegionProfileData row;
  226. List<RegionProfileData> rows = new List<RegionProfileData>();
  227. while ((row = dbm.Manager.readSimRow(reader)) != null)
  228. {
  229. rows.Add(row);
  230. }
  231. reader.Close();
  232. result.Dispose();
  233. return rows.ToArray();
  234. }
  235. catch (Exception e)
  236. {
  237. dbm.Manager.Reconnect();
  238. m_log.Error(e.ToString());
  239. return null;
  240. }
  241. finally
  242. {
  243. dbm.Release();
  244. }
  245. }
  246. /// <summary>
  247. /// Returns up to maxNum profiles of regions that have a name starting with namePrefix
  248. /// </summary>
  249. /// <param name="name">The name to match against</param>
  250. /// <param name="maxNum">Maximum number of profiles to return</param>
  251. /// <returns>A list of sim profiles</returns>
  252. override public List<RegionProfileData> GetRegionsByName(string namePrefix, uint maxNum)
  253. {
  254. MySQLSuperManager dbm = GetLockedConnection();
  255. try
  256. {
  257. Dictionary<string, object> param = new Dictionary<string, object>();
  258. param["?name"] = namePrefix + "%";
  259. IDbCommand result =
  260. dbm.Manager.Query(
  261. "SELECT * FROM regions WHERE regionName LIKE ?name",
  262. param);
  263. IDataReader reader = result.ExecuteReader();
  264. RegionProfileData row;
  265. List<RegionProfileData> rows = new List<RegionProfileData>();
  266. while (rows.Count < maxNum && (row = dbm.Manager.readSimRow(reader)) != null)
  267. {
  268. rows.Add(row);
  269. }
  270. reader.Close();
  271. result.Dispose();
  272. return rows;
  273. }
  274. catch (Exception e)
  275. {
  276. dbm.Manager.Reconnect();
  277. m_log.Error(e.ToString());
  278. return null;
  279. }
  280. finally
  281. {
  282. dbm.Release();
  283. }
  284. }
  285. /// <summary>
  286. /// Returns a sim profile from it's location
  287. /// </summary>
  288. /// <param name="handle">Region location handle</param>
  289. /// <returns>Sim profile</returns>
  290. override public RegionProfileData GetProfileByHandle(ulong handle)
  291. {
  292. MySQLSuperManager dbm = GetLockedConnection();
  293. try
  294. {
  295. Dictionary<string, object> param = new Dictionary<string, object>();
  296. param["?handle"] = handle.ToString();
  297. IDbCommand result = dbm.Manager.Query("SELECT * FROM regions WHERE regionHandle = ?handle", param);
  298. IDataReader reader = result.ExecuteReader();
  299. RegionProfileData row = dbm.Manager.readSimRow(reader);
  300. reader.Close();
  301. result.Dispose();
  302. return row;
  303. }
  304. catch (Exception e)
  305. {
  306. dbm.Manager.Reconnect();
  307. m_log.Error(e.ToString());
  308. return null;
  309. }
  310. finally
  311. {
  312. dbm.Release();
  313. }
  314. }
  315. /// <summary>
  316. /// Returns a sim profile from it's UUID
  317. /// </summary>
  318. /// <param name="uuid">The region UUID</param>
  319. /// <returns>The sim profile</returns>
  320. override public RegionProfileData GetProfileByUUID(UUID uuid)
  321. {
  322. MySQLSuperManager dbm = GetLockedConnection();
  323. try
  324. {
  325. Dictionary<string, object> param = new Dictionary<string, object>();
  326. param["?uuid"] = uuid.ToString();
  327. IDbCommand result = dbm.Manager.Query("SELECT * FROM regions WHERE uuid = ?uuid", param);
  328. IDataReader reader = result.ExecuteReader();
  329. RegionProfileData row = dbm.Manager.readSimRow(reader);
  330. reader.Close();
  331. result.Dispose();
  332. return row;
  333. }
  334. catch (Exception e)
  335. {
  336. dbm.Manager.Reconnect();
  337. m_log.Error(e.ToString());
  338. return null;
  339. } finally
  340. {
  341. dbm.Release();
  342. }
  343. }
  344. /// <summary>
  345. /// Returns a sim profile from it's Region name string
  346. /// </summary>
  347. /// <returns>The sim profile</returns>
  348. override public RegionProfileData GetProfileByString(string regionName)
  349. {
  350. if (regionName.Length > 2)
  351. {
  352. MySQLSuperManager dbm = GetLockedConnection();
  353. try
  354. {
  355. Dictionary<string, object> param = new Dictionary<string, object>();
  356. // Add % because this is a like query.
  357. param["?regionName"] = regionName + "%";
  358. // Order by statement will return shorter matches first. Only returns one record or no record.
  359. IDbCommand result =
  360. dbm.Manager.Query(
  361. "SELECT * FROM regions WHERE regionName like ?regionName order by LENGTH(regionName) asc LIMIT 1",
  362. param);
  363. IDataReader reader = result.ExecuteReader();
  364. RegionProfileData row = dbm.Manager.readSimRow(reader);
  365. reader.Close();
  366. result.Dispose();
  367. return row;
  368. }
  369. catch (Exception e)
  370. {
  371. dbm.Manager.Reconnect();
  372. m_log.Error(e.ToString());
  373. return null;
  374. }
  375. finally
  376. {
  377. dbm.Release();
  378. }
  379. }
  380. m_log.Error("[GRID DB]: Searched for a Region Name shorter then 3 characters");
  381. return null;
  382. }
  383. /// <summary>
  384. /// Adds a new profile to the database
  385. /// </summary>
  386. /// <param name="profile">The profile to add</param>
  387. /// <returns>Successful?</returns>
  388. override public DataResponse AddProfile(RegionProfileData profile)
  389. {
  390. MySQLSuperManager dbm = GetLockedConnection();
  391. try {
  392. if (dbm.Manager.insertRegion(profile))
  393. {
  394. return DataResponse.RESPONSE_OK;
  395. }
  396. return DataResponse.RESPONSE_ERROR;
  397. }
  398. finally
  399. {
  400. dbm.Release();
  401. }
  402. }
  403. /// <summary>
  404. /// Update a sim profile
  405. /// </summary>
  406. /// <param name="profile">The profile to update</param>
  407. /// <returns>Sucessful?</returns>
  408. /// <remarks>Same as AddProfile</remarks>
  409. override public DataResponse UpdateProfile(RegionProfileData profile)
  410. {
  411. return AddProfile(profile);
  412. }
  413. /// <summary>
  414. /// Deletes a sim profile from the database
  415. /// </summary>
  416. /// <param name="uuid">the sim UUID</param>
  417. /// <returns>Successful?</returns>
  418. //public DataResponse DeleteProfile(RegionProfileData profile)
  419. override public DataResponse DeleteProfile(string uuid)
  420. {
  421. MySQLSuperManager dbm = GetLockedConnection();
  422. try {
  423. if (dbm.Manager.deleteRegion(uuid))
  424. {
  425. return DataResponse.RESPONSE_OK;
  426. }
  427. return DataResponse.RESPONSE_ERROR;
  428. } finally
  429. {
  430. dbm.Release();
  431. }
  432. }
  433. /// <summary>
  434. /// DEPRECATED. Attempts to authenticate a region by comparing a shared secret.
  435. /// </summary>
  436. /// <param name="uuid">The UUID of the challenger</param>
  437. /// <param name="handle">The attempted regionHandle of the challenger</param>
  438. /// <param name="authkey">The secret</param>
  439. /// <returns>Whether the secret and regionhandle match the database entry for UUID</returns>
  440. override public bool AuthenticateSim(UUID uuid, ulong handle, string authkey)
  441. {
  442. bool throwHissyFit = false; // Should be true by 1.0
  443. if (throwHissyFit)
  444. throw new Exception("CRYPTOWEAK AUTHENTICATE: Refusing to authenticate due to replay potential.");
  445. RegionProfileData data = GetProfileByUUID(uuid);
  446. return (handle == data.regionHandle && authkey == data.regionSecret);
  447. }
  448. /// <summary>
  449. /// NOT YET FUNCTIONAL. Provides a cryptographic authentication of a region
  450. /// </summary>
  451. /// <remarks>This requires a security audit.</remarks>
  452. /// <param name="uuid"></param>
  453. /// <param name="handle"></param>
  454. /// <param name="authhash"></param>
  455. /// <param name="challenge"></param>
  456. /// <returns></returns>
  457. public bool AuthenticateSim(UUID uuid, ulong handle, string authhash, string challenge)
  458. {
  459. // SHA512Managed HashProvider = new SHA512Managed();
  460. // Encoding TextProvider = new UTF8Encoding();
  461. // byte[] stream = TextProvider.GetBytes(uuid.ToString() + ":" + handle.ToString() + ":" + challenge);
  462. // byte[] hash = HashProvider.ComputeHash(stream);
  463. return false;
  464. }
  465. /// <summary>
  466. /// Adds a location reservation
  467. /// </summary>
  468. /// <param name="x">x coordinate</param>
  469. /// <param name="y">y coordinate</param>
  470. /// <returns></returns>
  471. override public ReservationData GetReservationAtPoint(uint x, uint y)
  472. {
  473. MySQLSuperManager dbm = GetLockedConnection();
  474. try
  475. {
  476. Dictionary<string, object> param = new Dictionary<string, object>();
  477. param["?x"] = x.ToString();
  478. param["?y"] = y.ToString();
  479. IDbCommand result =
  480. dbm.Manager.Query(
  481. "SELECT * FROM reservations WHERE resXMin <= ?x AND resXMax >= ?x AND resYMin <= ?y AND resYMax >= ?y",
  482. param);
  483. IDataReader reader = result.ExecuteReader();
  484. ReservationData row = dbm.Manager.readReservationRow(reader);
  485. reader.Close();
  486. result.Dispose();
  487. return row;
  488. }
  489. catch (Exception e)
  490. {
  491. dbm.Manager.Reconnect();
  492. m_log.Error(e.ToString());
  493. return null;
  494. } finally
  495. {
  496. dbm.Release();
  497. }
  498. }
  499. }
  500. }