View Javadoc
1   /*
2    * Copyright 2012-2017 CodeLibs Project and the Others.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13   * either express or implied. See the License for the specific language
14   * governing permissions and limitations under the License.
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; // 30sec
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             // monitoring
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                     // ignore
158                 }
159             }
160             if (currentProcess != null) {
161                 try {
162                     currentProcess.destroy();
163                 } catch (final Exception e) {
164                     // ignore
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                 // ignore
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          * @param finished
209          *            The finished to set.
210          */
211         public void setFinished(final boolean finished) {
212             this.finished = finished;
213         }
214 
215         /**
216          * @return Returns the teminated.
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 }