Skip to content

Commit 2134d5d

Browse files
committed
.
1 parent 82b2f1d commit 2134d5d

23 files changed

+1635
-61
lines changed

app/notes.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
* sftp-server(8): add a "[email protected]" extension
2+
request that allows the client to obtain user/group names that
3+
correspond to a set of uids/gids.
4+
5+
* sftp(1): use "[email protected]" sftp-server
6+
extension (when available) to fill in user/group names for
7+
directory listings.
8+
9+
* sftp-server(8): support the "home-directory" extension request
10+
defined in draft-ietf-secsh-filexfer-extensions-00. This overlaps
11+
a bit with the existing "[email protected]", but some other
12+
clients support it.
13+
14+
/usr/libexec/sftp-server
15+
16+
https://bugzilla.mindrot.org/show_bug.cgi?id=2948
17+
18+
19+
https://www.openssh.com/releasenotes.html
20+
https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-extensions-00
21+
https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-02

app/src/main/java/muon/dto/session/SessionInfo.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public class SessionInfo extends NamedItem {
2626
private transient String osName;
2727
private transient String osType;
2828
private transient Boolean shellAccess;
29+
private transient boolean loginAsSudo;
2930

3031
public int getAuthMode() {
3132
return authMode;
@@ -356,4 +357,11 @@ public void setShellAccess(Boolean shellAccess) {
356357
this.shellAccess = shellAccess;
357358
}
358359

360+
public boolean isLoginAsSudo() {
361+
return loginAsSudo;
362+
}
363+
364+
public void setLoginAsSudo(boolean loginAsSudo) {
365+
this.loginAsSudo = loginAsSudo;
366+
}
359367
}

app/src/main/java/muon/misc/ExtendedRemoteDirectory.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
*/
44
package muon.misc;
55

6+
7+
import muon.ssh.RemoteDirectory;
8+
import muon.ssh.SFTPEngine;
69
import net.schmizz.sshj.sftp.*;
7-
import net.schmizz.sshj.sftp.Response.StatusCode;
810

911
import java.io.IOException;
1012
import java.util.ArrayList;
@@ -59,7 +61,7 @@ public List<RemoteResourceInfoWrapper> scanExtended(
5961
break;
6062

6163
case STATUS:
62-
res.ensureStatusIs(StatusCode.EOF);
64+
res.ensureStatusIs(Response.StatusCode.EOF);
6365
break loop;
6466

6567
default:

app/src/main/java/muon/screens/appwin/filebrowser/FolderViewTableCellRenderer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,6 @@ public Component getTableCellRendererComponent(JTable table,
139139
public int calculateRowHeight() {
140140
iconLbl.setIcon(folderIcon);
141141
label1.setText("some text");
142-
return p1.getPreferredSize().height;
142+
return Math.max(p1.getPreferredSize().height, 30);
143143
}
144144
}

app/src/main/java/muon/screens/appwin/filebrowser/sftp/SftpOperations.java

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public void newOrRename(Action action) {
8080
if (retryWithSudo.get()) {
8181
var ret = SudoUtils.exec(fileBrowserView.getSessionInfo(), String.format("mv -f '%s' '%s'",
8282
fileBrowserView.getSelectedFiles().get(0).getPath(),
83-
PathUtils.combineUnix(fileBrowserView.getPath(), finalFileName)), AppUtils::getPassword);
83+
PathUtils.combineUnix(fileBrowserView.getPath(), finalFileName)), AppUtils::getPassword, cancellationToken);
8484
if (ret != 0) {
8585
throw new Exception("Operation failed");
8686
}
@@ -92,7 +92,7 @@ public void newOrRename(Action action) {
9292
case NewFile:
9393
if (retryWithSudo.get()) {
9494
var ret = SudoUtils.exec(fileBrowserView.getSessionInfo(), String.format("touch '%s'",
95-
PathUtils.combineUnix(fileBrowserView.getPath(), finalFileName)), AppUtils::getPassword);
95+
PathUtils.combineUnix(fileBrowserView.getPath(), finalFileName)), AppUtils::getPassword, cancellationToken);
9696
if (ret != 0) {
9797
throw new Exception("Operation failed");
9898
}
@@ -104,7 +104,7 @@ public void newOrRename(Action action) {
104104
case NewFolder:
105105
if (retryWithSudo.get()) {
106106
var ret = SudoUtils.exec(fileBrowserView.getSessionInfo(), String.format("mkdir '%s'",
107-
PathUtils.combineUnix(fileBrowserView.getPath(), finalFileName)), AppUtils::getPassword);
107+
PathUtils.combineUnix(fileBrowserView.getPath(), finalFileName)), AppUtils::getPassword, cancellationToken);
108108
if (ret != 0) {
109109
throw new Exception("Operation failed");
110110
}
@@ -119,14 +119,6 @@ public void newOrRename(Action action) {
119119
}, retryWithSudo);
120120
}
121121

122-
public void sameOriginCopy() {
123-
var selected = fileBrowserView.getSelectedFiles();
124-
if (selected.size() == 0) {
125-
return;
126-
}
127-
sameOriginCopy(selected, fileBrowserView.getPath(), false);
128-
}
129-
130122
public void sameOriginCopy(List<FileInfo> files, String folder, boolean overwrite) {
131123
var fileSet = fileBrowserView.getAllFiles().stream().map(FileInfo::getName).collect(Collectors.toSet());
132124
var args = new ArrayList<String>();
@@ -161,14 +153,14 @@ public void sameOriginCopy(List<FileInfo> files, String folder, boolean overwrit
161153
}
162154
if (retryWithSudo.get()) {
163155
var ret = SudoUtils.exec(fileBrowserView.getSessionInfo(),
164-
String.format("sh -c \"%s\"", command), AppUtils::getPassword);
156+
String.format("sh -c \"%s\"", command), AppUtils::getPassword, cancellationToken);
165157
if (ret != 0) {
166158
throw new Exception("Operation failed");
167159
}
168160
} else {
169161
if (App.getMultiplexingSessionService().executeCommand(
170162
this.fileBrowserView.getSession().getSessionInfo(),
171-
command) != 0) {
163+
command, cancellationToken) != 0) {
172164
throw new FSAccessException("Operation failed");
173165
}
174166
}
@@ -222,7 +214,7 @@ public void delete() {
222214
.collect(Collectors.joining(" ")));
223215
if (retryWithSudo.get()) {
224216
var ret = SudoUtils.exec(this.fileBrowserView.getSession().getSessionInfo(),
225-
str, AppUtils::getPassword);
217+
str, AppUtils::getPassword, cancellationToken);
226218
if (ret != 0) {
227219
if (ret == -2) {
228220
throw new Exception("Operation cancelled");
@@ -231,7 +223,7 @@ public void delete() {
231223
}
232224
return;
233225
}
234-
if (App.getMultiplexingSessionService().executeCommand(this.fileBrowserView.getSession().getSessionInfo(), str) == 0) {
226+
if (App.getMultiplexingSessionService().executeCommand(this.fileBrowserView.getSession().getSessionInfo(), str, cancellationToken) == 0) {
235227
System.out.println("Shell delete success!");
236228
return;
237229
}
@@ -273,18 +265,16 @@ public void delete() {
273265
}
274266

275267
public void compress() {
276-
var stopFlag = new AtomicBoolean(false);
277268
var selected = fileBrowserView.getSelectedFiles();
278269

279270
var arcOps = new ArchiveOperations();
280-
arcOps.createArchive(fileBrowserView.getSessionInfo(), selected.stream().map(FileInfo::getPath).toList(), fileBrowserView.getPath(), stopFlag);
271+
arcOps.createArchive(fileBrowserView.getSessionInfo(), selected.stream().map(FileInfo::getPath).toList(), fileBrowserView.getPath());
281272
}
282273

283274
public void extract() {
284-
var stopFlag = new AtomicBoolean(false);
285275
var selected = fileBrowserView.getSelectedFiles();
286276
var arcOps = new ArchiveOperations();
287-
arcOps.extractArchive(fileBrowserView.getSessionInfo(), selected.get(0).getPath(), fileBrowserView.getPath(), stopFlag);
277+
arcOps.extractArchive(fileBrowserView.getSessionInfo(), selected.get(0).getPath(), fileBrowserView.getPath());
288278
}
289279

290280
private void traverse(String path, ArrayList<String> files, ArrayList<String> folders, CancellationToken cancellationToken)
@@ -296,6 +286,9 @@ private void traverse(String path, ArrayList<String> files, ArrayList<String> fo
296286
}
297287
for (var folder :
298288
fileList.getFolders()) {
289+
if (cancellationToken.isCancellationRequested()) {
290+
return;
291+
}
299292
if (folder.getFileType() == FileType.Directory) {
300293
traverse(folder.getPath(), files, folders, cancellationToken);
301294
} else {
@@ -432,12 +425,12 @@ public void copy(String targetFolder, List<FileInfo> sourceFiles) {
432425
var str = String.format("cp -rf %s '%s'", String.join(" ", files), targetFolder);
433426
System.out.println(str);
434427
if (retryWithSudo.get()) {
435-
var ret = SudoUtils.exec(fileBrowserView.getSessionInfo(), str, AppUtils::getPassword);
428+
var ret = SudoUtils.exec(fileBrowserView.getSessionInfo(), str, AppUtils::getPassword, cancellationToken);
436429
if (ret != 0) {
437430
throw new Exception("Operation failed");
438431
}
439432
} else {
440-
if (App.getMultiplexingSessionService().executeCommand(this.fileBrowserView.getSession().getSessionInfo(), str) != 0) {
433+
if (App.getMultiplexingSessionService().executeCommand(this.fileBrowserView.getSession().getSessionInfo(), str, cancellationToken) != 0) {
441434
throw new FSAccessException("Operation failed");
442435
}
443436
}

app/src/main/java/muon/screens/appwin/filebrowser/sftp/archiving/ArchiveOperations.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import muon.App;
44
import muon.dto.session.SessionInfo;
5+
import muon.misc.CancellationToken;
56
import muon.util.AppUtils;
67
import muon.util.PathUtils;
78

@@ -89,8 +90,8 @@ private String getArchiveFileName(String archivePath) {
8990
public void extractArchive(
9091
SessionInfo sessionInfo,
9192
String archivePath,
92-
String targetFolder,
93-
AtomicBoolean stopFlag) {
93+
String targetFolder) {
94+
var cancellationToken = new CancellationToken();
9495
String command = getExtractCommand(archivePath);
9596
if (command == null) {
9697
System.out.println("Unsupported file: " + archivePath);
@@ -105,14 +106,14 @@ public void extractArchive(
105106
System.out.println("Invoke command: " + command);
106107
String finalCommand = command;
107108
App.getAppWindow().blockInput(true, "Please wait...", "", e -> {
108-
stopFlag.set(true);
109+
cancellationToken.requestCancellation();
109110
});
110111
AppUtils.runAsync(() -> {
111112
try {
112113
var ret = App.getMultiplexingSessionService().executeCommand(
113114
sessionInfo,
114115
finalCommand,
115-
stopFlag);
116+
cancellationToken);
116117
System.out.println("output: " + ret);
117118
if (ret != 0) {
118119
AppUtils.swingInvokeSync(() -> JOptionPane.showMessageDialog(null, "Operation failed"));
@@ -128,8 +129,8 @@ public void extractArchive(
128129
public void createArchive(
129130
SessionInfo sessionInfo,
130131
List<String> files,
131-
String targetFolder,
132-
AtomicBoolean stopFlag) {
132+
String targetFolder) {
133+
var cancellationToken = new CancellationToken();
133134
String text = files.size() > 1 ? PathUtils.getFileName(targetFolder)
134135
: files.get(0);
135136
JTextField txtFileName = new JTextField(text);
@@ -156,12 +157,12 @@ public void createArchive(
156157
String cd = String.format("cd \"%s\";", txtTargetFolder.getText());
157158
System.out.println(cd + compressCmd);
158159
App.getAppWindow().blockInput(true, "Please wait...", "", e -> {
159-
stopFlag.set(true);
160+
cancellationToken.requestCancellation();
160161
});
161162
AppUtils.runAsync(() -> {
162163
try {
163164
var ret = App.getMultiplexingSessionService().executeCommand(
164-
sessionInfo, cd + compressCmd, stopFlag);
165+
sessionInfo, cd + compressCmd, cancellationToken);
165166
System.out.println("output: " + ret);
166167
if (ret != 0) {
167168
AppUtils.swingInvokeSync(() -> JOptionPane.showMessageDialog(null, "Operation failed"));

app/src/main/java/muon/screens/appwin/filebrowser/transfer/foreground/ForegroundTransferPanel.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package muon.screens.appwin.filebrowser.transfer.foreground;
22

33
import muon.exceptions.FSAccessException;
4+
import muon.misc.CancellationToken;
45
import muon.service.SftpUploadTask;
56
import muon.service.TransferTask;
67
import muon.util.AppUtils;
@@ -20,6 +21,7 @@ public class ForegroundTransferPanel extends JPanel {
2021
private Consumer<Boolean> callback;
2122
private TransferProgressPanel transferProgressPanel;
2223
private TransferRetryPanel transferRetryPanel;
24+
private CancellationToken cancellationToken;
2325

2426
public ForegroundTransferPanel() {
2527
super(new CardLayout());
@@ -34,7 +36,12 @@ public void submitTask(TransferTask transferTask,
3436

3537
public void showProgress() {
3638
if (transferProgressPanel == null) {
37-
transferProgressPanel = new TransferProgressPanel(e -> this.transferTask.requestCancellation());
39+
transferProgressPanel = new TransferProgressPanel(e -> {
40+
this.transferTask.requestCancellation();
41+
if (Objects.nonNull(this.cancellationToken)) {
42+
this.cancellationToken.requestCancellation();
43+
}
44+
});
3845
this.add(transferProgressPanel, "TransferProgressPanel");
3946
}
4047
transferProgressPanel.setProgress(0);
@@ -79,6 +86,7 @@ public void transfer() {
7986
AppUtils.runAsync(() -> {
8087
var retry = new AtomicBoolean(false);
8188
do {
89+
cancellationToken = new CancellationToken();
8290
try {
8391
transferFiles();
8492
if (retry.get() && this.transferTask instanceof SftpUploadTask && !StringUtils.isEmpty(((SftpUploadTask) this.transferTask).getActualTargetPath())) {
@@ -117,7 +125,7 @@ private void copyUsingSudo(String source, String target) throws Exception {
117125
transferProgressPanel.setStatus("Running shell command...");
118126
});
119127
var ret = SudoUtils.exec(((SftpUploadTask) this.transferTask).getSftpProvider().getSessionInfo(),
120-
command, out, err, AppUtils::getPassword);
128+
command, out, err, AppUtils::getPassword, cancellationToken);
121129
if (ret != 0) {
122130
if (ret == -2) {
123131
throw new Exception("Operation cancelled");

app/src/main/java/muon/screens/appwin/toolbox/pages/process/ProcessViewerPage.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import muon.dto.session.SessionInfo;
88
import muon.exceptions.FSAccessException;
99
import muon.exceptions.FSConnectException;
10+
import muon.misc.CancellationToken;
1011
import muon.screens.appwin.toolbox.PageContainer;
1112
import muon.util.AppUtils;
1213
import muon.util.ScriptLoader;
@@ -105,7 +106,7 @@ private void kill(ActionEvent actionEvent) {
105106
try {
106107
var out = new ByteArrayOutputStream();
107108
var err = new ByteArrayOutputStream();
108-
var ret = SudoUtils.exec(sessionInfo, "kill -9 " + pid, out, err, this.pageContainer::getPassword);
109+
var ret = SudoUtils.exec(sessionInfo, "kill -9 " + pid, out, err, this.pageContainer::getPassword, new CancellationToken());
109110
if (ret != 0) {
110111
//Notify sudo failed
111112
if (ret == -2) {

app/src/main/java/muon/screens/appwin/toolbox/pages/service/ServicePanel.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import muon.App;
77
import muon.dto.session.SessionInfo;
8+
import muon.misc.CancellationToken;
89
import muon.screens.appwin.toolbox.PageContainer;
910
import muon.util.AppUtils;
1011
import muon.util.SudoUtils;
@@ -411,7 +412,7 @@ private void performServiceAction(int option) {
411412
public boolean runCommandWithSudo(String command) throws Exception {
412413
var out = new ByteArrayOutputStream();
413414
var err = new ByteArrayOutputStream();
414-
return SudoUtils.exec(this.sessionInfo, command, out, err, this.pageContainer::getPassword) == 0;
415+
return SudoUtils.exec(this.sessionInfo, command, out, err, this.pageContainer::getPassword, new CancellationToken()) == 0;
415416
}
416417

417418
public boolean runCommand(String command) throws Exception {

app/src/main/java/muon/screens/sessionmgr/SshInfoPanel.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public class SshInfoPanel extends JPanel {
2525
private SessionInfo sessionInfo;
2626
private JLabel lblPassword, lblKeyFile, lblIdentity, lblUserName;
2727
private JButton btnBrowseKey, btnEditIdentities;
28+
private JCheckBox chkUseSudo;
2829

2930
public SshInfoPanel() {
3031
super(new GridBagLayout());
@@ -46,6 +47,7 @@ public SshInfoPanel() {
4647
txtLocalFolder = new JTextField();
4748
cmbStartPage = new JComboBox<>(new String[]{"SFTP+Terminal", "SFTP", "Terminal", "Port forwarding"});
4849
swCombinedMode = new SwitchButton();
50+
chkUseSudo = new JCheckBox("Login with sudo privilege");
4951

5052
cmbStartPage.setFont(cmbFont);
5153
txtPass.setEchoChar('*');
@@ -73,6 +75,9 @@ public SshInfoPanel() {
7375
attachTextListener(txtKeyFile, text -> sessionInfo.setPrivateKeyFile(text));
7476
attachTextListener(txtRemoteFolder, text -> sessionInfo.setRemoteFolder(text));
7577
attachTextListener(txtLocalFolder, text -> sessionInfo.setLocalFolder(text));
78+
chkUseSudo.addChangeListener(e -> {
79+
sessionInfo.setLoginAsSudo(chkUseSudo.isSelected());
80+
});
7681

7782
attachPasswordListener(txtPass, password -> {
7883
sessionInfo.setPassword(new String(password));
@@ -104,6 +109,7 @@ public void setValue(SessionInfo info) {
104109
txtLocalFolder.setText(info.getLocalFolder());
105110
cmbAuthMethod.setSelectedIndex(info.getAuthMode());
106111
txtPass.setText(info.getPassword());
112+
chkUseSudo.setSelected(false);
107113
}
108114

109115
private void attachTextListener(JTextField txt, Consumer<String> consumer) {
@@ -371,6 +377,17 @@ private void createUI() {
371377

372378
c++;
373379

380+
gc = new GridBagConstraints();
381+
gc.gridx = 1;
382+
gc.gridy = c;
383+
gc.fill = GridBagConstraints.HORIZONTAL;
384+
gc.gridwidth = 2;
385+
gc.weightx = 1;
386+
gc.insets = insets;
387+
this.add(chkUseSudo, gc);
388+
389+
c++;
390+
374391
gc = new GridBagConstraints();
375392
gc.gridx = 0;
376393
gc.gridy = c;

0 commit comments

Comments
 (0)