zmldifexport 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/Gemfile +2 -0
- data/README.md +2 -0
- data/Rakefile +9 -0
- data/bin/zmldifexport +46 -0
- data/data/amavisd-new-attrs.xml +103 -0
- data/data/zimbra-attrs.xml +8472 -0
- data/data/zimbra-ocs.xml +296 -0
- data/lib/zmldifexport.rb +487 -0
- data/lib/zmldifexport/version.rb +3 -0
- data/test/test_helper.rb +6 -0
- data/test/test_zmldifexport.rb +62 -0
- data/zmldifexport.gemspec +30 -0
- metadata +144 -0
data/data/zimbra-ocs.xml
ADDED
@@ -0,0 +1,296 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
|
3
|
+
<objectclasses group="ZimbraObjectClass" groupid="2">
|
4
|
+
|
5
|
+
<objectclass id="1" name="zimbraAccount" type="AUXILIARY">
|
6
|
+
<sup>zimbraMailRecipient</sup>
|
7
|
+
<desc>Account object</desc>
|
8
|
+
<comment>
|
9
|
+
zimbraAccount extends zimbraMailRecipient. It represents a real
|
10
|
+
account in the system (either admin or end-user) that can be logged
|
11
|
+
into, etc.
|
12
|
+
|
13
|
+
It is defined as AUXILIARY in case it needs to be mixed to an
|
14
|
+
existing directory deployment.
|
15
|
+
|
16
|
+
cn - full name, common name
|
17
|
+
co - country friendly name
|
18
|
+
company - company (company name)
|
19
|
+
displayName - name to display in admin tool, outlook uses as well
|
20
|
+
(cn is multi-valued)
|
21
|
+
gn - first name (given name)
|
22
|
+
initials - middle initial
|
23
|
+
l - city (locality)
|
24
|
+
ou - organizational unit
|
25
|
+
physicalDeliveryOfficeName - office
|
26
|
+
street - street address
|
27
|
+
postalCode - zip code
|
28
|
+
sn - last name (sir name)
|
29
|
+
st - state
|
30
|
+
telephoneNumber - phone
|
31
|
+
</comment>
|
32
|
+
</objectclass>
|
33
|
+
|
34
|
+
<objectclass id="3" name="zimbraCOS" type="STRUCTURAL">
|
35
|
+
<sup>top</sup>
|
36
|
+
<desc>Class of Service data</desc>
|
37
|
+
<comment>
|
38
|
+
zimbraCOS is the class of service object. it should have attributes
|
39
|
+
that are used as default values for accounts that belong to the COS
|
40
|
+
and do not have the same attribute defined in the account directory
|
41
|
+
entry.
|
42
|
+
</comment>
|
43
|
+
</objectclass>
|
44
|
+
|
45
|
+
<objectclass id="4" name="zimbraDomain" type="AUXILIARY">
|
46
|
+
<sup>zimbraMailRecipient</sup>
|
47
|
+
<desc>Domain object</desc>
|
48
|
+
<comment>
|
49
|
+
zimbraDomain is used to represent a domain in the directory. For
|
50
|
+
example, if we created the foo.com domain, then the dc=foo,dc=com
|
51
|
+
entry in LDAP would have an objectclass of zimbraDomain.
|
52
|
+
domain-specific configuration information (if so-needed) could be
|
53
|
+
added here.
|
54
|
+
</comment>
|
55
|
+
</objectclass>
|
56
|
+
|
57
|
+
<objectclass id="5" name="zimbraSecurityGroup" type="AUXILIARY">
|
58
|
+
<sup>top</sup>
|
59
|
+
<desc>Security Group</desc>
|
60
|
+
<comment>
|
61
|
+
zimbraSecurityGroup is used to represent a security group in the
|
62
|
+
directory. Members of this group have a zimbraMemberOf attr with
|
63
|
+
this group's zimbraId. If this group is a member of other groups,
|
64
|
+
then it will have those group's zimbraIds in its own zimbraMemberOf
|
65
|
+
list.
|
66
|
+
</comment>
|
67
|
+
</objectclass>
|
68
|
+
|
69
|
+
<objectclass id="6" name="zimbraDistributionList" type="STRUCTURAL">
|
70
|
+
<sup>top</sup>
|
71
|
+
<desc>Distribution List object</desc>
|
72
|
+
<comment>
|
73
|
+
zimbraDistributionList represents a distribution/mailing list. It
|
74
|
+
inherits from zimbraMailRecipient. Members (who can be
|
75
|
+
internal/external) are represented as zimbraMailForwardingAddress
|
76
|
+
attrs.
|
77
|
+
</comment>
|
78
|
+
</objectclass>
|
79
|
+
|
80
|
+
<objectclass id="7" name="zimbraMailRecipient" type="AUXILIARY">
|
81
|
+
<sup>top</sup>
|
82
|
+
<desc>Mail recipient object</desc>
|
83
|
+
<comment>
|
84
|
+
zimbraMailRecipient is used to represent entries in the directory
|
85
|
+
that can receive mail. i.e., they have a visible external address, that
|
86
|
+
gets expanded into one or more internal/external addresses.
|
87
|
+
</comment>
|
88
|
+
</objectclass>
|
89
|
+
|
90
|
+
<objectclass id="8" name="zimbraServer" type="STRUCTURAL">
|
91
|
+
<sup>top</sup>
|
92
|
+
<desc>Server in the cluster</desc>
|
93
|
+
<comment>
|
94
|
+
zimbraServer is used to represent a defined server within a zimbra
|
95
|
+
install. server-specific configuration information will be added
|
96
|
+
here. This includes information like which services should be
|
97
|
+
running on a server, whether or not the server is normally a
|
98
|
+
master/slave, etc.
|
99
|
+
</comment>
|
100
|
+
</objectclass>
|
101
|
+
|
102
|
+
<objectclass id="9" name="zimbraGlobalConfig" type="AUXILIARY">
|
103
|
+
<sup>top</sup>
|
104
|
+
<desc>global config</desc>
|
105
|
+
<comment>
|
106
|
+
zimbraGlobalConfig is the entry that holds all the global
|
107
|
+
configuration attrs.
|
108
|
+
</comment>
|
109
|
+
</objectclass>
|
110
|
+
|
111
|
+
<objectclass id="11" name="zimbraAlias" type="STRUCTURAL">
|
112
|
+
<sup>top</sup>
|
113
|
+
<desc>An alias to another zimbra object</desc>
|
114
|
+
<comment>
|
115
|
+
zimbraAlias is used to privision aliases
|
116
|
+
</comment>
|
117
|
+
</objectclass>
|
118
|
+
|
119
|
+
<objectclass id="12" name="zimbraMimeEntry" type="STRUCTURAL">
|
120
|
+
<sup>top</sup>
|
121
|
+
<desc>MIME type info</desc>
|
122
|
+
<comment>
|
123
|
+
zimbraMimeEntry is used to represent status about mime types
|
124
|
+
</comment>
|
125
|
+
</objectclass>
|
126
|
+
|
127
|
+
<objectclass id="13" name="zimbraObjectEntry" type="STRUCTURAL">
|
128
|
+
<sup>top</sup>
|
129
|
+
<desc>Object type info</desc>
|
130
|
+
<comment>
|
131
|
+
zimbraObjectEntry is used to represent status about object types
|
132
|
+
</comment>
|
133
|
+
</objectclass>
|
134
|
+
|
135
|
+
<objectclass id="14" name="zimbraTimeZone" type="STRUCTURAL">
|
136
|
+
<sup>top</sup>
|
137
|
+
<desc>Time Zone info</desc>
|
138
|
+
<comment>
|
139
|
+
zimbraTimeZone is used to define a timezone with daylight savings
|
140
|
+
time rules. Used in calendar.
|
141
|
+
</comment>
|
142
|
+
</objectclass>
|
143
|
+
|
144
|
+
<objectclass id="15" name="zimbraZimletEntry" type="STRUCTURAL">
|
145
|
+
<sup>top</sup>
|
146
|
+
<desc>Zimlet info</desc>
|
147
|
+
<comment>
|
148
|
+
zimbraZimletEntry is used to represent Zimlets
|
149
|
+
</comment>
|
150
|
+
</objectclass>
|
151
|
+
|
152
|
+
<objectclass id="16" name="zimbraCalendarResource" type="AUXILIARY">
|
153
|
+
<sup>top</sup>
|
154
|
+
<desc>Calendar resource object</desc>
|
155
|
+
<comment>
|
156
|
+
zimbraCalendarResource is used to represent a calendar resource This
|
157
|
+
objectclass should be mixed in to a zimbraAccount entry.
|
158
|
+
</comment>
|
159
|
+
</objectclass>
|
160
|
+
|
161
|
+
<objectclass id="17" name="zimbraIdentity" type="STRUCTURAL">
|
162
|
+
<sup>top</sup>
|
163
|
+
<desc>Account Email Identity information</desc>
|
164
|
+
<comment>
|
165
|
+
zimbraIdentity is the account identity object, which stores information about
|
166
|
+
Email "identities"
|
167
|
+
</comment>
|
168
|
+
</objectclass>
|
169
|
+
|
170
|
+
<objectclass id="18" name="zimbraDataSource" type="STRUCTURAL">
|
171
|
+
<sup>top</sup>
|
172
|
+
<desc>base class for data source objects</desc>
|
173
|
+
<comment>
|
174
|
+
zimbraDataSource is the base class for data source objects
|
175
|
+
</comment>
|
176
|
+
</objectclass>
|
177
|
+
|
178
|
+
<objectclass id="19" name="zimbraPop3DataSource" type="STRUCTURAL">
|
179
|
+
<sup>zimbraDataSource</sup>
|
180
|
+
<desc>POP3 data source object</desc>
|
181
|
+
<comment>
|
182
|
+
zimbraPop3DataSource is the base class for POP3 data source objects
|
183
|
+
</comment>
|
184
|
+
</objectclass>
|
185
|
+
|
186
|
+
<objectclass id="20" name="zimbraImapDataSource" type="STRUCTURAL">
|
187
|
+
<sup>zimbraDataSource</sup>
|
188
|
+
<desc>IMAP data source object</desc>
|
189
|
+
<comment>
|
190
|
+
zimbraImapDataSource is the base class for IMAP data source objects
|
191
|
+
</comment>
|
192
|
+
</objectclass>
|
193
|
+
|
194
|
+
<objectclass id="21" name="zimbraSignature" type="STRUCTURAL">
|
195
|
+
<sup>top</sup>
|
196
|
+
<desc>Account Email Signature information</desc>
|
197
|
+
<comment>
|
198
|
+
zimbraSignature is the account Signature object, which stores information about
|
199
|
+
Email "signatures"
|
200
|
+
</comment>
|
201
|
+
</objectclass>
|
202
|
+
|
203
|
+
<objectclass id="22" name="zimbraXMPPComponent" type="STRUCTURAL">
|
204
|
+
<sup>top</sup>
|
205
|
+
<desc>XMPP Component Configuration Record</desc>
|
206
|
+
<comment>
|
207
|
+
zimbraXMPPComponent defines information about routable XMPP services such as conference
|
208
|
+
rooms, file transfer servies, etc
|
209
|
+
</comment>
|
210
|
+
</objectclass>
|
211
|
+
|
212
|
+
<objectclass id="23" name="zimbraAclTarget" type="STRUCTURAL">
|
213
|
+
<sup>top</sup>
|
214
|
+
<desc>acl target</desc>
|
215
|
+
<comment>
|
216
|
+
target entries on which rights(acl) can be granted
|
217
|
+
</comment>
|
218
|
+
</objectclass>
|
219
|
+
|
220
|
+
<objectclass id="24" name="zimbraRssDataSource" type="STRUCTURAL">
|
221
|
+
<sup>zimbraDataSource</sup>
|
222
|
+
<desc>RSS data source object</desc>
|
223
|
+
<comment>
|
224
|
+
Represents an RSS data source.
|
225
|
+
</comment>
|
226
|
+
</objectclass>
|
227
|
+
|
228
|
+
<objectclass id="25" name="zimbraLiveDataSource" type="STRUCTURAL">
|
229
|
+
<sup>zimbraDataSource</sup>
|
230
|
+
<desc>Hotmail data source object</desc>
|
231
|
+
<comment>
|
232
|
+
Represents a Hotmail data source.
|
233
|
+
</comment>
|
234
|
+
</objectclass>
|
235
|
+
|
236
|
+
<objectclass id="26" name="zimbraGalDataSource" type="STRUCTURAL">
|
237
|
+
<sup>zimbraDataSource</sup>
|
238
|
+
<desc>GAL data source object</desc>
|
239
|
+
<comment>
|
240
|
+
Represents a GAL data source.
|
241
|
+
</comment>
|
242
|
+
</objectclass>
|
243
|
+
|
244
|
+
<objectclass id="27" name="zimbraGroup" type="AUXILIARY">
|
245
|
+
<sup>top</sup>
|
246
|
+
<desc>hold attributes for dynamic groups</desc>
|
247
|
+
<comment>
|
248
|
+
hold attributes for dynamic groups
|
249
|
+
</comment>
|
250
|
+
</objectclass>
|
251
|
+
|
252
|
+
<objectclass id="28" name="zimbraGroupDynamicUnit" type="AUXILIARY">
|
253
|
+
<sup>top</sup>
|
254
|
+
<desc>hold attributes for the dynamic entry of dynamic groups.</desc>
|
255
|
+
<comment>
|
256
|
+
Dynamic entry of a dynamic group is for members provisioned on the system.
|
257
|
+
</comment>
|
258
|
+
</objectclass>
|
259
|
+
|
260
|
+
<objectclass id="29" name="zimbraGroupStaticUnit" type="STRUCTURAL">
|
261
|
+
<sup>top</sup>
|
262
|
+
<desc>hold attributes for the static entry of dynamic groups.</desc>
|
263
|
+
<comment>
|
264
|
+
Static entry of a dynamic group is for members not provisioned on the system.
|
265
|
+
</comment>
|
266
|
+
</objectclass>
|
267
|
+
|
268
|
+
<objectclass id="30" name="zimbraShareLocator" type="STRUCTURAL">
|
269
|
+
<sup>top</sup>
|
270
|
+
<desc>Share locator</desc>
|
271
|
+
<comment>
|
272
|
+
A share locator points to the current location of the share. This
|
273
|
+
allows shared data to be relocated transparently without requiring
|
274
|
+
the share users to update their mountpoint definition.
|
275
|
+
</comment>
|
276
|
+
</objectclass>
|
277
|
+
|
278
|
+
<objectclass id="31" name="zimbraUCService" type="STRUCTURAL">
|
279
|
+
<sup>top</sup>
|
280
|
+
<desc>Unified Communication service in the cluster</desc>
|
281
|
+
<comment>
|
282
|
+
zimbraUCService is used to represent a third party UC service.
|
283
|
+
This includes information like URLs to the UC providers service and
|
284
|
+
Zimbra extensions to load for the service, etc.
|
285
|
+
</comment>
|
286
|
+
</objectclass>
|
287
|
+
|
288
|
+
<objectclass id="32" name="zimbraAlwaysOnCluster" type="STRUCTURAL">
|
289
|
+
<sup>top</sup>
|
290
|
+
<desc>AlwaysOn Cluster</desc>
|
291
|
+
<comment>
|
292
|
+
zimbraAlwaysOnCluster is used to represent a alwaysOn cluster.
|
293
|
+
</comment>
|
294
|
+
</objectclass>
|
295
|
+
|
296
|
+
</objectclasses>
|
data/lib/zmldifexport.rb
ADDED
@@ -0,0 +1,487 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'nokogiri'
|
3
|
+
require 'net-ldap'
|
4
|
+
|
5
|
+
module ZmLdifExport
|
6
|
+
|
7
|
+
class << self
|
8
|
+
@options = nil
|
9
|
+
@file_path = nil
|
10
|
+
|
11
|
+
def ldif_file
|
12
|
+
begin
|
13
|
+
open @file_path
|
14
|
+
rescue Errno::ENOENT => _
|
15
|
+
puts "File #{@file_path} not found"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Load the LDIF in Memory
|
20
|
+
def ldif_data
|
21
|
+
ldif_data = Net::LDAP::Dataset.read_ldif ldif_file
|
22
|
+
puts "File #{@file_path} not a valid LDIF file" if ldif_data.empty?
|
23
|
+
ldif_data
|
24
|
+
end
|
25
|
+
|
26
|
+
def parse_ldif
|
27
|
+
ldif_data.to_entries.each do |entry|
|
28
|
+
Domain.add Domain.new(entry) if entry.objectclass.include?(OCLASS_DOMAIN)
|
29
|
+
DistributionList.add DistributionList.new(entry) if entry.objectclass.include?(OCLASS_DL)
|
30
|
+
Account.add Account.new(entry) if valid_account_entry?(entry)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def valid_account_entry?(entry)
|
35
|
+
entry.objectclass.include?(OCLASS_ACCOUNT) && entry.dn =~ /(#{domain_to_ldif_dc}$|uid=admin|uid=spam|uid=virus|uid=nospam)/
|
36
|
+
end
|
37
|
+
|
38
|
+
# Entry point do all
|
39
|
+
def run(options)
|
40
|
+
@options = options
|
41
|
+
@file_path = @options[:ldif_file]
|
42
|
+
@domain = @options[:domain]
|
43
|
+
@command = @options[:command]
|
44
|
+
|
45
|
+
parse_ldif
|
46
|
+
self.send(@command)
|
47
|
+
end
|
48
|
+
|
49
|
+
def exportAccounts
|
50
|
+
puts "export LC_ALL=en_US.UTF-8"
|
51
|
+
puts "export LANG=en_US.UTF-8"
|
52
|
+
Account.all.each do |a|
|
53
|
+
next if a.name.first =~ /(admin|spam|ham|galsync|virus|wiki)/
|
54
|
+
puts "# zmprov ca #{a.name(:zmprov, @domain)}"
|
55
|
+
puts a.zmprov_ca(domain: @domain)
|
56
|
+
puts ""
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def exportAliases
|
61
|
+
Account.all.each do |a|
|
62
|
+
next unless a.has_alias?
|
63
|
+
puts "# zmprov aaa #{a.name(:zmprov, @domain)}"
|
64
|
+
puts a.zmprov_aaa(domain: @domain)
|
65
|
+
puts ""
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def exportDls
|
70
|
+
puts "export LC_ALL=en_US.UTF-8"
|
71
|
+
puts "export LANG=en_US.UTF-8"
|
72
|
+
DistributionList.all.each do |dl|
|
73
|
+
next if dl.name(:zmprov) =~ /archive/i
|
74
|
+
puts "# Create DistributionList #{dl.name(:zmprov, @domain)}"
|
75
|
+
puts dl.zmprov_cdl domain: "@#{@domain}"
|
76
|
+
puts ""
|
77
|
+
puts "# Add members to DistributionList #{dl.name(:zmprov, @domain)}"
|
78
|
+
puts dl.zmprov_adlm domain: "@#{@domain}"
|
79
|
+
puts ""
|
80
|
+
puts "# Add Alias to DistributionList #{dl.name(:zmprov, @domain)}"
|
81
|
+
puts dl.zmprov_adla domain: "@#{@domain}"
|
82
|
+
puts ""
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def domain_to_ldif_dc
|
87
|
+
"dc=#{@domain.split('.').join(',dc=')}"
|
88
|
+
end
|
89
|
+
|
90
|
+
# Build the resulting command
|
91
|
+
# @param zm_cmd [String] `zmprov` or `zmmailbox`
|
92
|
+
# @param command [String] command to excute, like `ca` or `createDomain`
|
93
|
+
# @param attributes [Array] Array of attributes to pass to the command
|
94
|
+
# @param full [Boolean] If print `zm_cmd` or just the `command`
|
95
|
+
# @returns [String]
|
96
|
+
def mk_command(zm_cmd: 'zmprov', command:, resource:, attributes: {}, full: true)
|
97
|
+
comment = "# #{zm_cmd} #{command} #{resource}"
|
98
|
+
command = full ? "#{zm_cmd} #{command} #{resource}" : "#{command} #{resource}"
|
99
|
+
command = command + " \\" if attributes.keys.any?
|
100
|
+
attributes = attrs_to_cmd(attributes)
|
101
|
+
|
102
|
+
[comment, command, attributes].compact.join("\n")
|
103
|
+
end
|
104
|
+
|
105
|
+
# Return an array of attributes
|
106
|
+
def attrs_to_cmd(attributes = {})
|
107
|
+
lines = []
|
108
|
+
attributes.each do |k,v|
|
109
|
+
values = [v].flatten.compact
|
110
|
+
values.each do |value|
|
111
|
+
lines.push "#{k} #{value} \\"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
return nil if lines.empty?
|
115
|
+
lines.last.gsub!(' \\', '')
|
116
|
+
return lines
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
# CONSTANTS
|
122
|
+
ZIMBRA_ATTRS_FILE = './data/zimbra-attrs.xml'.freeze
|
123
|
+
LDIF_FILE = ARGV[0]
|
124
|
+
OCLASS_DOMAIN = 'zimbraDomain'
|
125
|
+
OCLASS_ACCOUNT = 'zimbraAccount'
|
126
|
+
OCLASS_DL = 'zimbraDistributionList'
|
127
|
+
|
128
|
+
# LDIF_DATA = Net::LDAP::Dataset.read_ldif(open(LDIF_FILE)).to_entries
|
129
|
+
@domains = nil
|
130
|
+
@accounts = nil
|
131
|
+
@aliases = nil
|
132
|
+
|
133
|
+
class Attribute
|
134
|
+
attr_reader :name, :type
|
135
|
+
|
136
|
+
WHITE_LIST = ['zimbraId', 'zimbraMailAlias'].freeze
|
137
|
+
|
138
|
+
def initialize(xml_attr)
|
139
|
+
@name = xml_attr.attributes['name'].value
|
140
|
+
@singular = attr_is_singular?(xml_attr)
|
141
|
+
@immutable = attr_is_immutable?(xml_attr)
|
142
|
+
@type = xml_attr.attributes['type'].value
|
143
|
+
end
|
144
|
+
|
145
|
+
def singular?
|
146
|
+
@singular
|
147
|
+
end
|
148
|
+
|
149
|
+
def immutable?
|
150
|
+
@immutable
|
151
|
+
end
|
152
|
+
|
153
|
+
def multiline?
|
154
|
+
name == 'zimbraPrefMailSignatureHTML' || name == 'zimbraMailSieveScript' || name == 'zimbraPrefOutOfOfficeReply' || name == 'zimbraPrefOutOfOfficeExternalReply'
|
155
|
+
end
|
156
|
+
|
157
|
+
def text?
|
158
|
+
type == 'string' || type == 'duration' || name == 'zimbraACE' || type == 'regex'
|
159
|
+
end
|
160
|
+
|
161
|
+
def number?
|
162
|
+
type == 'integer' || type == 'long'
|
163
|
+
end
|
164
|
+
|
165
|
+
def enum?
|
166
|
+
type == 'enum'
|
167
|
+
end
|
168
|
+
|
169
|
+
def boolean?
|
170
|
+
type == "boolean"
|
171
|
+
end
|
172
|
+
|
173
|
+
def to_zmprov(value, plus = false)
|
174
|
+
if multiline?
|
175
|
+
command_multiline attr_name: name, value: value
|
176
|
+
elsif singular?
|
177
|
+
return " \\\n#{name} '#{value.join('').to_s}'" if text?
|
178
|
+
return " \\\n#{name} #{value.join('').to_i}" if number?
|
179
|
+
return " \\\n#{name} #{value.join('').to_s}"
|
180
|
+
else
|
181
|
+
line = " \\\n#{name} "
|
182
|
+
value = text? ? value.map {|v| "'#{v}'"} : value
|
183
|
+
if plus
|
184
|
+
line << value.join(" +#{name} ")
|
185
|
+
else
|
186
|
+
line << value.join(" \\\n#{name} ")
|
187
|
+
end
|
188
|
+
|
189
|
+
line
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def command_multiline(attr_name:,value:)
|
194
|
+
final_wrapper = attr_name == 'zimbraMailSieveScript' ? "'" : '"'
|
195
|
+
original_wrapper = attr_name == 'zimbraMailSieveScript' ? '"' : "'"
|
196
|
+
line = "#{attr_name} #{final_wrapper}"
|
197
|
+
line << value.join('').to_s.gsub(final_wrapper, original_wrapper).split(/\n/).join("\n")
|
198
|
+
line << "#{final_wrapper} \\\n"
|
199
|
+
line
|
200
|
+
end
|
201
|
+
|
202
|
+
private
|
203
|
+
|
204
|
+
def attr_is_singular?(attr)
|
205
|
+
return false if attr.attributes['cardinality'].nil?
|
206
|
+
return true if attr.attributes['type'] == 'astring'
|
207
|
+
attr.attributes['cardinality'].value == 'single'
|
208
|
+
end
|
209
|
+
|
210
|
+
def attr_is_immutable?(attr)
|
211
|
+
return false if WHITE_LIST.include?(name)
|
212
|
+
return false if attr.attributes['immutable'].nil?
|
213
|
+
attr.attributes['immutable'].value == '1'
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
class ZimbraResource
|
218
|
+
|
219
|
+
BLACK_LIST_ATTRS = [
|
220
|
+
"dn", "objectclass", "o", "dc", "structuralobjectclass", "entryuuid", "creatorsname",
|
221
|
+
"createtimestamp", "entrycsn", "modifiersname", "modifytimestamp", "uid",
|
222
|
+
"zimbraaggregatequotalastusage", "zimbradomainstatus", "zimbramailstatus", "zimbraauthtokens",
|
223
|
+
"amavisarchivequarantineto", 'zimbramailhost', 'zimbramailtransport', 'zimbramailquota',
|
224
|
+
'zimbraaccountstatus', 'zimbraarchiveaccount', 'zimbraarchiveenabled', 'zimbraauthtokenvalidityvalue',
|
225
|
+
'zimbraPasswordModifiedTime', 'zimbrapreftimezoneid', 'zimbraquotalastwarntime', 'zimbracsrftokendata',
|
226
|
+
'zimbraprefpop3downloadsince', 'zimbrapasswordmodifiedtime', 'zimbracosid',
|
227
|
+
'zimbrapreflistviewcolumns', 'zimbraisadminaccount', 'zimbraisadminaccount', 'zimbraAdminSavedSearches',
|
228
|
+
'zimbraisadmingroup', 'zimbraadminconsoleuicomponents'
|
229
|
+
]
|
230
|
+
|
231
|
+
attr_reader :attributes, :name, :real_attrs
|
232
|
+
|
233
|
+
@immutable_attrs = nil
|
234
|
+
@attrs_xml = nil
|
235
|
+
|
236
|
+
def self.attrs_xml
|
237
|
+
return @attrs_xml if @attrs_xml
|
238
|
+
xml_doc = Nokogiri::XML(open(ZIMBRA_ATTRS_FILE))
|
239
|
+
@attrs_xml = xml_doc.xpath('//attr')
|
240
|
+
return @attrs_xml
|
241
|
+
end
|
242
|
+
|
243
|
+
def zimbra_attrs
|
244
|
+
return @zimbra_attrs if @zimbra_attrs
|
245
|
+
@zimbra_attrs = {}
|
246
|
+
ZimbraResource.attrs_xml.each do |attr|
|
247
|
+
zm_attr = Attribute.new(attr)
|
248
|
+
@zimbra_attrs[zm_attr.name.downcase] = zm_attr
|
249
|
+
end
|
250
|
+
return @zimbra_attrs
|
251
|
+
end
|
252
|
+
|
253
|
+
def name(mode = nil, domain = '')
|
254
|
+
return @name if mode.nil?
|
255
|
+
return "#{@name.join('').to_s}@#{domain}" if mode == :zmprov
|
256
|
+
end
|
257
|
+
|
258
|
+
def id
|
259
|
+
attributes['zimbraid'].first.to_s
|
260
|
+
end
|
261
|
+
|
262
|
+
def parse_attributes(ldif:, exclude: [])
|
263
|
+
attrs = {}
|
264
|
+
ldif.each_attribute do |a|
|
265
|
+
value = ldif[a]
|
266
|
+
next if invalid_attribute?(a.to_s) || exclude.include?(a) || invalid_value?(value)
|
267
|
+
attrs[a.to_s] = value
|
268
|
+
end
|
269
|
+
attrs
|
270
|
+
end
|
271
|
+
|
272
|
+
def invalid_value?(value)
|
273
|
+
return true if value.join('').to_s =~ /null/
|
274
|
+
end
|
275
|
+
|
276
|
+
def invalid_attribute?(attr_name)
|
277
|
+
return true if BLACK_LIST_ATTRS.include?(attr_name)
|
278
|
+
zm_attr = zimbra_attrs[attr_name]
|
279
|
+
zm_attr.nil? ? false : zm_attr.immutable?
|
280
|
+
end
|
281
|
+
|
282
|
+
def to_zmprov(command: 'ca')
|
283
|
+
zmprov_ca if command == 'ca'
|
284
|
+
zmprov_aaa if command == 'aaa'
|
285
|
+
zmprov_cdl if command == 'cdl'
|
286
|
+
zmprov_adlm if command == 'adlm'
|
287
|
+
zmprov_adla if command == 'adla'
|
288
|
+
end
|
289
|
+
|
290
|
+
def attr_to_zmprov(attr_name:, value:, plus: false)
|
291
|
+
zm_attr = zimbra_attrs[attr_name]
|
292
|
+
zm_attr.to_zmprov(value, plus)
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
class Domain < ZimbraResource
|
297
|
+
@resources = []
|
298
|
+
|
299
|
+
def self.all
|
300
|
+
return @resources
|
301
|
+
end
|
302
|
+
|
303
|
+
def self.add(resource)
|
304
|
+
@resources.push resource
|
305
|
+
end
|
306
|
+
|
307
|
+
def initialize(ldif)
|
308
|
+
@name = ldif[:zimbradomainname]
|
309
|
+
@attributes = parse_attributes(ldif: ldif, exclude: [:zimbradomainname])
|
310
|
+
end
|
311
|
+
|
312
|
+
def zmprov_cd
|
313
|
+
line = ['cd', name]
|
314
|
+
attributes.each do |name, value|
|
315
|
+
line << attr_to_zmprov(attr_name: name, value: value)
|
316
|
+
end
|
317
|
+
line.join(' ')
|
318
|
+
end
|
319
|
+
|
320
|
+
end
|
321
|
+
|
322
|
+
class Account < ZimbraResource
|
323
|
+
@resources = []
|
324
|
+
attr_accessor :ldif
|
325
|
+
|
326
|
+
def self.all
|
327
|
+
return @resources
|
328
|
+
end
|
329
|
+
|
330
|
+
def self.add(resource)
|
331
|
+
@resources.push resource
|
332
|
+
end
|
333
|
+
|
334
|
+
def has_alias?
|
335
|
+
attributes['zimbramailalias'] ? attributes['zimbramailalias'].any? : false
|
336
|
+
end
|
337
|
+
|
338
|
+
def initialize(ldif)
|
339
|
+
@name = ldif[:uid]
|
340
|
+
@ldif = ldif
|
341
|
+
@attributes = parse_attributes(ldif: ldif, exclude: [:uid])
|
342
|
+
end
|
343
|
+
|
344
|
+
|
345
|
+
def zmprov_ca(domain: '', mail_host: nil, skip: [], print: false)
|
346
|
+
line = [
|
347
|
+
'/opt/zimbra/bin/zmprov ca',
|
348
|
+
name(:zmprov, domain),
|
349
|
+
'P4ssW04r..,. '
|
350
|
+
]
|
351
|
+
line.push "zimbraMailHost #{mail_host} " unless mail_host.nil?
|
352
|
+
attributes.each do |name, value|
|
353
|
+
next if name =~ /(zimbramailalias|admin|zimbracosid|zimbraace|zimbrasharedItem|signature|zimbramailsievescript)/i
|
354
|
+
next if skip.include?(name)
|
355
|
+
line << attr_to_zmprov(attr_name: name, value: value)
|
356
|
+
end
|
357
|
+
cmd = line.join(' ')
|
358
|
+
puts cmd if print
|
359
|
+
return cmd
|
360
|
+
end
|
361
|
+
|
362
|
+
def zmprov_aaa(print: false, domain:)
|
363
|
+
aliases = attributes['zimbramailalias']
|
364
|
+
return if aliases.nil? || aliases.empty?
|
365
|
+
lines = []
|
366
|
+
aliases.each do |mail|
|
367
|
+
lines.push("aaa #{name(:zmprov, domain)} #{mail}")
|
368
|
+
end
|
369
|
+
|
370
|
+
cmd = lines.join("\n")
|
371
|
+
puts cmd if print
|
372
|
+
return cmd
|
373
|
+
end
|
374
|
+
|
375
|
+
def ace_to_json(zm_attr = '')
|
376
|
+
ace = {}
|
377
|
+
keys_and_values = zm_attr.split(';')
|
378
|
+
keys_and_values.each do |kv|
|
379
|
+
key, value = kv.split(':')
|
380
|
+
ace[key] = kv[value]
|
381
|
+
end
|
382
|
+
return ace
|
383
|
+
end
|
384
|
+
|
385
|
+
def aces(zma)
|
386
|
+
aces = []
|
387
|
+
zma.each do |zm|
|
388
|
+
aces.push(ace_to_json(zm))
|
389
|
+
end
|
390
|
+
|
391
|
+
return aces
|
392
|
+
end
|
393
|
+
|
394
|
+
def zmprov_sharing(print: false, domain:)
|
395
|
+
return nil if attributes['zimbrashareditem'].nil?
|
396
|
+
line = []
|
397
|
+
zimbra_shares = aces(attributes['zimbrashareditem'])
|
398
|
+
zimbra_shares.each do |ace|
|
399
|
+
next if ace['granteeName'] == 'null'
|
400
|
+
line.push("zmmailbox -z -m #{name(:zmprov, domain)} mfg '#{ace['folderPath']}' account #{ace['granteeName']} #{ace['rights']}")
|
401
|
+
end
|
402
|
+
|
403
|
+
cmd = line.size > 0 ? line.join("\n") + "\n" : nil
|
404
|
+
puts cmd if print
|
405
|
+
return cmd
|
406
|
+
end
|
407
|
+
|
408
|
+
def zmprov_signatures(print: false, domain:)
|
409
|
+
return nil if attributes['zimbraprefmailsignaturehtml'].nil?
|
410
|
+
line = []
|
411
|
+
zimbra_shares = attributes['zimbraprefmailsignaturehtml']
|
412
|
+
zimbra_shares.each do |ace|
|
413
|
+
ace = ace.gsub(/'/, '"')
|
414
|
+
line.push("ma #{name(:zmprov, domain)} zimbraPrefMailSignatureHtml '#{ace}'")
|
415
|
+
end
|
416
|
+
|
417
|
+
cmd = line.size > 0 ? line.join("\n") + "\n" : nil
|
418
|
+
puts cmd if print
|
419
|
+
return cmd
|
420
|
+
end
|
421
|
+
|
422
|
+
end
|
423
|
+
|
424
|
+
class DistributionList < ZimbraResource
|
425
|
+
@resources = []
|
426
|
+
|
427
|
+
def self.all
|
428
|
+
return @resources
|
429
|
+
end
|
430
|
+
|
431
|
+
def self.add(resource)
|
432
|
+
@resources.push resource
|
433
|
+
end
|
434
|
+
|
435
|
+
def initialize(ldif)
|
436
|
+
@name = ldif[:uid]
|
437
|
+
@attributes = parse_attributes(ldif: ldif, exclude: [:uid])
|
438
|
+
end
|
439
|
+
|
440
|
+
def zmprov_cdl(domain: '', print: false)
|
441
|
+
line = ['zmprov cdl', name(:zmprov, domain)]
|
442
|
+
attributes.each do |name, value|
|
443
|
+
next if name =~ /(zimbraMailForwardingAddress)|(zimbraMailAlias)/i
|
444
|
+
line << attr_to_zmprov(attr_name: name, value: value)
|
445
|
+
end
|
446
|
+
cmd = line.join(' ')
|
447
|
+
puts cmd if print
|
448
|
+
return cmd
|
449
|
+
end
|
450
|
+
|
451
|
+
def zmprov_adlm(domain:, print: false)
|
452
|
+
line = ['adlm', name(:zmprov, domain)]
|
453
|
+
attributes.each do |name, value|
|
454
|
+
next unless name =~ /zimbraMailForwardingAddress/i
|
455
|
+
line << value.join(' ')
|
456
|
+
end
|
457
|
+
cmd = line.join(' ')
|
458
|
+
puts cmd if print
|
459
|
+
return cmd
|
460
|
+
end
|
461
|
+
|
462
|
+
def zmprov_adla(domain:, print: false)
|
463
|
+
lines = []
|
464
|
+
attributes.each do |name, value|
|
465
|
+
next unless name =~ /zimbraMailAlias/i
|
466
|
+
value.each do |v|
|
467
|
+
lines << "adla #{name(:zmprov, domain)} #{v}"
|
468
|
+
end
|
469
|
+
end
|
470
|
+
cmd = lines.join(' ')
|
471
|
+
puts cmd if print
|
472
|
+
return cmd
|
473
|
+
end
|
474
|
+
end
|
475
|
+
|
476
|
+
|
477
|
+
|
478
|
+
|
479
|
+
# Domain.all.each do |domain|
|
480
|
+
# next if domain.name(:zmprov) =~ /archive/i
|
481
|
+
# puts domain.zmprov_cd
|
482
|
+
# puts "\n"
|
483
|
+
# end
|
484
|
+
|
485
|
+
|
486
|
+
end
|
487
|
+
|