cmd.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. /*
  2. * Copyright (C) by Olivier Goffart <ogoffart@owncloud.com>
  3. * Copyright (C) by Klaas Freitag <freitag@owncloud.com>
  4. *
  5. * This program is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful, but
  11. * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  12. * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  13. * for more details.
  14. */
  15. #include <iostream>
  16. #include <qcoreapplication.h>
  17. #include <QStringList>
  18. #include <QUrl>
  19. #include <QFile>
  20. #include <qdebug.h>
  21. #include "account.h"
  22. #include "clientproxy.h"
  23. #include "configfile.h" // ONLY ACCESS THE STATIC FUNCTIONS!
  24. #include "creds/httpcredentials.h"
  25. #include "simplesslerrorhandler.h"
  26. #include "syncengine.h"
  27. #include "syncjournaldb.h"
  28. #include "config.h"
  29. #include "cmd.h"
  30. #include "theme.h"
  31. #include "netrcparser.h"
  32. #include "config.h"
  33. #ifdef Q_OS_WIN32
  34. #include <windows.h>
  35. #else
  36. #include <termios.h>
  37. #endif
  38. using namespace OCC;
  39. struct CmdOptions {
  40. QString source_dir;
  41. QString target_url;
  42. QString config_directory;
  43. QString user;
  44. QString password;
  45. QString proxy;
  46. bool silent;
  47. bool trustSSL;
  48. bool useNetrc;
  49. bool interactive;
  50. bool ignoreHiddenFiles;
  51. bool nonShib;
  52. QString exclude;
  53. QString unsyncedfolders;
  54. QString davPath;
  55. int restartTimes;
  56. };
  57. // we can't use csync_set_userdata because the SyncEngine sets it already.
  58. // So we have to use a global variable
  59. CmdOptions *opts = 0;
  60. class EchoDisabler
  61. {
  62. public:
  63. EchoDisabler()
  64. {
  65. #ifdef Q_OS_WIN
  66. hStdin = GetStdHandle(STD_INPUT_HANDLE);
  67. GetConsoleMode(hStdin, &mode);
  68. SetConsoleMode(hStdin, mode & (~ENABLE_ECHO_INPUT));
  69. #else
  70. tcgetattr(STDIN_FILENO, &tios);
  71. termios tios_new = tios;
  72. tios_new.c_lflag &= ~ECHO;
  73. tcsetattr(STDIN_FILENO, TCSANOW, &tios_new);
  74. #endif
  75. }
  76. ~EchoDisabler()
  77. {
  78. #ifdef Q_OS_WIN
  79. SetConsoleMode(hStdin, mode);
  80. #else
  81. tcsetattr(STDIN_FILENO, TCSANOW, &tios);
  82. #endif
  83. }
  84. private:
  85. #ifdef Q_OS_WIN
  86. DWORD mode = 0;
  87. HANDLE hStdin;
  88. #else
  89. termios tios;
  90. #endif
  91. };
  92. QString queryPassword(const QString &user)
  93. {
  94. EchoDisabler disabler;
  95. std::cout << "Password for user " << qPrintable(user) << ": ";
  96. std::string s;
  97. std::getline(std::cin, s);
  98. return QString::fromStdString(s);
  99. }
  100. class HttpCredentialsText : public HttpCredentials {
  101. public:
  102. HttpCredentialsText(const QString& user, const QString& password)
  103. : HttpCredentials(user, password, "", ""), // FIXME: not working with client certs yet (qknight)
  104. _sslTrusted(false)
  105. {}
  106. void askFromUser() Q_DECL_OVERRIDE {
  107. _password = ::queryPassword(user());
  108. _ready = true;
  109. persist();
  110. emit asked();
  111. }
  112. void setSSLTrusted( bool isTrusted ) {
  113. _sslTrusted = isTrusted;
  114. }
  115. bool sslIsTrusted() Q_DECL_OVERRIDE {
  116. return _sslTrusted;
  117. }
  118. private:
  119. bool _sslTrusted;
  120. };
  121. void help()
  122. {
  123. const char *binaryName = APPLICATION_EXECUTABLE "cmd";
  124. std::cout << binaryName << " - command line " APPLICATION_NAME " client tool" << std::endl;
  125. std::cout << "" << std::endl;
  126. std::cout << "Usage: " << binaryName << " [OPTION] <source_dir> <server_url>" << std::endl;
  127. std::cout << "" << std::endl;
  128. std::cout << "A proxy can either be set manually using --httpproxy." << std::endl;
  129. std::cout << "Otherwise, the setting from a configured sync client will be used." << std::endl;
  130. std::cout << std::endl;
  131. std::cout << "Options:" << std::endl;
  132. std::cout << " --silent, -s Don't be so verbose" << std::endl;
  133. std::cout << " --httpproxy [proxy] Specify a http proxy to use." << std::endl;
  134. std::cout << " Proxy is http://server:port" << std::endl;
  135. std::cout << " --trust Trust the SSL certification." << std::endl;
  136. std::cout << " --exclude [file] Exclude list file" << std::endl;
  137. std::cout << " --unsyncedfolders [file] File containing the list of unsynced folders (selective sync)" << std::endl;
  138. std::cout << " --user, -u [name] Use [name] as the login name" << std::endl;
  139. std::cout << " --password, -p [pass] Use [pass] as password" << std::endl;
  140. std::cout << " -n Use netrc (5) for login" << std::endl;
  141. std::cout << " --non-interactive Do not block execution with interaction" << std::endl;
  142. std::cout << " --nonshib Use Non Shibboleth WebDAV authentication" << std::endl;
  143. std::cout << " --davpath [path] Custom themed dav path, overrides --nonshib" << std::endl;
  144. std::cout << " --max-sync-retries [n] Retries maximum n times (default to 3)" << std::endl;
  145. std::cout << " -h Sync hidden files,do not ignore them" << std::endl;
  146. std::cout << " --version, -v Display version and exit" << std::endl;
  147. std::cout << "" << std::endl;
  148. exit(0);
  149. }
  150. void showVersion() {
  151. const char *binaryName = APPLICATION_EXECUTABLE "cmd";
  152. std::cout << binaryName << " version " << qPrintable(Theme::instance()->version()) << std::endl;
  153. exit(0);
  154. }
  155. void parseOptions( const QStringList& app_args, CmdOptions *options )
  156. {
  157. QStringList args(app_args);
  158. int argCount = args.count();
  159. if( argCount < 3 ) {
  160. if (argCount >= 2) {
  161. const QString option = args.at(1);
  162. if (option == "-v" || option == "--version") {
  163. showVersion();
  164. }
  165. }
  166. help();
  167. }
  168. options->target_url = args.takeLast();
  169. options->source_dir = args.takeLast();
  170. if (!options->source_dir.endsWith('/')) {
  171. options->source_dir.append('/');
  172. }
  173. if( !QFile::exists( options->source_dir )) {
  174. std::cerr << "Source dir '" << qPrintable(options->source_dir) << "' does not exist." << std::endl;
  175. exit(1);
  176. }
  177. QStringListIterator it(args);
  178. // skip file name;
  179. if (it.hasNext()) it.next();
  180. while(it.hasNext()) {
  181. const QString option = it.next();
  182. if( option == "--httpproxy" && !it.peekNext().startsWith("-")) {
  183. options->proxy = it.next();
  184. } else if( option == "-s" || option == "--silent") {
  185. options->silent = true;
  186. } else if( option == "--trust") {
  187. options->trustSSL = true;
  188. } else if( option == "-n") {
  189. options->useNetrc = true;
  190. } else if( option == "-h") {
  191. options->ignoreHiddenFiles = false;
  192. } else if( option == "--non-interactive") {
  193. options->interactive = false;
  194. } else if( (option == "-u" || option == "--user") && !it.peekNext().startsWith("-") ) {
  195. options->user = it.next();
  196. } else if( (option == "-p" || option == "--password") && !it.peekNext().startsWith("-") ) {
  197. options->password = it.next();
  198. } else if( option == "--exclude" && !it.peekNext().startsWith("-") ) {
  199. options->exclude = it.next();
  200. } else if( option == "--unsyncedfolders" && !it.peekNext().startsWith("-") ) {
  201. options->unsyncedfolders = it.next();
  202. } else if( option == "--nonshib" ) {
  203. options->nonShib = true;
  204. } else if( option == "--davpath" && !it.peekNext().startsWith("-") ) {
  205. options->davPath = it.next();
  206. } else if( option == "--max-sync-retries" && !it.peekNext().startsWith("-") ) {
  207. options->restartTimes = it.next().toInt();
  208. } else {
  209. help();
  210. }
  211. }
  212. if( options->target_url.isEmpty() || options->source_dir.isEmpty() ) {
  213. help();
  214. }
  215. }
  216. /* If the selective sync list is different from before, we need to disable the read from db
  217. (The normal client does it in SelectiveSyncDialog::accept*)
  218. */
  219. void selectiveSyncFixup(OCC::SyncJournalDb *journal, const QStringList &newList)
  220. {
  221. if (!journal->exists()) {
  222. return;
  223. }
  224. SqlDatabase db;
  225. if (!db.openOrCreateReadWrite(journal->databaseFilePath())) {
  226. return;
  227. }
  228. auto oldBlackListSet = journal->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList).toSet();
  229. auto blackListSet = newList.toSet();
  230. auto changes = (oldBlackListSet - blackListSet) + (blackListSet - oldBlackListSet);
  231. foreach(const auto &it, changes) {
  232. journal->avoidReadFromDbOnNextSync(it);
  233. }
  234. journal->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, newList);
  235. }
  236. int main(int argc, char **argv) {
  237. QCoreApplication app(argc, argv);
  238. qsrand(QTime::currentTime().msec() * QCoreApplication::applicationPid());
  239. CmdOptions options;
  240. options.silent = false;
  241. options.trustSSL = false;
  242. options.useNetrc = false;
  243. options.interactive = true;
  244. options.ignoreHiddenFiles = true;
  245. options.nonShib = false;
  246. options.restartTimes = 3;
  247. ClientProxy clientProxy;
  248. parseOptions( app.arguments(), &options );
  249. AccountPtr account = Account::create();
  250. if( !account ) {
  251. qFatal("Could not initialize account!");
  252. return EXIT_FAILURE;
  253. }
  254. // check if the webDAV path was added to the url and append if not.
  255. if(!options.target_url.endsWith("/")) {
  256. options.target_url.append("/");
  257. }
  258. if( options.nonShib ) {
  259. account->setNonShib(true);
  260. }
  261. if(!options.davPath.isEmpty()) {
  262. account->setDavPath( options.davPath );
  263. }
  264. if( !options.target_url.contains( account->davPath() )) {
  265. options.target_url.append(account->davPath());
  266. }
  267. if (options.target_url.startsWith("http"))
  268. options.target_url.replace(0, 4, "owncloud");
  269. QUrl url = QUrl::fromUserInput(options.target_url);
  270. // Order of retrieval attempt (later attempts override earlier ones):
  271. // 1. From URL
  272. // 2. From options
  273. // 3. From netrc (if enabled)
  274. // 4. From prompt (if interactive)
  275. QString user = url.userName();
  276. QString password = url.password();
  277. if (!options.user.isEmpty()) {
  278. user = options.user;
  279. }
  280. if (!options.password.isEmpty()) {
  281. password = options.password;
  282. }
  283. if (options.useNetrc) {
  284. NetrcParser parser;
  285. if (parser.parse()) {
  286. NetrcParser::LoginPair pair = parser.find(url.host());
  287. user = pair.first;
  288. password = pair.second;
  289. }
  290. }
  291. if (options.interactive) {
  292. if (user.isEmpty()) {
  293. std::cout << "Please enter user name: ";
  294. std::string s;
  295. std::getline(std::cin, s);
  296. user = QString::fromStdString(s);
  297. }
  298. if (password.isEmpty()) {
  299. password = queryPassword(user);
  300. }
  301. }
  302. // ### ensure URL is free of credentials
  303. if (url.userName().isEmpty()) {
  304. url.setUserName(user);
  305. }
  306. if (url.password().isEmpty()) {
  307. url.setPassword(password);
  308. }
  309. // take the unmodified url to pass to csync_create()
  310. QByteArray remUrl = options.target_url.toUtf8();
  311. // Find the folder and the original owncloud url
  312. QStringList splitted = url.path().split(account->davPath());
  313. url.setPath(splitted.value(0));
  314. url.setScheme(url.scheme().replace("owncloud", "http"));
  315. QString folder = splitted.value(1);
  316. SimpleSslErrorHandler *sslErrorHandler = new SimpleSslErrorHandler;
  317. HttpCredentialsText *cred = new HttpCredentialsText(user, password);
  318. if( options.trustSSL ) {
  319. cred->setSSLTrusted(true);
  320. }
  321. account->setUrl(url);
  322. account->setCredentials(cred);
  323. account->setSslErrorHandler(sslErrorHandler);
  324. // much lower age than the default since this utility is usually made to be run right after a change in the tests
  325. SyncEngine::minimumFileAgeForUpload = 0;
  326. int restartCount = 0;
  327. restart_sync:
  328. csync_set_log_level(options.silent ? 1 : 11);
  329. opts = &options;
  330. if( !options.proxy.isNull() ) {
  331. QString host;
  332. int port = 0;
  333. bool ok;
  334. QStringList pList = options.proxy.split(':');
  335. if(pList.count() == 3) {
  336. // http: //192.168.178.23 : 8080
  337. // 0 1 2
  338. host = pList.at(1);
  339. if( host.startsWith("//") ) host.remove(0, 2);
  340. port = pList.at(2).toInt(&ok);
  341. QNetworkProxyFactory::setUseSystemConfiguration(false);
  342. QNetworkProxy::setApplicationProxy(QNetworkProxy(QNetworkProxy::HttpProxy, host, port));
  343. }
  344. } else {
  345. clientProxy.setupQtProxyFromConfig();
  346. QString url( options.target_url );
  347. if( url.startsWith("owncloud")) {
  348. url.remove(0, 8);
  349. url = QString("http%1").arg(url);
  350. }
  351. }
  352. QStringList selectiveSyncList;
  353. if (!options.unsyncedfolders.isEmpty()) {
  354. QFile f(options.unsyncedfolders);
  355. if (!f.open(QFile::ReadOnly)) {
  356. qCritical() << "Could not open file containing the list of unsynced folders: " << options.unsyncedfolders;
  357. } else {
  358. // filter out empty lines and comments
  359. selectiveSyncList = QString::fromUtf8(f.readAll()).split('\n').filter(QRegExp("\\S+")).filter(QRegExp("^[^#]"));
  360. for (int i = 0; i < selectiveSyncList.count(); ++i) {
  361. if (!selectiveSyncList.at(i).endsWith(QLatin1Char('/'))) {
  362. selectiveSyncList[i].append(QLatin1Char('/'));
  363. }
  364. }
  365. }
  366. }
  367. Cmd cmd;
  368. SyncJournalDb db(options.source_dir);
  369. if (!selectiveSyncList.empty()) {
  370. selectiveSyncFixup(&db, selectiveSyncList);
  371. }
  372. SyncEngine engine(account, options.source_dir, QUrl(options.target_url), folder, &db);
  373. engine.setIgnoreHiddenFiles(options.ignoreHiddenFiles);
  374. QObject::connect(&engine, SIGNAL(finished(bool)), &app, SLOT(quit()));
  375. QObject::connect(&engine, SIGNAL(transmissionProgress(ProgressInfo)), &cmd, SLOT(transmissionProgressSlot()));
  376. // Exclude lists
  377. engine.excludedFiles().addExcludeFilePath(ConfigFile::excludeFileFromSystem());
  378. if( QFile::exists(options.exclude) )
  379. engine.excludedFiles().addExcludeFilePath(options.exclude);
  380. if (!engine.excludedFiles().reloadExcludes()) {
  381. // Always make sure at least one list has been loaded
  382. qFatal("Cannot load system exclude list or list supplied via --exclude");
  383. return EXIT_FAILURE;
  384. }
  385. // Have to be done async, else, an error before exec() does not terminate the event loop.
  386. QMetaObject::invokeMethod(&engine, "startSync", Qt::QueuedConnection);
  387. app.exec();
  388. if (engine.isAnotherSyncNeeded()) {
  389. if (restartCount < options.restartTimes) {
  390. restartCount++;
  391. qDebug() << "Restarting Sync, because another sync is needed" << restartCount;
  392. goto restart_sync;
  393. }
  394. qWarning() << "Another sync is needed, but not done because restart count is exceeded" << restartCount;
  395. }
  396. return 0;
  397. }