Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
T
tg
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
tg
Commits
14fd4006
Commit
14fd4006
authored
Oct 28, 2013
by
vysheng
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Changed authorization. Sorry, if that fails anything. Now supports download of photos from other DC
parent
62f7295f
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
345 additions
and
50 deletions
+345
-50
loop.c
loop.c
+152
-27
net.h
net.h
+2
-0
queries.c
queries.c
+185
-23
queries.h
queries.h
+6
-0
No files found.
loop.c
View file @
14fd4006
...
...
@@ -87,6 +87,30 @@ void net_loop (int flags, int (*is_end)(void)) {
}
}
char
**
_s
;
size_t
*
_l
;
int
got_it_ok
;
void
got_it
(
char
*
line
)
{
*
_s
=
strdup
(
line
);
*
_l
=
strlen
(
line
);
got_it_ok
=
1
;
}
int
is_got_it
(
void
)
{
return
got_it_ok
;
}
int
net_getline
(
char
**
s
,
size_t
*
l
)
{
got_it_ok
=
0
;
_s
=
s
;
_l
=
l
;
rl_callback_handler_install
(
0
,
got_it
);
net_loop
(
1
,
is_got_it
);
printf
(
"'%s'
\n
"
,
*
s
);
return
0
;
}
int
ret1
(
void
)
{
return
0
;
}
int
main_loop
(
void
)
{
...
...
@@ -116,13 +140,14 @@ void write_dc (int auth_file_fd, struct dc *DC) {
}
assert
(
write
(
auth_file_fd
,
&
DC
->
server_salt
,
8
)
==
8
);
assert
(
write
(
auth_file_fd
,
&
DC
->
has_auth
,
4
)
==
4
);
}
int
our_id
;
void
write_auth_file
(
void
)
{
int
auth_file_fd
=
open
(
get_auth_key_filename
(),
O_CREAT
|
O_RDWR
,
0600
);
assert
(
auth_file_fd
>=
0
);
int
x
=
DC_SERIALIZED_MAGIC
;
int
x
=
DC_SERIALIZED_MAGIC
_V2
;
assert
(
write
(
auth_file_fd
,
&
x
,
4
)
==
4
);
x
=
MAX_DC_ID
;
assert
(
write
(
auth_file_fd
,
&
x
,
4
)
==
4
);
...
...
@@ -143,7 +168,7 @@ void write_auth_file (void) {
close
(
auth_file_fd
);
}
void
read_dc
(
int
auth_file_fd
,
int
id
)
{
void
read_dc
(
int
auth_file_fd
,
int
id
,
unsigned
ver
)
{
int
port
=
0
;
assert
(
read
(
auth_file_fd
,
&
port
,
4
)
==
4
);
int
l
=
0
;
...
...
@@ -159,37 +184,48 @@ void read_dc (int auth_file_fd, int id) {
if
(
DC
->
auth_key_id
)
{
DC
->
flags
|=
1
;
}
if
(
ver
!=
DC_SERIALIZED_MAGIC
)
{
assert
(
read
(
auth_file_fd
,
&
DC
->
has_auth
,
4
)
==
4
);
}
else
{
DC
->
has_auth
=
0
;
}
}
void
empty_auth_file
(
void
)
{
struct
dc
*
DC
=
alloc_dc
(
1
,
strdup
(
TG_SERVER
),
443
);
assert
(
DC
);
dc_working_num
=
1
;
auth_state
=
0
;
write_auth_file
();
}
int
need_dc_list_update
;
void
read_auth_file
(
void
)
{
int
auth_file_fd
=
open
(
get_auth_key_filename
(),
O_CREAT
|
O_RDWR
,
0600
);
if
(
auth_file_fd
<
0
)
{
empty_auth_file
();
}
assert
(
auth_file_fd
>=
0
);
int
x
;
if
(
read
(
auth_file_fd
,
&
x
,
4
)
<
4
||
x
!=
DC_SERIALIZED_MAGIC
)
{
unsigned
x
;
unsigned
m
;
if
(
read
(
auth_file_fd
,
&
m
,
4
)
<
4
||
(
m
!=
DC_SERIALIZED_MAGIC
&&
m
!=
DC_SERIALIZED_MAGIC_V2
))
{
close
(
auth_file_fd
);
empty_auth_file
();
return
;
}
assert
(
read
(
auth_file_fd
,
&
x
,
4
)
==
4
);
assert
(
x
>=
0
&&
x
<=
MAX_DC_ID
);
assert
(
x
<=
MAX_DC_ID
);
assert
(
read
(
auth_file_fd
,
&
dc_working_num
,
4
)
==
4
);
assert
(
read
(
auth_file_fd
,
&
auth_state
,
4
)
==
4
);
if
(
m
==
DC_SERIALIZED_MAGIC
)
{
auth_state
=
700
;
}
int
i
;
for
(
i
=
0
;
i
<=
x
;
i
++
)
{
for
(
i
=
0
;
i
<=
(
int
)
x
;
i
++
)
{
int
y
;
assert
(
read
(
auth_file_fd
,
&
y
,
4
)
==
4
);
if
(
y
)
{
read_dc
(
auth_file_fd
,
i
);
read_dc
(
auth_file_fd
,
i
,
m
);
}
}
int
l
=
read
(
auth_file_fd
,
&
our_id
,
4
);
...
...
@@ -197,59 +233,148 @@ void read_auth_file (void) {
assert
(
!
l
);
}
close
(
auth_file_fd
);
DC_working
=
DC_list
[
dc_working_num
];
if
(
m
==
DC_SERIALIZED_MAGIC
)
{
DC_working
->
has_auth
=
1
;
}
}
extern
int
max_chat_size
;
int
mcs
(
void
)
{
return
max_chat_size
;
}
int
readline_active
;
int
new_dc_num
;
int
loop
(
void
)
{
on_start
();
read_auth_file
();
readline_active
=
1
;
rl_set_prompt
(
""
);
assert
(
DC_list
[
dc_working_num
]);
DC_working
=
DC_list
[
dc_working_num
];
if
(
!
DC_working
->
auth_key_id
)
{
if
(
auth_state
==
0
)
{
DC_working
=
DC_list
[
dc_working_num
];
assert
(
!
DC_working
->
auth_key_id
);
dc_authorize
(
DC_working
);
}
else
{
dc_create_session
(
DC_working
);
assert
(
DC_working
->
auth_key_id
);
auth_state
=
100
;
write_auth_file
();
}
if
(
verbosity
)
{
logprintf
(
"Requesting info about DC...
\n
"
);
}
do_help_get_config
();
net_loop
(
0
,
mcs
);
if
(
verbosity
)
{
logprintf
(
"DC_info: %d new DC got
\n
"
,
new_dc_num
);
}
if
(
new_dc_num
)
{
int
i
;
for
(
i
=
0
;
i
<=
MAX_DC_NUM
;
i
++
)
if
(
DC_list
[
i
]
&&
!
DC_list
[
i
]
->
auth_key_id
)
{
dc_authorize
(
DC_list
[
i
]);
assert
(
DC_list
[
i
]
->
auth_key_id
);
write_auth_file
();
}
}
if
(
!
auth_state
)
{
if
(
auth_state
==
100
)
{
if
(
!
default_username
)
{
size_t
size
=
0
;
char
*
user
=
0
;
if
(
!
user
&&
!
auth_token
)
{
if
(
!
user
)
{
printf
(
"Telephone number (with '+' sign): "
);
if
(
getline
(
&
user
,
&
size
,
stdin
)
==
-
1
)
{
if
(
net_getline
(
&
user
,
&
size
)
==
-
1
)
{
perror
(
"getline()"
);
exit
(
EXIT_FAILURE
);
}
user
[
strlen
(
user
)
-
1
]
=
0
;
set_default_username
(
user
);
}
}
do_send_code
(
default_username
);
char
*
code
=
0
;
size_t
size
=
0
;
printf
(
"Code from sms: "
);
while
(
1
)
{
if
(
getline
(
&
code
,
&
size
,
stdin
)
==
-
1
)
{
int
res
=
do_auth_check_phone
(
default_username
);
assert
(
res
>=
0
);
logprintf
(
"%s
\n
"
,
res
>
0
?
"phone registered"
:
"phone not registered"
);
if
(
res
>
0
)
{
do_send_code
(
default_username
);
char
*
code
=
0
;
size_t
size
=
0
;
printf
(
"Code from sms: "
);
while
(
1
)
{
if
(
net_getline
(
&
code
,
&
size
)
==
-
1
)
{
perror
(
"getline()"
);
exit
(
EXIT_FAILURE
);
}
if
(
do_send_code_result
(
code
)
>=
0
)
{
break
;
}
printf
(
"Invalid code. Try again: "
);
free
(
code
);
}
auth_state
=
300
;
}
else
{
printf
(
"User is not registered. Do you want to register? [Y/n] "
);
char
*
code
;
size_t
size
;
if
(
net_getline
(
&
code
,
&
size
)
==
-
1
)
{
perror
(
"getline()"
);
exit
(
EXIT_FAILURE
);
}
code
[
strlen
(
code
)
-
1
]
=
0
;
if
(
do_send_code_result
(
code
)
>=
0
)
{
break
;
if
(
!*
code
||
*
code
==
'y'
)
{
printf
(
"Ok, starting registartion.
\n
"
);
}
else
{
printf
(
"Then try again
\n
"
);
exit
(
EXIT_SUCCESS
);
}
char
*
first_name
;
printf
(
"Name: "
);
if
(
net_getline
(
&
first_name
,
&
size
)
==
-
1
)
{
perror
(
"getline()"
);
exit
(
EXIT_FAILURE
);
}
printf
(
"Invalid code. Try again: "
);
char
*
last_name
;
printf
(
"Name: "
);
if
(
net_getline
(
&
last_name
,
&
size
)
==
-
1
)
{
perror
(
"getline()"
);
exit
(
EXIT_FAILURE
);
}
int
dc_num
=
do_get_nearest_dc
();
assert
(
dc_num
>=
0
&&
dc_num
<=
MAX_DC_NUM
&&
DC_list
[
dc_num
]);
dc_working_num
=
dc_num
;
DC_working
=
DC_list
[
dc_working_num
];
do_send_code
(
default_username
);
printf
(
"Code from sms: "
);
while
(
1
)
{
if
(
net_getline
(
&
code
,
&
size
)
==
-
1
)
{
perror
(
"getline()"
);
exit
(
EXIT_FAILURE
);
}
if
(
do_send_code_result_auth
(
code
,
first_name
,
last_name
)
>=
0
)
{
break
;
}
printf
(
"Invalid code. Try again: "
);
free
(
code
);
}
auth_state
=
300
;
}
auth_state
=
1
;
}
int
i
;
for
(
i
=
0
;
i
<=
MAX_DC_NUM
;
i
++
)
if
(
DC_list
[
i
]
&&
!
DC_list
[
i
]
->
has_auth
)
{
do_export_auth
(
i
);
do_import_auth
(
i
);
DC_list
[
i
]
->
has_auth
=
1
;
write_auth_file
();
}
write_auth_file
();
fflush
(
stdin
);
fflush
(
stdout
);
fflush
(
stderr
);
readline_active
=
1
;
rl_callback_handler_install
(
get_default_prompt
(),
interpreter
);
rl_attempted_completion_function
=
(
CPPFunction
*
)
complete_text
;
rl_completion_entry_function
=
complete_none
;
...
...
net.h
View file @
14fd4006
...
...
@@ -73,9 +73,11 @@ struct dc {
int
server_time_delta
;
double
server_time_udelta
;
int
has_auth
;
};
#define DC_SERIALIZED_MAGIC 0x64582faa
#define DC_SERIALIZED_MAGIC_V2 0x94032abb
struct
dc_serialized
{
int
magic
;
int
port
;
...
...
queries.c
View file @
14fd4006
...
...
@@ -206,6 +206,7 @@ void query_result (long long id UU) {
if
(
verbosity
)
{
logprintf
(
"No such query
\n
"
);
}
in_ptr
=
in_end
;
}
else
{
if
(
!
(
q
->
flags
&
QUERY_ACK_RECEIVED
))
{
remove_event_timer
(
&
q
->
ev
);
...
...
@@ -213,6 +214,7 @@ void query_result (long long id UU) {
queries_tree
=
tree_delete_query
(
queries_tree
,
q
);
if
(
q
->
methods
&&
q
->
methods
->
on_answer
)
{
q
->
methods
->
on_answer
(
q
);
assert
(
in_ptr
==
in_end
);
}
free
(
q
->
data
);
free
(
q
);
...
...
@@ -263,6 +265,7 @@ void work_timers (void) {
int
max_chat_size
;
int
want_dc_num
;
int
new_dc_num
;
extern
struct
dc
*
DC_list
[];
extern
struct
dc
*
DC_working
;
...
...
@@ -294,6 +297,7 @@ int help_get_config_on_answer (struct query *q UU) {
}
if
(
!
DC_list
[
id
])
{
alloc_dc
(
id
,
strndup
(
ip
,
l2
),
port
);
new_dc_num
++
;
}
}
max_chat_size
=
fetch_int
();
...
...
@@ -307,10 +311,16 @@ struct query_methods help_get_config_methods = {
.
on_answer
=
help_get_config_on_answer
};
void
do_help_get_config
(
void
)
{
clear_packet
();
out_int
(
CODE_help_get_config
);
send_query
(
DC_working
,
packet_ptr
-
packet_buffer
,
packet_buffer
,
&
help_get_config_methods
,
0
);
}
char
*
phone_code_hash
;
int
send_code_on_answer
(
struct
query
*
q
UU
)
{
assert
(
fetch_int
()
==
CODE_auth_sent_code
);
assert
(
fetch_
int
()
==
(
int
)
CODE_bool_true
);
assert
(
fetch_
bool
()
);
int
l
=
prefetch_strlen
();
char
*
s
=
fetch_str
(
l
);
if
(
phone_code_hash
)
{
...
...
@@ -350,6 +360,7 @@ char *suser;
extern
int
dc_working_num
;
void
do_send_code
(
const
char
*
user
)
{
suser
=
strdup
(
user
);
fprintf
(
stderr
,
"user='%s'
\n
"
,
user
);
want_dc_num
=
0
;
clear_packet
();
out_int
(
CODE_auth_send_code
);
...
...
@@ -363,29 +374,11 @@ void do_send_code (const char *user) {
net_loop
(
0
,
code_is_sent
);
if
(
want_dc_num
==
-
1
)
{
return
;
}
if
(
DC_list
[
want_dc_num
])
{
DC_working
=
DC_list
[
want_dc_num
];
if
(
!
DC_working
->
auth_key_id
)
{
dc_authorize
(
DC_working
);
}
if
(
!
DC_working
->
sessions
[
0
])
{
dc_create_session
(
DC_working
);
}
dc_working_num
=
want_dc_num
;
}
else
{
clear_packet
();
out_int
(
CODE_help_get_config
);
send_query
(
DC_working
,
packet_ptr
-
packet_buffer
,
packet_buffer
,
&
help_get_config_methods
,
0
);
net_loop
(
0
,
config_got
);
DC_working
=
DC_list
[
want_dc_num
];
if
(
!
DC_working
->
auth_key_id
)
{
dc_authorize
(
DC_working
);
}
if
(
!
DC_working
->
sessions
[
0
])
{
dc_create_session
(
DC_working
);
}
dc_working_num
=
want_dc_num
;
DC_working
=
DC_list
[
want_dc_num
];
if
(
!
DC_working
->
sessions
[
0
])
{
dc_create_session
(
DC_working
);
}
dc_working_num
=
want_dc_num
;
want_dc_num
=
0
;
clear_packet
();
out_int
(
CODE_auth_send_code
);
...
...
@@ -400,6 +393,97 @@ void do_send_code (const char *user) {
assert
(
want_dc_num
==
-
1
);
}
int
check_phone_result
;
int
cr_f
(
void
)
{
return
check_phone_result
>=
0
;
}
int
check_phone_on_answer
(
struct
query
*
q
UU
)
{
assert
(
fetch_int
()
==
(
int
)
CODE_auth_checked_phone
);
check_phone_result
=
fetch_bool
();
fetch_bool
();
return
0
;
}
int
check_phone_on_error
(
struct
query
*
q
UU
,
int
error_code
,
int
l
,
char
*
error
)
{
int
s
=
strlen
(
"PHONE_MIGRATE_"
);
int
s2
=
strlen
(
"NETWORK_MIGRATE_"
);
if
(
l
>=
s
&&
!
memcmp
(
error
,
"PHONE_MIGRATE_"
,
s
))
{
int
i
=
error
[
s
]
-
'0'
;
assert
(
DC_list
[
i
]);
dc_working_num
=
i
;
DC_working
=
DC_list
[
i
];
write_auth_file
();
check_phone_result
=
1
;
}
else
if
(
l
>=
s2
&&
!
memcmp
(
error
,
"NETWORK_MIGRATE_"
,
s
))
{
int
i
=
error
[
s2
]
-
'0'
;
assert
(
DC_list
[
i
]);
dc_working_num
=
i
;
DC_working
=
DC_list
[
i
];
write_auth_file
();
check_phone_result
=
1
;
}
else
{
logprintf
(
"error_code = %d, error = %.*s
\n
"
,
error_code
,
l
,
error
);
assert
(
0
);
}
return
0
;
}
struct
query_methods
check_phone_methods
=
{
.
on_answer
=
check_phone_on_answer
,
.
on_error
=
check_phone_on_error
};
int
do_auth_check_phone
(
const
char
*
user
)
{
suser
=
strdup
(
user
);
clear_packet
();
out_int
(
CODE_auth_check_phone
);
out_string
(
user
);
printf
(
"'%s'
\n
"
,
user
);
check_phone_result
=
-
1
;
send_query
(
DC_working
,
packet_ptr
-
packet_buffer
,
packet_buffer
,
&
check_phone_methods
,
0
);
net_loop
(
0
,
cr_f
);
return
check_phone_result
;
}
int
nearest_dc_num
;
int
nr_f
(
void
)
{
return
nearest_dc_num
>=
0
;
}
int
nearest_dc_on_answer
(
struct
query
*
q
UU
)
{
assert
(
fetch_int
()
==
(
int
)
CODE_nearest_dc
);
char
*
country
=
fetch_str_dup
();
if
(
verbosity
>
0
)
{
logprintf
(
"Server thinks that you are in %s
\n
"
,
country
);
}
fetch_int
();
// this_dc
nearest_dc_num
=
fetch_int
();
assert
(
nearest_dc_num
>=
0
);
return
0
;
}
int
fail_on_error
(
struct
query
*
q
UU
,
int
error_code
UU
,
int
l
UU
,
char
*
error
UU
)
{
fprintf
(
stderr
,
"error #%d: %.*s
\n
"
,
error_code
,
l
,
error
);
assert
(
0
);
return
0
;
}
struct
query_methods
nearest_dc_methods
=
{
.
on_answer
=
nearest_dc_on_answer
,
.
on_error
=
fail_on_error
};
int
do_get_nearest_dc
(
void
)
{
clear_packet
();
out_int
(
CODE_help_get_nearest_dc
);
nearest_dc_num
=
-
1
;
send_query
(
DC_working
,
packet_ptr
-
packet_buffer
,
packet_buffer
,
&
nearest_dc_methods
,
0
);
net_loop
(
0
,
nr_f
);
return
nearest_dc_num
;
}
int
sign_in_ok
;
int
sign_in_is_ok
(
void
)
{
return
sign_in_ok
;
...
...
@@ -415,12 +499,14 @@ int sign_in_on_answer (struct query *q UU) {
if
(
verbosity
)
{
logprintf
(
"authorized successfully: name = '%s %s', phone = '%s', expires = %d
\n
"
,
User
.
first_name
,
User
.
last_name
,
User
.
phone
,
(
int
)(
expires
-
get_double_time
()));
}
DC_working
->
has_auth
=
1
;
return
0
;
}
int
sign_in_on_error
(
struct
query
*
q
UU
,
int
error_code
,
int
l
,
char
*
error
)
{
logprintf
(
"error_code = %d, error = %.*s
\n
"
,
error_code
,
l
,
error
);
sign_in_ok
=
-
1
;
assert
(
0
);
return
0
;
}
...
...
@@ -441,6 +527,20 @@ int do_send_code_result (const char *code) {
return
sign_in_ok
;
}
int
do_send_code_result_auth
(
const
char
*
code
,
const
char
*
first_name
,
const
char
*
last_name
)
{
clear_packet
();
out_int
(
CODE_auth_sign_up
);
out_string
(
suser
);
out_string
(
phone_code_hash
);
out_string
(
code
);
out_string
(
first_name
);
out_string
(
last_name
);
send_query
(
DC_working
,
packet_ptr
-
packet_buffer
,
packet_buffer
,
&
sign_in_methods
,
0
);
sign_in_ok
=
0
;
net_loop
(
0
,
sign_in_is_ok
);
return
sign_in_ok
;
}
extern
char
*
user_list
[];
int
get_contacts_on_answer
(
struct
query
*
q
UU
)
{
...
...
@@ -1219,3 +1319,65 @@ void do_load_video (struct video *V, int next) {
D
->
fd
=
-
1
;
load_next_part
(
D
);
}
char
*
export_auth_str
;
int
export_auth_str_len
;
int
is_export_auth_str
(
void
)
{
return
export_auth_str
!=
0
;
}
int
isn_export_auth_str
(
void
)
{
return
export_auth_str
==
0
;
}
int
export_auth_on_answer
(
struct
query
*
q
UU
)
{
assert
(
fetch_int
()
==
(
int
)
CODE_auth_exported_authorization
);
int
l
=
fetch_int
();
if
(
!
our_id
)
{
our_id
=
l
;
}
else
{
assert
(
our_id
==
l
);
}
l
=
prefetch_strlen
();
char
*
s
=
malloc
(
l
);
memcpy
(
s
,
fetch_str
(
l
),
l
);
export_auth_str_len
=
l
;
export_auth_str
=
s
;
return
0
;
}
struct
query_methods
export_auth_methods
=
{
.
on_answer
=
export_auth_on_answer
,
.
on_error
=
fail_on_error
};
void
do_export_auth
(
int
num
)
{
export_auth_str
=
0
;
clear_packet
();
out_int
(
CODE_auth_export_authorization
);
out_int
(
num
);
send_query
(
DC_working
,
packet_ptr
-
packet_buffer
,
packet_buffer
,
&
export_auth_methods
,
0
);
net_loop
(
0
,
is_export_auth_str
);
}
int
import_auth_on_answer
(
struct
query
*
q
UU
)
{
assert
(
fetch_int
()
==
(
int
)
CODE_auth_authorization
);
fetch_int
();
// expires
fetch_alloc_user
();
free
(
export_auth_str
);
export_auth_str
=
0
;
return
0
;
}
struct
query_methods
import_auth_methods
=
{
.
on_answer
=
import_auth_on_answer
,
.
on_error
=
fail_on_error
};
void
do_import_auth
(
int
num
)
{
clear_packet
();
out_int
(
CODE_auth_import_authorization
);
out_int
(
our_id
);
out_cstring
(
export_auth_str
,
export_auth_str_len
);
send_query
(
DC_list
[
num
],
packet_ptr
-
packet_buffer
,
packet_buffer
,
&
import_auth_methods
,
0
);
net_loop
(
0
,
isn_export_auth_str
);
}
queries.h
View file @
14fd4006
...
...
@@ -84,5 +84,11 @@ struct video;
void
do_load_photo
(
struct
photo
*
photo
,
int
next
);
void
do_load_video_thumb
(
struct
video
*
video
,
int
next
);
void
do_load_video
(
struct
video
*
V
,
int
next
);
void
do_help_get_config
(
void
);
int
do_auth_check_phone
(
const
char
*
user
);
int
do_get_nearest_dc
(
void
);
int
do_send_code_result_auth
(
const
char
*
code
,
const
char
*
first_name
,
const
char
*
last_name
);
void
do_import_auth
(
int
num
);
void
do_export_auth
(
int
num
);
#endif
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