001    /**
002     * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.license.util;
016    
017    import com.liferay.portal.kernel.cluster.ClusterExecutorUtil;
018    import com.liferay.portal.kernel.cluster.ClusterNode;
019    import com.liferay.portal.kernel.cluster.ClusterNodeResponse;
020    import com.liferay.portal.kernel.cluster.ClusterNodeResponses;
021    import com.liferay.portal.kernel.cluster.ClusterRequest;
022    import com.liferay.portal.kernel.cluster.FutureClusterResponses;
023    import com.liferay.portal.kernel.json.JSONFactoryUtil;
024    import com.liferay.portal.kernel.json.JSONObject;
025    import com.liferay.portal.kernel.log.Log;
026    import com.liferay.portal.kernel.log.LogFactoryUtil;
027    import com.liferay.portal.kernel.util.Base64;
028    import com.liferay.portal.kernel.util.CharPool;
029    import com.liferay.portal.kernel.util.ClassLoaderUtil;
030    import com.liferay.portal.kernel.util.Constants;
031    import com.liferay.portal.kernel.util.ContentTypes;
032    import com.liferay.portal.kernel.util.FileUtil;
033    import com.liferay.portal.kernel.util.GetterUtil;
034    import com.liferay.portal.kernel.util.Http;
035    import com.liferay.portal.kernel.util.MethodHandler;
036    import com.liferay.portal.kernel.util.MethodKey;
037    import com.liferay.portal.kernel.util.ParamUtil;
038    import com.liferay.portal.kernel.util.ReleaseInfo;
039    import com.liferay.portal.kernel.util.StringPool;
040    import com.liferay.portal.kernel.util.StringUtil;
041    import com.liferay.portal.kernel.util.Validator;
042    import com.liferay.portal.util.PortalUtil;
043    import com.liferay.portal.util.PropsUtil;
044    import com.liferay.portal.util.PropsValues;
045    import com.liferay.util.Encryptor;
046    
047    import java.io.File;
048    import java.io.InputStream;
049    
050    import java.net.Inet4Address;
051    import java.net.InetAddress;
052    import java.net.NetworkInterface;
053    import java.net.URI;
054    import java.net.URL;
055    
056    import java.security.Key;
057    import java.security.KeyFactory;
058    import java.security.PublicKey;
059    import java.security.SecureRandom;
060    import java.security.spec.X509EncodedKeySpec;
061    
062    import java.util.Arrays;
063    import java.util.Collections;
064    import java.util.HashMap;
065    import java.util.HashSet;
066    import java.util.Iterator;
067    import java.util.List;
068    import java.util.Map;
069    import java.util.Set;
070    import java.util.TreeMap;
071    import java.util.concurrent.TimeUnit;
072    
073    import javax.crypto.KeyGenerator;
074    
075    import javax.servlet.http.HttpServletRequest;
076    
077    import org.apache.commons.io.IOUtils;
078    import org.apache.http.HttpEntity;
079    import org.apache.http.HttpHost;
080    import org.apache.http.HttpResponse;
081    import org.apache.http.auth.AuthScope;
082    import org.apache.http.auth.UsernamePasswordCredentials;
083    import org.apache.http.client.CredentialsProvider;
084    import org.apache.http.client.HttpClient;
085    import org.apache.http.client.methods.HttpPost;
086    import org.apache.http.conn.HttpClientConnectionManager;
087    import org.apache.http.entity.ByteArrayEntity;
088    import org.apache.http.impl.client.BasicCredentialsProvider;
089    import org.apache.http.impl.client.HttpClientBuilder;
090    import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
091    
092    /**
093     * @author Amos Fong
094     */
095    public class LicenseUtil {
096    
097            public static final String LICENSE_REPOSITORY_DIR =
098                    PropsValues.LIFERAY_HOME.concat("/data/license");
099    
100            public static final String LICENSE_SERVER_URL = GetterUtil.get(
101                    PropsUtil.get("license.server.url"), "https://www.liferay.com");
102    
103            public static Map<String, String> getClusterServerInfo(String clusterNodeId)
104                    throws Exception {
105    
106                    ClusterNode localClusterNode =
107                            ClusterExecutorUtil.getLocalClusterNode();
108    
109                    String localClusterNodeId = localClusterNode.getClusterNodeId();
110    
111                    if (clusterNodeId.equals(localClusterNodeId)) {
112                            return getServerInfo();
113                    }
114    
115                    List<ClusterNode> clusterNodes = ClusterExecutorUtil.getClusterNodes();
116    
117                    ClusterNode clusterNode = null;
118    
119                    for (ClusterNode curClusterNode : clusterNodes) {
120                            String curClusterNodeId = curClusterNode.getClusterNodeId();
121    
122                            if (curClusterNodeId.equals(clusterNodeId)) {
123                                    clusterNode = curClusterNode;
124    
125                                    break;
126                            }
127                    }
128    
129                    if (clusterNode == null) {
130                            return null;
131                    }
132    
133                    try {
134                            ClusterRequest clusterRequest = ClusterRequest.createUnicastRequest(
135                                    _getServerInfoMethodHandler, clusterNodeId);
136    
137                            FutureClusterResponses futureClusterResponses =
138                                    ClusterExecutorUtil.execute(clusterRequest);
139    
140                            ClusterNodeResponses clusterNodeResponses =
141                                    futureClusterResponses.get(20000, TimeUnit.MILLISECONDS);
142    
143                            ClusterNodeResponse clusterNodeResponse =
144                                    clusterNodeResponses.getClusterResponse(
145                                            clusterNode.getClusterNodeId());
146    
147                            return (Map<String, String>)clusterNodeResponse.getResult();
148                    }
149                    catch (Exception e) {
150                            _log.error(e, e);
151    
152                            throw e;
153                    }
154            }
155    
156            /**
157             * @deprecated As of 6.2.0, replaced by {@link PortalUtil#getComputerName()}
158             */
159            @Deprecated
160            public static String getHostName() {
161                    return PortalUtil.getComputerName();
162            }
163    
164            public static Set<String> getIpAddresses() {
165                    if (_ipAddresses != null) {
166                            return new HashSet<>(_ipAddresses);
167                    }
168    
169                    _ipAddresses = new HashSet<>();
170    
171                    try {
172                            List<NetworkInterface> networkInterfaces = Collections.list(
173                                    NetworkInterface.getNetworkInterfaces());
174    
175                            for (NetworkInterface networkInterface : networkInterfaces) {
176                                    List<InetAddress> inetAddresses = Collections.list(
177                                            networkInterface.getInetAddresses());
178    
179                                    for (InetAddress inetAddress : inetAddresses) {
180                                            if (inetAddress.isLinkLocalAddress() ||
181                                                    inetAddress.isLoopbackAddress() ||
182                                                    !(inetAddress instanceof Inet4Address)) {
183    
184                                                    continue;
185                                            }
186    
187                                            _ipAddresses.add(inetAddress.getHostAddress());
188                                    }
189                            }
190                    }
191                    catch (Exception e) {
192                            _log.error("Unable to read local server's IP addresses");
193    
194                            _log.error(e, e);
195                    }
196    
197                    return new HashSet<>(_ipAddresses);
198            }
199    
200            public static Set<String> getMacAddresses() {
201                    if (_macAddresses != null) {
202                            return new HashSet<>(_macAddresses);
203                    }
204    
205                    _macAddresses = new HashSet<>();
206    
207                    try {
208                            List<NetworkInterface> networkInterfaces = Collections.list(
209                                    NetworkInterface.getNetworkInterfaces());
210    
211                            for (NetworkInterface networkInterface : networkInterfaces) {
212                                    byte[] hardwareAddress = networkInterface.getHardwareAddress();
213    
214                                    if (hardwareAddress == null) {
215                                            continue;
216                                    }
217    
218                                    StringBuilder sb = new StringBuilder(
219                                            (hardwareAddress.length * 3) - 1);
220    
221                                    String hexString = StringUtil.bytesToHexString(hardwareAddress);
222    
223                                    for (int i = 0; i < hexString.length(); i += 2) {
224                                            if (i != 0) {
225                                                    sb.append(CharPool.COLON);
226                                            }
227    
228                                            sb.append(Character.toLowerCase(hexString.charAt(i)));
229                                            sb.append(Character.toLowerCase(hexString.charAt(i + 1)));
230                                    }
231    
232                                    _macAddresses.add(sb.toString());
233                            }
234                    }
235                    catch (Exception e) {
236                            _log.error("Unable to read local server's MAC addresses");
237    
238                            _log.error(e, e);
239                    }
240    
241                    return new HashSet<>(_macAddresses);
242            }
243    
244            public static byte[] getServerIdBytes() throws Exception {
245                    if (_serverIdBytes != null) {
246                            return _serverIdBytes;
247                    }
248    
249                    File serverIdFile = new File(
250                            LICENSE_REPOSITORY_DIR + "/server/serverId");
251    
252                    if (!serverIdFile.exists()) {
253                            return new byte[0];
254                    }
255    
256                    _serverIdBytes = FileUtil.getBytes(serverIdFile);
257    
258                    return _serverIdBytes;
259            }
260    
261            public static Map<String, String> getServerInfo() {
262                    Map<String, String> serverInfo = new HashMap<>();
263    
264                    serverInfo.put("hostName", PortalUtil.getComputerName());
265                    serverInfo.put("ipAddresses", StringUtil.merge(getIpAddresses()));
266                    serverInfo.put("macAddresses", StringUtil.merge(getMacAddresses()));
267    
268                    return serverInfo;
269            }
270    
271            public static void registerOrder(HttpServletRequest request) {
272                    String orderUuid = ParamUtil.getString(request, "orderUuid");
273                    String productEntryName = ParamUtil.getString(
274                            request, "productEntryName");
275                    int maxServers = ParamUtil.getInteger(request, "maxServers");
276    
277                    List<ClusterNode> clusterNodes = ClusterExecutorUtil.getClusterNodes();
278    
279                    if ((clusterNodes.size() <= 1) || Validator.isNull(productEntryName) ||
280                            Validator.isNull(orderUuid)) {
281    
282                            Map<String, Object> attributes = registerOrder(
283                                    orderUuid, productEntryName, maxServers);
284    
285                            for (Map.Entry<String, Object> entry : attributes.entrySet()) {
286                                    request.setAttribute(entry.getKey(), entry.getValue());
287                            }
288                    }
289                    else {
290                            for (ClusterNode clusterNode : clusterNodes) {
291                                    boolean register = ParamUtil.getBoolean(
292                                            request, clusterNode.getClusterNodeId() + "_register");
293    
294                                    if (!register) {
295                                            continue;
296                                    }
297    
298                                    try {
299                                            _registerClusterOrder(
300                                                    request, clusterNode, orderUuid, productEntryName,
301                                                    maxServers);
302                                    }
303                                    catch (Exception e) {
304                                            _log.error(e, e);
305    
306                                            InetAddress inetAddress = clusterNode.getBindInetAddress();
307    
308                                            String message =
309                                                    "Error contacting " + inetAddress.getHostName();
310    
311                                            if (clusterNode.getPortalPort() != -1) {
312                                                    message +=
313                                                            StringPool.COLON + clusterNode.getPortalPort();
314                                            }
315    
316                                            request.setAttribute(
317                                                    clusterNode.getClusterNodeId() + "_ERROR_MESSAGE",
318                                                    message);
319                                    }
320                            }
321                    }
322            }
323    
324            public static Map<String, Object> registerOrder(
325                    String orderUuid, String productEntryName, int maxServers) {
326    
327                    Map<String, Object> attributes = new HashMap<>();
328    
329                    if (Validator.isNull(orderUuid)) {
330                            return attributes;
331                    }
332    
333                    try {
334                            JSONObject jsonObject = _createRequest(
335                                    orderUuid, productEntryName, maxServers);
336    
337                            String response = sendRequest(jsonObject.toString());
338    
339                            JSONObject responseJSONObject = JSONFactoryUtil.createJSONObject(
340                                    response);
341    
342                            attributes.put(
343                                    "ORDER_PRODUCT_ID", responseJSONObject.getString("productId"));
344                            attributes.put(
345                                    "ORDER_PRODUCTS", _getOrderProducts(responseJSONObject));
346    
347                            String errorMessage = responseJSONObject.getString("errorMessage");
348    
349                            if (Validator.isNotNull(errorMessage)) {
350                                    attributes.put("ERROR_MESSAGE", errorMessage);
351    
352                                    return attributes;
353                            }
354    
355                            String licenseXML = responseJSONObject.getString("licenseXML");
356    
357                            if (Validator.isNotNull(licenseXML)) {
358                                    LicenseManagerUtil.registerLicense(responseJSONObject);
359    
360                                    attributes.clear();
361                                    attributes.put(
362                                            "SUCCESS_MESSAGE",
363                                            "Your license has been successfully registered.");
364                            }
365                    }
366                    catch (Exception e) {
367                            _log.error(e, e);
368    
369                            attributes.put(
370                                    "ERROR_MESSAGE",
371                                    "There was an error contacting " + LICENSE_SERVER_URL);
372                    }
373    
374                    return attributes;
375            }
376    
377            public static String sendRequest(String request) throws Exception {
378                    HttpClient httpClient = null;
379    
380                    HttpClientConnectionManager httpClientConnectionManager =
381                            new BasicHttpClientConnectionManager();
382    
383                    try {
384                            HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
385    
386                            httpClientBuilder.setConnectionManager(httpClientConnectionManager);
387    
388                            String serverURL = LICENSE_SERVER_URL;
389    
390                            if (!serverURL.endsWith(StringPool.SLASH)) {
391                                    serverURL += StringPool.SLASH;
392                            }
393    
394                            serverURL += "osb-portlet/license";
395    
396                            URI uri = new URI(serverURL);
397    
398                            HttpPost httpPost = new HttpPost(uri);
399    
400                            CredentialsProvider credentialsProvider =
401                                    new BasicCredentialsProvider();
402    
403                            HttpHost proxyHttpHost = null;
404    
405                            if (Validator.isNotNull(_PROXY_URL)) {
406                                    if (_log.isInfoEnabled()) {
407                                            _log.info(
408                                                    "Using proxy " + _PROXY_URL + StringPool.COLON +
409                                                            _PROXY_PORT);
410                                    }
411    
412                                    proxyHttpHost = new HttpHost(_PROXY_URL, _PROXY_PORT);
413    
414                                    if (Validator.isNotNull(_PROXY_USER_NAME)) {
415                                            credentialsProvider.setCredentials(
416                                                    new AuthScope(_PROXY_URL, _PROXY_PORT),
417                                                    new UsernamePasswordCredentials(
418                                                            _PROXY_USER_NAME, _PROXY_PASSWORD));
419                                    }
420                            }
421    
422                            httpClientBuilder.setDefaultCredentialsProvider(
423                                    credentialsProvider);
424                            httpClientBuilder.setProxy(proxyHttpHost);
425    
426                            httpClient = httpClientBuilder.build();
427    
428                            ByteArrayEntity byteArrayEntity = new ByteArrayEntity(
429                                    _encryptRequest(serverURL, request));
430    
431                            byteArrayEntity.setContentType(ContentTypes.APPLICATION_JSON);
432    
433                            httpPost.setEntity(byteArrayEntity);
434    
435                            HttpResponse httpResponse = httpClient.execute(httpPost);
436    
437                            HttpEntity httpEntity = httpResponse.getEntity();
438    
439                            String response = _decryptResponse(
440                                    serverURL, httpEntity.getContent());
441    
442                            if (_log.isDebugEnabled()) {
443                                    _log.debug("Server response: " + response);
444                            }
445    
446                            if (Validator.isNull(response)) {
447                                    throw new Exception("Server response is null");
448                            }
449    
450                            return response;
451                    }
452                    finally {
453                            if (httpClient != null) {
454                                    httpClientConnectionManager.shutdown();
455                            }
456                    }
457            }
458    
459            public static void writeServerProperties(byte[] serverIdBytes)
460                    throws Exception {
461    
462                    File serverIdFile = new File(
463                            LICENSE_REPOSITORY_DIR + "/server/serverId");
464    
465                    FileUtil.write(serverIdFile, serverIdBytes);
466            }
467    
468            private static JSONObject _createRequest(
469                            String orderUuid, String productEntryName, int maxServers)
470                    throws Exception {
471    
472                    JSONObject jsonObject = JSONFactoryUtil.createJSONObject();
473    
474                    jsonObject.put("version", 2);
475                    jsonObject.put("orderUuid", orderUuid);
476                    jsonObject.put("liferayVersion", ReleaseInfo.getBuildNumber());
477    
478                    if (Validator.isNull(productEntryName)) {
479                            jsonObject.put(Constants.CMD, "QUERY");
480                    }
481                    else {
482                            jsonObject.put(Constants.CMD, "REGISTER");
483    
484                            if (productEntryName.startsWith("basic")) {
485                                    jsonObject.put("productEntryName", "basic");
486    
487                                    if (productEntryName.equals("basic-cluster")) {
488                                            jsonObject.put("cluster", true);
489                                            jsonObject.put("maxServers", maxServers);
490                                    }
491                                    else if (productEntryName.startsWith("basic-")) {
492                                            String[] productNameArray = StringUtil.split(
493                                                    productEntryName, StringPool.DASH);
494    
495                                            if (productNameArray.length >= 3) {
496                                                    jsonObject.put("offeringEntryId", productNameArray[1]);
497                                                    jsonObject.put("clusterId", productNameArray[2]);
498                                            }
499                                    }
500                            }
501                            else {
502                                    jsonObject.put("productEntryName", productEntryName);
503                            }
504    
505                            jsonObject.put("hostName", PortalUtil.getComputerName());
506                            jsonObject.put("ipAddresses", StringUtil.merge(getIpAddresses()));
507                            jsonObject.put("macAddresses", StringUtil.merge(getMacAddresses()));
508                            jsonObject.put("serverId", Arrays.toString(getServerIdBytes()));
509                    }
510    
511                    return jsonObject;
512            }
513    
514            private static String _decryptResponse(
515                            String serverURL, InputStream inputStream)
516                    throws Exception {
517    
518                    if (serverURL.startsWith(Http.HTTPS)) {
519                            return StringUtil.read(inputStream);
520                    }
521    
522                    byte[] bytes = IOUtils.toByteArray(inputStream);
523    
524                    if ((bytes == null) || (bytes.length <= 0)) {
525                            return null;
526                    }
527    
528                    bytes = Encryptor.decryptUnencodedAsBytes(_symmetricKey, bytes);
529    
530                    return new String(bytes, StringPool.UTF8);
531            }
532    
533            private static byte[] _encryptRequest(String serverURL, String request)
534                    throws Exception {
535    
536                    byte[] bytes = request.getBytes(StringPool.UTF8);
537    
538                    if (serverURL.startsWith(Http.HTTPS)) {
539                            return bytes;
540                    }
541    
542                    JSONObject jsonObject = JSONFactoryUtil.createJSONObject();
543    
544                    bytes = Encryptor.encryptUnencoded(_symmetricKey, bytes);
545    
546                    jsonObject.put("content", Base64.objectToString(bytes));
547                    jsonObject.put("key", _encryptedSymmetricKey);
548    
549                    return jsonObject.toString().getBytes(StringPool.UTF8);
550            }
551    
552            private static Map<String, String> _getOrderProducts(
553                    JSONObject jsonObject) {
554    
555                    JSONObject productsJSONObject = jsonObject.getJSONObject(
556                            "productsJSONObject");
557    
558                    if (productsJSONObject == null) {
559                            return null;
560                    }
561    
562                    Map<String, String> sortedMap = new TreeMap<>(
563                            String.CASE_INSENSITIVE_ORDER);
564    
565                    Iterator<String> itr = productsJSONObject.keys();
566    
567                    while (itr.hasNext()) {
568                            String key = itr.next();
569    
570                            sortedMap.put(key, productsJSONObject.getString(key));
571                    }
572    
573                    return sortedMap;
574            }
575    
576            private static void _initKeys() {
577                    ClassLoader classLoader = ClassLoaderUtil.getPortalClassLoader();
578    
579                    if ((classLoader == null) || (_encryptedSymmetricKey != null)) {
580                            return;
581                    }
582    
583                    try {
584                            URL url = classLoader.getResource(
585                                    "com/liferay/portal/license/public.key");
586    
587                            byte[] bytes = IOUtils.toByteArray(url.openStream());
588    
589                            X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(
590                                    bytes);
591    
592                            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
593    
594                            PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
595    
596                            KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
597    
598                            keyGenerator.init(128, new SecureRandom());
599    
600                            _symmetricKey = keyGenerator.generateKey();
601    
602                            byte[] encryptedSymmetricKey = Encryptor.encryptUnencoded(
603                                    publicKey, _symmetricKey.getEncoded());
604    
605                            _encryptedSymmetricKey = Base64.objectToString(
606                                    encryptedSymmetricKey);
607                    }
608                    catch (Exception e) {
609                            _log.error(e, e);
610                    }
611            }
612    
613            private static void _registerClusterOrder(
614                            HttpServletRequest request, ClusterNode clusterNode,
615                            String orderUuid, String productEntryName, int maxServers)
616                    throws Exception {
617    
618                    MethodHandler methodHandler = new MethodHandler(
619                            _registerOrderMethodKey, orderUuid, productEntryName, maxServers);
620    
621                    ClusterRequest clusterRequest = ClusterRequest.createUnicastRequest(
622                            methodHandler, clusterNode.getClusterNodeId());
623    
624                    FutureClusterResponses futureClusterResponses =
625                            ClusterExecutorUtil.execute(clusterRequest);
626    
627                    ClusterNodeResponses clusterNodeResponses = futureClusterResponses.get(
628                            20000, TimeUnit.MILLISECONDS);
629    
630                    ClusterNodeResponse clusterNodeResponse =
631                            clusterNodeResponses.getClusterResponse(
632                                    clusterNode.getClusterNodeId());
633    
634                    Map<String, Object> attributes =
635                            (Map<String, Object>)clusterNodeResponse.getResult();
636    
637                    for (Map.Entry<String, Object> entry : attributes.entrySet()) {
638                            request.setAttribute(
639                                    clusterNode.getClusterNodeId() + StringPool.UNDERLINE +
640                                            entry.getKey(),
641                                    entry.getValue());
642                    }
643            }
644    
645            private static final String _PROXY_PASSWORD = GetterUtil.getString(
646                    PropsUtil.get("license.proxy.password"));
647    
648            private static final int _PROXY_PORT = GetterUtil.getInteger(
649                    PropsUtil.get("license.proxy.port"), 80);
650    
651            private static final String _PROXY_URL = PropsUtil.get("license.proxy.url");
652    
653            private static final String _PROXY_USER_NAME = GetterUtil.getString(
654                    PropsUtil.get("license.proxy.username"));
655    
656            private static final Log _log = LogFactoryUtil.getLog(LicenseUtil.class);
657    
658            private static String _encryptedSymmetricKey;
659            private static final MethodHandler _getServerInfoMethodHandler =
660                    new MethodHandler(new MethodKey(LicenseUtil.class, "getServerInfo"));
661            private static Set<String> _ipAddresses;
662            private static Set<String> _macAddresses;
663            private static final MethodKey _registerOrderMethodKey = new MethodKey(
664                    LicenseUtil.class, "registerOrder", String.class, String.class,
665                    int.class);
666            private static byte[] _serverIdBytes;
667            private static Key _symmetricKey;
668    
669            static {
670                    _initKeys();
671            }
672    
673    }