activemq-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From dej...@apache.org
Subject svn commit: r1393988 - in /activemq/trunk/activemq-core/src: main/java/org/apache/activemq/security/ test/java/org/apache/activemq/usecases/ test/resources/org/apache/activemq/security/ test/resources/org/apache/activemq/usecases/
Date Thu, 04 Oct 2012 11:09:21 GMT
Author: dejanb
Date: Thu Oct  4 11:09:21 2012
New Revision: 1393988

URL: http://svn.apache.org/viewvc?rev=1393988&view=rev
Log:
https://issues.apache.org/jira/browse/AMQ-3851 - refactor AuthorizationBroker so that addDestinationInfo
is properly handled

Added:
    activemq/trunk/activemq-core/src/test/java/org/apache/activemq/usecases/TwoSecureBrokerRequestReplyTest.java
    activemq/trunk/activemq-core/src/test/resources/org/apache/activemq/usecases/receiver-secured.xml
  (with props)
    activemq/trunk/activemq-core/src/test/resources/org/apache/activemq/usecases/sender-secured.xml
  (with props)
Modified:
    activemq/trunk/activemq-core/src/main/java/org/apache/activemq/security/AuthorizationBroker.java
    activemq/trunk/activemq-core/src/test/resources/org/apache/activemq/security/simple-auth-broker.xml

Modified: activemq/trunk/activemq-core/src/main/java/org/apache/activemq/security/AuthorizationBroker.java
URL: http://svn.apache.org/viewvc/activemq/trunk/activemq-core/src/main/java/org/apache/activemq/security/AuthorizationBroker.java?rev=1393988&r1=1393987&r2=1393988&view=diff
==============================================================================
--- activemq/trunk/activemq-core/src/main/java/org/apache/activemq/security/AuthorizationBroker.java
(original)
+++ activemq/trunk/activemq-core/src/main/java/org/apache/activemq/security/AuthorizationBroker.java
Thu Oct  4 11:09:21 2012
@@ -45,25 +45,21 @@ public class AuthorizationBroker extends
         super(next);
         this.authorizationMap = authorizationMap;
     }
-           
-    @Override
-    public void addDestinationInfo(ConnectionContext context, DestinationInfo info) throws
Exception {
-        addDestination(context, info.getDestination(),true);
-        super.addDestinationInfo(context, info);
-    }
 
-    @Override
-    public Destination addDestination(ConnectionContext context, ActiveMQDestination destination,boolean
create) throws Exception {
+    protected SecurityContext checkSecurityContext(ConnectionContext context) throws SecurityException
{
         final SecurityContext securityContext = context.getSecurityContext();
         if (securityContext == null) {
             throw new SecurityException("User is not authenticated.");
         }
-        
+        return securityContext;
+    }
+
+    protected boolean checkDestinationAdmin(SecurityContext securityContext, ActiveMQDestination
destination) {
         Destination existing = this.getDestinationMap().get(destination);
         if (existing != null) {
-        	return super.addDestination(context, destination,create);
+            return true;
         }
-        
+
         if (!securityContext.isBrokerContext()) {
             Set<?> allowedACLs = null;
             if (!destination.isTemporary()) {
@@ -73,9 +69,29 @@ public class AuthorizationBroker extends
             }
 
             if (allowedACLs != null && !securityContext.isInOneOf(allowedACLs)) {
-                throw new SecurityException("User " + securityContext.getUserName() + " is
not authorized to create: " + destination);
+                return false;
             }
+        }
+        return true;
+    }
+           
+    @Override
+    public void addDestinationInfo(ConnectionContext context, DestinationInfo info) throws
Exception {
+        final SecurityContext securityContext = checkSecurityContext(context);
 
+        if (!checkDestinationAdmin(securityContext, info.getDestination())) {
+            throw new SecurityException("User " + securityContext.getUserName() + " is not
authorized to create: " + info.getDestination());
+        }
+
+        super.addDestinationInfo(context, info);
+    }
+
+    @Override
+    public Destination addDestination(ConnectionContext context, ActiveMQDestination destination,boolean
create) throws Exception {
+        final SecurityContext securityContext = checkSecurityContext(context);
+        
+        if (!checkDestinationAdmin(securityContext, destination)) {
+            throw new SecurityException("User " + securityContext.getUserName() + " is not
authorized to create: " + destination);
         }
 
         return super.addDestination(context, destination,create);
@@ -83,31 +99,30 @@ public class AuthorizationBroker extends
 
     @Override
     public void removeDestination(ConnectionContext context, ActiveMQDestination destination,
long timeout) throws Exception {
+        final SecurityContext securityContext = checkSecurityContext(context);
 
-        final SecurityContext securityContext = context.getSecurityContext();
-        if (securityContext == null) {
-            throw new SecurityException("User is not authenticated.");
-        }
-        Set<?> allowedACLs = null;
-        if (!destination.isTemporary()) {
-            allowedACLs = authorizationMap.getAdminACLs(destination);
-        } else {
-            allowedACLs = authorizationMap.getTempDestinationAdminACLs();
-        }
-
-        if (!securityContext.isBrokerContext() && allowedACLs != null &&
!securityContext.isInOneOf(allowedACLs)) {
+        if (!checkDestinationAdmin(securityContext, destination)) {
             throw new SecurityException("User " + securityContext.getUserName() + " is not
authorized to remove: " + destination);
         }
+
         super.removeDestination(context, destination, timeout);
     }
 
     @Override
-    public Subscription addConsumer(ConnectionContext context, ConsumerInfo info) throws
Exception {
+    public void removeDestinationInfo(ConnectionContext context, DestinationInfo info) throws
Exception {
+        final SecurityContext securityContext = checkSecurityContext(context);
 
-        final SecurityContext subject = context.getSecurityContext();
-        if (subject == null) {
-            throw new SecurityException("User is not authenticated.");
+        if (!checkDestinationAdmin(securityContext, info.getDestination())) {
+            throw new SecurityException("User " + securityContext.getUserName() + " is not
authorized to remove: " + info.getDestination());
         }
+
+        super.removeDestinationInfo(context, info);
+    }
+
+    @Override
+    public Subscription addConsumer(ConnectionContext context, ConsumerInfo info) throws
Exception {
+        final SecurityContext securityContext = checkSecurityContext(context);
+
         Set<?> allowedACLs = null;
         if (!info.getDestination().isTemporary()) {
             allowedACLs = authorizationMap.getReadACLs(info.getDestination());
@@ -115,10 +130,10 @@ public class AuthorizationBroker extends
             allowedACLs = authorizationMap.getTempDestinationReadACLs();
         }
 
-        if (!subject.isBrokerContext() && allowedACLs != null && !subject.isInOneOf(allowedACLs))
{
-            throw new SecurityException("User " + subject.getUserName() + " is not authorized
to read from: " + info.getDestination());
+        if (!securityContext.isBrokerContext() && allowedACLs != null &&
!securityContext.isInOneOf(allowedACLs)) {
+            throw new SecurityException("User " + securityContext.getUserName() + " is not
authorized to read from: " + info.getDestination());
         }
-        subject.getAuthorizedReadDests().put(info.getDestination(), info.getDestination());
+        securityContext.getAuthorizedReadDests().put(info.getDestination(), info.getDestination());
 
         /*
          * Need to think about this a little more. We could do per message
@@ -146,12 +161,9 @@ public class AuthorizationBroker extends
 
     @Override
     public void addProducer(ConnectionContext context, ProducerInfo info) throws Exception
{
+        final SecurityContext securityContext = checkSecurityContext(context);
 
-        SecurityContext subject = context.getSecurityContext();
-        if (subject == null) {
-            throw new SecurityException("User is not authenticated.");
-        }
-        if (!subject.isBrokerContext() && info.getDestination() != null) {
+        if (!securityContext.isBrokerContext() && info.getDestination() != null)
{
 
             Set<?> allowedACLs = null;
             if (!info.getDestination().isTemporary()) {
@@ -159,10 +171,10 @@ public class AuthorizationBroker extends
             } else {
                 allowedACLs = authorizationMap.getTempDestinationWriteACLs();
             }
-            if (allowedACLs != null && !subject.isInOneOf(allowedACLs)) {
-                throw new SecurityException("User " + subject.getUserName() + " is not authorized
to write to: " + info.getDestination());
+            if (allowedACLs != null && !securityContext.isInOneOf(allowedACLs)) {
+                throw new SecurityException("User " + securityContext.getUserName() + " is
not authorized to write to: " + info.getDestination());
             }
-            subject.getAuthorizedWriteDests().put(info.getDestination(), info.getDestination());
+            securityContext.getAuthorizedWriteDests().put(info.getDestination(), info.getDestination());
         }
 
         super.addProducer(context, info);
@@ -170,11 +182,9 @@ public class AuthorizationBroker extends
 
     @Override
     public void send(ProducerBrokerExchange producerExchange, Message messageSend) throws
Exception {
-        SecurityContext subject = producerExchange.getConnectionContext().getSecurityContext();
-        if (subject == null) {
-            throw new SecurityException("User is not authenticated.");
-        }
-        if (!subject.isBrokerContext() && !subject.getAuthorizedWriteDests().contains(messageSend.getDestination()))
{
+        final SecurityContext securityContext = checkSecurityContext(producerExchange.getConnectionContext());
+
+        if (!securityContext.isBrokerContext() && !securityContext.getAuthorizedWriteDests().contains(messageSend.getDestination()))
{
 
             Set<?> allowedACLs = null;
             if (!messageSend.getDestination().isTemporary()) {
@@ -183,10 +193,10 @@ public class AuthorizationBroker extends
                 allowedACLs = authorizationMap.getTempDestinationWriteACLs();
             }
 
-            if (allowedACLs != null && !subject.isInOneOf(allowedACLs)) {
-                throw new SecurityException("User " + subject.getUserName() + " is not authorized
to write to: " + messageSend.getDestination());
+            if (allowedACLs != null && !securityContext.isInOneOf(allowedACLs)) {
+                throw new SecurityException("User " + securityContext.getUserName() + " is
not authorized to write to: " + messageSend.getDestination());
             }
-            subject.getAuthorizedWriteDests().put(messageSend.getDestination(), messageSend.getDestination());
+            securityContext.getAuthorizedWriteDests().put(messageSend.getDestination(), messageSend.getDestination());
         }
 
         super.send(producerExchange, messageSend);

Added: activemq/trunk/activemq-core/src/test/java/org/apache/activemq/usecases/TwoSecureBrokerRequestReplyTest.java
URL: http://svn.apache.org/viewvc/activemq/trunk/activemq-core/src/test/java/org/apache/activemq/usecases/TwoSecureBrokerRequestReplyTest.java?rev=1393988&view=auto
==============================================================================
--- activemq/trunk/activemq-core/src/test/java/org/apache/activemq/usecases/TwoSecureBrokerRequestReplyTest.java
(added)
+++ activemq/trunk/activemq-core/src/test/java/org/apache/activemq/usecases/TwoSecureBrokerRequestReplyTest.java
Thu Oct  4 11:09:21 2012
@@ -0,0 +1,89 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); 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 "AS IS" 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.
+ */
+package org.apache.activemq.usecases;
+
+import org.apache.activemq.ActiveMQConnection;
+import org.apache.activemq.ActiveMQMessageConsumer;
+import org.apache.activemq.JmsMultipleBrokersTestSupport;
+import org.apache.activemq.command.ActiveMQQueue;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.io.ClassPathResource;
+
+import javax.jms.ConnectionFactory;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+import javax.jms.TemporaryQueue;
+
+public class TwoSecureBrokerRequestReplyTest extends JmsMultipleBrokersTestSupport {
+    private static final Logger LOG = LoggerFactory.getLogger(TwoSecureBrokerRequestReplyTest.class);
+
+    public void setUp() throws Exception {
+        super.setAutoFail(true);
+        super.setUp();
+
+        createBroker(new ClassPathResource("org/apache/activemq/usecases/sender-secured.xml"));
+        createBroker(new ClassPathResource("org/apache/activemq/usecases/receiver-secured.xml"));
+    }
+
+    public void testRequestReply() throws Exception {
+        ActiveMQQueue requestReplyDest = new ActiveMQQueue("RequestReply");
+
+        startAllBrokers();
+        waitForBridgeFormation();
+        waitForMinTopicRegionConsumerCount("sender", 1);
+        waitForMinTopicRegionConsumerCount("receiver", 1);
+
+
+        ConnectionFactory factory = getConnectionFactory("sender");
+        ActiveMQConnection conn = (ActiveMQConnection) factory.createConnection("system",
"manager");
+        conn.setWatchTopicAdvisories(false);
+        conn.start();
+        Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+        ConnectionFactory replyFactory = getConnectionFactory("receiver");
+        for (int i = 0; i < 2000; i++) {
+            TemporaryQueue tempDest = session.createTemporaryQueue();
+            MessageProducer producer = session.createProducer(requestReplyDest);
+            javax.jms.Message message = session.createTextMessage("req-" + i);
+            message.setJMSReplyTo(tempDest);
+
+            ActiveMQMessageConsumer consumer = (ActiveMQMessageConsumer) session.createConsumer(tempDest);
+            producer.send(message);
+
+            ActiveMQConnection replyConnection = (ActiveMQConnection) replyFactory.createConnection("system",
"manager");
+            replyConnection.setWatchTopicAdvisories(false);
+            replyConnection.start();
+            Session replySession = replyConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+            ActiveMQMessageConsumer replyConsumer = (ActiveMQMessageConsumer) replySession.createConsumer(requestReplyDest);
+            javax.jms.Message msg = replyConsumer.receive(10000);
+            assertNotNull("request message not null: " + i, msg);
+            MessageProducer replyProducer = replySession.createProducer(msg.getJMSReplyTo());
+            replyProducer.send(session.createTextMessage("reply-" + i));
+            replyConnection.close();
+
+            javax.jms.Message reply = consumer.receive(10000);
+            assertNotNull("reply message : " + i + ", to: " + tempDest + ", by consumer:"
+ consumer.getConsumerId(), reply);
+            consumer.close();
+            tempDest.delete();
+            LOG.info("message #" + i + " processed");
+        }
+
+    }
+
+
+}

Modified: activemq/trunk/activemq-core/src/test/resources/org/apache/activemq/security/simple-auth-broker.xml
URL: http://svn.apache.org/viewvc/activemq/trunk/activemq-core/src/test/resources/org/apache/activemq/security/simple-auth-broker.xml?rev=1393988&r1=1393987&r2=1393988&view=diff
==============================================================================
--- activemq/trunk/activemq-core/src/test/resources/org/apache/activemq/security/simple-auth-broker.xml
(original)
+++ activemq/trunk/activemq-core/src/test/resources/org/apache/activemq/security/simple-auth-broker.xml
Thu Oct  4 11:09:21 2012
@@ -76,7 +76,7 @@
               <authorizationEntry topic="ActiveMQ.Advisory.>" read="guests,users" write="guests,users"
admin="guests,users"/>
             </authorizationEntries>
             <tempDestinationAuthorizationEntry>  
-                <tempDestinationAuthorizationEntry read="admin" write="admin" admin="admin"/>

+                <tempDestinationAuthorizationEntry read="admins" write="admins" admin="admins"/>
             </tempDestinationAuthorizationEntry>     
           </authorizationMap>
         </map>

Added: activemq/trunk/activemq-core/src/test/resources/org/apache/activemq/usecases/receiver-secured.xml
URL: http://svn.apache.org/viewvc/activemq/trunk/activemq-core/src/test/resources/org/apache/activemq/usecases/receiver-secured.xml?rev=1393988&view=auto
==============================================================================
--- activemq/trunk/activemq-core/src/test/resources/org/apache/activemq/usecases/receiver-secured.xml
(added)
+++ activemq/trunk/activemq-core/src/test/resources/org/apache/activemq/usecases/receiver-secured.xml
Thu Oct  4 11:09:21 2012
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); 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 "AS IS" 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.
+-->
+<beans 
+  xmlns="http://www.springframework.org/schema/beans" 
+  xmlns:amq="http://activemq.apache.org/schema/core"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
+  http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core.xsd">
+
+  <broker brokerName="receiver" persistent="false" useJmx="true" allowTempAutoCreationOnSend="true"
xmlns="http://activemq.apache.org/schema/core">
+
+    <networkConnectors>
+      <networkConnector uri="static:(tcp://localhost:62001)" userName="system" password="manager"/>
+    </networkConnectors>
+
+    <persistenceAdapter>
+      <memoryPersistenceAdapter/>
+    </persistenceAdapter>
+
+      <plugins>
+          <simpleAuthenticationPlugin>
+              <users>
+                  <authenticationUser username="system" password="manager"
+                                      groups="users,admins"/>
+                  <authenticationUser username="user" password="user"
+                                      groups="users"/>
+                  <authenticationUser username="guest" password="guest" groups="guests"/>
+              </users>
+          </simpleAuthenticationPlugin>
+
+
+          <!--  lets configure a destination based authorization mechanism -->
+          <authorizationPlugin>
+              <map>
+                  <authorizationMap>
+                      <authorizationEntries>
+                          <authorizationEntry queue=">" read="admins" write="admins"
admin="admins" />
+                          <authorizationEntry queue="USERS.>" read="users" write="users"
admin="users" />
+                          <authorizationEntry queue="GUEST.>" read="guests" write="guests,users"
admin="guests,users" />
+
+                          <authorizationEntry queue="TEST.Q" read="guests" write="guests"
/>
+
+                          <authorizationEntry topic=">" read="admins" write="admins"
admin="admins" />
+                          <authorizationEntry topic="USERS.>" read="users" write="users"
admin="users" />
+                          <authorizationEntry topic="GUEST.>" read="guests" write="guests,users"
admin="guests,users" />
+
+                          <authorizationEntry topic="ActiveMQ.Advisory.>" read="guests,users"
write="guests,users" admin="guests,users"/>
+                      </authorizationEntries>
+                      <tempDestinationAuthorizationEntry>
+                          <tempDestinationAuthorizationEntry read="admins" write="admins"
admin="admins"/>
+                      </tempDestinationAuthorizationEntry>
+                  </authorizationMap>
+              </map>
+          </authorizationPlugin>
+      </plugins>
+
+    <transportConnectors>
+      <transportConnector uri="tcp://localhost:62002"/>
+    </transportConnectors>
+
+  </broker>
+
+</beans>

Propchange: activemq/trunk/activemq-core/src/test/resources/org/apache/activemq/usecases/receiver-secured.xml
------------------------------------------------------------------------------
    svn:executable = *

Added: activemq/trunk/activemq-core/src/test/resources/org/apache/activemq/usecases/sender-secured.xml
URL: http://svn.apache.org/viewvc/activemq/trunk/activemq-core/src/test/resources/org/apache/activemq/usecases/sender-secured.xml?rev=1393988&view=auto
==============================================================================
--- activemq/trunk/activemq-core/src/test/resources/org/apache/activemq/usecases/sender-secured.xml
(added)
+++ activemq/trunk/activemq-core/src/test/resources/org/apache/activemq/usecases/sender-secured.xml
Thu Oct  4 11:09:21 2012
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); 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 "AS IS" 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.
+-->
+<beans 
+  xmlns="http://www.springframework.org/schema/beans" 
+  xmlns:amq="http://activemq.apache.org/schema/core"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
+  http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core.xsd">
+
+  <broker brokerName="sender" persistent="false" useJmx="true" xmlns="http://activemq.apache.org/schema/core">
+
+    <networkConnectors>
+      <networkConnector uri="static:(tcp://localhost:62002)" userName="system" password="manager">
+        <staticallyIncludedDestinations>
+          <topic physicalName=">"/>
+        </staticallyIncludedDestinations>
+      </networkConnector>
+    </networkConnectors>
+
+    <persistenceAdapter>
+      <memoryPersistenceAdapter/>
+    </persistenceAdapter>
+
+      <plugins>
+          <simpleAuthenticationPlugin>
+              <users>
+                  <authenticationUser username="system" password="manager"
+                                      groups="users,admins"/>
+                  <authenticationUser username="user" password="user"
+                                      groups="users"/>
+                  <authenticationUser username="guest" password="guest" groups="guests"/>
+              </users>
+          </simpleAuthenticationPlugin>
+
+
+          <!--  lets configure a destination based authorization mechanism -->
+          <authorizationPlugin>
+              <map>
+                  <authorizationMap>
+                      <authorizationEntries>
+                          <authorizationEntry queue=">" read="admins" write="admins"
admin="admins" />
+                          <authorizationEntry queue="USERS.>" read="users" write="users"
admin="users" />
+                          <authorizationEntry queue="GUEST.>" read="guests" write="guests,users"
admin="guests,users" />
+
+                          <authorizationEntry queue="TEST.Q" read="guests" write="guests"
/>
+
+                          <authorizationEntry topic=">" read="admins" write="admins"
admin="admins" />
+                          <authorizationEntry topic="USERS.>" read="users" write="users"
admin="users" />
+                          <authorizationEntry topic="GUEST.>" read="guests" write="guests,users"
admin="guests,users" />
+
+                          <authorizationEntry topic="ActiveMQ.Advisory.>" read="guests,users"
write="guests,users" admin="guests,users"/>
+                      </authorizationEntries>
+                      <tempDestinationAuthorizationEntry>
+                          <tempDestinationAuthorizationEntry read="admins" write="admins"
admin="admins"/>
+                      </tempDestinationAuthorizationEntry>
+                  </authorizationMap>
+              </map>
+          </authorizationPlugin>
+      </plugins>
+
+    <transportConnectors>
+      <transportConnector uri="tcp://localhost:62001"/>
+    </transportConnectors>
+
+  </broker>
+
+</beans>

Propchange: activemq/trunk/activemq-core/src/test/resources/org/apache/activemq/usecases/sender-secured.xml
------------------------------------------------------------------------------
    svn:executable = *



Mime
View raw message