1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.codelibs.fess.auth.chain;
17
18 import static org.codelibs.core.stream.StreamUtil.stream;
19
20 import java.io.BufferedReader;
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.InputStreamReader;
25 import java.io.UnsupportedEncodingException;
26 import java.util.LinkedList;
27 import java.util.List;
28
29 import org.apache.logging.log4j.LogManager;
30 import org.apache.logging.log4j.Logger;
31 import org.codelibs.core.lang.StringUtil;
32 import org.codelibs.core.lang.ThreadUtil;
33 import org.codelibs.fess.crawler.Constants;
34 import org.codelibs.fess.crawler.exception.CrawlerSystemException;
35 import org.codelibs.fess.es.user.exentity.User;
36 import org.codelibs.fess.exception.CommandExecutionException;
37
38 public class CommandChain implements AuthenticationChain {
39
40 private static final Logger logger = LogManager.getLogger(CommandChain.class);
41
42 protected File workingDirectory = null;
43
44 protected int maxOutputLine = 1000;
45
46 protected long executionTimeout = 30L * 1000L;
47
48 protected String commandOutputEncoding = System.getProperty("file.encoding");
49
50 protected String[] updateCommand;
51
52 protected String[] deleteCommand;
53
54 protected String[] targetUsers;
55
56 @Override
57 public void update(final User user) {
58 final String username = user.getName();
59 final String password = user.getOriginalPassword();
60 changePassword(username, password);
61 }
62
63 @Override
64 public void delete(final User user) {
65 final String username = user.getName();
66 if (isTargetUser(username)) {
67 executeCommand(deleteCommand, username, StringUtil.EMPTY);
68 }
69 }
70
71 @Override
72 public boolean changePassword(final String username, final String password) {
73 if (isTargetUser(username) && StringUtil.isNotBlank(password)) {
74 return executeCommand(updateCommand, username, password) == 0;
75 }
76 return true;
77 }
78
79 @Override
80 public User load(final User user) {
81 return user;
82 }
83
84 protected boolean isTargetUser(final String username) {
85 if (targetUsers == null) {
86 return true;
87 }
88 return stream(targetUsers).get(stream -> stream.anyMatch(s -> s.equals(username)));
89 }
90
91 protected int executeCommand(final String[] commands, final String username, final String password) {
92 if (commands == null || commands.length == 0) {
93 throw new CommandExecutionException("command is empty.");
94 }
95
96 if (logger.isInfoEnabled()) {
97 logger.info("Command: {}", String.join(" ", commands));
98 }
99
100 final String[] cmds = stream(commands).get(stream -> stream.map(s -> {
101 if ("$USERNAME".equals(s)) {
102 return username;
103 }
104 if ("$PASSWORD".equals(s)) {
105 return password;
106 }
107 return s;
108 }).toArray(n -> new String[n]));
109 final ProcessBuilder pb = new ProcessBuilder(cmds);
110 if (workingDirectory != null) {
111 pb.directory(workingDirectory);
112 }
113 pb.redirectErrorStream(true);
114
115 Process currentProcess = null;
116 MonitorThread mt = null;
117 try {
118 currentProcess = pb.start();
119
120
121 mt = new MonitorThread(currentProcess, executionTimeout);
122 mt.start();
123
124 final InputStreamThread it = new InputStreamThread(currentProcess.getInputStream(), commandOutputEncoding, maxOutputLine);
125 it.start();
126
127 currentProcess.waitFor();
128 it.join(5000);
129
130 if (mt.isTeminated()) {
131 throw new CommandExecutionException("The command execution is timeout: " + String.join(" ", commands));
132 }
133
134 final int exitValue = currentProcess.exitValue();
135
136 if (logger.isInfoEnabled()) {
137 logger.info("Exit Code: {} - Process Output:\n{}", exitValue, it.getOutput());
138 }
139 if (exitValue == 143 && mt.isTeminated()) {
140 throw new CommandExecutionException("The command execution is timeout: " + String.join(" ", commands));
141 }
142 return exitValue;
143 } catch (final CrawlerSystemException e) {
144 throw e;
145 } catch (final InterruptedException e) {
146 if (mt != null && mt.isTeminated()) {
147 throw new CommandExecutionException("The command execution is timeout: " + String.join(" ", commands), e);
148 }
149 throw new CommandExecutionException("Process terminated.", e);
150 } catch (final Exception e) {
151 throw new CommandExecutionException("Process terminated.", e);
152 } finally {
153 if (mt != null) {
154 mt.setFinished(true);
155 try {
156 mt.interrupt();
157 } catch (final Exception e) {
158
159 }
160 }
161 if (currentProcess != null) {
162 try {
163 currentProcess.destroy();
164 } catch (final Exception e) {
165
166 }
167 }
168 currentProcess = null;
169
170 }
171 }
172
173 protected static class MonitorThread extends Thread {
174 private final Process process;
175
176 private final long timeout;
177
178 private boolean finished = false;
179
180 private boolean teminated = false;
181
182 public MonitorThread(final Process process, final long timeout) {
183 this.process = process;
184 this.timeout = timeout;
185 }
186
187 @Override
188 public void run() {
189 ThreadUtil.sleepQuietly(timeout);
190
191 if (!finished) {
192 try {
193 process.destroy();
194 teminated = true;
195 } catch (final Exception e) {
196 if (logger.isInfoEnabled()) {
197 logger.info("Could not kill the subprocess.", e);
198 }
199 }
200 }
201 }
202
203
204
205
206
207 public void setFinished(final boolean finished) {
208 this.finished = finished;
209 }
210
211
212
213
214 public boolean isTeminated() {
215 return teminated;
216 }
217 }
218
219 protected static class InputStreamThread extends Thread {
220
221 private BufferedReader br;
222
223 private final List<String> list = new LinkedList<>();
224
225 private final int maxLineBuffer;
226
227 public InputStreamThread(final InputStream is, final String charset, final int maxOutputLineBuffer) {
228 try {
229 br = new BufferedReader(new InputStreamReader(is, charset));
230 } catch (final UnsupportedEncodingException e) {
231 br = new BufferedReader(new InputStreamReader(is, Constants.UTF_8_CHARSET));
232 }
233 maxLineBuffer = maxOutputLineBuffer;
234 }
235
236 @Override
237 public void run() {
238 for (;;) {
239 try {
240 final String line = br.readLine();
241 if (line == null) {
242 break;
243 }
244 if (logger.isDebugEnabled()) {
245 logger.debug(line);
246 }
247 list.add(line);
248 if (list.size() > maxLineBuffer) {
249 list.remove(0);
250 }
251 } catch (final IOException e) {
252 throw new CrawlerSystemException(e);
253 }
254 }
255 }
256
257 public String getOutput() {
258 final StringBuilder buf = new StringBuilder(100);
259 for (final String value : list) {
260 buf.append(value).append("\n");
261 }
262 return buf.toString();
263 }
264
265 }
266
267 public void setWorkingDirectory(final File workingDirectory) {
268 this.workingDirectory = workingDirectory;
269 }
270
271 public void setMaxOutputLine(final int maxOutputLine) {
272 this.maxOutputLine = maxOutputLine;
273 }
274
275 public void setExecutionTimeout(final long executionTimeout) {
276 this.executionTimeout = executionTimeout;
277 }
278
279 public void setCommandOutputEncoding(final String commandOutputEncoding) {
280 this.commandOutputEncoding = commandOutputEncoding;
281 }
282
283 public void setUpdateCommand(final String[] updateCommand) {
284 this.updateCommand = updateCommand;
285 }
286
287 public void setDeleteCommand(final String[] deleteCommand) {
288 this.deleteCommand = deleteCommand;
289 }
290
291 public void setTargetUsers(final String[] targetUsers) {
292 this.targetUsers = targetUsers;
293 }
294
295 }