1/*
2 * Copyright (C) 2011 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
18package com.android.emailcommon.provider;
19
20import android.content.ContentValues;
21import android.content.Context;
22import android.database.Cursor;
23import android.net.Uri;
24import android.os.Parcel;
25import android.os.Parcelable;
26import android.text.TextUtils;
27
28import com.android.emailcommon.provider.EmailContent.HostAuthColumns;
29import com.android.emailcommon.utility.SSLUtils;
30import com.android.emailcommon.utility.Utility;
31
32import java.net.URI;
33import java.net.URISyntaxException;
34
35public final class HostAuth extends EmailContent implements HostAuthColumns, Parcelable {
36    public static final String TABLE_NAME = "HostAuth";
37    @SuppressWarnings("hiding")
38    public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/hostauth");
39    // TODO the three following constants duplicate constants in Store.java; remove those and
40    //      just reference these.
41    public static final String SCHEME_IMAP = "imap";
42    public static final String SCHEME_POP3 = "pop3";
43    public static final String SCHEME_EAS = "eas";
44    public static final String SCHEME_SMTP = "smtp";
45    public static final String SCHEME_TRUST_ALL_CERTS = "trustallcerts";
46
47    public static final int PORT_UNKNOWN = -1;
48
49    public static final int FLAG_NONE         = 0x00;    // No flags
50    public static final int FLAG_SSL          = 0x01;    // Use SSL
51    public static final int FLAG_TLS          = 0x02;    // Use TLS
52    public static final int FLAG_AUTHENTICATE = 0x04;    // Use name/password for authentication
53    public static final int FLAG_TRUST_ALL    = 0x08;    // Trust all certificates
54    // Mask of settings directly configurable by the user
55    public static final int USER_CONFIG_MASK  = 0x0b;
56
57    public String mProtocol;
58    public String mAddress;
59    public int mPort;
60    public int mFlags;
61    public String mLogin;
62    public String mPassword;
63    public String mDomain;
64    public String mClientCertAlias = null;
65
66    public static final int CONTENT_ID_COLUMN = 0;
67    public static final int CONTENT_PROTOCOL_COLUMN = 1;
68    public static final int CONTENT_ADDRESS_COLUMN = 2;
69    public static final int CONTENT_PORT_COLUMN = 3;
70    public static final int CONTENT_FLAGS_COLUMN = 4;
71    public static final int CONTENT_LOGIN_COLUMN = 5;
72    public static final int CONTENT_PASSWORD_COLUMN = 6;
73    public static final int CONTENT_DOMAIN_COLUMN = 7;
74    public static final int CONTENT_CLIENT_CERT_ALIAS_COLUMN = 8;
75
76    public static final String[] CONTENT_PROJECTION = new String[] {
77        RECORD_ID, HostAuthColumns.PROTOCOL, HostAuthColumns.ADDRESS, HostAuthColumns.PORT,
78        HostAuthColumns.FLAGS, HostAuthColumns.LOGIN,
79        HostAuthColumns.PASSWORD, HostAuthColumns.DOMAIN, HostAuthColumns.CLIENT_CERT_ALIAS
80    };
81
82    /**
83     * no public constructor since this is a utility class
84     */
85    public HostAuth() {
86        mBaseUri = CONTENT_URI;
87
88        // other defaults policy)
89        mPort = PORT_UNKNOWN;
90    }
91
92     /**
93     * Restore a HostAuth from the database, given its unique id
94     * @param context
95     * @param id
96     * @return the instantiated HostAuth
97     */
98    public static HostAuth restoreHostAuthWithId(Context context, long id) {
99        return EmailContent.restoreContentWithId(context, HostAuth.class,
100                HostAuth.CONTENT_URI, HostAuth.CONTENT_PROJECTION, id);
101    }
102
103
104    /**
105     * Returns the scheme for the specified flags.
106     */
107    public static String getSchemeString(String protocol, int flags) {
108        return getSchemeString(protocol, flags, null);
109    }
110
111    /**
112     * Builds a URI scheme name given the parameters for a {@code HostAuth}.
113     * If a {@code clientAlias} is provided, this indicates that a secure connection must be used.
114     */
115    public static String getSchemeString(String protocol, int flags, String clientAlias) {
116        String security = "";
117        switch (flags & USER_CONFIG_MASK) {
118            case FLAG_SSL:
119                security = "+ssl+";
120                break;
121            case FLAG_SSL | FLAG_TRUST_ALL:
122                security = "+ssl+trustallcerts";
123                break;
124            case FLAG_TLS:
125                security = "+tls+";
126                break;
127            case FLAG_TLS | FLAG_TRUST_ALL:
128                security = "+tls+trustallcerts";
129                break;
130        }
131
132        if (!TextUtils.isEmpty(clientAlias)) {
133            if (TextUtils.isEmpty(security)) {
134                throw new IllegalArgumentException(
135                        "Can't specify a certificate alias for a non-secure connection");
136            }
137            if (!security.endsWith("+")) {
138                security += "+";
139            }
140            security += SSLUtils.escapeForSchemeName(clientAlias);
141        }
142
143        return protocol + security;
144    }
145
146    /**
147     * Returns the flags for the specified scheme.
148     */
149    public static int getSchemeFlags(String scheme) {
150        String[] schemeParts = scheme.split("\\+");
151        int flags = HostAuth.FLAG_NONE;
152        if (schemeParts.length >= 2) {
153            String part1 = schemeParts[1];
154            if ("ssl".equals(part1)) {
155                flags |= HostAuth.FLAG_SSL;
156            } else if ("tls".equals(part1)) {
157                flags |= HostAuth.FLAG_TLS;
158            }
159            if (schemeParts.length >= 3) {
160                String part2 = schemeParts[2];
161                if (SCHEME_TRUST_ALL_CERTS.equals(part2)) {
162                    flags |= HostAuth.FLAG_TRUST_ALL;
163                }
164            }
165        }
166        return flags;
167    }
168
169    @Override
170    public void restore(Cursor cursor) {
171        mBaseUri = CONTENT_URI;
172        mId = cursor.getLong(CONTENT_ID_COLUMN);
173        mProtocol = cursor.getString(CONTENT_PROTOCOL_COLUMN);
174        mAddress = cursor.getString(CONTENT_ADDRESS_COLUMN);
175        mPort = cursor.getInt(CONTENT_PORT_COLUMN);
176        mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN);
177        mLogin = cursor.getString(CONTENT_LOGIN_COLUMN);
178        mPassword = cursor.getString(CONTENT_PASSWORD_COLUMN);
179        mDomain = cursor.getString(CONTENT_DOMAIN_COLUMN);
180        mClientCertAlias = cursor.getString(CONTENT_CLIENT_CERT_ALIAS_COLUMN);
181    }
182
183    @Override
184    public ContentValues toContentValues() {
185        ContentValues values = new ContentValues();
186        values.put(HostAuthColumns.PROTOCOL, mProtocol);
187        values.put(HostAuthColumns.ADDRESS, mAddress);
188        values.put(HostAuthColumns.PORT, mPort);
189        values.put(HostAuthColumns.FLAGS, mFlags);
190        values.put(HostAuthColumns.LOGIN, mLogin);
191        values.put(HostAuthColumns.PASSWORD, mPassword);
192        values.put(HostAuthColumns.DOMAIN, mDomain);
193        values.put(HostAuthColumns.CLIENT_CERT_ALIAS, mClientCertAlias);
194        values.put(HostAuthColumns.ACCOUNT_KEY, 0); // Need something to satisfy the DB
195        return values;
196    }
197
198    /**
199     * Sets the user name and password from URI user info string
200     */
201    public void setLogin(String userInfo) {
202        String userName = null;
203        String userPassword = null;
204        if (!TextUtils.isEmpty(userInfo)) {
205            String[] userInfoParts = userInfo.split(":", 2);
206            userName = userInfoParts[0];
207            if (userInfoParts.length > 1) {
208                userPassword = userInfoParts[1];
209            }
210        }
211        setLogin(userName, userPassword);
212    }
213
214    /**
215     * Sets the user name and password
216     */
217    public void setLogin(String userName, String userPassword) {
218        mLogin = userName;
219        mPassword = userPassword;
220
221        if (mLogin == null) {
222            mFlags &= ~FLAG_AUTHENTICATE;
223        } else {
224            mFlags |= FLAG_AUTHENTICATE;
225        }
226    }
227
228    /**
229     * Returns the login information. [0] is the username and [1] is the password. If
230     * {@link #FLAG_AUTHENTICATE} is not set, {@code null} is returned.
231     */
232    public String[] getLogin() {
233        if ((mFlags & FLAG_AUTHENTICATE) != 0) {
234            String trimUser = (mLogin != null) ? mLogin.trim() : "";
235            String password = (mPassword != null) ? mPassword : "";
236            return new String[] { trimUser, password };
237        }
238        return null;
239    }
240
241    public void setConnection(String protocol, String address, int port, int flags) {
242        setConnection(protocol, address, port, flags, null);
243    }
244
245    /**
246     * Sets the internal connection parameters based on the specified parameter values.
247     * @param protocol the mail protocol to use (e.g. "eas", "imap").
248     * @param address the address of the server
249     * @param port the port for the connection
250     * @param flags flags indicating the security and type of the connection
251     * @param clientCertAlias an optional alias to use if a client user certificate is to be
252     *     presented during connection establishment. If this is non-empty, it must be the case
253     *     that flags indicates use of a secure connection
254     */
255    public void setConnection(String protocol, String address,
256            int port, int flags, String clientCertAlias) {
257        // Set protocol, security, and additional flags based on uri scheme
258        mProtocol = protocol;
259
260        mFlags &= ~(FLAG_SSL | FLAG_TLS | FLAG_TRUST_ALL);
261        mFlags |= (flags & USER_CONFIG_MASK);
262
263        boolean useSecureConnection = (flags & (FLAG_SSL | FLAG_TLS)) != 0;
264        if (!useSecureConnection && !TextUtils.isEmpty(clientCertAlias)) {
265            throw new IllegalArgumentException("Can't use client alias on non-secure connections");
266        }
267
268        mAddress = address;
269        mPort = port;
270        if (mPort == PORT_UNKNOWN) {
271            boolean useSSL = ((mFlags & FLAG_SSL) != 0);
272            // infer port# from protocol + security
273            // SSL implies a different port - TLS runs in the "regular" port
274            // NOTE: Although the port should be setup in the various setup screens, this
275            // block cannot easily be moved because we get process URIs from other sources
276            // (e.g. for tests, provider templates and account restore) that may or may not
277            // have a port specified.
278            if (SCHEME_POP3.equals(mProtocol)) {
279                mPort = useSSL ? 995 : 110;
280            } else if (SCHEME_IMAP.equals(mProtocol)) {
281                mPort = useSSL ? 993 : 143;
282            } else if (SCHEME_EAS.equals(mProtocol)) {
283                mPort = useSSL ? 443 : 80;
284            } else if (SCHEME_SMTP.equals(mProtocol)) {
285                mPort = useSSL ? 465 : 587;
286            }
287        }
288
289        mClientCertAlias = clientCertAlias;
290    }
291
292    /** Returns {@code true} if this is an EAS connection; otherwise, {@code false}. */
293    public boolean isEasConnection() {
294        return SCHEME_EAS.equals(mProtocol);
295    }
296
297    /** Convenience method to determine if SSL is used. */
298    public boolean shouldUseSsl() {
299        return (mFlags & FLAG_SSL) != 0;
300    }
301
302    /** Convenience method to determine if all server certs should be used. */
303    public boolean shouldTrustAllServerCerts() {
304        return (mFlags & FLAG_TRUST_ALL) != 0;
305    }
306
307    /**
308     * Supports Parcelable
309     */
310    @Override
311    public int describeContents() {
312        return 0;
313    }
314
315    /**
316     * Supports Parcelable
317     */
318    public static final Parcelable.Creator<HostAuth> CREATOR
319            = new Parcelable.Creator<HostAuth>() {
320        @Override
321        public HostAuth createFromParcel(Parcel in) {
322            return new HostAuth(in);
323        }
324
325        @Override
326        public HostAuth[] newArray(int size) {
327            return new HostAuth[size];
328        }
329    };
330
331    /**
332     * Supports Parcelable
333     */
334    @Override
335    public void writeToParcel(Parcel dest, int flags) {
336        // mBaseUri is not parceled
337        dest.writeLong(mId);
338        dest.writeString(mProtocol);
339        dest.writeString(mAddress);
340        dest.writeInt(mPort);
341        dest.writeInt(mFlags);
342        dest.writeString(mLogin);
343        dest.writeString(mPassword);
344        dest.writeString(mDomain);
345        dest.writeString(mClientCertAlias);
346    }
347
348    /**
349     * Supports Parcelable
350     */
351    public HostAuth(Parcel in) {
352        mBaseUri = CONTENT_URI;
353        mId = in.readLong();
354        mProtocol = in.readString();
355        mAddress = in.readString();
356        mPort = in.readInt();
357        mFlags = in.readInt();
358        mLogin = in.readString();
359        mPassword = in.readString();
360        mDomain = in.readString();
361        mClientCertAlias = in.readString();
362    }
363
364    @Override
365    public boolean equals(Object o) {
366        if (!(o instanceof HostAuth)) {
367            return false;
368        }
369        HostAuth that = (HostAuth)o;
370        return mPort == that.mPort
371                && mFlags == that.mFlags
372                && Utility.areStringsEqual(mProtocol, that.mProtocol)
373                && Utility.areStringsEqual(mAddress, that.mAddress)
374                && Utility.areStringsEqual(mLogin, that.mLogin)
375                && Utility.areStringsEqual(mPassword, that.mPassword)
376                && Utility.areStringsEqual(mDomain, that.mDomain)
377                && Utility.areStringsEqual(mClientCertAlias, that.mClientCertAlias);
378    }
379
380    /**
381     * The flag, password, and client cert alias are the only items likely to change after a
382     * HostAuth is created
383     */
384    @Override
385    public int hashCode() {
386        int hashCode = 29;
387        if (mPassword != null) {
388            hashCode += mPassword.hashCode();
389        }
390        if (mClientCertAlias != null) {
391            hashCode += (mClientCertAlias.hashCode() << 8);
392        }
393        return (hashCode << 8) + mFlags;
394    }
395
396    /**
397     * Legacy URI parser. Used in parsing template from provider.xml
398     * Example string:
399     *   "eas+ssl+trustallcerts://user:password@server/domain:123"
400     *
401     * Note that the use of client certificate is specified in the URI, a secure connection type
402     * must be used.
403     */
404    public static void setHostAuthFromString(HostAuth auth, String uriString)
405            throws URISyntaxException {
406        URI uri = new URI(uriString);
407        String path = uri.getPath();
408        String domain = null;
409        if (!TextUtils.isEmpty(path)) {
410            // Strip off the leading slash that begins the path.
411            domain = path.substring(1);
412        }
413        auth.mDomain = domain;
414        auth.setLogin(uri.getUserInfo());
415
416        String scheme = uri.getScheme();
417        auth.setConnection(scheme, uri.getHost(), uri.getPort());
418    }
419
420    /**
421     * Legacy code for setting connection values from a "scheme" (see above)
422     */
423    public void setConnection(String scheme, String host, int port) {
424        String[] schemeParts = scheme.split("\\+");
425        String protocol = schemeParts[0];
426        String clientCertAlias = null;
427        int flags = getSchemeFlags(scheme);
428
429        // Example scheme: "eas+ssl+trustallcerts" or "eas+tls+trustallcerts+client-cert-alias"
430        if (schemeParts.length > 3) {
431            clientCertAlias = schemeParts[3];
432        } else if (schemeParts.length > 2) {
433            if (!SCHEME_TRUST_ALL_CERTS.equals(schemeParts[2])) {
434                mClientCertAlias = schemeParts[2];
435            }
436        }
437
438        setConnection(protocol, host, port, flags, clientCertAlias);
439    }
440
441}
442