1
0
Fork 0
mirror of https://github.com/seiichiro0185/sailotp.git synced 2024-11-25 00:29:42 +00:00

Version 0.2

* Added possibility to edit existing entries
* Better error handling for invalid secrets
* Better input checks on add/edit
This commit is contained in:
seiichiro 2014-01-08 19:28:06 +01:00
parent 0c0f10f7ab
commit 631868ddba
12 changed files with 415 additions and 295 deletions

26
COPYING Normal file
View file

@ -0,0 +1,26 @@
Copyright (c) 2013, Stefan Brand <seiichiro@seiichiro0185.org>
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or other
materials provided with the distribution.
3. The names of the contributors may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

36
README.md Normal file
View file

@ -0,0 +1,36 @@
# SailOTP
SailOTP is a Sailfish Implementation of the Google-Authenticator algorithm,
also known as Timebased One Time Pad (TOPT) as described in RFC 6238. A growing
number of sites uses this algorithm for two-factor-authentication, including
Github, Linode and several Google services.
At the moment the App is quite basic. One can add new OTP-entries using the
pulley-menu. The main view of the app will show a List off all entries and
their current One-Time-Tokens. The entries will be regenerated every 30 seconds, the remaining time for the current tokens is shown through a progress bar at the top of the app. An entry can be deleted by long-pressing on it.
## Known Limitations
At the moment the only way to insert new entries into the app is to insert the
title and secret key by hand. It's not possible to use the QR-Codes some sites
provide directly.
## Contact and Issues
If you find any bugs or want to suggest a feature, feel free to use Githubs
Issues feature.
## License
SailOTP is licensed under a 3-Clause BSD-License. See COPYING for details.
## Accnowledgements
SailOTP uses the SHA-1 and HMAC-Implementation from
<a href="https://github.com/Caligatio/jsSHA" target="_blank">https://github.com/Caligatio/jsSHA</a>
The implementation of the TOTP-algorithm was inspired by:
<a href="http://blog.tinisles.com/2011/10/google-authenticator-one-time-password-algorithm-in-javascript/" target="_blank">http://blog.tinisles.com/2011/10/google-authenticator-one-time-password-algorithm-in-javascript/</a>

View file

@ -30,19 +30,22 @@
import QtQuick 2.0 import QtQuick 2.0
import Sailfish.Silica 1.0 import Sailfish.Silica 1.0
// Define the Layout of the Active Cover
CoverBackground { CoverBackground {
Image { // Show the SailOTP Logo
id: logo Image {
source: "../sailotp.png" id: logo
anchors.horizontalCenter: parent.horizontalCenter source: "../sailotp.png"
anchors.top: parent.top anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 48 anchors.top: parent.top
} anchors.topMargin: 48
}
Label { // Show the Application Name
id: label Label {
anchors.centerIn: parent id: label
text: "SailOTP" anchors.centerIn: parent
} text: "SailOTP"
}
} }

View file

@ -33,8 +33,8 @@ import "pages"
ApplicationWindow ApplicationWindow
{ {
initialPage: Component { MainView { } } initialPage: Component { MainView { } }
cover: Qt.resolvedUrl("cover/CoverPage.qml") cover: Qt.resolvedUrl("cover/CoverPage.qml")
} }

View file

@ -29,49 +29,66 @@
.import "./sha.js" as SHA .import "./sha.js" as SHA
// Helper Functions // *** Helper Functions *** //
// Decimal to HEX
function dec2hex(s) { return (s < 15.5 ? '0' : '') + Math.round(s).toString(16); } function dec2hex(s) { return (s < 15.5 ? '0' : '') + Math.round(s).toString(16); }
// HEX to Decimal
function hex2dec(s) { return parseInt(s, 16); } function hex2dec(s) { return parseInt(s, 16); }
// Convert Base32-secret to HEX Value
function base32tohex(base32) { function base32tohex(base32) {
var base32chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; var base32chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
var bits = ""; var bits = "";
var hex = ""; var hex = "";
for (var i = 0; i < base32.length; i++) { for (var i = 0; i < base32.length; i++) {
var val = base32chars.indexOf(base32.charAt(i).toUpperCase()); var val = base32chars.indexOf(base32.charAt(i).toUpperCase());
bits += leftpad(val.toString(2), 5, '0'); bits += leftpad(val.toString(2), 5, '0');
} }
for (var i = 0; i+4 <= bits.length; i+=4) {
var chunk = bits.substr(i, 4);
hex = hex + parseInt(chunk, 2).toString(16) ;
}
return hex;
for (var i = 0; i+4 <= bits.length; i+=4) {
var chunk = bits.substr(i, 4);
hex = hex + parseInt(chunk, 2).toString(16) ;
}
return hex;
} }
// Pad Strings to given length
function leftpad(str, len, pad) { function leftpad(str, len, pad) {
if (len + 1 >= str.length) { if (len + 1 >= str.length) {
str = Array(len + 1 - str.length).join(pad) + str; str = Array(len + 1 - str.length).join(pad) + str;
} }
return str; return str;
} }
// *** Main Function *** //
// Calculate an OTP-Value from the given secret // Calculate an OTP-Value from the given secret
// Parameter is the secret key in Base32-notation
function calcOTP(secret) { function calcOTP(secret) {
var key = base32tohex(secret); // Convert the key to HEX
var epoch = Math.round(new Date().getTime() / 1000.0); var key = base32tohex(secret);
var time = leftpad(dec2hex(Math.floor(epoch / 30)), 16, '0'); // Get current Time in UNIX Timestamp format (Seconds since 01.01.1970 00:00 UTC)
var epoch = Math.round(new Date().getTime() / 1000.0);
// Get last full 30 / 60 Seconds and convert to HEX
var time = leftpad(dec2hex(Math.floor(epoch / 30)), 16, '0');
try {
// Calculate the SHA-1 HMAC Value from time and key
var hmacObj = new SHA.jsSHA(time, 'HEX'); var hmacObj = new SHA.jsSHA(time, 'HEX');
var hmac = hmacObj.getHMAC(key, 'HEX', 'SHA-1', "HEX"); var hmac = hmacObj.getHMAC(key, 'HEX', 'SHA-1', "HEX");
// Finally convert the HMAC-Value to the corresponding 6-digit token
var offset = hex2dec(hmac.substring(hmac.length - 1)); var offset = hex2dec(hmac.substring(hmac.length - 1));
var otp = (hex2dec(hmac.substr(offset * 2, 8)) & hex2dec('7fffffff')) + ''; var otp = (hex2dec(hmac.substr(offset * 2, 8)) & hex2dec('7fffffff')) + '';
otp = (otp).substr(otp.length - 6, 6); otp = (otp).substr(otp.length - 6, 6);
return otp; } catch (e) {
otp = "Invalid Secret!"
}
// return the calculated token
return otp;
} }

View file

@ -31,52 +31,63 @@
// Get DB Connection // Get DB Connection
function getDB() { function getDB() {
return LS.LocalStorage.openDatabaseSync("harbour-sailotp", "1.0", "SailOTP Config Storage", 1000000); return LS.LocalStorage.openDatabaseSync("harbour-sailotp", "1.0", "SailOTP Config Storage", 1000000);
} }
// Initialize Table if not exists // Initialize Table if not exists
function initialize() { function initialize() {
var db = getDB(); var db = getDB();
db.transaction( db.transaction(
function(tx) { function(tx) {
tx.executeSql("CREATE TABLE IF NOT EXISTS OTPStorage(title TEXT, secret TEXT);"); tx.executeSql("CREATE TABLE IF NOT EXISTS OTPStorage(title TEXT, secret TEXT);");
} }
) )
} }
// Get all OTPs into the list model // Get all OTPs into the list model
function getOTP() { function getOTP() {
var db = getDB(); var db = getDB();
db.transaction( db.transaction(
function(tx) { function(tx) {
var res = tx.executeSql("select * from OTPStorage;"); var res = tx.executeSql("select * from OTPStorage;");
for (var i=0; i < res.rows.length; i++) { for (var i=0; i < res.rows.length; i++) {
mainPage.appendOTP(res.rows.item(i).title, res.rows.item(i).secret); mainPage.appendOTP(res.rows.item(i).title, res.rows.item(i).secret);
} }
} }
) )
} }
// Add a new OTP // Add a new OTP
function addOTP(title, secret) { function addOTP(title, secret) {
var db = getDB(); var db = getDB();
db.transaction( db.transaction(
function(tx) { function(tx) {
tx.executeSql("INSERT INTO OTPStorage VALUES(?, ?);", [title, secret]); tx.executeSql("INSERT INTO OTPStorage VALUES(?, ?);", [title, secret]);
} }
) )
} }
// Remove an existing OTP // Remove an existing OTP
function removeOTP(title, secret) { function removeOTP(title, secret) {
var db = getDB(); var db = getDB();
db.transaction( db.transaction(
function(tx) { function(tx) {
tx.executeSql("DELETE FROM OTPStorage WHERE title=? and secret=?;", [title, secret]); tx.executeSql("DELETE FROM OTPStorage WHERE title=? and secret=?;", [title, secret]);
} }
) )
}
// Change an existing OTP
function changeOTP(title, secret, oldtitle, oldsecret) {
var db = getDB();
db.transaction(
function(tx) {
tx.executeSql("UPDATE OTPStorage SET title=?, secret=? WHERE title=? and secret=?;", [title, secret, oldtitle, oldsecret]);
}
)
} }

View file

@ -30,56 +30,54 @@
import QtQuick 2.0 import QtQuick 2.0
import Sailfish.Silica 1.0 import Sailfish.Silica 1.0
// Define the Layout of the About Page
Page { Page {
id: aboutPage id: aboutPage
Image {
Image { id: logo
id: logo source: "../sailotp.png"
source: "../sailotp.png" anchors.horizontalCenter: parent.horizontalCenter
anchors.horizontalCenter: parent.horizontalCenter y: 200
y: 200 }
Label {
id: name
anchors.horizontalCenter: parent.horizontalCenter
y: 320
font.bold: true
text: "SailOTP 0.2"
}
Text {
id: desc
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: name.bottom
anchors.topMargin: 20
text: "A Simple Sailfish TOTP Generator<br />(RFC 6238 compatible)"
color: "white"
}
Text {
id: copyright
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: desc.bottom
anchors.topMargin: 20
text: "Copyright: Stefan Brand<br />License: BSD (3-clause)"
color: "white"
}
Button {
id: homepage
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: copyright.bottom
anchors.topMargin: 20
text: "<a href=\"https://github.com/seiichiro0185/sailotp\">SailOTP on Github</a>"
onClicked: {
Qt.openUrlExternally("https://github.com/seiichiro0185/sailotp")
} }
}
Label { Text {
id: name id: accnowledgement
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
y: 320 anchors.top: homepage.bottom
font.bold: true anchors.topMargin: 20
text: "SailOTP 0.1" text: "SailOTP uses the SHA-1 Implementation<br />from http://caligatio.github.io/jsSHA/"
} color: "white"
Text { }
id: desc
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: name.bottom
anchors.topMargin: 20
text: "A Simple Sailfish TOTP Generator<br />(RFC 6238 compatible)"
color: "white"
}
Text {
id: copyright
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: desc.bottom
anchors.topMargin: 20
text: "Copyright: Stefan Brand<br />License: BSD (3-clause)"
color: "white"
}
Button {
id: homepage
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: copyright.bottom
anchors.topMargin: 20
text: "<a href=\"https://github.com/seiichiro0185/sailotp\">SailOTP on Github</a>"
onClicked: {
Qt.openUrlExternally("https://github.com/seiichiro0185/sailotp")
}
}
Text {
id: accnowledgement
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: homepage.bottom
anchors.topMargin: 20
text: "SailOTP uses the SHA-1 Implementation<br />from http://caligatio.github.io/jsSHA/"
color: "white"
}
} }

View file

@ -30,52 +30,69 @@
import QtQuick 2.0 import QtQuick 2.0
import Sailfish.Silica 1.0 import Sailfish.Silica 1.0
import "../lib/storage.js" as DB import "../lib/storage.js" as DB // Import the storage library for Config-Access
// Define Layout of the Add OTP Dialog
Dialog { Dialog {
id: addOTP id: addOTP
property QtObject parentPage: null // We get the Object of the parent page on call to refresh it after adding a new Entry
property QtObject parentPage: null
SilicaFlickable { // If we want to edit a Key we get title and key from the calling page
id: addOtpList property string paramLabel: ""
anchors.fill: parent property string paramKey: ""
VerticalScrollDecorator {} SilicaFlickable {
id: addOtpList
anchors.fill: parent
Column { VerticalScrollDecorator {}
anchors.fill: parent
DialogHeader {
acceptText: "Add"
}
TextField { Column {
id: otpLabel anchors.fill: parent
width: parent.width DialogHeader {
label: "Title" acceptText: paramLabel != "" ? "Save" : "Add"
placeholderText: "Title for the OTP" }
focus: true TextField {
horizontalAlignment: TextInput.AlignLeft id: otpLabel
} width: parent.width
label: "Title"
TextField { placeholderText: "Title for the OTP"
id: otpSecret text: paramLabel != "" ? paramLabel : ""
width: parent.width focus: true
label: "Secret" horizontalAlignment: TextInput.AlignLeft
placeholderText: "Secret OTP Key" }
focus: true TextField {
horizontalAlignment: TextInput.AlignLeft id: otpSecret
} width: parent.width
} label: "Secret (at least 16 characters)"
text: paramKey != "" ? paramKey : ""
placeholderText: "Secret OTP Key"
focus: true
horizontalAlignment: TextInput.AlignLeft
}
} }
}
onDone: { // Check if we can Save
if (otpLabel.text != "" && otpSecret.text != "") { canAccept: otpLabel.text.length > 0 && otpSecret.text.length >= 16 ? true : false
DB.addOTP(otpLabel.text, otpSecret.text);
parentPage.refreshOTPList(); // Save if page is Left with Add
} onDone: {
if (result == DialogResult.Accepted) {
// Save the entry to the Config DB
if (paramLabel != "" && paramKey != "") {
// Parameters where filled -> Change existing entry
DB.changeOTP(otpLabel.text, otpSecret.text, paramLabel, paramKey)
} else {
// There were no parameters -> Add new entry
DB.addOTP(otpLabel.text, otpSecret.text);
}
// Refresh the main Page
parentPage.refreshOTPList();
} }
}
} }

View file

@ -34,139 +34,153 @@ import "../lib/storage.js" as DB
import "../lib/crypto.js" as OTP import "../lib/crypto.js" as OTP
Page { Page {
id: mainPage id: mainPage
ListModel { ListModel {
id: otpListModel id: otpListModel
}
// This holds the time of the last update of the page as Unix Timestamp (in Milliseconds)
property double lastUpdated: null
// Add an entry to the list
function appendOTP(title, secret) {
otpListModel.append({"secret": secret, "title": title, "otp": ""});
}
// Reload the List of OTPs from storage
function refreshOTPList() {
otpListModel.clear();
DB.getOTP();
refreshOTPValues();
}
// Calculate new OTPs for every entry
function refreshOTPValues() {
// get seconds from current Date
var curDate = new Date();
var seconds = curDate.getSeconds();
// Iterate over all List entries
for (var i=0; i<otpListModel.count; i++) {
// Only update on full 30 / 60 Seconds or if last run of the Functions is more than 2s in the past (e.g. app was in background)
if (otpListModel.get(i).otp == "" || seconds == 30 || seconds == 0 || (curDate.getTime() - lastUpdated > 2000)) {
var curOTP = OTP.calcOTP(otpListModel.get(i).secret)
otpListModel.setProperty(i, "otp", curOTP);
console.log("Updating Value ", i);
}
} }
property double lastUpdated: null // Update the Progressbar
updateProgress.value = 29 - (seconds % 30)
// Set lastUpdate property
lastUpdated = curDate.getTime();
}
// Add an entry to the list Timer {
function appendOTP(title, secret) { interval: 1000
otpListModel.append({"secret": secret, "title": title, "otp": ""}); running: Qt.application.active // Timer only runs when App is active
repeat: true
onTriggered: refreshOTPValues();
}
SilicaFlickable {
anchors.fill: parent
PullDownMenu {
MenuItem {
text: "About"
onClicked: pageStack.push(Qt.resolvedUrl("About.qml"))
}
MenuItem {
text: "Add OTP"
onClicked: pageStack.push(Qt.resolvedUrl("AddOTP.qml"), {parentPage: mainPage})
}
} }
// Reload the List of OTPs from storage ProgressBar {
function refreshOTPList() { id: updateProgress
otpListModel.clear(); width: parent.width
DB.getOTP(); maximumValue: 29
refreshOTPValues(); anchors.top: parent.top
anchors.topMargin: 48
} }
// Calculate new OTPs for every entry SilicaListView {
function refreshOTPValues() { id: otpList
var curDate = new Date(); header: PageHeader {
var seconds = curDate.getSeconds(); title: "SailOTP"
}
anchors.fill: parent
model: otpListModel
width: parent.width
for (var i=0; i<otpListModel.count; i++) { ViewPlaceholder {
if (otpListModel.get(i).otp == "" || seconds == 30 || seconds == 0 || (curDate.getTime() - lastUpdated > 2000)) { enabled: otpList.count == 0
var curOTP = OTP.calcOTP(otpListModel.get(i).secret) text: "Nothing here"
otpListModel.setProperty(i, "otp", curOTP); hintText: "Pull down to add a OTP"
console.log("Updating Value ", i); }
}
delegate: ListItem {
id: otpListItem
menu: otpContextMenu
width: otpList.width
contentHeight: Theme.itemSizeMedium
function remove() {
// Show 5s countdown, then delete from DB and List
remorseAction("Deleting", function() { DB.removeOTP(title, secret); otpListModel.remove(index) })
} }
updateProgress.value = 29 - (seconds % 30) ListView.onRemove: animateRemoval()
lastUpdated = curDate.getTime(); Rectangle {
} anchors.horizontalCenter: parent.horizontalCenter
Timer { Label {
interval: 1000 id: otpLabel
running: Qt.application.active text: model.title
repeat: true color: Theme.secondaryColor
anchors.horizontalCenter: parent.horizontalCenter
}
onTriggered: refreshOTPValues(); Label {
} id: otpValue
anchors.top: otpLabel.bottom
text: model.otp
anchors.horizontalCenter: parent.horizontalCenter
color: Theme.highlightColor
font.pixelSize: Theme.fontSizeLarge
}
}
SilicaFlickable { Component {
anchors.fill: parent id: otpContextMenu
ContextMenu {
PullDownMenu {
MenuItem { MenuItem {
text: "About" text: "Edit"
onClicked: pageStack.push(Qt.resolvedUrl("About.qml")) onClicked: {
pageStack.push(Qt.resolvedUrl("AddOTP.qml"), {parentPage: mainPage, paramLabel: title, paramKey: secret})
}
} }
MenuItem { MenuItem {
text: "Add OTP" text: "Delete"
onClicked: pageStack.push(Qt.resolvedUrl("AddOTP.qml"), {parentPage: mainPage}) onClicked: remove()
} }
}
} }
}
VerticalScrollDecorator{}
ProgressBar { Component.onCompleted: {
id: updateProgress // Initialize DB (create tables etc..)
width: parent.width DB.initialize();
maximumValue: 29 // Load list of OTP-Entries
anchors.top: parent.top refreshOTPList();
anchors.topMargin: 48 }
}
SilicaListView {
id: otpList
header: PageHeader {
title: "SailOTP"
}
anchors.fill: parent
model: otpListModel
width: parent.width
ViewPlaceholder {
enabled: otpList.count == 0
text: "Nothing here"
hintText: "Pull down to add a OTP"
}
delegate: ListItem {
id: otpListItem
menu: otpContextMenu
width: otpList.width
contentHeight: Theme.itemSizeMedium
function remove() {
remorseAction("Deleting", function() { DB.removeOTP(title, secret); otpListModel.remove(index) })
}
ListView.onRemove: animateRemoval()
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
Label {
id: otpLabel
text: model.title
color: Theme.secondaryColor
anchors.horizontalCenter: parent.horizontalCenter
}
Label {
id: otpValue
anchors.top: otpLabel.bottom
text: model.otp
anchors.horizontalCenter: parent.horizontalCenter
color: Theme.highlightColor
font.pixelSize: Theme.fontSizeLarge
}
}
Component {
id: otpContextMenu
ContextMenu {
MenuItem {
text: "Delete"
onClicked: remove()
}
}
}
}
VerticalScrollDecorator{}
Component.onCompleted: {
DB.initialize();
refreshOTPList();
}
}
} }
}
} }

View file

@ -13,7 +13,7 @@ Name: harbour-sailotp
%{!?qtc_make:%define qtc_make make} %{!?qtc_make:%define qtc_make make}
%{?qtc_builddir:%define _builddir %qtc_builddir} %{?qtc_builddir:%define _builddir %qtc_builddir}
Summary: SailOTP Summary: SailOTP
Version: 0.1 Version: 0.2
Release: 1 Release: 1
Group: Security Group: Security
License: BSD License: BSD

View file

@ -1,6 +1,6 @@
Name: harbour-sailotp Name: harbour-sailotp
Summary: SailOTP Summary: SailOTP
Version: 0.1 Version: 0.2
Release: 1 Release: 1
Group: Security Group: Security
URL: https://github.com/seiichiro0185/sailotp/ URL: https://github.com/seiichiro0185/sailotp/

View file

@ -1,32 +1,31 @@
/* /*
Copyright (C) 2013 Jolla Ltd. * Copyright (c) 2013, Stefan Brand <seiichiro@seiichiro0185.org>
Contact: Thomas Perl <thomas.perl@jollamobile.com> * All rights reserved.
All rights reserved. *
* Redistribution and use in source and binary forms, with or without modification,
You may use this file under the terms of BSD license as follows: * are permitted provided that the following conditions are met:
*
Redistribution and use in source and binary forms, with or without * 1. Redistributions of source code must retain the above copyright notice, this
modification, are permitted provided that the following conditions are met: * list of conditions and the following disclaimer.
* Redistributions of source code must retain the above copyright *
notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, this
* Redistributions in binary form must reproduce the above copyright * list of conditions and the following disclaimer in the documentation and/or other
notice, this list of conditions and the following disclaimer in the * materials provided with the distribution.
documentation and/or other materials provided with the distribution. *
* Neither the name of the Jolla Ltd nor the * 3. The names of the contributors may not be used to endorse or promote products
names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission.
derived from this software without specific prior written permission. *
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
*/
#ifdef QT_QML_DEBUG #ifdef QT_QML_DEBUG
#include <QtQuick> #include <QtQuick>
@ -34,9 +33,8 @@
#include <sailfishapp.h> #include <sailfishapp.h>
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
return SailfishApp::main(argc, argv); return SailfishApp::main(argc, argv);
} }