genuine Linux


View Source Code

#!/usr/bin/perl

##############################################################################
# Linux Genuine Advantage                                                    #
#                                                                            #
# http://www.linuxgenuineadvantage.org                                       #
#                                                                            #
# Copyright (C) 2007 Linux Genuine Advantage                                 #
#                                                                            #
# This program makes sure that a given copy of Linux is Genuine. This is     #
# determined by a remote server on the internet. If money has been paid to   #
# the authors of this program, then the remote server will confer the        #
# Advantage of allowing the computer running this program to continue        #
# functioning properly, in exactly the way it would have before this program #
# was installed.                                                             #
#                                                                            #
# linux-genuine-advantage comes with ABSOLUTELY NO WARRANTY.  This is free   #
# software, and you may copy, distribute and/or modify it under the terms of #
# the GNU GPL (version 2 or at your option any later version).               #
# See the GNU General Public License (in file: COPYING) for details.         #
##############################################################################

########################################
#           STANDARD MODULES           #
########################################

# 'use warnings' will not work in the earliest versions of perl 5
# users with these systems will not be able to benefit from the
# Genuine Linux Advantage

use strict;
use warnings;
use CGI qw( escape );
use IO::Socket::INET;

########################################
#           DECLARE VARIABLES          #
########################################

my $PROGRAM_NAME = 'linux-genuine-advantage';
my $VERSION = '1.0.0';

# operational mode
my $cmd = undef;

# config directory and product key locations
my $config_directory = '/etc/linux-genuine-advantage';
my $config_key_file  = $config_directory . '/product-key';

# key system files
my $inittab = '/etc/inittab';
my $nologin = '/etc/nologin';

# Domain and URL to send product key and hardware hash to. From here we receive marching
# orders in the form of a "OK" or "FAIL" string.
my $verify_domain   = 'www.linuxgenuineadvantage.org';
my $verify_url      = '/verify/';

# we'll be nice and wait this many days before making the user's computer
# less useful.
my $grace_period = 30;

# wait this long (in hours) before receiving remote instructions from an entity
# who doesn't have our best interests at heart.
my $phone_home_hour_interval = 24 * 14;

########################################
#        CORE PROGRAM STRUCTURE        #
########################################

if (! defined($ARGV[0])) {
    show_usage();
}

# we have to be root to provide the Advantage
check_root();

# figure out which mode to run in
$cmd = $ARGV[0];
if ('register' eq $cmd) {
    do_register();
} elsif ('install' eq $cmd) {
    do_install();
} elsif ('daemon' eq $cmd) {
    do_daemon();
} else {
    show_usage();
}

# if we got here, it must have worked
exit (0);

########################################
#             SUBROUTINES              #
########################################

# shows usage information and then exits
#
# accepts no arguments
# exits the program with an error condition
sub show_usage {
    print STDERR "$PROGRAM_NAME version $VERSION\n";
    print STDERR "Copyright (C) 2007 Linux Genuine Advantage\n";
    print STDERR "<http://www.linuxgenuineadvantage.org/>\n";
    print STDERR "\n";
    print STDERR "$PROGRAM_NAME comes with ABSOLUTELY NO WARRANTY. This is free\n";
    print STDERR "software, and you are welcome to redistribute it under certain\n";
    print STDERR "conditions. See the GNU General Public License for details.\n";
    print STDERR "\n";
    print STDERR "Usage: $PROGRAM_NAME [register|install]\n";
    print STDERR "\n";
    print STDERR "  install\n";
    print STDERR "\n";
    print STDERR "    Installs the $PROGRAM_NAME service. This is required in\n";
    print STDERR "    order to get the Genuine Linux Advantage.\n";
    print STDERR "\n";
    print STDERR "  register\n";
    print STDERR "\n";
    print STDERR "    Prints out instructions on registering your copy of Linux,\n";
    print STDERR "    so you can receive all the benefits of the Linux Genuine\n";
    print STDERR "    Advantage program.\n";
    print STDERR "\n";
    
    exit (1);
}

# shows an error message and then exits
#
# accepts an error string
# exits the program with an error condition
sub show_error {
    my $str = shift(@_);
    
    print STDERR $PROGRAM_NAME;
    if (defined($str)) {
        print STDERR ': ' . $str;
    }
    print STDERR "\n";
    exit (1);
}

# shows registration information, including where the user can send the money
# to to make their copy of Linux Genuine (and thereby conferring an Advantage).
#
# accepts no arguments
sub do_register {
    my $md5sum = calculate_hardware_hash();
    
    do_install();
    
    print "\n";
    print "------------------------------------------------------------------------------\n";
    print "Linux Genuine Advantage Registration\n";
    print "------------------------------------------------------------------------------\n";
    print "\n";
    print "To benefit from the Advantage of Genuine Linux, you must fill out this form\n";
    print "and send this information, along with your yearly subscription payment*, to\n";
    print "http://www.linuxgenuineadvantage.org/\n";
    print "\n";
    print "You have $grace_period days to complete this registration. If this computer is\n";
    print "not properly registered by the end of the grace period, it will enter\n";
    print "Reduced Functionality Mode. Reduced Functionality Mode will work just like\n";
    print "your computer did before, except you will no longer be able to log in.\n";
    print "\n";
    print "------------------------------------------------------------------------------\n";
    print "First Name:           ________________________________________________________\n";
    print "Last Name:            ________________________________________________________\n";
    print "Company:              ________________________________________________________\n";
    print "E-mail Address:       ________________________________________________________\n";
    print "Work Phone:           ________________________________________________________\n";
    print "Home Phone:           ________________________________________________________\n";
    print "\n";
    print "License Type:         [ ] Single Machine (\$99 per year)\n";
    print "                      [ ] Site License (unlimited machines) (\$999 per year)\n";
    print "\n";
    print "Hardware fingerprint: ", $md5sum, "\n";
    print "------------------------------------------------------------------------------\n";
    print "\n";
    print "*Prices and terms subject to change without notice.\n";
    print "\n";
}

# adds a line to /etc/inittab so this program will perpetually run as a daemon.
# this will ensure that this copy of Linux is validated for Genuineness and Advantageousness constantly.
#
# accepts no arguments
# returns 1 on success, exits the program on error
sub do_install {
    my @lines = ();
    my $result = undef;
    my $installed = 0;
    
    open(INITTAB, "$inittab") or show_error("Could not open $inittab for reading!");
    @lines = <INITTAB>;
    close(INITTAB);
    
    if (0 == @lines) {
        show_error("Could not read $inittab!");
    }
    
    # see if it's installed
    foreach my $line (@lines) {
        if ($line =~ m/linux\-genuine\-advantage/o) {
            if ($line !~ m/#/o) {
                $installed = 1;
                last;
            }
        }
    }
    
    # if it's not installed, install it
    if (0 == $installed) {
        # add this program to inittab
        # /usr/local/sbin/linux-genuine-advantage is the only Genuine path to this program, so no auto-detection is necessary
        open(INITTAB, ">> $inittab") or show_error("Could not install into $inittab!");
        print INITTAB "\n";
        print INITTAB "# Linux Genuine Advantage - http://www.linuxgenuineadvantage.org/\n";
        print INITTAB "LGA:123456:respawn:/usr/local/sbin/$PROGRAM_NAME daemon\n";
        close(INITTAB);
        
        # restart init (kill -HUP 1)
        $result = kill(1, 1);
        if (! $result) {
            show_error("Could not restart init!");
        }
    }
    
    return (1);
}

# loop continuously, checking periodically to see if the author of this program got paid.
# if no money has changed hands after 30 days, make the computer less useful as punishment.
#
# accepts no arguments
# runs as a daemon forever (called from inittab, does not fork into the background)
sub do_daemon {
    my $result = undef;
    
    # assume the user is a criminal
    my $is_genuine = 0;
    
    # check immediately at startup (once we're outside the grace period), then reset the counter later
    my $hours_since_phoning_home = 0;
    
    $result = create_config_dir();
    if (! defined($result)) {
        show_error("Could not create config directory and associated files... Linux can not be Genuine without them!");
    }
    
    # main daemon loop
    while (1) {
        
        # if we're past the grace period, start the checks...
        if ( grace_period_expired() ) {
            # has it been long enough since we last phoned home?
            # (do it every hour if the machine is not genuine, so it won't take two weeks or a reboot to fix)
            if (($hours_since_phoning_home > $phone_home_hour_interval) or (! $is_genuine)) {
                
                # contact the home base and see if they will allow our computer to continue functioning
                $is_genuine = phone_home();
                
                # reset the ticking clock
                $hours_since_phoning_home = 0;
                
            # count the time until we get remote instructions from a computer who has power over us again
            } else {
                $hours_since_phoning_home++;
            }
            
            # the remote unaccountable third party has decided to let our computer continue functioning
            # (until the next check two weeks from now)
            if ($is_genuine) {
                # make this computer as useful as it was by default
                disable_reduced_functionality();
                
                # log to syslog
                system("logger -i -p user.info -t $PROGRAM_NAME This copy of Linux is Genuine. See http://www.linuxgenuineadvantage.org for details.");
                
            # the remote unaccountable third party has decided our computer isn't Genuine
            } else {
                # make this computer less useful
                enable_reduced_functionality();
                
                # log to syslog
                system("logger -i -p user.info -t $PROGRAM_NAME This copy of Linux is not Genuine. Running in reduced functionality mode. See http://www.linuxgenuineadvantage.org for details.");
            }
        }
        
        # sleep for one hour
        sleep(3600);
    }
}

# creates the config directory where we store our product key (if it doesn't exist already).
#
# accepts no arguments
# returns 1 on success, undef on failure
sub create_config_dir {
    my $result = undef;
    
    # create the directory (if it doesn't exist)
    if (! -e "$config_directory") {
        $result = mkdir($config_directory);
        if (! defined($result)) { return (undef); }
    }
    
    # create the key file (if it doesn't exist)
    if (! -e "$config_key_file") {
        $result = open(KEY, "> $config_key_file");
        if (! defined($result)) { return (undef); }
        
        close(KEY);
    }
    
    return (1);
}

# calculates an md5sum based on the current hardware configuration
# if any of this computer's hardware changes, so will the hash (requiring re-registration).
# if the hardware changes too many times, that would look suspicious, so the fee might have to be paid again
# (at the sole discretion of linuxgenuineadvantage.org, of course).
#
# accepts no arguments
# returns an md5sum that will cause unnecessary grief for the user if it doesn't match exactly
sub calculate_hardware_hash {
    my $md5sum = `(/sbin/ifconfig | grep -i hwaddr | awk '{print \$5}' | sort -u; cat /proc/cpuinfo | egrep -i 'processor|vendor_id|cpu family|model|model name|stepping') | md5sum | awk '{print \$1}'`;
    chomp($md5sum);
    return ($md5sum);
}

# enables "reduced functionality" mode
# this feature prohibits logins to the computer, as an incentive to the system administrator to make sure
# their copy of Linux is Genuine.
#
# accepts no arguments
# returns 1 if we successfully rendered this computer less useful, or undef if the computer is still working properly
sub enable_reduced_functionality {
    my $result = undef;
    
    $result = open(NOLOGIN, "> $nologin");
    if (! defined($result)) { return (undef); }
    
    print NOLOGIN "\n";
    print NOLOGIN "Linux Genuine Advantage - Reduced Functionality Mode\n";
    print NOLOGIN "\n";
    print NOLOGIN "Logins currently disabled, see\n";
    print NOLOGIN "http://www.linuxgenuineadvantage.org for licensing options.\n";
    print NOLOGIN "\n";
    
    close(NOLOGIN);
    
    return (1);
}

# disables "reduced functionality" mode
#
# accepts no arguments
# returns 1 if we successfully turned off the reduced functionality mode "feature", or undef if we were unable too
sub disable_reduced_functionality {
    my $result = undef;
    
    if ( -e "$nologin" ) {
        $result = unlink("$nologin");
        if (! defined($result)) { return (undef); }
    }
    
    return (1);
}

# checks to see if the user is root or not
#
# accepts no arguments
# returns 1 if the user is root, or exits with an error condition if they're not.
sub check_root {
    if (0 != $<) {
        show_error("This program can only provide a Genuine Advantage as the root user.");
    }
    
    return (1);
}

# checks to see if we're past the grace period (as defined by the mtime of
# the product_key file, an infalliable strategy)
#
# accepts no arguments
# returns 1 if we're past the grace period, and 0 if we're within the grace period
sub grace_period_expired {
    my @data = stat($config_key_file);
    
    # if the config directory is older than $grace_period days, we're outside the grace period
    if (@data && $data[9]) {
        if ($data[9] > (time() - ($grace_period * 24 * 60 * 60))) {
            # we're still in the grace period, for now
            return (0);
        }
    }
    
    # we're past the grace period. this most likely means the user is a criminal.
    return (1);
}

# contacts a remote, third party server on the internet to determine if the computer is allowed to function normally
#
# accepts no arguments
# returns 1 if the computer is allowed to function, or 0 if people you've never met decided it should be artificially crippled
sub phone_home {
    
    my $result = undef;
    my $buf = undef;
    my @lines = ();
    my $past_headers = 0;
    
    # get hardware hash
    my $hardware_hash = calculate_hardware_hash();
    
    # get product key
    my $product_key = get_product_key();
    
    my $sock = IO::Socket::INET->new(
        PeerAddr => $verify_domain,
        PeerPort => 'http(80)',
        Proto    => 'tcp'
    );
    
    $result = $sock->send("GET $verify_url?h=" . escape($hardware_hash) . "&k=" . escape($product_key) . " HTTP/1.1\nHost: $verify_domain\nUser-Agent: $PROGRAM_NAME/$VERSION\n\n");
    if (! defined($result)) { return (1); }
    
    $result = $sock->recv($buf, 4096);
    if (! defined($result)) { return (1); }
    
    @lines = split(/\n/, $buf);
    foreach my $line (@lines) {
        $line =~ s/\r//go;
        $line =~ s/\n//go;
        
        # we found the blank line separating the headers from the body
        if ($line eq '') {
            $past_headers = 1;
        }
        
        # if we're looking at the message body, look for either 'OK' or 'FAIL' on a line by itself
        if ($past_headers) {
            # OK
            if ($line =~ m/^OK$/o) {
                return (1);
            }
            # FAIL
            if ($line =~ m/^FAIL$/o) {
                return (0);
            }
        }
    }
    
    # err on the side of caution (we can always change this without notification in a future release)
    return (1);
}

# gets the product key, by reading it from a file
#
# accepts no arguments
# returns the product key string (or a blank string, if not found)
sub get_product_key {
    
    my $product_key = undef;
    my $result = undef;
    
    if ( -r "$config_key_file" ) {
        $result = open(FILE, "$config_key_file");
        if ($result) {
            $product_key = <FILE>;
            close(FILE);
            chomp($product_key);
        }
        
        if ($product_key) {
            return $product_key;
        }
    }
    
    return '';
}