You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

470 lines
15 KiB

8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
2 years ago
2 years ago
8 years ago
2 years ago
2 years ago
8 years ago
2 years ago
2 years ago
2 years ago
2 years ago
8 years ago
8 years ago
8 years ago
8 years ago
  1. #!/usr/bin/env sh
  2. # Script to deploy certificates to remote server by SSH
  3. # Note that SSH must be able to login to remote host without a password...
  4. # SSH Keys must have been exchanged with the remote host. Validate and
  5. # test that you can login to USER@SERVER from the host running acme.sh before
  6. # using this script.
  7. #
  8. # The following variables exported from environment will be used.
  9. # If not set then values previously saved in domain.conf file are used.
  10. #
  11. # Only a username is required. All others are optional.
  12. #
  13. # The following examples are for QNAP NAS running QTS 4.2
  14. # export DEPLOY_SSH_CMD="" # defaults to "ssh -T"
  15. # export DEPLOY_SSH_USER="admin" # required
  16. # export DEPLOY_SSH_SERVER="host1 host2:8022 192.168.0.1:9022" # defaults to domain name, support multiple servers with optional port
  17. # export DEPLOY_SSH_KEYFILE="/etc/stunnel/stunnel.pem"
  18. # export DEPLOY_SSH_CERTFILE="/etc/stunnel/stunnel.pem"
  19. # export DEPLOY_SSH_CAFILE="/etc/stunnel/uca.pem"
  20. # export DEPLOY_SSH_FULLCHAIN=""
  21. # export DEPLOY_SSH_REMOTE_CMD="/etc/init.d/stunnel.sh restart"
  22. # export DEPLOY_SSH_BACKUP="" # yes or no, default to yes or previously saved value
  23. # export DEPLOY_SSH_BACKUP_PATH=".acme_ssh_deploy" # path on remote system. Defaults to .acme_ssh_deploy
  24. # export DEPLOY_SSH_MULTI_CALL="" # yes or no, default to no or previously saved value
  25. # export DEPLOY_SSH_USE_SCP="" yes or no, default to no
  26. # export DEPLOY_SSH_SCP_CMD="" defaults to "scp -q"
  27. #
  28. ######## Public functions #####################
  29. #domain keyfile certfile cafile fullchain
  30. ssh_deploy() {
  31. _cdomain="$1"
  32. _ckey="$2"
  33. _ccert="$3"
  34. _cca="$4"
  35. _cfullchain="$5"
  36. _deploy_ssh_servers=""
  37. _debug _cdomain "$_cdomain"
  38. _debug _ckey "$_ckey"
  39. _debug _ccert "$_ccert"
  40. _debug _cca "$_cca"
  41. _debug _cfullchain "$_cfullchain"
  42. if ! _ssh_load_config; then
  43. return 1
  44. fi
  45. _deploy_ssh_servers="$DEPLOY_SSH_SERVER"
  46. for DEPLOY_SSH_SERVER in $_deploy_ssh_servers; do
  47. _ssh_deploy
  48. done
  49. }
  50. _ssh_load_config() {
  51. _deploy_ssh_servers=""
  52. # USER is required to login by SSH to remote host.
  53. _migratedeployconf Le_Deploy_ssh_user DEPLOY_SSH_USER
  54. _getdeployconf DEPLOY_SSH_USER
  55. _debug2 DEPLOY_SSH_USER "$DEPLOY_SSH_USER"
  56. if [ -z "$DEPLOY_SSH_USER" ]; then
  57. _err "DEPLOY_SSH_USER not defined."
  58. return 1
  59. fi
  60. _savedeployconf DEPLOY_SSH_USER "$DEPLOY_SSH_USER"
  61. # SERVER is optional. If not provided then use _cdomain
  62. _migratedeployconf Le_Deploy_ssh_server DEPLOY_SSH_SERVER
  63. _getdeployconf DEPLOY_SSH_SERVER
  64. _debug2 DEPLOY_SSH_SERVER "$DEPLOY_SSH_SERVER"
  65. if [ -z "$DEPLOY_SSH_SERVER" ]; then
  66. DEPLOY_SSH_SERVER="$_cdomain"
  67. fi
  68. _savedeployconf DEPLOY_SSH_SERVER "$DEPLOY_SSH_SERVER"
  69. # CMD is optional. If not provided then use ssh
  70. _migratedeployconf Le_Deploy_ssh_cmd DEPLOY_SSH_CMD
  71. _getdeployconf DEPLOY_SSH_CMD
  72. _debug2 DEPLOY_SSH_CMD "$DEPLOY_SSH_CMD"
  73. if [ -z "$DEPLOY_SSH_CMD" ]; then
  74. DEPLOY_SSH_CMD="ssh -T"
  75. fi
  76. _savedeployconf DEPLOY_SSH_CMD "$DEPLOY_SSH_CMD"
  77. # BACKUP is optional. If not provided then default to previously saved value or yes.
  78. _migratedeployconf Le_Deploy_ssh_backup DEPLOY_SSH_BACKUP
  79. _getdeployconf DEPLOY_SSH_BACKUP
  80. _debug2 DEPLOY_SSH_BACKUP "$DEPLOY_SSH_BACKUP"
  81. if [ -z "$DEPLOY_SSH_BACKUP" ]; then
  82. DEPLOY_SSH_BACKUP="yes"
  83. fi
  84. _savedeployconf DEPLOY_SSH_BACKUP "$DEPLOY_SSH_BACKUP"
  85. # BACKUP_PATH is optional. If not provided then default to previously saved value or .acme_ssh_deploy
  86. _migratedeployconf Le_Deploy_ssh_backup_path DEPLOY_SSH_BACKUP_PATH
  87. _getdeployconf DEPLOY_SSH_BACKUP_PATH
  88. _debug2 DEPLOY_SSH_BACKUP_PATH "$DEPLOY_SSH_BACKUP_PATH"
  89. if [ -z "$DEPLOY_SSH_BACKUP_PATH" ]; then
  90. DEPLOY_SSH_BACKUP_PATH=".acme_ssh_deploy"
  91. fi
  92. _savedeployconf DEPLOY_SSH_BACKUP_PATH "$DEPLOY_SSH_BACKUP_PATH"
  93. # MULTI_CALL is optional. If not provided then default to previously saved
  94. # value (which may be undefined... equivalent to "no").
  95. _migratedeployconf Le_Deploy_ssh_multi_call DEPLOY_SSH_MULTI_CALL
  96. _getdeployconf DEPLOY_SSH_MULTI_CALL
  97. _debug2 DEPLOY_SSH_MULTI_CALL "$DEPLOY_SSH_MULTI_CALL"
  98. if [ -z "$DEPLOY_SSH_MULTI_CALL" ]; then
  99. DEPLOY_SSH_MULTI_CALL="no"
  100. fi
  101. _savedeployconf DEPLOY_SSH_MULTI_CALL "$DEPLOY_SSH_MULTI_CALL"
  102. # KEYFILE is optional.
  103. # If provided then private key will be copied to provided filename.
  104. _migratedeployconf Le_Deploy_ssh_keyfile DEPLOY_SSH_KEYFILE
  105. _getdeployconf DEPLOY_SSH_KEYFILE
  106. _debug2 DEPLOY_SSH_KEYFILE "$DEPLOY_SSH_KEYFILE"
  107. if [ -n "$DEPLOY_SSH_KEYFILE" ]; then
  108. _savedeployconf DEPLOY_SSH_KEYFILE "$DEPLOY_SSH_KEYFILE"
  109. fi
  110. # CERTFILE is optional.
  111. # If provided then certificate will be copied or appended to provided filename.
  112. _migratedeployconf Le_Deploy_ssh_certfile DEPLOY_SSH_CERTFILE
  113. _getdeployconf DEPLOY_SSH_CERTFILE
  114. _debug2 DEPLOY_SSH_CERTFILE "$DEPLOY_SSH_CERTFILE"
  115. if [ -n "$DEPLOY_SSH_CERTFILE" ]; then
  116. _savedeployconf DEPLOY_SSH_CERTFILE "$DEPLOY_SSH_CERTFILE"
  117. fi
  118. # CAFILE is optional.
  119. # If provided then CA intermediate certificate will be copied or appended to provided filename.
  120. _migratedeployconf Le_Deploy_ssh_cafile DEPLOY_SSH_CAFILE
  121. _getdeployconf DEPLOY_SSH_CAFILE
  122. _debug2 DEPLOY_SSH_CAFILE "$DEPLOY_SSH_CAFILE"
  123. if [ -n "$DEPLOY_SSH_CAFILE" ]; then
  124. _savedeployconf DEPLOY_SSH_CAFILE "$DEPLOY_SSH_CAFILE"
  125. fi
  126. # FULLCHAIN is optional.
  127. # If provided then fullchain certificate will be copied or appended to provided filename.
  128. _migratedeployconf Le_Deploy_ssh_fullchain DEPLOY_SSH_FULLCHAIN
  129. _getdeployconf DEPLOY_SSH_FULLCHAIN
  130. _debug2 DEPLOY_SSH_FULLCHAIN "$DEPLOY_SSH_FULLCHAIN"
  131. if [ -n "$DEPLOY_SSH_FULLCHAIN" ]; then
  132. _savedeployconf DEPLOY_SSH_FULLCHAIN "$DEPLOY_SSH_FULLCHAIN"
  133. fi
  134. # REMOTE_CMD is optional.
  135. # If provided then this command will be executed on remote host.
  136. _migratedeployconf Le_Deploy_ssh_remote_cmd DEPLOY_SSH_REMOTE_CMD
  137. _getdeployconf DEPLOY_SSH_REMOTE_CMD
  138. _debug2 DEPLOY_SSH_REMOTE_CMD "$DEPLOY_SSH_REMOTE_CMD"
  139. if [ -n "$DEPLOY_SSH_REMOTE_CMD" ]; then
  140. _savedeployconf DEPLOY_SSH_REMOTE_CMD "$DEPLOY_SSH_REMOTE_CMD"
  141. fi
  142. # USE_SCP is optional. If not provided then default to previously saved
  143. # value (which may be undefined... equivalent to "no").
  144. _getdeployconf DEPLOY_SSH_USE_SCP
  145. _debug2 DEPLOY_SSH_USE_SCP "$DEPLOY_SSH_USE_SCP"
  146. if [ -z "$DEPLOY_SSH_USE_SCP" ]; then
  147. DEPLOY_SSH_USE_SCP="no"
  148. fi
  149. _savedeployconf DEPLOY_SSH_USE_SCP "$DEPLOY_SSH_USE_SCP"
  150. # SCP_CMD is optional. If not provided then use scp
  151. _getdeployconf DEPLOY_SSH_SCP_CMD
  152. _debug2 DEPLOY_SSH_SCP_CMD "$DEPLOY_SSH_SCP_CMD"
  153. if [ -z "$DEPLOY_SSH_SCP_CMD" ]; then
  154. DEPLOY_SSH_SCP_CMD="scp -q"
  155. fi
  156. _savedeployconf DEPLOY_SSH_SCP_CMD "$DEPLOY_SSH_SCP_CMD"
  157. if [ "$DEPLOY_SSH_USE_SCP" = "yes" ]; then
  158. DEPLOY_SSH_MULTI_CALL="yes"
  159. _info "Using scp as alternate method for copying files. Multicall Mode is implicit"
  160. elif [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
  161. _info "Using MULTI_CALL mode... Required commands sent in multiple calls to remote host"
  162. else
  163. _info "Required commands batched and sent in single call to remote host"
  164. fi
  165. }
  166. _ssh_deploy() {
  167. _err_code=0
  168. _cmdstr=""
  169. _backupprefix=""
  170. _backupdir=""
  171. _local_cert_file=""
  172. _local_ca_file=""
  173. _local_full_file=""
  174. case $DEPLOY_SSH_SERVER in
  175. *:*)
  176. _host=${DEPLOY_SSH_SERVER%:*}
  177. _port=${DEPLOY_SSH_SERVER##*:}
  178. ;;
  179. *)
  180. _host=$DEPLOY_SSH_SERVER
  181. _port=
  182. ;;
  183. esac
  184. _info "Deploy certificates to remote server $DEPLOY_SSH_USER@$_host:$_port"
  185. if [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then
  186. _backupprefix="$DEPLOY_SSH_BACKUP_PATH/$_cdomain-backup"
  187. _backupdir="$_backupprefix-$(_utc_date | tr ' ' '-')"
  188. # run cleanup on the backup directory, erase all older
  189. # than 180 days (15552000 seconds).
  190. _cmdstr="{ now=\"\$(date -u +%s)\"; for fn in $_backupprefix*; \
  191. do if [ -d \"\$fn\" ] && [ \"\$(expr \$now - \$(date -ur \$fn +%s) )\" -ge \"15552000\" ]; \
  192. then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; done; }; $_cmdstr"
  193. # Alternate version of above... _cmdstr="find $_backupprefix* -type d -mtime +180 2>/dev/null | xargs rm -rf; $_cmdstr"
  194. # Create our backup directory for overwritten cert files.
  195. _cmdstr="mkdir -p $_backupdir; $_cmdstr"
  196. _info "Backup of old certificate files will be placed in remote directory $_backupdir"
  197. _info "Backup directories erased after 180 days."
  198. if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
  199. if ! _ssh_remote_cmd "$_cmdstr"; then
  200. return $_err_code
  201. fi
  202. _cmdstr=""
  203. fi
  204. fi
  205. if [ -n "$DEPLOY_SSH_KEYFILE" ]; then
  206. if [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then
  207. # backup file we are about to overwrite.
  208. _cmdstr="$_cmdstr cp $DEPLOY_SSH_KEYFILE $_backupdir >/dev/null;"
  209. if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
  210. if ! _ssh_remote_cmd "$_cmdstr"; then
  211. return $_err_code
  212. fi
  213. _cmdstr=""
  214. fi
  215. fi
  216. # copy new key into file.
  217. if [ "$DEPLOY_SSH_USE_SCP" = "yes" ]; then
  218. # scp the file
  219. if ! _scp_remote_cmd "$_ckey" "$DEPLOY_SSH_KEYFILE"; then
  220. return $_err_code
  221. fi
  222. else
  223. # ssh echo to the file
  224. _cmdstr="$_cmdstr echo \"$(cat "$_ckey")\" > $DEPLOY_SSH_KEYFILE;"
  225. _info "will copy private key to remote file $DEPLOY_SSH_KEYFILE"
  226. if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
  227. if ! _ssh_remote_cmd "$_cmdstr"; then
  228. return $_err_code
  229. fi
  230. _cmdstr=""
  231. fi
  232. fi
  233. fi
  234. if [ -n "$DEPLOY_SSH_CERTFILE" ]; then
  235. _pipe=">"
  236. if [ "$DEPLOY_SSH_CERTFILE" = "$DEPLOY_SSH_KEYFILE" ]; then
  237. # if filename is same as previous file then append.
  238. _pipe=">>"
  239. elif [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then
  240. # backup file we are about to overwrite.
  241. _cmdstr="$_cmdstr cp $DEPLOY_SSH_CERTFILE $_backupdir >/dev/null;"
  242. if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
  243. if ! _ssh_remote_cmd "$_cmdstr"; then
  244. return $_err_code
  245. fi
  246. _cmdstr=""
  247. fi
  248. fi
  249. # copy new certificate into file.
  250. if [ "$DEPLOY_SSH_USE_SCP" = "yes" ]; then
  251. # scp the file
  252. _local_cert_file=$(_mktemp)
  253. if [ "$DEPLOY_SSH_CERTFILE" = "$DEPLOY_SSH_KEYFILE" ]; then
  254. cat "$_ckey" >>"$_local_cert_file"
  255. fi
  256. cat "$_ccert" >>"$_local_cert_file"
  257. if ! _scp_remote_cmd "$_local_cert_file" "$DEPLOY_SSH_CERTFILE"; then
  258. return $_err_code
  259. fi
  260. else
  261. # ssh echo to the file
  262. _cmdstr="$_cmdstr echo \"$(cat "$_ccert")\" $_pipe $DEPLOY_SSH_CERTFILE;"
  263. _info "will copy certificate to remote file $DEPLOY_SSH_CERTFILE"
  264. if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
  265. if ! _ssh_remote_cmd "$_cmdstr"; then
  266. return $_err_code
  267. fi
  268. _cmdstr=""
  269. fi
  270. fi
  271. fi
  272. if [ -n "$DEPLOY_SSH_CAFILE" ]; then
  273. _pipe=">"
  274. if [ "$DEPLOY_SSH_CAFILE" = "$DEPLOY_SSH_KEYFILE" ] ||
  275. [ "$DEPLOY_SSH_CAFILE" = "$DEPLOY_SSH_CERTFILE" ]; then
  276. # if filename is same as previous file then append.
  277. _pipe=">>"
  278. elif [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then
  279. # backup file we are about to overwrite.
  280. _cmdstr="$_cmdstr cp $DEPLOY_SSH_CAFILE $_backupdir >/dev/null;"
  281. if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
  282. if ! _ssh_remote_cmd "$_cmdstr"; then
  283. return $_err_code
  284. fi
  285. _cmdstr=""
  286. fi
  287. fi
  288. # copy new certificate into file.
  289. if [ "$DEPLOY_SSH_USE_SCP" = "yes" ]; then
  290. # scp the file
  291. _local_ca_file=$(_mktemp)
  292. if [ "$DEPLOY_SSH_CAFILE" = "$DEPLOY_SSH_KEYFILE" ]; then
  293. cat "$_ckey" >>"$_local_ca_file"
  294. fi
  295. if [ "$DEPLOY_SSH_CAFILE" = "$DEPLOY_SSH_CERTFILE" ]; then
  296. cat "$_ccert" >>"$_local_ca_file"
  297. fi
  298. cat "$_cca" >>"$_local_ca_file"
  299. if ! _scp_remote_cmd "$_local_ca_file" "$DEPLOY_SSH_CAFILE"; then
  300. return $_err_code
  301. fi
  302. else
  303. # ssh echo to the file
  304. _cmdstr="$_cmdstr echo \"$(cat "$_cca")\" $_pipe $DEPLOY_SSH_CAFILE;"
  305. _info "will copy CA file to remote file $DEPLOY_SSH_CAFILE"
  306. if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
  307. if ! _ssh_remote_cmd "$_cmdstr"; then
  308. return $_err_code
  309. fi
  310. _cmdstr=""
  311. fi
  312. fi
  313. fi
  314. if [ -n "$DEPLOY_SSH_FULLCHAIN" ]; then
  315. _pipe=">"
  316. if [ "$DEPLOY_SSH_FULLCHAIN" = "$DEPLOY_SSH_KEYFILE" ] ||
  317. [ "$DEPLOY_SSH_FULLCHAIN" = "$DEPLOY_SSH_CERTFILE" ] ||
  318. [ "$DEPLOY_SSH_FULLCHAIN" = "$DEPLOY_SSH_CAFILE" ]; then
  319. # if filename is same as previous file then append.
  320. _pipe=">>"
  321. elif [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then
  322. # backup file we are about to overwrite.
  323. _cmdstr="$_cmdstr cp $DEPLOY_SSH_FULLCHAIN $_backupdir >/dev/null;"
  324. if [ "$DEPLOY_SSH_FULLCHAIN" = "yes" ]; then
  325. if ! _ssh_remote_cmd "$_cmdstr"; then
  326. return $_err_code
  327. fi
  328. _cmdstr=""
  329. fi
  330. fi
  331. # copy new certificate into file.
  332. if [ "$DEPLOY_SSH_USE_SCP" = "yes" ]; then
  333. # scp the file
  334. _local_full_file=$(_mktemp)
  335. if [ "$DEPLOY_SSH_FULLCHAIN" = "$DEPLOY_SSH_KEYFILE" ]; then
  336. cat "$_ckey" >>"$_local_full_file"
  337. fi
  338. if [ "$DEPLOY_SSH_FULLCHAIN" = "$DEPLOY_SSH_CERTFILE" ]; then
  339. cat "$_ccert" >>"$_local_full_file"
  340. fi
  341. if [ "$DEPLOY_SSH_FULLCHAIN" = "$DEPLOY_SSH_CAFILE" ]; then
  342. cat "$_cca" >>"$_local_full_file"
  343. fi
  344. cat "$_cfullchain" >>"$_local_full_file"
  345. if ! _scp_remote_cmd "$_local_full_file" "$DEPLOY_SSH_FULLCHAIN"; then
  346. return $_err_code
  347. fi
  348. else
  349. # ssh echo to the file
  350. _cmdstr="$_cmdstr echo \"$(cat "$_cfullchain")\" $_pipe $DEPLOY_SSH_FULLCHAIN;"
  351. _info "will copy fullchain to remote file $DEPLOY_SSH_FULLCHAIN"
  352. if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
  353. if ! _ssh_remote_cmd "$_cmdstr"; then
  354. return $_err_code
  355. fi
  356. _cmdstr=""
  357. fi
  358. fi
  359. fi
  360. # cleanup local files if any
  361. if [ -f "$_local_cert_file" ]; then
  362. rm -f "$_local_cert_file"
  363. fi
  364. if [ -f "$_local_ca_file" ]; then
  365. rm -f "$_local_ca_file"
  366. fi
  367. if [ -f "$_local_full_file" ]; then
  368. rm -f "$_local_full_file"
  369. fi
  370. if [ -n "$DEPLOY_SSH_REMOTE_CMD" ]; then
  371. _cmdstr="$_cmdstr $DEPLOY_SSH_REMOTE_CMD;"
  372. _info "Will execute remote command $DEPLOY_SSH_REMOTE_CMD"
  373. if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
  374. if ! _ssh_remote_cmd "$_cmdstr"; then
  375. return $_err_code
  376. fi
  377. _cmdstr=""
  378. fi
  379. fi
  380. # if commands not all sent in multiple calls then all commands sent in a single SSH call now...
  381. if [ -n "$_cmdstr" ]; then
  382. if ! _ssh_remote_cmd "$_cmdstr"; then
  383. return $_err_code
  384. fi
  385. fi
  386. # cleanup in case all is ok
  387. return 0
  388. }
  389. #cmd
  390. _ssh_remote_cmd() {
  391. _cmd="$1"
  392. _ssh_cmd="$DEPLOY_SSH_CMD"
  393. if [ -n "$_port" ]; then
  394. _ssh_cmd="$_ssh_cmd -p $_port"
  395. fi
  396. _secure_debug "Remote commands to execute: $_cmd"
  397. _info "Submitting sequence of commands to remote server by $_ssh_cmd"
  398. # quotations in bash cmd below intended. Squash travis spellcheck error
  399. # shellcheck disable=SC2029
  400. $_ssh_cmd "$DEPLOY_SSH_USER@$_host" sh -c "'$_cmd'"
  401. _err_code="$?"
  402. if [ "$_err_code" != "0" ]; then
  403. _err "Error code $_err_code returned from ssh"
  404. fi
  405. return $_err_code
  406. }
  407. # cmd scp
  408. _scp_remote_cmd() {
  409. _src=$1
  410. _dest=$2
  411. _scp_cmd="$DEPLOY_SSH_SCP_CMD"
  412. if [ -n "$_port" ]; then
  413. _scp_cmd="$_scp_cmd -P $_port"
  414. fi
  415. _secure_debug "Remote copy source $_src to destination $_dest"
  416. _info "Submitting secure copy by $_scp_cmd"
  417. $_scp_cmd "$_src" "$DEPLOY_SSH_USER"@"$_host":"$_dest"
  418. _err_code="$?"
  419. if [ "$_err_code" != "0" ]; then
  420. _err "Error code $_err_code returned from scp"
  421. fi
  422. return $_err_code
  423. }