db-derby-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Satheesh Bandaram <sathe...@Sourcery.Org>
Subject Re: Grant and Revoke, Part I ... DERBY-464...
Date Fri, 23 Dec 2005 00:30:04 GMT
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
  <meta content="text/html;charset=ISO-8859-1" http-equiv="Content-Type">
  <title></title>
</head>
<body bgcolor="#ffffff" text="#000000">
Hi Francois,<br>
<br>
Francois Orsini wrote:<br>
<blockquote
 cite="mid7921d3e40512220208u44bb2a54se440d79de684be1@mail.gmail.com"
 type="cite">Hi Satheesh,<br>
  <br>
Regarding the issues you mentioned:<br>
  <br>
&nbsp; &gt; 1) Should Derby support upgrading a 10.1 database to 10.2
directly into 'sqlStandard' mode (optionally) or not?<br>
  <br>
I would say no - as this is a new feature of 10.2, we should let the
user configure it on its own after upgrade. Also what does it mean and
incur to upgrade from legacy to 'sqlStandard' mode automatically (say
the option has been selected by user)...<br>
</blockquote>
I agree.. I am definitely leaning that way.<br>
<blockquote
 cite="mid7921d3e40512220208u44bb2a54se440d79de684be1@mail.gmail.com"
 type="cite"><br>
This would mean adding entries to
the new system catalogs to match privilege settings defined in the
legacy metadata...In Derby 10.1, a user can select data from a table
that is in a different schema; with traditional grant/revoke
(sqlStandard) I don't think a user can select a table in a different
schema if he is not granted access first and he is not the owner -
hence when sqlStandard is turned on, users would only have access to
their own objects (i.e. tables) in their schema unless system catalogs
have been added
entries corresponding to granted privileges coming out of 'legacy'
mode (default is 'fullAccess') - say during upgrade, there are
permissions defined such as
'derby.fullAccessUsers=sa,FRED,mary', will this get converted into
respective sqlStandard mode metadata - could get interesting?<br>
  <br>
&nbsp; &gt; Would it be sufficient if 10.2 upgrade mechanism moves a
legacy database into 10.2 keeping the current authorization model and
if users need to use 'sqlStandard' mode, they have to set it after
upgrade?<br>
  <br>
I would vote +1 for that ;-) (let's keep it simple)<br>
  <br>
&nbsp; &gt; 2) Current proposal doesn't handle Synonyms. I am wondering
if Synonyms should follow authorization model like views... <br>
&nbsp; &gt; Any opinions?<br>
  <br>
Sounds like it - views can have synonyms as well.<br>
  <br>
</blockquote>
I will try to address synonyms after I am done implementing current
proposal as much as possible.<br>
<blockquote
 cite="mid7921d3e40512220208u44bb2a54se440d79de684be1@mail.gmail.com"
 type="cite">&nbsp;&nbsp; &gt;3) Dan raised an important question about whether
defaultConnectionMode should be made a database-only property<br>
&nbsp; &gt; and without affecting existing applications. I am currently
thinking if defaultConnectionMode is set to sqlStandard \<br>
&nbsp; &gt; as a database property, it would override system-wide property
for that database only. Once defaultConnectionMode is<br>
&nbsp; &gt; set to sqlStandard as a database property, it cann't be changed
to legacy modes or removed.<br>
  <br>
What is the rationale for making defaultConnectionMode property a
database-only one? Why not leaving both ways (system &amp; database)?<br>
  <br>
</blockquote>
<blockquote
 cite="mid7921d3e40512220208u44bb2a54se440d79de684be1@mail.gmail.com"
 type="cite">&nbsp; &gt; Once defaultConnectionMode is set to sqlStandard as
a
database property, it can't be changed to legacy modes or removed.<br>
  <br>
Why? is this technical or pure deliberate choice? (not saying it's
wrong, just curious)<br>
  <br>
</blockquote>
Deliberate choice... I think sqlStandard mode should be the future
preferred choice and don't want to provide for switching between
standard mode and legacy modes. Applications are likely to see some
differences as they switch (including external security behavior) and
not sure why anyone would want to keep switching. <br>
<br>
Satheesh<br>
<blockquote
 cite="mid7921d3e40512220208u44bb2a54se440d79de684be1@mail.gmail.com"
 type="cite">Thanks,<br>
  <br>
--francois<br>
  <br>
  <div><span class="gmail_quote">On 12/2/05, <b
 class="gmail_sendername">Satheesh Bandaram</b> &lt;<a
 href="mailto:satheesh@sourcery.org" target="_blank"
 onclick="return top.js.OpenExtLink(window,event,this)">
satheesh@sourcery.org</a>&gt; wrote:</span>
  <blockquote class="gmail_quote"
 style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left:
1ex;">
I would like to start contributing code that implements Grant/Revoke in
batches. My first patch would include DDL support for both
Grant/Revoke. This patch has passed 'derbyALL' suite and contains
implementation for the following. Let me know if anyone would like to
join completing the rest. Lot more needs to be done.<span><span> :-( </span></span><br>
    <ol>
      <li>Grant/Revoke DDL parsing and execution</li>
      <li>Addition of several new system tables to hold the system
metadata. I will update my spec to include detailed schema for new
system tables, so that they can be included in 10.2 documentation.<br>
      </li>
      <li>Enhancing the syntax for routine creation to include
external-security clause</li>
      <li>Very simple tests to cover only the DDL. I would be expanding
on
the testing in the later submissions, including a JUnit test suite.</li>
      <li>Grant/Revoke DDL is only supported if
derby.database.defaultConnectionMode property is set to 'sqlStandard'.</li>
      <li>I also have some implementation for permission checking for
DMLs
to test what has been implemented so far. But I will submit that in
follow up patches.</li>
    </ol>
Most of the code will not be visible to users who don't set
'sqlStandard' property and hence shouldn't cause any issues.<br>
    <br>
I am also debating following issues:<br>
    <ol>
      <li>Should Derby support upgrading a 10.1 database to 10.2
directly
into 'sqlStandard' mode (optionally) or not? Would it be sufficient if
10.2 upgrade mechanism moves a legacy database into 10.2 keeping the
current authorization model and if users need to use 'sqlStandard'
mode, they have to set it after upgrade?</li>
      <li>Current proposal doesn't handle Synonyms. I am wondering if
Synonyms should follow authorization model like views... Any opinions?</li>
      <li>Dan raised an important question about whether
defaultConnectionMode should be made a database-only property and
without affecting existing applications. I am currently thinking if
defaultConnectionMode is set to sqlStandard as a database property, it
would override system-wide property for that database only. Once
defaultConnectionMode is set to sqlStandard as a database property, it
cann't be changed to legacy modes or removed.</li>
    </ol>
Satheesh<br>
    <br>
Satheesh Bandaram wrote:<br>
    <blockquote cite="http://mid435FCD22.3050607@Sourcery.Org"
 type="cite"> Hi<br>
      <br>
I just attached my proposal to enhance Derby by adding Grant and Revoke
capability to <a href="http://issues.apache.org/jira/browse/DERBY-464"
 target="_blank" onclick="return top.js.OpenExtLink(window,event,this)">DERBY-464</a>.
Hope this leads to many other enhancements to Derby in the
access-control and security areas to make Derby much more capable in
client-server configurations.<br>
      <br>
I am also attaching the spec here and invite anyone interested in
joining the development. Francois had expressed interest sometime ago. <br>
      <br>
Satheesh<br>
      <br>
      <br>
      <hr size="4" width="90%"><br>
      <center>
      <h1>Grant and Revoke in Derby</h1>
      </center>
      <center>Satheesh Bandaram <br>
24 Oct 2005</center>
      <h2>Introduction</h2>
Originally Cloudscape/Derby used a very simple permissions scheme,
which
is quite suitable for an embedded database system. Embedded database
users
typically don't see Derby database directly and instead talk to
applications
that embeds Derby. So Derby left most of the access control work to
applications.
Under this scheme, Derby limits database access on database or system
basis.
A user can be granted <i>full</i>, <i>read-only</i>, or <i>no
access</i>.
This is less suitable for general purpose client-server database
configurations.
When end users or diverse applications can issue SQL commands directly
against the database, Derby must provide more precise mechanisms to
limit
who can do what with the database.
      <p>I propose to implement a subset of SQL2003 access control that
deals
with tables, views, procedures, and functions to start with. Each
database
will have the option of operating using the legacy Derby access control
system or a SQL2003 compatible access control system. This paper
describes
a proposal to introduce Derby's SQL2003 compatible access control
system.
There are many further enhancements possible in access control and
security
areas. My current itch is to limit the scope to what is proposed here. </p>
      <p>I wish to propose a staged development plan with following
tasks
as
mentioned in <a href="http://issues.apache.org/jira/browse/DERBY-464"
 target="_blank" onclick="return top.js.OpenExtLink(window,event,this)">DERBY-464</a>:
(<a href="http://issues.apache.org/jira/browse/DERBY-464"
 target="_blank" onclick="return top.js.OpenExtLink(window,event,this)">http://issues.apache.org/jira/browse/DERBY-464</a>)
      </p>
      <ol>
        <li>Submit support for DDL. This would implement grant and
revoke
statements
and recording of permissions in system tables.</li>
        <li>Add run-time support to enforce permission checking.</li>
        <li>Address upgrade, migration and metadata changes.</li>
      </ol>
      <h2>Grant and Revoke Statements</h2>
The GRANT statement is used to grant permissions to users. The REVOKE
statement
is used to revoke permissions.
      <p><i>grant-statement ::= table-grant-statement</i> <br>
      <i>&nbsp; | routine-grant-statement</i> </p>
      <p><i>table-grant-statement ::=</i> <br>
      <i>&nbsp; </i>GRANT<i> table-privileges </i>TO<i>
grantees</i> </p>
      <p><i>routine-grant-statement ::=</i> <br>
      <i>&nbsp; </i>GRANT EXECUTE ON<i> routine </i>TO<i>
grantees</i> </p>
      <p><i>revoke-statement ::= table-revoke-statement</i> <br>
      <i>&nbsp; | routine-revoke-statement</i> </p>
      <p><i>table-revoke-statement ::=</i> <br>
      <i>&nbsp; </i>REVOKE<i> table-privileges </i>FROM<i>
grantee, ...</i>
      </p>
      <p><i>routine-revoke-statement ::=</i> <br>
      <i>&nbsp; </i>REVOKE<i> </i>EXECUTE ON<i> routine
</i>FROM<i>
grantee,
... </i>RESTRICT </p>
      <p><i>table-privileges ::= table-action</i> ON [TABLE] <i>table-or-view-name</i>
      </p>
      <p><i>grantees ::= { </i>PUBLIC<i> | user-identifier,... }</i>
</p>
      <p><i>table-action ::= </i>ALL PRIVILEGES <i>| action, ...</i>
</p>
      <p><i>action ::= </i>SELECT<i>&nbsp; [ ( privilege-column-list
) ]</i>
      <br>
      <i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp; </i>DELETE
<br>
      <i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp; </i>INSERT
<br>
      <i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp; </i>UPDATE<i>
[ ( privilege-column-list
) ]</i> <br>
      <i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp; </i>REFERENCES<i>
[
( privilege-column-list ) ]</i> <br>
      <i>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp; </i>TRIGGER
</p>
      <p><i>routine ::= { </i>FUNCTION<i> | </i>PROCEDURE<i>
}
routine-designator</i> </p>
      <p><i>routine-designator ::= qualified-name [ signature ]</i> </p>
      <p><i>signature ::= ( data-type, ... )</i> </p>
      <p>Some examples: </p>
      <p>&nbsp;GRANT SELECT, update(description) ON t TO maria,harry <br>
&nbsp;GRANT SELECT ON TABLE s.v to PUBLIC <br>
&nbsp;GRANT EXECUTE ON PROCEDURE p TO george <br>
&nbsp;REVOKE update(description) ON t FROM maria </p>
      <p>The table <b>select</b> privilege is permission to perform a
select
on the named table or view. If there is a column list then the
permission
is only on those columns. If there is no column list then the select
privilege
is on all columns in the table. </p>
      <p>The table <b>references</b> privilege is permission to create
a
foreign
key reference to the named table. If there is a column list then the
permission
is only on foreign key references to the named columns. </p>
      <p>The table <b>trigger</b> privilege is permission to create a
trigger
on the named table. </p>
      <p>When a table, view, function, or procedure is created its
owner
(creator)
has full privileges on it. No other user has any privileges on it until
the owner grants privileges. </p>
      <p>Privileges may be granted to specific users or to everyone:
all
current
and future users. The word "PUBLIC" denotes everyone. </p>
      <p>Privileges granted to PUBLIC and to individual users are
independent.
Suppose SELECT privilege on table t was granted to both PUBLIC and
harry.
If SELECT privilege is revoked from harry, harry will still be able to
access table t; he uses the PUBLIC privilege. </p>
      <p>The REVOKE statement revokes privileges. Revoking a privilege
without
specifying a column list revokes the privilege for all columns. </p>
      <p>RESTRICT is mandatory with routine revoke statements. That
means
that
execute permission on a function may not be revoked if that function is
used in a view, trigger, or constraint, and permission is being revoked
from the owner of the view, trigger, or constraint. </p>
      <p>Only the owner (creator) of an object can grant or revoke
privileges
on that object. </p>
      <h2>Permission Checking</h2>
Permissions are checked when a statement is executed, not when it is
prepared.
This allows statement cache to share statements among different users.
      <h2>Views, Triggers, and Constraints</h2>
Views, triggers, and constraints operate with the permissions of the
owner
of the view, trigger, or constraint.
      <p>For example, consider the following view definition created by
user
jane <br>
&nbsp; </p>
      <p>&nbsp; CREATE VIEW s.v(vc1,vc2,vc3) <br>
&nbsp;&nbsp;&nbsp; AS SELECT t1.c1,t1.c2,f(t1.c3) FROM t1 JOIN t2 ON
t1.c1 = t2.c1 WHERE t2.c2 = 5 </p>
      <p>Jane needs the following permissions in order to create the
view: </p>
      <ul>
        <li>ownership of schema s (so that she can create something in
it),</li>
        <li>ownership of table t1 (so that she can allow others to see
columns in
it),</li>
        <li>select permission on t2.c1 and t2.c1, and</li>
        <li>execute permission on f.</li>
      </ul>
When the view is created only <i>jane</i> has select permission on it.
Jane can grant select permission on any or all of the columns of s.v to
anyone, even to users who do not have select permission t1 or t2 or
execute
permission on f. Suppose <i>jane</i> grants select permission on s.v
to <i>harry</i>.
When Derby executes a select on s.v on behalf of <i>harry</i>, Derby
only
checks that <i>harry</i> has select permission on s.v; it does not
check
that <i>harry</i> has select permission on t1, or t2 or execute
permission
on f.
      <p>Similarly with triggers and constraints: a trigger or
constraint
may
operate on columns for which the current user does not have the
appropriate
permissions. It is only required that the owner of the trigger or
constraint
have the appropriate permissions. </p>
      <p>When a view, trigger, or constraint is created Derby checks
that
the
owner has the required permissions, throwing an SQLException if not. If
any of the required permissions are later revoked then the view,
trigger,
or constraint is dropped as part of the REVOKE statement. </p>
      <h2>Current User</h2>
The permission system relies on Derby authentication to establish the
identity
of the current user. Permission checking is of little value unless
Derby
authentication is turned on. By default, Derby's authentication is OFF
and can be turned ON by setting derby.connection.requireAuthentication
to TRUE.
      <p>When a routine (function or procedure) is created you can
specify
whether
the routine should execute with the permissions of the routine owner or
those of the invoker. This done in the external-security-clause of
function
and procedure element lists. The syntax of external-security-clause is:
      </p>
      <p><i>external-security-clause ::=</i> <br>
&nbsp; [ EXTERNAL SECURITY DEFINER | EXTERNAL SECURITY INVOKER ] </p>
      <p>EXTERNAL SECURITY DEFINER means that the owner of the routine
is
the
effective user as long as the routine executes. That is, the routine
executes
with the permissions of the owner (creator) of the routine and any
objects
created by the routine are owned by the owner of the routine. This is
the
default, as specified by SQL2003. EXTERNAL SECURITY INVOKER means that
the routine executes with the permissions of the invoker of the
routine. </p>
      <p>For example: </p>
      <p>CREATE PROCEDURE SALES.TOTAL_REVENUE(IN S_MONTH INTEGER, <br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
IN S_YEAR INTEGER, <br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
OUT TOTAL DECIMAL(10,2)) <br>
&nbsp; PARAMETER STYLE JAVA <br>
&nbsp; READS SQL DATA <br>
&nbsp; LANGUAGE JAVA <br>
&nbsp; EXTERNAL NAME 'com.acme.sales.calculateRevenueByMonth' <br>
&nbsp; EXTERNAL SECURITY INVOKER </p>
      <p>This specifies that procedure sales.total_revenue can only
read
columns
that the invoker can read directly. If instead the definition of
sales.total_revenue
used EXTERNAL SECURITY DEFINER, or it did not have an external security
clause, then the procedure can only read columns that the creator of
sales.total_revenue
is permitted to read. It then may be able to access data that the
invoker
of sales.total_revenue is not permitted to read directly. </p>
      <h2>Table, View, Trigger, and Schema Creation Permissions</h2>
A table may only be created or dropped by the owner of the table's
schema.
Table creation permission is not grantable. (This is the SQL2003 spec).
Only the owner of a table may create or drop indices or constraints on
the table. Views and triggers may only be created or dropped by the
owner
of the view or trigger's schema.
      <p>When a schema is created the schema owner is defined using the
authorization
clause of the CREATE SCHEMA statement: </p>
      <p><i>create-schema-statement ::=</i> <br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATE SCHEMA <i>schema-name</i>
<br>
&nbsp;&nbsp; |&nbsp;&nbsp; CREATE SCHEMA <i>schema-name</i> AUTHORIZATION
<i>user-name</i>
      <br>
&nbsp;&nbsp; |&nbsp;&nbsp; CREATE SCHEMA AUTHORIZATION <i>user-name</i>
</p>
      <p>The first form creates a new schema that is owned by the
current
effective
user. The second form creates a schema owned by the named user. The
final
form creates a new schema that is owned by the named user and that has
the same name as its owner. </p>
      <p>The owner of the database may create any schema with any owner
(authorization).
Other users may only create schemas whose name is the same as their
user
name and that are owned by the user. For example, if user angelica does
not own the database then she can only create a schema with name and
authorization
angelica. </p>
      <p>A schema may only be dropped by its owner or the owner of the
database. </p>
      <h2>System Schemata, Functions, Procedures</h2>
All of the built-in schemata (SYS, SYSCAT, APP, etc) are owned by the
owner
of the database. For databases created under the SQL standard security
model, the owner is the user who opened the connection used to create
the
database. For databases created under the old Derby security model and
switched to the standard model the owner is the owner of the connection
when the security model was changed to the standard model.
      <p>The standard builtin functions, ABS, CURRENT_USER, TRIM, etc
are
treated
differently than the builtin functions and procedures in the SYSCS_UTIL
schema. When a database is created all users have execute permission on
the standard builtin functions. This permission cannot be revoked.
However
when a database is created only the database owner has execute
permission
on the SYSCS_UTIL functions and procedures. So initially only the owner
of the database may execute Derby system functions and procedures such
as SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY and
SYSCS_UTIL.SYSCS_EXPORT_TABLE.
The owner of the database may grant execute permission on these
routines
to other users. All the built in functions and procedures have EXTERNAL
SECURITY INVOKER. So, for instance a user cannot call
SYSCS_EXPORT_TABLE
to see tables on which he has no SELECT permission. <a
 href="http://issues.apache.org/jira/browse/DERBY-475" target="_blank"
 onclick="return top.js.OpenExtLink(window,event,this)">DERBY-475</a>
has added a new table based mechanism to define functions in SYSFUN
schema.
These will also be treated as builtin functions for the permission
scheme. </p>
      <p>One must be particularly careful about granting execute
permission
on
SYSCS_BACKUP_DATABASE, SYSCS_EXPORT_TABLE, SYSCS_EXPORT_QUERY,
SYSCS_IMPORT_TABLE,
and SYSCS_IMPORT_DATA because can read or write files. The operating
system
will let them read or write any file accessible to the user who started
the JVM. The operating system does not know anything about Derby
connections
or who Derby thinks the current user is. </p>
      <p>One should also be careful about granting execute permission
on
SYSCS_COMPRESS_TABLE,
SYSCS_FREEZE_DATABASE, SYSCS_UNFREEZE_DATABASE, and
SYSCS_SET_DATABASE_PROPERTY.
SYSCS_COMPRESS_TABLE and SYSCS_FREEZE_DATABASE lock up the database or
part of it for some time. SYSCS_SET_DATABASE_PROPERTY affects the whole
database. </p>
      <h2>Derby upgrade and migration</h2>
Derby 10.1 authorization is on a database basis. A user can be granted
full (read/write), read-only, or no access to a database. This is done
through Derby properties. The derby.database.defaultConnectionMode
property
specifies the default access permission. Its value must be "noAccess",
"readOnlyAccess", or "fullAccess" (case insensitive). The
derby.database.fullAccessUsers
and derby.database.readOnlyAccessUsers properties specify lists of
users
with full or read-only access. The default for
derby.database.defaultConnectionMode
is "fullAccess", so if no authorization properties are specified then
all
users have full access. This is incompatible with SQL2003.
      <p>I propose to add a new derby.database.defaultConnectionMode
property
value to Derby, "sqlStandard". If the
derby.database.defaultConnectionMode
property value for a database is "sqlStandard" then authorizations for
the database will follow the SQL standard model described in this
document.
If the value is "noAccess", "readOnlyAccess", "fullAccess", or null
then
the Derby legacy authorization model will be used. GRANT and REVOKE
statements
will raise an error when run in a database using the legacy
authorization
model. </p>
      <p>An extant database may be switched from the legacy
authorization
model
to the SQL2003 standard model. This is done by <a
 href="http://mail-archives.apache.org/mod_mbox/db-derby-dev/200503.mbox/%3c423896FC.6070200@debrunners.com%3e"
 target="_blank" onclick="return top.js.OpenExtLink(window,event,this)">
upgrading
the database</a> and changing derby.database.defaultConnectionMode
property
value to "sqlStandard". All tables and views will be owned by the
database
owner. Until a GRANT statement is issued, only the table owner will
have
access to a table. </p>
      <p>Security mode switching is performed using the
SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY
procedure. In a database operating under the legacy security model any
user with fullAccess can call this procedure to switch the security
mode
to "sqlStandard". A database may not be reverted from the standard
security
mode to a legacy security mode. </p>
      <p>It may be good to switch the default connection mode to
standard
model
and hence support grant/revoke by default in future releases. A scheme
needs to be evolved to reduce any disruptions to existing users of
Derby. </p>
    </blockquote>
  </blockquote>
  </div>
  <br>
</blockquote>
</body>
</html>


Mime
View raw message