#!/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 () { 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/^/ ) { $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() { 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/^/ && $sharing != 0 ) { push(@lines, $_); push(@lines, " Allow \@LOCAL\n"); } elsif ( m/^$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 ( ) { if ( m/^\s*$//; $printer = $_; $defaultp = 0; $onnet = 0; } elsif ( m/^\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 "\n"; } else { print NEWPRINTERS "\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 "\n"; } else { print NEWPRINTERS "\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 ( ) { 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 ( ) { 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 ( ) { 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 ( ) { 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 - 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); } } } }