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