<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>code.openark.org &#187; Security</title>
	<atom:link href="http://code.openark.org/blog/tag/security/feed" rel="self" type="application/rss+xml" />
	<link>http://code.openark.org/blog</link>
	<description>Blog by Shlomi Noach</description>
	<lastBuildDate>Wed, 01 Feb 2012 08:19:12 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3</generator>
		<item>
		<title>common_schema rev. 68: eval(), processlist_grantees, candidate_keys, easter_day()</title>
		<link>http://code.openark.org/blog/mysql/common_schema-rev-68-eval-processlist_grantees-candidate_keys-easter_day</link>
		<comments>http://code.openark.org/blog/mysql/common_schema-rev-68-eval-processlist_grantees-candidate_keys-easter_day#comments</comments>
		<pubDate>Tue, 06 Sep 2011 07:05:34 +0000</pubDate>
		<dc:creator>shlomi</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[common_schema]]></category>
		<category><![CDATA[Indexing]]></category>
		<category><![CDATA[Open Source]]></category>
		<category><![CDATA[Security]]></category>

		<guid isPermaLink="false">http://code.openark.org/blog/?p=3952</guid>
		<description><![CDATA[Revision 68 of common_schema is out, and includes some interesting features: eval(): Evaluates the queries generated by a given query match_grantee(): Match an existing account based on user+host processlist_grantees: Assigning of GRANTEEs for connected processes candidate_keys: Listing of prioritized candidate keys: keys which are UNIQUE, by order of best-use. easter_day(): Returns DATE of easter day [...]]]></description>
			<content:encoded><![CDATA[<p>Revision <strong>68</strong> of <a rel="nofollow" href="http://code.google.com/p/common-schema/">common_schema</a> is out, and includes some interesting features:</p>
<ul>
<li><strong>eval()</strong>: Evaluates the queries generated by a given query</li>
<li><strong>match_grantee()</strong>: Match an existing account based on user+host</li>
<li><strong>processlist_grantees</strong>: Assigning of GRANTEEs for connected processes</li>
<li><strong>candidate_keys</strong>: Listing of prioritized candidate keys: keys which are UNIQUE, by order of best-use.</li>
<li><strong>easter_day()</strong>: Returns DATE of easter day in given DATETIME's year.</li>
</ul>
<p>Let's take a slightly closer look at these:</p>
<h4>eval()</h4>
<p>I've dedicated this blog post on <a href="http://code.openark.org/blog/mysql/mysql-eval">MySQL eval()</a> to describe it. In simple summary: <strong>eval()</strong> takes a query which generates queries (most common use queries on <strong>INFORMATION_SCHEMA</strong>) and auto-evaluates (executes) those queries. <a href="http://common-schema.googlecode.com/svn/trunk/common_schema/doc/html/general_procedures.html#eval">Read more</a></p>
<h4>match_grantee()</h4>
<p>As presented in <a title="Link to Finding CURRENT_USER for any user" rel="bookmark" href="http://code.openark.org/blog/mysql/finding-current_user-for-any-user">Finding CURRENT_USER for any user</a>, I've developed the algorithm to match a connected user+host details (as presented with <strong>PROCESSLIST</strong>) with the grantee tables (i.e. the <strong>mysql.user</strong> table), in a manner which simulates the MySQL server account matching algorithm.</p>
<p>This is now available as a stored function: given a user+host, the function returns with the best matched grantee. <a href="http://common-schema.googlecode.com/svn/trunk/common_schema/doc/html/privileges_functions.html#match_grantee">Read more</a></p>
<h4>processlist_grantees</h4>
<p>This view relies on the above, and maps the entire <strong>PROCESSLIST</strong> onto GRANTEEs. The view maps each process onto the GRANTEE (MySQL account) which is the owner of that process. Surprisingly, MySQL does not provide one with such information.<span id="more-3952"></span></p>
<p>The view also provides with the following useful metadata:</p>
<ul>
<li>Is said process executes under a SUPER privilege?</li>
<li>Is this a replication thread, or serving a replicating client?</li>
<li>Is this process the current connection (myself)?</li>
</ul>
<p>In the spirit of <strong>common_schema</strong>, it provides with the SQL commands necessary to <strong>KILL</strong> and <strong>KILL QUERY</strong> for each process. A sample output:</p>
<blockquote>
<pre>mysql&gt; SELECT * FROM common_schema.processlist_grantees;
+--------+------------+---------------------+------------------------+--------------+--------------+----------+---------+-------------------+---------------------+
| ID     | USER       | HOST                | GRANTEE                | grantee_user | grantee_host | is_super | is_repl | sql_kill_query    | sql_kill_connection |
+--------+------------+---------------------+------------------------+--------------+--------------+----------+---------+-------------------+---------------------+
| 650472 | replica    | jboss00.myweb:34266 | 'replica'@'%.myweb'    | replica      | %.myweb      |        0 |       1 | KILL QUERY 650472 | KILL 650472         |
| 692346 | openarkkit | jboss02.myweb:43740 | 'openarkkit'@'%.myweb' | openarkkit   | %.myweb      |        0 |       0 | KILL QUERY 692346 | KILL 692346         |
| 842853 | root       | localhost           | 'root'@'localhost'     | root         | localhost    |        1 |       0 | KILL QUERY 842853 | KILL 842853         |
| 843443 | jboss      | jboss03.myweb:40007 | 'jboss'@'%.myweb'      | jboss        | %.myweb      |        0 |       0 | KILL QUERY 843443 | KILL 843443         |
| 843444 | jboss      | jboss03.myweb:40012 | 'jboss'@'%.myweb'      | jboss        | %.myweb      |        0 |       0 | KILL QUERY 843444 | KILL 843444         |
| 843510 | jboss      | jboss00.myweb:49850 | 'jboss'@'%.myweb'      | jboss        | %.myweb      |        0 |       0 | KILL QUERY 843510 | KILL 843510         |
| 844559 | jboss      | jboss01.myweb:37031 | 'jboss'@'%.myweb'      | jboss        | %.myweb      |        0 |       0 | KILL QUERY 844559 | KILL 844559         |
+--------+------------+---------------------+------------------------+--------------+--------------+----------+---------+-------------------+---------------------+</pre>
</blockquote>
<p>Finally, it is now possible to execute the following:  “Kill all slow queries which are not executed by users with the SUPER privilege or are replication threads”. To just generate the commands, execute:</p>
<blockquote>
<pre>mysql&gt; SELECT <strong>sql_kill_connection</strong> FROM <strong>common_schema.processlist_grantees</strong> WHERE is_super = 0 AND is_repl = 0;</pre>
</blockquote>
<p>Sorry, did you only want to kill the queries? Those which are very slow? Do as follows:</p>
<blockquote>
<pre>mysql&gt; SELECT sql_kill_connection FROM common_schema.processlist_grantees JOIN INFORMATION_SCHEMA.PROCESSLIST <strong>USING(ID)</strong> WHERE <strong>TIME &gt; 10</strong> AND is_super = 0 AND is_repl = 0;</pre>
</blockquote>
<p>But, really, we don't just want <em>commands</em>. We really want to execute this!</p>
<p>Good! Step in <strong>eval()</strong>:</p>
<blockquote>
<pre>mysql&gt; CALL common_schema.<strong>eval</strong>('SELECT <strong>sql_kill_query</strong> FROM common_schema.processlist_grantees JOIN INFORMATION_SCHEMA.PROCESSLIST USING(id) WHERE TIME &gt; 10 AND is_super = 0 AND is_repl = 0');</pre>
</blockquote>
<p><a href="http://common-schema.googlecode.com/svn/trunk/common_schema/doc/html/processlist_grantees.html">Read more</a></p>
<h4>candidate_keys</h4>
<p>A view which lists the candidate keys for tables and provides ranking for those keys, based on some simple heuristics.</p>
<p>This view uses  the same algorithm as that used by <a href="http://openarkkit.googlecode.com/svn/trunk/openarkkit/doc/html/oak-chunk-update.html">oak-chunk-update</a> and <a href="http://openarkkit.googlecode.com/svn/trunk/openarkkit/doc/html/oak-online-alter-table.html">oak-online-alter-table</a>, tools in the <a href="http://code.openark.org/forge/openark-kit">openark kit</a>. So it provides with a way to choose the best candidate key to walk through a table. At current, a table's <strong>PRIMARY KEY</strong> is always considered to be best, because of InnoDB's structure of clustered index. But I intend to change that as well and provide general recommendation about candidate keys (so for example, I would be able to recommend that the <strong>PRIMARY KEY</strong> is not optimal for some table).</p>
<p>Actually, after a discussion initiated by Giuseppe and Roland, starting <a href="http://datacharmer.blogspot.com/2011/09/finding-tables-without-primary-keys.html">here</a> and continuing on mail, there are more checks to be made for candidate keys, and I suspect the next version of <em>candidate_keys</em> will be more informational.</p>
<p><a href="http://common-schema.googlecode.com/svn/trunk/common_schema/doc/html/candidate_keys.html">Read more</a></p>
<h4>easter_day()</h4>
<p>Many thanks to <a href="http://rpbouman.blogspot.com/">Roland Bouman</a> who suggested his code for calculating easter day for a given year. <em>Weehee!</em> This is the first contribution to <em>common_schema</em>! <a href="http://common-schema.googlecode.com/svn/trunk/common_schema/doc/html/time_functions.html#easter_day">Read more</a></p>
<h4>Get it</h4>
<p><em>common_schema</em> is an open source project. It is released under the BSD license.</p>
<p><a href="http://code.google.com/p/common-schema/">Find it here</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://code.openark.org/blog/mysql/common_schema-rev-68-eval-processlist_grantees-candidate_keys-easter_day/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Finding CURRENT_USER for any user</title>
		<link>http://code.openark.org/blog/mysql/finding-current_user-for-any-user</link>
		<comments>http://code.openark.org/blog/mysql/finding-current_user-for-any-user#comments</comments>
		<pubDate>Tue, 09 Aug 2011 11:19:10 +0000</pubDate>
		<dc:creator>shlomi</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[common_schema]]></category>
		<category><![CDATA[Security]]></category>
		<category><![CDATA[SQL]]></category>

		<guid isPermaLink="false">http://code.openark.org/blog/?p=3871</guid>
		<description><![CDATA[A MySQL account is a user/host combination. A MySQL connection is done by a user connecting from some host. However, the user/host from which the connection is made are not the same as the user/host as specified in the account. For example, the account may be created thus: CREATE USER 'temp'@'10.0.0.%' IDENTIFIED BY '123456'; The [...]]]></description>
			<content:encoded><![CDATA[<p>A MySQL account is a user/host combination. A MySQL connection is done by a user connecting from some host.</p>
<p>However, the user/host from which the connection is made are not the same as the user/host as specified in the account. For example, the account may be created thus:</p>
<blockquote>
<pre>CREATE USER 'temp'@'10.0.0.%' IDENTIFIED BY '123456';</pre>
</blockquote>
<p>The host as specified in the above account is a wildcard host. A connection by the <strong>'temp'</strong> user from <strong>'10.0.0.3'</strong> can map into that account. It thus happens that the connected user is <strong>'temp'@'10.0.0.3'</strong>, yet the assigned account is <strong>'temp'@'10.0.0.%'</strong>.</p>
<p>MySQL provides with the <strong>USER()</strong> and <strong>CURRENT_USER()</strong> which map to the connected user and the assigned account, respectively, and which lets the current session identify the relation between the two. Read more on this on the <a href="http://dev.mysql.com/doc/refman/5.1/en/account-activity-auditing.html">MySQL docs</a>.</p>
<h4>The problem</h4>
<p>And the trouble is: MySQL only provides this functionality for the <em>current session</em>. Surprisingly, given a user/host combination, I cannot get MySQL to tell me which account matches those details.</p>
<h4>The inconsistency</h4>
<p>And I care because there is an inconsistency. Namely, when I do <strong>SHOW PROCESSLIST</strong> MySQL tells me the user &amp; host from which the connection is made. It does <em>not</em> tell me the account for which the process is assigned.<span id="more-3871"></span></p>
<blockquote>
<pre>root@mysql-5.1.51&gt; SHOW PROCESSLIST;
+----+------+----------------+---------------+---------+------+-------+------------------+
| Id | User | Host           | db            | Command | Time | State | Info             |
+----+------+----------------+---------------+---------+------+-------+------------------+
| 16 | temp | 10.0.0.3:54142 | common_schema | Query   |    0 | NULL  | SELECT id, ...   |
+----+------+----------------+---------------+---------+------+-------+------------------+</pre>
</blockquote>
<p>The absurdness is that a super user, the manager of a MySQL server, has the full listing of connections, yet is unable to map those connections to accounts.</p>
<p>I got into this because of a suggestion by <a href="http://forge.mysql.com/people/person.php?id=340">Matthew Montgomery</a> to include a <a href="http://forge.mysql.com/tools/tool.php?id=106">tool</a> of his into <a href="http://code.openark.org/forge/common_schema">common_schema</a>.The tool says "Kill all slow queries which are not executed by users with the SUPER privilege".</p>
<p>Great idea! But then, how do you identify such users?</p>
<p>The tool attempts to find an exact match between <strong>INFORMATION_SCHEMA.PROCESSLIST</strong>'s user/host and <strong>INFORMATION_SCHEMA.USER_PRIVILEGES</strong>'s user/host. This will do well when the account is <strong>'root'@'localhost'</strong>, but less so when it is <strong>'maatkit'@'%.mydomain'</strong>.</p>
<p>Unfortunately, many things fall under <strong>SUPER</strong>'s attention, and such accounts as monitoring, backup, management may require that privilege.</p>
<h4>Account matching</h4>
<p>Matching is achievable, but not completely trivial. If you're not aware of this, you should note that <strong>'temp'@'10.0.0.3'</strong> can match any of the following:</p>
<blockquote>
<pre>+------+----------+
| temp | 10.0.%   |
| %    | 10.0.0.3 |
| temp | 10.0.0.3 |
| temp | 10.0.0.% |
+------+----------+</pre>
</blockquote>
<p>And the rule is we must match by most specific host first, then by most specific user. The order of matching should be this:</p>
<blockquote>
<pre>+------+----------+
| temp | 10.0.0.3 |
| %    | 10.0.0.3 |
| temp | 10.0.0.% |
| temp | 10.0.%   |
+------+----------+</pre>
</blockquote>
<p>The first row to match our connection's user/host is the matched account.</p>
<h4>The good news</h4>
<p>This means the problem is reduced to an ORDER BY and to regular expressions. Easy enough to do with SQL. We prefer hosts with no wildcard to those with; we prefer more subdomains, we prefer no wildcard for users.</p>
<h4>The code</h4>
<p>The following query assumes you have two session variables: <strong>@connection_user</strong> and <strong>@connection_host</strong>.</p>
<blockquote>
<pre>SELECT
  user, host
FROM
  mysql.user
WHERE
  @connection_user RLIKE
    CONCAT('^',
      REPLACE(
        user,
        '%', '.*'),
      '$')
  AND SUBSTRING_INDEX(@connection_host, ':', 1) RLIKE
    CONCAT('^',
      REPLACE(
      REPLACE(
        host,
        '.', '\\.'),
        '%', '.*'),
      '$')
ORDER BY
  CHAR_LENGTH(host) - CHAR_LENGTH(REPLACE(host, '%', '')) ASC,
  CHAR_LENGTH(host) - CHAR_LENGTH(REPLACE(host, '.', '')) DESC,
  host ASC,
  CHAR_LENGTH(user) - CHAR_LENGTH(REPLACE(user, '%', '')) ASC,
  user ASC
LIMIT 1
;</pre>
</blockquote>
<p>There is still a slight fine-tuning to do for the above, but it should work for the majority of security setups.</p>
<p>The above (in rewritten form) and derivative work will, of course, be part of the next <a href="../../forge/common_schema">common_schema</a> release, expected early September.</p>
]]></content:encoded>
			<wfw:commentRss>http://code.openark.org/blog/mysql/finding-current_user-for-any-user/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Announcing common_schema: common views &amp; routines for MySQL</title>
		<link>http://code.openark.org/blog/mysql/announcing-common_schema-common-views-routines-for-mysql</link>
		<comments>http://code.openark.org/blog/mysql/announcing-common_schema-common-views-routines-for-mysql#comments</comments>
		<pubDate>Wed, 13 Jul 2011 04:25:24 +0000</pubDate>
		<dc:creator>shlomi</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[Analysis]]></category>
		<category><![CDATA[common_schema]]></category>
		<category><![CDATA[Data Types]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[Indexing]]></category>
		<category><![CDATA[INFORMATION_SCHEMA]]></category>
		<category><![CDATA[InnoDB]]></category>
		<category><![CDATA[Monitoring]]></category>
		<category><![CDATA[Open Source]]></category>
		<category><![CDATA[Schema]]></category>
		<category><![CDATA[Security]]></category>
		<category><![CDATA[SQL]]></category>
		<category><![CDATA[Stored routines]]></category>

		<guid isPermaLink="false">http://code.openark.org/blog/?p=3794</guid>
		<description><![CDATA[Today I have released common_schema, a utility schema for MySQL which includes many views and functions, and is aimed to be installed on any MySQL server. What does it do? There are views answering for all sorts of useful information: stuff related to schema analysis, data dimensions, monitoring, processes &#38; transactions, security, internals... There are [...]]]></description>
			<content:encoded><![CDATA[<p>Today I have released <a title="common_schema" href="http://code.openark.org/forge/common_schema">common_schema</a>, a utility schema for MySQL which includes many views and functions, and is aimed to be installed on any MySQL server.</p>
<h4>What does it do?</h4>
<p>There are views answering for all sorts of useful information: stuff related to schema analysis, data dimensions, monitoring, processes &amp; transactions, security, internals... There are basic functions answering for common needs.</p>
<p>Some of the views/routines simply formalize those queries we tend to write over and over again. Others take the place of external tools, answering complex questions via SQL and metadata. Still others help out with SQL generation.</p>
<p>Here are a few highlights:</p>
<ul>
<li>Did you know you can work out <a href="http://common-schema.googlecode.com/svn/trunk/common_schema/doc/html/global_status_diff_nonzero.html">simple monitoring</a> of your server with a <em>query</em>?  There's a view to do that for you.</li>
<li>How about showing just <a href="http://common-schema.googlecode.com/svn/trunk/common_schema/doc/html/processlist_top.html">the good parts of the processlist</a>?</li>
<li>Does your schema have <a href="http://common-schema.googlecode.com/svn/trunk/common_schema/doc/html/redundant_keys.html">redundant keys</a>?</li>
<li>Or InnoDB tables with <a href="http://common-schema.googlecode.com/svn/trunk/common_schema/doc/html/no_pk_innodb_tables.html">no PRIMARY KEY</a>?</li>
<li>Is AUTO_INCREMENT <a href="http://common-schema.googlecode.com/svn/trunk/common_schema/doc/html/auto_increment_columns.html">running out of space</a>?</li>
<li>Can I get the SQL statements to <a href="http://common-schema.googlecode.com/svn/trunk/common_schema/doc/html/sql_foreign_keys.html">generate my FOREIGN KEYs</a>? To drop them?</li>
<li>And can we finally get <a href="http://common-schema.googlecode.com/svn/trunk/common_schema/doc/html/sql_show_grants.html">SHOW GRANTS for all accounts</a>, and as an <a href="http://common-schema.googlecode.com/svn/trunk/common_schema/doc/html/sql_grants.html">SQL query</a>?</li>
<li>Ever needed a <a href="http://common-schema.googlecode.com/svn/trunk/common_schema/doc/html/general_functions.html#crc64">64 bit CRC function</a>?</li>
<li>And aren't you tired of writing the cumbersome SUBSTRING_INDEX(SUBSTRING_INDEX(str, ',', 3), ',', -1)? <a href="http://common-schema.googlecode.com/svn/trunk/common_schema/doc/html/string_functions.html#split_token">There's an alternative</a>.</li>
</ul>
<p>There's more. Take a look at the <a href="http://common-schema.googlecode.com/svn/trunk/common_schema/doc/html/introduction.html">common_schema documentation</a> for full listing. And it's evolving: I've got quite a few ideas already for future components.</p>
<p>Some of these views rely on heavyweight INFORMATION_SCHEMA tables. You should be aware of the impact and <a href="http://common-schema.googlecode.com/svn/trunk/common_schema/doc/html/risks.html">risks</a>.</p>
<h4>What do I need to install?</h4>
<p>There's no script or executable file. It's just a schema. The distribution in an SQL file which generates <em>common_schema</em>. Much like a dump file.</p>
<h4><span id="more-3794"></span>What are the system requirements?</h4>
<p>It's just between you and your MySQL. There are currently three distribution files, dedicated for different versions of MySQL (and allowing for increased functionality):</p>
<ul>
<li><strong>common_schema_mysql_51</strong>: fits all MySQL &gt;= 5.1 distributions</li>
<li><strong>common_schema_innodb_plugin</strong>: fits MySQL &gt;= 5.1, with InnoDB plugin + INFORMATION_SCHEMA tables enabled</li>
<li><strong>common_schema_percona_server</strong>: fits Percona Server &gt;= 5.1</li>
</ul>
<p>Refer to the <a rel="nofollow" href="http://common-schema.googlecode.com/svn/trunk/common_schema/doc/html/download.html">documentation</a> for more details.</p>
<h4>What are the terms of use?</h4>
<p><em>common_schema</em> is released under the <a href="http://www.opensource.org/licenses/bsd-license.php">BSD license</a>.</p>
<h4>Where can I download it?</h4>
<p>On the <a href="http://code.google.com/p/common-schema/">common_schema project page</a>. Enjoy it!</p>
]]></content:encoded>
			<wfw:commentRss>http://code.openark.org/blog/mysql/announcing-common_schema-common-views-routines-for-mysql/feed</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>ROUTINE_PRIVILEGES implementation</title>
		<link>http://code.openark.org/blog/mysql/routine_privileges-implementation</link>
		<comments>http://code.openark.org/blog/mysql/routine_privileges-implementation#comments</comments>
		<pubDate>Wed, 22 Jun 2011 12:14:45 +0000</pubDate>
		<dc:creator>shlomi</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[INFORMATION_SCHEMA]]></category>
		<category><![CDATA[Open Source]]></category>
		<category><![CDATA[Security]]></category>
		<category><![CDATA[Stored routines]]></category>
		<category><![CDATA[Views]]></category>

		<guid isPermaLink="false">http://code.openark.org/blog/?p=3764</guid>
		<description><![CDATA[Following up on MySQL security: inconsistencies, and on MySQL bug #61596, I was thinking it may take a long time till the non-existent ROUTINE_PRIVILEGES view is implemented. Here's my own implementation of the view. I've followed the somewhat strange conventions used in the *_PRIVILEGES tables in INFORMATION_SCHEMA, where the IS_GRANTABLE is a separate column, although [...]]]></description>
			<content:encoded><![CDATA[<p>Following up on <a title="Link to MySQL security: inconsistencies" rel="bookmark" href="http://code.openark.org/blog/mysql/mysql-security-inconsistencies">MySQL security: inconsistencies</a>, and on <a href="http://bugs.mysql.com/bug.php?id=61596">MySQL bug #61596</a>, I was thinking it may take a long time till the non-existent <strong>ROUTINE_PRIVILEGES</strong> view is implemented. Here's my own implementation of the view.</p>
<p>I've followed the somewhat strange conventions used in the <strong>*_PRIVILEGES</strong> tables in <strong>INFORMATION_SCHEMA</strong>, where the <strong>IS_GRANTABLE</strong> is a separate column, although in <em><del>2nd</del> 1st normal form</em>.</p>
<p>I present it here as a query, using session variables, rather than a view definition:<span id="more-3764"></span></p>
<blockquote>
<pre>SELECT STRAIGHT_JOIN
  CONCAT('\'', User, '\'@\'', Host, '\'') AS GRANTEE,
  NULL AS ROUTINE_CATALOG,
  Db AS ROUTINE_SCHEMA,
  Routine_name AS ROUTINE_NAME,
  Routine_type AS ROUTINE_TYPE,
  UPPER(SUBSTRING_INDEX(SUBSTRING_INDEX(Proc_priv, ',', n+1), ',', -1)) AS PRIVILEGE_TYPE,
  IF(grantable_procs_priv.User IS NULL, 'NO', 'YES') AS IS_GRANTABLE
FROM
  mysql.procs_priv
  CROSS JOIN (SELECT @counter := -1) select_init
  CROSS JOIN (
    SELECT
      @counter := @counter+1 AS n
    FROM
      INFORMATION_SCHEMA.COLLATIONS
    LIMIT 5
  ) numbers
  LEFT JOIN (
      SELECT
        DISTINCT User, Host, Db, Routine_name
      FROM
        mysql.procs_priv
      WHERE
         find_in_set('Grant', Proc_priv) &gt; 0
    ) grantable_procs_priv USING (User, Host, Db, Routine_name)
WHERE
  numbers.n BETWEEN 0 AND CHAR_LENGTH(Proc_priv) - CHAR_LENGTH(REPLACE(Proc_priv, ',', ''))
  AND UPPER(SUBSTRING_INDEX(SUBSTRING_INDEX(Proc_priv, ',', n+1), ',', -1)) != 'GRANT'
ORDER BY
  GRANTEE, ROUTINE_SCHEMA, ROUTINE_NAME, ROUTINE_TYPE, n
;</pre>
</blockquote>
<p>It takes <strong>2</strong> views and a table to make this a VIEW rather than a query.</p>
<p>First teaser: the view which represents this query, along with many other interesting diagnostic views, is to take part in a new open source project I'm working on.</p>
<h4>[UPDATE]</h4>
<p>Guess I was in a rush to produce the query. Here's a shorter, cleaner one:</p>
<blockquote>
<pre>SELECT
  CONCAT('\'', User, '\'@\'', Host, '\'') AS GRANTEE,
  NULL AS ROUTINE_CATALOG,
  Db AS ROUTINE_SCHEMA,
  Routine_name AS ROUTINE_NAME,
  Routine_type AS ROUTINE_TYPE,
  UPPER(SUBSTRING_INDEX(SUBSTRING_INDEX(Proc_priv, ',', n+1), ',', -1)) AS PRIVILEGE_TYPE,
  IF(find_in_set('Grant', Proc_priv) &gt; 0, 'YES', 'NO') AS IS_GRANTABLE
FROM
  mysql.procs_priv
  CROSS JOIN (
    SELECT
      @counter := @counter+1 AS n
    FROM
      INFORMATION_SCHEMA.COLLATIONS, (SELECT @counter := -1) select_init
    LIMIT 5
  ) numbers
WHERE
  numbers.n BETWEEN 0 AND CHAR_LENGTH(Proc_priv) - CHAR_LENGTH(REPLACE(Proc_priv, ',', ''))
  AND UPPER(SUBSTRING_INDEX(SUBSTRING_INDEX(Proc_priv, ',', n+1), ',', -1)) != 'GRANT'
ORDER BY
  GRANTEE, ROUTINE_SCHEMA, ROUTINE_NAME, ROUTINE_TYPE, n
;</pre>
</blockquote>
<p>Example output:</p>
<blockquote>
<pre>+--------------------------+-----------------+----------------+----------------------------+--------------+----------------+--------------+
| GRANTEE                  | ROUTINE_CATALOG | ROUTINE_SCHEMA | ROUTINE_NAME               | ROUTINE_TYPE | PRIVILEGE_TYPE | IS_GRANTABLE |
+--------------------------+-----------------+----------------+----------------------------+--------------+----------------+--------------+
| 'other_user'@'localhost' |            NULL | sakila         | film_in_stock              | PROCEDURE    | EXECUTE        | NO           |
| 'other_user'@'localhost' |            NULL | sakila         | film_in_stock              | PROCEDURE    | ALTER ROUTINE  | NO           |
| 'other_user'@'localhost' |            NULL | sakila         | get_customer_balance       | FUNCTION     | EXECUTE        | NO           |
| 'other_user'@'localhost' |            NULL | sakila         | get_customer_balance       | FUNCTION     | ALTER ROUTINE  | NO           |
| 'other_user'@'localhost' |            NULL | sakila         | inventory_held_by_customer | FUNCTION     | EXECUTE        | NO           |
| 'other_user'@'localhost' |            NULL | sakila         | inventory_held_by_customer | FUNCTION     | ALTER ROUTINE  | NO           |
| 'shlomi'@'127.0.0.1'     |            NULL | sakila         | film_in_stock              | PROCEDURE    | EXECUTE        | YES          |
| 'shlomi'@'127.0.0.1'     |            NULL | sakila         | get_customer_balance       | FUNCTION     | EXECUTE        | NO           |
| 'shlomi'@'127.0.0.1'     |            NULL | sakila         | get_customer_balance       | FUNCTION     | ALTER ROUTINE  | NO           |
| 'world_user'@'localhost' |            NULL | sakila         | get_customer_balance       | FUNCTION     | EXECUTE        | YES          |
| 'world_user'@'localhost' |            NULL | sakila         | get_customer_balance       | FUNCTION     | ALTER ROUTINE  | YES          |
+--------------------------+-----------------+----------------+----------------------------+--------------+----------------+--------------+</pre>
</blockquote>
]]></content:encoded>
			<wfw:commentRss>http://code.openark.org/blog/mysql/routine_privileges-implementation/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>MySQL security: inconsistencies</title>
		<link>http://code.openark.org/blog/mysql/mysql-security-inconsistencies</link>
		<comments>http://code.openark.org/blog/mysql/mysql-security-inconsistencies#comments</comments>
		<pubDate>Wed, 22 Jun 2011 06:39:01 +0000</pubDate>
		<dc:creator>shlomi</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[INFORMATION_SCHEMA]]></category>
		<category><![CDATA[Security]]></category>

		<guid isPermaLink="false">http://code.openark.org/blog/?p=3667</guid>
		<description><![CDATA[Doing some work with MySQL security, I've noticed a few inconsistencies. They're mostly not-too-terrible for daily work, except they get in my way right now. The ALL PRIVILEGES inconsistency The preferred way of assigning account privileges in MySQL is by way of using GRANT. With GRANT, one assigns one or more privileges to an account, [...]]]></description>
			<content:encoded><![CDATA[<p>Doing some work with MySQL security, I've noticed a few inconsistencies. They're mostly not-too-terrible for daily work, except they get in my way right now.</p>
<h4>The ALL PRIVILEGES inconsistency</h4>
<p>The preferred way of assigning account privileges in MySQL is by way of using <a href="http://dev.mysql.com/doc/refman/5.1/en/grant.html">GRANT</a>.</p>
<p>With <strong>GRANT</strong>, one assigns one or more privileges to an account, such as <strong>SELECT</strong>, <strong>UPDATE</strong>, <strong>ALTER</strong>, <strong>SUPER</strong> ,etc. Sometimes it makes sense for an account to have complete control over a domain. For example, the <strong>root</strong> account is typically assigned with all privileges. Or, some user may require all possible privileges on a certain schema.</p>
<p>Instead of listing the entire set of privileges, the <strong>ALL PRIVILEGES</strong> meta-privilege can be used. There is a fine issue to notice here; typically this is not a problem, but I see it as a flaw. Assume the following account:</p>
<blockquote>
<pre>root@mysql-5.1.51&gt; GRANT <strong>ALL PRIVILEGES</strong> ON world.* TO 'world_user'@'localhost';

root@mysql-5.1.51&gt; SHOW GRANTS FOR 'world_user'@'localhost';
+---------------------------------------------------------------+
| Grants for world_user@localhost                               |
+---------------------------------------------------------------+
| GRANT USAGE ON *.* TO 'world_user'@'localhost'                |
| GRANT <strong>ALL PRIVILEGES</strong> ON `world`.* TO 'world_user'@'localhost' |
+---------------------------------------------------------------</pre>
</blockquote>
<p>This makes sense. We granted <strong>ALL PRIVILEGES</strong> and we see that the account is granted with <strong>ALL PRIVILEGES</strong>.</p>
<p>Now notice the following:<span id="more-3667"></span></p>
<blockquote>
<pre>root@mysql-5.1.51&gt; GRANT ALTER, ALTER ROUTINE, CREATE, CREATE ROUTINE, CREATE TEMPORARY TABLES, CREATE VIEW, DELETE, DROP, EVENT, EXECUTE, INDEX, INSERT, LOCK TABLES, REFERENCES, SELECT, SHOW VIEW, TRIGGER, UPDATE ON `world`.* TO 'other_user'@'localhost';

root@mysql-5.1.51&gt; SHOW GRANTS FOR 'other_user'@'localhost';
+---------------------------------------------------------------+
| Grants for other_user@localhost                               |
+---------------------------------------------------------------+
| GRANT USAGE ON *.* TO 'other_user'@'localhost'                |
| GRANT <strong>ALL PRIVILEGES</strong> ON `world`.* TO 'other_user'@'localhost' |
+---------------------------------------------------------------+</pre>
</blockquote>
<p>I didn't <em>ask</em> for <strong>ALL PRIVILEGES</strong>. I explicitly listed what I thought should be an account's privileges. It just so happens that these make for the entire set of privileges available on the schema domain.</p>
<p>You might think this is a nice feature, an ease out MySQL provides with. I do not see it this way.</p>
<p>My preferred way of upgrading MySQL version involves exporting and importing of the GRANTs. That is, I do not dump and load the <strong>mysql</strong> system tables, but rather export all the <strong>SHOW GRANTS FOR ...</strong> (e.g. with mk-show-grants), then execute these on the new version. This process was extremely useful on upgrades from <strong>5.0</strong> to <strong>5.1</strong>, where some <strong>mysql</strong> system tables were modified.</p>
<p>Now, consider the case where some new MySQL version introduced a new set of privileges. My <strong>'other_user'@'localhost'</strong> was not created with that set of privileges, nor did I intend it to have them. However, when exporting with <strong>SHOW GRANTS</strong>, the account is said to have <strong>ALL PRIVILEGES</strong>. When executed on the new version, the account will have privileges which I <em>never assigned it</em>.</p>
<p>Typically, this is not an issue. I mean, how many times do I assign an account with the entire set of privileges, yet do not intend it to have all privileges? Nevertheless, this makes for an inconsistency. It is unclear, by way of definition, which privileges are assigned to a user, without knowing the context of the version and the set of privileges per version. It makes for an inconsistency when moving between versions. And right now I'm working on some code which doesn't like these inconsistencies.</p>
<h4>The WITH GRANT OPTION inconsistency</h4>
<p>An account can be granted with the <strong>WITH GRANT OPTION</strong> privilege, which means the account's user can assign her privileges to other accounts. The inconsistency I found is that the <strong>GRANT</strong> mechanism is fuzzy with regard to <strong>GRANT OPTION</strong>, and falsely presents us with the wrong impression.</p>
<p>Let's begin with the bottom line: the <strong>WITH GRANT OPTION</strong> can only be set globally for an account-domain combination. Consider:</p>
<blockquote>
<pre>root@mysql-5.1.51&gt; GRANT INSERT, DELETE, UPDATE ON world.City TO 'gromit'@'localhost';
Query OK, 0 rows affected (0.00 sec)

root@mysql-5.1.51&gt; GRANT SELECT ON world.City TO 'gromit'@'localhost' WITH GRANT OPTION;
Query OK, 0 rows affected (0.00 sec)

root@mysql-5.1.51&gt; SHOW GRANTS FOR 'gromit'@'localhost';
+--------------------------------------------------------------------------------------------------+
| Grants for gromit@localhost                                                                      |
+--------------------------------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO 'gromit'@'localhost'                                                       |
| GRANT <strong>SELECT, INSERT, UPDATE, DELETE</strong> ON `world`.`City` TO 'gromit'@'localhost' <strong>WITH GRANT OPTION</strong> |
+--------------------------------------------------------------------------------------------------+
</pre>
</blockquote>
<p>The syntax of first two queries leads us to believe that we're only providing the <strong>WITH GRANT OPTION</strong> for the <strong>SELECT</strong> privilege. But that is not so: the <strong>WITH GRANT OPTION</strong> is assigned for all privileges on <strong>world.City</strong> to <strong>'gromit'@'localhost'</strong>.</p>
<p>The syntax would be more correct if we were to write something like:</p>
<blockquote>
<pre>GRANT <strong>GRANT_OPTION</strong> ON world.* TO 'gromit'@'localhost';</pre>
</blockquote>
<p>That would make it clear that this privilege does not depend on other privileges set on the specified domain.</p>
<h4>The USAGE inconsistency</h4>
<p>You can <strong>GRANT</strong> the <strong>USAGE</strong> privilege, but you may never <strong>REVOKE</strong> it. To revoke <strong>USAGE</strong> means to <strong>DROP USER</strong>.</p>
<h4>The missing ROUTINES_PRIVILEGES inconsistency</h4>
<p><strong>INFORMATION_SCHEMA</strong> provides with four privileges tables: <strong>USER_PRIVILEGES</strong>, <strong>SCHEMA_PRIVILEGES</strong>, <strong>TABLE_PRIVILEGES</strong>, <strong>COLUMN_PRIVILEGES</strong>, which map well to <strong>mysql</strong>'s <strong>user</strong>, <strong>db</strong>, <strong>tables_priv</strong> and <strong>columns_priv</strong> tables, respectively.</p>
<p>Ahem, which <strong>INFORMATION_SCHEMA</strong> table maps to <strong>mysql.procs_priv</strong>?</p>
]]></content:encoded>
			<wfw:commentRss>http://code.openark.org/blog/mysql/mysql-security-inconsistencies/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Pop quiz: what is the most basic privilege an account can be assigned with?</title>
		<link>http://code.openark.org/blog/mysql/pop-quiz-what-is-the-most-basic-privilege-an-account-can-be-assigned-with</link>
		<comments>http://code.openark.org/blog/mysql/pop-quiz-what-is-the-most-basic-privilege-an-account-can-be-assigned-with#comments</comments>
		<pubDate>Tue, 14 Jun 2011 07:30:45 +0000</pubDate>
		<dc:creator>shlomi</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[Security]]></category>

		<guid isPermaLink="false">http://code.openark.org/blog/?p=3677</guid>
		<description><![CDATA[I asked this during my presentation on the MySQL Conference this year. And I got a unanimous answer from the crowd. Take a moment to think this over, before coming up with the answer. Meanwhile, an intermezzo. Tam dam dam... Pom pom Pom pom Pom pom... If your answer is that the most basic privilege [...]]]></description>
			<content:encoded><![CDATA[<p>I asked this during my presentation on the MySQL Conference this year. And I got a unanimous answer from the crowd. Take a moment to think this over, before coming up with the answer. Meanwhile, an intermezzo.</p>
<p><em><strong>Tam dam dam...</strong></em></p>
<p><em><strong>Pom pom Pom pom Pom pom...</strong></em></p>
<p>If your answer is that the most basic privilege an account can be assigned with is the USAGE privilege, you are right!</p>
<p>And then again, you're also <em>wrong</em>.</p>
<p><span id="more-3677"></span>Technically, <strong>USAGE</strong> is the right answer. Conceptually, there's something far more fundamental than <strong>USAGE</strong>. An account with only <strong>USAGE</strong> privilege cannot do much, right? Well, I argue on that as well, but bear with me. How did that user get to login in the first place?</p>
<p>That's right, the <strong>USAGE</strong> privilege first and foremost allows one to <em>login</em>. I <a href="http://code.openark.org/blog/mysql/blocking-user-accounts">wrote on this</a> before, and I cannot stress this enough: there should be a <strong>LOGIN</strong> privilege for MySQL, one which can be turned off with <strong>REVOKE</strong>.</p>
<p>Can you <strong>REVOKE</strong> the <strong>USAGE</strong> privilege? You cannot. Once an account exists, it is allowed to login and do stuff. Actually:</p>
<blockquote>
<pre>USAGE = LOGIN + USE + ABUSE YOUR SYSTEM
</pre>
</blockquote>
<p>Which is why <strong>ABUSAGE</strong> is a more fitting name for this privilege. There is no justification to the many things a user can do with <strong>USAGE</strong>.</p>
<p>Am I exaggerating? What does <strong>USAGE</strong> allow one to do? Let's look at some unconventional usage:</p>
<blockquote>
<pre>SELECT benchmark(10000000000000000, sin(sqrt(rand())));
+-------------------------------------------------+
| benchmark(10000000000000000, sin(sqrt(rand()))) |
+-------------------------------------------------+
|                                               0 |
+-------------------------------------------------+
1 row in set (<strong>gazillion seconds to complete, one core down</strong>)
</pre>
</blockquote>
<blockquote>
<pre>SELECT
 DISTINCT 0 * COUNT(*) AS result
FROM
 INFORMATION_SCHEMA.COLLATIONS c0,
 INFORMATION_SCHEMA.COLLATIONS c1,
 INFORMATION_SCHEMA.COLLATIONS c2,
 INFORMATION_SCHEMA.COLLATIONS c3,
 INFORMATION_SCHEMA.COLLATIONS c4,
 INFORMATION_SCHEMA.COLLATIONS c5,
 INFORMATION_SCHEMA.COLLATIONS c6,
 INFORMATION_SCHEMA.COLLATIONS c7,
 INFORMATION_SCHEMA.COLLATIONS c8,
 INFORMATION_SCHEMA.COLLATIONS c9
GROUP BY
 c1.COLLATION_NAME, c7.SORTLEN
;
+--------+
| result |
+--------+
|      0 |
+--------+
1 row in set (<strong>yet again gazillion seconds to complete, with huge disk temporary table</strong>)
</pre>
</blockquote>
<blockquote>
<pre>SELECT COUNT(DISTINCT SLEEP(1000)) FROM INFORMATION_SCHEMA.TABLES;
+-----------------------------+
| COUNT(DISTINCT SLEEP(1000)) |
+-----------------------------+
|                           1 |
+-----------------------------+
1 row in set (<strong>want to gamble how much time your DB will spend in complete lockdown?</strong>)
</pre>
</blockquote>
<p>And I should also mention open many concurrent connections (thankfully there is syntax to limit this!).</p>
]]></content:encoded>
			<wfw:commentRss>http://code.openark.org/blog/mysql/pop-quiz-what-is-the-most-basic-privilege-an-account-can-be-assigned-with/feed</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<item>
		<title>Recovering a MySQL `root` password: the fourth solution</title>
		<link>http://code.openark.org/blog/mysql/recovering-a-mysql-root-password-the-fourth-solution</link>
		<comments>http://code.openark.org/blog/mysql/recovering-a-mysql-root-password-the-fourth-solution#comments</comments>
		<pubDate>Tue, 22 Mar 2011 07:47:46 +0000</pubDate>
		<dc:creator>shlomi</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[Configuration]]></category>
		<category><![CDATA[Replication]]></category>
		<category><![CDATA[Security]]></category>

		<guid isPermaLink="false">http://code.openark.org/blog/?p=3412</guid>
		<description><![CDATA[Have just read Darren Cassar's Recovering a MySQL `root` password – Three solutions. There's a fourth solution: using an init-file, which leads to just one restart of the database instead of two. It also avoids the security issue involved with using skip-grant-tables. I've written all about it before on Dangers of skip-grant-tables. Darren's 1st advice [...]]]></description>
			<content:encoded><![CDATA[<p>Have just read Darren Cassar's <a title="Permanent Link to Recovering a MySQL `root` password – Three solutions" rel="bookmark" href="http://mysqlpreacher.com/wordpress/2011/03/recovering-a-mysql-root-password-three-solutions/">Recovering a MySQL `root` password – Three solutions</a>. There's a fourth solution: using an <strong>init-file</strong>, which leads to just one restart of the database instead of two. It also avoids the security issue involved with using <strong>skip-grant-tables</strong>.</p>
<p>I've written all about it before on <a title="Permanent Link to Dangers of skip-grant-tables" rel="bookmark" href="http://code.openark.org/blog/mysql/dangers-of-skip-grant-tables">Dangers of skip-grant-tables</a>.</p>
<p>Darren's 1st advice (look for password ini files, scripts, etc.) is a very good one. One password that can always be looked up in files is the replication's password.</p>
<p>Replication's password is easily forgotten: you only set it once and never use it again; never script it nor manually login with. When setting up new slaves, though, you suddenly need it.</p>
<p>Apparently not many realize that the replication password is written in plaintext in the <strong>master.info</strong> file. This file tells the slave all about it's master connection: host, port, user &amp; password are all there for you to read.</p>
]]></content:encoded>
			<wfw:commentRss>http://code.openark.org/blog/mysql/recovering-a-mysql-root-password-the-fourth-solution/feed</wfw:commentRss>
		<slash:comments>11</slash:comments>
		</item>
		<item>
		<title>Limiting table disk quota in MySQL</title>
		<link>http://code.openark.org/blog/mysql/limiting-table-disk-quota-in-mysql</link>
		<comments>http://code.openark.org/blog/mysql/limiting-table-disk-quota-in-mysql#comments</comments>
		<pubDate>Mon, 07 Mar 2011 07:08:21 +0000</pubDate>
		<dc:creator>shlomi</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[File System]]></category>
		<category><![CDATA[InnoDB]]></category>
		<category><![CDATA[MyISAM]]></category>
		<category><![CDATA[Security]]></category>
		<category><![CDATA[Triggers]]></category>

		<guid isPermaLink="false">http://code.openark.org/blog/?p=3359</guid>
		<description><![CDATA[Question asked by a student: is there a way to limit a table's quote on disk? Say, limit a table to 2GB, after which it will refuse to grow? Note that the requirement is that rows are never DELETEd. The table must simply refuse to be updated once it reaches a certain size. There is [...]]]></description>
			<content:encoded><![CDATA[<p>Question asked by a student: is there a way to limit a table's quote on disk? Say, limit a table to 2GB, after which it will refuse to grow? Note that the requirement is that rows are never DELETEd. The table must simply refuse to be updated once it reaches a certain size.</p>
<p>There is no built-in way to limit a table's quota on disk. First thing to observe is that MySQL has nothing to do with this. It is entirely up to the storage engine to provide with such functionality. The storage engine is the one to handle data storage: how table and keys are stored on disk. Just consider the difference between MyISAM's <strong>.MYD</strong> &amp; <strong>.MYI</strong> to InnoDB's shared tablespace <strong>ibdata1</strong> to InnoDB's file-per table <strong>.ibd</strong> files.</p>
<p>The only engine I know of that has a quota is the MEMORY engine: it accepts the <strong>max_heap_table_size</strong>, which limits the size of a single table in memory. Hrmmm... In memory...</p>
<h4>Why limit?</h4>
<p>I'm not as yet aware of the specific requirements of said company, but this is not the first time I heard this question.</p>
<p>The fact is: when MySQL runs out of disk space, it goes with a BOOM. It crashed ungracefully, with binary logs being out of sync, replication being out of sync. To date, and I've seen some cases, InnoDB merely crashes and manages to recover once disk space is salvaged, but I am not certain this is guaranteed to be the case. Anyone?</p>
<p>And, with MyISAM..., who knows?</p>
<p>Rule #1 of MySQL disk usage: <em>don't run out of disk space.</em></p>
<h4>Workarounds</h4>
<p>I can think of two workarounds, none of which is pretty. The first involves triggers (actually, a few variations for this one), the second involves privileges.<span id="more-3359"></span></p>
<h4>Triggers</h4>
<p>The following code (first presented in <a title="Permanent Link to Triggers Use Case Compilation, Part II" rel="bookmark" href="http://code.openark.org/blog/mysql/triggers-use-case-compilation-part-ii">Triggers Use Case Compilation, Part II</a>) assumed the DATA_LENGTH and INDEX_LENGTH values in INFORMATION_SCHEMA to be good indicators:</p>
<blockquote>
<pre>DROP TABLE IF EXISTS `world`.`logs`;
CREATE TABLE  `world`.`logs` (
  `logs_id` int(11) NOT NULL auto_increment,
  `ts` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
  `message` varchar(255) character set utf8 NOT NULL,
  PRIMARY KEY  (`logs_id`)
) ENGINE=MyISAM;

DELIMITER $$

DROP TRIGGER IF EXISTS logs_bi $$
CREATE TRIGGER logs_bi BEFORE INSERT ON logs
FOR EACH ROW
BEGIN
  SELECT DATA_LENGTH+INDEX_LENGTH FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='world' AND TABLE_NAME='LOGS' INTO @estimated_table_size;
  IF (@estimated_table_size &gt; 25*1024) THEN
    SELECT 0 FROM `logs table is full` INTO @error;
  END IF;
END $$

DELIMITER ;
</pre>
</blockquote>
<p>Or, you could write your own UDF, e.g. <strong>get_table_file_size(fully_qualified_table_name)</strong> and be more accurate:</p>
<blockquote>
<pre>DELIMITER $$

DROP TRIGGER IF EXISTS logs_bi $$
CREATE TRIGGER logs_bi BEFORE INSERT ON logs
FOR EACH ROW
BEGIN
  SELECT get_table_file_size('world.logs') INTO @table_size;
  IF (@table_size &gt; 25*1024) THEN
    SELECT 0 FROM `logs table is full` INTO @error;
  END IF;
END $$

DELIMITER ;
</pre>
</blockquote>
<p>(Same should be done for <strong>UPDATE</strong> operations)</p>
<p>In both workarounds above, triggers are pre-defined. But triggers are performance-killers.</p>
<p>How about preventing writing to the table only when it's truly on the edge? A simple shell script, spawned by a cronjob, could do this well: get the file size of a specific table, and test if it's larger than <em>n</em> bytes. If not, the script exits. If the file is indeed too large, the scripts invokes the following on <em>mysql</em>:</p>
<blockquote>
<pre>DELIMITER $$

DROP TRIGGER IF EXISTS logs_bi $$
CREATE TRIGGER logs_bi BEFORE INSERT ON logs
FOR EACH ROW
BEGIN
  SELECT 0 FROM `logs table is full` INTO @error;
END $$

DELIMITER ;
</pre>
</blockquote>
<p>So, during most of the time, there is no trigger. Only when the external script detects that table is too large, does it create a trigger. The trigger has no logic: it simply raises an error (PS, use <strong>raise</strong> in MySQL <strong>5.5</strong>).</p>
<h4>Privileges</h4>
<p>Another way to work around the problem is to use security features. Instead of creating a trigger on the table, <strong>REVOKE</strong> the <strong>INSERT</strong> &amp; <strong>UPDATE</strong> privileges from the appropriate user on that table.</p>
<p>This may turn out to be a difficult task, since MySQL has no notion of <em>fine grain changes</em>. That is, suppose we have:</p>
<blockquote>
<pre>GRANT INSERT, UPDATE, DELETE, SELECT ON mydb.* TO 'webuser'@'%.webdomain'</pre>
</blockquote>
<p>If we just do:</p>
<blockquote>
<pre>REVOKE SELECT ON mydb.logs FROM 'webuser'@'%.webdomain'</pre>
</blockquote>
<p>We get:</p>
<blockquote>
<pre>There is no such grant defined for user 'webuser' on host '%.webdomain' on table 'logs'.</pre>
</blockquote>
<p>So this requires setting up privileges on the table level in the first place. Plus note that as long as the grants on the database level do allow for INSERTs, you cannot override it on the table level.</p>
<h4>Other ideas?</h4>
<p>I never actually implemented table disk quota. I'm not sure this is a viable solution; but I haven't heard all the arguments in favor as yet, so I don't want to rule this out.</p>
<p>Please share below if you are using other means of table size control, other than the trivial cleanup of old records.</p>
]]></content:encoded>
			<wfw:commentRss>http://code.openark.org/blog/mysql/limiting-table-disk-quota-in-mysql/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Upgrading passwords from old_passwords to &quot;new passwords&quot;</title>
		<link>http://code.openark.org/blog/mysql/upgrading-passwords-from-old_passwords-to-new-passwords</link>
		<comments>http://code.openark.org/blog/mysql/upgrading-passwords-from-old_passwords-to-new-passwords#comments</comments>
		<pubDate>Mon, 28 Feb 2011 13:50:52 +0000</pubDate>
		<dc:creator>shlomi</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[Configuration]]></category>
		<category><![CDATA[Security]]></category>

		<guid isPermaLink="false">http://code.openark.org/blog/?p=3297</guid>
		<description><![CDATA[You have old_passwords=1 in your my.cnf. I'm guessing this is because you used one of the my-small.cnf, my-large.cnf etc. templates provided with your MySQL distribution. These files can easily win the "most outdated sample configuration file contest". Usually it's no big deal: if some parameter isn't right, you just go and change it. Some variables, [...]]]></description>
			<content:encoded><![CDATA[<p>You have <strong>old_passwords=1</strong> in your <strong>my.cnf</strong>. I'm guessing this is because you used one of the <strong>my-small.cnf</strong>, <strong>my-large.cnf</strong> etc. templates provided with your MySQL distribution.</p>
<p>These files can easily win the "most outdated sample configuration file contest".</p>
<p>Usually it's no big deal: if some parameter isn't right, you just go and change it. Some variables, though, have a long-lasting effect, and are not easily reversed.</p>
<h4>What's the deal with old_passwords?</h4>
<p>No one should be using these anymore. This variable makes the password hashing algorithm compatible with that of MySQL <strong>4.0</strong>. I'm pretty sure <strong>4.0</strong> was released <strong>9</strong> years ago. I don't know of anyone still using it (or <strong>4.0</strong> client libraries).</p>
<p>The deal is this: with old_passwords you get a <strong>16</strong> hexadecimal digits (<strong>64</strong> bit) hashing of your passwords. With so called <em>"new passwords"</em> you get <strong>40</strong> hexadecimal digits (plus extra "<strong>*</strong>"). So this is about better encryption of your password. Read more on the <a href="http://dev.mysql.com/doc/refman/5.1/en/password-hashing.html">manual</a>.</p>
<h4>How do I upgrade to new password format?</h4>
<p>You can't just put a comment on the "<strong>old_passwords=1</strong>" entry in the configuration file. If you do so, the next client to connect will attempt to match a <strong>41</strong> characters hashed password to your existing <strong>16</strong> characters entry in the <strong>mysql.users</strong> table. So you need to make a simultaneous change: both remove the <strong>old_passwords</strong> entry and set a new password. You must know all accounts' passwords before you begin.</p>
<p><span id="more-3297"></span>Interestingly, <strong>old_passwords</strong> is both a global and a session variable. To work out an example, let's assume the account <strong>'webuser'@'localhost'</strong> enters with '123456'. Take a look at the following:</p>
<blockquote>
<pre>root@mysql-5.1.51&gt; SET SESSION old_passwords=0;
Query OK, 0 rows affected (0.00 sec)

root@mysql-5.1.51&gt; SELECT PASSWORD('123456');
+-------------------------------------------+
| PASSWORD('123456')                        |
+-------------------------------------------+
| *6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9 |
+-------------------------------------------+
1 row in set (0.00 sec)

root@mysql-5.1.51&gt; SET SESSION old_passwords=1;
Query OK, 0 rows affected (0.00 sec)

root@mysql-5.1.51&gt; SELECT PASSWORD('123456');
+--------------------+
| PASSWORD('123456') |
+--------------------+
| 565491d704013245   |
+--------------------+
1 row in set (0.00 sec</pre>
</blockquote>
<p>So, the <strong>PASSWORD()</strong> function consults the <strong>old_passwords</strong> session variable.</p>
<p>To upgrade <strong>'webuser'@'localhost'</strong>'s password we do:</p>
<blockquote>
<pre>root@mysql-5.1.51&gt; SET SESSION old_passwords=0;
Query OK, 0 rows affected (0.00 sec)

root@mysql-5.1.51&gt; SET PASSWORD FOR 'webuser'@'localhost' = PASSWORD('123456')</pre>
</blockquote>
<p>Go ahead and see the <strong>password</strong> entry on the <strong>mysql.users</strong> table.</p>
<p>What we've just done is to set a <strong>41</strong> characters password hash for that account. Now, the next time the client wishes to connect, it must know in advance it is to expect a new password, otherwise it will encode a <strong>16</strong> characters hash, and try to match it with our new <strong>41</strong> characters hash. It is now time to perform:</p>
<blockquote>
<pre>root@mysql-5.1.51&gt; SET GLOBAL old_passwords=0;
Query OK, 0 rows affected (0.00 sec</pre>
</blockquote>
<p>This will apply to all new connections made from that moment on (not affecting any existing connections). So, make sure you have updated passwords for all accounts.</p>
<p>To wrap it up, don't forget to set <strong>old_passwords=0</strong> in the <strong>my.cnf</strong> file, or, better yet, completely remove the entry.</p>
]]></content:encoded>
			<wfw:commentRss>http://code.openark.org/blog/mysql/upgrading-passwords-from-old_passwords-to-new-passwords/feed</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>Tool of the day: autossh</title>
		<link>http://code.openark.org/blog/mysql/tool-of-the-day-autossh</link>
		<comments>http://code.openark.org/blog/mysql/tool-of-the-day-autossh#comments</comments>
		<pubDate>Mon, 13 Sep 2010 07:42:25 +0000</pubDate>
		<dc:creator>shlomi</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[Security]]></category>

		<guid isPermaLink="false">http://code.openark.org/blog/?p=2943</guid>
		<description><![CDATA[Maybe I'm like an old replication server, lagging way behind, but a couple of weeks ago I found autossh, which is a wrapper around ssh, that keeps reconnecting the session if it breaks. With public key encryption, I am now able to work out pretty reliable SSH tunneling among servers, which doesn't break. It seems [...]]]></description>
			<content:encoded><![CDATA[<p>Maybe I'm like an old replication server, lagging way behind, but a couple of weeks ago I found <em><a href="http://www.harding.motd.ca/autossh/">autossh</a></em>, which is a wrapper around <em>ssh</em>, that keeps reconnecting the session if it breaks.</p>
<p>With public key encryption, I am now able to work out pretty reliable SSH tunneling among servers, which doesn't break. It seems to be working well during these couple of weeks. And it's in my favorite distro's repository <img src='http://code.openark.org/blog/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p>I suppose use cases are as many as those for SSH or SSH tunneling, and I'm putting it to an interesting use. But I suppose the most obvious use in the MySQL world would be to encrypt client connections over unsafe network, or make the network more reliable, for that matter. Yes, there's SSL connections, but opening your <strong>3306</strong> port on your firewall? Too risky for my taste.</p>
]]></content:encoded>
			<wfw:commentRss>http://code.openark.org/blog/mysql/tool-of-the-day-autossh/feed</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
	</channel>
</rss>

