1 /*
2  * Copyright (C) 2021 The Android Open Source Project
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, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tv.settings.connectivity;
18 
19 import android.app.admin.DevicePolicyManager;
20 import android.content.ComponentName;
21 import android.content.ContentResolver;
22 import android.content.Context;
23 import android.content.pm.PackageManager;
24 import android.net.wifi.WifiConfiguration;
25 import android.net.wifi.WifiConfiguration.AuthAlgorithm;
26 import android.net.wifi.WifiConfiguration.KeyMgmt;
27 import android.net.wifi.WifiInfo;
28 import android.net.wifi.WifiManager;
29 import android.os.UserHandle;
30 import android.os.UserManager;
31 import android.provider.Settings;
32 import android.text.TextUtils;
33 import android.util.Log;
34 
35 import com.android.tv.settings.library.network.AccessPoint;
36 import com.android.tv.settings.R;
37 import com.android.tv.settings.connectivity.util.WifiSecurityUtil;
38 import com.android.wifitrackerlib.WifiEntry;
39 
40 import java.util.List;
41 import java.util.regex.Matcher;
42 import java.util.regex.Pattern;
43 
44 /**
45  * Helper class that deals with Wi-fi configuration.
46  */
47 public final class WifiConfigHelper {
48 
49     private static final String TAG = "WifiConfigHelper";
50     private static final boolean DEBUG = false;
51 
52     // Allows underscore char to supports proxies that do not
53     // follow the spec
54     private static final String HC = "a-zA-Z0-9\\_";
55 
56     // Matches blank input, ips, and domain names
57     private static final String HOSTNAME_REGEXP =
58             "^$|^[" + HC + "]+(\\-[" + HC + "]+)*(\\.[" + HC + "]+(\\-[" + HC + "]+)*)*$";
59     private static final Pattern HOSTNAME_PATTERN;
60     private static final String EXCLUSION_REGEXP =
61             "$|^(\\*)?\\.?[" + HC + "]+(\\-[" + HC + "]+)*(\\.[" + HC + "]+(\\-[" + HC + "]+)*)*$";
62     private static final Pattern EXCLUSION_PATTERN;
63 
64     private static final String BYPASS_PROXY_EXCLUDE_REGEX =
65             "[a-zA-Z0-9*]+(\\-[a-zA-Z0-9*]+)*(\\.[a-zA-Z0-9*]+(\\-[a-zA-Z0-9*]+)*)*";
66     private static final String BYPASS_PROXY_EXCLUDE_LIST_REGEXP = "^$|^"
67             + BYPASS_PROXY_EXCLUDE_REGEX + "(," + BYPASS_PROXY_EXCLUDE_REGEX + ")*$";
68     private static final Pattern BYPASS_PROXY_EXCLUSION_PATTERN;
69 
70     static {
71         HOSTNAME_PATTERN = Pattern.compile(HOSTNAME_REGEXP);
72         EXCLUSION_PATTERN = Pattern.compile(EXCLUSION_REGEXP);
73         BYPASS_PROXY_EXCLUSION_PATTERN = Pattern.compile(BYPASS_PROXY_EXCLUDE_LIST_REGEXP);
74     }
75 
WifiConfigHelper()76     private WifiConfigHelper() {
77     }
78 
79     /**
80      * Set configuration ssid.
81      *
82      * @param config configuration
83      * @param ssid   network ssid
84      */
setConfigSsid(WifiConfiguration config, String ssid)85     public static void setConfigSsid(WifiConfiguration config, String ssid) {
86         config.SSID = AccessPoint.convertToQuotedString(ssid);
87     }
88 
89     /**
90      * Set configuration key managment by security.
91      */
setConfigKeyManagementBySecurity( WifiConfiguration config, int security)92     public static void setConfigKeyManagementBySecurity(
93             WifiConfiguration config, int security) {
94         // WifiInfo and WifiConfiguration constants are the same so we don't need to translate
95         // between them.
96         config.setSecurityParams(security);
97     }
98 
99     /**
100      * validate syntax of hostname and port entries
101      *
102      * @param hostname host name to be used
103      * @param port port to be used
104      * @param exclList what should be accepted as input
105      * @return 0 on success, string resource ID on failure
106      */
validate(String hostname, String port, String exclList)107     public static int validate(String hostname, String port, String exclList) {
108         return validate(hostname, port, exclList, false);
109     }
110 
111     /**
112      * validate syntax of hostname and port entries
113      *
114      * @param hostname host name to be used
115      * @param port port to be used
116      * @param exclList what should be accepted as input
117      * @param forProxyCheck if extra check for bypass proxy should be done
118      * @return 0 on success, string resource ID on failure
119      */
validate(String hostname, String port, String exclList, boolean forProxyCheck)120     public static int validate(String hostname, String port, String exclList,
121                                boolean forProxyCheck) {
122         if (DEBUG) {
123             Log.i(TAG, "validate, hostname: " + hostname + ", for proxy=" + forProxyCheck);
124         }
125         Matcher match = HOSTNAME_PATTERN.matcher(hostname);
126         String[] exclListArray = exclList.split(",");
127 
128         if (!match.matches()) return R.string.proxy_error_invalid_host;
129 
130         for (String excl : exclListArray) {
131             Matcher m;
132             if (forProxyCheck) {
133                 m = BYPASS_PROXY_EXCLUSION_PATTERN.matcher(excl);
134             } else {
135                 m = EXCLUSION_PATTERN.matcher(excl);
136             }
137             if (!m.matches()) {
138                 return R.string.proxy_error_invalid_exclusion_list;
139             }
140         }
141 
142         if (hostname.length() > 0 && port.length() == 0) {
143             return R.string.proxy_error_empty_port;
144         }
145 
146         if (port.length() > 0) {
147             if (hostname.length() == 0) {
148                 return R.string.proxy_error_empty_host_set_port;
149             }
150             int portVal = -1;
151             try {
152                 portVal = Integer.parseInt(port);
153             } catch (NumberFormatException ex) {
154                 return R.string.proxy_error_invalid_port;
155             }
156             if (portVal <= 0 || portVal > 0xFFFF) {
157                 return R.string.proxy_error_invalid_port;
158             }
159         }
160         return 0;
161     }
162 
163     /**
164      * Get {@link WifiConfiguration} based upon the {@link WifiManager} and networkId.
165      *
166      * @param networkId the id of the network.
167      * @return the {@link WifiConfiguration} of the specified network.
168      */
getWifiConfiguration(WifiManager wifiManager, int networkId)169     public static WifiConfiguration getWifiConfiguration(WifiManager wifiManager, int networkId) {
170         List<WifiConfiguration> configuredNetworks = wifiManager.getConfiguredNetworks();
171         if (configuredNetworks != null) {
172             for (WifiConfiguration configuredNetwork : configuredNetworks) {
173                 if (configuredNetwork.networkId == networkId) {
174                     return configuredNetwork;
175                 }
176             }
177         }
178         return null;
179     }
180 
181     /**
182      * Return the configured network that matches the ssid/security pair, or create one.
183      */
getConfiguration(String ssid, int security)184     public static WifiConfiguration getConfiguration(String ssid, int security) {
185         WifiConfiguration config = new WifiConfiguration();
186         setConfigSsid(config, ssid);
187         setConfigKeyManagementBySecurity(config, security);
188         return config;
189     }
190 
191     /**
192      * @param context Context of caller
193      * @param config  The WiFi config.
194      * @return true if Settings cannot modify the config due to lockDown.
195      */
isNetworkLockedDown(Context context, WifiConfiguration config)196     public static boolean isNetworkLockedDown(Context context, WifiConfiguration config) {
197         if (config == null) {
198             return false;
199         }
200 
201         final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
202         final PackageManager pm = context.getPackageManager();
203         final UserManager um = context.getSystemService(UserManager.class);
204 
205         // Check if device has DPM capability. If it has and dpm is still null, then we
206         // treat this case with suspicion and bail out.
207         if (pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN) && dpm == null) {
208             return true;
209         }
210 
211         boolean isConfigEligibleForLockdown = false;
212         if (dpm != null) {
213             final ComponentName deviceOwner = dpm.getDeviceOwnerComponentOnAnyUser();
214             if (deviceOwner != null) {
215                 final int deviceOwnerUserId = dpm.getDeviceOwnerUserId();
216                 try {
217                     final int deviceOwnerUid = pm.getPackageUidAsUser(deviceOwner.getPackageName(),
218                             deviceOwnerUserId);
219                     isConfigEligibleForLockdown = deviceOwnerUid == config.creatorUid;
220                 } catch (PackageManager.NameNotFoundException e) {
221                     // don't care
222                 }
223             } else if (dpm.isOrganizationOwnedDeviceWithManagedProfile()) {
224                 int profileOwnerUserId = getManagedProfileId(um, UserHandle.myUserId());
225                 final ComponentName profileOwner = dpm.getProfileOwnerAsUser(profileOwnerUserId);
226                 if (profileOwner != null) {
227                     try {
228                         final int profileOwnerUid = pm.getPackageUidAsUser(
229                                 profileOwner.getPackageName(), profileOwnerUserId);
230                         isConfigEligibleForLockdown = profileOwnerUid == config.creatorUid;
231                     } catch (PackageManager.NameNotFoundException e) {
232                         // don't care
233                     }
234                 }
235             }
236         }
237         if (!isConfigEligibleForLockdown) {
238             return false;
239         }
240 
241         final ContentResolver resolver = context.getContentResolver();
242         final boolean isLockdownFeatureEnabled = Settings.Global.getInt(resolver,
243                 Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) != 0;
244         return isLockdownFeatureEnabled;
245     }
246 
247     /**
248      * Retrieves the id for the given user's  profile.
249      *
250      * @return the profile id or UserHandle.USER_NULL if there is none.
251      */
getManagedProfileId(UserManager um, int parentUserId)252     private static int getManagedProfileId(UserManager um, int parentUserId) {
253         final int[] profileIds = um.getProfileIdsWithDisabled(parentUserId);
254         for (int profileId : profileIds) {
255             if (profileId != parentUserId && um.isManagedProfile(profileId)) {
256                 return profileId;
257             }
258         }
259         return UserHandle.USER_NULL;
260     }
261 }
262