Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
ruanhaishen
Acme.Sh
Commits
32902087
Unverified
Commit
32902087
authored
Jun 25, 2021
by
neil
Committed by
GitHub
Jun 25, 2021
Browse files
Merge pull request #3573 from acmesh-official/dev
Dev
parents
7e43c794
3106187a
Changes
14
Hide whitespace changes
Inline
Side-by-side
.github/workflows/FreeBSD.yml
View file @
32902087
...
...
@@ -5,14 +5,14 @@ on:
-
'
*'
paths
:
-
'
*.sh'
-
'
**
.yml'
-
'
.github/workflows/FreeBSD
.yml'
pull_request
:
branches
:
-
dev
paths
:
-
'
*.sh'
-
'
**
.yml'
-
'
.github/workflows/FreeBSD
.yml'
jobs
:
...
...
.github/workflows/Linux.yml
View file @
32902087
...
...
@@ -5,14 +5,14 @@ on:
-
'
*'
paths
:
-
'
*.sh'
-
'
**
.yml'
-
'
.github/workflows/Linux
.yml'
pull_request
:
branches
:
-
dev
paths
:
-
'
*.sh'
-
'
**
.yml'
-
'
.github/workflows/Linux
.yml'
...
...
.github/workflows/MacOS.yml
View file @
32902087
...
...
@@ -5,14 +5,14 @@ on:
-
'
*'
paths
:
-
'
*.sh'
-
'
**
.yml'
-
'
.github/workflows/MacOS
.yml'
pull_request
:
branches
:
-
dev
paths
:
-
'
*.sh'
-
'
**
.yml'
-
'
.github/workflows/MacOS
.yml'
jobs
:
...
...
.github/workflows/PebbleStrict.yml
View file @
32902087
...
...
@@ -5,13 +5,13 @@ on:
-
'
*'
paths
:
-
'
*.sh'
-
'
**
.yml'
-
'
.github/workflows/PebbleStrict
.yml'
pull_request
:
branches
:
-
dev
paths
:
-
'
*.sh'
-
'
**
.yml'
-
'
.github/workflows/PebbleStrict
.yml'
jobs
:
PebbleStrict
:
...
...
.github/workflows/Solaris.yml
View file @
32902087
...
...
@@ -5,14 +5,14 @@ on:
-
'
*'
paths
:
-
'
*.sh'
-
'
**
.yml'
-
'
.github/workflows/Solaris
.yml'
pull_request
:
branches
:
-
dev
paths
:
-
'
*.sh'
-
'
**
.yml'
-
'
.github/workflows/Solaris
.yml'
jobs
:
...
...
.github/workflows/Ubuntu.yml
View file @
32902087
...
...
@@ -5,14 +5,14 @@ on:
-
'
*'
paths
:
-
'
*.sh'
-
'
**
.yml'
-
'
.github/workflows/Ubuntu
.yml'
pull_request
:
branches
:
-
dev
paths
:
-
'
*.sh'
-
'
**
.yml'
-
'
.github/workflows/Ubuntu
.yml'
jobs
:
...
...
@@ -28,6 +28,7 @@ jobs:
CA_ECDSA
:
"
ZeroSSL
ECC
Domain
Secure
Site
CA"
CA
:
"
ZeroSSL
RSA
Domain
Secure
Site
CA"
CA_EMAIL
:
"
githubtest@acme.sh"
runs-on
:
ubuntu-latest
env
:
TEST_LOCAL
:
1
...
...
@@ -35,6 +36,7 @@ jobs:
CA_ECDSA
:
${{ matrix.CA_ECDSA }}
CA
:
${{ matrix.CA }}
CA_EMAIL
:
${{ matrix.CA_EMAIL }}
NO_ECC_384
:
${{ matrix.NO_ECC_384 }}
steps
:
-
uses
:
actions/checkout@v2
-
name
:
Install tools
...
...
.github/workflows/Windows.yml
View file @
32902087
...
...
@@ -5,14 +5,14 @@ on:
-
'
*'
paths
:
-
'
*.sh'
-
'
**
.yml'
-
'
.github/workflows/Windows
.yml'
pull_request
:
branches
:
-
dev
paths
:
-
'
*.sh'
-
'
**
.yml'
-
'
.github/workflows/Windows
.yml'
jobs
:
...
...
.github/workflows/dockerhub.yml
View file @
32902087
...
...
@@ -6,6 +6,11 @@ on:
-
'
*'
tags
:
-
'
*'
paths
:
-
'
**.sh'
-
"
Dockerfile"
-
'
.github/workflows/dockerhub.yml'
jobs
:
CheckToken
:
...
...
.github/workflows/shellcheck.yml
View file @
32902087
...
...
@@ -5,13 +5,13 @@ on:
-
'
*'
paths
:
-
'
**.sh'
-
'
**
.yml'
-
'
.github/workflows/shellcheck
.yml'
pull_request
:
branches
:
-
dev
paths
:
-
'
**.sh'
-
'
**
.yml'
-
'
.github/workflows/shellcheck
.yml'
jobs
:
ShellCheck
:
...
...
acme.sh
View file @
32902087
...
...
@@ -2540,12 +2540,18 @@ _initAPI() {
_api_server
=
"
${
1
:-
$ACME_DIRECTORY
}
"
_debug
"_init api for server:
$_api_server
"
if
[
-z
"
$ACME_NEW_ACCOUNT
"
]
;
then
MAX_API_RETRY_TIMES
=
10
_sleep_retry_sec
=
10
_request_retry_times
=
0
while
[
-z
"
$ACME_NEW_ACCOUNT
"
]
&&
[
"
${
_request_retry_times
}
"
-lt
"
$MAX_API_RETRY_TIMES
"
]
;
do
_request_retry_times
=
$(
_math
"
$_request_retry_times
"
+ 1
)
response
=
$(
_get
"
$_api_server
"
)
if
[
"
$?
"
!=
"0"
]
;
then
_debug2
"response"
"
$response
"
_err
"Can not init api for:
$_api_server
."
return
1
_info
"Can not init api for:
$_api_server
."
_info
"Sleep
$_sleep_retry_sec
and retry."
_sleep
"
$_sleep_retry_sec
"
continue
fi
response
=
$(
echo
"
$response
"
| _json_decode
)
_debug2
"response"
"
$response
"
...
...
@@ -2578,8 +2584,14 @@ _initAPI() {
_debug
"ACME_REVOKE_CERT"
"
$ACME_REVOKE_CERT
"
_debug
"ACME_AGREEMENT"
"
$ACME_AGREEMENT
"
_debug
"ACME_NEW_NONCE"
"
$ACME_NEW_NONCE
"
fi
if
[
"
$ACME_NEW_ACCOUNT
"
]
&&
[
"
$ACME_NEW_ORDER
"
]
;
then
return
0
fi
_info
"Sleep
$_sleep_retry_sec
and retry."
_sleep
"
$_sleep_retry_sec
"
done
_err
"Can not init api, for
$_api_server
"
return
1
}
#[domain] [keylength or isEcc flag]
...
...
deploy/consul.sh
0 → 100644
View file @
32902087
#!/usr/bin/env sh
# Here is a script to deploy cert to hashicorp consul using curl
# (https://www.consul.io/)
#
# it requires following environment variables:
#
# CONSUL_PREFIX - this contains the prefix path in consul
# CONSUL_HTTP_ADDR - consul requires this to find your consul server
#
# additionally, you need to ensure that CONSUL_HTTP_TOKEN is available
# to access the consul server
#returns 0 means success, otherwise error.
######## Public functions #####################
#domain keyfile certfile cafile fullchain
consul_deploy
()
{
_cdomain
=
"
$1
"
_ckey
=
"
$2
"
_ccert
=
"
$3
"
_cca
=
"
$4
"
_cfullchain
=
"
$5
"
_debug _cdomain
"
$_cdomain
"
_debug _ckey
"
$_ckey
"
_debug _ccert
"
$_ccert
"
_debug _cca
"
$_cca
"
_debug _cfullchain
"
$_cfullchain
"
# validate required env vars
_getdeployconf CONSUL_PREFIX
if
[
-z
"
$CONSUL_PREFIX
"
]
;
then
_err
"CONSUL_PREFIX needs to be defined (contains prefix path in vault)"
return
1
fi
_savedeployconf CONSUL_PREFIX
"
$CONSUL_PREFIX
"
_getdeployconf CONSUL_HTTP_ADDR
if
[
-z
"
$CONSUL_HTTP_ADDR
"
]
;
then
_err
"CONSUL_HTTP_ADDR needs to be defined (contains consul connection address)"
return
1
fi
_savedeployconf CONSUL_HTTP_ADDR
"
$CONSUL_HTTP_ADDR
"
CONSUL_CMD
=
$(
command
-v
consul
)
# force CLI, but the binary does not exist => error
if
[
-n
"
$USE_CLI
"
]
&&
[
-z
"
$CONSUL_CMD
"
]
;
then
_err
"Cannot find the consul binary!"
return
1
fi
# use the CLI first
if
[
-n
"
$USE_CLI
"
]
||
[
-n
"
$CONSUL_CMD
"
]
;
then
_info
"Found consul binary, deploying with CLI"
consul_deploy_cli
"
$CONSUL_CMD
"
"
$CONSUL_PREFIX
"
else
_info
"Did not find consul binary, deploying with API"
consul_deploy_api
"
$CONSUL_HTTP_ADDR
"
"
$CONSUL_PREFIX
"
"
$CONSUL_HTTP_TOKEN
"
fi
}
consul_deploy_api
()
{
CONSUL_HTTP_ADDR
=
"
$1
"
CONSUL_PREFIX
=
"
$2
"
CONSUL_HTTP_TOKEN
=
"
$3
"
URL
=
"
$CONSUL_HTTP_ADDR
/v1/kv/
$CONSUL_PREFIX
"
export
_H1
=
"X-Consul-Token:
$CONSUL_HTTP_TOKEN
"
if
[
-n
"
$FABIO
"
]
;
then
_post
"
$(
cat
"
$_cfullchain
"
)
"
"
$URL
/
${
_cdomain
}
-cert.pem"
''
"PUT"
||
return
1
_post
"
$(
cat
"
$_ckey
"
)
"
"
$URL
/
${
_cdomain
}
-key.pem"
''
"PUT"
||
return
1
else
_post
"
$(
cat
"
$_ccert
"
)
"
"
$URL
/
${
_cdomain
}
/cert.pem"
''
"PUT"
||
return
1
_post
"
$(
cat
"
$_ckey
"
)
"
"
$URL
/
${
_cdomain
}
/cert.key"
''
"PUT"
||
return
1
_post
"
$(
cat
"
$_cca
"
)
"
"
$URL
/
${
_cdomain
}
/chain.pem"
''
"PUT"
||
return
1
_post
"
$(
cat
"
$_cfullchain
"
)
"
"
$URL
/
${
_cdomain
}
/fullchain.pem"
''
"PUT"
||
return
1
fi
}
consul_deploy_cli
()
{
CONSUL_CMD
=
"
$1
"
CONSUL_PREFIX
=
"
$2
"
if
[
-n
"
$FABIO
"
]
;
then
$CONSUL_CMD
kv put
"
${
CONSUL_PREFIX
}
/
${
_cdomain
}
-cert.pem"
@
"
$_cfullchain
"
||
return
1
$CONSUL_CMD
kv put
"
${
CONSUL_PREFIX
}
/
${
_cdomain
}
-key.pem"
@
"
$_ckey
"
||
return
1
else
$CONSUL_CMD
kv put
"
${
CONSUL_PREFIX
}
/
${
_cdomain
}
/cert.pem"
value
=
@
"
$_ccert
"
||
return
1
$CONSUL_CMD
kv put
"
${
CONSUL_PREFIX
}
/
${
_cdomain
}
/cert.key"
value
=
@
"
$_ckey
"
||
return
1
$CONSUL_CMD
kv put
"
${
CONSUL_PREFIX
}
/
${
_cdomain
}
/chain.pem"
value
=
@
"
$_cca
"
||
return
1
$CONSUL_CMD
kv put
"
${
CONSUL_PREFIX
}
/
${
_cdomain
}
/fullchain.pem"
value
=
@
"
$_cfullchain
"
||
return
1
fi
}
dnsapi/dns_azion.sh
0 → 100644
View file @
32902087
#!/usr/bin/env sh
#
#AZION_Email=""
#AZION_Password=""
#
AZION_Api
=
"https://api.azionapi.net"
######## Public functions ########
# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
# Used to add txt record
dns_azion_add
()
{
fulldomain
=
$1
txtvalue
=
$2
_debug
"Detect the root zone"
if
!
_get_root
"
$fulldomain
"
;
then
_err
"Domain not found"
return
1
fi
_debug _sub_domain
"
$_sub_domain
"
_debug _domain
"
$_domain
"
_debug _domain_id
"
$_domain_id
"
_info
"Add or update record"
_get_record
"
$_domain_id
"
"
$_sub_domain
"
if
[
"
$record_id
"
]
;
then
_payload
=
"{
\"
record_type
\"
:
\"
TXT
\"
,
\"
entry
\"
:
\"
$_sub_domain
\"
,
\"
answers_list
\"
: [
$answers_list
,
\"
$txtvalue
\"
],
\"
ttl
\"
: 20}"
if
_azion_rest PUT
"intelligent_dns/
$_domain_id
/records/
$record_id
"
"
$_payload
"
;
then
if
_contains
"
$response
"
"
$txtvalue
"
;
then
_info
"Record updated."
return
0
fi
fi
else
_payload
=
"{
\"
record_type
\"
:
\"
TXT
\"
,
\"
entry
\"
:
\"
$_sub_domain
\"
,
\"
answers_list
\"
: [
\"
$txtvalue
\"
],
\"
ttl
\"
: 20}"
if
_azion_rest POST
"intelligent_dns/
$_domain_id
/records"
"
$_payload
"
;
then
if
_contains
"
$response
"
"
$txtvalue
"
;
then
_info
"Record added."
return
0
fi
fi
fi
_err
"Failed to add or update record."
return
1
}
# Usage: fulldomain txtvalue
# Used to remove the txt record after validation
dns_azion_rm
()
{
fulldomain
=
$1
txtvalue
=
$2
_debug
"Detect the root zone"
if
!
_get_root
"
$fulldomain
"
;
then
_err
"Domain not found"
return
1
fi
_debug _sub_domain
"
$_sub_domain
"
_debug _domain
"
$_domain
"
_debug _domain_id
"
$_domain_id
"
_info
"Removing record"
_get_record
"
$_domain_id
"
"
$_sub_domain
"
if
[
"
$record_id
"
]
;
then
if
_azion_rest DELETE
"intelligent_dns/
$_domain_id
/records/
$record_id
"
;
then
_info
"Record removed."
return
0
else
_err
"Failed to remove record."
return
1
fi
else
_info
"Record not found or already removed."
return
0
fi
}
#################### Private functions below ##################################
# Usage: _acme-challenge.www.domain.com
# returns
# _sub_domain=_acme-challenge.www
# _domain=domain.com
# _domain_id=sdjkglgdfewsdfg
_get_root
()
{
domain
=
$1
i
=
1
p
=
1
if
!
_azion_rest GET
"intelligent_dns"
;
then
return
1
fi
while
true
;
do
h
=
$(
printf
"%s"
"
$domain
"
|
cut
-d
.
-f
$i
-100
)
_debug h
"
$h
"
if
[
-z
"
$h
"
]
;
then
# not valid
return
1
fi
if
_contains
"
$response
"
"
\"
domain
\"
:
\"
$h
\"
"
;
then
_domain_id
=
$(
echo
"
$response
"
|
tr
'{'
"
\n
"
|
grep
"
\"
domain
\"
:
\"
$h
\"
"
| _egrep_o
"
\"
id
\"
:[0-9]*"
| _head_n 1 |
cut
-d
:
-f
2 |
tr
-d
\"
)
_debug _domain_id
"
$_domain_id
"
if
[
"
$_domain_id
"
]
;
then
_sub_domain
=
$(
printf
"%s"
"
$domain
"
|
cut
-d
.
-f
1-
$p
)
_domain
=
$h
return
0
fi
return
1
fi
p
=
$i
i
=
$(
_math
"
$i
"
+ 1
)
done
return
1
}
_get_record
()
{
_domain_id
=
$1
_record
=
$2
if
!
_azion_rest GET
"intelligent_dns/
$_domain_id
/records"
;
then
return
1
fi
if
_contains
"
$response
"
"
\"
entry
\"
:
\"
$_record
\"
"
;
then
_json_record
=
$(
echo
"
$response
"
|
tr
'{'
"
\n
"
|
grep
"
\"
entry
\"
:
\"
$_record
\"
"
)
if
[
"
$_json_record
"
]
;
then
record_id
=
$(
echo
"
$_json_record
"
| _egrep_o
"
\"
record_id
\"
:[0-9]*"
| _head_n 1 |
cut
-d
:
-f
2 |
tr
-d
\"
)
answers_list
=
$(
echo
"
$_json_record
"
| _egrep_o
"
\"
answers_list
\"
:
\[
.*
\]
"
| _head_n 1 |
cut
-d
:
-f
2 |
tr
-d
\[\]
)
return
0
fi
return
1
fi
return
1
}
_get_token
()
{
AZION_Email
=
"
${
AZION_Email
:-
$(
_readaccountconf_mutable AZION_Email
)
}
"
AZION_Password
=
"
${
AZION_Password
:-
$(
_readaccountconf_mutable AZION_Password
)
}
"
if
!
_contains
"
$AZION_Email
"
"@"
;
then
_err
"It seems that the AZION_Email is not a valid email address. Revalidate your environments."
return
1
fi
if
[
-z
"
$AZION_Email
"
]
||
[
-z
"
$AZION_Password
"
]
;
then
_err
"You didn't specified a AZION_Email/AZION_Password to generate Azion token."
return
1
fi
_saveaccountconf_mutable AZION_Email
"
$AZION_Email
"
_saveaccountconf_mutable AZION_Password
"
$AZION_Password
"
_basic_auth
=
$(
printf
"%s:%s"
"
$AZION_Email
"
"
$AZION_Password
"
| _base64
)
_debug _basic_auth
"
$_basic_auth
"
export
_H1
=
"Accept: application/json; version=3"
export
_H2
=
"Content-Type: application/json"
export
_H3
=
"Authorization: Basic
$_basic_auth
"
response
=
"
$(
_post
""
"
$AZION_Api
/tokens"
""
"POST"
)
"
if
_contains
"
$response
"
"
\"
token
\"
:
\"
"
>
/dev/null
;
then
_azion_token
=
$(
echo
"
$response
"
| _egrep_o
"
\"
token
\"
:
\"
[^
\"
]*
\"
"
|
cut
-d
:
-f
2 |
tr
-d
\"
)
export
AZION_Token
=
"
$_azion_token
"
else
_err
"Failed to generate Azion token"
return
1
fi
}
_azion_rest
()
{
_method
=
$1
_uri
=
"
$2
"
_data
=
"
$3
"
if
[
-z
"
$AZION_Token
"
]
;
then
_get_token
fi
_debug2 token
"
$AZION_Token
"
export
_H1
=
"Accept: application/json; version=3"
export
_H2
=
"Content-Type: application/json"
export
_H3
=
"Authorization: token
$AZION_Token
"
if
[
"
$_method
"
!=
"GET"
]
;
then
_debug _data
"
$_data
"
response
=
"
$(
_post
"
$_data
"
"
$AZION_Api
/
$_uri
"
""
"
$_method
"
)
"
else
response
=
"
$(
_get
"
$AZION_Api
/
$_uri
"
)
"
fi
_debug2 response
"
$response
"
if
[
"
$?
"
!=
"0"
]
;
then
_err
"error
$_method
$_uri
$_data
"
return
1
fi
return
0
}
dnsapi/dns_pdns.sh
View file @
32902087
...
...
@@ -103,7 +103,7 @@ set_record() {
_build_record_string
"
$oldchallenge
"
done
if
!
_pdns_rest
"PATCH"
"/api/v1/servers/
$PDNS_ServerId
/zones/
$root
"
"{
\"
rrsets
\"
: [{
\"
changetype
\"
:
\"
REPLACE
\"
,
\"
name
\"
:
\"
$full
.
\"
,
\"
type
\"
:
\"
TXT
\"
,
\"
ttl
\"
:
$PDNS_Ttl
,
\"
records
\"
: [
$_record_string
]}]}"
;
then
if
!
_pdns_rest
"PATCH"
"/api/v1/servers/
$PDNS_ServerId
/zones/
$root
"
"{
\"
rrsets
\"
: [{
\"
changetype
\"
:
\"
REPLACE
\"
,
\"
name
\"
:
\"
$full
.
\"
,
\"
type
\"
:
\"
TXT
\"
,
\"
ttl
\"
:
$PDNS_Ttl
,
\"
records
\"
: [
$_record_string
]}]}"
"application/json"
;
then
_err
"Set txt record error."
return
1
fi
...
...
@@ -126,7 +126,7 @@ rm_record() {
if
_contains
"
$_existing_challenges
"
"
$txtvalue
"
;
then
#Delete all challenges (PowerDNS API does not allow to delete content)
if
!
_pdns_rest
"PATCH"
"/api/v1/servers/
$PDNS_ServerId
/zones/
$root
"
"{
\"
rrsets
\"
: [{
\"
changetype
\"
:
\"
DELETE
\"
,
\"
name
\"
:
\"
$full
.
\"
,
\"
type
\"
:
\"
TXT
\"
}]}"
;
then
if
!
_pdns_rest
"PATCH"
"/api/v1/servers/
$PDNS_ServerId
/zones/
$root
"
"{
\"
rrsets
\"
: [{
\"
changetype
\"
:
\"
DELETE
\"
,
\"
name
\"
:
\"
$full
.
\"
,
\"
type
\"
:
\"
TXT
\"
}]}"
"application/json"
;
then
_err
"Delete txt record error."
return
1
fi
...
...
@@ -140,7 +140,7 @@ rm_record() {
fi
done
#Recreate the existing challenges
if
!
_pdns_rest
"PATCH"
"/api/v1/servers/
$PDNS_ServerId
/zones/
$root
"
"{
\"
rrsets
\"
: [{
\"
changetype
\"
:
\"
REPLACE
\"
,
\"
name
\"
:
\"
$full
.
\"
,
\"
type
\"
:
\"
TXT
\"
,
\"
ttl
\"
:
$PDNS_Ttl
,
\"
records
\"
: [
$_record_string
]}]}"
;
then
if
!
_pdns_rest
"PATCH"
"/api/v1/servers/
$PDNS_ServerId
/zones/
$root
"
"{
\"
rrsets
\"
: [{
\"
changetype
\"
:
\"
REPLACE
\"
,
\"
name
\"
:
\"
$full
.
\"
,
\"
type
\"
:
\"
TXT
\"
,
\"
ttl
\"
:
$PDNS_Ttl
,
\"
records
\"
: [
$_record_string
]}]}"
"application/json"
;
then
_err
"Set txt record error."
return
1
fi
...
...
@@ -203,12 +203,13 @@ _pdns_rest() {
method
=
$1
ep
=
$2
data
=
$3
ct
=
$4
export
_H1
=
"X-API-Key:
$PDNS_Token
"
if
[
!
"
$method
"
=
"GET"
]
;
then
_debug data
"
$data
"
response
=
"
$(
_post
"
$data
"
"
$PDNS_Url$ep
"
""
"
$method
"
)
"
response
=
"
$(
_post
"
$data
"
"
$PDNS_Url$ep
"
""
"
$method
"
"
$ct
"
)
"
else
response
=
"
$(
_get
"
$PDNS_Url$ep
"
)
"
fi
...
...
notify/telegram.sh
View file @
32902087
...
...
@@ -27,7 +27,7 @@ telegram_send() {
fi
_saveaccountconf_mutable TELEGRAM_BOT_CHATID
"
$TELEGRAM_BOT_CHATID
"
_content
=
"
$(
printf
"%s"
"
$_content
"
|
sed
-e
's/
*/
\\\\
*/
'
)
"
_content
=
"
$(
printf
"%s"
"
$_content
"
|
sed
-e
's/
\([_*`\[]\)/\
\\\\
1/g
'
)
"
_content
=
"
$(
printf
"*%s*
\n
%s"
"
$_subject
"
"
$_content
"
| _json_encode
)
"
_data
=
"{
\"
text
\"
:
\"
$_content
\"
, "
_data
=
"
$_data
\"
chat_id
\"
:
\"
$TELEGRAM_BOT_CHATID
\"
, "
...
...
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