1/*
2 * Copyright (C) 2014 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
17package com.android.server;
18
19import android.content.pm.FeatureInfo;
20import android.os.*;
21import android.os.Process;
22import android.util.ArrayMap;
23import android.util.ArraySet;
24import android.util.Slog;
25import android.util.SparseArray;
26import android.util.Xml;
27import com.android.internal.util.XmlUtils;
28import org.xmlpull.v1.XmlPullParser;
29import org.xmlpull.v1.XmlPullParserException;
30
31import java.io.File;
32import java.io.FileNotFoundException;
33import java.io.FileReader;
34import java.io.IOException;
35import java.util.HashMap;
36import java.util.HashSet;
37
38import static com.android.internal.util.ArrayUtils.appendInt;
39
40/**
41 * Loads global system configuration info.
42 */
43public class SystemConfig {
44    static final String TAG = "SystemConfig";
45
46    static SystemConfig sInstance;
47
48    // Group-ids that are given to all packages as read from etc/permissions/*.xml.
49    int[] mGlobalGids;
50
51    // These are the built-in uid -> permission mappings that were read from the
52    // system configuration files.
53    final SparseArray<HashSet<String>> mSystemPermissions = new SparseArray<>();
54
55    // These are the built-in shared libraries that were read from the
56    // system configuration files.  Keys are the library names; strings are the
57    // paths to the libraries.
58    final ArrayMap<String, String> mSharedLibraries  = new ArrayMap<>();
59
60    // These are the features this devices supports that were read from the
61    // system configuration files.
62    final HashMap<String, FeatureInfo> mAvailableFeatures = new HashMap<>();
63
64    public static final class PermissionEntry {
65        public final String name;
66        public int[] gids;
67
68        PermissionEntry(String _name) {
69            name = _name;
70        }
71    }
72
73    // These are the permission -> gid mappings that were read from the
74    // system configuration files.
75    final ArrayMap<String, PermissionEntry> mPermissions = new ArrayMap<>();
76
77    // These are the packages that are white-listed to be able to run in the
78    // background while in power save mode, as read from the configuration files.
79    final ArraySet<String> mAllowInPowerSave = new ArraySet<>();
80
81    // These are the app package names that should not allow IME switching.
82    final ArraySet<String> mFixedImeApps = new ArraySet<>();
83
84    public static SystemConfig getInstance() {
85        synchronized (SystemConfig.class) {
86            if (sInstance == null) {
87                sInstance = new SystemConfig();
88            }
89            return sInstance;
90        }
91    }
92
93    public int[] getGlobalGids() {
94        return mGlobalGids;
95    }
96
97    public SparseArray<HashSet<String>> getSystemPermissions() {
98        return mSystemPermissions;
99    }
100
101    public ArrayMap<String, String> getSharedLibraries() {
102        return mSharedLibraries;
103    }
104
105    public HashMap<String, FeatureInfo> getAvailableFeatures() {
106        return mAvailableFeatures;
107    }
108
109    public ArrayMap<String, PermissionEntry> getPermissions() {
110        return mPermissions;
111    }
112
113    public ArraySet<String> getAllowInPowerSave() {
114        return mAllowInPowerSave;
115    }
116
117    public ArraySet<String> getFixedImeApps() {
118        return mFixedImeApps;
119    }
120
121    SystemConfig() {
122        // Read configuration from system
123        readPermissions(Environment.buildPath(
124                Environment.getRootDirectory(), "etc", "sysconfig"), false);
125        // Read configuration from the old permissions dir
126        readPermissions(Environment.buildPath(
127                Environment.getRootDirectory(), "etc", "permissions"), false);
128        // Only read features from OEM config
129        readPermissions(Environment.buildPath(
130                Environment.getOemDirectory(), "etc", "sysconfig"), true);
131        readPermissions(Environment.buildPath(
132                Environment.getOemDirectory(), "etc", "permissions"), true);
133    }
134
135    void readPermissions(File libraryDir, boolean onlyFeatures) {
136        // Read permissions from given directory.
137        if (!libraryDir.exists() || !libraryDir.isDirectory()) {
138            if (!onlyFeatures) {
139                Slog.w(TAG, "No directory " + libraryDir + ", skipping");
140            }
141            return;
142        }
143        if (!libraryDir.canRead()) {
144            Slog.w(TAG, "Directory " + libraryDir + " cannot be read");
145            return;
146        }
147
148        // Iterate over the files in the directory and scan .xml files
149        for (File f : libraryDir.listFiles()) {
150            // We'll read platform.xml last
151            if (f.getPath().endsWith("etc/permissions/platform.xml")) {
152                continue;
153            }
154
155            if (!f.getPath().endsWith(".xml")) {
156                Slog.i(TAG, "Non-xml file " + f + " in " + libraryDir + " directory, ignoring");
157                continue;
158            }
159            if (!f.canRead()) {
160                Slog.w(TAG, "Permissions library file " + f + " cannot be read");
161                continue;
162            }
163
164            readPermissionsFromXml(f, onlyFeatures);
165        }
166
167        // Read permissions from .../etc/permissions/platform.xml last so it will take precedence
168        final File permFile = new File(Environment.getRootDirectory(),
169                "etc/permissions/platform.xml");
170        readPermissionsFromXml(permFile, onlyFeatures);
171    }
172
173    private void readPermissionsFromXml(File permFile, boolean onlyFeatures) {
174        FileReader permReader = null;
175        try {
176            permReader = new FileReader(permFile);
177        } catch (FileNotFoundException e) {
178            Slog.w(TAG, "Couldn't find or open permissions file " + permFile);
179            return;
180        }
181
182        try {
183            XmlPullParser parser = Xml.newPullParser();
184            parser.setInput(permReader);
185
186            int type;
187            while ((type=parser.next()) != parser.START_TAG
188                       && type != parser.END_DOCUMENT) {
189                ;
190            }
191
192            if (type != parser.START_TAG) {
193                throw new XmlPullParserException("No start tag found");
194            }
195
196            if (!parser.getName().equals("permissions") && !parser.getName().equals("config")) {
197                throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() +
198                        ", expected 'permissions' or 'config'");
199            }
200
201            while (true) {
202                XmlUtils.nextElement(parser);
203                if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
204                    break;
205                }
206
207                String name = parser.getName();
208                if ("group".equals(name) && !onlyFeatures) {
209                    String gidStr = parser.getAttributeValue(null, "gid");
210                    if (gidStr != null) {
211                        int gid = android.os.Process.getGidForName(gidStr);
212                        mGlobalGids = appendInt(mGlobalGids, gid);
213                    } else {
214                        Slog.w(TAG, "<group> without gid at "
215                                + parser.getPositionDescription());
216                    }
217
218                    XmlUtils.skipCurrentTag(parser);
219                    continue;
220                } else if ("permission".equals(name) && !onlyFeatures) {
221                    String perm = parser.getAttributeValue(null, "name");
222                    if (perm == null) {
223                        Slog.w(TAG, "<permission> without name at "
224                                + parser.getPositionDescription());
225                        XmlUtils.skipCurrentTag(parser);
226                        continue;
227                    }
228                    perm = perm.intern();
229                    readPermission(parser, perm);
230
231                } else if ("assign-permission".equals(name) && !onlyFeatures) {
232                    String perm = parser.getAttributeValue(null, "name");
233                    if (perm == null) {
234                        Slog.w(TAG, "<assign-permission> without name at "
235                                + parser.getPositionDescription());
236                        XmlUtils.skipCurrentTag(parser);
237                        continue;
238                    }
239                    String uidStr = parser.getAttributeValue(null, "uid");
240                    if (uidStr == null) {
241                        Slog.w(TAG, "<assign-permission> without uid at "
242                                + parser.getPositionDescription());
243                        XmlUtils.skipCurrentTag(parser);
244                        continue;
245                    }
246                    int uid = Process.getUidForName(uidStr);
247                    if (uid < 0) {
248                        Slog.w(TAG, "<assign-permission> with unknown uid \""
249                                + uidStr + "\" at "
250                                + parser.getPositionDescription());
251                        XmlUtils.skipCurrentTag(parser);
252                        continue;
253                    }
254                    perm = perm.intern();
255                    HashSet<String> perms = mSystemPermissions.get(uid);
256                    if (perms == null) {
257                        perms = new HashSet<String>();
258                        mSystemPermissions.put(uid, perms);
259                    }
260                    perms.add(perm);
261                    XmlUtils.skipCurrentTag(parser);
262
263                } else if ("library".equals(name) && !onlyFeatures) {
264                    String lname = parser.getAttributeValue(null, "name");
265                    String lfile = parser.getAttributeValue(null, "file");
266                    if (lname == null) {
267                        Slog.w(TAG, "<library> without name at "
268                                + parser.getPositionDescription());
269                    } else if (lfile == null) {
270                        Slog.w(TAG, "<library> without file at "
271                                + parser.getPositionDescription());
272                    } else {
273                        //Log.i(TAG, "Got library " + lname + " in " + lfile);
274                        mSharedLibraries.put(lname, lfile);
275                    }
276                    XmlUtils.skipCurrentTag(parser);
277                    continue;
278
279                } else if ("feature".equals(name)) {
280                    String fname = parser.getAttributeValue(null, "name");
281                    if (fname == null) {
282                        Slog.w(TAG, "<feature> without name at "
283                                + parser.getPositionDescription());
284                    } else {
285                        //Log.i(TAG, "Got feature " + fname);
286                        FeatureInfo fi = new FeatureInfo();
287                        fi.name = fname;
288                        mAvailableFeatures.put(fname, fi);
289                    }
290                    XmlUtils.skipCurrentTag(parser);
291                    continue;
292
293                } else if ("allow-in-power-save".equals(name)) {
294                    String pkgname = parser.getAttributeValue(null, "package");
295                    if (pkgname == null) {
296                        Slog.w(TAG, "<allow-in-power-save> without package at "
297                                + parser.getPositionDescription());
298                    } else {
299                        mAllowInPowerSave.add(pkgname);
300                    }
301                    XmlUtils.skipCurrentTag(parser);
302                    continue;
303
304                } else if ("fixed-ime-app".equals(name)) {
305                    String pkgname = parser.getAttributeValue(null, "package");
306                    if (pkgname == null) {
307                        Slog.w(TAG, "<fixed-ime-app> without package at "
308                                + parser.getPositionDescription());
309                    } else {
310                        mFixedImeApps.add(pkgname);
311                    }
312                    XmlUtils.skipCurrentTag(parser);
313                    continue;
314
315                } else {
316                    XmlUtils.skipCurrentTag(parser);
317                    continue;
318                }
319
320            }
321            permReader.close();
322        } catch (XmlPullParserException e) {
323            Slog.w(TAG, "Got execption parsing permissions.", e);
324        } catch (IOException e) {
325            Slog.w(TAG, "Got execption parsing permissions.", e);
326        }
327    }
328
329    void readPermission(XmlPullParser parser, String name)
330            throws IOException, XmlPullParserException {
331
332        name = name.intern();
333
334        PermissionEntry perm = mPermissions.get(name);
335        if (perm == null) {
336            perm = new PermissionEntry(name);
337            mPermissions.put(name, perm);
338        }
339        int outerDepth = parser.getDepth();
340        int type;
341        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
342               && (type != XmlPullParser.END_TAG
343                       || parser.getDepth() > outerDepth)) {
344            if (type == XmlPullParser.END_TAG
345                    || type == XmlPullParser.TEXT) {
346                continue;
347            }
348
349            String tagName = parser.getName();
350            if ("group".equals(tagName)) {
351                String gidStr = parser.getAttributeValue(null, "gid");
352                if (gidStr != null) {
353                    int gid = Process.getGidForName(gidStr);
354                    perm.gids = appendInt(perm.gids, gid);
355                } else {
356                    Slog.w(TAG, "<group> without gid at "
357                            + parser.getPositionDescription());
358                }
359            }
360            XmlUtils.skipCurrentTag(parser);
361        }
362    }
363}
364