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