/************************************************************************* * UrBackup - Client/Server backup system * Copyright (C) 2011-2014 Martin Raiber * * This program 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. * * This program 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 this program. If not, see . **************************************************************************/ #include "server_channel.h" #include "../Interface/Server.h" #include "../Interface/Database.h" #include "../Interface/Query.h" #include "database.h" #include "server_get.h" #include "../stringtools.h" #include "../urbackupcommon/os_functions.h" #include "../fsimageplugin/IFSImageFactory.h" #include "../fsimageplugin/IVHDFile.h" #include "server_status.h" #include "server_settings.h" #include "../urbackupcommon/capa_bits.h" #include "serverinterface/helper.h" #include "serverinterface/login.h" #include #include #include const unsigned short serviceport=35623; extern IFSImageFactory *image_fak; namespace { IDatabase* getDatabase(void) { Helper helper(Server->getThreadID(), NULL, NULL); return helper.getDatabase(); } bool needs_login(void) { db_results res=getDatabase()->Read("SELECT count(*) AS c FROM settings_db.si_users"); if(watoi(res[0][L"c"])>0) { return true; } else { return false; } } } ServerChannelThread::ServerChannelThread(BackupServerGet *pServer_get, int clientid, bool internet_mode, const std::string& identity) : server_get(pServer_get), clientid(clientid), settings(NULL), internet_mode(internet_mode), identity(identity) { do_exit=false; mutex=Server->createMutex(); input=NULL; if(clientid!=-1) { combat_mode=false; } else { combat_mode=true; } tcpstack.setAddChecksum(internet_mode); img_id_offset=Server->getRandomNumber()%((unsigned int)INT_MAX/2); } ServerChannelThread::~ServerChannelThread(void) { delete settings; Server->destroy(mutex); } void ServerChannelThread::operator()(void) { int64 lastpingtime=0; lasttime=0; settings=new ServerSettings(Server->getDatabase(Server->getThreadID(), URBACKUPDB_SERVER), clientid); while(do_exit==false) { if(input==NULL) { IPipe *np=server_get->getClientCommandConnection(10000, &client_addr); if(np==NULL) { Server->Log("Connecting Channel to ClientService failed - CONNECT error -55", LL_DEBUG); Server->wait(10000); } else { { IScopedLock lock(mutex); input=np; } if(combat_mode) { tcpstack.Send(input, identity+"CHANNEL"); } else { tcpstack.Send(input, identity+"1CHANNEL capa="+nconvert(constructCapabilities())); } lasttime=Server->getTimeMS(); lastpingtime=lasttime; } } else { if(Server->getTimeMS()-lasttime>180000) { Server->Log("Resetting channel because of timeout.", LL_DEBUG); IScopedLock lock(mutex); Server->destroy(input); input=NULL; tcpstack.reset(); } if(input!=NULL) { std::string ret; size_t rc=input->Read(&ret, 80000); if(rc>0) { tcpstack.AddData((char*)ret.c_str(), ret.size()); size_t packetsize; char *pck=tcpstack.getPacket(&packetsize); if(pck!=NULL && packetsize>0) { ret=pck; delete [] pck; lasttime=Server->getTimeMS(); std::string r=processMsg(ret); if(!r.empty()) tcpstack.Send(input, r); } bool was_updated; settings->getSettings(&was_updated); if(input!=NULL && was_updated && !combat_mode) { IScopedLock lock(mutex); Server->destroy(input); input=NULL; tcpstack.reset(); } } else if(rc==0) { if(input->hasError()) { Server->Log("Lost channel connection to client. has_error=true", LL_DEBUG); IScopedLock lock(mutex); Server->destroy(input); input=NULL; tcpstack.reset(); Server->wait(1000); } else { Server->Log("Lost channel connection to client. has_error=false", LL_DEBUG); Server->wait(1000); } } } } } if(input!=NULL) { Server->destroy(input); } } void ServerChannelThread::doExit(void) { IScopedLock lock(mutex); do_exit=true; if(input!=NULL) { input->shutdown(); } } std::string ServerChannelThread::processMsg(const std::string &msg) { if(msg=="ERR") { combat_mode=true; } else if(msg=="START BACKUP INCR") { server_get->sendToPipe("START BACKUP INCR"); } else if(msg=="START BACKUP FULL") { server_get->sendToPipe("START BACKUP FULL"); } else if(msg=="PING") { return "PONG"; } else if(msg=="UPDATE SETTINGS") { server_get->sendToPipe("UPDATE SETTINGS"); } else if(msg=="START IMAGE FULL") { server_get->sendToPipe("START IMAGE FULL"); } else if(msg=="START IMAGE INCR") { server_get->sendToPipe("START IMAGE INCR"); } else if(msg.find("LOGIN ")==0 && !internet_mode) { std::string s_params=msg.substr(6); str_map params; ParseParamStrHttp(s_params, ¶ms); LOGIN(params); } else if(msg.find("SALT ")==0 && !internet_mode) { std::string s_params=msg.substr(5); str_map params; ParseParamStrHttp(s_params, ¶ms); SALT(params); } else if(msg=="GET BACKUPCLIENTS" && !internet_mode && hasDownloadImageRights() ) { GET_BACKUPCLIENTS(); } else if(msg.find("GET BACKUPIMAGES ")==0 && !internet_mode && hasDownloadImageRights()) { std::wstring name=Server->ConvertToUnicode(msg.substr(17)); GET_BACKUPIMAGES(name); } else if(msg.find("DOWNLOAD IMAGE ")==0 && !internet_mode && hasDownloadImageRights()) { std::string s_params=msg.substr(15); str_map params; ParseParamStrHttp(s_params, ¶ms); DOWNLOAD_IMAGE(params); } else { IScopedLock lock(mutex); Server->destroy(input); input=NULL; tcpstack.reset(); Server->wait(60000); } return ""; } int ServerChannelThread::constructCapabilities(void) { int capa=0; SSettings *cs=settings->getSettings(); if(!cs->allow_overwrite) capa|=DONT_SHOW_SETTINGS; if(!cs->allow_pause) capa|=DONT_ALLOW_PAUSE; if(!cs->allow_starting_full_file_backups && !cs->allow_starting_incr_file_backups) capa|=DONT_ALLOW_STARTING_FILE_BACKUPS; if(!cs->allow_starting_full_image_backups && !cs->allow_starting_incr_image_backups) capa|=DONT_ALLOW_STARTING_IMAGE_BACKUPS; if(!cs->allow_config_paths) capa|=DONT_ALLOW_CONFIG_PATHS; if(!cs->allow_log_view) capa|=DONT_SHOW_LOGS; if(cs->no_images || (internet_mode && !cs->internet_image_backups)) capa|=DONT_DO_IMAGE_BACKUPS; if(internet_mode && !cs->internet_full_file_backups) capa|=DONT_DO_FULL_FILE_BACKUPS; if(!cs->allow_starting_full_file_backups) capa|=DONT_ALLOW_STARTING_FULL_FILE_BACKUPS; if(!cs->allow_starting_incr_file_backups) capa|=DONT_ALLOW_STARTING_INCR_FILE_BACKUPS; if(!cs->allow_starting_full_image_backups) capa|=DONT_ALLOW_STARTING_FULL_IMAGE_BACKUPS; if(!cs->allow_starting_incr_image_backups) capa|=DONT_ALLOW_STARTING_INCR_IMAGE_BACKUPS; if(!cs->allow_tray_exit) capa|=DONT_ALLOW_EXIT_TRAY_ICON; return capa; } void ServerChannelThread::LOGIN(str_map& params) { str_nmap PARAMS; str_map GET; if(!session.empty()) { GET[L"ses"]=session; } Helper helper(Server->getThreadID(), &GET, &PARAMS); if(needs_login()) { if(session.empty()) { session=helper.generateSession(L"anonymous"); GET[L"ses"]=session; helper.update(Server->getThreadID(), &GET, &PARAMS); } helper.getSession()->mStr[L"rnd"]=widen(salt); int user_id; if(helper.checkPassword(params[L"username"], params[L"password"], &user_id)) { helper.getSession()->id=user_id; PARAMS["REMOTE_ADDR"]=client_addr; logLogin(helper, PARAMS, params[L"username"], LoginMethod_RestoreCD); tcpstack.Send(input, "ok"); } else { helper.getSession()->id=-1; tcpstack.Send(input, "err"); } } else { logLogin(helper, PARAMS, L"anonymous", LoginMethod_RestoreCD); tcpstack.Send(input, "ok"); } } void ServerChannelThread::SALT(str_map& params) { if(needs_login()) { std::wstring username=params[L"username"]; if(username.empty()) { tcpstack.Send(input, "err: username empty"); return; } str_nmap PARAMS; str_map GET; if(!session.empty()) { GET[L"ses"]=session; } Helper helper(Server->getThreadID(), &GET, &PARAMS); IQuery * q = helper.getDatabase()->Prepare("SELECT salt FROM settings_db.si_users WHERE name=?"); q->Bind(username); db_results res=q->Read(); if(res.empty()) { tcpstack.Send(input, "err: user not found"); } else { salt=ServerSettings::generateRandomAuthKey(); tcpstack.Send(input, "ok;"+Server->ConvertToUTF8(res[0][L"salt"])+";"+salt); } } else { tcpstack.Send(input, "ok"); } } bool ServerChannelThread::hasDownloadImageRights() { if(!needs_login()) { all_client_rights=true; return true; } str_map GET; str_nmap PARAMS; GET[L"ses"]=session; Helper helper(Server->getThreadID(), &GET, &PARAMS); if(helper.getSession()->id==-1) { all_client_rights=false; return false; } client_right_ids=helper.clientRights("download_image", all_client_rights); if(all_client_rights) { return true; } return !client_right_ids.empty(); } void ServerChannelThread::GET_BACKUPCLIENTS(void) { IDatabase *db=Server->getDatabase(Server->getThreadID(), URBACKUPDB_SERVER); std::string t_where=""; if(!all_client_rights) { t_where=" WHERE 1=2"; for(size_t i=0;iRead("SELECT name,id FROM clients"+t_where); std::string clients; for(size_t i=0;iConvertToUTF8(res[i][L"id"])+"|"+Server->ConvertToUTF8(res[i][L"name"])+"\n"; } tcpstack.Send(input, clients); ServerStatus::updateActive(); } void ServerChannelThread::GET_BACKUPIMAGES(const std::wstring& clientname) { IDatabase *db=Server->getDatabase(Server->getThreadID(), URBACKUPDB_SERVER); //TODO language IQuery *q=db->Prepare("SELECT backupid AS id, strftime('%s', backuptime) AS timestamp, strftime('%Y-%m-%d %H:%M',backuptime) AS backuptime, letter, clientid FROM ((SELECT id AS backupid, clientid, backuptime, letter, complete FROM backup_images) c INNER JOIN (SELECT id FROM clients WHERE name=?) b ON c.clientid=b.id) a WHERE a.complete=1 AND length(a.letter)<=2 ORDER BY backuptime DESC"); q->Bind(clientname); db_results res=q->Read(); for(size_t i=0;idestroyAllQueries(); return; } } std::string r; q=db->Prepare("SELECT backupid AS id,strftime('%s', backuptime) AS timestamp, strftime('%Y-%m-%d %H:%M',backuptime) AS backuptime FROM ((SELECT id AS backupid, clientid, backuptime, letter, complete FROM backup_images) c INNER JOIN (SELECT assoc_id, img_id FROM assoc_images WHERE img_id=?) b ON backupid=b.assoc_id) a WHERE a.complete=1 ORDER BY backuptime DESC"); for(size_t i=0;iConvertToUTF8(res[i][L"timestamp"])+"|"+Server->ConvertToUTF8(res[i][L"backuptime"])+"|"+Server->ConvertToUTF8(res[i][L"letter"])+"\n"; q->Bind(watoi(res[i][L"id"])); db_results res2=q->Read(); q->Reset(); for(size_t j=0;jConvertToUTF8(res2[j][L"timestamp"])+"|"+Server->ConvertToUTF8(res2[j][L"backuptime"])+"\n"; } } tcpstack.Send(input, r); db->destroyAllQueries(); ServerStatus::updateActive(); } void ServerChannelThread::DOWNLOAD_IMAGE(str_map& params) { int img_id=watoi(params[L"img_id"])-img_id_offset; IDatabase *db=Server->getDatabase(Server->getThreadID(), URBACKUPDB_SERVER); IQuery *q=db->Prepare("SELECT path, version, clientid FROM backup_images WHERE id=? AND strftime('%s', backuptime)=?"); q->Bind(img_id); q->Bind(params[L"time"]); db_results res=q->Read(); if(res.empty()) { _i64 r=-1; input->Write((char*)&r, sizeof(_i64)); } else { if( !all_client_rights && std::find(client_right_ids.begin(), client_right_ids.end(), watoi(res[0][L"clientid"]))==client_right_ids.end()) { _i64 r=-1; input->Write((char*)&r, sizeof(_i64)); return; } int img_version=watoi(res[0][L"version"]); if(params[L"mbr"]==L"true") { IFile *f=Server->openFile(os_file_prefix(res[0][L"path"]+L".mbr"), MODE_READ); if(f==NULL) { _i64 r=-1; input->Write((char*)&r, sizeof(_i64)); } else { _i64 r=f->Size(); input->Write((char*)&r, sizeof(_i64)); char buf[4096]; _u32 rc; do { rc=f->Read(buf, 4096); if(rc>0) { bool b=input->Write(buf, rc); if(!b) { Server->Log("Writing to output pipe failed processMsg-2", LL_ERROR); Server->destroy(f); db->destroyAllQueries(); Server->destroy(input); input=NULL; return; } lasttime=Server->getTimeMS(); } } while(rc>0); Server->destroy(f); db->destroyAllQueries(); return; } } uint64 offset=0; str_map::iterator it1=params.find(L"offset"); if(it1!=params.end()) offset=(uint64)os_atoi64(wnarrow(it1->second)); ServerStatus::updateActive(); lasttime=Server->getTimeMS(); IVHDFile *vhdfile=image_fak->createVHDFile(res[0][L"path"], true, 0); if(!vhdfile->isOpen()) { _i64 r=-1; input->Write((char*)&r, sizeof(_i64)); } else { int skip=1024*512; if(img_version==0) skip=512*512; _i64 r=(_i64)vhdfile->getSize()-skip; input->Write((char*)&r, sizeof(_i64)); unsigned int blocksize=vhdfile->getBlocksize(); char buffer[4096]; size_t read; uint64 currpos=offset; _i64 currblock=(currpos+skip)%blocksize; vhdfile->Seek(skip); /*vhdfile->Read(buffer, 512, read); if(read!=512) { Server->Log("Error: Could not read 512 bytes", LL_ERROR); image_fak->destroyVHDFile(vhdfile); db->destroyAllQueries(); return ""; } input->Write(buffer, (_u32)read);*/ int64 last_update_time=Server->getTimeMS(); bool is_ok=true; do { if(vhdfile->has_sector()) { is_ok=vhdfile->Read(buffer, 4096, read); if(read<4096) { Server->Log("Padding zero bytes...", LL_WARNING); memset(&buffer[read], 0, 4096-read); read=4096; } input->Write((char*)&currpos, sizeof(uint64)); bool b=input->Write(buffer, (_u32)read); if(!b) { Server->Log("Writing to output pipe failed processMsg-1", LL_ERROR); image_fak->destroyVHDFile(vhdfile); db->destroyAllQueries(); Server->destroy(input); input=NULL; return; } lasttime=Server->getTimeMS(); } else { if(Server->getTimeMS()-lasttime>30000) { input->Write((char*)&currpos, sizeof(uint64)); memset(buffer, 0, 4096); input->Write(buffer, (_u32)4096); lasttime=Server->getTimeMS(); } read=4096; vhdfile->Seek(skip+currpos+4096); } currpos+=read; if(Server->getTimeMS()-last_update_time>60000) { last_update_time=Server->getTimeMS(); ServerStatus::updateActive(); } } while( is_ok && (_i64)currpos=r) { input->Write((char*)&currpos, sizeof(uint64)); } } image_fak->destroyVHDFile(vhdfile); } db->destroyAllQueries(); }