Repository: certbot/certbot

Issue:
redirect fails on apache
in apache if there is a default-ssl conf around then redirect fail
on apache 2.2 and 2.4 it gives this error:

```
2016-02-11 20:31:40,023:WARNING:letsencrypt_apache.configurator:Failed redirect for noah2.isnot.org
2016-02-11 20:31:40,023:WARNING:letsencrypt.client:Unable to set enhancement redirect for noah2.isnot.org
Unable to find corresponding HTTP vhost; Unable to create one as intended addresses conflict; Current configuration does not support automated redirection

IMPORTANT NOTES:
 - If you lose your account credentials, you can recover through
   e-mails sent to noah@eff.org.
 - We were unable to set up enhancement redirect for your server,
   however, we successfully installed your certificate.
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/noah2.isnot.org/fullchain.pem. Your cert will
   expire on 2016-05-12. To obtain a new version of the certificate in
   the future, simply run Let's Encrypt again.
 - Your account credentials have been saved in your Let's Encrypt
   configuration directory at /etc/letsencrypt. You should make a
   secure backup of this folder now. This configuration directory will
   also contain certificates and private keys obtained by Let's
   Encrypt so making regular backups of this folder is ideal.
```

according to @bmw it should work on 2.4 but not 2.2 because of how _default_:443 is handled, but we don't seem to have code to deal with this

relevant code is configurator.py lin 1050



Discussion:
Okay the issue is in line 1042 or configurator.py which reads:
`1042         general_vh = self._get_http_vhost(ssl_vhost)`
which calls

```
1235     def _get_http_vhost(self, ssl_vhost):                                        
1236         """Find appropriate HTTP vhost for ssl_vhost."""                         
1237         # First candidate vhosts filter                                          
1238         candidate_http_vhs = [                                                   
1239             vhost for vhost in self.vhosts if not vhost.ssl                      
1240         ]                                                                        
1241                                                                                  
1242         # Second filter - check addresses                                        
1243         for http_vh in candidate_http_vhs:                                       
1244             if http_vh.same_server(ssl_vhost):                                   
1245                 return http_vh                                                   
1246                                                                                  
1247         return None               
```

which calls same_server (in obj.py):

```
202         if vhost.get_names() != self.get_names():                                
203             return False  
```

which now fails because we're adding names to the vhost we're writing

which leads us into this code in _enable_redirect:

```
1044         if general_vh is None:                                                   
1045             # Add virtual_server with redirect                                   
1046             logger.debug("Did not find http version of ssl virtual host "        
1047                          "attempting to create")                                 
1048             redirect_addrs = self._get_proposed_addrs(ssl_vhost)                 
1049             for vhost in self.vhosts:
1050                 if vhost.enabled and vhost.conflicts(redirect_addrs):            
1051                     raise errors.PluginError(
```

Which fails - since we'd be writing another *:80 vhost for the redirect.
Previously this didn't happen because we'd find the generic *:80 vhost with same_server and not enter this code and successfully re-write

I think the solution is to now add some logic to try to re-write the :80 vhost but with the names/aliases from the newly written ssl vhost so long as some other vhost on :80 doesn't have one of those names on it - if it does then we can error.

so the new logic could be 
`if general_vh is None and not same_server_names(self.vhosts):`
or something 

Same problem here, it used to work...

@SwartzCr figured it out, my problem is that my site-enabled/xxx.conf and the ssl conf do not have a `ServerName` field (they were commented out by apache and I didn't notice)

we should make a change that either writes a new conf file that has a redirecting vhost - or add that redirecting vhost to the conflicting file.


Resolving patch:
diff --git a/letsencrypt-apache/letsencrypt_apache/configurator.py b/letsencrypt-apache/letsencrypt_apache/configurator.py
--- a/letsencrypt-apache/letsencrypt_apache/configurator.py
+++ b/letsencrypt-apache/letsencrypt_apache/configurator.py
@@ -1073,7 +1073,12 @@ def _enable_redirect(self, ssl_vhost, unused_options):
             #     even with save() and load()
             if not self._is_rewrite_engine_on(general_vh):
                 self.parser.add_dir(general_vh.path, "RewriteEngine", "on")
-
+            names = ssl_vhost.get_names()
+            for idx, name in enumerate(names):
+                args = ["%{SERVER_NAME}", "={0}".format(name), "[OR]"]
+                if idx == len(names) - 1:
+                    args.pop()
+                self.parser.add_dir(general_vh.path, "RewriteCond", args)
             if self.get_version() >= (2, 3, 9):
                 self.parser.add_dir(general_vh.path, "RewriteRule",
                                     constants.REWRITE_HTTPS_ARGS_WITH_END)
@@ -1243,6 +1248,10 @@ def _get_http_vhost(self, ssl_vhost):
         for http_vh in candidate_http_vhs:
             if http_vh.same_server(ssl_vhost):
                 return http_vh
+        # Third filter - if none with same names, return generic
+        for http_vh in candidate_http_vhs:
+            if http_vh.same_server(ssl_vhost, generic=True):
+                return http_vh
 
         return None
 
diff --git a/letsencrypt-apache/letsencrypt_apache/obj.py b/letsencrypt-apache/letsencrypt_apache/obj.py
--- a/letsencrypt-apache/letsencrypt_apache/obj.py
+++ b/letsencrypt-apache/letsencrypt_apache/obj.py
@@ -189,22 +189,28 @@ def conflicts(self, addrs):
                     return True
         return False
 
-    def same_server(self, vhost):
+    def same_server(self, vhost, generic=False):
         """Determines if the vhost is the same 'server'.
 
         Used in redirection - indicates whether or not the two virtual hosts
         serve on the exact same IP combinations, but different ports.
+        The generic flag indicates that that we're trying to match to a
+        default or generic vhost
 
         .. todo:: Handle _default_
 
         """
 
-        if vhost.get_names() != self.get_names():
-            return False
+        if not generic:
+            if vhost.get_names() != self.get_names():
+                return False
 
-        # If equal and set is not empty... assume same server
-        if self.name is not None or self.aliases:
-            return True
+            # If equal and set is not empty... assume same server
+            if self.name is not None or self.aliases:
+                return True
+        # If we're looking for a generic vhost, don't return one with a ServerName
+        elif self.name:
+                return False
 
         # Both sets of names are empty.
 


Test patch:
diff --git a/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py b/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py
--- a/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py
+++ b/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py
@@ -746,9 +746,9 @@ def test_enhance_unknown_vhost(self, mock_exe):
         self.config.parser.modules.add("rewrite_module")
         mock_exe.return_value = True
         ssl_vh = obj.VirtualHost(
-            "fp", "ap", set([obj.Addr(("*", "443")),
-                             obj.Addr(("satoshi.com",))]),
+            "fp", "ap", set([obj.Addr(("*", "443"))]),
             True, False)
+        ssl_vh.name = "satoshi.com"
         self.config.vhosts.append(ssl_vh)
         self.assertRaises(
             errors.PluginError,