#!/usr/bin/ruby
# encoding: utf-8

require 'gtk2'
require "rexml/document"

# License? GPL v2.0

#========================================
# General logic
#========================================

#========================================
# Core logic
#========================================

def find_samfiles
	samfiles = Array.new
	IO.popen("find /media -type f -name SAM") { |io| 
		samfiles.push($_.strip) unless $_ =~ /\/RegBack\// while io.gets
	}
	return samfiles
end

def rebuild_sam_combo(samcombo, samfiles)
	100.downto(0) { |i|
		begin
			samcombo.remove_text(i)
		rescue
		end
	}
	samfiles.each { |j|
		samcombo.append_text(j)
	}
	if samfiles.size < 1
		samcombo.append_text("Keine SAM-Datei gefunden!")
		samcombo.sensitive = false
	else
		samcombo.sensitive = true
	end
	samcombo.active = 0
end

def read_userlist(samfile)
	samusers = Array.new
	IO.popen("chntpw -l '" + samfile + "'") { |io|
		accstart = false
		while io.gets
			toks = $_.split("|")
			if accstart == true
				$stderr.puts "Hex ID:" + toks[1].to_s.strip
				$stderr.puts "Username: " + toks[2].to_s.strip.gsub(/[^[:print:]]/, '_')
				samusers.push( [ toks[1].to_s.strip, toks[2].to_s.strip.gsub(/[^[:print:]]/, '_') ] )
			end
			accstart = true if $_ =~ / ^|\sRID/
		end
	}
	return samusers
end

def update_user_combo(usercombo, samusers)
	100.downto(0) { |i|
		begin
			usercombo.remove_text(i)
		rescue
		end
	}
	samusers.each { |u|
		usercombo.append_text(u[1])
	}
	if samusers.size > 0
		usercombo.sensitive = true
		usercombo.active = 0
		return true
	else
		usercombo.append_text("Keine Nutzer gefunden!")
		usercombo.active = 0
		usercombo.sensitive = false
		return false
	end
end

def reset_sam_users(usercombo)
	100.downto(0) { |i|
		begin
			usercombo.remove_text(i)
		rescue
		end
	}
	usercombo.append_text("Bitte Nutzer neu einlesen!")
	usercombo.active = 0
	usercombo.sensitive = false
end

def apply_settings(samfile, hexaccount, interactive, assi)
	if interactive == true
		exstring = "Terminal --geometry=80x25 --hide-toolbars --hide-menubar --disable-server -T \"Passwort zurücksetzen - interaktiv - Bitte nicht schließen\" -x chntpw -u 0x" + hexaccount + " '" + samfile + "'"
	else
		exstring = "printf \"1\\ny\\n\" | chntpw -u 0x" + hexaccount + " '" + samfile + "'"
		# exstring = "printf \"1\\ny\\n\" | chntpw -u 0x03ff '" + samfile + "'"
	end
	$stderr.puts exstring
	system(exstring)
	retval = $?.exitstatus
	$stderr.puts retval.to_s
	if retval == 2 && interactive == false
		dialog = Gtk::Dialog.new("Passwort zurückgesetzt",assi,Gtk::Dialog::MODAL,
			[ Gtk::Stock::OK, Gtk::Dialog::RESPONSE_OK ]
		)
		dialog.has_separator = false
		label = Gtk::Label.new("Das Windows-Passwort des ausgewählten Nutzers wurde zurückgesetzt!")
		label.wrap = true
		image = Gtk::Image.new(Gtk::Stock::DIALOG_INFO, Gtk::IconSize::DIALOG)
		hbox = Gtk::HBox.new(false, 5)
		hbox.border_width = 10
		hbox.pack_start_defaults(image);
		hbox.pack_start_defaults(label);
		dialog.vbox.add(hbox)
		dialog.show_all
		dialog.run
		dialog.destroy
	elsif interactive == false
		dialog = Gtk::Dialog.new("Fehler beim Zurücksetzen",assi,Gtk::Dialog::MODAL,
			[ Gtk::Stock::OK, Gtk::Dialog::RESPONSE_OK ]
		)
		dialog.has_separator = false
		label = Gtk::Label.new("Beim Versuch, das Passwort zurückzusetzen trat ein Fehler auf!")
		label.wrap = true
		image = Gtk::Image.new(Gtk::Stock::DIALOG_ERROR, Gtk::IconSize::DIALOG)
		hbox = Gtk::HBox.new(false, 5)
		hbox.border_width = 10
		hbox.pack_start_defaults(image);
		hbox.pack_start_defaults(label);
		dialog.vbox.add(hbox)
		dialog.show_all
		dialog.run
		dialog.destroy
	else
		dialog = Gtk::Dialog.new("Aktion ausgeführt",assi,Gtk::Dialog::MODAL,
			[ Gtk::Stock::OK, Gtk::Dialog::RESPONSE_OK ]
		)
		dialog.has_separator = false
		label = Gtk::Label.new("Bitte starten Sie Windows nun neu und prüfen Sie, ob die vorgenommenen Einstellungen wirksam sind.")
		label.wrap = true
		image = Gtk::Image.new(Gtk::Stock::DIALOG_INFO, Gtk::IconSize::DIALOG)
		hbox = Gtk::HBox.new(false, 5)
		hbox.border_width = 10
		hbox.pack_start_defaults(image);
		hbox.pack_start_defaults(label);
		dialog.vbox.add(hbox)
		dialog.show_all
		dialog.run
		dialog.destroy
	end
end

#========================================
# Globally used vars
#========================================

#LANGUAGE = ENV['LANGUAGE'][0..1]

#@xbranding = File.new( "/etc/lesslinux/branding/branding.xml" )
#@branding = REXML::Document.new  @xbranding

assi = Gtk::Assistant.new
# assi.deletable = false
samfiles = Array.new
samusers = Array.new

#========================================
# First window - tell the user what we do
#========================================

desclabel = Gtk::Label.new
desclabel.set_markup("Dieser Assistent hilft Ihnen beim <b>Zurücksetzen des Passwortes von Windows XP, Vista oder 7</b>. Nach der Ausführung dieses Programms können Sie sich ohne Passwort an Ihrer Windows-Installation anmelden.")
desclabel.wrap = true
desclabel.width_request = 550
desclabel.xalign = 0.1

descpage = assi.append_page(desclabel)
assi.set_page_title(desclabel, "Zurücksetzen des Windows-Passwortes" )
assi.set_page_type(desclabel, Gtk::Assistant::PAGE_CONTENT)
assi.set_page_complete(desclabel, true) # false)

#========================================
# Second window - search SAM file
#========================================

searchlabel = Gtk::Label.new
searchlabel.set_markup("Wählen Sie die SAM-Datei aus, die Ihre Passwort-Informationen enthält. Sie können die Datei automatisch suchen lassen oder sie direkt wählen. Ihre Windows-Systempartition muss hierfür schreibbar eingebunden sein.")
searchlabel.wrap = true
searchlabel.width_request = 550
searchlabel.xalign = 0.1
searchbox = Gtk::VBox.new(false, 5)
searchradio = Gtk::RadioButton.new("SAM-Datei automatisch suchen")
searchcombo = Gtk::ComboBox.new
searchcombo.append_text("Bitte Suche starten!")
searchcombo.active = 0
searchcombo.sensitive = false
searchbutton = Gtk::Button.new("Jetzt suchen")
searchhbox = Gtk::HBox.new(false, 5)
searchhbox.pack_start(searchcombo, true, true, 5)
searchhbox.pack_start(searchbutton, false, true, 5)
searchman = Gtk::RadioButton.new(searchradio, "SAM-Datei auswählen")
searchchooser  = Gtk::FileChooserButton.new("SAM-Datei auswählen", Gtk::FileChooser::ACTION_OPEN)
searchchooser.current_folder = "/media"
searchchooser.use_preview_label = false
searchchooser.sensitive = false

searchbox.pack_start(searchlabel, false, true, 5)
searchbox.pack_start(searchradio, false, true, 5)
searchbox.pack_start(searchhbox, false, true, 5)
searchbox.pack_start(searchman, false, true, 5)
searchbox.pack_start(searchchooser, false, true, 5)

searchpage = assi.append_page(searchbox)
assi.set_page_title(searchbox, "Auswahl der SAM-Datei")
assi.set_page_type(searchbox, Gtk::Assistant::PAGE_CONTENT)
assi.set_page_complete(searchbox, false)

#========================================
# Third window - choose user to edit 
#========================================

userlabel = Gtk::Label.new
userlabel.set_markup("Wählen Sie das Nutzerkonto, dessen Passwort zurückgesetzt werden soll. In der Liste der Nutzernamen sind Sonderzeichen durch Unterstriche ersetzt. Diese Konten werden dennoch korrekt verarbeitet.")
userlabel.wrap = true
userlabel.width_request = 550
userlabel.xalign = 0.1
usercombo = Gtk::ComboBox.new
userbutton = Gtk::Button.new("Konten einlesen")
usercombo.append_text("Bitte lesen Sie die Nutzerkonten ein!")
usercombo.active = 0
usercombo.sensitive = false
userhbox = Gtk::HBox.new(false, 5)
userhbox.pack_start(usercombo, true, true, 5)
userhbox.pack_start(userbutton, false, true, 5)

methlabel = Gtk::Label.new
methlabel.set_markup("Wollen Sie erweiterte Funktionen von \"chntpw\" nutzen? Diese umfassen beispielsweise die Möglichkeit, einem Nutzer Administratorrechte einzuräumen. Die Nutzung erweiterter Funktionen empfehlen wir nur für versierte Anwender!")
methlabel.wrap = true
methlabel.width_request = 550
methlabel.xalign = 0.1
methcheck = Gtk::CheckButton.new("Erweiterte Funktionen nutzen")

userbox = Gtk::VBox.new(false, 5)
userbox.pack_start(userlabel, false, true, 5)
userbox.pack_start(userhbox, false, true, 5)
userbox.pack_start(methlabel, false, true, 5)
userbox.pack_start(methcheck, false, true, 5)

userpage = assi.append_page(userbox)
assi.set_page_title(userbox, "Auswahl des Nutzerkontos")
assi.set_page_type(userbox, Gtk::Assistant::PAGE_CONTENT)
assi.set_page_complete(userbox, false)

#========================================
# Last window - confirm the settings
#========================================

lastlabel = Gtk::Label.new("Wollen Sie das Zurücksetzen des Passwortes mit den vorgenommenen Einstellungen durchführen?")
lastlabel.wrap = true
lastlabel.width_request = 550
lastlabel.xalign = 0.1

ovlabel = Gtk::Label.new
ovlabel.wrap = true
ovlabel.width_request = 500
ovlabel.xalign = 0.3

lastdesc = Gtk::Label.new
lastdesc.set_markup("<b>Haftungsausschluß:</b> Ich bin mir bewußt, dass beim Zurücksetzen des Passwortes die Passwortdatei beschädigt werden kann, was zu einem Windows-System führt, das nicht hochfährt oder keine Anmeldung zulässt. Eine Sicherung dieser Datei habe ich daher erstellt.")
lastdesc.wrap = true
lastdesc.width_request = 500
lastdesc.xalign = 0.1
lastcheck = Gtk::CheckButton.new
lasthbox = Gtk::HBox.new(false, 5)
lasthbox.pack_start(lastcheck, false, true, 5)
lasthbox.pack_start(lastdesc, false, true, 5)

lastbox = Gtk::VBox.new(false, 5)
lastbox.pack_start(lastlabel, false, true, 5)
lastbox.pack_start(ovlabel, false, true, 5)
lastbox.pack_start(lasthbox, false, true, 5)

lastpage = assi.append_page(lastbox)
assi.set_page_title(lastbox, "Zusammenfassung")
assi.set_page_type(lastbox, Gtk::Assistant::PAGE_CONFIRM)
assi.set_page_complete(lastbox, false)

#========================================
# Common settings and callbacks
#========================================

searchradio.signal_connect('clicked') {
	searchbutton.sensitive = true
	searchchooser.sensitive = false
	searchcombo.sensitive = true if samfiles.size > 0
	reset_sam_users(usercombo)
	assi.set_page_complete(userbox, false)
	lastcheck.active = false
	if samfiles.size < 1
		assi.set_page_complete(searchbox, false)
	else
		assi.set_page_complete(searchbox, true)
	end
}

searchman.signal_connect('clicked') {
	searchbutton.sensitive = false
	searchchooser.sensitive = true
	searchcombo.sensitive = false
	reset_sam_users(usercombo)
	assi.set_page_complete(userbox, false)
	lastcheck.active = false
	assi.set_page_complete(lastbox, false)
	if searchchooser.filename.nil?
		assi.set_page_complete(searchbox, false)
	else
		assi.set_page_complete(searchbox, true)
	end
}

searchbutton.signal_connect('clicked') {
	samfiles = find_samfiles
	rebuild_sam_combo(searchcombo, samfiles)
}

searchchooser.signal_connect('selection_changed') {
	reset_sam_users(usercombo)
	lastcheck.active = false
	assi.set_page_complete(lastbox, false)
	assi.set_page_complete(userbox, false)
	assi.set_page_complete(searchbox, true) unless searchchooser.filename.nil?
}

searchcombo.signal_connect('changed') {
	reset_sam_users(usercombo)
	lastcheck.active = false
	assi.set_page_complete(lastbox, false)
	assi.set_page_complete(userbox, false)
	assi.set_page_complete(searchbox, true) if samfiles.size > 0
}

userbutton.signal_connect('clicked') {
	if searchradio.active?
		samfile = samfiles[searchcombo.active]
	else
		samfile = searchchooser.filename
	end
	samusers = read_userlist(samfile)
	assi.set_page_complete(userbox, update_user_combo(usercombo, samusers))
}

lastcheck.signal_connect('clicked') { |b|
	if b.active? 
		assi.set_page_complete(lastbox, true)
	else
		assi.set_page_complete(lastbox, false)
	end
}

assi.set_forward_page_func{|curr|
	samfile = nil
	if searchradio.active?
		samfile = samfiles[searchcombo.active]
	else
		samfile = searchchooser.filename
	end
	unless methcheck.active?
		method = "Standard, nur Zurücksetzen"
	else
		method = "Interaktiv, erweiterte Funktionen nutzen"
	end
	username = "unbekannt"
	username =  samusers[usercombo.active][1].to_s if samusers.size > 0
	ovlabel.set_markup("<b>SAM-Datei:</b> " + samfile.to_s + "\n\n<b>Nutzerkonto:</b> " + username  + 
		"\n\n<b>Methode:</b> " + method )
	curr += 1
}

assi.set_title  "Windows-Passwort zurücksetzen"
assi.border_width = 10
assi.set_size_request(600, 410)
assi.signal_connect('destroy') { Gtk.main_quit }
assi.signal_connect('cancel')  { assi.destroy }
assi.signal_connect('close')   { |w|
	if searchradio.active?
		samfile = samfiles[searchcombo.active]
	else
		samfile = searchchooser.filename
	end
	hex_account =  samusers[usercombo.active][0]
	apply_settings(samfile, hex_account, methcheck.active?, assi)
	w.destroy
}

assi.show_all
Gtk.main