Index: /trunk/examples/divan/divan/moderation.py
===================================================================
--- /trunk/examples/divan/divan/moderation.py	(revision 190)
+++ /trunk/examples/divan/divan/moderation.py	(revision 200)
@@ -49,5 +49,7 @@
 def notify(post, reaction):
     # Send an email to someone who can review and maybe publish the comment
-    if smtp_configured():
+    recipients = [addr.strip() for addr in
+                  app.config.get('moderation_email_to', '').split(',')]
+    if recipients and smtp_configured():
         template = app.templates.load('moderation/notification.txt',
                                       cls=NewTextTemplate)
@@ -61,5 +63,5 @@
             ),
             body = body,
-            from_address = 'noreply@cmlenz.net'
+            recipients = recipients
         )
 
Index: /trunk/examples/divan/divan/comments.py
===================================================================
--- /trunk/examples/divan/divan/comments.py	(revision 199)
+++ /trunk/examples/divan/divan/comments.py	(revision 200)
@@ -98,6 +98,6 @@
             'Host': get_hostname(comment.openid_server)
         })
-        result = body.strip().split(':', 1)
-        if result == ['is_valid', 'true']:
+        result = parse_kv_encoding(body.strip())
+        if result['is_valid'] == 'true':
             log.info('OpenID authentication for %r validated by %r',
                      comment.openid, comment.openid_server)
@@ -142,12 +142,12 @@
             return redirect_to(view.post, *post.path())
         if form.validate(request.POST) and 'preview' not in request.POST:
-            log.info('Attempting OpenID authentication for %r',
-                     form['openid_url'])
+            claimed_id = form['openid_url']
+            log.info('Attempting OpenID authentication for %r', claimed_id)
             try:
-                server, identity = resolve_openid(form['openid_url'])
+                provider, local_id, version = resolve_openid(claimed_id)
             except AuthenticationError, e:
                 form.errors[None] = [e.args[0]]
             else:
-                log.info('OpenID resolved to %r via %r', identity, server)
+                log.info('OpenID resolved to %r via %r', local_id, provider)
 
                 try:
@@ -158,7 +158,7 @@
                 else:
                     comment = Comment(post_id=post.id, time=datetime.utcnow(),
-                                      openid=form['openid_url'],
-                                      openid_server=server,
-                                      openid_identity=identity,
+                                      openid=claimed_id,
+                                      openid_server=provider,
+                                      openid_identity=local_id,
                                       markup=markup,
                                       **form.data)
@@ -166,12 +166,25 @@
 
                     response.set_cookie('author', comment.author)
-                    response.set_cookie('openid', comment.openid)
-
-                    return redirect_to(server, **{
+                    response.set_cookie('openid', claimed_id)
+
+                    params = {
                         'openid.mode': 'checkid_setup',
-                        'openid.identity': identity,
-                        'openid.return_to': abs_url(path_to('authenticate', comment.id)),
-                        'openid.trust_root': abs_url(path_to('index'))
-                    })
+                        'openid.identity': local_id,
+                        'openid.return_to': abs_url(path_to('authenticate', comment.id))
+                    }
+                    if version >= 2:
+                        params.update({
+                            'openid.ns': 'http://specs.openid.net/auth/2.0',
+                            'openid.claimed_id': claimed_id,
+                            'openid.identity': local_id,
+                            'openid.realm': abs_url(path_to('index'))
+                        })
+                    else:
+                        params.update({
+                            'openid.trust_root': abs_url(path_to('index'))
+                        })
+
+                    return redirect_to(provider, **params)
+
         try:
             preview = to_xhtml(form['content'])
@@ -208,5 +221,17 @@
 
 
+def parse_kv_encoding(string):
+    lines = string.splitlines()
+    retval = {}
+    for line in lines:
+        key, value = line.split(':', 1)
+        retval[key] = value
+    return retval
+
+
 def resolve_openid(url):
+    """Resolve the given OpenID URL and return a tuple of the form
+    `(provider_url, identity_url, version)`.
+    """
     http = Http()
     resp, body = http.request(url, method='GET', headers={
@@ -225,15 +250,23 @@
 
     soup = BeautifulSoup(body, fromEncoding=params.get('charset'))
-    server_tag = soup.findAll('link', rel='openid.server')
-    if not server_tag:
-        raise AuthenticationError('OpenID HTML resource contains no '
-                                  '"openid.server" link')
-    server_url = server_tag[0]['href']
-
-    delegate_tag = soup.findAll('link', rel='openid.delegate')
-    if not delegate_tag:
-        openid_identity = url
-    else:
-        openid_identity = delegate_tag[0]['href']
-
-    return server_url, openid_identity
+    provider_tag = soup.findAll('link', rel='openid2.provider')
+    if provider_tag:
+        version = 2
+    else:
+        provider_tag = soup.findAll('link', rel='openid.server')
+        if not provider_tag:
+            raise AuthenticationError('OpenID HTML resource contains no '
+                                      '"openid.server" link')
+        version = 1
+    provider_url = provider_tag[0]['href']
+
+    if version == 2:
+        identity_tag = soup.findAll('link', rel='openid2.local_id')
+    else:
+        identity_tag = soup.findAll('link', rel='openid.delegate')
+    if not identity_tag:
+        identity_url = url
+    else:
+        identity_url = identity_tag[0]['href']
+
+    return provider_url, identity_url, version
