/*
* Copyright (C) 2011 Andrew E. Bruno <aeb@qnot.org>
*
* This file is part of passtab.
*
* passtab is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* passtab is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with passtab. If not, see <http://www.gnu.org/licenses/>.
*/
package org.qnot.passtab;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.google.gson.Gson;
/**
* Main class for the PassTab application
*
* @author Andrew E. Bruno <aeb@qnot.org>
*/
public class PassTab {
private static Log logger = LogFactory.getLog(PassTab.class);
private Options options;
protected PropertiesConfiguration properties;
public PassTab() {
properties = new PropertiesConfiguration();
try {
properties.load(".passtab");
} catch(ConfigurationException ignored) {
}
}
public static void main(String[] args) {
PassTab passtab = new PassTab();
try {
passtab.run(args);
} catch(Exception e) {
logger.fatal("Something really bad happened: "+e.getMessage());
e.printStackTrace();
}
}
public void run(String[] args) throws IOException {
buildOptions();
CommandLineParser parser = new PosixParser();
CommandLine cmd = null;
try {
cmd = parser.parse(options, args);
} catch (ParseException e) {
printHelpAndExit(options, e.getMessage());
}
if (cmd.hasOption("h")) {
printHelpAndExit(options);
}
if (cmd.hasOption("p")) {
print(cmd);
} else if(cmd.hasOption("e")) {
export(cmd);
} else if (cmd.hasOption("g") || cmd.hasOption("t")) {
printPassword(cmd);
} else {
generate(cmd);
}
System.exit(0);
}
public void print(CommandLine cmd) throws IOException {
TabulaRecta tabulaRecta = getDatabase(cmd);
getOutputFormat(cmd).output(getOutputStream(cmd), tabulaRecta);
}
@SuppressWarnings("unchecked")
public void export(CommandLine cmd) throws IOException {
Iterator<String> kit = properties.getKeys("tag");
if(kit.hasNext()) {
Map<String, Boolean> tagMap = new HashMap<String, Boolean>();
while(kit.hasNext()) {
String key = kit.next();
String[] parts = key.split("\\.");
if(parts == null || parts.length < 2) {
printHelpAndExit(options, "Malformed configuration file. This property is invalid: "+key);
}
tagMap.put(parts[1], true);
}
if(cmd.hasOption("v")) {
System.out.println("tag\tcoords\tpassword\tlogin\twebsite");
}
for(String tag : tagMap.keySet()) {
String key = "tag."+tag;
String coords = properties.getString(key+".coords");
String login = properties.getString(key+".login", "");
String website = properties.getString(key+".website", "");
String password = fetchPassword(coords, cmd);
if(cmd.hasOption("v")) {
System.out.println(tag+"\t"+coords+"\t"+password+"\t"+login+"\t"+website);
} else {
System.out.println(tag+"\t"+password);
}
}
} else {
printHelpAndExit(options, "No tags defined in user config file: "+properties.getFile().getAbsolutePath());
}
}
@SuppressWarnings("unchecked")
public void printPassword(CommandLine cmd) throws IOException {
String coords = null;
String login = null;
String website = null;
String tag = null;
if(cmd.hasOption("t")) {
tag = cmd.getOptionValue("t");
if(tag == null || tag.length() == 0) {
printHelpAndExit(options, "Please provide a non empty tag");
}
Iterator<String> kit = properties.getKeys("tag");
if(kit.hasNext()) {
while(kit.hasNext()) {
String key = kit.next();
if(key.contains(tag+".coords")) {
String prefix = key.replaceAll("\\.coords", "");
coords = properties.getString(key);
login = properties.getString(prefix+".login", "");
website = properties.getString(prefix+".website", "");
break;
}
}
}
if(coords == null) {
die("Tag not found in configuration file: "+tag);
}
} else {
coords = cmd.getOptionValue("g");
}
if (coords == null || coords.length() == 0) {
printHelpAndExit(options,
"Please provide a row:column (ex. C:F)");
}
String password = fetchPassword(coords, cmd);
if(cmd.hasOption("v") && tag != null) {
System.out.println(tag+"\t"+coords+"\t"+password+"\t"+login+"\t"+website);
} else {
if(cmd.hasOption("m")) {
System.out.print(password);
} else {
System.out.println(password);
}
}
}
private String fetchPassword(String coords, CommandLine cmd) throws IOException {
String[] parts = coords.split(":");
if (parts == null || parts.length != 2) {
printHelpAndExit(options,
"Invalid value. Please provide a row:column (ex. C:F)");
}
String row = parts[0];
String col = parts[1];
String seq = cmd.getOptionValue("s");
if (seq == null || seq.length() == 0) {
String[] seqItems = properties.getStringArray("sequence");
seq = StringUtils.join(seqItems, ",");
}
if(seq == null) {
seq = Sequence.DEFAULT_SEQUENCE;
}
Sequence sequence = null;
try {
sequence = Sequence.fromString(seq);
} catch(SequenceParseException e) {
printHelpAndExit(options, "Invalid sequence string: "+e.getMessage());
}
Direction[] directionPriority = null;
if(cmd.hasOption("c")) {
String collision = cmd.getOptionValue("c");
if("cc".equalsIgnoreCase(collision) ||
"clockwise_compass".equalsIgnoreCase(collision)) {
directionPriority = Direction.clockwiseCompass();
} else if("ccc".equalsIgnoreCase(collision) ||
"counterclockwise_compass".equalsIgnoreCase(collision)) {
directionPriority = Direction.counterclockwiseCompass();
} else {
String[] cparts = collision.split(",");
List<Direction> list = new ArrayList<Direction>();
for(String c : cparts) {
try {
list.add(Direction.fromString(c));
} catch(SequenceParseException e) {
printHelpAndExit(options, "Direction parse error: "+e.getMessage());
}
}
directionPriority = list.toArray(new Direction[list.size()]);
}
}
boolean skipStart = false;
if(cmd.hasOption("k") || properties.getBoolean("skipstart", false)) {
skipStart = true;
}
int skipInterval = properties.getInt("skip.interval", 0);
if(cmd.hasOption("x")) {
String skipStr = cmd.getOptionValue("x");
try {
skipInterval = Integer.valueOf(skipStr);
} catch(NumberFormatException e) {
printHelpAndExit(options, "Please provide a positive integer for the skip value");
}
}
if(skipInterval < 0) {
printHelpAndExit(options, "Please provide a positive integer for the skip value");
}
TabulaRecta tabulaRecta = getDatabase(cmd);
int rowIndex = tabulaRecta.getHeader().getIndex(row);
int colIndex = tabulaRecta.getHeader().getIndex(col);
if (rowIndex == -1 || colIndex == -1) {
die("Invalid row/column.");
}
String password = tabulaRecta.getPassword(rowIndex, colIndex, sequence, skipStart, skipInterval, directionPriority);
return password;
}
public void generate(CommandLine cmd) throws IOException {
Alphabet headerAlphabet = Alphabet.ALPHA_UPPER_NUM;
Alphabet dataAlphabet = Alphabet.ALPHA_NUM_SYMBOL;
String[] dataAlphaPropArray = properties.getStringArray("alphabet");
if(cmd.hasOption("a")) {
try {
dataAlphabet = Alphabet.fromString(cmd.getOptionValue("a"));
} catch(AlphabetParseException e) {
printHelpAndExit(options, "Alphabet parsing error: "+e.getMessage());
}
} else if(dataAlphaPropArray != null && dataAlphaPropArray.length > 0) {
try {
dataAlphabet = Alphabet.fromStringArray(dataAlphaPropArray);
} catch(AlphabetParseException e) {
printHelpAndExit(options, "Alphabet parsing error: "+e.getMessage());
}
}
String[] headerAlphaPropArray = properties.getStringArray("alphabet.header");
if(cmd.hasOption("b")) {
try {
headerAlphabet = Alphabet.fromString(cmd.getOptionValue("b"));
} catch(AlphabetParseException e) {
printHelpAndExit(options, "Header Alphabet parsing error: "+e.getMessage());
}
} else if(headerAlphaPropArray != null && headerAlphaPropArray.length > 0) {
try {
headerAlphabet = Alphabet.fromStringArray(headerAlphaPropArray);
} catch(AlphabetParseException e) {
printHelpAndExit(options, "Header Alphabet parsing error: "+e.getMessage());
}
}
if(headerAlphabet.size() > 36) {
printHelpAndExit(options, "Header alphabets with more than 36 symbols are not supported yet :)");
}
TabulaRecta tabulaRecta = new TabulaRecta(headerAlphabet, dataAlphabet);
logger.info("Generating a random Tabula Recta (might take a while)...");
tabulaRecta.generate();
if (cmd.hasOption("d")) {
OutputStream jsonOut = null;
OutputStream pdfOut = null;
String name = cmd.getOptionValue("n");
if (name != null && name.length() > 0) {
jsonOut = new FileOutputStream(name + ".json");
pdfOut = new FileOutputStream(name + ".pdf");
} else {
jsonOut = new FileOutputStream(new File(System
.getProperty("user.home"), ".passtab_db"));
pdfOut = new FileOutputStream("passtab.pdf");
}
OutputFormat jsonFormat = new JSONOutput();
OutputFormat pdfFormat = new PDFOutput();
jsonFormat.output(jsonOut, tabulaRecta);
pdfFormat.output(pdfOut, tabulaRecta);
} else {
getOutputFormat(cmd).output(getOutputStream(cmd), tabulaRecta);
}
}
private OutputFormat getOutputFormat(CommandLine cmd) {
String format = cmd.getOptionValue("f");
OutputFormat outputFormat = null;
if ("json".equals(format)) {
outputFormat = new JSONOutput();
} else if ("pdf".equals(format)) {
if(cmd.hasOption("w")) {
outputFormat = new PDFOutput(false);
} else {
outputFormat = new PDFOutput();
}
} else {
outputFormat = new AsciiOutput();
}
return outputFormat;
}
private TabulaRecta getDatabase(CommandLine cmd) throws IOException {
File jsonFile = null;
String inFile = cmd.getOptionValue("i");
String inFileProps = properties.getString("passtab.path");
if (inFile != null && inFile.length() > 0) {
jsonFile = new File(inFile);
} else if(inFileProps != null && inFileProps.length() > 0){
jsonFile = new File(inFileProps);
} else {
jsonFile = new File(System.getProperty("user.home"), ".passtab_db");
}
if(!jsonFile.exists() || !jsonFile.canRead()) {
printHelpAndExit(options, "Failed to read db file: "+jsonFile.getAbsolutePath());
}
String json = FileUtils.readFileToString(jsonFile);
Gson gson = new Gson();
TabulaRecta tabulaRecta = gson.fromJson(json, TabulaRecta.class);
// Check to make sure we loaded a valid JSON file
if(tabulaRecta == null ||
tabulaRecta.getHeader() == null || tabulaRecta.getHeader().size() == 0 ||
tabulaRecta.getDataAlphabet() == null || tabulaRecta.getDataAlphabet().size() == 0 ||
tabulaRecta.getRawData() == null || tabulaRecta.rows() == 0 || tabulaRecta.cols() == 0
) {
printHelpAndExit(options, "Not a valid passtab JSON file: "+jsonFile.getAbsolutePath());
}
return tabulaRecta;
}
private OutputStream getOutputStream(CommandLine cmd) throws IOException {
String outFile = cmd.getOptionValue("o");
OutputStream outputStream = System.out;
if (outFile != null && outFile.length() > 0) {
outputStream = new FileOutputStream(outFile);
}
return outputStream;
}
@SuppressWarnings("static-access")
private void buildOptions() {
options = new Options();
options.addOption(
OptionBuilder.withLongOpt("output")
.withDescription("output file")
.hasArg()
.create("o")
);
options.addOption(
OptionBuilder.withLongOpt("help")
.withDescription("print usage info")
.create("h")
);
options.addOption(
OptionBuilder.withLongOpt("dbsave")
.withDescription("save passtab db to JSON and write out PDF file")
.create("d")
);
options.addOption(
OptionBuilder.withLongOpt("name")
.withDescription("name of database, creates [name].pdf and [name].json files")
.hasArg()
.create("n")
);
options.addOption(
OptionBuilder.withLongOpt("format")
.withDescription("Output format [pdf|json|ascii]")
.hasArg()
.create("f")
);
options.addOption(
OptionBuilder.withLongOpt("getpass")
.withDescription("[row:column] - get the password at row:column (ex. B:W)")
.hasArg()
.create("g")
);
options.addOption(
OptionBuilder.withLongOpt("print")
.withDescription("print existing passtab database")
.create("p")
);
options.addOption(
OptionBuilder.withLongOpt("input")
.withDescription("input file for retrieving passwords")
.hasArg()
.create("i")
);
options.addOption(
OptionBuilder.withLongOpt("sequence")
.withDescription("sequence for fetching password")
.hasArg()
.create("s")
);
options.addOption(
OptionBuilder.withLongOpt("alphabet")
.withDescription("alphabet to use for Password Recta")
.hasArg()
.create("a")
);
options.addOption(
OptionBuilder.withLongOpt("header")
.withDescription("header alphabet to use for row/column headings")
.hasArg()
.create("b")
);
options.addOption(
OptionBuilder.withLongOpt("collision")
.withDescription("direction precedence should a collision occur")
.hasArg()
.create("c")
);
options.addOption(
OptionBuilder.withLongOpt("tag")
.withDescription("get password for tag")
.hasArg()
.create("t")
);
options.addOption(
OptionBuilder.withLongOpt("skipstart")
.withDescription("Don't include the starting cell in the password")
.create("k")
);
options.addOption(
OptionBuilder.withLongOpt("skip")
.withDescription("Skip interval. Skip every X symbol when generating password")
.hasArg()
.create("x")
);
options.addOption(
OptionBuilder.withLongOpt("export")
.withDescription("Export password for all tags defined in user config file")
.create("e")
);
options.addOption(
OptionBuilder.withLongOpt("verbose")
.withDescription("print out all fields from config file when fetching a password")
.create("v")
);
options.addOption(
OptionBuilder.withLongOpt("nocolor")
.withDescription("Print Password Recta with no color")
.create("w")
);
options.addOption(
OptionBuilder.withLongOpt("chomp")
.withDescription("Don't print newline when fetching password")
.create("m")
);
}
public void die(String message) {
if (message != null) logger.fatal("Fatal error: " + message);
System.exit(1);
}
public void printHelpAndExit(Options options, String message) {
if (message != null)
logger.fatal("Usage error: " + message + "\n");
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp("passtab", options);
if (message != null) {
System.exit(1);
} else {
System.exit(0);
}
}
public void printHelpAndExit(Options options) {
printHelpAndExit(options, null);
}
}