summaryrefslogtreecommitdiff
path: root/bin/ldap2apache
blob: cfef1c714a4f10a9e5830c8764e89650cbdd0cf2 (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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
#!/usr/bin/ruby

#
# Copyright (c) 2004,2013 Peter Palfrader <peter@palfrader.org>
#
# All rights reserved.
#

require "ldap"
require "getoptlong"
require "myldap"
require "yaml"
require "etc"
require 'ipaddr'

@config = YAML::load( File.open( '/etc/noreply/config' ) )
ldap = MyLDAP.new(@config, "ldap2apache")
clients = ldap.conn.search2(@config['basedn'], LDAP::LDAP_SCOPE_SUBTREE, 'objectclass=tnClient')
# webstat
@rmagicdir = @config['module']['analog']['rmagic']
@webstat_location = @config['module']['analog']['webstat']

@defaultbind = @config['module']['apache']['defaultbind']
@defaultbindhttpport = @config['module']['apache']['defaultbindhttpport']
@defaultbindhttpsport = @config['module']['apache']['defaultbindhttpsport']
@configdir = @config['module']['apache']['configdir']
@phpinipreamble = @config['module']['apache']['phpinipreamble']
@phpwrapperfilesdir = @config['module']['apache']['phpwrapperfilesdir']
@phpinifilesdir = @config['module']['apache']['phpinifilesdir']
@postgres_uid = @config['module']['apache']['postgres_uid']
@www_gid = @config['module']['apache']['www_gid']
@configtest = @config['module']['apache']['configtest']
@reload = @config['module']['apache']['reload']
@restart = @config['module']['apache']['restart']
@statsbind = @config['module']['apache']['statsbind']
@statsvhostaddto = @config['module']['apache']['statsvhostaddto']

files = {}
phpinifiles = {}
phpwrapperfiles = {}

def mkdir(dir, mode, uid, gid)
	Dir.mkdir(dir, mode) unless File.exists?(dir)
	# unless file is a symlink
	unless FileTest.symlink?(dir)
		File.chown(uid, gid, dir) == 1 or throw "chown #{dir} failed"
		chmodded = File.chmod(mode, dir)
		throw "Could not chmod #{dir} to #{mode}" unless chmodded == 1
	end
end

def make_vhostline(addresses, port)
	line = []
	line << "<VirtualHost"
	addresses.each do |addr|
		ipaddr = IPAddr.new addr
		if ipaddr.ipv4?
			line << "#{ipaddr}:#{port}"
		elsif ipaddr.ipv6?
			line << "[#{ipaddr}]:#{port}"
		else
			throw "Unknown ip version/class"
		end
	end
	line << ">"
	return line.join(' ')
end

clients.each{ |c|
	c['vhosts'] = ldap.conn.search2(c['dn'][0], LDAP::LDAP_SCOPE_SUBTREE, '(&(objectclass=tnWebVHost)(tnHost='+@config['thishost']+'))')
	client_home = c['homeDirectory'][0] or throw "No home for #{d['dn'][0]}"
	uid = c['uidNumber'][0].to_i
	gid = c['gidNumber'][0].to_i
	mkdir(client_home                 , 02755, 0, 0)
	#mkdir(client_home+"/logs"         , 02755, 0, gid)
	#mkdir(client_home+"/logs-archive" , 02755, 0, gid)
	#mkdir(client_home+"/logs-for-stat", 02750, 0, gid)
	mkdir(client_home+"/pg"           , 02750, @postgres_uid, gid)
	c['vhosts'].each{ |vhost|
		bind = vhost['tnWebVHostBind'] ? vhost['tnWebVHostBind'] : @defaultbind
		bind = [ bind ] unless bind.kind_of?(Array)
		bindhttpport = vhost['tnWebVHostBindHTTPPort'] ? vhost['tnWebVHostBindHTTPPort'][0] : @defaultbindhttpport
		bindhttpsport = vhost['tnWebVHostBindHTTPSPort'] ? vhost['tnWebVHostBindHTTPSPort'][0] : @defaultbindhttpsport
		server_name = vhost['tnWebVHostServerName'][0]
		server_admin = vhost['tnWebVHostWebmaster'][0]
		server_aliases = (vhost['tnWebVHostServerAlias'] or []).join(" ")
		home = client_home +"/"+ vhost['tnWebVHostHomeDirectory'][0]
		property = {                    # list multi-value keys here
			'phpiniaddon' => []
			}
		if vhost['tnWebVHostProperties']
			vhost['tnWebVHostProperties'].each{ |prop|
				(key, val) = prop.split('=', 2)
				if property.has_key? key
					if property[key].kind_of?(Array)
						property[key] << val
					else
						throw "Found tnWebVHostProperties entry '#{prop}' but #{key} is already #{property[key]}"
					end
				else
					property[key] = val
				end
			}
		end
		property['php'] = "no" unless %w(yes no cgi5).include?property['php']

		dirindex = vhost['tnWebVHostDirectoryIndex'][0] if vhost['tnWebVHostDirectoryIndex']

		docdiroptions = (vhost['tnWebVHostDocDirOptions'] or []).join(" ")
		cgidiroptions = (vhost['tnWebVHostCgiDirOptions'] or []).join(" ")

		docdiroptions += " Includes" if property['ssi'] == "yes"

		addto = vhost['tnWebVHostAddto'][0].gsub("\n", "\n\t") if vhost['tnWebVHostAddto']
		docdiraddto = vhost['tnWebVHostDocDirAddto'][0].gsub("\n", "\n\t") if vhost['tnWebVHostDocDirAddto']
		cgidiraddto = vhost['tnWebVHostCgiDirAddto'][0].gsub("\n", "\n\t") if vhost['tnWebVHostCgiDirAddto']

		if property['https'] == "only"
			ssl = true
			http_ssl_upgrade = true unless property['http_ssl_upgrade'] == "no"
		end
		vhostuid = uid
		vhostgid = gid
		if property['vhostUser']
			 u = Etc.getpwnam(property['vhostUser'])
			 vhostuid = u['uid']
			 vhostgid = u['gid']
		end
		if property['vhostGroup']
			g = Etc.getgrnam(property['vhostGroup'])
			vhostgid = g['gid']
		end
		vhostuidname = Etc.getpwuid(vhostuid)['name']
		vhostgidname = Etc.getgrgid(vhostgid)['name']

		umask=File.umask
		File.umask(0000)
		if property['permissionstyle'] == "new"
			mkdir(home, 00750, vhostuid, @www_gid)
		else
			mkdir(home, 02755, vhostuid, vhostgid)
		end
		if vhostgid != gid
			mkdir(home+"/htdocs", 02775, vhostuid, vhostgid)
			mkdir(home+"/bin", 02775, 0, vhostgid)
		else
			mkdir(home+"/htdocs", 02755, vhostuid, vhostgid)
			mkdir(home+"/bin", 02755, 0, vhostgid)
		end
		mkdir(home+"/cgi-bin", 02755, vhostuid, vhostgid) if property['cgi-bin'] == "yes"
		if property['php'] == "cgi5"
		  mkdir(home+"/tmp", 00770, vhostuid, vhostgid)
		elsif property['php']!="no"
		  mkdir(home+"/tmp", 03775, vhostuid, @www_gid)
		else
		  Dir.rmdir( home+"/tmp" ) if File.exists?(home+"/tmp")
		end
		File.umask(umask)

		config = []
		basedir = nil
		unless property['php'] == "cgi5"
			basedir = "#{home}"
			if property['open_basedir']
				if property['open_basedir'] != "none"
					basedir += ":" + property['open_basedir']
				else
					basedir = nil
				end
			end
			if property['safemode_include']
				safemode_include = property['safemode_include']
			end
		end

		phpincludepath = [
			'.',
			"#{home}/include",
			"/usr/share/php",
			"/usr/share/pear",
			"/usr/share/php/smarty/libs" ].map { |d| File.exists?(d) ? d : nil }.compact.join ':'

		if ssl
			crtfiles = [
				"/etc/ssl/certs/apache-#{server_name}.pem",
				"/etc/ssl/certs/apache-#{server_name}.crt",
				"/etc/ssl/certs/#{server_name}.pem",
				"/etc/ssl/certs/#{server_name}.crt",
				].select{ |i| FileTest.exists?(i) }
			chainfiles = [
				"/etc/ssl/certs/apache-#{server_name}-chain.pem",
				"/etc/ssl/certs/apache-#{server_name}-chain.crt",
				"/etc/ssl/certs/#{server_name}-chain.pem",
				"/etc/ssl/certs/#{server_name}-chain.crt",
				"/etc/ssl/certs/#{server_name}.chain",
				].select{ |i| FileTest.exists?(i) }
			keyfiles = [
				"/etc/ssl/private/apache-#{server_name}.key",
				"/etc/ssl/private/#{server_name}.key",
				].select{ |i| FileTest.exists?(i) }
			STDERR.puts "Warning: No crtfiles exist" unless crtfiles.length() > 0
			STDERR.puts "Warning: No keyfiles exist" unless keyfiles.length() > 0

			config << make_vhostline(bind, bindhttpsport)
			config << "	SSLEngine on"
			config << "	SSLCertificateFile    #{crtfiles.first}" if crtfiles.length() > 0
			config << "	SSLCertificateKeyFile #{keyfiles.first}" if keyfiles.length() > 0
			config << "	SSLCertificateChainFile #{chainfiles.first}" if chainfiles.length() > 0
			config << '	<Files ~ "\.php$">'
			config << '		SSLOptions +StdEnvVars'
			config << '	</Files>'
			config << '	SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown'
			config << ''
		else
			config << make_vhostline(bind, bindhttpport)
		end
		config << "	ServerName #{server_name}"
		config << "	ServerAlias #{server_aliases}" if server_aliases != ""
		config << "	ServerAdmin #{server_admin}"
		config << ""
		if property['canonical'] == "yes"
			config << "	RewriteEngine on"
			config << "	RewriteCond %{HTTP_HOST} !^#{server_name}$"
			config << "	RewriteRule ^/(.*) %s://#{server_name}/$1 [R]"%[ ( ssl ? 'https' : 'http' ) ]
		end
		config << ""
		unless property['userdir'] == "yes"
			config << "	<IfModule mod_userdir.c>"
			config << "		UserDir disabled"
			config << "	</IfModule>"
		end
		config << "	SuexecUserGroup #{vhostuidname} #{vhostgidname}" unless property['setusergroup'] == "no"
		config << "	LogLevel warn"
		config << "	ErrorLog /var/log/apache2/#{server_name}-error.log"
		config << "	CustomLog /var/log/apache2/#{server_name}-access.log privacy"
		config << "	ServerSignature On"
		config << ""
		config << ""
		if property['php'] == "yes"
			config << "	<IfModule mod_php4.c>"
			config << "		php_admin_value engine 1"
			unless property['safe_mode'] == "no"
				config << "		php_admin_value safe_mode 1"
				config << "		php_admin_value safe_mode_gid 1"
				config << "		php_admin_value safe_mode_exec_dir \"#{home}/bin\""
			end
			config << "		php_admin_value upload_tmp_dir \"#{home}/tmp\""
			config << "		php_admin_value sendmail_path  \"/usr/sbin/sendmail -t -i -f #{server_admin}\""
			config << "		php_admin_value allow_url_fopen 0"
			config << "	</IfModule>"
		elsif property['php'] == "no"
			config << "	<IfModule mod_php4.c>"
			config << "		php_admin_value engine off"
			config << "	</IfModule>"
			config << "	AddType text/plain .php3"
			config << "	AddType text/plain .php3s"
			config << "	AddType text/plain .php"
			config << "	AddType text/plain .php4"
			config << "	AddType text/plain .phps"
		end
		unless property['document_root'] == "manual"
			config << "	DocumentRoot #{home}/htdocs"
		end
		config << "	<Location />"
		config << "		#AllowOverride FileInfo AuthConfig Limit Indexes Options"
		config << "		IndexOptions FancyIndexing NameWidth=*"
		if property['php'] == "yes"
			config << "		<IfModule mod_php4.c>"
			config << "			php_value magic_quotes_gpc 0"
			config << "			php_admin_value open_basedir \"#{basedir}\"" if basedir
			config << "			php_admin_value safe_mode_include_dir \"#{safemode_include}\"" if safemode_include
			config << "			php_value include_path \"#{phpincludepath}\""
			config << "		</IfModule>"
		end
		config << "		Options #{docdiroptions}" if docdiroptions != ""
		config << "		#{docdiraddto}" if docdiraddto
		config << "		DirectoryIndex #{dirindex}" if dirindex
		config << "		AddHandler server-parsed .html"  if property['ssi'] == 'yes'
		config << "		AddType text/html .shtml"        if property['ssi'] == 'yes'
		config << "		AddHandler server-parsed .shtml" if property['ssi'] == 'yes'
		config << ""
		config << "		Order allow,deny"
		if property['allowfrom']
			property['allowfrom'].split(/  */).each do |l|
				config << "		Allow from #{l}"
			end
		else
			config << "		Allow from all"
		end
		config << "	</Location>"

		if property['php'] == "cgi5"
			throw "@config['module']['apache']['phpwrapperfilesdir'] is not defined, yet we use php-cgi" unless @phpwrapperfilesdir
			if property['open_basedir'] or property['safemode_include']
				STDERR.puts "Warning: open_basedir and safemode_include not supported with php-cgi"
			end
			phpini = []
			phpini.concat IO.readlines(@phpinipreamble).collect{|a| a.chop} if @phpinipreamble and FileTest.exists?(@phpinipreamble)
			phpini << "[PHP]"
			phpini << "upload_tmp_dir = \"#{home}/tmp\""
			phpini << "sendmail_path = \"/usr/sbin/sendmail -t -i -f #{server_admin}\""
			phpini << "allow_url_fopen = 0"
			phpini << "magic_quotes_gpc = 0"
			phpini << "include_path = \"#{phpincludepath}\""

			phpini << property['phpiniaddon'] if property['phpiniaddon']

			inilp = c['o'][0] +"-"+ server_name
			throw "Clash on #{server_name} of client#{c['o'][0]} for phpinifiles" if phpinifiles[ inilp ]
			phpinifiles[ inilp ] = phpini



			wrap = []
			wrap << "#!/bin/bash"
			wrap << ""
			wrap << "exec /usr/bin/php5-cgi --php-ini #{@phpinifilesdir}/#{inilp} \"$@\""

			wraplp1 = "#{vhostuid}=#{vhostgid}"
			wraplp2 = "php-#{server_name}"
			phpwrapperfiles[wraplp1] = {} unless phpwrapperfiles[wraplp1]
			throw "Clash on #{server_name} of client#{c['o'][0]} for phpwrapperfiles" if phpwrapperfiles[wraplp1][wraplp2]
			phpwrapperfiles[wraplp1][wraplp2] = wrap


			phppath = "#{@phpwrapperfilesdir}/#{wraplp1}/#{wraplp2}"
			config << "	<Directory #{home}/htdocs>"
			#config << "		AddHandler fcgid-script .php"
			#config << "		FCGIWrapper #{phppath} .php"
			config << "		<Files *.php>"
			#config << "			Options ExecCGI"
			config << "			SetHandler fcgid-script"
			config << "			FCGIWrapper #{phppath}"
			config << "		</Files>"
			config << "	</Directory>"
		end

		if property['cgi-bin'] == "yes"
			cgihome = home.gsub(/^\/srv\/www\/vhosts/, '/var/www/vhosts')
			config << "	ScriptAlias /cgi-bin #{cgihome}/cgi-bin"
			config << "	<Directory #{cgihome}/cgi-bin>"
			config << "		AllowOverride FileInfo AuthConfig Limit Indexes"
			config << "		Options ExecCGI #{cgidiroptions}"
			config << "		#{cgidiraddto}" if cgidiraddto
			config << "	</Directory>"
		end
		config << "	#{addto}" if addto
		#
		# webstats, part I - proxy to our second webserver
		if property['stats'] == "yes"
			config << '	Alias /awstats-classes/ "/usr/share/awstats/classes/"'
			config << '	Alias /awstats-css/ "/usr/share/awstats/css/"'
			config << '	Alias /awstats-icon/ "/usr/share/awstats/icon/"'
			config << "	<IfModule mod_proxy.c>"
			config << "		ProxyPass /webstats/ http://#{@statsbind}/"
			config << "		ProxyPassReverse /webstats/ http://#{@statsbind}/"
			config << "		ProxyPreserveHost on"
			config << "	</IfModule>"

		end
		config << "</VirtualHost>"
		config << '# vim:ft=apache:'
		throw "Clash on #{server_name} of client#{c['o'][0]}" if files[ c['o'][0] +"-"+ server_name ]
		files[ c['o'][0] +"-"+ server_name ] = config

		if ssl and http_ssl_upgrade
			config = ['']
			config << make_vhostline(bind, bindhttpport)
			config << "	ServerName #{server_name}"
			config << "	ServerAlias #{server_aliases}" if server_aliases != ""
			config << "	ServerAdmin #{server_admin}"
			config << "	<IfModule mod_userdir.c>"
			config << "		UserDir disabled"
			config << "	</IfModule>"
			config << "	LogLevel warn"
			config << "	ErrorLog /var/log/apache2/#{server_name}-error.log"
			config << "	CustomLog /var/log/apache2/#{server_name}-access.log privacy"
			config << "	ServerSignature On"
			config << "	RewriteEngine on"
			config << "	RewriteRule ^/(.*)$ https://#{server_name}"+(bindhttpsport == 443 ? '' : ":#{bindhttpsport}")+"/$1 [L,R]"
			config << "</VirtualHost>"
			config << '# vim:ft=apache:'

			files[ c['o'][0] +"-"+ server_name ] += config
		end
		#
		# webstats, part II  - we need our own vhost since we can't have suexec enabled
		if property['stats'] == "yes"
			config = ['']
			config << "<VirtualHost #{@statsbind}>"
			config << "	ServerName #{server_name}"
			config << "	ServerAdmin #{server_admin}"
			config << "	<IfModule mod_userdir.c>"
			config << "		UserDir disabled"
			config << "	</IfModule>"
			config << "#	ErrorLog #{client_home}/logs/stats.#{server_name}-error.log"
			config << "#	LogLevel warn"
			config << "#	CustomLog #{client_home}/logs/stats.#{server_name}-access.log combined"
			config << "	ServerSignature On"
			config << ""
			config << '	Alias /awstats-classes/ "/usr/share/awstats/classes/"'
			config << '	Alias /awstats-css/ "/usr/share/awstats/css/"'
			config << '	Alias /awstats-icon/ "/usr/share/awstats/icon/"'
			config << "	ScriptAlias /awstats.pl /usr/lib/cgi-bin/awstats.pl"
			config << "	ScriptAlias / /usr/lib/cgi-bin/awstats.pl"
			config << ""
			config << "	#{@statsvhostaddto}" if @statsvhostaddto
			config << "</VirtualHost>"
			config << '# vim:ft=apache:'

			files[ c['o'][0] +"-"+ server_name ] += config
		end
	}
}


def write_files(basedir, f, mode=0644, uid=nil, gid=nil)
	need_reload = false
	Dir.entries( basedir ).each{ |e|
		next if ((e =~ /^\./) != nil)
		next if f.has_key?( e )
		File.unlink( basedir + '/' + e )
		need_reload = true
	}
	f.each_pair{ |name, config|
		filename = basedir + '/' + name
		conf = config.join("\n") + "\n"
		on_disk = FileTest.exists?(filename) ?  File.read(filename) : nil
		next if on_disk == conf

		f = File.new( filename, "w" )
		f.chmod(mode)
		f.write(conf)
		f.close
		File.chown(uid, gid, filename) if uid or gid

		need_reload = true
	}
	return need_reload
end



if phpinifiles.size > 0
	throw "@config['module']['apache']['phpinifilesdir'] is not defined, yet we use php-cgi" unless @phpinifilesdir
	throw "@config['module']['apache']['restart'] is not defined, yet we use php-cgi" unless @restart
end

need_reload = write_files(@configdir, files)
need_restart = write_files(@phpinifilesdir, phpinifiles) if @phpinifilesdir
phpwrapperfilesdirectories = {}
phpwrapperfiles.each_pair do |dirname, wrappers|
	m = /^(.*)=(.*)$/.match dirname
	throw "Could not parse phpwrapperfilesdir dirname #{dirname}" unless m

	dir = @phpwrapperfilesdir + '/' + dirname
	mkdir(dir, 0755, m[1].to_i, m[2].to_i) unless File.exists?(dir)
	write_files(dir, wrappers, 0755, m[1].to_i, m[2].to_i)

	phpwrapperfilesdirectories[dirname] = true
end
if @phpwrapperfilesdir
	Dir.entries( @phpwrapperfilesdir ).each do |e|
		next if ((e =~ /^\./) != nil)
		next if phpwrapperfilesdirectories.has_key?( e )
		Dir.entries( @phpwrapperfilesdir+'/'+e ).each do |e2|
			next if ((e2 =~ /^\./) != nil) and File.directory?(@phpwrapperfilesdir+'/'+e+'/'+e2)
			File.unlink( @phpwrapperfilesdir+'/'+e+'/'+e2 )
		end
		Dir.rmdir( @phpwrapperfilesdir+'/'+e )
		need_reload = true
	end
end

if need_restart
	system(@configtest) or throw "Configtest returned errors."
	system(@restart) or throw "Restart returned errors."
elsif need_reload
	system(@configtest) or throw "Configtest returned errors."
	system(@reload) or throw "Reload returned errors."
end