<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[13926] CalendarServer/trunk</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.calendarserver.org//changeset/13926">13926</a></dd>
<dt>Author</dt> <dd>sagen@apple.com</dd>
<dt>Date</dt> <dd>2014-08-29 16:52:53 -0700 (Fri, 29 Aug 2014)</dd>
</dl>

<h3>Log Message</h3>
<pre>calendarserver_diagnose</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServertrunksetuppy">CalendarServer/trunk/setup.py</a></li>
<li><a href="#CalendarServertrunksupportApplemake">CalendarServer/trunk/support/Apple.make</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#CalendarServertrunkcalendarservertoolsdiagnosepy">CalendarServer/trunk/calendarserver/tools/diagnose.py</a></li>
<li><a href="#CalendarServertrunkcalendarservertoolstesttest_diagnosepy">CalendarServer/trunk/calendarserver/tools/test/test_diagnose.py</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServertrunkcalendarservertoolsdiagnosepy"></a>
<div class="addfile"><h4>Added: CalendarServer/trunk/calendarserver/tools/diagnose.py (0 => 13926)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/calendarserver/tools/diagnose.py                                (rev 0)
+++ CalendarServer/trunk/calendarserver/tools/diagnose.py        2014-08-29 23:52:53 UTC (rev 13926)
</span><span class="lines">@@ -0,0 +1,759 @@
</span><ins>+#!/usr/bin/env python
+
+##
+# Copyright (c) 2014 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+from __future__ import print_function
+import sys
+
+
+from getopt import getopt, GetoptError
+import os
+from plistlib import readPlist, readPlistFromString
+import re
+import subprocess
+import urllib2
+
+from twext.internet.ssl import ChainingOpenSSLContextFactory
+import OpenSSL
+
+
+PREFS_PLIST = &quot;/Library/Preferences/com.apple.servermgr_calendar.plist&quot;
+SSLPrivateKey = &quot;&quot;
+SSLCertAdmin = &quot;&quot;
+SSLPassPhraseDialog = &quot;&quot;
+SSLPort = &quot;&quot;
+ServerHostName = &quot;&quot;
+
+
+class FileNotFound(Exception):
+    &quot;&quot;&quot;
+    Missing file exception
+    &quot;&quot;&quot;
+
+
+def usage(e=None):
+    if e:
+        print(e)
+        print(&quot;&quot;)
+
+    name = os.path.basename(sys.argv[0])
+    print(&quot;usage: {} [options]&quot;.format(name))
+    print(&quot;options:&quot;)
+    print(&quot;  -h --help: print this help and exit&quot;)
+
+    if e:
+        sys.exit(64)
+    else:
+        sys.exit(0)
+
+
+def main():
+    try:
+        (optargs, args) = getopt(
+            sys.argv[1:], &quot;h&quot;, [
+                &quot;help&quot;,
+            ],
+        )
+    except GetoptError, e:
+        usage(e)
+
+    for opt, arg in optargs:
+
+        # Args come in as encoded bytes
+        arg = arg.decode(&quot;utf-8&quot;)
+
+        if opt in (&quot;-h&quot;, &quot;--help&quot;):
+            usage()
+
+
+    osBuild = getOSBuild()
+    print(&quot;OS Build: {}&quot;.format(osBuild))
+
+    serverBuild = getServerBuild()
+    print(&quot;Server Build: {}&quot;.format(serverBuild))
+
+
+    print()
+
+    try:
+        if checkPlist(PREFS_PLIST):
+            print(&quot;{} exists and can be parsed&quot;.format(PREFS_PLIST))
+        else:
+            print(&quot;{} exists but cannot be parsed&quot;.format(PREFS_PLIST))
+    except FileNotFound:
+        print(&quot;{} does not exist (but that's ok)&quot;.format(PREFS_PLIST))
+
+    serverRoot = getServerRoot()
+    print(&quot;Prefs plist says ServerRoot directory is: {}&quot;.format(serverRoot))
+
+    systemPlist = os.path.join(serverRoot, &quot;Config&quot;, &quot;caldavd-system.plist&quot;)
+    try:
+        if checkPlist(systemPlist):
+            print(&quot;{} exists and can be parsed&quot;.format(systemPlist))
+        else:
+            print(&quot;{} exists but cannot be parsed&quot;.format(systemPlist))
+    except FileNotFound:
+        print(&quot;{} does not exist&quot;.format(systemPlist))
+
+
+    keys = showConfigKeys()
+
+    showProcesses()
+
+    showServerctlStatus()
+
+    showDiskSpace(serverRoot)
+
+    postgresRunning = showPostgresStatus(serverRoot)
+
+    if postgresRunning:
+        showPostgresContent()
+
+
+    password = getPasswordFromKeychain(&quot;com.apple.calendarserver&quot;)
+
+    connectToAgent(password)
+
+    if keys.get(&quot;EnableSSL&quot;, &quot;False&quot;) == &quot;True&quot;:
+        success, message = verifyTLSCertificate(keys)
+        if success:
+            print(&quot;TLS Certificate OK&quot;)
+        else:
+            print(&quot;Problem with TLS certificate: {}&quot;.format(message))
+            print(&quot;Try resetting the certificate for Calendar and Contacts in Server.app&quot;)
+    else:
+        print(&quot;TLS is disabled&quot;)
+
+    connectToCaldavd(keys)
+
+
+def showProcesses():
+
+    print()
+    print(&quot;Calendar and Contacts service processes:&quot;)
+
+    code, stdout, stderr = runCommand(
+        &quot;/bin/ps&quot;, &quot;ax&quot;,
+        &quot;-o user&quot;,
+        &quot;-o pid&quot;,
+        &quot;-o %cpu&quot;,
+        &quot;-o %mem&quot;,
+        &quot;-o rss&quot;,
+        &quot;-o etime&quot;,
+        &quot;-o lstart&quot;,
+        &quot;-o command&quot;
+    )
+    for line in stdout.split(&quot;\n&quot;):
+        if &quot;_calendar&quot; in line or &quot;CalendarServer&quot; in line or &quot;COMMAND&quot; in line:
+            print(line)
+
+
+def showServerctlStatus():
+
+    print()
+    print(&quot;Serverd status:&quot;)
+
+    code, stdout, stderr = runCommand(
+        &quot;/Applications/Server.app/Contents/ServerRoot/usr/sbin/serverctl&quot;,
+        &quot;list&quot;,
+    )
+    services = {
+        &quot;org.calendarserver.agent&quot;: False,
+        &quot;org.calendarserver.calendarserver&quot;: False,
+        &quot;org.calendarserver.relocate&quot;: False,
+    }
+
+    enabledBucket = False
+
+    for line in stdout.split(&quot;\n&quot;):
+        if &quot;enabledServices&quot; in line:
+            enabledBucket = True
+        if &quot;disabledServices&quot; in line:
+            enabledBucket = False
+
+        for service in services:
+            if service in line:
+                services[service] = enabledBucket
+
+    for service, enabled in services.iteritems():
+        print(
+            &quot;{service} is {enabled}&quot;.format(
+                service=service,
+                enabled=&quot;enabled&quot; if enabled else &quot;disabled&quot;
+            )
+        )
+
+
+def showDiskSpace(serverRoot):
+
+    print()
+    print(&quot;Disk space on boot volume:&quot;)
+
+    code, stdout, stderr = runCommand(
+        &quot;/bin/df&quot;,
+        &quot;-H&quot;,
+        &quot;/&quot;,
+    )
+    print(stdout)
+
+    print(&quot;Disk space on service data volume:&quot;)
+
+    code, stdout, stderr = runCommand(
+        &quot;/bin/df&quot;,
+        &quot;-H&quot;,
+        serverRoot
+    )
+    print(stdout)
+
+    print(&quot;Disk space used by Calendar and Contacts service:&quot;)
+    code, stdout, stderr = runCommand(
+        &quot;/usr/bin/du&quot;,
+        &quot;-sh&quot;,
+        os.path.join(serverRoot, &quot;Config&quot;),
+        os.path.join(serverRoot, &quot;Data&quot;),
+        os.path.join(serverRoot, &quot;Logs&quot;),
+    )
+    print(stdout)
+
+
+def showPostgresStatus(serverRoot):
+
+    clusterPath = os.path.join(serverRoot, &quot;Data&quot;, &quot;Database.xpg&quot;, &quot;cluster.pg&quot;)
+
+    print()
+    print(&quot;Postgres status for cluster {}:&quot;.format(clusterPath))
+
+    code, stdout, stderr = runCommand(
+        &quot;/usr/bin/sudo&quot;,
+        &quot;-u&quot;,
+        &quot;calendar&quot;,
+        &quot;/Applications/Server.app/Contents/ServerRoot/usr/bin/pg_ctl&quot;,
+        &quot;status&quot;,
+        &quot;-D&quot;,
+        clusterPath
+    )
+    if stdout:
+        print(stdout)
+    if stderr:
+        print(stderr)
+    if code:
+        return False
+    return True
+
+
+def runSQLQuery(query):
+
+    code, stdout, stderr = runCommand(
+        &quot;/Applications/Server.app/Contents/ServerRoot/usr/bin/psql&quot;,
+        &quot;-h&quot;,
+        &quot;/var/run/caldavd/PostgresSocket&quot;,
+        &quot;--dbname=caldav&quot;,
+        &quot;--username=caldav&quot;,
+        &quot;--command={}&quot;.format(query),
+    )
+    if stdout:
+        print(stdout)
+    if stderr:
+        print(stderr)
+
+
+def countFromSQLQuery(query):
+
+    code, stdout, stderr = runCommand(
+        &quot;/Applications/Server.app/Contents/ServerRoot/usr/bin/psql&quot;,
+        &quot;-h&quot;,
+        &quot;/var/run/caldavd/PostgresSocket&quot;,
+        &quot;--dbname=caldav&quot;,
+        &quot;--username=caldav&quot;,
+        &quot;--command={}&quot;.format(query),
+    )
+    lines = stdout.split(&quot;\n&quot;)
+    count = int(lines[2])
+    return count
+
+
+def listDatabases():
+
+    code, stdout, stderr = runCommand(
+        &quot;/Applications/Server.app/Contents/ServerRoot/usr/bin/psql&quot;,
+        &quot;-h&quot;,
+        &quot;/var/run/caldavd/PostgresSocket&quot;,
+        &quot;--dbname=caldav&quot;,
+        &quot;--username=caldav&quot;,
+        &quot;--list&quot;,
+    )
+    if stdout:
+        print(stdout)
+    if stderr:
+        print(stderr)
+
+
+def showPostgresContent():
+
+    print()
+    print(&quot;Postgres content:&quot;)
+    print()
+
+    listDatabases()
+
+    print(&quot;'calendarserver' table...&quot;)
+    runSQLQuery(&quot;select * from calendarserver;&quot;)
+
+    count = countFromSQLQuery(&quot;select count(*) from calendar_home;&quot;)
+    print(&quot;Number of calendar homes: {}&quot;.format(count))
+
+    count = countFromSQLQuery(&quot;select count(*) from calendar_object;&quot;)
+    print(&quot;Number of calendar events: {}&quot;.format(count))
+
+    count = countFromSQLQuery(&quot;select count(*) from addressbook_home;&quot;)
+    print(&quot;Number of contacts homes: {}&quot;.format(count))
+
+    count = countFromSQLQuery(&quot;select count(*) from addressbook_object;&quot;)
+    print(&quot;Number of contacts cards: {}&quot;.format(count))
+
+    count = countFromSQLQuery(&quot;select count(*) from delegates;&quot;)
+    print(&quot;Number of non-group delegate assignments: {}&quot;.format(count))
+
+    count = countFromSQLQuery(&quot;select count(*) from delegate_groups;&quot;)
+    print(&quot;Number of group delegate assignments: {}&quot;.format(count))
+
+
+def showConfigKeys():
+
+    print()
+    print(&quot;Configuration:&quot;)
+
+    code, stdout, stderr = runCommand(
+        &quot;/Applications/Server.app/Contents/ServerRoot/usr/sbin/calendarserver_config&quot;,
+        &quot;EnableCalDAV&quot;,
+        &quot;EnableCardDAV&quot;,
+        &quot;Notifications.Services.APNS.Enabled&quot;,
+        &quot;Scheduling.iMIP.Enabled&quot;,
+        &quot;Authentication.Basic.Enabled&quot;,
+        &quot;Authentication.Digest.Enabled&quot;,
+        &quot;Authentication.Kerberos.Enabled&quot;,
+        &quot;EnableSSL&quot;,
+        &quot;HTTPPort&quot;,
+        &quot;SSLPort&quot;,
+        &quot;RedirectHTTPToHTTPS&quot;,
+        &quot;SSLCertificate&quot;,
+        &quot;SSLPrivateKey&quot;,
+        &quot;SSLAuthorityChain&quot;,
+        &quot;SSLCertAdmin&quot;,
+        &quot;SSLPassPhraseDialog&quot;,
+        &quot;ServerHostName&quot;,
+    )
+    hidden = [
+        &quot;SSLCertificate&quot;,
+        &quot;SSLPrivateKey&quot;,
+        &quot;SSLAuthorityChain&quot;,
+        &quot;SSLCertAdmin&quot;,
+        &quot;SSLPassPhraseDialog&quot;,
+        &quot;ServerHostName&quot;,
+    ]
+    ifHasValue = [
+        &quot;SSLCertificate&quot;,
+        &quot;SSLPrivateKey&quot;,
+        &quot;SSLAuthorityChain&quot;,
+    ]
+    keys = {}
+    for line in stdout.split(&quot;\n&quot;):
+        if &quot;=&quot; in line:
+            key, value = line.strip().split(&quot;=&quot;, 1)
+            keys[key] = value
+            if key not in hidden:
+                print(&quot;{key} : {value}&quot;.format(key=key, value=value))
+            if key in ifHasValue and value:
+                print(&quot;{key} is set&quot;.format(key=key))
+    return keys
+
+
+
+def runCommand(commandPath, *args):
+    &quot;&quot;&quot;
+    Run a command line tool and return the output
+    &quot;&quot;&quot;
+    if not os.path.exists(commandPath):
+        raise FileNotFound
+
+    commandLine = [commandPath]
+    if args:
+        commandLine.extend(args)
+
+    child = subprocess.Popen(
+        args=commandLine,
+        stdin=subprocess.PIPE,
+        stdout=subprocess.PIPE,
+        stderr=subprocess.PIPE,
+    )
+    output, error = child.communicate()
+    return child.returncode, output, error
+
+
+def getOSBuild():
+    try:
+        code, stdout, stderr = runCommand(&quot;/usr/bin/sw_vers&quot;, &quot;-buildVersion&quot;)
+        if not code:
+            return stdout.strip()
+    except:
+        return &quot;Unknown&quot;
+
+
+def getServerBuild():
+    try:
+        code, stdout, stderr = runCommand(&quot;/usr/sbin/serverinfo&quot;, &quot;--buildversion&quot;)
+        if not code:
+            return stdout.strip()
+    except:
+        pass
+    return &quot;Unknown&quot;
+
+
+def getServerRoot():
+    &quot;&quot;&quot;
+    Return the ServerRoot value from the servermgr_calendar.plist.  If not
+    present, return the default.
+
+    @rtype: C{unicode}
+    &quot;&quot;&quot;
+    try:
+        plist = &quot;/Library/Preferences/com.apple.servermgr_calendar.plist&quot;
+        serverRoot = u&quot;/Library/Server/Calendar and Contacts&quot;
+        if os.path.exists(plist):
+            serverRoot = readPlist(plist).get(&quot;ServerRoot&quot;, serverRoot)
+        if isinstance(serverRoot, str):
+            serverRoot = serverRoot.decode(&quot;utf-8&quot;)
+        return serverRoot
+    except:
+        return &quot;Unknown&quot;
+
+
+def checkPlist(plistPath):
+    if not os.path.exists(plistPath):
+        raise FileNotFound
+
+    try:
+        readPlist(plistPath)
+    except:
+        return False
+
+    return True
+
+##
+# Keychain access
+##
+
+passwordRegExp = re.compile(r'password: &quot;(.*)&quot;')
+
+
+def getPasswordFromKeychain(account):
+    code, stdout, stderr = runCommand(
+        &quot;/usr/bin/security&quot;,
+        &quot;find-generic-password&quot;,
+        &quot;-a&quot;,
+        account,
+        &quot;-g&quot;,
+    )
+    if code:
+        return None
+    else:
+        match = passwordRegExp.search(stderr)
+        if not match:
+            print(
+                &quot;Password for {} not found in keychain&quot;.format(account)
+            )
+            return None
+        else:
+            return match.group(1)
+
+
+readCommand = &quot;&quot;&quot;&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+&lt;!DOCTYPE plist PUBLIC &quot;-//Apple Computer//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;
+&lt;plist version=&quot;1.0&quot;&gt;
+&lt;dict&gt;
+        &lt;key&gt;command&lt;/key&gt;
+        &lt;string&gt;readConfig&lt;/string&gt;
+&lt;/dict&gt;
+&lt;/plist&gt;
+&quot;&quot;&quot;
+
+
+def connectToAgent(password):
+
+    print()
+    print(&quot;Agent:&quot;)
+
+    url = &quot;http://localhost:62308/gateway/&quot;
+    user = &quot;com.apple.calendarserver&quot;
+    auth_handler = urllib2.HTTPDigestAuthHandler()
+    auth_handler.add_password(
+        realm=&quot;/Local/Default&quot;,
+        uri=url,
+        user=user,
+        passwd=password
+    )
+    opener = urllib2.build_opener(auth_handler)
+    # ...and install it globally so it can be used with urlopen.
+    urllib2.install_opener(opener)
+
+    # Send HTTP POST request
+    request = urllib2.Request(url, readCommand)
+    try:
+        print(&quot;Attempting to send a request to the agent...&quot;)
+        response = urllib2.urlopen(request, timeout=30)
+    except Exception as e:
+        print(&quot;Can't connect to agent: {}&quot;.format(e))
+        return False
+
+    html = response.read()
+    code = response.getcode()
+    if code == 200:
+        try:
+            data = readPlistFromString(html)
+        except Exception as e:
+            print(
+                &quot;Could not parse response from agent: {error}\n{html}&quot;.format(
+                    error=e, html=html
+                )
+            )
+            return False
+
+        if &quot;result&quot; in data:
+            print(&quot;...success&quot;)
+        else:
+            print(&quot;Error in agent's response:\n{}&quot;.format(html))
+            return False
+    else:
+        print(&quot;Got an error back from the agent: {code} {html}&quot;.format(
+            code=code, html=html)
+        )
+
+    return True
+
+
+def connectToCaldavd(keys):
+
+    print()
+    print(&quot;Server connection:&quot;)
+
+    httpPort = keys.get(&quot;HTTPPort&quot;, &quot;8008&quot;)
+    sslPort = keys.get(&quot;SSLPort&quot;, &quot;8443&quot;)
+    # redirect = keys.get(&quot;RedirectHTTPToHTTPS&quot;, &quot;False&quot;) == &quot;True&quot;
+    sslEnabled = keys.get(&quot;EnableSSL&quot;, &quot;False&quot;) == &quot;True&quot;
+
+    if httpPort:
+        url = &quot;http://localhost:{}/&quot;.format(httpPort)
+        try:
+            print(&quot;Attempting to send a request to port {}...&quot;.format(httpPort))
+            response = urllib2.urlopen(url, timeout=30)
+            html = response.read()
+            code = response.getcode()
+            print(code, html)
+            if code == 200:
+                print(&quot;Received 200 response&quot;)
+
+        except urllib2.HTTPError as e:
+            code = e.code
+            reason = e.reason
+
+            if code == 401:
+                print(&quot;Got the expected response&quot;)
+            else:
+                print(
+                    &quot;Got an unexpected response: {code} {reason}&quot;.format(
+                        code=code, reason=reason
+                    )
+                )
+
+        except Exception as e:
+            print(
+                &quot;Can't connect to port {port}: {error}&quot;.format(
+                    port=httpPort, error=e
+                )
+            )
+
+
+    if sslPort and sslEnabled:
+        url = &quot;https://localhost:{}/&quot;.format(sslPort)
+        try:
+            print(&quot;Attempting to send a request to port {}...&quot;.format(sslPort))
+            response = urllib2.urlopen(url, timeout=30)
+            html = response.read()
+            code = response.getcode()
+            print(code, html)
+            if code == 200:
+                print(&quot;Received 200 response&quot;)
+
+        except urllib2.HTTPError as e:
+            code = e.code
+            reason = e.reason
+
+            if code == 401:
+                print(&quot;Got the expected response&quot;)
+            else:
+                print(
+                    &quot;Got an unexpected response: {code} {reason}&quot;.format(
+                        code=code, reason=reason
+                    )
+                )
+
+        except Exception as e:
+            print(
+                &quot;Can't connect to port {port}: {error}&quot;.format(
+                    port=sslPort, error=e
+                )
+            )
+    else:
+        print(&quot;Skipping TLS port since it's disabled&quot;)
+
+
+
+def getSSLPassphrase(*ignored):
+
+    if not SSLPrivateKey:
+        return None
+
+    if SSLCertAdmin and os.path.isfile(SSLCertAdmin):
+        child = subprocess.Popen(
+            args=[
+                &quot;sudo&quot;, SSLCertAdmin,
+                &quot;--get-private-key-passphrase&quot;, SSLPrivateKey,
+            ],
+            stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+        )
+        output, error = child.communicate()
+
+        if child.returncode:
+            print(
+                &quot;Could not get passphrase for key: {error}&quot;.format(
+                    error=error
+                )
+            )
+        else:
+            print(&quot;Obtained passphrase for key&quot;)
+            return output.strip()
+
+    if (
+        SSLPassPhraseDialog and
+        os.path.isfile(SSLPassPhraseDialog)
+    ):
+        sslPrivKey = open(SSLPrivateKey)
+        try:
+            keyType = None
+            for line in sslPrivKey.readlines():
+                if &quot;-----BEGIN RSA PRIVATE KEY-----&quot; in line:
+                    keyType = &quot;RSA&quot;
+                    break
+                elif &quot;-----BEGIN DSA PRIVATE KEY-----&quot; in line:
+                    keyType = &quot;DSA&quot;
+                    break
+        finally:
+            sslPrivKey.close()
+
+        if keyType is None:
+            print(&quot;Could not get private key type for key&quot;)
+        else:
+            child = subprocess.Popen(
+                args=[
+                    SSLPassPhraseDialog,
+                    &quot;{}:{}&quot;.format(ServerHostName, SSLPort),
+                    keyType,
+                ],
+                stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+            )
+            output, error = child.communicate()
+
+            if child.returncode:
+                print(
+                    &quot;Could not get passphrase for key: {error}&quot;.format(
+                        error=error
+                    )
+                )
+            else:
+                return output.strip()
+
+    return None
+
+
+
+def verifyTLSCertificate(keys):
+    &quot;&quot;&quot;
+    If a TLS certificate is configured, make sure it exists, is non empty,
+    and that it's valid.
+    &quot;&quot;&quot;
+    global SSLPrivateKey
+    global SSLCertAdmin
+    global SSLPassPhraseDialog
+    global SSLPort
+    global ServerHostName
+
+    certPath = keys.get(&quot;SSLCertificate&quot;, &quot;&quot;)
+    keyPath = keys.get(&quot;SSLPrivateKey&quot;, &quot;&quot;)
+    chainPath = keys.get(&quot;SSLAuthorityChain&quot;, &quot;&quot;)
+
+    SSLPrivateKey = keyPath
+    SSLCertAdmin = keys.get(&quot;SSLCertAdmin&quot;, &quot;&quot;)
+    SSLPassPhraseDialog = keys.get(&quot;SSLPassPhraseDialog&quot;, &quot;&quot;)
+    SSLPort = keys.get(&quot;SSLPort&quot;, &quot;&quot;)
+    ServerHostName = keys.get(&quot;ServerHostName&quot;, &quot;&quot;)
+
+    print()
+    print(&quot;Checking TLS Certificate:&quot;)
+
+    if certPath:
+        if not os.path.exists(certPath):
+            message = (
+                &quot;The configured TLS certificate ({cert}) is missing&quot;.format(
+                    cert=certPath
+                )
+            )
+            return False, message
+    else:
+        return False, &quot;EnableSSL is set to true, but certificate path not set&quot;
+
+    length = os.stat(certPath).st_size
+    if length == 0:
+            message = (
+                &quot;The configured TLS certificate ({cert}) is empty&quot;.format(
+                    cert=certPath
+                )
+            )
+            return False, message
+
+    try:
+        ChainingOpenSSLContextFactory(
+            keyPath,
+            certPath,
+            certificateChainFile=chainPath,
+            passwdCallback=getSSLPassphrase,
+            sslmethod=getattr(OpenSSL.SSL, &quot;SSLv23_METHOD&quot;),
+            ciphers=&quot;RC4-SHA:HIGH:!ADH&quot;
+        )
+    except Exception as e:
+        message = (
+            &quot;The configured TLS certificate ({cert}) cannot be used: {reason}&quot;.format(
+                cert=certPath,
+                reason=str(e)
+            )
+        )
+        return False, message
+
+    return True, &quot;TLS enabled&quot;
+
+
+
+if __name__ == &quot;__main__&quot;:
+    main()
</ins></span></pre></div>
<a id="CalendarServertrunkcalendarservertoolstesttest_diagnosepy"></a>
<div class="addfile"><h4>Added: CalendarServer/trunk/calendarserver/tools/test/test_diagnose.py (0 => 13926)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/calendarserver/tools/test/test_diagnose.py                                (rev 0)
+++ CalendarServer/trunk/calendarserver/tools/test/test_diagnose.py        2014-08-29 23:52:53 UTC (rev 13926)
</span><span class="lines">@@ -0,0 +1,35 @@
</span><ins>+##
+# Copyright (c) 2014 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+from __future__ import print_function
+
+from calendarserver.tools.diagnose import (
+    runCommand, FileNotFound
+)
+from twisted.trial.unittest import TestCase
+
+
+class DiagnoseTestCase(TestCase):
+
+    def test_runCommand(self):
+        code, stdout, stderr = runCommand(
+            &quot;/bin/ls&quot;, &quot;-al&quot;, &quot;/&quot;
+        )
+        self.assertEquals(code, 0)
+        self.assertEquals(stderr, &quot;&quot;)
+        self.assertTrue(&quot;total&quot; in stdout)
+
+    def test_runCommand_nonExistent(self):
+        self.assertRaises(FileNotFound, runCommand, &quot;/xyzzy/plugh/notthere&quot;)
</ins></span></pre></div>
<a id="CalendarServertrunksetuppy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/setup.py (13925 => 13926)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/setup.py        2014-08-29 18:44:34 UTC (rev 13925)
+++ CalendarServer/trunk/setup.py        2014-08-29 23:52:53 UTC (rev 13926)
</span><span class="lines">@@ -163,6 +163,9 @@
</span><span class="cx">     &quot;dbinspect&quot;:
</span><span class="cx">     (&quot;calendarserver.tools.dbinspect&quot;, &quot;main&quot;),
</span><span class="cx"> 
</span><ins>+    &quot;diagnose&quot;:
+    (&quot;calendarserver.tools.diagnose&quot;, &quot;main&quot;),
+
</ins><span class="cx">     &quot;dkimtool&quot;:
</span><span class="cx">     (&quot;calendarserver.tools.dkimtool&quot;, &quot;main&quot;),
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServertrunksupportApplemake"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/support/Apple.make (13925 => 13926)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/support/Apple.make        2014-08-29 18:44:34 UTC (rev 13925)
+++ CalendarServer/trunk/support/Apple.make        2014-08-29 23:52:53 UTC (rev 13926)
</span><span class="lines">@@ -149,6 +149,7 @@
</span><span class="cx">                   caldavd                                                                                                        \
</span><span class="cx">                   calendarserver_config                                                                                          \
</span><span class="cx">                   calendarserver_command_gateway                                                                                 \
</span><ins>+                  calendarserver_diagnose                                                                                        \
</ins><span class="cx">                   calendarserver_export                                                                                          \
</span><span class="cx">                   calendarserver_manage_principals                                                                               \
</span><span class="cx">                   calendarserver_purge_attachments                                                                               \
</span></span></pre>
</div>
</div>

</body>
</html>