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