Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
M
MariaDB
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
nexedi
MariaDB
Commits
df2b3891
Commit
df2b3891
authored
Mar 05, 2005
by
gluh@gluh.mysql.r18.ru
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
WL2131: Access control for SHOW ... PROCEDURE|FUNCTION ...
parent
9ca989af
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
206 additions
and
31 deletions
+206
-31
mysql-test/r/information_schema.result
mysql-test/r/information_schema.result
+42
-3
mysql-test/t/information_schema.test
mysql-test/t/information_schema.test
+30
-7
sql/mysql_priv.h
sql/mysql_priv.h
+1
-0
sql/sp_head.cc
sql/sp_head.cc
+33
-2
sql/sql_acl.cc
sql/sql_acl.cc
+31
-0
sql/sql_acl.h
sql/sql_acl.h
+4
-0
sql/sql_parse.cc
sql/sql_parse.cc
+32
-0
sql/sql_show.cc
sql/sql_show.cc
+33
-19
No files found.
mysql-test/r/information_schema.result
View file @
df2b3891
show variables where variable_name like "skip_show_database";
Variable_name Value
skip_show_database OFF
grant all privileges on test.* to mysqltest_1@localhost;
grant select, update, execute on test.* to mysqltest_2@localhost;
grant select, update on test.* to mysqltest_1@localhost;
select * from information_schema.SCHEMATA where schema_name > 'm';
CATALOG_NAME SCHEMA_NAME DEFAULT_CHARACTER_SET_NAME SQL_PATH
NULL mysql latin1 NULL
...
...
@@ -229,6 +230,44 @@ sel2 sel2
select count(*) from information_schema.ROUTINES;
count(*)
2
select ROUTINE_NAME, ROUTINE_DEFINITION from information_schema.ROUTINES;
ROUTINE_NAME ROUTINE_DEFINITION
show create function sub1;
ERROR 42000: FUNCTION sub1 does not exist
select ROUTINE_NAME, ROUTINE_DEFINITION from information_schema.ROUTINES;
ROUTINE_NAME ROUTINE_DEFINITION
sel2
sub1
grant all privileges on test.* to mysqltest_1@localhost;
select ROUTINE_NAME, ROUTINE_DEFINITION from information_schema.ROUTINES;
ROUTINE_NAME ROUTINE_DEFINITION
sel2
sub1
create function sub2(i int) returns int
return i+1;
select ROUTINE_NAME, ROUTINE_DEFINITION from information_schema.ROUTINES;
ROUTINE_NAME ROUTINE_DEFINITION
sel2
sub1
sub2 return i+1
show create procedure sel2;
Procedure sql_mode Create Procedure
sel2
show create function sub1;
Function sql_mode Create Function
sub1
show create function sub2;
Function sql_mode Create Function
sub2 CREATE FUNCTION `test`.`sub2`(i int) RETURNS int
return i+1
drop function sub2;
show create procedure sel2;
Procedure sql_mode Create Procedure
sel2 CREATE PROCEDURE `test`.`sel2`()
begin
select * from t1;
select * from t2;
end
create view v0 (c) as select schema_name from information_schema.schemata;
select * from v0;
c
...
...
@@ -311,8 +350,8 @@ GRANTEE TABLE_CATALOG TABLE_SCHEMA TABLE_NAME COLUMN_NAME PRIVILEGE_TYPE IS_GRAN
'mysqltest_1'@'localhost' NULL test t1 a INSERT NO
'mysqltest_1'@'localhost' NULL test t1 a UPDATE NO
'mysqltest_1'@'localhost' NULL test t1 a REFERENCES NO
delete from mysql.user where user='mysqltest_1';
delete from mysql.db where user='mysqltest_1';
delete from mysql.user where user='mysqltest_1'
or user='mysqltest_2'
;
delete from mysql.db where user='mysqltest_1'
or user='mysqltest_2'
;
delete from mysql.tables_priv where user='mysqltest_1';
delete from mysql.columns_priv where user='mysqltest_1';
flush privileges;
...
...
mysql-test/t/information_schema.test
View file @
df2b3891
...
...
@@ -3,7 +3,8 @@
# show databases
show
variables
where
variable_name
like
"skip_show_database"
;
grant
all
privileges
on
test
.*
to
mysqltest_1
@
localhost
;
grant
select
,
update
,
execute
on
test
.*
to
mysqltest_2
@
localhost
;
grant
select
,
update
on
test
.*
to
mysqltest_1
@
localhost
;
select
*
from
information_schema
.
SCHEMATA
where
schema_name
>
'm'
;
select
schema_name
from
information_schema
.
schemata
;
...
...
@@ -104,6 +105,30 @@ select a.ROUTINE_NAME, b.name from information_schema.ROUTINES a,
mysql
.
proc
b
where
a
.
ROUTINE_NAME
=
convert
(
b
.
name
using
utf8
);
select
count
(
*
)
from
information_schema
.
ROUTINES
;
connect
(
user1
,
localhost
,
mysqltest_1
,,);
connect
(
user3
,
localhost
,
mysqltest_2
,,);
connection
user1
;
select
ROUTINE_NAME
,
ROUTINE_DEFINITION
from
information_schema
.
ROUTINES
;
--
error
1305
show
create
function
sub1
;
connection
user3
;
select
ROUTINE_NAME
,
ROUTINE_DEFINITION
from
information_schema
.
ROUTINES
;
connection
default
;
grant
all
privileges
on
test
.*
to
mysqltest_1
@
localhost
;
connect
(
user2
,
localhost
,
mysqltest_1
,,);
connection
user2
;
select
ROUTINE_NAME
,
ROUTINE_DEFINITION
from
information_schema
.
ROUTINES
;
create
function
sub2
(
i
int
)
returns
int
return
i
+
1
;
select
ROUTINE_NAME
,
ROUTINE_DEFINITION
from
information_schema
.
ROUTINES
;
show
create
procedure
sel2
;
show
create
function
sub1
;
show
create
function
sub2
;
connection
default
;
disconnect
user1
;
drop
function
sub2
;
show
create
procedure
sel2
;
#
# Test for views
#
...
...
@@ -138,8 +163,8 @@ select * from information_schema.USER_PRIVILEGES where grantee like '%mysqltest_
select
*
from
information_schema
.
SCHEMA_PRIVILEGES
where
grantee
like
'%mysqltest_1%'
;
select
*
from
information_schema
.
TABLE_PRIVILEGES
where
grantee
like
'%mysqltest_1%'
;
select
*
from
information_schema
.
COLUMN_PRIVILEGES
where
grantee
like
'%mysqltest_1%'
;
delete
from
mysql
.
user
where
user
=
'mysqltest_1'
;
delete
from
mysql
.
db
where
user
=
'mysqltest_1'
;
delete
from
mysql
.
user
where
user
=
'mysqltest_1'
or
user
=
'mysqltest_2'
;
delete
from
mysql
.
db
where
user
=
'mysqltest_1'
or
user
=
'mysqltest_2'
;
delete
from
mysql
.
tables_priv
where
user
=
'mysqltest_1'
;
delete
from
mysql
.
columns_priv
where
user
=
'mysqltest_1'
;
flush
privileges
;
...
...
@@ -160,13 +185,11 @@ TABLE_SCHEMA= "test";
select
*
from
information_schema
.
KEY_COLUMN_USAGE
where
TABLE_SCHEMA
=
"test"
;
connect
(
user1
,
localhost
,
mysqltest_1
,,);
connection
user1
;
connection
user2
;
select
table_name
from
information_schema
.
TABLES
where
table_schema
like
"test%"
;
select
table_name
,
column_name
from
information_schema
.
COLUMNS
where
table_schema
like
"test%"
;
select
ROUTINE_NAME
from
information_schema
.
ROUTINES
;
disconnect
user
1
;
disconnect
user
2
;
connection
default
;
delete
from
mysql
.
user
where
user
=
'mysqltest_1'
;
drop
table
t1
;
...
...
sql/mysql_priv.h
View file @
df2b3891
...
...
@@ -453,6 +453,7 @@ bool check_procedure_access(THD *thd,ulong want_access,char *db,char *name,
bool
check_some_access
(
THD
*
thd
,
ulong
want_access
,
TABLE_LIST
*
table
);
bool
check_merge_table_access
(
THD
*
thd
,
char
*
db
,
TABLE_LIST
*
table_list
);
bool
check_some_routine_access
(
THD
*
thd
,
char
*
db
,
char
*
name
);
bool
multi_update_precheck
(
THD
*
thd
,
TABLE_LIST
*
tables
);
bool
multi_delete_precheck
(
THD
*
thd
,
TABLE_LIST
*
tables
,
uint
*
table_count
);
bool
mysql_multi_update_prepare
(
THD
*
thd
);
...
...
sql/sp_head.cc
View file @
df2b3891
...
...
@@ -1004,6 +1004,27 @@ sp_head::restore_thd_mem_root(THD *thd)
}
bool
check_show_routine_acceess
(
THD
*
thd
,
sp_head
*
sp
,
bool
*
full_access
)
{
TABLE_LIST
tables
;
bzero
((
char
*
)
&
tables
,
sizeof
(
tables
));
tables
.
db
=
(
char
*
)
"mysql"
;
tables
.
table_name
=
tables
.
alias
=
(
char
*
)
"proc"
;
*
full_access
=
!
check_table_access
(
thd
,
SELECT_ACL
,
&
tables
,
1
);
if
(
!
(
*
full_access
))
*
full_access
=
(
!
strcmp
(
sp
->
m_definer_user
.
str
,
thd
->
priv_user
)
&&
!
strcmp
(
sp
->
m_definer_host
.
str
,
thd
->
priv_host
));
if
(
!
(
*
full_access
))
{
#ifndef NO_EMBEDDED_ACCESS_CHECKS
return
check_some_routine_access
(
thd
,
(
char
*
)
sp
->
m_db
.
str
,
(
char
*
)
sp
->
m_name
.
str
);
#endif
}
return
0
;
}
int
sp_head
::
show_create_procedure
(
THD
*
thd
)
{
...
...
@@ -1016,11 +1037,15 @@ sp_head::show_create_procedure(THD *thd)
sys_var
*
sql_mode_var
;
byte
*
sql_mode_str
;
ulong
sql_mode_len
;
bool
full_access
;
DBUG_ENTER
(
"sp_head::show_create_procedure"
);
DBUG_PRINT
(
"info"
,
(
"procedure %s"
,
m_name
.
str
));
LINT_INIT
(
sql_mode_str
);
LINT_INIT
(
sql_mode_len
);
if
(
check_show_routine_acceess
(
thd
,
this
,
&
full_access
))
return
1
;
old_sql_mode
=
thd
->
variables
.
sql_mode
;
thd
->
variables
.
sql_mode
=
m_sql_mode
;
...
...
@@ -1047,7 +1072,8 @@ sp_head::show_create_procedure(THD *thd)
protocol
->
store
(
m_name
.
str
,
m_name
.
length
,
system_charset_info
);
if
(
sql_mode_var
)
protocol
->
store
((
char
*
)
sql_mode_str
,
sql_mode_len
,
system_charset_info
);
protocol
->
store
(
m_defstr
.
str
,
m_defstr
.
length
,
system_charset_info
);
if
(
full_access
)
protocol
->
store
(
m_defstr
.
str
,
m_defstr
.
length
,
system_charset_info
);
res
=
protocol
->
write
();
send_eof
(
thd
);
...
...
@@ -1085,11 +1111,15 @@ sp_head::show_create_function(THD *thd)
sys_var
*
sql_mode_var
;
byte
*
sql_mode_str
;
ulong
sql_mode_len
;
bool
full_access
;
DBUG_ENTER
(
"sp_head::show_create_function"
);
DBUG_PRINT
(
"info"
,
(
"procedure %s"
,
m_name
.
str
));
LINT_INIT
(
sql_mode_str
);
LINT_INIT
(
sql_mode_len
);
if
(
check_show_routine_acceess
(
thd
,
this
,
&
full_access
))
return
1
;
old_sql_mode
=
thd
->
variables
.
sql_mode
;
thd
->
variables
.
sql_mode
=
m_sql_mode
;
sql_mode_var
=
find_sys_var
(
"SQL_MODE"
,
8
);
...
...
@@ -1114,7 +1144,8 @@ sp_head::show_create_function(THD *thd)
protocol
->
store
(
m_name
.
str
,
m_name
.
length
,
system_charset_info
);
if
(
sql_mode_var
)
protocol
->
store
((
char
*
)
sql_mode_str
,
sql_mode_len
,
system_charset_info
);
protocol
->
store
(
m_defstr
.
str
,
m_defstr
.
length
,
system_charset_info
);
if
(
full_access
)
protocol
->
store
(
m_defstr
.
str
,
m_defstr
.
length
,
system_charset_info
);
res
=
protocol
->
write
();
send_eof
(
thd
);
...
...
sql/sql_acl.cc
View file @
df2b3891
...
...
@@ -3583,6 +3583,37 @@ bool check_grant_procedure(THD *thd, ulong want_access,
}
/*
Check if routine has any of the
procedure level grants
SYNPOSIS
bool check_routine_level_acl()
thd Thread handler
db Database name
name Routine name
RETURN
1 error
0 Ok
*/
bool
check_routine_level_acl
(
THD
*
thd
,
char
*
db
,
char
*
name
)
{
bool
no_routine_acl
=
1
;
if
(
grant_option
)
{
GRANT_NAME
*
grant_proc
;
rw_rdlock
(
&
LOCK_grant
);
if
((
grant_proc
=
proc_hash_search
(
thd
->
priv_host
,
thd
->
ip
,
db
,
thd
->
priv_user
,
name
,
0
)))
no_routine_acl
=
!
(
grant_proc
->
privs
&
SHOW_PROC_ACLS
);
rw_unlock
(
&
LOCK_grant
);
}
return
no_routine_acl
;
}
/*****************************************************************************
Functions to retrieve the grant for a table/column (for SHOW functions)
*****************************************************************************/
...
...
sql/sql_acl.h
View file @
df2b3891
...
...
@@ -63,6 +63,9 @@
#define PROC_ACLS \
(ALTER_PROC_ACL | EXECUTE_ACL | GRANT_ACL)
#define SHOW_PROC_ACLS \
(ALTER_PROC_ACL | EXECUTE_ACL | CREATE_PROC_ACL)
#define GLOBAL_ACLS \
(SELECT_ACL | INSERT_ACL | UPDATE_ACL | DELETE_ACL | CREATE_ACL | DROP_ACL | \
RELOAD_ACL | SHUTDOWN_ACL | PROCESS_ACL | FILE_ACL | GRANT_ACL | \
...
...
@@ -216,6 +219,7 @@ void fill_effective_table_privileges(THD *thd, GRANT_INFO *grant,
const
char
*
db
,
const
char
*
table
);
bool
sp_revoke_privileges
(
THD
*
thd
,
const
char
*
sp_db
,
const
char
*
sp_name
);
bool
sp_grant_privileges
(
THD
*
thd
,
const
char
*
sp_db
,
const
char
*
sp_name
);
bool
check_routine_level_acl
(
THD
*
thd
,
char
*
db
,
char
*
name
);
#ifdef NO_EMBEDDED_ACCESS_CHECKS
#define check_grant(A,B,C,D,E,F) 0
...
...
sql/sql_parse.cc
View file @
df2b3891
...
...
@@ -4745,6 +4745,38 @@ check_procedure_access(THD *thd, ulong want_access,char *db, char *name,
return
FALSE
;
}
/*
Check if the routine has any of the routine privileges
SYNOPSIS
check_some_routine_access()
thd Thread handler
db Database name
name Routine name
RETURN
0 ok
1 error
*/
bool
check_some_routine_access
(
THD
*
thd
,
char
*
db
,
char
*
name
)
{
ulong
save_priv
;
if
(
thd
->
master_access
&
SHOW_PROC_ACLS
)
return
FALSE
;
if
(
!
check_access
(
thd
,
SHOW_PROC_ACLS
,
db
,
&
save_priv
,
0
,
1
)
||
(
save_priv
&
SHOW_PROC_ACLS
))
return
FALSE
;
#ifndef NO_EMBEDDED_ACCESS_CHECKS
if
(
grant_option
)
return
check_routine_level_acl
(
thd
,
db
,
name
);
#endif
return
FALSE
;
}
/*
Check if the given table has any of the asked privileges
...
...
sql/sql_show.cc
View file @
df2b3891
...
...
@@ -2468,32 +2468,41 @@ int fill_schema_coll_charset_app(THD *thd, TABLE_LIST *tables, COND *cond)
}
void
store_schema_proc
(
THD
*
thd
,
TABLE
*
table
,
TABLE
*
proc_table
,
const
char
*
wild
)
void
store_schema_proc
(
THD
*
thd
,
TABLE
*
table
,
TABLE
*
proc_table
,
const
char
*
wild
,
bool
full_access
,
const
char
*
sp_user
)
{
String
tmp_string
;
TIME
time
;
LEX
*
lex
=
thd
->
lex
;
CHARSET_INFO
*
cs
=
system_charset_info
;
restore_record
(
table
,
s
->
default_values
);
const
char
*
sp_db
,
*
sp_name
,
*
definer
;
sp_db
=
get_field
(
thd
->
mem_root
,
proc_table
->
field
[
0
]);
sp_name
=
get_field
(
thd
->
mem_root
,
proc_table
->
field
[
1
]);
definer
=
get_field
(
thd
->
mem_root
,
proc_table
->
field
[
11
]);
if
(
!
full_access
)
full_access
=
!
strcmp
(
sp_user
,
definer
);
if
(
!
full_access
)
{
#ifndef NO_EMBEDDED_ACCESS_CHECKS
if
(
check_some_routine_access
(
thd
,
(
char
*
)
sp_db
,
(
char
*
)
sp_name
))
return
;
#endif
}
if
(
lex
->
orig_sql_command
==
SQLCOM_SHOW_STATUS_PROC
&&
proc_table
->
field
[
2
]
->
val_int
()
==
TYPE_ENUM_PROCEDURE
||
lex
->
orig_sql_command
==
SQLCOM_SHOW_STATUS_FUNC
&&
proc_table
->
field
[
2
]
->
val_int
()
==
TYPE_ENUM_FUNCTION
||
lex
->
orig_sql_command
==
SQLCOM_END
)
{
tmp_string
.
length
(
0
);
get_field
(
thd
->
mem_root
,
proc_table
->
field
[
1
],
&
tmp_string
);
if
(
!
wild
||
!
wild
[
0
]
||
!
wild_compare
(
tmp_string
.
ptr
(),
wild
,
0
))
restore_record
(
table
,
s
->
default_values
);
if
(
!
wild
||
!
wild
[
0
]
||
!
wild_compare
(
sp_name
,
wild
,
0
))
{
table
->
field
[
3
]
->
store
(
tmp_string
.
ptr
(),
tmp_string
.
length
(
),
cs
);
table
->
field
[
3
]
->
store
(
sp_name
,
strlen
(
sp_name
),
cs
);
tmp_string
.
length
(
0
);
get_field
(
thd
->
mem_root
,
proc_table
->
field
[
3
],
&
tmp_string
);
table
->
field
[
0
]
->
store
(
tmp_string
.
ptr
(),
tmp_string
.
length
(),
cs
);
tmp_string
.
length
(
0
);
get_field
(
thd
->
mem_root
,
proc_table
->
field
[
0
],
&
tmp_string
);
table
->
field
[
2
]
->
store
(
tmp_string
.
ptr
(),
tmp_string
.
length
(),
cs
);
table
->
field
[
2
]
->
store
(
sp_db
,
strlen
(
sp_db
),
cs
);
tmp_string
.
length
(
0
);
get_field
(
thd
->
mem_root
,
proc_table
->
field
[
2
],
&
tmp_string
);
table
->
field
[
4
]
->
store
(
tmp_string
.
ptr
(),
tmp_string
.
length
(),
cs
);
...
...
@@ -2504,10 +2513,13 @@ void store_schema_proc(THD *thd, TABLE *table,
table
->
field
[
5
]
->
store
(
tmp_string
.
ptr
(),
tmp_string
.
length
(),
cs
);
table
->
field
[
5
]
->
set_notnull
();
}
if
(
full_access
)
{
tmp_string
.
length
(
0
);
get_field
(
thd
->
mem_root
,
proc_table
->
field
[
10
],
&
tmp_string
);
table
->
field
[
7
]
->
store
(
tmp_string
.
ptr
(),
tmp_string
.
length
(),
cs
);
}
table
->
field
[
6
]
->
store
(
"SQL"
,
3
,
cs
);
tmp_string
.
length
(
0
);
get_field
(
thd
->
mem_root
,
proc_table
->
field
[
10
],
&
tmp_string
);
table
->
field
[
7
]
->
store
(
tmp_string
.
ptr
(),
tmp_string
.
length
(),
cs
);
table
->
field
[
10
]
->
store
(
"SQL"
,
3
,
cs
);
tmp_string
.
length
(
0
);
get_field
(
thd
->
mem_root
,
proc_table
->
field
[
6
],
&
tmp_string
);
...
...
@@ -2531,9 +2543,7 @@ void store_schema_proc(THD *thd, TABLE *table,
tmp_string
.
length
(
0
);
get_field
(
thd
->
mem_root
,
proc_table
->
field
[
15
],
&
tmp_string
);
table
->
field
[
18
]
->
store
(
tmp_string
.
ptr
(),
tmp_string
.
length
(),
cs
);
tmp_string
.
length
(
0
);
get_field
(
thd
->
mem_root
,
proc_table
->
field
[
11
],
&
tmp_string
);
table
->
field
[
19
]
->
store
(
tmp_string
.
ptr
(),
tmp_string
.
length
(),
cs
);
table
->
field
[
19
]
->
store
(
definer
,
strlen
(
definer
),
cs
);
table
->
file
->
write_row
(
table
->
record
[
0
]);
}
}
...
...
@@ -2547,14 +2557,18 @@ int fill_schema_proc(THD *thd, TABLE_LIST *tables, COND *cond)
const
char
*
wild
=
thd
->
lex
->
wild
?
thd
->
lex
->
wild
->
ptr
()
:
NullS
;
int
res
=
0
;
TABLE
*
table
=
tables
->
table
,
*
old_open_tables
=
thd
->
open_tables
;
bool
full_access
;
char
definer
[
HOSTNAME_LENGTH
+
USERNAME_LENGTH
+
2
];
DBUG_ENTER
(
"fill_schema_proc"
);
strxmov
(
definer
,
thd
->
priv_user
,
"@"
,
thd
->
priv_host
,
NullS
);
bzero
((
char
*
)
&
proc_tables
,
sizeof
(
proc_tables
));
proc_tables
.
db
=
(
char
*
)
"mysql"
;
proc_tables
.
db_length
=
5
;
proc_tables
.
table_name
=
proc_tables
.
alias
=
(
char
*
)
"proc"
;
proc_tables
.
table_name_length
=
4
;
proc_tables
.
lock_type
=
TL_READ
;
full_access
=
!
check_table_access
(
thd
,
SELECT_ACL
,
&
proc_tables
,
1
);
if
(
!
(
proc_table
=
open_ltable
(
thd
,
&
proc_tables
,
TL_READ
)))
{
DBUG_RETURN
(
1
);
...
...
@@ -2565,9 +2579,9 @@ int fill_schema_proc(THD *thd, TABLE_LIST *tables, COND *cond)
res
=
(
res
==
HA_ERR_END_OF_FILE
)
?
0
:
1
;
goto
err
;
}
store_schema_proc
(
thd
,
table
,
proc_table
,
wild
);
store_schema_proc
(
thd
,
table
,
proc_table
,
wild
,
full_access
,
definer
);
while
(
!
proc_table
->
file
->
index_next
(
proc_table
->
record
[
0
]))
store_schema_proc
(
thd
,
table
,
proc_table
,
wild
);
store_schema_proc
(
thd
,
table
,
proc_table
,
wild
,
full_access
,
definer
);
err:
proc_table
->
file
->
ha_index_end
();
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment