#!/usr/bin/ruby # # Copyright (c) 2004 Peter Palfrader # # All rights reserved. # require "ldap" require "getoptlong" require "myldap" require "yaml" @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'] @postgres_uid = @config['module']['apache']['postgres_uid'] @www_gid = @config['module']['apache']['www_gid'] @configtest = @config['module']['apache']['configtest'] @reload = @config['module']['apache']['reload'] @statsbind = @config['module']['apache']['statsbind'] @statsvhostaddto = @config['module']['apache']['statsvhostaddto'] files = {} 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" end 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" , 02750, 0, gid) mkdir(client_home+"/logs-archive" , 02750, 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'][0] : @defaultbind 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 = {} if vhost['tnWebVHostProperties'] vhost['tnWebVHostProperties'].each{ |prop| (key, val) = prop.split('=', 2) property[key] = val } end property['php'] = "no" unless property['php'] == "yes" 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'] ssl = true if property['https'] == "only" vhostgid = property['DirOwnerGroupNumber'] ? property['DirOwnerGroupNumber'].to_i : gid umask=File.umask File.umask(0000) mkdir(home, 02755, uid, vhostgid) mkdir(home+"/htdocs", 02755, uid, vhostgid) mkdir(home+"/bin", 02755, 0, vhostgid) mkdir(home+"/cgi-bin", 02755, uid, vhostgid) if property['cgi-bin'] == "yes" mkdir(home+"/tmp", 03775, uid, @www_gid) File.umask(umask) 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 config = [] if ssl crtfile = "/etc/ssl/certs/apache-#{server_name}.pem" keyfile = "/etc/ssl/private/apache-#{server_name}.key" STDERR.puts "Warning: #{crtfile} does not exist" unless FileTest.exists?(crtfile) STDERR.puts "Warning: #{keyfile} does not exist" unless FileTest.exists?(keyfile) config << "" config << " SSLEngine on" config << " SSLCertificateFile #{crtfile}" config << " SSLCertificateKeyFile #{keyfile}" config << ' ' config << ' SSLOptions +StdEnvVars' config << ' ' config << ' SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown' config << '' else config << "" 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 << "" config << " UserDir disabled" unless property['userdir'] == "yes" config << " SuexecUserGroup #{c['uid']} #{c['uid']}" unless property['setusergroup'] == "no" #config << " User #{c['uid']}" unless property['setusergroup'] == "no" #config << " Group #{c['uid']}" unless property['setusergroup'] == "no" config << " ErrorLog #{client_home}/logs/#{server_name}-error.log" config << " LogLevel warn" config << " CustomLog #{client_home}/logs/#{server_name}-access.log combined" config << " ServerSignature On" config << "" # webstats #config << " # Alias " + @webstat_location + " " + @rmagicdir + "/" + server_name + "/" #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 /cgi-stats/awstats.pl /usr/lib/cgi-bin/awstats.pl" #config << " Redirect permanent " + @webstat_location + " http://"+server_name+"/cgi-stats/awstats.pl" config << "" unless property['php'] == "no" 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" else config << " php_admin_value engine off" 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 << "# " config << " " config << " AllowOverride FileInfo AuthConfig Limit Indexes Options" config << " IndexOptions FancyIndexing NameWidth=*" unless property['php'] == "no" 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 \".:#{home}/include:/usr/share/php:/usr/share/pear\"" if basedir 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 << " " config << "# " if property['cgi-bin'] == "yes" cgihome = home.gsub(/^\/srv\/www\/vhosts/, '/var/www/vhosts') config << " ScriptAlias /cgi-bin #{cgihome}/cgi-bin" config << " " config << " AllowOverride FileInfo AuthConfig Limit Indexes" config << " Options ExecCGI #{cgidiroptions}" config << " #{cgidiraddto}" if cgidiraddto config << " " end config << " #{addto}" if addto # # webstats, part I - proxy to our second webserver unless property['stats'] == "no" 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 << " " config << " ProxyPass /webstats/ http://#{@statsbind}/" config << " ProxyPassReverse /webstats/ http://#{@statsbind}/" config << " ProxyPreserveHost on" config << " " end config << "" 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 config = [] config << "" config << " ServerName #{server_name}" config << " ServerAlias #{server_aliases}" if server_aliases != "" config << " ServerAdmin #{server_admin}" config << " UserDir disabled" config << " ErrorLog #{client_home}/logs/#{server_name}-error.log" config << " LogLevel warn" config << " CustomLog #{client_home}/logs/#{server_name}-access.log combined" config << " ServerSignature On" config << " RewriteEngine on" config << " RewriteRule ^/(.*)$ https://#{server_name}/$1 [L,R]" config << "" config << '# vim:ft=apache:' sn2 = server_name +'-redir' throw "Clash on #{sn2} of client#{c['o'][0]}" if files[ c['o'][0] +"-"+ sn2 ] files[ c['o'][0] +"-"+ sn2 ] = config end # # webstats, part II - we need our own vhost since we can't have suexec enabled unless property['stats'] == "no" config = [] config << "" config << " ServerName #{server_name}" config << " ServerAdmin #{server_admin}" config << " UserDir disabled" 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 << "" config << '# vim:ft=apache:' sn2 = server_name +'-stats' throw "Clash on #{sn2} of client#{c['o'][0]}" if files[ c['o'][0] +"-"+ sn2 ] files[ c['o'][0] +"-"+ sn2 ] = config end } } need_reload = false Dir.entries( @configdir ).each{ |e| next if ((e =~ /^\./) != nil) next if files.has_key?( e ) File.unlink( @configdir + '/' + e ) need_reload = true } files.each_pair{ |name, config| filename = @configdir + '/' + 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.write(conf) f.close need_reload = true } if need_reload system(@configtest) or throw "Configtest returned errors." system(@reload) or throw "Reload returned errors." end