View Javadoc

1   /*
2    * Copyright (C) 2017-2019 Centre National d'Etudes Spatiales (CNES).
3    *
4    * This library is free software; you can redistribute it and/or
5    * modify it under the terms of the GNU Lesser General Public
6    * License as published by the Free Software Foundation; either
7    * version 3.0 of the License, or (at your option) any later version.
8    *
9    * This library is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12   * Lesser General Public License for more details.
13   *
14   * You should have received a copy of the GNU Lesser General Public
15   * License along with this library; if not, write to the Free Software
16   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17   * MA 02110-1301  USA
18   */
19  package fr.cnes.doi.server;
20  
21  import fr.cnes.doi.exception.ClientMdsException;
22  import java.io.BufferedReader;
23  import java.io.FileInputStream;
24  import java.io.FileNotFoundException;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.io.InputStreamReader;
28  import java.io.Reader;
29  import java.nio.charset.StandardCharsets;
30  import java.nio.file.Files;
31  import java.nio.file.Paths;
32  import java.util.stream.Collectors;
33  
34  import org.apache.logging.log4j.LogManager;
35  import org.apache.logging.log4j.Logger;
36  
37  import fr.cnes.doi.exception.DoiRuntimeException;
38  import fr.cnes.doi.security.TokenSecurity;
39  import fr.cnes.doi.security.UtilsCryptography;
40  import fr.cnes.doi.settings.Consts;
41  import fr.cnes.doi.settings.DoiSettings;
42  import fr.cnes.doi.utils.spec.Requirement;
43  import gnu.getopt.Getopt;
44  import gnu.getopt.LongOpt;
45  import java.nio.charset.Charset;
46  
47  /**
48   * DOI server
49   *
50   * @author Jean-Christophe Malapert (jean-christophe.malapert@cnes.fr)
51   */
52  @Requirement(reqId = Requirement.DOI_DEV_010, reqName = Requirement.DOI_DEV_010_NAME)
53  @Requirement(reqId = Requirement.DOI_DEV_020, reqName = Requirement.DOI_DEV_020_NAME)
54  public final class Starter {
55  
56      /**
57       * Length of the secret key {@value #BITS_16}
58       */
59      public static final int BITS_16 = 16;
60  
61      /**
62       * Logger.
63       */
64      private static final Logger LOG = LogManager.getLogger(Starter.class.getName());
65  
66      /**
67       * DOI Server.
68       */
69      private static DoiServer doiServer;
70  
71      static {
72          final java.util.logging.Logger rootLogger = java.util.logging.LogManager.getLogManager()
73                  .getLogger("");
74          final java.util.logging.Handler[] handlers = rootLogger.getHandlers();
75          rootLogger.removeHandler(handlers[0]);
76      }
77  
78      private static void displayHelp() {
79          LOG.traceEntry();
80          final DoiSettings settings = DoiSettings.getInstance();
81          final StringBuilder help = new StringBuilder();
82          help.append("\n------------ Help for DOI Server -----------\n");
83          help.append("\n");
84          help.append("Usage: java -jar ").append(settings.getString(Consts.APP_NAME)).append("-")
85                  .append(settings.getString(Consts.VERSION))
86                  .append(".jar [--secret <key>] [OPTIONS] [-s]\n");
87          help.append("\n\n");
88          help.append("with :\n");
89          help.append("  --secret <key>               : The 16 bits secret key to crypt/decrypt\n");
90          help.append("  --key-sign-secret <key>      : The key to sign the token\n");
91          help.append("                                 If not provided, a default one is used\n");
92          help.append("  -s|--start                   : Starts the server\n");
93          help.append("  -t|--stop                    : Stops the server\n");
94          help.append("  -l|--status                  : Status of the server\n");
95          help.append("with OPTIONS:\n");
96          help.append("  -h|--help                    : This output\n");
97          help.append("  -k|--key-sign                : Creates a key to sign JWT token\n");
98          help.append("  -c <string>                  : Crypts a string in the standard output\n");
99          help.append("  -e <string>                  : Decrypts a string in the standard output\n");
100         help.append("  -d                           : Displays the configuration file\n");
101         help.append("  -f <path>                    : Loads the configuation file\n");
102         help.append(
103                 "  -y|--cryptProperties <path>  : crypts the properties file on the output standard\n");
104         help.append(
105                 "  -z|--decryptProperties <path>: Decrypts the properties on the output standard\n");
106         help.append("  -v|--version                 : DOI server version\n");
107         help.append("\n");
108         help.append("\n");
109         LOG.info(help.toString());
110         LOG.traceExit();
111     }
112 
113     /**
114      * Stops the server
115      *
116      * @param server HTTP or HTTPS server
117      */
118     private static void stopServer(final Thread server) {
119         LOG.traceEntry();
120         try {
121             try {
122                 doiServer.stop();
123             } catch (Exception ex) {
124                 LOG.fatal("Unable to stop the server", ex);
125             } finally {
126                 LOG.info("Interrups the server, which is stopping");
127                 server.interrupt();
128                 server.join();
129             }
130         } catch (InterruptedException e) {
131             LOG.fatal("Cannot interrupt the server", e);
132         }
133         LOG.traceExit();
134     }
135 
136     /**
137      * Starts the server
138      *
139      * @param server the server
140      */
141     @Requirement(reqId = Requirement.DOI_ARCHI_040, reqName = Requirement.DOI_ARCHI_040_NAME)
142     private static void startServer(final DoiServer server) {
143         final DoiSettings settings = DoiSettings.getInstance();
144         final String progName = settings.getString(Consts.APP_NAME) + "-" + settings.getString(
145                 Consts.VERSION) + ".jar";
146         final String stopPid = getCurrentPid(progName);
147         if (stopPid == null) {
148             try {
149                 server.start();
150                 infoProject();
151             } catch (Exception ex) {
152                 LOG.info("Unable to start the server");
153             }
154         } else {
155             LOG.info("The server is already started");
156         }
157     }
158 
159     /**
160      * Info about project.
161      */
162     private static void infoProject() {
163         LOG.info("-------------------------------------------------");
164         LOG.info("              DOI-server project");
165         LOG.info("-------------------------------------------------");
166         LOG.info("Project : https://cnes.github.io/DOI-server");
167         LOG.info("Source : https://github.com/cnes/DOI-server");
168         LOG.info("Issues : https://github.com/CNES/DOI-server");
169         LOG.info("Copyright (C) 2017-2019 Centre National d'Etudes Spatiales (CNES)");
170         LOG.info("License : LGPLV3");
171 
172     }
173 
174     /**
175      * Stops the server
176      *
177      */
178     private static void stopServer() {
179         try {
180             final DoiSettings settings = DoiSettings.getInstance();
181             final String progName = settings.getString(Consts.APP_NAME) + "-" + settings.getString(
182                     Consts.VERSION) + ".jar";
183             final String stopPid = getCurrentPid(progName);
184             if (stopPid != null) {
185                 Runtime.getRuntime().exec("kill -9 " + stopPid);
186                 LOG.info("Stopping the DOI server .... OK");
187             } else {
188                 LOG.info("The DOI server is already stopped");
189             }
190         } catch (IOException e) {
191             LOG.info("Stopping the DOI server .... Failed");
192         }
193     }
194 
195     /**
196      * Status of the server
197      *
198      */
199     private static void statusServer() {
200         final DoiSettings settings = DoiSettings.getInstance();
201         final String progName = settings.getString(Consts.APP_NAME) + "-" + settings.getString(
202                 Consts.VERSION) + ".jar";
203         final String stopPid = getCurrentPid(progName);
204         if (stopPid != null) {
205             LOG.info("The DOI server is running with the pid {}", stopPid);
206         } else {
207             LOG.info("The DOI server is stopped");
208         }
209     }
210 
211     /**
212      * Launches the server.
213      *
214      * @param settings Configuration
215      */
216     private static void launchServer(final DoiSettings settings) {
217         LOG.traceEntry();
218         try {
219             settings.validConfigurationFile();
220             doiServer = new DoiServer(settings);
221             final Thread server = new Thread() {
222                 @Override
223                 public void run() {
224                     startServer(doiServer);
225                 }
226             };
227 
228             Runtime.getRuntime().addShutdownHook(new Thread() {
229                 @Override
230                 public void run() {
231                     LOG.info("interrupt received, killing server…");
232                     stopServer(server);
233                 }
234             });
235 
236             server.start();
237         } catch (ClientMdsException| DoiRuntimeException ex) {
238             LOG.info("Error when starting the server: " + ex.getMessage());
239         } 
240         LOG.traceExit();
241     }
242 
243     /**
244      * Displays version.
245      */
246     private static void displayVersion() {
247         LOG.traceEntry();
248         final DoiSettings settings = DoiSettings.getInstance();
249         final String appName = settings.getString(Consts.APP_NAME);
250         final String version = settings.getString(Consts.VERSION);
251         final String copyright = settings.getString(Consts.COPYRIGHT);
252         LOG.info("{} ({}) - Version:{}\n", appName, copyright, version);
253         LOG.traceExit();
254     }
255 
256     /**
257      * Return the server PID from the system.
258      *
259      * @param serverName server name
260      * @return the PID or null
261      */
262     private static String getCurrentPid(final String serverName) {
263         LOG.traceEntry("Parameter\n\tserverName:{}", serverName);
264         String stopPid = null;
265         BufferedReader reader = null;
266         Process pro = null;
267         try {
268             pro = Runtime.getRuntime().exec("ps aux");
269             reader = new BufferedReader(new InputStreamReader(pro.
270                     getInputStream(), Charset.defaultCharset()));
271             final String processName = java.lang.management.ManagementFactory.getRuntimeMXBean().
272                     getName();
273             final String myPid = processName.split("@")[0];
274             String line;
275             while ((line = reader.readLine()) != null) {
276                 if (line.contains(serverName) && line.contains("java")) {
277                     final String[] split = line.split("\\s+");
278                     final String currentPid = split[1].trim();
279                     if (!currentPid.equals(myPid)) {
280                         stopPid = currentPid;
281                         break;
282                     }
283                 }
284             }
285         } catch (IOException ex) {
286         } finally {
287             if (reader != null) {
288                 try {
289                     reader.close();
290                 } catch (IOException ex) {
291                 }
292             }
293             if (pro != null) {
294                 pro.destroy();
295             }
296         }
297         return LOG.traceExit(stopPid);
298     }
299 
300     /**
301      * Main.
302      *
303      * @param argv command line arguments
304      */
305     public static void main(final String[] argv) {
306         final DoiSettings settings = DoiSettings.getInstance();
307         final String progName = settings.getString(Consts.APP_NAME) + "-" + settings.getString(
308                 Consts.VERSION) + ".jar";
309         final String progNameWithJar = "java -jar " + progName;
310 
311         int c;
312         String arg;
313 
314         final StringBuffer sb = new StringBuffer();
315         final LongOpt[] longopts = new LongOpt[10];
316         longopts[0] = new LongOpt("help", LongOpt.NO_ARGUMENT, null, 'h');
317         longopts[1] = new LongOpt("version", LongOpt.NO_ARGUMENT, null, 'v');
318         longopts[2] = new LongOpt("secret", LongOpt.REQUIRED_ARGUMENT, sb, 0);
319         longopts[3] = new LongOpt("decryptProperties", LongOpt.REQUIRED_ARGUMENT, null, 'z');
320         longopts[4] = new LongOpt("cryptProperties", LongOpt.REQUIRED_ARGUMENT, null, 'y');
321         longopts[5] = new LongOpt("key-sign", LongOpt.NO_ARGUMENT, null, 'k');
322         longopts[6] = new LongOpt("key-sign-secret", LongOpt.REQUIRED_ARGUMENT, null, 'a');
323         longopts[7] = new LongOpt("start", LongOpt.NO_ARGUMENT, null, 's');
324         longopts[8] = new LongOpt("stop", LongOpt.NO_ARGUMENT, null, 't');
325         longopts[8] = new LongOpt("status", LongOpt.NO_ARGUMENT, null, 'l');
326         //
327         final Getopt g = new Getopt(progNameWithJar, argv, "hvdstke:c:f:y:z:a:b:", longopts);
328         //
329         while ((c = g.getopt()) != -1) {
330             switch (c) {
331                 case 0:
332                     final String secretKey = g.getOptarg();
333                     if (secretKey.length() != BITS_16) {
334                         throw new IllegalArgumentException(
335                                 "The secret key must have 16 characters.");
336                     } else {
337                         settings.setSecretKey(secretKey);
338                     }
339                     break;
340                 //
341                 case 'a':
342                     LOG.debug("a option is selected");
343                     final String secretSignToken = g.getOptarg();
344                     TokenSecurity.getInstance().setTokenKey(secretSignToken);
345                     break;
346                 case 'h':
347                     LOG.debug("h option is selected");
348                     displayHelp();
349                     break;
350                 //
351                 case 's':
352                     LOG.debug("s option is selected");
353                     launchServer(settings);
354                     break;
355                 case 't':
356                     LOG.debug("t option is selected");
357                     stopServer();
358                     break;
359                 case 'l':
360                     LOG.debug("l option is selected");
361                     statusServer();
362                     break;
363                 //
364                 case 'k':
365                     LOG.debug("k option is selected");
366                     LOG.info(TokenSecurity.createKeySignatureHS256());
367                     break;
368                 //
369                 case 'e':
370                     LOG.debug("e option is selected");
371                     arg = g.getOptarg();
372                     try {
373                         LOG.info(UtilsCryptography.decrypt(arg, settings.getSecretKey()));
374                     } catch (DoiRuntimeException ex) {
375                         LOG.fatal("Unable to decrypt {} : {}", arg, ex.getMessage());
376                     }
377                     break;
378                 //
379                 case 'c':
380                     LOG.debug("c option is selected");
381                     arg = g.getOptarg();
382                     try {
383                         LOG.info(UtilsCryptography.encrypt(arg, settings.getSecretKey()));
384                     } catch (DoiRuntimeException ex) {
385                         LOG.fatal("Unable to encrypt {} : {}", arg, ex.getMessage());
386                     }
387                     break;
388                 //
389                 case 'd':
390                     LOG.debug("d option is selected");
391                     settings.displayConfigFile();
392                     break;
393                 //
394                 case 'f':
395                     LOG.debug("f option is selected");
396                     try {
397                         settings.setPropertiesFile(g.getOptarg());
398                     } catch (IOException ex) {
399                         LOG.fatal(ex.getMessage());
400                     }
401                     break;
402                 //
403                 case 'v':
404                     LOG.debug("v option is selected");
405                     displayVersion();
406                     break;
407                 //
408                 case 'y':
409                     LOG.debug("y option is selected");
410                     try {
411                         final byte[] encodedFile = Files.readAllBytes(Paths.get(g.getOptarg()));
412                         String contentFile = new String(encodedFile, StandardCharsets.UTF_8);
413                         contentFile = UtilsCryptography.encrypt(contentFile,
414                                 settings.getSecretKey());
415                         LOG.info(contentFile);
416                     } catch (IOException ex) {
417                         LOG.fatal("Error: {}", ex.getMessage());
418                     }
419                     break;
420                 //
421                 case 'z':
422                     LOG.debug("z option is selected");
423                     InputStream inputStream = null;
424                     BufferedReader reader = null;
425                     Reader inputReader = null;
426 
427                     try {
428                         inputStream = new FileInputStream(g.getOptarg());
429                         inputReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
430                         reader = new BufferedReader(inputReader);
431                         String content = reader.lines().collect(Collectors.joining("\n"));
432                         content = UtilsCryptography.decrypt(content, settings.getSecretKey());
433                         LOG.info(content);
434                     } catch (FileNotFoundException ex) {
435                         LOG.fatal("Error: {}", ex.getMessage());
436                     } finally {
437                         if (inputStream != null) {
438                             try {
439                                 inputStream.close();
440                             } catch (IOException e) {
441                                 LOG.fatal("Error closing inputstream: {}", e.getMessage(), e);
442                             }
443                         }
444                         if (inputReader != null) {
445                             try {
446                                 inputReader.close();
447                             } catch (IOException ex) {
448                                 LOG.fatal("Error closing inputReader: {}", ex.getMessage(), ex);
449                             }
450                         }
451                         if (reader != null) {
452                             try {
453                                 reader.close();
454                             } catch (IOException ex) {
455                                 LOG.fatal("Error closing reader: {}", ex.getMessage(), ex);
456                             }
457                         }
458                     }
459                     break;
460                 case '?':
461                     break; // getopt() already printed an error
462                 //
463                 default:
464                     LOG.debug("getopt() returned {}\n", c);
465             }
466         }
467         //
468         for (int i = g.getOptind(); i < argv.length; i++) {
469             LOG.info("Non option argv element: {}\n", argv[i]);
470         }
471 
472         if (argv.length == 0) {
473             displayHelp();
474         }
475 
476     }
477 
478     /**
479      * "Static" class cannot be instantiated
480      */
481     private Starter() {
482     }
483 
484 }
485 // Generating a self-signed certificate
486 // --------------------------------------
487 // keytool -keystore serverKey.jks -alias server -genkey -keyalg RSA
488 // -keysize 2048 -dname "CN=www.cnes.fr,OU=Jean-Christophe Malapert,O=CNES,C=FR"
489 // -sigalg "SHA1withRSA"
490 //
491 // Note that you’ll be prompted for passwords for the keystore and the key
492 // itself. Let’s
493 // enter password as the example value. This certificate can then be exported as
494 // an inde-
495 // pendent certificate file server.crt, using this command (providing the same
496 // password):
497 // keytool -exportcert -keystore serverKey.jks -alias server -file serverKey.crt
498 //
499 // Generating a certificate request
500 // ----------------------------------
501 // keytool -certreq -keystore serverKey.jks -alias server -file serverKey.crt
502 //
503 // Importing a trusted certificate
504 // ----------------------------------
505 // After approval of the certificate request, the CA will provide a certificate
506 // file, usually in
507 // PEM or DER format. It needs to be imported back into the keystore to be used
508 // as a
509 // server certificate:
510 // keytool -import -keystore serverKey.jks -alias server -file serverKey.crt
511 // This command is also used for importing CA certificates into a special
512 // keystore that’s
513 // going to be used as a truststore—on the client side, for example. In this
514 // case the
515 // -trustcacerts options may also be required:
516 // keytool -import -keystore clientTrust.jks -trustcacerts -alias server -file
517 // serverKey.crt
518 // This trusted certificate may also be imported explicitly into your browser or
519 // used by a
520 // programmatic HTTPS client. This is useful if you’re deploying your own
521 // infrastruc-
522 // ture, or during development phases.