diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..c568412 --- /dev/null +++ b/COPYING @@ -0,0 +1,26 @@ +Copyright (c) 2013, Stefan Brand +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..dddbb0c --- /dev/null +++ b/README.md @@ -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 + +https://github.com/Caligatio/jsSHA + +The implementation of the TOTP-algorithm was inspired by: + +http://blog.tinisles.com/2011/10/google-authenticator-one-time-password-algorithm-in-javascript/ + diff --git a/qml/cover/CoverPage.qml b/qml/cover/CoverPage.qml index 07e83ab..e307c99 100644 --- a/qml/cover/CoverPage.qml +++ b/qml/cover/CoverPage.qml @@ -30,19 +30,22 @@ import QtQuick 2.0 import Sailfish.Silica 1.0 +// Define the Layout of the Active Cover CoverBackground { - Image { - id: logo - source: "../sailotp.png" - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: parent.top - anchors.topMargin: 48 - } + // Show the SailOTP Logo + Image { + id: logo + source: "../sailotp.png" + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.topMargin: 48 + } - Label { - id: label - anchors.centerIn: parent - text: "SailOTP" - } + // Show the Application Name + Label { + id: label + anchors.centerIn: parent + text: "SailOTP" + } } diff --git a/qml/harbour-sailotp.qml b/qml/harbour-sailotp.qml index 260a2f3..b8632dc 100644 --- a/qml/harbour-sailotp.qml +++ b/qml/harbour-sailotp.qml @@ -33,8 +33,8 @@ import "pages" ApplicationWindow { - initialPage: Component { MainView { } } - cover: Qt.resolvedUrl("cover/CoverPage.qml") + initialPage: Component { MainView { } } + cover: Qt.resolvedUrl("cover/CoverPage.qml") } diff --git a/qml/lib/crypto.js b/qml/lib/crypto.js index 2d96a78..56f1a8a 100644 --- a/qml/lib/crypto.js +++ b/qml/lib/crypto.js @@ -29,49 +29,62 @@ .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); } +// HEX to Decimal function hex2dec(s) { return parseInt(s, 16); } - +// Convert Base32-secret to HEX Value function base32tohex(base32) { - var base32chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; - var bits = ""; - var hex = ""; + var base32chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + var bits = ""; + var hex = ""; - for (var i = 0; i < base32.length; i++) { - var val = base32chars.indexOf(base32.charAt(i).toUpperCase()); - 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 < base32.length; i++) { + var val = base32chars.indexOf(base32.charAt(i).toUpperCase()); + 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; } +// Pad Strings to given length function leftpad(str, len, pad) { - if (len + 1 >= str.length) { - str = Array(len + 1 - str.length).join(pad) + str; - } - return str; + if (len + 1 >= str.length) { + str = Array(len + 1 - str.length).join(pad) + str; + } + return str; } +// *** Main Function *** // + // Calculate an OTP-Value from the given secret +// Parameter is the secret key in Base32-notation function calcOTP(secret) { - var key = base32tohex(secret); - var epoch = Math.round(new Date().getTime() / 1000.0); - var time = leftpad(dec2hex(Math.floor(epoch / 30)), 16, '0'); + // Convert the key to HEX + var key = base32tohex(secret); + // 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'); - var hmacObj = new SHA.jsSHA(time, 'HEX'); - var hmac = hmacObj.getHMAC(key, 'HEX', 'SHA-1', "HEX"); + // Calculate the SHA-1 HMAC Value from time and key + var hmacObj = new SHA.jsSHA(time, 'HEX'); + var hmac = hmacObj.getHMAC(key, 'HEX', 'SHA-1', "HEX"); - var offset = hex2dec(hmac.substring(hmac.length - 1)); + // Finally convert the HMAC-Value to the corresponding 6-digit token + var offset = hex2dec(hmac.substring(hmac.length - 1)); - var otp = (hex2dec(hmac.substr(offset * 2, 8)) & hex2dec('7fffffff')) + ''; - otp = (otp).substr(otp.length - 6, 6); - return otp; + var otp = (hex2dec(hmac.substr(offset * 2, 8)) & hex2dec('7fffffff')) + ''; + otp = (otp).substr(otp.length - 6, 6); + + // return the calculated token + return otp; } diff --git a/qml/lib/storage.js b/qml/lib/storage.js index b6826ab..7ec2108 100644 --- a/qml/lib/storage.js +++ b/qml/lib/storage.js @@ -31,52 +31,52 @@ // Get DB Connection 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 function initialize() { - var db = getDB(); + var db = getDB(); - db.transaction( - function(tx) { - tx.executeSql("CREATE TABLE IF NOT EXISTS OTPStorage(title TEXT, secret TEXT);"); - } - ) + db.transaction( + function(tx) { + tx.executeSql("CREATE TABLE IF NOT EXISTS OTPStorage(title TEXT, secret TEXT);"); + } + ) } // Get all OTPs into the list model function getOTP() { - var db = getDB(); + var db = getDB(); - db.transaction( - function(tx) { - var res = tx.executeSql("select * from OTPStorage;"); - for (var i=0; i < res.rows.length; i++) { - mainPage.appendOTP(res.rows.item(i).title, res.rows.item(i).secret); - } - } - ) + db.transaction( + function(tx) { + var res = tx.executeSql("select * from OTPStorage;"); + for (var i=0; i < res.rows.length; i++) { + mainPage.appendOTP(res.rows.item(i).title, res.rows.item(i).secret); + } + } + ) } // Add a new OTP function addOTP(title, secret) { - var db = getDB(); + var db = getDB(); - db.transaction( - function(tx) { - tx.executeSql("INSERT INTO OTPStorage VALUES(?, ?);", [title, secret]); - } - ) + db.transaction( + function(tx) { + tx.executeSql("INSERT INTO OTPStorage VALUES(?, ?);", [title, secret]); + } + ) } // Remove an existing OTP function removeOTP(title, secret) { - var db = getDB(); + var db = getDB(); - db.transaction( - function(tx) { - tx.executeSql("DELETE FROM OTPStorage WHERE title=? and secret=?;", [title, secret]); - } - ) + db.transaction( + function(tx) { + tx.executeSql("DELETE FROM OTPStorage WHERE title=? and secret=?;", [title, secret]); + } + ) } diff --git a/qml/pages/About.qml b/qml/pages/About.qml index e75d2f1..463d4b1 100644 --- a/qml/pages/About.qml +++ b/qml/pages/About.qml @@ -30,56 +30,54 @@ import QtQuick 2.0 import Sailfish.Silica 1.0 +// Define the Layout of the About Page Page { - id: aboutPage - - Image { - id: logo - source: "../sailotp.png" - anchors.horizontalCenter: parent.horizontalCenter - y: 200 + id: aboutPage + Image { + id: logo + source: "../sailotp.png" + anchors.horizontalCenter: parent.horizontalCenter + y: 200 + } + Label { + id: name + anchors.horizontalCenter: parent.horizontalCenter + y: 320 + font.bold: true + text: "SailOTP 0.1" + } + Text { + id: desc + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: name.bottom + anchors.topMargin: 20 + text: "A Simple Sailfish TOTP Generator
(RFC 6238 compatible)" + color: "white" + } + Text { + id: copyright + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: desc.bottom + anchors.topMargin: 20 + text: "Copyright: Stefan Brand
License: BSD (3-clause)" + color: "white" + } + Button { + id: homepage + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: copyright.bottom + anchors.topMargin: 20 + text: "SailOTP on Github" + onClicked: { + Qt.openUrlExternally("https://github.com/seiichiro0185/sailotp") } - - Label { - id: name - anchors.horizontalCenter: parent.horizontalCenter - y: 320 - font.bold: true - text: "SailOTP 0.1" - } - Text { - id: desc - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: name.bottom - anchors.topMargin: 20 - text: "A Simple Sailfish TOTP Generator
(RFC 6238 compatible)" - color: "white" - } - Text { - id: copyright - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: desc.bottom - anchors.topMargin: 20 - text: "Copyright: Stefan Brand
License: BSD (3-clause)" - color: "white" - } - Button { - id: homepage - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: copyright.bottom - anchors.topMargin: 20 - text: "SailOTP on Github" - 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
from http://caligatio.github.io/jsSHA/" - color: "white" - } - + } + Text { + id: accnowledgement + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: homepage.bottom + anchors.topMargin: 20 + text: "SailOTP uses the SHA-1 Implementation
from http://caligatio.github.io/jsSHA/" + color: "white" + } } diff --git a/qml/pages/AddOTP.qml b/qml/pages/AddOTP.qml index 3968b96..ebeddf2 100644 --- a/qml/pages/AddOTP.qml +++ b/qml/pages/AddOTP.qml @@ -30,52 +30,55 @@ import QtQuick 2.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 { - 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 { - id: addOtpList - anchors.fill: parent + SilicaFlickable { + id: addOtpList + anchors.fill: parent - VerticalScrollDecorator {} + VerticalScrollDecorator {} - Column { - anchors.fill: parent - DialogHeader { - acceptText: "Add" - } - - TextField { - id: otpLabel - width: parent.width - label: "Title" - placeholderText: "Title for the OTP" - focus: true - horizontalAlignment: TextInput.AlignLeft - } - - TextField { - id: otpSecret - width: parent.width - label: "Secret" - placeholderText: "Secret OTP Key" - focus: true - horizontalAlignment: TextInput.AlignLeft - } - } + Column { + anchors.fill: parent + DialogHeader { + acceptText: "Add" + } + TextField { + id: otpLabel + width: parent.width + label: "Title" + placeholderText: "Title for the OTP" + focus: true + horizontalAlignment: TextInput.AlignLeft + } + TextField { + id: otpSecret + width: parent.width + label: "Secret" + placeholderText: "Secret OTP Key" + focus: true + horizontalAlignment: TextInput.AlignLeft + } } + } - onDone: { - if (otpLabel.text != "" && otpSecret.text != "") { - DB.addOTP(otpLabel.text, otpSecret.text); - parentPage.refreshOTPList(); - } + // Save if page is Left with Add + onDone: { + // Some basic Input Check, we need both Values to work + if (otpLabel.text != "" && otpSecret.text != "") { + // Save the entry to the Config DB + DB.addOTP(otpLabel.text, otpSecret.text); + // Refresh the main Page + parentPage.refreshOTPList(); } - + } } diff --git a/qml/pages/MainView.qml b/qml/pages/MainView.qml index 9f7d3de..e9fea04 100644 --- a/qml/pages/MainView.qml +++ b/qml/pages/MainView.qml @@ -34,139 +34,147 @@ import "../lib/storage.js" as DB import "../lib/crypto.js" as OTP Page { - id: mainPage + id: mainPage - ListModel { - id: otpListModel + ListModel { + 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 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 - function appendOTP(title, secret) { - otpListModel.append({"secret": secret, "title": title, "otp": ""}); + Timer { + interval: 1000 + 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 - function refreshOTPList() { - otpListModel.clear(); - DB.getOTP(); - refreshOTPValues(); + ProgressBar { + id: updateProgress + width: parent.width + maximumValue: 29 + anchors.top: parent.top + anchors.topMargin: 48 } - // Calculate new OTPs for every entry - function refreshOTPValues() { - var curDate = new Date(); - var seconds = curDate.getSeconds(); + SilicaListView { + id: otpList + header: PageHeader { + title: "SailOTP" + } + anchors.fill: parent + model: otpListModel + width: parent.width - for (var i=0; i 2000)) { - var curOTP = OTP.calcOTP(otpListModel.get(i).secret) - otpListModel.setProperty(i, "otp", curOTP); - console.log("Updating Value ", i); - } + 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() { + // Show 5s countdown, then delete from DB and List + remorseAction("Deleting", function() { DB.removeOTP(title, secret); otpListModel.remove(index) }) } - updateProgress.value = 29 - (seconds % 30) - lastUpdated = curDate.getTime(); - } + ListView.onRemove: animateRemoval() + Rectangle { + anchors.horizontalCenter: parent.horizontalCenter - Timer { - interval: 1000 - running: Qt.application.active - repeat: true + Label { + id: otpLabel + text: model.title + 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 { - anchors.fill: parent - - PullDownMenu { + Component { + id: otpContextMenu + ContextMenu { MenuItem { - text: "About" - onClicked: pageStack.push(Qt.resolvedUrl("About.qml")) - } - MenuItem { - text: "Add OTP" - onClicked: pageStack.push(Qt.resolvedUrl("AddOTP.qml"), {parentPage: mainPage}) + text: "Delete" + onClicked: remove() } + } } + } + VerticalScrollDecorator{} - ProgressBar { - id: updateProgress - width: parent.width - maximumValue: 29 - anchors.top: parent.top - 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(); - } - } + Component.onCompleted: { + // Initialize DB (create tables etc..) + DB.initialize(); + // Load list of OTP-Entries + refreshOTPList(); + } } + } } diff --git a/src/harbour-sailotp.cpp b/src/harbour-sailotp.cpp index 0f897ec..f6d8d32 100644 --- a/src/harbour-sailotp.cpp +++ b/src/harbour-sailotp.cpp @@ -1,32 +1,31 @@ /* - Copyright (C) 2013 Jolla Ltd. - Contact: Thomas Perl - All rights reserved. - - You may use this file under the terms of BSD license as follows: - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * 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. - * Neither the name of the Jolla Ltd nor the - names of its contributors may 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 HOLDERS 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. -*/ + * Copyright (c) 2013, Stefan Brand + * 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. + */ #ifdef QT_QML_DEBUG #include @@ -34,9 +33,8 @@ #include - int main(int argc, char *argv[]) { - return SailfishApp::main(argc, argv); + return SailfishApp::main(argc, argv); }