1 /*
2  * Copyright (C) 2010 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.services.telephony.sip;
18 
19 import com.android.internal.os.AtomicFile;
20 
21 import android.content.Context;
22 import android.net.sip.SipProfile;
23 import android.text.TextUtils;
24 import android.util.EventLog;
25 import android.util.Log;
26 
27 import java.io.File;
28 import java.io.FileOutputStream;
29 import java.io.IOException;
30 import java.io.ObjectInputStream;
31 import java.io.ObjectOutputStream;
32 import java.util.ArrayList;
33 import java.util.Collections;
34 import java.util.List;
35 
36 /**
37  * Utility class that helps perform operations on the SipProfile database.
38  */
39 class SipProfileDb {
40     private static final String PREFIX = "[SipProfileDb] ";
41     private static final boolean VERBOSE = false; /* STOP SHIP if true */
42 
43     private static final String PROFILES_DIR = "/profiles/";
44     private static final String PROFILE_OBJ_FILE = ".pobj";
45 
46     private static final String SCHEME_PREFIX = "sip:";
47 
48     private Context mContext;
49     private String mProfilesDirectory;
50     private SipPreferences mSipPreferences;
51     private int mProfilesCount = -1;
52 
SipProfileDb(Context context)53     public SipProfileDb(Context context) {
54         // Sip Profile Db should always reference CE storage.
55         mContext = context.createCredentialProtectedStorageContext();
56         setupDatabase();
57     }
58 
59     // Only should be used during migration from M->N to move database
accessDEStorageForMigration()60     public void accessDEStorageForMigration() {
61         mContext = mContext.createDeviceProtectedStorageContext();
62         setupDatabase();
63     }
64 
setupDatabase()65     private void setupDatabase() {
66         mProfilesDirectory = mContext.getFilesDir().getAbsolutePath() + PROFILES_DIR;
67         mSipPreferences = new SipPreferences(mContext);
68     }
69 
deleteProfile(SipProfile p)70     public void deleteProfile(SipProfile p) throws IOException {
71         synchronized(SipProfileDb.class) {
72             File profileFile = new File(mProfilesDirectory, p.getProfileName());
73             if (!isChild(new File(mProfilesDirectory), profileFile)) {
74                 throw new IOException("Invalid Profile Credentials!");
75             }
76             deleteProfile(profileFile);
77             if (mProfilesCount < 0) retrieveSipProfileListInternal();
78         }
79     }
80 
deleteProfile(File file)81     private void deleteProfile(File file) {
82         if (file.isDirectory()) {
83             for (File child : file.listFiles()) deleteProfile(child);
84         }
85         file.delete();
86     }
87 
cleanupUponMigration()88     public void cleanupUponMigration() {
89         // Remove empty .../profiles/ directory
90         File dbDir = new File(mProfilesDirectory);
91         if(dbDir.isDirectory()) {
92             dbDir.delete();
93         }
94         // Remove SharedPreferences file as well
95         mSipPreferences.clearSharedPreferences();
96     }
97 
saveProfile(SipProfile p)98     public void saveProfile(SipProfile p) throws IOException {
99         synchronized(SipProfileDb.class) {
100             if (mProfilesCount < 0) retrieveSipProfileListInternal();
101             File f = new File(mProfilesDirectory, p.getProfileName());
102             if (!isChild(new File(mProfilesDirectory), f)) {
103                 throw new IOException("Invalid Profile Credentials!");
104             }
105             if (!f.exists()) f.mkdirs();
106             AtomicFile atomicFile = new AtomicFile(new File(f, PROFILE_OBJ_FILE));
107             FileOutputStream fos = null;
108             ObjectOutputStream oos = null;
109             try {
110                 fos = atomicFile.startWrite();
111                 oos = new ObjectOutputStream(fos);
112                 oos.writeObject(p);
113                 oos.flush();
114                 atomicFile.finishWrite(fos);
115             } catch (IOException e) {
116                 atomicFile.failWrite(fos);
117                 throw e;
118             } finally {
119                 if (oos != null) oos.close();
120             }
121         }
122     }
123 
retrieveSipProfileList()124     public List<SipProfile> retrieveSipProfileList() {
125         synchronized(SipProfileDb.class) {
126             return retrieveSipProfileListInternal();
127         }
128     }
129 
retrieveSipProfileListInternal()130     private List<SipProfile> retrieveSipProfileListInternal() {
131         List<SipProfile> sipProfileList = Collections.synchronizedList(
132                 new ArrayList<SipProfile>());
133 
134         File root = new File(mProfilesDirectory);
135         String[] dirs = root.list();
136         if (dirs == null) return sipProfileList;
137         for (String dir : dirs) {
138             SipProfile p = retrieveSipProfileFromName(dir);
139             if (p == null) continue;
140             sipProfileList.add(p);
141         }
142         mProfilesCount = sipProfileList.size();
143         return sipProfileList;
144     }
145 
retrieveSipProfileFromName(String name)146     public SipProfile retrieveSipProfileFromName(String name) {
147         if (TextUtils.isEmpty(name)) {
148             return null;
149         }
150 
151         File root = new File(mProfilesDirectory);
152         File f = new File(new File(root, name), PROFILE_OBJ_FILE);
153         if (f.exists()) {
154             try {
155                 SipProfile p = deserialize(f);
156                 if (p != null && name.equals(p.getProfileName())) {
157                     return p;
158                 }
159             } catch (IOException e) {
160                 log("retrieveSipProfileListInternal, exception: " + e);
161             }
162         }
163         return null;
164     }
165 
deserialize(File profileObjectFile)166     private SipProfile deserialize(File profileObjectFile) throws IOException {
167         AtomicFile atomicFile = new AtomicFile(profileObjectFile);
168         ObjectInputStream ois = null;
169         try {
170             ois = new ObjectInputStream(atomicFile.openRead());
171             SipProfile p = (SipProfile) ois.readObject();
172             return p;
173         } catch (ClassNotFoundException e) {
174             log("deserialize, exception: " + e);
175         } finally {
176             if (ois!= null) ois.close();
177         }
178         return null;
179     }
180 
log(String msg)181     private static void log(String msg) {
182         Log.d(SipUtil.LOG_TAG, PREFIX + msg);
183     }
184 
185     /**
186      * Verifies that the file is a direct child of the base directory.
187      */
isChild(File base, File file)188     private boolean isChild(File base, File file) {
189         if (base == null || file == null) {
190             return false;
191         }
192         if (!base.equals(file.getAbsoluteFile().getParentFile())) {
193             Log.w(SipUtil.LOG_TAG, "isChild, file is not a child of the base dir.");
194             EventLog.writeEvent(0x534e4554, "31530456", -1, "");
195             return false;
196         }
197         return true;
198     }
199 }
200