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
0d7e68c9
Commit
0d7e68c9
authored
Mar 05, 2005
by
unknown
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
WL2131: Access control for SHOW ... PROCEDURE|FUNCTION ...
parent
0ae5efb4
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 @
0d7e68c9
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 @
0d7e68c9
...
...
@@ -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 @
0d7e68c9
...
...
@@ -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 @
0d7e68c9
...
...
@@ -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 @
0d7e68c9
...
...
@@ -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 @
0d7e68c9
...
...
@@ -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 @
0d7e68c9
...
...
@@ -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 @
0d7e68c9
...
...
@@ -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