1# Copyright (C) 2016-2019, 2020 Free Software Foundation, Inc.
2#
3# This file is part of DejaGnu.
4#
5# DejaGnu is free software; you can redistribute it and/or modify it
6# under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 3 of the License, or
8# (at your option) any later version.
9#
10# DejaGnu is distributed in the hope that it will be useful, but
11# WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13# General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with DejaGnu; if not, write to the Free Software Foundation,
17# Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
18
19# Connect using ssh(1).
20
21set ssh_initialized "no"
22set ssh_useropts " -o ControlPersist=yes -o ControlMaster=auto -o ControlPath=\"/tmp/ssh-%r@%h:%p\""
23
24# Default to the ssh and scp in the user's path.
25set SSH ssh
26set SCP scp
27
28# Download SRCFILE to DESTFILE on DESTHOST.
29#
30proc ssh_download {desthost srcfile destfile} {
31    global SSH SCP ssh_initialized timeout
32
33    set ssh_port ""
34    set ssh_user ""
35    set ssh_useropts ""
36    set name ""
37    set hostname ""
38
39    if {[board_info $desthost exists scp_prog]} {
40	set SCP [board_info $desthost scp_prog]
41    }
42
43    if {[board_info $desthost exists ssh_prog]} {
44	set SSH [board_info $desthost ssh_prog]
45    }
46
47    # The default user name is the person running the tests
48    if {[board_info $desthost exists username]} {
49	set ssh_user "[board_info $desthost username]@"
50    }
51
52    if {[board_info $desthost exists ssh_opts]} {
53	append ssh_useropts " [board_info $desthost ssh_opts]"
54    }
55
56    # The default SSH port is 22
57    if {[board_info $desthost exists port]} {
58	set ssh_port "[board_info $desthost port]"
59    } else {
60	set ssh_port 22
61    }
62
63    if {[board_info $desthost exists name]} {
64	set name [board_info $desthost name]
65    }
66
67    if {[board_info $desthost exists hostname]} {
68	set hostname [board_info $desthost hostname]
69    } else {
70	set hostname $desthost
71    }
72
73    append ssh_useropts " -o ControlPersist=yes -o ControlMaster=auto -o ControlPath=/tmp/ssh-%r@%h:%p"
74
75    set ret [local_exec "$SCP -P $ssh_port $ssh_useropts $srcfile $ssh_user$hostname:$destfile" "" "" $timeout]
76    set status [lindex $ret 0]
77    set output [lindex $ret 1]
78    if { $status == 0 } {
79	set ssh_initialized "yes"
80	verbose "Copied $srcfile to $desthost:$destfile" 2
81	return $destfile
82    } else {
83	verbose "Download via ssh to $desthost failed."
84	return ""
85    }
86}
87
88proc ssh_upload {desthost srcfile destfile} {
89    global SSH SCP
90
91    if {[board_info $desthost exists scp_prog]} {
92	set SCP [board_info $desthost scp_prog]
93    }
94
95    if {[board_info $desthost exists username]} {
96	set ssh_user "[board_info $desthost username]@"
97    } else {
98	set ssh_user ""
99    }
100
101    if {[board_info $desthost exists name]} {
102	set desthost [board_info $desthost name]
103    }
104
105    if {[board_info $desthost exists hostname]} {
106	set desthost [board_info $desthost hostname]
107    }
108
109    set status [catch "exec $SCP $ssh_user$desthost:$srcfile $destfile" output]
110    if { $status == 0 } {
111	verbose "Copied $desthost:$srcfile to $destfile" 2
112	return $destfile
113    } else {
114	verbose "Upload from $desthost failed, $output."
115	return ""
116    }
117}
118
119# Execute CMD on BOARDNAME.
120#
121proc ssh_exec { boardname program pargs inp outp } {
122    global SSH timeout
123
124    set ssh_port ""
125    set scp_port ""
126    set ssh_user ""
127    set ssh_useropts ""
128    set name ""
129    set hostname ""
130
131    verbose "Executing on $boardname: $program $pargs"
132
133    if {![board_info $boardname exists ssh_prog]} {
134	set SSH ssh
135    } else {
136	set SSH [board_info $boardname ssh_prog]
137    }
138
139    if {[board_info $boardname exists username]} {
140	set ssh_user "[board_info $boardname username]@"
141    } else {
142	set ssh_user ""
143    }
144
145    if {[board_info $boardname exists ssh_useropts]} {
146	append ssh_useropts " [board_info $boardname ssh_opts]"
147    }
148
149    if {[board_info $boardname exists name]} {
150	set boardname [board_info $boardname name]
151    }
152
153    if {[board_info $boardname exists hostname]} {
154	set hostname [board_info $boardname hostname]
155    } else {
156	set hostname $boardname
157    }
158
159    if {[board_info $boardname  exists port]} {
160	append ssh_useropts " -p [board_info $boardname port]"
161    }
162
163    append ssh_useropts " -o ControlPersist=yes -o ControlMaster=auto -o ControlPath=\"/tmp/ssh-%r@%h:%p\""
164
165    # If CMD sends any output to stderr, exec will think it failed.
166    # More often than not that will be true, but it doesn't catch the
167    # case where there is no output but the exit code is non-zero.
168    if { $inp eq "" } {
169	set inp "/dev/null"
170    }
171
172    # We use && here, as otherwise the echo always works, which makes it look
173    # like execution succeeded when in reality it failed.
174    set ret [local_exec "$SSH $ssh_useropts $ssh_user$hostname sh -c '$program $pargs 2>&1 && echo XYZ\\\${?}ZYX \\; rm -f $program'" $inp $outp $timeout]
175    set status [lindex $ret 0]
176    set output [lindex $ret 1]
177
178    verbose "$SSH status is $status, output is $output"
179
180    # `status' doesn't mean much here other than ssh worked ok.
181    # What we want is whether $program ran ok.  Return $status
182    # if the program timed out, status will be 1 indicating that
183    # ssh ran and failed.  If ssh fails, we will get FAIL rather
184    # than UNRESOLVED - this will help the problem be noticed.
185    if { $status != 0 } {
186	regsub "XYZ(\[0-9\]*)ZYX\n?" $output "" output
187	return [list $status "$SSH to $boardname failed for $program, $output"]
188    }
189    if { [regexp "XYZ(\[0-9\]*)ZYX" $output junk status] == 0 } {
190	set status ""
191    }
192    verbose "ssh_exec: status:$status text:$output" 4
193    if { $status eq "" } {
194	return [list -1 "Couldn't parse $SSH output, $output."]
195    }
196    regsub "XYZ(\[0-9\]*)ZYX\n?" $output "" output
197    return [list [expr {$status != 0}] $output]
198}
199
200proc ssh_close { desthost } {
201    global SSH ssh_initialized
202
203    verbose "Closing the SSH connection to $desthost"
204
205    set ssh_port ""
206    set scp_port ""
207    set ssh_user ""
208    set ssh_useropts ""
209    set name ""
210    set hostname ""
211
212    if {[board_info $desthost exists username]} {
213	set ssh_useropts "-l [board_info $desthost username]"
214	set ssh_user "[board_info $desthost username]@"
215    } else {
216	set ssh_user ""
217	set ssh_useropts ""
218    }
219
220    if {[board_info $desthost exists hostname]} {
221	set hostname [board_info $desthost hostname]
222    }
223
224    if {[board_info $desthost exists ssh_opts]} {
225	append ssh_useropts " [board_info $desthost ssh_opts]"
226    }
227
228    if {[board_info $desthost exists port]} {
229	set ssh_port " -p [board_info $desthost port]"
230    } else {
231	set ssh_port ""
232    }
233
234    set args "$ssh_user$hostname $ssh_port"
235
236   # Kill the remote server
237    set status [catch "exec ssh $ssh_port -o ControlPath=/tmp/ssh-%r@%h:%p -O exit $args"]
238    set ssh_initialized "no"
239
240     return ""
241}
242