1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.codehaus.plexus.components.secdispatcher.internal.sources;
20
21 import javax.inject.Named;
22 import javax.inject.Singleton;
23
24 import java.io.BufferedReader;
25 import java.io.IOException;
26 import java.io.InputStreamReader;
27 import java.io.OutputStream;
28 import java.net.StandardProtocolFamily;
29 import java.net.UnixDomainSocketAddress;
30 import java.nio.channels.Channels;
31 import java.nio.channels.SocketChannel;
32 import java.nio.file.Files;
33 import java.nio.file.Path;
34 import java.nio.file.Paths;
35 import java.util.ArrayList;
36 import java.util.HashMap;
37 import java.util.HexFormat;
38 import java.util.List;
39 import java.util.Optional;
40
41 import org.codehaus.plexus.components.secdispatcher.MasterSourceMeta;
42 import org.codehaus.plexus.components.secdispatcher.SecDispatcher;
43 import org.codehaus.plexus.components.secdispatcher.SecDispatcherException;
44
45
46
47
48
49
50 @Singleton
51 @Named(GpgAgentMasterSource.NAME)
52 public final class GpgAgentMasterSource extends PrefixMasterSourceSupport implements MasterSourceMeta {
53 public static final String NAME = "gpg-agent";
54
55 public GpgAgentMasterSource() {
56 super(NAME + ":");
57 }
58
59 @Override
60 public String description() {
61 return "GPG Agent (agent socket path should be edited)";
62 }
63
64 @Override
65 public Optional<String> configTemplate() {
66 return Optional.of(NAME + ":$agentSocketPath");
67 }
68
69 @Override
70 protected String doHandle(String transformed) throws SecDispatcherException {
71 String extra = "";
72 if (transformed.contains("?")) {
73 extra = transformed.substring(transformed.indexOf("?"));
74 transformed = transformed.substring(0, transformed.indexOf("?"));
75 }
76 String socketLocation = transformed;
77 boolean interactive = !extra.contains("non-interactive");
78 try {
79 Path socketLocationPath = Paths.get(socketLocation);
80 if (!socketLocationPath.isAbsolute()) {
81 socketLocationPath = Paths.get(System.getProperty("user.home"))
82 .resolve(socketLocationPath)
83 .toAbsolutePath();
84 }
85 return load(socketLocationPath, interactive);
86 } catch (IOException e) {
87 throw new SecDispatcherException(e.getMessage(), e);
88 }
89 }
90
91 @Override
92 protected SecDispatcher.ValidationResponse doValidateConfiguration(String transformed) {
93 HashMap<SecDispatcher.ValidationResponse.Level, List<String>> report = new HashMap<>();
94 boolean valid = false;
95
96 String extra = "";
97 if (transformed.contains("?")) {
98 extra = transformed.substring(transformed.indexOf("?"));
99 transformed = transformed.substring(0, transformed.indexOf("?"));
100 }
101 Path socketLocation = Paths.get(transformed);
102 if (!socketLocation.isAbsolute()) {
103 socketLocation = Paths.get(System.getProperty("user.home"))
104 .resolve(socketLocation)
105 .toAbsolutePath();
106 }
107 if (Files.exists(socketLocation)) {
108 report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.ERROR, k -> new ArrayList<>())
109 .add("Unix domain socket for GPG Agent does not exist. Maybe you need to start gpg-agent?");
110 } else {
111 report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.INFO, k -> new ArrayList<>())
112 .add("Unix domain socket for GPG Agent exist");
113 valid = true;
114 }
115 boolean interactive = !extra.contains("non-interactive");
116 if (!interactive) {
117 report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.WARNING, k -> new ArrayList<>())
118 .add(
119 "Non-interactive flag found, gpg-agent will not ask for passphrase, it can use only cached ones");
120 }
121 return new SecDispatcher.ValidationResponse(getClass().getSimpleName(), valid, report, List.of());
122 }
123
124 private String load(Path socketPath, boolean interactive) throws IOException {
125 try (SocketChannel sock = SocketChannel.open(StandardProtocolFamily.UNIX)) {
126 sock.connect(UnixDomainSocketAddress.of(socketPath));
127 try (BufferedReader in = new BufferedReader(new InputStreamReader(Channels.newInputStream(sock)));
128 OutputStream os = Channels.newOutputStream(sock)) {
129
130 expectOK(in);
131 String display = System.getenv("DISPLAY");
132 if (display != null) {
133 os.write(("OPTION display=" + display + "\n").getBytes());
134 os.flush();
135 expectOK(in);
136 }
137 String term = System.getenv("TERM");
138 if (term != null) {
139 os.write(("OPTION ttytype=" + term + "\n").getBytes());
140 os.flush();
141 expectOK(in);
142 }
143
144 String instruction = "GET_PASSPHRASE "
145 + (!interactive ? "--no-ask " : "")
146 + "plexus:secDispatcherMasterPassword"
147 + " "
148 + "X "
149 + "Maven+Master+Password "
150 + "Please+enter+your+Maven+master+password"
151 + "+to+use+it+for+decrypting+Maven+Settings\n";
152 os.write((instruction).getBytes());
153 os.flush();
154 return mayExpectOK(in);
155 }
156 }
157 }
158
159 private void expectOK(BufferedReader in) throws IOException {
160 String response = in.readLine();
161 if (!response.startsWith("OK")) {
162 throw new IOException("Expected OK but got this instead: " + response);
163 }
164 }
165
166 private String mayExpectOK(BufferedReader in) throws IOException {
167 String response = in.readLine();
168 if (response.startsWith("ERR")) {
169 return null;
170 } else if (!response.startsWith("OK")) {
171 throw new IOException("Expected OK/ERR but got this instead: " + response);
172 }
173 return new String(HexFormat.of()
174 .parseHex(response.substring(Math.min(response.length(), 3)).trim()));
175 }
176 }