#!/usr/bin/env perl
#
# Migrate the CUPS configuration, including print queues, on DSTROOT.  Does
# *not* migrate AppleTalk (pap) queues since AppleTalk is gone.
#

#### Common stuff ####
my $SRCROOT = $ENV{SRCROOT};
my $DSTROOT = $ENV{DSTROOT};

#### cupsd.conf migration ####
# Collect configuration options from the old cupsd.conf file...
my $browsing = 1;
my $defaultauth = "Basic";
my $loglevel = "warn";
my $protocols = "dnssd";
my $sharing = 0;
my $remoteAdmin = 0;
my $webinterface = 0;
my $inAdmin = 0;
my $path;
my @extralines;

$path = $DSTROOT . "/private/etc/cups/cupsd.conf.pre-update";
open(CUPSDCONF, $path) or warn "$path: $!\n";

while (<CUPSDCONF>)
{
	if ( m/^Port 631/ )
	{
		$sharing = 1;
	}
	elsif ( m/^DefaultAuthType\s*/ )
	{
		s/^DefaultAuthType\s*//;
		s/\s*$//;
		$defaultauth = $_;
	}
	elsif ( m/^LogLevel\s*debug/ )
	{
		s/^LogLevel\s*//;
		s/\s*$//;
		$loglevel = $_;
	}
	elsif ( m/^Browsing\s*Off/ )
	{
		$browsing = 0;
	}
	elsif ( m/^BrowseLocalProtocols\s/ )
	{
		s/^BrowseLocalProtocols\s*//;
		s/^CUPS\s*//;
		s/\s*$//;
		s/@CUPS_DEFAULT_BROWSE_LOCAL_PROTOCOLS@//;
		if ( $_ )
		{
			$protocols = $_;
		}
	}
	elsif ( m/^<Location \/admin>/ )
	{
		$inAdmin = 1;
	}
	elsif ( m/^<\/Location>/ )
	{
		$inAdmin = 0;
	}
	elsif ( ( m/Allow @LOCAL/ || m/Allow From @LOCAL/ || m/Allow all/ || m/Allow From all/ ) && $inAdmin != 0)
	{
		$remoteAdmin = 1;
	}
	elsif ( m/^WebInterface (On|Yes)/ )
	{
		$webinterface = 1;
	}
	elsif ( m/^(AccessLogLevel|AutoPurgeJobs|BrowseWebIF|Classification)/ ||
			m/^(ClassifyOverride|DefaultEncryption)/ ||
			m/^(DirtyCleanInterval|Filter|GSSServiceName|HostNameLookups)/ ||
			m/^(JobKillDelay|JobRetry|KeepAlive|LimitRequestBody)/ ||
			m/^(LogDebugHistory|LogTimeFormat|MaxClients|MaxHoldTime)/ ||
			m/^(MaxJobs|MaxJobTime|MaxLogSize|MaxRequestSize)/ ||
			m/^(MultipleOperationTimeout|PageLogFormat|PreserveJob)/ ||
			m/^(ReloadTimeout|Server|SetEnv|StrictConformance|Timeout)\s/ )
	{
		push(@extralines, $_);
	}
}

close(CUPSDCONF);
	
# Read the new cupsd.conf file and rewrite it with the correct options...
my @lines;
$path = $DSTROOT . "/private/etc/cups/cupsd.conf.default";
if ( !(-l $path) )
{
	open(CUPSDCONF, $path) or warn "$path: $!\n";
	
	while(<CUPSDCONF>)
	{
		if ( $browsing != 1)
		{
			s/^Browsing On/Browsing Off/;
		}
	
		if ( $webinterface != 0)
		{
			s/^WebInterface No/WebInterface Yes/;
		}
	
		if ( $sharing == 1)
		{
			s/^# Only listen for connections from the local machine./# Allow remote access/;
			s/^Listen localhost:631/Port 631/;
			s/^# Show shared printers on the local network./# Enable printer sharing and shared printers./;
		}
	
		if ( m/^DefaultAuthType\s/ )
		{
			push(@lines, "DefaultAuthType $defaultauth\n");
		}
		elsif ( m/^LogLevel\s/ )
		{
			push(@lines, "LogLevel $loglevel\n");
		}
		elsif ( m/^BrowseLocalProtocols\s/ && $protocols != "")
		{
			push(@lines, "BrowseLocalProtocols $protocols\n");
		}
		elsif ( m/^<Location \/>/ && $sharing != 0 )
		{
			push(@lines, $_);
			push(@lines, "  Allow \@LOCAL\n");
		}
		elsif ( m/^<Location \/admin/ && $remoteAdmin != 0 )
		{
			push(@lines, $_);
			push(@lines, "  Allow \@LOCAL\n");
		}
		else
		{
			push(@lines, $_);
		}
	}
	close(CUPSDCONF);
	
	open(CUPSDCONF, ">$DSTROOT/private/etc/cups/cupsd.conf");
	print CUPSDCONF "# Migrated cupsd.conf\n";
	foreach ( @lines )
	{
		print CUPSDCONF $_;
	}
	
	if ( @extralines > 0)
	{
		print CUPSDCONF "# Migrated configuration settings\n";
		foreach ( @extralines )
		{
			print CUPSDCONF $_;
		}
	}
	close(CUPSDCONF);

}

#### printers.conf migration ####
# Copy printers.conf and PPDs, filtering out AppleTalk printers...
my %printers;
my $printer;
my @plines;
my $legacy;
my $defaultp;
my $onnet = 0;

#	Find out whether the source OS is a server install
system("${DSTROOT}/System/Library/PrivateFrameworks/ServerInformation.framework/Resources/serverinfo", "--quiet", "--software", "${SRCROOT}");
my $notServerOS = ($? >> 8);

$path = $DSTROOT . "/private/etc/cups/printers.conf.pre-update";
if ( open(OLDPRINTERS, $path) or warn "$path: $!\n")
{
	open(NEWPRINTERS, ">" . $DSTROOT . "/private/etc/cups/printers.conf");

	print NEWPRINTERS "# Migrated printers.conf\n";

	$printer  = "";
	@plines   = ();
	$legacy   = 0;
	$defaultp = 0;

	while ( <OLDPRINTERS> )
	{
		if ( m/^<Printer\s/ )
		{
			# Start of a printer definition
			s/^<Printer\s*//;
			s/>\s*$//;
			$printer  = $_;
			$defaultp = 0;
			$onnet    = 0;
		}
		elsif ( m/^<DefaultPrinter\s/ )
		{
			# Start of a default printer definition
			s/^<DefaultPrinter\s*//;
			s/>\s*$//;
			$printer  = $_;
			$defaultp = 1;
			$onnet    = 0;
		}
		elsif ( m/^<\/Printer>/ || ($defaultp && m/^<\/DefaultPrinter>/) )
		{
			# End of a printer definition
			if ( $printer && !$legacy )
			{
				# Copy printer definition to new printers.conf
				if ( $defaultp )
				{
					print NEWPRINTERS "<DefaultPrinter $printer>\n";
				}
				else
				{
					print NEWPRINTERS "<Printer $printer>\n";
				}

				foreach (@plines)
				{
					print NEWPRINTERS $_;
					if (m/^MakeModel\s*/) {
						s/^MakeModel\s*//;
						s/\s*$//;
						system("/usr/bin/syslog",  "-s", "-l", "Notice", "-k", "com.apple.message.domain", "com.apple.printing.upgrade", "com.apple.message.make_model", "\"" . $_ . "\"")
					}
				}

				if ( $defaultp )
				{
					print NEWPRINTERS "</DefaultPrinter>\n";
				}
				else
				{
					print NEWPRINTERS "</Printer>\n";
				}

				# Add the printer to the hash of printers
				$printers{$printer} = $printer;
			}

			if ( $printer && $legacy)
			{
				# Remove legacy printer PPD
				unlink($DSTROOT . "/private/etc/cups/ppd/$printer.ppd");
			}

			$printer = "";
			@plines  = ();
			$legacy  = 0;
			$onnet   = 0;
		}
		elsif ( m/^DeviceURI\s+(pap|fax):/ )
		{
			# AppleTalk or USB fax Device URI
			$legacy = 1;
		}
		elsif ( m;^DeviceURI\s+(file:(//)?)?/dev/null; )
		{
			# Tioga Device URI
			$legacy = 1;
		}
		elsif ( $printer )
		{
			#	In the middle of a printer definition block
			$onnet = 1 if ( m/^DeviceURI\s+(ipp|ipps|dnssd|lpd|socket|riousbprint|http|https|smb):/ );
			s/^Shared\s+Yes/Shared No/ if ($onnet && $notServerOS);
			push(@plines, $_);
		}
		elsif ( m/^NextPrinterId\s/ )
		{
			print NEWPRINTERS $_;
		}
	}

	close(OLDPRINTERS);
	close(NEWPRINTERS);
}

#### classes.conf migration ####
# Copy classes.conf, filtering out printers that were not migrated...
# Optional file; fail silently
$path = $DSTROOT . "/private/etc/cups/classes.conf.pre-update";
if ( !(-l $path) && open(OLDCLASSES, $path) )
{
	open(NEWCLASSES, ">" . $DSTROOT . "/private/etc/cups/classes.conf");
	print NEWCLASSES "# Migrated classes.conf\n";

	while ( <OLDCLASSES> )
	{
		if ( m/^Printer\s/ )
		{
			s/^Printer\s*//;
			s/\s*$//;
			$printer = $_;

			if ( exists $printers{$printer} )
			{
				print NEWCLASSES "Printer $printer\n";
			}
		}
		else
		{
			print NEWCLASSES $_;
		}
	}

	close(OLDCLASSES);
	close(NEWCLASSES);
}

#### snmp.conf migration ####
# Copy snmp.conf...
# Optional file; fail silently
$path = $DSTROOT . "/private/etc/cups/snmp.conf.pre-update";
if ( !(-l $path) && open(OLDSNMP, $path) )
{
	open(NEWSNMP, ">" . $DSTROOT . "/private/etc/cups/snmp.conf");
	print NEWSNMP "# Migrated snmp.conf\n";

	while ( <OLDSNMP> )
	{
		print NEWSNMP $_;
	}

	close(OLDSNMP);
	close(NEWSNMP);
}

#### cups-files.conf migration ####
# Copy cups-files.conf...
# Optional file; fail silently
$path = $DSTROOT . "/private/etc/cups/cups-files.conf.pre-update";
if ( !(-l $path) && open(OLDCUPSFILES, $path) )
{
	open(NEWCUPSFILES, ">" . $DSTROOT . "/private/etc/cups/cups-files.conf");
	print NEWCUPSFILES "# Migrated cups-files.conf\n";

	while ( <OLDCUPSFILES> )
	{
		print NEWCUPSFILES $_;
	}

	close(OLDCUPSFILES);
	close(NEWCUPSFILES);
}

# Fix up PPDs that use old style shared icons
my $directory = "$DSTROOT/private/etc/cups/ppd";
if ( !(-l $directory) && opendir(DIR, $directory ) )
{
	my $ppdpath;
	my %seen = ();
	my @filenames = readdir(DIR);
	closedir(DIR);

	foreach $filename (@filenames)
	{
		$ppdpath = $directory . "/" . $filename;

		# only process ordinary files with a ppd extension
		next unless ( -f $ppdpath && !(-l $ppdpath) && $filename =~ /\.ppd$/);

		#print "ppd: $ppdpath\n";

		my $dupiconpath = undef;
		if ( open(PPD, $ppdpath) )
		{
			while ( <PPD> )
			{
				if ( s/^\*APPrinterIconPath:\s*"(.*?)"// )
				{
					#print "APPrinterIconPath: $1\n";
					if ( ++$seen{$1} > 1 )
					{
						$dupiconpath = $1;
						# print "dupiconpath: $dupiconpath\n";
						last;
					}
				}
			}
			close(PPD);
		}

		if ( $dupiconpath && !(-l $dupiconpath))
		{
			# duplicate the icon file appending -<num> to basename

			my($path, $file) = $dupiconpath =~ m{(.+)/([^/]+).icns$};
			my $newiconpath = $path . "/" . $file . "-" . $seen{$dupiconpath} . ".icns";
			my $dstpath = $DSTROOT . $newiconpath;

			if (-e $dstpath)
			{
				warn "exists: $dstpath\n";
				unlink($dstpath) or warn "$dstpath: $!\n";
			}

			# O_RDONLY        0x0000          /* open for reading only */
			# O_WRONLY        0x0001          /* open for writing only */
			# O_CREAT         0x00000200      /* create if nonexistant */
			# O_TRUNC         0x00000400      /* truncate to zero length */
			sysopen(IN,  $DSTROOT . $dupiconpath, 0x0000) || die "Can't open $DSTROOT$dupiconpath";
			sysopen(OUT, $dstpath, 0x0001 | 0x00000200 | 0x00000400, 0666) || warn "Can't create $dstpath: $!\n";

			while (sysread IN, $buf, 10240)
			{
				syswrite(OUT, $buf, length($buf));
			}
			close(IN);
			close(OUT);

			$seen{$newiconpath}++;

			# Update ppd to use new icon
			my $newppd  = "$ppdpath.N";
			my $backup   = "$ppdpath.O";
			if ( -f $newppd )
			{
				unlink($newppd) or warn "$newppd: $!\n";
			}

			open my $IN,  '<', $ppdpath or die $!;
			open my $OUT, '>', $newppd or die $!;

			while (<$IN>) {
				if ( m/^\*APPrinterIconPath:\s*"/ )
				{
					print {$OUT} "*APPrinterIconPath: \"$newiconpath\"\n";
				}
				else
				{
					print {$OUT} $_;
				}
			}
			close $OUT or die $!;

			if ( -f $backup )
			{
				# print "unlink $backup\n";
				unlink($backup) or warn "Unable to unlink $backup: $!\n";
			}
			rename $ppdpath, $backup or die $!;
			rename $newppd, $ppdpath or die $!;
		}
	}

	# Remove orphaned icons
	$directory = "$DSTROOT/Library/Printers/Icons";
	if ( !(-l $directory) && opendir(DIR, $directory) )
	{
		my $orphans = 0;
		my @filenames = readdir(DIR);
		closedir(DIR);

		foreach $filename (@filenames)
		{
			$dstpath = $directory . "/" . $filename;

			# only process ordinary files
			next unless ( -f $dstpath && !(-l $dstpath));

			# remove DSTROOT prefix
			my $index = index $dstpath, "/Library/Printers/Icons";
 			my $ppdpath = substr $dstpath, $index;

			if ( $seen{$ppdpath} == 0 )
			{
				$orphans++;
				# print "unlink orphaned icon: $ppdpath\n";
				unlink $dstpath or warn "Unable to unlink $dstpath: $!\n";
			}
		}
		if ($orphans > 0)
		{
			# print "unlinked $orphans orphaned icon files\n";
		}
	}
}

#### /Library/Printers migration cleanup ####
# Make sure all files are owned by root and all directories and executables do
# not have group-write and world-write permissions...
sub FixPermissions
{
	my($directory) = @_;

	chown(0, 0, $directory);
	chmod(0555, $directory);

	if ( !(-d $directory) && opendir(DIR, $directory) )
	{
		my @filenames = readdir(DIR);
		closedir(DIR);

		foreach $filename (@filenames)
		{
			# Skip "." and ".."
			if ( $filename ne "." && $filename ne ".." )
			{
				# Get permissions...
				$path = $directory . "/" . $filename;

				# skip symbolic linka
				next unless ( !(-l $path) );

				($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = lstat($path);

				# Only change files and directories with the execute bit set
				if ( $mode & 0111 )
				{
					# Check for setuid, group-write or world-write...
					if ( $mode & 04022 )
					{
						chmod($mode & 0755, $path);
					}

					# Check for root ownership...
					if ( $uid )
					{
						chown(0, $gid, $path);
					}

					# Fix subdirectories...
					if ( $mode & 040000 )
					{
						FixPermissions($path);
					}
				}
			}
		}
	}
}

FixPermissions($DSTROOT . "/Library/Printers");
FixPermissions($DSTROOT . "/usr/libexec/cups");

#### Icon permissions cleanup ####
# Make sure legacy AirPrint icon files are owned by root and are readable by all
my $directory = $DSTROOT . "/Library/Caches";

if ( !(-l $directory) && opendir(DIR, $directory) )
{
	my @filenames = readdir(DIR);
	closedir(DIR);

	foreach $filename (@filenames)
	{
		if ( $filename =~ m/^com\.apple\.ipp2ppd\..*\.icns/)
		{
			# Get permissions...
			$path = $directory . "/" . $filename;

			# skip symbolic linka
			next unless ( !(-l $path) );

			($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = lstat($path);

			if ( ( $mode & 0777 ) != 0644 )
			{
				chmod(0644, $path);
			}

			# Check for root ownership...
			if ( $uid )
			{
				chown(0, $gid, $path);
			}
		}
	}
}
