aboutsummaryrefslogtreecommitdiffstats
path: root/main/ruby/openssl-config-support-include-directive.patch
blob: 2abf463760a083ca69947bc0536fc032bb9a4d31 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
From f46bac1f3e8634e24c747d06b28e11b874f1e488 Mon Sep 17 00:00:00 2001
From: Kazuki Yamaguchi <k@rhe.jp>
Date: Thu, 16 Aug 2018 19:40:48 +0900
Subject: [PATCH] config: support .include directive

OpenSSL 1.1.1 introduces a new '.include' directive. Update our config
parser to support that.

As mentioned in the referenced GitHub issue, we should use the OpenSSL
API instead of implementing the parsing logic ourselves, but it will
need backwards-incompatible changes which we can't backport to stable
versions. So continue to use the Ruby implementation for now.

Reference: https://github.com/ruby/openssl/issues/208

Patch-Source: https://src.fedoraproject.org/rpms/ruby/blob/04b63f48ea89ff10fcffafe2ff3815dfa0e16e99/f/ruby-2.6.0-config-support-include-directive.patch
---
 ext/openssl/lib/openssl/config.rb | 54 ++++++++++++++++++++-----------
 test/openssl/test_config.rb       | 54 +++++++++++++++++++++++++++++++
 2 files changed, 90 insertions(+), 18 deletions(-)

diff --git a/ext/openssl/lib/openssl/config.rb b/ext/openssl/lib/openssl/config.rb
index 88225451..ba3a54c8 100644
--- a/ext/openssl/lib/openssl/config.rb
+++ b/ext/openssl/lib/openssl/config.rb
@@ -77,29 +77,44 @@ def get_key_string(data, section, key) # :nodoc:
       def parse_config_lines(io)
         section = 'default'
         data = {section => {}}
-        while definition = get_definition(io)
+        io_stack = [io]
+        while definition = get_definition(io_stack)
           definition = clear_comments(definition)
           next if definition.empty?
-          if definition[0] == ?[
+          case definition
+          when /\A\[/
             if /\[([^\]]*)\]/ =~ definition
               section = $1.strip
               data[section] ||= {}
             else
               raise ConfigError, "missing close square bracket"
             end
-          else
-            if /\A([^:\s]*)(?:::([^:\s]*))?\s*=(.*)\z/ =~ definition
-              if $2
-                section = $1
-                key = $2
-              else
-                key = $1
+          when /\A\.include (\s*=\s*)?(.+)\z/
+            path = $2
+            if File.directory?(path)
+              files = Dir.glob(File.join(path, "*.{cnf,conf}"), File::FNM_EXTGLOB)
+            else
+              files = [path]
+            end
+
+            files.each do |filename|
+              begin
+                io_stack << StringIO.new(File.read(filename))
+              rescue
+                raise ConfigError, "could not include file '%s'" % filename
               end
-              value = unescape_value(data, section, $3)
-              (data[section] ||= {})[key] = value.strip
+            end
+          when /\A([^:\s]*)(?:::([^:\s]*))?\s*=(.*)\z/
+            if $2
+              section = $1
+              key = $2
             else
-              raise ConfigError, "missing equal sign"
+              key = $1
             end
+            value = unescape_value(data, section, $3)
+            (data[section] ||= {})[key] = value.strip
+          else
+            raise ConfigError, "missing equal sign"
           end
         end
         data
@@ -212,10 +227,10 @@ def clear_comments(line)
         scanned.join
       end
 
-      def get_definition(io)
-        if line = get_line(io)
+      def get_definition(io_stack)
+        if line = get_line(io_stack)
           while /[^\\]\\\z/ =~ line
-            if extra = get_line(io)
+            if extra = get_line(io_stack)
               line += extra
             else
               break
@@ -225,9 +240,12 @@ def get_definition(io)
         end
       end
 
-      def get_line(io)
-        if line = io.gets
-          line.gsub(/[\r\n]*/, '')
+      def get_line(io_stack)
+        while io = io_stack.last
+          if line = io.gets
+            return line.gsub(/[\r\n]*/, '')
+          end
+          io_stack.pop
         end
       end
     end
diff --git a/test/openssl/test_config.rb b/test/openssl/test_config.rb
index 99dcc497..5653b5d0 100644
--- a/test/openssl/test_config.rb
+++ b/test/openssl/test_config.rb
@@ -120,6 +120,49 @@ def test_s_parse_format
     assert_equal("error in line 7: missing close square bracket", excn.message)
   end
 
+  def test_s_parse_include
+    in_tmpdir("ossl-config-include-test") do |dir|
+      Dir.mkdir("child")
+      File.write("child/a.conf", <<~__EOC__)
+        [default]
+        file-a = a.conf
+        [sec-a]
+        a = 123
+      __EOC__
+      File.write("child/b.cnf", <<~__EOC__)
+        [default]
+        file-b = b.cnf
+        [sec-b]
+        b = 123
+      __EOC__
+      File.write("include-child.conf", <<~__EOC__)
+        key_outside_section = value_a
+        .include child
+      __EOC__
+
+      include_file = <<~__EOC__
+        [default]
+        file-main = unnamed
+        [sec-main]
+        main = 123
+        .include = include-child.conf
+      __EOC__
+
+      # Include a file by relative path
+      c1 = OpenSSL::Config.parse(include_file)
+      assert_equal(["default", "sec-a", "sec-b", "sec-main"], c1.sections.sort)
+      assert_equal(["file-main", "file-a", "file-b"], c1["default"].keys)
+      assert_equal({"a" => "123"}, c1["sec-a"])
+      assert_equal({"b" => "123"}, c1["sec-b"])
+      assert_equal({"main" => "123", "key_outside_section" => "value_a"}, c1["sec-main"])
+
+      # Relative paths are from the working directory
+      assert_raise(OpenSSL::ConfigError) do
+        Dir.chdir("child") { OpenSSL::Config.parse(include_file) }
+      end
+    end
+  end
+
   def test_s_load
     # alias of new
     c = OpenSSL::Config.load
@@ -299,6 +342,17 @@ def test_clone
     @it['newsection'] = {'a' => 'b'}
     assert_not_equal(@it.sections.sort, c.sections.sort)
   end
+
+  private
+
+  def in_tmpdir(*args)
+    Dir.mktmpdir(*args) do |dir|
+      dir = File.realpath(dir)
+      Dir.chdir(dir) do
+        yield dir
+      end
+    end
+  end
 end
 
 end