1 /*
2  * Copyright (C) 2017 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.settingslib.wifi.AccessPoint;
36 import com.android.tv.settings.R;
37 import com.android.tv.settings.connectivity.util.WifiSecurityUtil;
38 
39 import java.util.List;
40 import java.util.regex.Matcher;
41 import java.util.regex.Pattern;
42 
43 /**
44  * Helper class that deals with Wi-fi configuration.
45  */
46 public final class WifiConfigHelper {
47 
48     private static final String TAG = "WifiConfigHelper";
49     private static final boolean DEBUG = false;
50 
51     // Allows underscore char to supports proxies that do not
52     // follow the spec
53     private static final String HC = "a-zA-Z0-9\\_";
54 
55     // Matches blank input, ips, and domain names
56     private static final String HOSTNAME_REGEXP =
57             "^$|^[" + HC + "]+(\\-[" + HC + "]+)*(\\.[" + HC + "]+(\\-[" + HC + "]+)*)*$";
58     private static final Pattern HOSTNAME_PATTERN;
59     private static final String EXCLUSION_REGEXP =
60             "$|^(\\*)?\\.?[" + HC + "]+(\\-[" + HC + "]+)*(\\.[" + HC + "]+(\\-[" + HC + "]+)*)*$";
61     private static final Pattern EXCLUSION_PATTERN;
62 
63     static {
64         HOSTNAME_PATTERN = Pattern.compile(HOSTNAME_REGEXP);
65         EXCLUSION_PATTERN = Pattern.compile(EXCLUSION_REGEXP);
66     }
67 
WifiConfigHelper()68     private WifiConfigHelper() {
69     }
70 
71     /**
72      * Set configuration ssid.
73      *
74      * @param config configuration
75      * @param ssid   network ssid
76      */
setConfigSsid(WifiConfiguration config, String ssid)77     public static void setConfigSsid(WifiConfiguration config, String ssid) {
78         config.SSID = AccessPoint.convertToQuotedString(ssid);
79     }
80 
81     /**
82      * Set configuration key managment by security.
83      */
setConfigKeyManagementBySecurity( WifiConfiguration config, int security)84     public static void setConfigKeyManagementBySecurity(
85             WifiConfiguration config, int security) {
86         config.allowedKeyManagement.clear();
87         config.allowedAuthAlgorithms.clear();
88         switch (security) {
89             case AccessPoint.SECURITY_NONE:
90                 config.allowedKeyManagement.set(KeyMgmt.NONE);
91                 break;
92             case AccessPoint.SECURITY_WEP:
93                 config.allowedKeyManagement.set(KeyMgmt.NONE);
94                 config.allowedAuthAlgorithms.set(AuthAlgorithm.OPEN);
95                 config.allowedAuthAlgorithms.set(AuthAlgorithm.SHARED);
96                 break;
97             case AccessPoint.SECURITY_PSK:
98                 config.allowedKeyManagement.set(KeyMgmt.WPA_PSK);
99                 break;
100             case AccessPoint.SECURITY_EAP:
101                 config.allowedKeyManagement.set(KeyMgmt.WPA_EAP);
102                 config.allowedKeyManagement.set(KeyMgmt.IEEE8021X);
103                 break;
104         }
105     }
106 
107     /**
108      * validate syntax of hostname and port entries
109      *
110      * @return 0 on success, string resource ID on failure
111      */
validate(String hostname, String port, String exclList)112     public static int validate(String hostname, String port, String exclList) {
113         Matcher match = HOSTNAME_PATTERN.matcher(hostname);
114         String[] exclListArray = exclList.split(",");
115 
116         if (!match.matches()) return R.string.proxy_error_invalid_host;
117 
118         for (String excl : exclListArray) {
119             Matcher m = EXCLUSION_PATTERN.matcher(excl);
120             if (!m.matches()) return R.string.proxy_error_invalid_exclusion_list;
121         }
122 
123         if (hostname.length() > 0 && port.length() == 0) {
124             return R.string.proxy_error_empty_port;
125         }
126 
127         if (port.length() > 0) {
128             if (hostname.length() == 0) {
129                 return R.string.proxy_error_empty_host_set_port;
130             }
131             int portVal = -1;
132             try {
133                 portVal = Integer.parseInt(port);
134             } catch (NumberFormatException ex) {
135                 return R.string.proxy_error_invalid_port;
136             }
137             if (portVal <= 0 || portVal > 0xFFFF) {
138                 return R.string.proxy_error_invalid_port;
139             }
140         }
141         return 0;
142     }
143 
144     /**
145      * Get {@link WifiConfiguration} based upon the {@link WifiManager} and networkId.
146      * @param wifiManager
147      * @param networkId the id of the network.
148      * @return the {@link WifiConfiguration} of the specified network.
149      */
getWifiConfiguration(WifiManager wifiManager, int networkId)150     public static WifiConfiguration getWifiConfiguration(WifiManager wifiManager, int networkId) {
151         List<WifiConfiguration> configuredNetworks = wifiManager.getConfiguredNetworks();
152         if (configuredNetworks != null) {
153             for (WifiConfiguration configuredNetwork : configuredNetworks) {
154                 if (configuredNetwork.networkId == networkId) {
155                     return configuredNetwork;
156                 }
157             }
158         }
159         return null;
160     }
161 
162     /**
163      * Did this config come out of the supplicant?  NOT "Is the config currently in the supplicant?"
164      */
isNetworkSaved(WifiConfiguration config)165     public static boolean isNetworkSaved(WifiConfiguration config) {
166         return config != null && config.networkId > -1;
167     }
168 
169     /**
170      * Return the configured network that matches the ssid/security pair, or create one.
171      */
getConfiguration(Context context, String ssid, int security)172     public static WifiConfiguration getConfiguration(Context context, String ssid, int security) {
173         WifiConfiguration config = getFromConfiguredNetworks(context, ssid, security);
174 
175         if (config == null) {
176             // No configured network found; populate a new one with the provided ssid / security.
177             config = new WifiConfiguration();
178             setConfigSsid(config, ssid);
179             setConfigKeyManagementBySecurity(config, security);
180         }
181         return config;
182     }
183 
184     /**
185      * Save a wifi configuration.
186      */
saveConfiguration(Context context, WifiConfiguration config)187     public static boolean saveConfiguration(Context context, WifiConfiguration config) {
188         if (config == null) {
189             return false;
190         }
191 
192         WifiManager wifiMan = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
193         int networkId = wifiMan.addNetwork(config);
194         if (networkId == -1) {
195             if (DEBUG) Log.e(TAG, "failed to add network: " + config.toString());
196             return false;
197         }
198 
199         if (!wifiMan.enableNetwork(networkId, false)) {
200             if (DEBUG) Log.e(TAG, "enable network failed: " + networkId + "; " + config.toString());
201             return false;
202         }
203 
204         if (!wifiMan.saveConfiguration()) {
205             if (DEBUG) Log.e(TAG, "failed to save: " + config.toString());
206             return false;
207         }
208 
209         if (DEBUG) Log.d(TAG, "saved network: " + config.toString());
210         return true;
211     }
212 
213     /**
214      * @return A matching WifiConfiguration from the list of configured
215      * networks, or null if no matching network is found.
216      */
getFromConfiguredNetworks(Context context, String ssid, int security)217     private static WifiConfiguration getFromConfiguredNetworks(Context context,
218             String ssid,
219             int security) {
220         WifiManager wifiMan = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
221         List<WifiConfiguration> configuredNetworks = wifiMan.getConfiguredNetworks();
222         if (configuredNetworks != null) {
223             for (WifiConfiguration configuredNetwork : configuredNetworks) {
224                 if (configuredNetwork == null || configuredNetwork.SSID == null) {
225                     continue;  // Does this ever really happen?
226                 }
227 
228                 // If the SSID and the security match, that's our network.
229                 String configuredSsid = WifiInfo.sanitizeSsid(configuredNetwork.SSID);
230                 if (TextUtils.equals(configuredSsid, ssid)) {
231                     int configuredSecurity = WifiSecurityUtil.getSecurity(configuredNetwork);
232                     if (configuredSecurity == security) {
233                         return configuredNetwork;
234                     }
235                 }
236             }
237         }
238 
239         return null;
240     }
241 
242     /**
243      * @param context Context of caller
244      * @param config  The WiFi config.
245      * @return true if Settings cannot modify the config due to lockDown.
246      */
isNetworkLockedDown(Context context, WifiConfiguration config)247     public static boolean isNetworkLockedDown(Context context, WifiConfiguration config) {
248         if (config == null) {
249             return false;
250         }
251 
252         final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
253         final PackageManager pm = context.getPackageManager();
254         final UserManager um = context.getSystemService(UserManager.class);
255 
256         // Check if device has DPM capability. If it has and dpm is still null, then we
257         // treat this case with suspicion and bail out.
258         if (pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN) && dpm == null) {
259             return true;
260         }
261 
262         boolean isConfigEligibleForLockdown = false;
263         if (dpm != null) {
264             final ComponentName deviceOwner = dpm.getDeviceOwnerComponentOnAnyUser();
265             if (deviceOwner != null) {
266                 final int deviceOwnerUserId = dpm.getDeviceOwnerUserId();
267                 try {
268                     final int deviceOwnerUid = pm.getPackageUidAsUser(deviceOwner.getPackageName(),
269                             deviceOwnerUserId);
270                     isConfigEligibleForLockdown = deviceOwnerUid == config.creatorUid;
271                 } catch (PackageManager.NameNotFoundException e) {
272                     // don't care
273                 }
274             } else if (dpm.isOrganizationOwnedDeviceWithManagedProfile()) {
275                 int profileOwnerUserId = getManagedProfileId(um, UserHandle.myUserId());
276                 final ComponentName profileOwner = dpm.getProfileOwnerAsUser(profileOwnerUserId);
277                 if (profileOwner != null) {
278                     try {
279                         final int profileOwnerUid = pm.getPackageUidAsUser(
280                                 profileOwner.getPackageName(), profileOwnerUserId);
281                         isConfigEligibleForLockdown = profileOwnerUid == config.creatorUid;
282                     } catch (PackageManager.NameNotFoundException e) {
283                         // don't care
284                     }
285                 }
286             }
287         }
288         if (!isConfigEligibleForLockdown) {
289             return false;
290         }
291 
292         final ContentResolver resolver = context.getContentResolver();
293         final boolean isLockdownFeatureEnabled = Settings.Global.getInt(resolver,
294                 Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) != 0;
295         return isLockdownFeatureEnabled;
296     }
297 
298     /**
299      * Retrieves the id for the given user's  profile.
300      *
301      * @return the profile id or UserHandle.USER_NULL if there is none.
302      */
getManagedProfileId(UserManager um, int parentUserId)303     private static int getManagedProfileId(UserManager um, int parentUserId) {
304         final int[] profileIds = um.getProfileIdsWithDisabled(parentUserId);
305         for (int profileId : profileIds) {
306             if (profileId != parentUserId && um.isManagedProfile(profileId)) {
307                 return profileId;
308             }
309         }
310         return UserHandle.USER_NULL;
311     }
312 }
313