/*
This file is part of Bolixo.
Bolixo 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.
Bolixo 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 Bolixo. If not, see .
*/
/*
Main server for the project. Hides database logic and controls security/access
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "filesystem.h"
#include "helper.h"
#include
#include
#include
#include
#include
#include "bolixo.h"
#include "bolixo.m"
#include
#define INSTRUMENT_DONOTOPEN
#include "instrument.h"
#include
#include "documentd_req.h"
#include "websocket-client.h"
using namespace std;
inline long long bod_getnow()
{
#ifdef INSTRUMENT
return fdpass_getnow();
#else
return 0;
#endif
}
static DEBUG_KEY D_PROTO ("proto","Protocol information");
using HANDLE_WSS_P = shared_ptr;
enum CONNECT_TYPE { TYPE_NONE, TYPE_CONTROL, TYPE_CLIENT, TYPE_ADMIN, TYPE_DOCUMENTD, TYPE_WEBSOCKET };
struct HANDLE_INFO: public ARRAY_OBJ{
CONNECT_TYPE type = TYPE_NONE;
int no=-1;
std::string host;
REQUEST_INFO req;
std::string userid;
int documentd_no = -1; // handle used for waitevent
// This handle is TYPE_DOCUMENTD or TYPE_WEBSOCKET
HANDLE_WSS_P wss=nullptr;
HANDLE_INFO(){
}
};
struct DIRENTRY{
std::string eventdate;
std::string modified;
VIEWED_STATUS viewed;
ENTRY_TYPE type;
FILE_TYPE file_type;
std::string owner;
std::string modifiedby;
std::string members;
std::string listmode;
std::string title;
unsigned size;
bool islarge;
DIRENTRY (const char *_eventdate, const char *_modified, ENTRY_TYPE _type, FILE_TYPE _file_type, bool _islarge, const char *_owner,
const char *_modifiedby,
const char *_members, const char *_listmode, unsigned _size, const char *_title, VIEWED_STATUS _viewed){
eventdate = _eventdate;
modified = _modified;
type = _type;
islarge = _islarge;
file_type = _file_type;
owner = _owner;
modifiedby = _modifiedby;
members = _members;
listmode = _listmode;
size = _size;
title = _title;
viewed = _viewed;
}
DIRENTRY(){
type = ENTRY_DELETED;
size = 0;
}
};
#define bod_control_status_NOTNEED
#define bod_control_quit_NOTNEED
#define bod_control_debug_NOTNEED
#define bod_control_debugfile_NOTNEED
#define bod_control_helptest_NOTNEED
#define bod_control_publishemail_NOTNEED
#define bod_control_help_connect_NOTNEED
#define bod_control_nodelogin_NOTNEED
#define bod_control_nodelogout_NOTNEED
#define bod_control_instrument_NOTNEED
#define bod_control_keepmsgs_NOTNEED
#define bod_control_set_admin_session_NOTNEED
#define bod_control_savegame_NOTNEED
#define bod_control_slowplaystep_NOTNEED
#include "proto/bod_control.protoch"
#define bolixoapi_test_NOTNEED
#define bolixoapi_registernode_NOTNEED
#define bolixoapi_publish_NOTNEED
#define bolixoapi_pub_search_NOTNEED
#define bolixoapi_readfile_NOTNEED
#include "proto/bolixod_client.protodef"
#include "proto/bolixoapi.protoch"
#define webapi_test_NOTNEED
#define webapi_login_NOTNEED
#define webapi_set_access_NOTNEED
#define webapi_verifysign_NOTNEED
#define webapi_list_inboxes_NOTNEED
#define webapi_list_msgs_NOTNEED
#define webapi_sendmsg_NOTNEED
#define webapi_sendmsg_project_NOTNEED
#define webapi_replymsg_NOTNEED
#define webapi_replymsg_project_NOTNEED
#define webapi_sendattach_NOTNEED
#define webapi_sendtalk_file_NOTNEED
#define webapi_list_talk_NOTNEED
#define webapi_public_listdir_NOTNEED
#define webapi_public_readfile_NOTNEED
#define webapi_public_list_talk_NOTNEED
#define webapi_config_read_NOTNEED
#define webapi_config_write_NOTNEED
#define webapi_contact_list_NOTNEED
#define webapi_list_lists_NOTNEED
#define webapi_list_groups_NOTNEED
#define webapi_list_contacts_NOTNEED
#define webapi_list_members_NOTNEED
#include "proto/webapi.protoch"
#include "proto/bod_control.protoh"
#define bod_client_rep_waitevent_NEEDED
#include "proto/bod_client.protoh"
#include "proto/bod_admin.protoh"
#include "proto/bo-writed_client.protoch"
#define documentd_client_startgame_NOTNEED
#define documentd_client_loadmore_NOTNEED // For now, incomplete
#define documentd_client_savemore_NOTNEED // For now, incomplete
#define documentd_client_waitevent_ASYNC
#include "proto/documentd_client.protoch"
#define bo_sessiond_client_getsessioninfovars_NOTNEED
#define bo_sessiond_client_getsessioninfovars_v2_NOTNEED
#define bo_sessiond_client_setvar_NOTNEED
#define bo_sessiond_client_delnotify_NOTNEED
#define bo_sessiond_client_waitevent_NOTNEED
#include "proto/bo-sessiond_client.protoch"
struct SESSION_CACHE{
time_t start;
string name;
unsigned userid;
bool is_admin;
string lang;
string timezone;
SESSION_CACHE(){
userid = 0;
is_admin = false;
start = 0;
}
SESSION_CACHE(const char *_name, unsigned _userid, bool _is_admin, const char *_lang, const char *_timezone){
start = time(NULL);
name = _name;
userid = _userid;
is_admin = _is_admin;
lang = _lang;
timezone = _timezone;
}
};
static map sessions;
/*
Erase old session (older than 10 seconds)
*/
static void bod_sessions_clean()
{
time_t t = time(NULL)-10;
for (auto it=sessions.begin(); it != sessions.end();){
if (it->second.start < t){
it = sessions.erase(it);
}else{
it++;
}
}
}
/*
Erase one session from the cache
*/
static void bod_sessions_erase(PARAM_STRING session)
{
auto it = sessions.find(session.ptr);
if (it != sessions.end()){
sessions.erase(it);
}
}
/*
Erase one session from the case and tell all other bod process to do the same
*/
static void bod_sessions_erase(PARAM_STRING session, const char *control)
{
glocal session;
glocal control;
("/var/run/blackhole");
// Avoid talking to itself
if (is_start_any_of(basename,NONEED,"bod-") && strcmp(glocal.control,path)!=0){
//tlmp_warning ("erase control=%s path=%s\n",glocal.control,path);
CONNECT_INFO con;
con.port = path;
(con,glocal.session.ptr);
}
bod_sessions_erase (session);
}
static string user_timezone="system"; // timezone of the current user
static string current_lang;
/*
Get the userid associated with the sessionid
*/
static unsigned trli_getsessionuser (CONNECT_INFO &con, const char *sessionid, bool &is_admin, string &name, string &msg)
{
unsigned userid;
auto s = sessions.find(sessionid);
if (s != sessions.end()){
userid = s->second.userid;
name = s->second.name;
is_admin = s->second.is_admin;
user_timezone = s->second.timezone;
current_lang = s->second.lang;
translat_selectlang(current_lang.c_str());
}else{
glocal const char *sessionid = sessionid;
glocal unsigned userid = 0;
glocal bool is_admin = false;
glocal name;
//long long start = fdpass_getnow();
//for (int i=0; i<10000; i++){
(con,sessionid);
// Node session are invalid here
if (success && userid != (unsigned)-1){
glocal.userid = userid;
glocal.is_admin = admin;
glocal.name = name;
current_lang = lang;
translat_selectlang(current_lang.c_str());
if (timezone[0] == '\0') timezone = "system";
user_timezone = timezone;
sessions[glocal.sessionid] = SESSION_CACHE(name,userid,admin,lang,timezone);
}
//}
//long long end = fdpass_getnow();
//printf ("exec time = %lf\n",(end-start)/1000000.0);
is_admin = glocal.is_admin;
userid = glocal.userid;
}
if (userid == 0) msg = MSG_U(E_IVLDSESSION,"Invalid session");
return userid;
}
static int bod_findentry (CONNECT_INFO &con, const char *sessionid, PARAM_STRING name, ENTRY &entry, const char *threshold, string &username)
{
int ret = -1;
entry.userid = trli_getsessionuser(con,sessionid,entry.is_admin,username,entry.msg);
if (entry.userid != 0){
ret = fs_findentry(name,entry,true,threshold);
}
return ret;
}
static int bod_findentry (CONNECT_INFO &con, const char *sessionid, PARAM_STRING name, ENTRY &entry, const char *threshold)
{
string username;
return bod_findentry(con,sessionid,name,entry,threshold,username);
}
/*
Identify the userid who will perform some group administration.
Normally, user are only allowed to work on list they own.
But administrator can work on behalf of another user. The parameter "owner" is the other user.
For normal user, this parameter is ignored.
Return 0 if not a valid userid (sessionid is invalid)
*/
static unsigned bo_writed_get_group_owner (CONNECT_INFO &con, const char *sessionid, const char *owner, string &username, string &msg)
{
bool is_admin;
glocal unsigned userid = trli_getsessionuser (con,sessionid,is_admin,username,msg);
if (glocal.userid != 0 && owner[0] != '\0'){
if (is_admin){
("select userid from id2name where name='%s'",owner);
glocal.userid = atoi(row[0]);
glocal.userid = 0;
username = owner;
if (glocal.userid == 0) msg = MSG_R(E_UNKNOWNUSER);
}else{
glocal.userid = 0;
msg = MSG_U(E_ONLYADMIN,"Only administrator may impersonate another user");
}
}
return glocal.userid;
}
static unsigned bo_writed_get_group_owner (CONNECT_INFO &con, const char *sessionid, const char *owner, string &msg)
{
string username;
return bo_writed_get_group_owner (con,sessionid,owner,username,msg);
}
/*
Keeps only a slice of the vector
*/
template static void bod_trim (vector &tb, unsigned offset, unsigned nb)
{
if (offset >= tb.size()){
tb.clear();
}else if (offset + nb >= tb.size()){
tb.erase (tb.begin(),tb.begin()+offset);
}else{
if (offset > 0){
tb.erase (tb.begin(),tb.begin()+offset);
}
if (nb < tb.size()){
tb.erase (tb.begin()+nb,tb.end());
}
}
}
/*
Check that public access is enable for one account.
Translate the relative_path into absolute path by using the username and the public_dir
Return the user id.
Return -1 if any problem.
relative_path may be NULL. It means we do not wish to convert the path to absolute.
*/
static int bod_checkpublic(const char *username, const char *relative_path, string &msg, string &abspath, bool &pubdir)
{
glocal string *msg = &msg;
glocal string public_dir;
glocal int ret = -1;
pubdir = false;
("select public_view,public_dir,id2name.userid from id2name"
" join config on config.userid=id2name.userid"
" where id2name.name='%s'",username);
(*glocal.msg) = "no user";
bool public_view = atoi(row[0]);
if (!public_view){
(*glocal.msg) = MSG_U(E_NOPUBLICACC,"no public data available");
}else{
glocal.public_dir = row[1];
glocal.ret = atoi(row[2]);
}
pubdir = glocal.public_dir.size() > 0;
if (glocal.ret != -1 && relative_path != NULL){
if (relative_path[0] == '/') relative_path++;
if (strncmp(relative_path,"project/",8)==0){
relative_path += 7;
if (is_any_of(relative_path,"/mini-photo.jpg","/photo.jpg")
|| glocal.public_dir == "/"){
abspath = string_f("/projects/%s/public",username);
}else{
abspath = string_f("/projects/%s/public/%s",username,glocal.public_dir.c_str());
}
if (strcmp(relative_path,"/") !=0){
if (relative_path[0] == '/'){
abspath += relative_path;
}else{
abspath += string_f ("/%s",relative_path);
}
}
}else if (strncmp(relative_path,"msg/",4)==0){
relative_path += 4;
abspath = string_f("/msgs/%s/short-inbox/public/%s",username,relative_path);
}else{
msg = MSG_U(E_IVLDPUBPREFIX,"Invalid prefix for public access");
glocal.ret = -1;
}
}
return glocal.ret;
}
static int bod_checkpublic(const char *username, const char *relative_path, string &msg, string &abspath)
{
bool pubdir;
return bod_checkpublic(username,relative_path,msg,abspath,pubdir);
}
static int bod_checkpublic(const char *username, string &msg, bool &pubdir)
{
string abspath;
return bod_checkpublic (username,NULL,msg,abspath,pubdir);
}
static int bod_checkpublic(const char *username, string &msg)
{
string abspath;
bool pubdir;
return bod_checkpublic (username,NULL,msg,abspath,pubdir);
}
#define UTF8_ONE_BYTE_MASK 0b10000000
#define UTF8_ONE_BYTE_COUNT 0
#define UTF8_TWO_BYTE_MASK 0b11100000
#define UTF8_TWO_BYTE_COUNT 0b11000000
#define UTF8_THREE_BYTE_MASK 0b11110000
#define UTF8_THREE_BYTE_COUNT 0b11100000
#define UTF8_FOUR_BYTE_MASK 0b11111000
#define UTF8_FOUR_BYTE_COUNT 0b11110000
// This one could use a better name, I just don't know a better one (yet?)
#define UTF8_OTHER_MASK 0b00111111
static size_t utf8_codepoint_size(uint8_t text)
{
if((text & UTF8_ONE_BYTE_MASK) == UTF8_ONE_BYTE_COUNT) {
return 1;
}
if((text & UTF8_TWO_BYTE_MASK) == UTF8_TWO_BYTE_COUNT) {
return 2;
}
if((text & UTF8_THREE_BYTE_MASK) == UTF8_THREE_BYTE_COUNT) {
return 3;
}
return 4;
}
/*
Extract the first line of a text.
Handle long lines. Assumes that each chunk of 80 characters in a line accounts for one line.
But long lines may hold a URL. This has to be preserved.
*/
static string bod_first_lines (const char *txt)
{
const unsigned nblines = 8;
const char *pt = txt;
unsigned noline = 0;
const char *start = txt;
while (*pt != '\0' && noline < nblines){
// We walk over one line until the \n or \0.
// Using UTF8 parsing, we compute the len of the line.
// start80s will record the start of every 80 UTF8 characters chunk.
unsigned lenline = 0;
vector start80s;
while (*pt != '\0' && *pt != '\n'){
if (lenline%80==0) start80s.push_back(pt);
lenline++;
size_t sizecar = utf8_codepoint_size(*pt);
while (sizecar > 0 && *pt != '\0'){
pt++;
sizecar--;
}
}
if (*pt == '\n') pt++;
{
unsigned add_lines = start80s.size();
noline += add_lines;
if (noline > nblines && add_lines > 0){
while (noline > nblines && add_lines > 0){
add_lines--;
noline--;
}
pt = start80s[add_lines];
// Check for URLs
const char *url = start;
while (url < pt){
const char *skip;
if (is_start_any_ofnc(url,skip,"http://","https://")){
// Find the end of the URL
if (*skip == '"') skip++;
while (*skip > ' ' && is_not_in(*skip,'"','>') && *skip > ' ') skip++;
if (skip > pt){
// The URL crossed the limit, so we extend it.
pt=skip;
break;
}else{
url = skip;
// Look for another URL.
}
}else{
url++;
}
}
break;
}
start = pt;
}
}
if (*pt == '\0'){
return txt;
}else{
return string(txt,pt-txt);
}
}
static VIEWED_STATUS bod_readviewed(const char *mark_modified, const char *modified)
{
VIEWED_STATUS ret = VIEWED_NEW;
// If there is no entry for the document or message in table marks, then it is new
if (mark_modified != NULL){
if (strcmp(mark_modified,modified)==0){
// Document has not changed since it was viewed
ret = VIEWED_OK;
}else{
ret = VIEWED_MODIFIED;
}
}
return ret;
}
#if 0
template
static bool msg_compare (const T &s1, const T &s2)
{
return s2.eventdate < s1.eventdate;
}
#endif
static unsigned keepdays = 200;
/*
Remove new messages up to firstseen from the list.
*/
template
static unsigned bod_firstseen (vector &msgs, const char *firstseen)
{
unsigned nbnew = 0;
if (firstseen[0] != '\0'){
unsigned count = 0;
for (auto &m:msgs){
if (m.uuid == firstseen) break;
count++;
}
if (count > 0 && count != msgs.size()){
msgs.erase(msgs.begin(),msgs.begin()+count);
nbnew = count;
}
}
return nbnew;
}
static void bod_list_talk (
unsigned userid, // We are reading for this user, useful for read marks
vector &msgs,
unsigned ownerid,
const char *groupowner,
const char *groupname,
const vector &fulltext,
unsigned offset,
unsigned nb,
const char *firstseen,
bool &deletes,
unsigned &total,
unsigned &nbnew,
string &msg)
{
glocal unsigned nb = nb;
glocal map mp;
glocal bool deletes = false;
glocal set fulltext;
for (auto f:fulltext) glocal.fulltext.insert(f);
int dirid = fs_find_short_inbox (ownerid,groupowner,groupname,msg);
if (dirid != -1){
("select dirs_content.name,dirs_content.modified,id2name.name,id2name_copy.name,files.filetype"
",dirs_content.itemid,files.content,convert_tz(dirs_content.eventtime,'system','%s'),dirs_content.type,files.signature,dirs_content.eventtime"
",marks.modified,files.recipients,dirs_content.recipients"
" from dirs_content"
" join files on files.id = dirs_content.itemid and files.modified = dirs_content.modified"
" join ids on dirs_content.itemid = ids.id"
" join id2name on ids.ownerid = id2name.userid"
" left join id2name as id2name_copy on dirs_content.copiedby = id2name_copy.userid"
" left join marks on marks.itemid=dirs_content.itemid and marks.userid=%u"
" where dirid = %d and (type=%u or type=%u) order by eventtime"
,user_timezone.c_str(),userid,dirid,ENTRY_MSG,ENTRY_DELETED);
SHORTMSG msg;
msg.uuid = row[0];
const char *modified = row[1];
// tlmp_error ("eventtime=%s timezone=%s\n",row[7],user_timezone.c_str());
if (row[7] != nullptr){
// eventtime can't be null, unless the timezone is invalid.
msg.submit = string(row[7],19); // The file modification time is not useful for message
// An image paste into a message list will keep its
// modification time. So we use the eventtime.
}
msg.from = row[3] != NULL ? row[3] : row[2]; // We pick copiedby instead of the file owner if not NULL
ENTRY_TYPE type = (ENTRY_TYPE)atoi(row[8]);
if (type == ENTRY_DELETED){
msg.file_type = FILE_UNKNOWN;
glocal.deletes = true;
}else{
msg.file_type = row[4] == NULL ? FILE_UNKNOWN : (FILE_TYPE)atoi(row[4]);
}
if (row[9] != nullptr) msg.signature = row[9];
msg.eventdate = row[10];
msg.viewed = bod_readviewed(row[11],modified);
// We take dirs_content.recipients if defined over files.recipients.
if (row[13] != nullptr){
msg.recipients = row[13];
}else if (row[12] != nullptr){
msg.recipients = row[12];
}
if (row[6] != NULL){
// Limit the number of lines if more than one message was requested
// Note the size is the complete size of the message, but what is sent may by few lines.
// So the application can tell this was truncated.
if (glocal.nb == 1 || glocal.fulltext.count(msg.uuid)){
msg.content = row[6];
msg.size = msg.content.size();
}else{
msg.content = bod_first_lines(row[6]);
msg.size = strlen(row[6]);
}
}else{
msg.size = fs_get_filesize(atoi(row[5]),modified);
}
glocal.mp[msg.uuid] = move(msg);
}
for (auto &m:glocal.mp){
if (m.second.file_type != FILE_UNKNOWN){
msgs.push_back(move(m.second));
}
}
sort (msgs.begin(),msgs.end(),[](const SHORTMSG &s1, const SHORTMSG &s2){return s2.eventdate
static bool nonstrict = false;
static int bod_bolixoapi_login(
CONNECT_HTTP_INFO &hcon, // Connection info to the bolixo directory
CONNECT_INFO &con, // Connection info to the writed
const char *nodename, // This system nodename as seen by the bolixo directory
string &session)
{
glocal string *session = &session;
glocal CONNECT_HTTP_INFO *hcon = &hcon;
glocal CONNECT_INFO *con = &con;
glocal int ret = -1;
(hcon,nodename);
if (!success){
tlmp_error ("nodelogin: internal_error=%d success=%d msg=%s session=%s\n",internal_error,success,msg,session);
}else{
glocal string sign;
*glocal.session = session;
(*glocal.con,session);
if (!success) tlmp_error ("nodepass: systemsign internal_error=%d success=%d msg=%s\n",internal_error,success,msg);
glocal.sign = sign;
if (glocal.sign.size()==0){
tlmp_error ("nodepass: empty signature\n");
}else{
//tlmp_error ("session=%s sign=%s\n",session,glocal.sign.c_str());
(*glocal.hcon,session,glocal.sign);
if (!success){
tlmp_error ("nodepass: internal_error=%d success=%d msg=%s\n",internal_error,success,msg);
}else{
glocal.ret = 0;
}
}
}
return glocal.ret;
}
/*
Send one image file to the bolixod server using http
*/
static void bod_sendimage (CONNECT_HTTP_INFO &hcon, PARAM_STRING session, PARAM_STRING user, PARAM_STRING name, PARAM_STRING fname)
{
FILE *fin = fopen (fname.ptr,"r");
if (fin == NULL){
tlmp_error (MSG_U(E_CANTOPEN,"Can't open file %s (%s)\n"),fname,strerror(errno));
}else{
struct stat64 st;
if (fstat64(fileno(fin),&st)==-1){
tlmp_error ("Can't stat file %s (%s)\n",fname.ptr,strerror(errno));
}else{
size_t size = st.st_size;
bool first = true;
while (size > 0){
char buf[REQ_CONTENT_CHUNK];
int n = fread (buf,1,sizeof(buf),fin);
if (n>0){
size -= n;
BOB_TYPE img;
img.setbuffer(buf,n,true);
(hcon,session,user,name,img,size > 0,!first);
if (!success) tlmp_error ("bod_sendimage publish_file internal_error=%d %s",internal_error,msg);
first = false;
}else{
tlmp_error (MSG_U(E_SENDIMAGE,"bod_sendimage: failed to read file %s (%s)\n"),fname,strerror(errno));
break;
}
}
}
fclose (fin);
}
}
static void bod_sendphoto (CONNECT_HTTP_INFO &hcon, PARAM_STRING session, const char *user, const char *name)
{
glocal ENTRY entry;
glocal user;
glocal hcon;
glocal name;
glocal const char *session = session.ptr;
string fpath = string_f("/projects/%s/public/%s",user,name);
glocal.entry.is_admin = true;
#if 0
int ok = fs_findentry(fpath,glocal.entry,true,"");
tlmp_warning ("fpath=%s %d msg=%s",fpath.c_str(),ok,glocal.entry.msg.c_str());
#endif
if (fs_findentry(fpath,glocal.entry,true,"") != -1
&& bolixo_isfile(glocal.entry.type)){
("select content,title,signature,filetype from files where id=%d and modified='%s'"
,glocal.entry.entryid,glocal.entry.modified.c_str());
if (row[0] != nullptr){
//unsigned len = strlen(row[0]);
tlmp_error ("bod_sendphoto row[0] != nullptr");
}else{
string filename = fs_createpath (glocal.entry.entryid,glocal.entry.modified);
bod_sendimage (glocal.hcon,glocal.session,glocal.user,glocal.name,filename);
}
}
}
/*
Publish the personnal information of one user (as requested by the user) to the bolixo.org directory
*/
static void bod_publish(
CONNECT_INFO &con,
PARAM_STRING user,
const USERPUBLICINFO &pinfo,
const char *dirserver, // URL of the directory server
const char *nodename)
{
CONNECT_HTTP_INFO hcon;
if (nonstrict) hcon.setnonstrictmode();
if (hcon.init(dirserver)==-1){
tlmp_error ("bod_publish: CONNECT_HTTP_INFO::init failed: dirserver=%s\n",dirserver);
}else{
hcon.setpageapi("bolixoapi");
string session;
if (bod_bolixoapi_login (hcon,con,nodename,session)!=-1){
if (pinfo.publish){
USERINFO_V2 info;
info.user = user.ptr;
info.fullname = pinfo.fullname;
info.address1 = pinfo.address1;
info.address2 = pinfo.address2;
info.city = pinfo.city;
info.state = pinfo.state;
info.country = pinfo.country;
info.zipcode = pinfo.zipcode;
info.email = pinfo.email;
info.phone = pinfo.phone;
info.fax = pinfo.fax;
info.bolixosite = pinfo.bosite_visible ? string_f("%s/%s/%s",nodename,MSG_U(I_PUBLIC,"public"),user.ptr) : "";
info.website = pinfo.website;
info.interest = pinfo.interest;
info.remove_photo = !pinfo.photo;
info.remove_mini_photo = !pinfo.mini_photo;
(hcon,session,info);
if (!success) tlmp_error ("publish: internal_error=%d success=%d msg=%s\n",internal_error,success,msg);
if (pinfo.photo){
bod_sendphoto (hcon,session,user.ptr,"photo.jpg");
}
if (pinfo.mini_photo){
bod_sendphoto (hcon,session,user.ptr,"mini-photo.jpg");
}
}else{
(hcon,session,user);
if (!success) tlmp_error ("remove: internal_error=%d success=%d msg=%s\n",internal_error,success,msg);
}
(hcon,session);
if (!success) tlmp_error ("nodelogout: internal_error=%d success=%d msg=%s\n",internal_error,success,msg);
}
}
}
/*
Record the email of a user to the bolixo.org directory to simplify login
*/
static void bod_record_email(
CONNECT_INFO &con,
PARAM_STRING userid,
PARAM_STRING email,
const char *dirserver, // URL of the directory server
const char *nodename)
{
CONNECT_HTTP_INFO hcon;
if (nonstrict) hcon.setnonstrictmode();
if (hcon.init(dirserver)==-1){
tlmp_error ("bod_record_email: CONNECT_HTTP_INFO::init failed: dirserver=%s\n",dirserver);
}else{
hcon.setpageapi("bolixoapi");
string session;
if (bod_bolixoapi_login (hcon,con,nodename,session)!=-1){
(hcon,session,userid,email);
if (!success) tlmp_error ("recordemail: internal_error=%d success=%d msg=%s\n",internal_error,success,msg);
(hcon,session);
if (!success) tlmp_error ("nodelogout: internal_error=%d success=%d msg=%s\n",internal_error,success,msg);
}
}
}
static int bod_nodelogin(
CONNECT_HTTP_INFO &hcon, // Connection info to the bolixo server (node)
CONNECT_INFO &con, // Connection info to the writed
const char *nodename,
string &session)
{
glocal session;
glocal hcon;
glocal con;
glocal int ret = -1;
if (nonstrict) hcon.setnonstrictmode();
(hcon,nodename);
// tlmp_error ("login1 success=%d msg=%s session=%s\n",success,msg,session);
if (!success){
tlmp_error ("nodelogin: internal_error=%d success=%d msg=%s session=%s\n",internal_error,success,msg,session);
}else{
glocal string sign;
glocal.session = session;
(glocal.con,session);
glocal.sign = sign;
// tlmp_error ("session=%s sign=%s\n",session,glocal.sign.c_str());
(glocal.hcon,session,glocal.sign);
if (!success){
tlmp_error ("nodepass: internal_error=%d success=%d msg=%s\n",internal_error,success,msg);
}else{
glocal.ret = 0;
}
}
return glocal.ret;
}
static void bod_nodelogout(CONNECT_HTTP_INFO &hcon, PARAM_STRING session)
{
if (nonstrict) hcon.setnonstrictmode();
(hcon,session);
}
static vector usehttp;
static void bod_inithcon (CONNECT_HTTP_INFO &hcon, PARAM_STRING remote)
{
hcon.host = remote.ptr;
if(find(usehttp.begin(),usehttp.end(),remote.ptr)!=usehttp.end()){
hcon.port = "80";
hcon.use_ssl = false;
}else{
if (nonstrict) hcon.setnonstrictmode();
hcon.port = "443";
hcon.use_ssl = true;
}
}
/*
Record user interest in another bolixo server
*/
static void bod_remote_interest_set(
CONNECT_INFO &con,
bool set_command,
PARAM_STRING user,
PARAM_STRING server,
const char *nodename)
{
CONNECT_HTTP_INFO hcon;
bod_inithcon (hcon,server);
string session;
(hcon,nodename);
if (bod_nodelogin (hcon,con,nodename,session)!=-1){
if (set_command){
(hcon,session,user);
if (!success) tlmp_error ("remote_interest_set: internal_error=%d success=%d msg=%s\n",internal_error,success,msg);
}else{
(hcon,session,user);
if (!success) tlmp_error ("remote_interest_set: internal_error=%d success=%d msg=%s\n",internal_error,success,msg);
}
bod_nodelogout(hcon,session);
}
}
static string bod_format_remote_user(PARAM_STRING user, PARAM_STRING our_nodename)
{
const char *nodename = our_nodename.ptr;
if (strncasecmp(nodename,"http://",7)==0){
nodename += 7;
}else if (strncasecmp(nodename,"https://",8)==0){
nodename += 8;
}
return string_f("%s@%s",user.ptr,nodename);
}
static int bod_remotelogin (
CONNECT_INFO &con, // To reach bo-writed
CONNECT_HTTP_INFO &hcon, // To reach remote server
PARAM_STRING local_user, // local user name
PARAM_STRING nodename, // Our node name
const char *local_session,
string &sessionid)
{
glocal int ret = -1;
glocal con;
glocal sessionid;
glocal hcon;
glocal local_session;
glocal const char *remote = hcon.host.c_str();
glocal string remote_user = bod_format_remote_user (local_user,nodename);
(hcon,glocal.remote_user);
if (!success){
tlmp_error ("remotelogin failed for %s, remote server %s returned: %s\n"
,glocal.remote_user.c_str(),glocal.remote,msg);
}else{
glocal string sign;
glocal.sessionid = sessionid;
(glocal.con,glocal.local_session,sessionid);
if (success){
glocal.sign = sign;
}else{
tlmp_error ("remotelogin can't sign for %s: %s\n",glocal.remote_user.c_str(),msg);
}
if (glocal.sign.size() > 0){
(glocal.hcon,sessionid,glocal.remote_user,glocal.sign);
if (success){
glocal.ret = 0;
}else{
tlmp_error ("remotepass failed for %s: %s\n",glocal.remote_user.c_str(),msg);
}
}
}
return glocal.ret;
}
static void bod_remotelogout(CONNECT_HTTP_INFO &hcon, PARAM_STRING sessionid)
{
(hcon,sessionid);
}
/*
Perform a remote contact request
Return -1 if any error
*/
static int bod_remote_contact_request(
CONNECT_INFO &con,
const char *sessionid,
PARAM_STRING caller, // User who created this request
PARAM_STRING user, // User we wish to contact
PARAM_STRING intro, // Small message
PARAM_STRING server, // Server where this user lives
const char *nodename, // Our nodename
string &msg)
{
glocal int ret = -1;
glocal msg;
glocal const char *server = server.ptr;
glocal const char *user = user.ptr;
CONNECT_HTTP_INFO hcon;
bod_inithcon (hcon,server);
string session;
if (bod_remotelogin (con,hcon,caller,nodename,sessionid,session)!=-1){
(hcon,session,"",user,intro);
if (success){
glocal.ret = 0;
}else{
tlmp_error ("remote_contact_request: internal_error=%d success=%d msg=%s\n",internal_error,success,msg);
glocal.msg = string_f(MSG_U(E_REMOTECONTACTREQ,"Remote contact request to account %s on server %s failed: %s")
,glocal.user,glocal.server,msg);
}
bod_remotelogout(hcon,session);
}else{
msg = string_f(MSG_U(E_REMOTELOGINFAIL,"Remote login to account %s on server %s failed"),caller.ptr,server.ptr);
}
return glocal.ret;
}
/*
Perform a remote contact manage
*/
static void bod_remote_contact_manage(
CONNECT_INFO &con,
const char *sessionid,
PARAM_STRING caller, // User who created this request
PARAM_STRING user, // User we wish to contact
CONTACT_STATUS status, // Was the contact request accepted
PARAM_STRING server, // Server where this user lives
const char *nodename) // Our nodename
{
CONNECT_HTTP_INFO hcon;
bod_inithcon(hcon,server);
string session;
if (bod_remotelogin (con,hcon,caller,nodename,sessionid,session)!=-1){
(hcon,session,"",user,status);
if (!success) tlmp_error ("remote_contact_manage: internal_error=%d success=%d msg=%s\n",internal_error,success,msg);
bod_remotelogout(hcon,session);
}
}
/*
Perform a remote contact remove
*/
static void bod_remote_contact_remove(
CONNECT_INFO &con,
const char *sessionid,
PARAM_STRING caller, // User who created this request
PARAM_STRING user, // User we wish to remove
PARAM_STRING server, // Server where this user lives
const char *nodename) // Our nodename
{
CONNECT_HTTP_INFO hcon;
bod_inithcon(hcon,server);
string session;
if (bod_remotelogin (con,hcon,caller,nodename,sessionid,session)!=-1){
(hcon,session,"",user);
if (!success) tlmp_error ("remote_contact_remove: internal_error=%d success=%d msg=%s\n",internal_error,success,msg);
bod_remotelogout(hcon,session);
}
}
struct REMPROJECT{
string name;
string access;
bool modify=false;
REMPROJECT(PARAM_STRING _name, PARAM_STRING _access)
:name(_name.ptr), access(_access.ptr){
}
};
/*
Perform a remote set_member
*/
static void bod_remote_set_member(
CONNECT_INFO &con,
const char *sessionid,
PARAM_STRING caller, // User who created this request
PARAM_STRING groupname,
bool create_group, // Create the group on the remote server
bool delete_group,
PARAM_STRING user, // User we wish to contact
PARAM_STRING server, // Server where this user lives
PARAM_STRING access,
PARAM_STRING role,
const char *nodename, // Our nodename
const vector &remprojects) // Projects to manage on the remote server
{
glocal bool success = true;
CONNECT_HTTP_INFO hcon;
bod_inithcon(hcon,server);
string session;
if (bod_remotelogin (con,hcon,caller,nodename,sessionid,session)!=-1){
if (create_group){
// The public group is created on the fly at account creation time, including remote account creation.
if (groupname != "public"){
(hcon,session,groupname,"");
if (!success){
tlmp_error ("remote_set_member create_group: internal_error=%d success=%d msg=%s\n",internal_error,success,msg);
glocal.success = false;
}
}
// If we create a remote group, remprojects contains a list of project to create on the remote server.
for (auto &r:remprojects){
if (glocal.success){
(hcon,session,r.name,"");
if (!success){
tlmp_error ("remote_set_member create_project: internal_error=%d success=%d msg=%s\n",internal_error,success,msg);
glocal.success = false;
}
// We must add the group in the project
if (glocal.success){
(hcon,session,r.name,groupname,r.access,"");
if (!success){
tlmp_error ("remote_set_member set_group: internal_error=%d success=%d msg=%s\n",internal_error,success,msg);
glocal.success = false;
}
}
}
}
}
(hcon,session,groupname,user,access,role,"");
if (!success) tlmp_error ("remote_set_member: internal_error=%d success=%d msg=%s\n",internal_error,success,msg);
if (delete_group){
// We never delete the remote public group
if (groupname != "public"){
(hcon,session,groupname,"");
if (!success){
tlmp_error ("remote_set_member delete_group: internal_error=%d success=%d msg=%s\n",internal_error,success,msg);
glocal.success = false;
}
}
// If we remove a remote group, remprojects contains a list of project to remove
for (auto &r:remprojects){
if (glocal.success){
(hcon,session,r.name,"");
if (!success){
tlmp_error ("remote_set_member delete_list: internal_error=%d success=%d msg=%s\n",internal_error,success,msg);
glocal.success = false;
}
}
}
}
bod_remotelogout(hcon,session);
}
}
/*
Perform a remote set_group for listname/groupname.
The group may be either assigned, create, deassigned or delete
*/
static void bod_remote_set_group (
CONNECT_INFO &con,
const char *sessionid,
PARAM_STRING caller,
const char *nodename,
const char *listname,
const char *groupname,
map> &servers,
const char *defaultaccess,
bool was_member)
{
glocal bool success = true;
for (auto &m:servers){
CONNECT_HTTP_INFO hcon;
bod_inithcon(hcon,m.first);
string session;
if (bod_remotelogin (con,hcon,caller,nodename,sessionid,session)!=-1){
// We create the project/list, but it may already exist, so we do not check the error
(hcon,session,listname,"");
(hcon,session,listname,groupname,defaultaccess,"");
if (!success) glocal.success = false;
bod_remotelogout(hcon,session);
}
if (!glocal.success) break;
}
}
/*
When bod performs a remote file operation and it must return a handle to the caller, it allocates this
to keep the context, like filesystem.tlcc does with local file handle.
There are two usage to this system. There are two addto() methods.
appendfile is used by addfile_bob/modifyfile_bod and sendtalk.
addfile_bob used the addto() to associate on local handle to a remote handle.
sendtalk associates a local handle with multiple remote handles.
*/
struct REMOTE_INFO{
string server;
string sessionid;
string handle;
CONNECT_HTTP_INFO con;
void logout();
REMOTE_INFO(){};
REMOTE_INFO(const REMOTE_INFO &c) = default;
REMOTE_INFO(REMOTE_INFO &&c) = default;
~REMOTE_INFO();
void initcon();
};
void REMOTE_INFO::initcon()
{
bod_inithcon(con,server);
}
struct REMOTE_INFO_FROM: public REMOTE_INFO{
string user;
};
struct REMOTE_INFO_TO: public REMOTE_INFO{
string groupname;
string groupowner;
vector users;
};
void REMOTE_INFO::logout()
{
if (sessionid.size() > 0){
bod_remotelogout(con,sessionid);
sessionid.clear();
}
}
REMOTE_INFO::~REMOTE_INFO()
{
logout();
}
struct REMOTE_HANDLE{
string username;
string handle;
REMOTE_INFO_FROM from;
vector to;
void setfrom (PARAM_STRING server){
from.server = server.ptr;
}
void addgroup (PARAM_STRING groupname, PARAM_STRING server, PARAM_STRING groupowner){
REMOTE_INFO_TO r;
r.groupname = groupname.ptr;
r.server = server.ptr;
r.groupowner = groupowner.ptr;
//tlmp_warning ("addgroup g=%s s=%s o=%s",groupname.ptr,server.ptr,groupowner.ptr);
to.emplace_back(r);
}
void addto (PARAM_STRING server, const vector &users){
REMOTE_INFO_TO r;
r.server = server.ptr;
r.users = users;
to.emplace_back(move(r));
}
void addto (PARAM_STRING server, PARAM_STRING handle, PARAM_STRING session){
REMOTE_INFO_TO r;
r.server = server.ptr;
r.handle = handle.ptr;
r.sessionid = session.ptr;
r.initcon();
to.emplace_back(move(r));
}
};
using REMOTE_HANDLE_P = unique_ptr;
static map rem_handles;
/*
Send the content of a game or document to documentd.
Some documents may reference other documents or games (document embedding).
documentd checks if other documents are needed and report it as a result of documentd_client_load (and loadmore, not implemented yet).
This means this function may load many documents. All documents are in the same project, so there is no security issue.
*/
static void bod_document_load (
CONNECT_INFO &con_doc,
unsigned userid,
const char *path)
{
glocal con_doc;
// tlmp_warning ("document_load %s\n",path);
glocal deque todo;
glocal set done;
glocal.todo.push_back(path);
while (glocal.todo.size() > 0){
glocal ENTRY entry;
glocal string name = glocal.todo.front();
glocal function &imbeds)> add_todo;
glocal.add_todo = [&](bool success, const char *msg, const vector &imbeds){
if (!success){
tlmp_error ("bod_document_load: documentd %s failed: %s",glocal.name.c_str(),msg);
}else{
for (auto im:imbeds){
if (glocal.done.count(im) == 0){
glocal.todo.push_back(im);
}
}
}
};
glocal.todo.pop_front();
glocal.entry.userid = userid;
if (fs_findentry(glocal.name,glocal.entry,true,"")==-1){
tlmp_error ("bod_document_load: missing document %s",glocal.name.c_str());
break;
}else{
//tlmp_warning ("Load %s todo.size()=%lu",glocal.name.c_str(),glocal.todo.size());
glocal.done.insert(glocal.name);
("select content,title,signature,filetype,modifiedby from files where id=%d and modified='%s'"
,glocal.entry.entryid,glocal.entry.modified.c_str());
// tlmp_warning ("document_load row[0] %d\n",row[0]!=nullptr);
if (row[0] != nullptr){
unsigned len = strlen(row[0]);
BOB_TYPE bob (row[0],len,false);
(glocal.con_doc,glocal.name,bob,false);
glocal.add_todo(success,msg,imbeds);
}else{
FILE *fin = fs_open_file (glocal.entry.entryid,glocal.entry.modified,"r");
if (fin == NULL){
glocal.entry.msg = "Internal error, reading content file";
}else{
struct stat64 st;
if (fstat64(fileno(fin),&st)==-1){
glocal.entry.msg = "Internal error, getting file size";
}else{
char buf[REQ_CONTENT_CHUNK];
int len = fread (buf,1,sizeof(buf),fin);
BOB_TYPE bob (buf,len,false);
bool more = len==REQ_CONTENT_CHUNK;
(glocal.con_doc,glocal.name,bob,more);
glocal.add_todo(success,msg,imbeds);
if (!more){
}
}
fclose (fin);
}
}
glocal.entry.msg = "Internal error, reading table files";
}
}
}
/*
Retrieve the content of a game or document from documentd and save it.
*/
static bool bod_document_save (
CONNECT_INFO &con,
CONNECT_INFO &con_doc,
const char *sessionid,
const char *path)
{
glocal bool ret = false;
glocal con;
glocal con_doc;
glocal path;
glocal sessionid;
(con_doc,path);
if (modified){
glocal modified_by;
(glocal.con_doc,glocal.path);
if (success){
(glocal.con,glocal.sessionid,glocal.path,content,false,glocal.modified_by);
if (!success) tlmp_error ("bod_document_save %s: %s\n",glocal.path,msg);
}
glocal.ret = true;
}
return glocal.ret;
}
/*
Remove a game/document from documentd.
*/
static bool bod_document_delfile (
CONNECT_INFO &con_doc,
const char *username,
const char *path)
{
glocal bool ret = false;
glocal con_doc;
glocal path;
glocal username;
(con_doc,path);
if (success){
(glocal.con_doc,glocal.path,revision,glocal.username,true,current_lang);
if (success){
glocal.ret = true;
}else{
tlmp_error ("bod_document_delfile: username=%s path=%s msg=%s",glocal.username,glocal.path,msg);
}
}else{
glocal.ret = true;
}
return glocal.ret;
}
/*
Perform a sendtalk to one remote user
*/
static void bod_remote_sendtalk(
CONNECT_INFO &con,
const char *sessionid,
REMOTE_HANDLE &rem,
const BOB_TYPE &content,
const char *nodename, // Our nodename
bool more,
PARAM_STRING name,
PARAM_STRING sign,
PARAM_STRING createdby)
{
for (auto &r:rem.to){
glocal r;
r.initcon();
if (bod_remotelogin (con,r.con,rem.username,nodename,sessionid,r.sessionid)!=-1){
//tlmp_warning ("sendtalk groupname=%s groupowner=%s\n",r.groupname.c_str(),r.groupowner.c_str());
(r.con,r.sessionid,"",r.users,r.groupname,r.groupowner,content,more,name,sign,createdby);
glocal.r.handle = handle;
if (!success) tlmp_error ("remote_sendtalk: internal_error=%d success=%d msg=%s\n",internal_error,success,msg);
if (!more){
r.logout();
}
}
}
}
struct REMOTE_RECIPIENTS{
string groupname;
string groupowner;
vector recipients;
};
/*
Perform a sendtalk_file to remote users
*/
static void bod_remote_sendtalk_file(
CONNECT_INFO &con,
const char *sessionid,
PARAM_STRING username,
const map &mp,
const BOB_TYPE *content,
FILE *fin,
const char *nodename, // Our nodename
PARAM_STRING sign,
PARAM_STRING created)
{
const char *name = "";
string createdby;
if (strchr(created.ptr,'@')!=nullptr){
createdby = created.ptr;
}else{
createdby = bod_format_remote_user(created,nodename);
}
for (auto &m:mp){
CONNECT_HTTP_INFO r_con;
bod_inithcon (r_con,m.first);
string r_sessionid;
tlmp_warning ("sendtalk_file: connect to %s\n",m.first.c_str());
if (bod_remotelogin (con,r_con,username,nodename,sessionid,r_sessionid)!=-1){
tlmp_warning ("sendtalk_file groupname=%s groupowner=%s createdby=%s sign=%s\n",m.second.groupname.c_str(),m.second.groupowner.c_str(),createdby.c_str(),sign.ptr);
if (content != nullptr){
(r_con,r_sessionid,"",m.second.recipients,m.second.groupname,m.second.groupowner,*content,false,name,sign,createdby);
if (!success) tlmp_error ("remote_sendtalk_file: internal_error=%d success=%d msg=%s\n",internal_error,success,msg);
}else{
struct stat64 st;
if (fstat64(fileno(fin),&st)==-1){
tlmp_error ("Internal error, getting file size\n");
}else{
rewind(fin);
bool first = true;
glocal string handle;
glocal bool done = false;
while (!glocal.done){
char buf[REQ_CONTENT_CHUNK];
int len = fread (buf,1,sizeof(buf),fin);
if (len == 0) break;
BOB_TYPE bob (buf,len,false);
bool more = len==REQ_CONTENT_CHUNK;
if (first){
first = false;
(r_con,r_sessionid,"",m.second.recipients,m.second.groupname,m.second.groupowner,bob,more,name,sign,createdby);
glocal.handle = handle;
if (!success){
tlmp_error ("remote_sendtalk_file: internal_error=%d success=%d msg=%s\n",internal_error,success,msg);
glocal.done = true;
}
}else{
(r_con,r_sessionid,glocal.handle,bob,more);
if (!success){
tlmp_error ("remote_sendtalk_file append: internal_error=%d success=%d msg=%s\n",internal_error,success,msg);
glocal.done = true;
}
}
}
}
}
bod_remotelogout(r_con,r_sessionid);
}
}
}
static void bod_remote_appendfile(
CONNECT_INFO &con,
const char *sessionid,
REMOTE_HANDLE &rem,
const char *nodename,
const BOB_TYPE &content,
bool more,
string &msg)
{
glocal msg;
for (auto &r:rem.to){
glocal REMOTE_INFO_TO *r = &r;
(r.con,r.sessionid,r.handle,content,more);
if (!success){
glocal.msg = msg;
tlmp_error ("remote_appendfile failed: %s@%s %s\n"
,glocal.r->users[0].c_str(),glocal.r->server.c_str(),msg);
}
if (glocal.msg.size() > 0) break;
}
}
/*
Convert a local name to remote name if it is local.
*/
static void bod_patchname(string &name, const string &remote)
{
if (name.size() > 0 && name.find('@') == string::npos){
name += string("@") + remote;
}
}
static void bod_remote_readfile_bob (
CONNECT_INFO &con,
const char *sessionid,
REMOTE_HANDLE_P &rem,
PARAM_STRING name,
bool nomore,
const char *nodename,
BOB_TYPE &bob,
READINFO &info,
bool &more,
string &msg)
{
glocal bob;
glocal more;
glocal msg;
glocal info;
glocal REMOTE_INFO_FROM *from = &rem->from;
glocal.from->initcon ();
if (bod_remotelogin (con,glocal.from->con,rem->username,nodename,sessionid,glocal.from->sessionid)!=-1){
(glocal.from->con,glocal.from->sessionid,name,"",nomore);
glocal.from->handle = handle;
glocal.msg = msg;
if (success){
glocal.bob = content;
glocal.more = more;
glocal.info = info;
}else{
tlmp_error ("remote_readfile_bob: internal_error=%d success=%d msg=%s\n",internal_error,success,msg);
}
if (!more){
glocal.from->logout();
}
}
}
static void bod_remote_readmore(
REMOTE_HANDLE_P &rem,
bool &success,
string &msg,
BOB_TYPE &content,
bool &more)
{
glocal bool *success = &success;
glocal string *msg = &msg;
glocal bool *more = &more;
glocal BOB_TYPE *content = &content;
(rem->from.con,rem->from.sessionid,rem->from.handle);
(*glocal.success) = success;
(*glocal.msg) = msg;
(*glocal.more) = more;
(*glocal.content) = content;
}
/*
Parse a user[@nodename]
Strip the nodename if it is this node.
If this is a local user, local_user will contain just the user part wihtout @our_nodename.
If this is a remote user
local_user will contain user
remote_user will contain the user part without the @some_server
server will contain some_server.
*/
static int bod_parse_user (
const char *user,
string &local_user,
string &remote_user,
string &server,
const char *our_nodename)
{
int ret = -1;
// Database use varchar(100)
if (strlen(user)<95){
local_user = user;
const char *pt = user;
bool bad = false;
#if 0
// What are the rules for hostname ?
while (*pt != '\0'){
// Better validation of hostname. More character are allowed
if (*pt <= ' '){
bad = true;
break;
}else if (!isalpha(*pt) && !isdigit(*pt) && *pt != '.' && *pt != '-' && *pt != '_' && *pt != '@'){
bad = true;
break;
}
pt++;
}
#endif
if (!bad){
pt = strchr(user,'@');
if (pt == NULL){
local_user = user;
ret = 0;
}else if (pt[1] != '-'){
// A hostname can't start with -
// This is user@nodename
const char *ptnode;
if (is_start_any_ofnc(our_nodename,ptnode,"http://","https://")){
our_nodename = ptnode;
}
if (strcasecmp(pt+1,our_nodename)==0){
// Local user
local_user = string(user,pt-user);
}else{
remote_user = string(user,pt-user);
server = pt+1;
}
ret = 0;
}
}
}
return ret;
}
/*
Return true if a user account is remote
*/
static bool bod_is_remote_user (PARAM_STRING user)
{
return strchr(user.ptr,'@') != nullptr;
}
#define _TLMP_bod_localremote
struct _F_bod_localremote{
string srcproject; // Part of fname representing the local project
string dstproject; // remote project
string mapfname (PARAM_STRING path);
bool dologout = true;
void donotlogout() { dologout = false;}
#define _F_bod_localremote_remote(x) void x remote(CONNECT_HTTP_INFO &con, const char *session, const char *fname, const char *username, const char *threshold, const char *server, string &msg)
virtual _F_bod_localremote_remote( )=0;
#define _F_bod_localremote_local(x) void x local(CONNECT_INFO &con, const char *session, const char *fname, const char *username, ENTRY &entry, const char *threshold, string &msg)
virtual _F_bod_localremote_local( )=0;
};
/*
path is a remote path. Convert it to local path.
So path looks like /projects/user@some-server/public/file and it is convert to /projects/user/public/file
so it is usable on the remote server.
Validate that the path is in the same remote project as fname supplied in the bod_localremote() call.
*/
string _F_bod_localremote::mapfname (PARAM_STRING path)
{
string ret;
const char *pt;
if (is_start_any_of(path.ptr,pt,srcproject.c_str())){
ret = dstproject + pt;
}
return ret;
}
static bool onlylocal = false; // Some instance of bod are not allowed to interact with remote servers
// This would create deadlocks.
static void bod_localremote(
_F_bod_localremote &c,
CONNECT_INFO &con,
CONNECT_INFO &con_sess,
const char *sessionid,
const char *fname,
bool expect_exist,
const char *threshold,
const char *nodename,
string &msg)
{
string username;
ENTRY entry;
entry.userid = trli_getsessionuser (con_sess, sessionid, entry.is_admin, username, msg);
if (entry.userid != 0){
string local_user,remote_user,server;
const char *pt;
vector tb; // Decomposed path after /projects/ and /msgs/
if (is_start_any_of(fname,pt,"/projects/","/msgs/")){
int n = str_splitline (pt,'/',tb);
if (n >= 2){
c.srcproject = string(fname,pt-fname)+tb[0]+'/'+tb[1]+'/';
c.dstproject = c.srcproject;
}
if (n >= 1){
if (bod_parse_user (tb[0].c_str(),local_user,remote_user,server,nodename)==-1){
msg = MSG_R(E_IVLDPATH);
}
}
}
if (msg.empty()){
if (remote_user.size() > 0){
if (onlylocal){
tlmp_error ("Remote requests not allowed for this bod: fname=%s\n",fname);
msg = "Remote request denied";
}else if (tb.size() >= 2){
c.dstproject = string(fname,pt-fname)+remote_user+'/'+tb[1] + '/';
CONNECT_HTTP_INFO hcon;
bod_inithcon(hcon,server);
string session;
if (bod_remotelogin (con,hcon,username,nodename,sessionid,session)!=-1){
string rem_fname (fname,pt-fname);
rem_fname += remote_user;
for (auto p=tb.begin()+1; p != tb.end(); p++){
rem_fname += '/';
rem_fname += *p;
}
c.remote(hcon,session.c_str(),rem_fname.c_str(),username.c_str(),threshold,server.c_str(),msg);
if (c.dologout){
bod_remotelogout(hcon,session);
}
}else{
tlmp_warning ("remote login failed: fname=%s server=%s\n",fname,server.c_str());
}
}else{
msg = MSG_R(E_IVLDPATH);
}
}else{
bool ok = true;
if (expect_exist){
ok = fs_findentry (fname,entry,true,threshold) != -1;
if (!ok){
msg = entry.msg;
}
}
if (ok){
c.local(con,sessionid,fname,username.c_str(),entry,threshold,msg);
}
}
}
}
}
static void bod_localremote(_F_bod_localremote &c, CONNECT_INFO &con, CONNECT_INFO &con_sess, const char *sessionid, const char *fname, const char *nodename, string &msg)
{
bod_localremote (c,con,con_sess,sessionid,fname,false,"",nodename,msg);
}
/*
Get the public key of a node
*/
static int bod_getpubkey (const char *nodename, string &pubkey)
{
glocal int ret = -1;
glocal const char *hostname = nodename;
glocal string *pubkey = &pubkey;
CONNECT_HTTP_INFO con;
if (con.init (nodename) != -1){
if (nonstrict) con.setnonstrictmode();
(con);
if (pubkey[0] == '\0'){
tlmp_error ("Enpty public key for host %s\n",glocal.hostname);
}else if (fs_valid_pubkey(pubkey)==-1){
tlmp_error ("Invalid public key for host %s\n",glocal.hostname);
}else{
*glocal.pubkey = pubkey;
glocal.ret = 0;
}
}
return glocal.ret;
}
// Get the name associated with a userid
static string bod_getname(unsigned userid)
{
glocal string ret;
("select name from id2name where userid=%u",userid);
glocal.ret = row[0];
return glocal.ret;
}
/*
We do some validation here, even if bo-writed does the same.
if "user" is an account on another node, we do the validation here
if the remote user does not exist in this node (remote accounts),
we tell bo-writed to create it.
Return 0 if everything is ok.
*/
static int bod_check_remote_user(
CONNECT_INFO &con,
const char *user,
const char *nodename,
string &msg,
string &local_user,
string &remote_user,
string &remote)
{
glocal con;
glocal user;
glocal int ret = -1;
glocal msg;
if (bod_parse_user(user,local_user,remote_user,remote,nodename)==-1){
msg = MSG_R(E_IVLDUSER);
}else{
glocal const char *remote_user = remote_user.c_str();
glocal const char *remote = remote.c_str();
glocal.user = local_user.c_str();
("select userid from id2name where name='%s'",glocal.user);
// User unknown
// tlmp_warning ("check_remote_user unknown user %s",glocal.user);
if (glocal.remote[0] != '\0'){
// Check if this user exists on the remote node
CONNECT_HTTP_INFO hcon;
bod_inithcon (hcon,glocal.remote);
// tlmp_warning ("check remote=%s remote_user=%s",glocal.remote,glocal.remote_user);
(hcon,glocal.remote_user);
// tlmp_warning ("check internal=%d success=%d sign=%s",internal_error,success,pubkey);
if (success){
if (fs_valid_pubkey(pubkey)==-1){
glocal.msg = MSG_U(E_IVLDPUBKEY,"Invalid public key for remote user");
}else{
(glocal.con,glocal.user,pubkey);
if (!success){
glocal.msg = msg;
}else{
glocal.ret = 0;
}
}
}else{
glocal.msg = string_f(MSG_U(E_CANTGETPUBKEY,"Can't get public key for remote user %s on server %s")
,glocal.remote_user,glocal.remote);
}
}else{
glocal.msg = MSG_R(E_IVLDUSER);
}
// User exist on the system, remote user or local user, does not matter
glocal.ret = 0;
}
return glocal.ret;
}
/*
Check if createdby is an existing account (local or remote).
If it is not on the system, but is a remote account, try to create it by retrieving its public key.
Return -1 if any error.
*/
static int bod_check_createdby(CONNECT_INFO &con, const char *createdby, const char *nodename, string &msg)
{
int ret = 0;
if (createdby[0] != '\0'){
string remote;
string local_user;
string remote_user;
ret = bod_check_remote_user(con,createdby,nodename,msg,local_user,remote_user,remote);
}
return ret;
}
/*
Find the remote users members of a group.
Organise then by server and then, for each server, the list of remote user.
*/
static void bod_get_remote_user(
map> &servers,
unsigned userid,
const char *groupname,
const char *nodename)
{
glocal servers;
glocal nodename;
("select id2name.name from groups"
" left join group_members on groups.id = group_members.groupid"
" join id2name on group_members.userid=id2name.userid"
" where groups.ownerid=%u and groups.name='%s'",userid,groupname);
string local_user,remote_user,server;
if (bod_parse_user (row[0],local_user,remote_user,server,glocal.nodename)==-1){
tlmp_error ("set_member bod_parse_user error %s\n",row[0]);
}else if (remote_user.size() > 0){
glocal.servers[server].insert(remote_user);
}
}
#define REPLYMSG(f,msg) if (msg.size()>0){ f(false,msg.c_str()); }else{ f(true,""); }
/*
Validate that an entry describe a document or game
*/
static bool bod_is_document (ENTRY &entry, string &msg)
{
glocal msg;
bool ret = false;
if (!bolixo_isfile(entry.type)){
msg = MSG_R(E_NOTAFILE);
}else{
glocal FILE_TYPE file_type = FILE_UNKNOWN;
("select filetype from files where id=%d and modified='%s'"
,entry.entryid,entry.modified.c_str());
glocal.file_type = (FILE_TYPE)atoi(row[0]);
glocal.msg = "Internal error, reading table files";
if (!file_is_doc(glocal.file_type)){
msg = MSG_U(E_NOTADOC,"File is not a game or document");
}else{
ret = true;
}
}
return ret;
}
// Split remote user by server
static map bod_classify_recipients(const vector &to, const char *nodename)
{
map mp;
for (auto s:to){
string tmpuser,remote_user,remote;
if (bod_parse_user(s,tmpuser,remote_user,remote,nodename)!=-1
&& remote.size() > 0){
auto &m = mp[remote];
m.recipients.push_back(remote_user);
}
}
return mp;
}
static int bod_main (
const char *bind,
const char *portstr,
const char *control, // Unix socket
const CONNECT_INFO &con,
const CONNECT_INFO &con_sess,
const CONNECT_INFO &con_doc,
const char *user,
const char *pidfile,
bool daemon,
const map &admin_secrets,
const map &client_secrets,
const char *nodename,
const char *dirserver,
unsigned maxaccts)
{
int ret = -1;
glocal const char *control = control;
glocal const char *nodename = nodename;
glocal const char *dirserver = dirserver;
glocal CONNECT_INFO con = con;
glocal CONNECT_INFO con_sess = con_sess;
glocal CONNECT_INFO con_doc = con_doc;
glocal const map *admin_secrets = &admin_secrets;
glocal const map *client_secrets = &client_secrets;
glocal unsigned long nbrequest_client = 0;
glocal unsigned long nbrequest_admin = 0;
glocal string unixportclient = string_f("unix:/tmp/bod-client-%s.sock",portstr);
glocal string unixportadmin = string_f("unix:/tmp/bod-admin-%s.sock",portstr);
glocal unsigned maxaccts = maxaccts;
glocal string admin_sessionid;
glocal string instrument_file = string_f("/tmp/instrument-%s.log",portstr);
glocal unsigned slowplaystep = 0;
(bind,glocal.unixportclient,5);
HANDLE_INFO *n = new HANDLE_INFO;
info.data = n;
if (strcmp(info.port,glocal.unixportclient.c_str())==0){
n->req.secret = fdpass_findsecret (*glocal.client_secrets,info.port);
if (n->req.secret.size() > 0){
n->type = TYPE_CLIENT;
}else{
tlmp_error ("Rejected client connexion from port %s\n",info.port);
endclient = true;
}
}else if (strcmp(info.port,glocal.unixportadmin.c_str())==0){
n->req.secret = fdpass_findsecret (*glocal.admin_secrets,info.port);
if (n->req.secret.size() > 0){
n->type = TYPE_ADMIN;
}else{
tlmp_error ("Rejected admin connexion from port %s\n",info.port);
endclient = true;
}
}else if (strncmp(info.port,"unix:",5)==0){
n->type = TYPE_CONTROL;
}else{
settcpnodelay(true);
char addr[20];
ipnum_ip2a (from,addr);
n->host = addr;
n->req.secret = fdpass_findsecret (*glocal.client_secrets,addr);
if (n->req.secret.size() > 0){
n->type = TYPE_CLIENT;
}else{
n->req.secret = fdpass_findsecret (*glocal.admin_secrets,addr);
if (n->req.secret.size() > 0){
n->type = TYPE_ADMIN;
}else{
tlmp_error ("Rejected connexion from IP %s\n",addr);
endclient = true;
}
}
}
HANDLE_INFO *c = (HANDLE_INFO*)info.data;
// See
if (c->documentd_no != -1){
closeclient (c->documentd_no);
}else if (is_any_of (c->type,TYPE_DOCUMENTD,TYPE_WEBSOCKET)){
closeclient (c->no);
}
HANDLE_INFO *c = (HANDLE_INFO*)info.data;
glocal HANDLE_INFO *c = c;
glocal string userid = c->userid;
debug_printf (D_PROTO,"receive line[%d]: %s\n",info.linelen,line);
{
// Clean the session cache once in a while
static time_t last_clean=0;
time_t now = time(NULL);
if (now > last_clean+10){
last_clean = now;
bod_sessions_clean();
}
}
if (c->type == TYPE_CONTROL){
(this,c->req,line,info.linelen, endserver, endclient, no,c,c->host.c_str());
vector tb;
tb.push_back(string_f("Version %s",VERSION));
tb.push_back(string_f("dirserver: %s",glocal.dirserver));
tb.push_back(string_f("nodename: %s",glocal.nodename));
tb.push_back(string_f("maxaccts: %u",glocal.maxaccts));
tb.push_back(string_f("keepmsgs: %u",keepdays));
tb.push_back(string_f ("nbrequest_admin=%lu",glocal.nbrequest_admin));
tb.push_back(string_f ("nbrequest_client=%lu",glocal.nbrequest_client));
tb.push_back(string_f("filehandle %u",fs_getnbhandle()));
tb.push_back(string_f("rem_filehandle %zu",rem_handles.size()));
tb.push_back(string_f("sessions %zu",sessions.size()));
tb.push_back(string_f("admin_sessionid: %s",glocal.admin_sessionid.c_str()));
tb.push_back(string_f("slowplaystep: %u",glocal.slowplaystep));
string useline = "usehttp:";
for (auto &s:usehttp) useline += string(" ") + s;
tb.push_back(useline);
instrument_status(tb);
tb.push_back(string_f("nonstrict: %d",nonstrict));
unsigned nbwaiting = 0,doc_waiting=0;
void *data;
int fd = glocal.TCPSERVER.iter_init (data);
while (fd != -1){
auto c = (HANDLE_INFO*)data;
if (c->documentd_no != -1) nbwaiting++;
if (c->type == TYPE_DOCUMENTD) doc_waiting++;
fd = glocal.TCPSERVER.iter_next (data);
}
tb.push_back(string_f("nbwaiting: %u %u",nbwaiting,doc_waiting));
rep_status(tb);
// dotimes:b readproc:b = lines:v
// This performs various test from inside the container that may failed or not.
// Bolixo containers are so minimal that they do not contain bash.
glocal vector lines;
if (dotimes){
struct rusage u;
if (getrusage(RUSAGE_SELF,&u)==-1){
glocal.lines.push_back(string_f("syscall getrusage failed: %s",strerror(errno)));
}else{
glocal.lines.push_back(string_f("rusage: %lu.%06lu,%lu.%06lu"
,u.ru_utime.tv_sec
,u.ru_utime.tv_usec
,u.ru_stime.tv_sec
,u.ru_stime.tv_usec));
}
}
if (readproc){
glocal.lines.push_back("readproc:");
("/proc");
return false;
glocal.lines.push_back(path);
}
rep_helptest(glocal.lines);
// connectto port send = lines:v
glocal const char *send = send;
glocal vector lines;
// We want to test bod connectivity to the outside
(connectto,port,5);
sendf ("%s\n",glocal.send);
glocal.lines.push_back(line);
end = true;
glocal.lines.emplace_back(string_f("tcpconnect fail: %s",strerror(errno)));
rep_help_connect (glocal.lines);
// session
bod_sessions_erase(session);
rep_erase_session (true,"");
endserver = true;
glocal const char *name = name;
glocal const char *email = email;
glocal string msg;
("select userid from id2name where name='%s'",name);
glocal.msg = "User not found";
bod_record_email (glocal.con,glocal.name,glocal.email,glocal.dirserver,glocal.nodename);
REPLYMSG(rep_publishemail,glocal.msg);
// node = success:b msg session
CONNECT_HTTP_INFO hcon;
hcon.init (node);
string session;
bool success = false;
if (nonstrict) hcon.setnonstrictmode();
(hcon,glocal.nodename);
if (bod_nodelogin(hcon,glocal.con,glocal.nodename,session)!=-1){
success = true;
}
rep_nodelogin (success,"",session);
// node session = success:b msg
CONNECT_HTTP_INFO hcon;
hcon.init (node);
bod_nodelogout(hcon,session);
rep_nodelogout (true);
glocal.admin_sessionid = session;
toggle_instrument_file(on,glocal.instrument_file);
if (on){
debug_seton();
}else{
debug_setoff();
}
debug_setfdebug (filename);
keepdays = days;
// path
bool success = bod_document_save(glocal.con,glocal.con_doc,glocal.admin_sessionid.c_str(),path);
rep_savegame(success,"");
// delay:u
glocal.slowplaystep=delay;
endclient = true;
}else if (c->type == TYPE_CLIENT){
glocal.nbrequest_client++;
(this,c->req,line,info.linelen, endserver, endclient,no,c,c->host.c_str());
(glocal.con);
glocal.bod_client.rep_createsession(sessionid);
(glocal.con,sessionid,email,password);
glocal.bod_client.rep_login(success,incomplete);
glocal sessionid;
(glocal.con,sessionid);
if (success){
(glocal.con_doc,glocal.sessionid);
}
glocal string confirmid;
glocal string msg;
("select count(*) from id2name where name not like '%%@%%'");
unsigned nb = atoi(row[0]);
if (nb >= glocal.maxaccts){
glocal.msg = MSG_U(E_MAXACCOUNTS,"Maximum account number reach on this server");
}
if (glocal.msg.size() == 0){
(glocal.con,name,email,password,lang);
glocal.msg = msg;
glocal.confirmid = confirmid;
}
rep_adduser (glocal.confirmid,glocal.msg);
(glocal.con,id,lang);
if (success) bod_record_email (glocal.con,userid,email,glocal.dirserver,glocal.nodename);
glocal.bod_client.rep_confirmuser(success,msg);
(glocal.con,sessionid);
glocal.bod_client.rep_deleteuser (email,confirmid);
(glocal.con,confirmid);
glocal.bod_client.rep_confirmdelete(success,msg);
// sessionid name = success:b dirid msg
glocal string msg;
(glocal.con,glocal.con_sess,sessionid,name,glocal.nodename,glocal.msg);
(con,session,fname);
if (!success) glocal.msg = msg;
(con,session,fname);
if (!success) glocal.msg = msg;
REPLYMSG(rep_mkdir,glocal.msg);
// sessionid name = success:b msg
glocal string msg;
(glocal.con,glocal.con_sess,sessionid,name,glocal.nodename,glocal.msg);
(con,session,fname);
if (!success) glocal.msg = msg;
(con,session,fname);
if (!success) glocal.msg = msg;
REPLYMSG(rep_rmdir,glocal.msg);
// sessionid name content = success:b msg
glocal content;
glocal string msg;
(glocal.con,glocal.con_sess,sessionid,name,glocal.nodename,glocal.msg);
(con,session,fname,glocal.content);
if (!success) glocal.msg = msg;
(con,session,fname,glocal.content);
if (!success) glocal.msg = msg;
REPLYMSG(rep_addfile,glocal.msg);
// sessionid name content:o more:b owner = success:b fileid msg
glocal content;
glocal more;
glocal owner;
glocal string msg;
glocal string handle;
debug_printf ("bob size=%zu\n",content.getsize());
(glocal.con,glocal.con_sess,sessionid,name,glocal.nodename,glocal.msg);
(con,session,fname,glocal.content,glocal.more,glocal.owner);
if (!success){
glocal.msg = msg;
}else{
glocal.handle = handle;
}
glocal server;
glocal username;
glocal session;
(con,session,fname,glocal.content,glocal.more,glocal.owner);
if (!success){
glocal.msg = msg;
}else if (handle[0] != '\0'){
auto rem = make_unique();
rem->username = glocal.username;
rem->addto(glocal.server,handle,glocal.session);
rem_handles[handle] = move(rem);
glocal.handle = handle;
glocal.bod_localremote.donotlogout();
}
if (glocal.msg.size() > 0){
rep_addfile_bob(false,"",glocal.msg);
}else{
rep_addfile_bob(true,glocal.handle,"");
}
// sessionid handle content:o more:b = success:b msg
glocal string msg;
// handle may be a remote_handle.
auto r = rem_handles.find(handle);
if (r != rem_handles.end()){
handle = r->second->handle.c_str(); // Retrieve the local handle.
}
if (handle[0] != '\0'){
(glocal.con,sessionid,handle,content,more);
if (!success) glocal.msg = msg;
}
if (r != rem_handles.end()){
if (glocal.msg.empty()){
bod_remote_appendfile(glocal.con,sessionid,*r->second,glocal.nodename,content,more,glocal.msg);
}
if (glocal.msg.size() > 0){
rem_handles.erase(r);
}
}
REPLYMSG(rep_appendfile,glocal.msg);
// sessionid name = success:b msg
glocal string msg;
(glocal.con,glocal.con_sess,sessionid,name,glocal.nodename,glocal.msg);
glocal fname;
glocal username;
bod_document_save(glocal.con,glocal.con_doc,session,fname);
(con,session,fname);
if (success){
bod_document_delfile (glocal.con_doc,glocal.username,glocal.fname);
}else{
glocal.msg = msg;
}
(con,session,fname);
if (!success) glocal.msg = msg;
REPLYMSG(rep_delfile,glocal.msg);
// sessionid dirname = success:b msg
glocal string msg;
(glocal.con,glocal.con_sess,sessionid,dirname,glocal.nodename,glocal.msg);
(con,session,fname);
if (!success) glocal.msg = msg;
(con,session,fname);
if (!success) glocal.msg = msg;
REPLYMSG(rep_undelete,glocal.msg);
// sessionid name content = success:b msg
glocal content;
glocal string msg;
(glocal.con,glocal.con_sess,sessionid,name,glocal.nodename,glocal.msg);
(con,session,fname,glocal.content);
if (!success) glocal.msg = msg;
glocal server;
glocal username;
(con,session,fname,glocal.content);
if (!success) glocal.msg = msg;
REPLYMSG(rep_modifyfile,glocal.msg);
// sessionid name content:o more:b owner = success:b handle msg
glocal content;
glocal more;
glocal owner;
glocal string msg;
glocal string handle;
(glocal.con,glocal.con_sess,sessionid,name,glocal.nodename,glocal.msg);
(con,session,fname,glocal.content,glocal.more,glocal.owner);
if (!success){
glocal.msg = msg;
}else{
glocal.handle = handle;
}
glocal server;
glocal username;
glocal session;
(con,session,fname,glocal.content,glocal.more,glocal.owner);
if (!success){
glocal.msg = msg;
}else if (handle[0] != '\0'){
auto rem = make_unique();
rem->username = glocal.username;
rem->addto(glocal.server,handle,glocal.session);
rem_handles[handle] = move(rem);
glocal.handle = handle;
glocal.bod_localremote.donotlogout();
}
if (glocal.msg.size() > 0){
rep_modifyfile_bob(false,"",glocal.msg);
}else{
rep_modifyfile_bob(true,glocal.handle,"");
}
// sessionid oldname newname = success:b msg
// Rename file, directory or symlink
glocal oldname;
glocal newname;
glocal string msg;
(glocal.con,glocal.con_sess,sessionid,oldname,glocal.nodename,glocal.msg);
glocal username;
(con,session,fname,glocal.newname);
if (success){
(glocal.con_doc,glocal.oldname,glocal.newname,glocal.username,current_lang);
if (!success){
tlmp_warning ("documentd_client_rename: %s",msg);
glocal.msg = msg;
}
}else{
glocal.msg = msg;
}
string newname = mapfname(glocal.newname);
(con,session,fname,newname);
if (!success) glocal.msg = msg;
REPLYMSG(rep_rename,glocal.msg);
// sessionid srcname srcdate dstname = success:b msg
// Copy file, directory or symlink
glocal srcdate;
glocal dstname;
glocal string msg;
(glocal.con,glocal.con_sess,sessionid,srcname,glocal.nodename,glocal.msg);
(con,session,fname,glocal.srcdate,glocal.dstname);
if (!success) glocal.msg = msg;
string dstname = mapfname(glocal.dstname);
(con,session,fname,glocal.srcdate,dstname);
if (!success) glocal.msg = msg;
REPLYMSG(rep_copy,glocal.msg);
// sessionid name threshold= success:b msg content
glocal READINFO info;
glocal string content;
glocal string msg;
(glocal.con,glocal.con_sess,sessionid,name,true,threshold,glocal.nodename,glocal.msg);
if (!bolixo_isfile(entry.type)){
glocal.msg = MSG_U(E_NOTAFILE,"Entry is not a file");
}else{
glocal entry;
// See readfile_bob for the limit 1 here. This is a patch.
("select content,title,signature,filetype,modifiedby from files where id=%d and modified='%s' limit 1"
,glocal.entry.entryid,glocal.entry.modified.c_str());
if (row[0] != NULL){
glocal.info.title = row[1] != NULL ? row[1] : "";
glocal.info.signature = row[2] != NULL ? row[2] : "";
glocal.info.modified = glocal.entry.modified;
glocal.info.file_type = (FILE_TYPE)atoi(row[3]);
glocal.info.size = strlen(row[0]);
glocal.content = row[0];
glocal.info.owner = bod_getname (glocal.entry.ownerid);
unsigned ownerid = atoi(row[4]);
glocal.info.modifiedby = bod_getname (ownerid);
}else{
glocal.msg = MSG_U(E_USEREADBOB,"Internal error, must use readfile_bob for large content");
}
glocal.msg = "Internal error, reading table files";
}
(con,session,fname,threshold);
if (!success){
glocal.msg = msg;
}else{
glocal.content = content;
glocal.info = info;
}
if (glocal.msg.size()!=0){
rep_readfile(false,glocal.msg,"",glocal.info);
}else{
rep_readfile(true,"",glocal.content,glocal.info);
}
// sessionid name threshold nomore:b = success:b msg content:o handle more:b
glocal bool nomore = nomore;
glocal READINFO info;
glocal ENTRY entry;
glocal name;
glocal sessionid;
// Are we trying to read /projects/user@remote_node/...
// In general, you are trying to read the public photo of the user.
// or the content of a remote project folder.
// Note that this does not apply to message (/msgs/) since they are copied from server to server
// while projects can't since they may be modified.
string remote_user,remote,remote_name;
if (strchr(name,'@')!=nullptr){
const char *user;
if (is_start_any_of(name,user,"/projects/")){
// All failure in this parsing end up with remote_user being empty,
// so the normal case (bod_findentry) is used.
const char *slash = strchr(user,'/');
if (slash != NULL){
string tmp = string(user,slash-user);
string local_user;
bod_parse_user (tmp.c_str(),local_user,remote_user,remote,glocal.nodename);
if (remote_user.size() > 0){
remote_name = string_f("/projects/%s/%s",remote_user.c_str(),slash+1);
}else{
remote_name = string_f("/projects/%s/%s",local_user.c_str(),slash+1);
name = remote_name.c_str();
}
}
}
}
if (remote_user.size() > 0){
auto rem = make_unique();
rem->setfrom (remote);
bool is_admin; // Just a place holder, admin rights have no effect on remote server.
unsigned userid = trli_getsessionuser (glocal.con_sess,sessionid,is_admin, rem->username, glocal.entry.msg);
if (userid != 0){
BOB_TYPE bob;
bool more = false;
READINFO info;
bod_remote_readfile_bob (glocal.con,sessionid
,rem,remote_name,nomore,glocal.nodename
,bob,info,more,glocal.entry.msg);
if (glocal.entry.msg.size() == 0){
// info come from the remote system.
// We have to patch the owner and modifiedby field to reflect that
bod_patchname (info.owner,remote);
bod_patchname (info.modifiedby,remote);
string handle;
if (more){
handle = fs_makeid();
rem_handles[handle] = move(rem);
}
rep_readfile_bob (true,"",bob,info,handle,more);
}
}
}else{
glocal bool redo = false;
do{
glocal.redo = false;
if (bod_findentry (glocal.con_sess,sessionid,name,glocal.entry,threshold)!=-1){
if (!bolixo_isfile(glocal.entry.type)){
glocal.entry.msg = MSG_R(E_NOTAFILE);
}else{
// limit 1 is there because there is a flaw in the bolixo database schema.
// The key to the table files is the id and the modified
// The modified field has a resolution of one second (it is a normal date)
// so if a file is modified twice in the same second, we end up with 2 entries
// with the same key, so the following query would return two values and rep_readfile_bob would
// be called twice.
// So the limit 1 is just a patch to prevent the application from repeating the rep_readfile_bob twice
// (and cause a protocol issue)
("select content,title,signature,filetype,modifiedby from files where id=%d and modified='%s' limit 1"
,glocal.entry.entryid,glocal.entry.modified.c_str());
glocal.info.title = row[1] != NULL ? row[1] : "";
glocal.info.signature = row[2] != NULL ? row[2] : "";
glocal.info.modified = glocal.entry.modified;
glocal.info.file_type = (FILE_TYPE)atoi(row[3]);
glocal.info.owner = bod_getname (glocal.entry.ownerid);
unsigned ownerid = atoi(row[4]);
glocal.info.modifiedby = bod_getname (ownerid);
if (row[0] != NULL){
unsigned len = strlen(row[0]);
glocal.info.size = len;
BOB_TYPE bob (row[0],len,false);
glocal.bod_client.rep_readfile_bob (true,"",bob,glocal.info,"",false);
}else{
if (file_is_doc(glocal.info.file_type)){
glocal.redo = bod_document_save(glocal.con,glocal.con_doc
,glocal.admin_sessionid.c_str(),glocal.name);
}
if (!glocal.redo){
string handle;
FILE *fin = fs_alloc_file_handle (glocal.entry.entryid,glocal.entry.modified,"r",handle,glocal.sessionid);
if (fin == NULL){
glocal.entry.msg = "Internal error, reading content file";
fs_delete_handle (handle);
}else{
struct stat64 st;
if (fstat64(fileno(fin),&st)==-1){
glocal.entry.msg = "Internal error, getting file size";
fs_delete_handle (handle);
}else{
char buf[REQ_CONTENT_CHUNK];
int len = fread (buf,1,sizeof(buf),fin);
BOB_TYPE bob (buf,len,false);
bool more = len==REQ_CONTENT_CHUNK;
if (!more || glocal.nomore){
fs_delete_handle(handle);
handle.clear();
}
glocal.info.size = st.st_size;
glocal.bod_client.rep_readfile_bob (true,""
,bob,glocal.info,handle,more);
}
}
}
}
glocal.entry.msg = "Internal error, reading table files";
}
}
}while (glocal.redo);
}
if (glocal.entry.msg.size()!=0){
rep_readfile_bob(false,glocal.entry.msg,BOB_TYPE(),glocal.info,"",false);
}
// sessionid handle = success:b msg content:o more:b
auto r = rem_handles.find(handle);
if (r != rem_handles.end()){
bool success = false;
string msg;
bool more = false;
BOB_TYPE content;
bod_remote_readmore(r->second,success,msg,content,more);
if (!more){
rem_handles.erase (r);
}
rep_readmore (success,msg,content,more);
}else{
FILE *fin = fs_get_file (handle,sessionid);
if (fin == NULL){
rep_readmore (false,"Internal error, reading content file",BOB_TYPE(),false);
fs_delete_handle (handle);
}else{
char buf[REQ_CONTENT_CHUNK];
int len = fread (buf,1,sizeof(buf),fin);
BOB_TYPE bob (buf,len,false);
bool more = len == REQ_CONTENT_CHUNK;
rep_readmore (true,"Ok",bob,more);
if (!more){
fs_delete_handle(handle);
}
}
}
// sessionid name username listname = success:b msg
(glocal.con,sessionid,name,username,listowner,listname,listmode,owner);
glocal.bod_client.rep_set_access (success,msg);
// sessionid name = success:b msg
glocal string msg;
(glocal.con,glocal.con_sess,sessionid,name,glocal.nodename,glocal.msg);
(con,session,fname);
if (!success) glocal.msg = msg;
(con,session,fname);
if (!success) glocal.msg = msg;
REPLYMSG(rep_markview,glocal.msg);
// sessionid name threshold = success:b msg file:U{FILEINFO}
glocal FILEINFO file;
glocal string msg;
(glocal.con,glocal.con_sess,sessionid,name,true,threshold,glocal.nodename,glocal.msg);
if (threshold[0] == '\0') threshold = END_OF_TIME;
("select dirs_content.name,dirs_content.modified,type,id2name.name"
",group_list_id,group_lists.name,ids.listmode"
",length(files.content),dirs_content.itemid,files.title,dirs_content.eventtime"
",files.filetype,marks.modified"
" from dirs_content"
" join ids on dirs_content.itemid=ids.id"
" join id2name on ids.ownerid=id2name.userid"
" left join group_lists on ids.group_list_id=group_lists.id"
" left join files on dirs_content.itemid=files.id and dirs_content.modified=files.modified"
" left join marks on marks.itemid=dirs_content.itemid and marks.userid=%u"
" where dirid=%d and dirs_content.name='%s' and dirs_content.eventtime <= '%s' order by eventtime desc limit 1"
,entry.userid,entry.dirid,entry.basename.c_str(),threshold);
const char *name = row[0];
const char *modified = row[1];
ENTRY_TYPE type = (ENTRY_TYPE) atoi(row[2]);
const char *owner = row[3];
const char *listid = row[4];
const char *listname = row[5];
const char *listmode = row[6] != NULL ? row[6] : "";
if (listname == NULL){
listname = "";
if (listid != NULL){
if (strcmp(listid,"0")==0){
listname = ALL_MAY_READ;
}
}
}
unsigned size=0;
if (row[7] != NULL){
size = atoi(row[7]);
}else if (bolixo_isfile(type)){
// Content stored as a file
size = fs_get_filesize(atoi(row[8]),modified);
}
const char *title = row[9] == NULL ? "" : row[9];
const char *eventdate = row[10];
FILE_TYPE file_type = row[11] == NULL ? FILE_UNKNOWN : (FILE_TYPE)atoi(row[11]);
VIEWED_STATUS viewed = bod_readviewed(row[12],modified);
if (!bolixo_isdeleted(type)){
glocal.file.name = name;
glocal.file.modified = modified;
glocal.file.type = type;
glocal.file.owner = owner;
glocal.file.listname = listname;
glocal.file.listmode = listmode;
glocal.file.size = size;
glocal.file.title = title;
glocal.file.eventdate = eventdate;
glocal.file.file_type = file_type;
glocal.file.viewed = viewed;
glocal.msg.clear();
}
(con,session,fname,threshold);
if (!success){
glocal.msg = msg;
}else{
glocal.file = file;
}
if (glocal.msg.size() > 0){
glocal.file.clear();
rep_stat (false,glocal.msg,glocal.file);
}else{
rep_stat (true,"",glocal.file);
}
glocal history;
glocal offset;
glocal nb;
glocal bool deletes=false; // Is there some entries to undelete
glocal vector files;
glocal long long start = bod_getnow();
glocal string msg;
(glocal.con,glocal.con_sess,sessionid,name,true,threshold,glocal.nodename,glocal.msg);
glocal map mp;
#ifdef INSTRUMENT
if (f_instrument != NULL){
long long end = bod_getnow();
fprintf (f_instrument,"%Ld listdir:findentry %lf userid=%u dirid=%d name=%s\n"
,glocal.start,(end-glocal.start)/1000000.0,entry.userid,entry.entryid,fname);
glocal.start = end;
}
#endif
if (!bolixo_isdir(entry.type)){
msg = MSG_U(E_NOTDIR,"Is not a directory");
}else if (entry.ownerid != entry.userid && entry.listmode == 'p' && !entry.is_admin){
msg = MSG_U(E_NOTALLOWEDLIST,"Not allowed to list");
}else{
if (threshold[0] == '\0') threshold = END_OF_TIME;
// We read dirs_content.modified twice because we let MariaDB perform the timezone conversion
// but we need the original date as well
("select dirs_content.name,dirs_content.modified,type,id2name.name"
",group_list_id,group_lists.name,ids.listmode"
",length(files.content),dirs_content.itemid,files.title,dirs_content.eventtime"
",files.filetype,files.content is null,marks.modified,id2name2.name"
",convert_tz(dirs_content.modified,'system','%s')"
" from dirs_content"
" join ids on dirs_content.itemid=ids.id"
" join id2name on ids.ownerid=id2name.userid"
" left join group_lists on ids.group_list_id=group_lists.id"
" left join files on dirs_content.itemid=files.id and dirs_content.modified=files.modified"
" left join id2name id2name2 on files.modifiedby=id2name2.userid"
" left join marks on marks.itemid=dirs_content.itemid and marks.userid=%u"
" where dirid=%d and dirs_content.eventtime <= '%s' order by eventtime"
,user_timezone.c_str(),entry.userid,entry.entryid,threshold);
#ifdef INSTRUMENT
if (rownum==0 && f_instrument != NULL){
long long end = bod_getnow();
fprintf (f_instrument,"%Ld listdir:row0 %lf\n",glocal.start,(end-glocal.start)/1000000.0);
glocal.start = end;
}
#endif
const char *name = row[0];
const char *modified = row[1];
ENTRY_TYPE type = (ENTRY_TYPE) atoi(row[2]);
const char *owner = row[3];
const char *listid = row[4];
const char *listname = row[5];
const char *listmode = row[6] != NULL ? row[6] : "";
if (listname == NULL){
listname = "";
if (listid != NULL){
if (strcmp(listid,"0")==0){
listname = ALL_MAY_READ;
}
}
}
unsigned size=0;
if (row[7] != NULL){
size = atoi(row[7]);
}else if (bolixo_isfile(type)){
// Content stored as a file
size = fs_get_filesize(atoi(row[8]),modified);
}
const char *title = row[9] == NULL ? "" : row[9];
const char *eventdate = row[10];
FILE_TYPE file_type = row[11] == NULL ? FILE_UNKNOWN : (FILE_TYPE)atoi(row[11]);
bool islarge = atoi(row[12])!=0;
VIEWED_STATUS viewed = bod_readviewed(row[13],modified);
const char *modifiedby = row[14] != NULL ? row[14] : "";
modified = row[15] != nullptr ? row[15] : "";
if (glocal.history){
FILEINFO info;
info.name = name;
info.eventdate = eventdate;
info.modified = modified;
info.type = type;
info.file_type = file_type;
info.owner = owner;
info.modifiedby = modifiedby;
info.listname = listname;
info.listmode = listmode;
info.size = size;
info.title = title;
info.viewed = viewed;
info.islarge = islarge;
glocal.files.push_back(move(info));
}else{
glocal.mp[name] = DIRENTRY(eventdate,modified,type,file_type,islarge
,owner,modifiedby,listname,listmode,size,title,viewed);
}
#ifdef INSTRUMENT
if (f_instrument != NULL){
long long end = bod_getnow();
fprintf (f_instrument,"%Ld listdir:endsql %lf\n",glocal.start,(end-glocal.start)/1000000.0);
glocal.start = end;
}
#endif
if (!glocal.history){
for (auto &x:glocal.mp){
if (!bolixo_isdeleted(x.second.type)){
FILEINFO info;
info.name = x.first;
info.type = x.second.type;
info.file_type = x.second.file_type;
info.eventdate = x.second.eventdate;
info.modified = x.second.modified;
info.owner = x.second.owner;
info.modifiedby = x.second.modifiedby;
info.listname = x.second.members;
info.listmode = x.second.listmode;
info.size = x.second.size;
info.title = x.second.title;
info.viewed = x.second.viewed;
info.islarge = x.second.islarge;
glocal.files.push_back(move(info));
}else{
glocal.deletes = true;
}
}
}
}
bod_trim (glocal.files,glocal.offset,glocal.nb);
(con,session,fname,threshold,glocal.history,glocal.offset,glocal.nb);
if (!success){
glocal.msg = msg;
}else{
for (auto &f:files) glocal.files.push_back(f);
}
#ifdef INSTRUMENT
if (f_instrument != NULL){
long long end = bod_getnow();
fprintf (f_instrument,"%Ld listdir:rep %lf\n",glocal.start,(end-glocal.start)/1000000.0);
}
#endif
if (glocal.msg.size() > 0){
glocal.files.clear();
rep_listdir (false,glocal.msg,glocal.files,false);
}else{
rep_listdir (true,"",glocal.files,glocal.deletes);
}
// sessionid listname owner = success:b msg
(glocal.con,sessionid,listname,owner);
glocal.bod_client.rep_create_project_dir(success,msg);
// sessionid listname owner = success:b msg
(glocal.con,sessionid,listname,owner);
glocal.bod_client.rep_create_group_list(success,msg);
// sessionid groupname owner = success:b msg
(glocal.con,sessionid,groupname,owner);
glocal.bod_client.rep_create_group(success,msg);
// sessionid listname groupname defaultaccess owner = success:b msg
/*
We are either assigning a group to a list, changing its defaultaccess, or removing it (defaultaccess == '-')
We have to check if this group hold some remote account.
If this is the case, we have to manage the list/group relation on the remote servers.
*/
glocal string msg;
string username;
unsigned userid = bo_writed_get_group_owner (glocal.con_sess, sessionid, owner, username,glocal.msg);
if (userid != 0){
// We have to know if this group was already a member of the list
glocal bool was_member = false;
("select 1 from group_lists"
" join group_list_members on group_list_members.group_list_id = group_lists.id"
" join groups on group_list_members.groupid = groups.id"
" where group_lists.ownerid=%u and groups.name='%s'"
,userid,groupname);
glocal.was_member = true;
(glocal.con,sessionid,listname,groupname,defaultaccess,owner);
glocal.msg = msg;
if (glocal.msg.size() == 0){
// Extract all servers related to the list
// We get the members of the group. From these members, we locate remote users and take note of their server.
// tlmp_warning ("set_member groupname=%s username=%s user=%s access=%s role=%s",groupname,username.c_str(),user,access,role);
map> servers; // Record per remote server accounts
bod_get_remote_user (servers,userid,groupname,glocal.nodename);
if (servers.size() > 0){
// Some job to do on remote servers
bod_remote_set_group (glocal.con,sessionid,username,glocal.nodename
,listname,groupname,servers,defaultaccess,glocal.was_member);
}
}
}
REPLYMSG(rep_set_group,glocal.msg);
// sessionid groupname user access owner = success:b msg
glocal bool success = false;
glocal string msg;
string username;
unsigned userid = bo_writed_get_group_owner (glocal.con_sess, sessionid, owner, username,glocal.msg);
if (userid != 0){
// Extract all members of the group before calling bo-writed.
// We will be able to figure out if a group has to be created or deleted on a remote server
// Note that we may create a project as well on the remote server if this group is a member.
// tlmp_warning ("set_member groupname=%s username=%s user=%s access=%s role=%s",groupname,username.c_str(),user,access,role);
glocal map> servers; // Record per remote server accounts
bod_get_remote_user (glocal.servers,userid,groupname,glocal.nodename);
(glocal.con,sessionid,groupname,user,access,role,owner);
//tlmp_warning ("writed set_member success=%d msg=%s",success,msg);
glocal.success = success;
glocal.msg = msg;
if (glocal.success){
// Check if the caller is a remote user. In that case, we do nothing.
string local_user,remote_user,server;
if (bod_parse_user (username.c_str(),local_user,remote_user,server,glocal.nodename)==-1){
}else if (remote_user.size() == 0){
// Check if user is a remote user. If this is the case, we run the request
// on the remote server.
if (bod_parse_user (user,local_user,remote_user,server,glocal.nodename)!=-1
&& remote_user.size() > 0){
bool create_group = true;
bool delete_group = false;
auto s = glocal.servers.find(server);
if (s != glocal.servers.end()){
create_group = false;
if (strcmp(access,"-")==0 && s->second.size() == 1){
// We delete this remote user from the group and he was the only member on that server
// so we can delete the group
delete_group = true;
}
}
glocal vector remprojects;
if (create_group || delete_group){
// We have to either create or delete the group on the remote server.
// We must find all the projects using this group.
// We must create, delete or modify those projects on the remote server.
("select group_lists.name,group_list_members.defaultaccess from group_lists"
" join group_list_members on group_lists.id=group_list_members.group_list_id"
" join groups on groups.id = group_list_members.groupid"
" where group_lists.ownerid = %u and groups.name='%s' and group_lists.name not like '#%%'"
,userid,groupname);
glocal.remprojects.emplace_back(row[0],row[1]);
}
bod_remote_set_member (glocal.con,sessionid,username,groupname,create_group,delete_group
,remote_user,server,access,role,glocal.nodename,glocal.remprojects);
}
}
}
}
glocal.bod_client.rep_set_member(glocal.success,glocal.msg);
// sessionid groupname members owner = success:b msg
// Not implemented yet in bo-writed.
vector v;
for (auto &m:members) v.push_back(m);
(glocal.con,sessionid,groupname,v,owner);
glocal.bod_client.rep_set_members(success,msg);
// sessionid listname description owner = success:b msg
(glocal.con,sessionid,listname,description,owner);
glocal.bod_client.rep_set_list_desc (success,msg);
// sessionid groupname description owner = success:b msg
(glocal.con,sessionid,groupname,description,owner);
glocal.bod_client.rep_set_group_desc (success,msg);
// sessionid listname owner = success:b msg
(glocal.con,sessionid,listname,owner);
glocal.bod_client.rep_delete_list(success,msg);
// sessionid groupname owner = success:b msg
(glocal.con,sessionid,groupname,owner);
glocal.bod_client.rep_delete_group(success,msg);
// sessionid owner = success:b msg lists:v groups:vv access:v
glocal string msg;
glocal vector lists;
unsigned userid = bo_writed_get_group_owner (glocal.con_sess, sessionid, owner, glocal.msg);
if (userid != 0){
glocal string last_list;
("select group_lists.name,group_lists.description,groups.name,group_list_members.defaultaccess"
" from group_lists"
" left join group_list_members on group_lists.id=group_list_members.group_list_id"
" left join groups on group_list_members.groupid=groups.id"
" where group_lists.ownerid=%u order by group_lists.name,groups.name",userid);
if (strcmp(glocal.last_list.c_str(),row[0])!=0){
glocal.last_list = row[0];
LIST l;
l.name = row[0];
l.description = row[1];
glocal.lists.push_back(l);
}
if (row[1] != NULL && row[2] != NULL){
LIST &l = glocal.lists[glocal.lists.size()-1];
l.groups.push_back(row[2]);
l.access.push_back(row[3]);
}
glocal.msg.clear();
glocal.msg = MSG_U(E_NOLIST,"No list");
}
bool ret = true;
if (glocal.msg.size() > 0){
ret = false;
glocal.lists.clear();
}
rep_list_lists (ret,glocal.msg,glocal.lists);
// sessionid owner = success:b msg members:v
// List all contacts
glocal string msg;
glocal vector members;
unsigned userid = bo_writed_get_group_owner (glocal.con_sess, sessionid, owner, glocal.msg);
if (userid != 0){
("select id2name.name from groups"
" join group_members on group_members.groupid = groups.id"
" join id2name on id2name.userid = group_members.userid"
" where groups.name='contacts' and groups.ownerid=%u",userid);
glocal.members.push_back(row[0]);
}
if (glocal.msg.size() > 0){
glocal.members.clear();
rep_list_contacts(false,glocal.msg,glocal.members);
}else{
rep_list_contacts(true,"",glocal.members);
}
// sessionid owner listname = success:b msg members:v
// List the members of a list. This is allowed if the user is a member or is admin
glocal string msg;
glocal vector members;
glocal bool user_is_member = false;
glocal unsigned userid = bo_writed_get_group_owner (glocal.con_sess, sessionid, owner, glocal.msg);
if (glocal.userid != 0){
int ownerid = fs_rec_getid("select userid from id2name where name='%s'",owner);
if (ownerid == -1){
glocal.msg = MSG_R(E_UNKNOWNUSER);
}else{
int listid = fs_rec_getid("select id from group_lists where ownerid=%d and name='%s'",ownerid,listname);
if (listid == -1){
glocal.msg = string_f(MSG_R(E_PRJNOTEXIST),listname);
}else{
(
"select distinct group_members.userid,id2name.name from group_lists"
" join group_list_members on group_lists.id = group_list_members.group_list_id"
" join groups on groups.id = group_list_members.groupid"
" join group_members on group_members.groupid = groups.id"
" join id2name on id2name.userid=group_members.userid"
" where group_lists.id = %u"
,listid);
unsigned member_id = atoi(row[0]);
if (member_id == glocal.userid) glocal.user_is_member = true;
glocal.members.push_back(row[1]);
}
}
}
if (!glocal.user_is_member){
glocal.msg = MSG_U(E_ACCESS,"Access denied");
}
if (glocal.msg.size() > 0){
glocal.members.clear();
rep_list_members(false,glocal.msg,glocal.members);
}else{
rep_list_members(true,"",glocal.members);
}
// sessionid owner only_onwer:b = success:b msg groups:v users:vv access:vv
// List either all groups own by the logged user
// or all groups it belongs to
glocal string msg;
glocal vector groups;
unsigned userid = bo_writed_get_group_owner (glocal.con_sess, sessionid, owner, glocal.msg);
if (userid != 0){
glocal unsigned last_group_id = (unsigned)-1;
string sql = "select groups.id,groups.name,id2.name,id2name.name,group_members.access,group_members.role,groups.description"
" from groups"
" left join group_members on groups.id=group_members.groupid"
" left join id2name on group_members.userid=id2name.userid"
" left join id2name id2 on groups.ownerid=id2.userid"
" where groups.name != 'contacts' and ";
if (only_owner){
sql += "groups.ownerid=%u";
}else{
sql += "groups.id in (select groupid from group_members where userid=%u)";
}
sql += " order by id2.name,groups.name,id2name.name";
(sql.c_str(),userid);
unsigned group_id = atoi(row[0]);
if (glocal.last_group_id != group_id){
glocal.last_group_id = group_id;
GROUP group;
group.name = row[1];
group.owner = row[2];
group.description = row[6];
glocal.groups.push_back(group);
}
if (row[3] != NULL && row[4] != NULL){
GROUP &g = glocal.groups[glocal.groups.size()-1];
g.users.push_back(row[3]);
g.access.push_back(row[4]);
const char *role = row[5];
if (role == NULL) role = "";
g.roles.push_back(role);
}
glocal.msg.clear();
glocal.msg = MSG_U(E_NOGROUP,"No group");
}
bool ret = true;
if (glocal.msg.size() > 0){
ret = false;
glocal.groups.clear();
}
rep_list_groups (ret,glocal.msg,glocal.groups);
// sessionid owner showroles = success:b msg inboxes:U{INBOX}v
// &INBOX manager project role
glocal string msg;
glocal vector inboxes;
unsigned userid = bo_writed_get_group_owner (glocal.con_sess, sessionid, owner, glocal.msg);
if (userid != 0){
glocal.msg.clear();
vector listids;
fs_list_inboxes (userid,glocal.inboxes,listids,showroles,list_own_projects);
}
bool ret = true;
if (glocal.msg.size() > 0){
ret = false;
glocal.inboxes.clear();
}
rep_list_inboxes (ret,glocal.msg,glocal.inboxes);
// sessionid owner project deleted:b offset:u nb:u = success:b msg messages:U{MESSAGE}v
// &MESSAGE manager project role uuid title submit viewed
// We can extract the messasges for either all inboxes or a single project
// if project is empty
glocal string msg;
glocal vector msgs;
string username;
unsigned userid = bo_writed_get_group_owner (glocal.con_sess, sessionid, owner, username, glocal.msg);
if (userid != 0){
glocal map id2prj;
string dirids;
const char *sep = "";
if (project[0] == '\0'){
int dirid = fs_find_inbox(userid,username.c_str(),false,glocal.msg);
if (dirid != -1){
dirids = string_f("%d",dirid);
sep = ",";
}
}
vector listids;
vector inboxes;
fs_list_inboxes (userid,inboxes,listids,true,false);
for (unsigned i=0; i roles;
roles.push_back("");
if (inb.role.size() > 0) roles.push_back(inb.role.c_str());
int ownerid = fs_rec_getid ("select userid from id2name where name='%s'",manager);
if (ownerid == -1){
glocal.msg = string_f("manager %s unknown",manager);
break;
}else{
INBOX tmp = inb;
for (auto role:roles){
int dirid = fs_find_project_inbox(ownerid, listid, manager,inb.project.c_str(),role,false,glocal.msg);
if (dirid != -1){
tmp.role = role;
glocal.id2prj[dirid] = tmp; //inb;
dirids += string_f ("%s%d",sep,dirid);
sep = ",";
}
}
}
}
if (dirids.size() > 0){
glocal.msg.clear();
("select dirs_content.name,files.title,dirs_content.modified,id2name.name,marks.modified,dirs_content.dirid"
" from dirs_content"
" join files on files.id = dirs_content.itemid and files.modified = dirs_content.modified"
" join ids on dirs_content.itemid = ids.id"
" join id2name on ids.ownerid = id2name.userid"
" left join marks on marks.itemid=dirs_content.itemid and marks.userid=%u"
" where dirid in (%s) and type=%u order by eventtime desc limit %u,%u"
,userid,dirids.c_str(),ENTRY_MSG,offset,nb);
MESSAGE msg;
msg.uuid = row[0];
msg.title = row[1];
msg.submit = row[2];
msg.from = row[3];
msg.viewed = bod_readviewed(row[4],row[2]);
int dirid = atoi(row[5]);
auto pt = glocal.id2prj.find(dirid);
if (pt != glocal.id2prj.end()){
msg.manager = pt->second.manager;
msg.project = pt->second.project;
msg.role = pt->second.role;
}
glocal.msgs.push_back(msg);
}
}
bool ret = true;
if (glocal.msg.size() > 0){
ret = false;
glocal.msgs.clear();
}
rep_list_msgs (ret,glocal.msg,glocal.msgs);
// sessionid owner recipients:v title content = success:b msg msgid
(glocal.con,sessionid,owner,recipients,title,content);
glocal.bod_client.rep_sendmsg(success,msg,msgid);
// sessionid owner manager project role title content = success:b msg msgid
(glocal.con,sessionid,owner,manager,project,role,title,content);
glocal.bod_client.rep_sendmsg_project(success,msg,msgid);
// sessionid owner msgid recipients:v title content = success:b msg replyid
(glocal.con,sessionid,owner,msgid,recipients,title,content);
glocal.bod_client.rep_replymsg(success,msg,replyid);
// sessionid owner manager project role msgid title content = success:b msg replyid
(glocal.con,sessionid,owner,manager,project,role,msgid,title,content);
glocal.bod_client.rep_replymsg_project(success,msg,replyid);
// sessionid owner msgid content:o more:b = success:b msg handle
(glocal.con,sessionid,owner,msgid,content,more);
glocal.bod_client.rep_sendattach(success,msg,handle);
// nickname = pubkey
("select pub_key from id2name where name='%s'",nickname);
glocal.bod_client.rep_getpubkey(false,"user unknown");
if (row[0] == NULL){
glocal.bod_client.rep_getpubkey(false,"no public key");
}else{
glocal.bod_client.rep_getpubkey(true,row[0]);
}
// nickname msg = status:e{ERR_CODE} msg
glocal const char *content = msg;
glocal ERR_CODE status = ERR_CODE_IVLDACCOUNT;
glocal string msg;
("select pub_key from id2name where name='%s'",nickname);
if (row[0] == NULL){
glocal.status = ERR_CODE_CANTVERIFY;
glocal.msg = MSG_U(E_NOPUBKEY,"No public key for this account");
}else{
EVP_PKEY *p = fs_load_public (row[0]);
if (p != NULL){
// Find the signature in the content
const char *pt = strstr(glocal.content,"------\n");
if (pt == NULL){
glocal.status = ERR_CODE_NOSIGFOUND;
glocal.msg = "Signature not found";
}else{
string tmp = string(glocal.content,pt-glocal.content);
string sig = pt+7;
strip_end (sig);
if (fs_verify(tmp,p,sig)==-1){
glocal.status = ERR_CODE_VERIFYFAILED;
glocal.msg = MSG_U(E_SIGNOMATCH,"Signature does not match");
}else{
glocal.status = ERR_CODE_NONE;
glocal.msg = MSG_U(I_SIGOK,"Signature OK");
}
}
fs_free_public (p);
}
}
rep_verifysign (glocal.status,glocal.msg);
// sessionid owner to:v groupname groupowner content:o more filename sign createdby = success:b msg handle
glocal groupname;
glocal groupowner;
glocal const char *sessionid = sessionid;
glocal string msg;
glocal string handle;
glocal to;
glocal content;
glocal string username;
glocal const char *filename = filename;
glocal bool more = more;
glocal const char *createdby = createdby;
unsigned userid = bo_writed_get_group_owner (glocal.con_sess, sessionid, owner, glocal.username, glocal.msg);
if (userid != 0 && bod_check_createdby(glocal.con,createdby,glocal.nodename,glocal.msg)!=-1){
(glocal.con,sessionid,owner,to,groupname,groupowner,content,more,filename,sign,createdby);
glocal.msg = msg;
glocal.handle = handle;
if (success && !bod_is_remote_user(glocal.username)){
/*
Messages propagation. How it works.
Bolixo is a distributed system. Messages are duplicated on servers as needed.
Private messages are simply copied to destination here
(here means in the following code).
bo-writed takes care of private messages distribution for local users and bod
handles the remote users.
Group messages are copied as needed by publishd. bo-writed communicates
with publishd.
There is an exception. When a group message originates on a server which
is not the owner of the group, it is copied here to the owner server.
The owner server will do the propagation to all other members
of the group. This is so because:
-remote server only knowns about their local group members.
They lack the full definition of the group.
-remote servers can't connect to the group owner remote accounts.
In the following code, we extract the remote recipients in glocal.to and
organize them per server. Messages are copied once per server since
Bolixo use shared folders for group messages.
Then we check if this is a remote group name. If so, we copy the message to the
group owner server.
There is an added complexity: the more flag. sendtalk may only
provide the start of the message and appendfile provides the rest.
The REMOTE_HANDLE object keep the stat for each remote destination.
The sendtalk_file has the same logic, but has access to the full content
of the message, so do not use REMOTE_HANDLE.
*/
// tlmp_warning ("propagate message %s from user %s",fileuuid,glocal.username.c_str());
// We have to find if there are remote users and we organize them by servers
auto mp = bod_classify_recipients(glocal.to,glocal.nodename);
string remote_group,remote;
{
string tmpuser;
bod_parse_user(glocal.groupowner,tmpuser,remote_group,remote,glocal.nodename);
}
// tlmp_warning ("mp.size()=%lu remote_user=%s remote=%s username=%s",mp.size(),remote_group.c_str(),remote.c_str(),glocal.username.c_str());
if (mp.size() > 0 || remote_group.size() > 0){
auto rem = make_unique();
rem->username = glocal.username;
if (remote_group.size() > 0) rem->addgroup (glocal.groupname,remote,remote_group);
for (auto p:mp) rem->addto(p.first,p.second.recipients);
bod_remote_sendtalk (glocal.con,glocal.sessionid,*rem,glocal.content,glocal.nodename
,glocal.more,fileuuid,sign,glocal.createdby);
if (handle[0] != '\0'){
rem->handle = handle;
glocal.handle = fs_makeid();
rem_handles[glocal.handle] = move(rem);
}
}
// tlmp_warning ("propagation done");
}
}
if (glocal.msg.size() > 0){
rep_sendtalk (false,glocal.msg,"");
}else{
rep_sendtalk(true,glocal.msg,glocal.handle);
}
// sessionid owner to:v groupname groupowner filename filedate = success:b msg handle
glocal sessionid;
glocal filename;
glocal filedate;
glocal groupname;
glocal owner;
glocal to;
glocal groupowner;
glocal string msg;
(glocal.con,sessionid,owner,to,groupname,groupowner,filename,filedate);
glocal.msg = msg;
if (success){
glocal map mp = bod_classify_recipients(glocal.to,glocal.nodename);
{
string tmpuser,remote_group,remote;
bod_parse_user(glocal.groupowner,tmpuser,remote_group,remote,glocal.nodename);
if (remote_group.size() > 0){
auto &m = glocal.mp[remote];
m.groupname = glocal.groupname;
m.groupowner = remote_group;
}
}
// tlmp_warning ("mp.size()=%lu remote_user=%s remote=%s username=%s",mp.size(),remote_group.c_str(),remote.c_str(),glocal.username.c_str());
if (glocal.mp.size() > 0){
glocal ENTRY entry;
if (bod_findentry (glocal.con_sess,glocal.sessionid,glocal.filename,glocal.entry,glocal.filedate)!=-1){
// See comment about limit 1 in
("select content,signature,id2name.name from files"
" join ids on ids.id=files.id"
" join id2name on id2name.userid=ids.ownerid"
" where files.id=%d and files.modified='%s' limit 1"
,glocal.entry.entryid,glocal.entry.modified.c_str());
const char *signature = row[1] != nullptr ? row[1] : "";
const char *createdby = row[2] != nullptr ? row[2] : "";
string username;
bo_writed_get_group_owner (glocal.con_sess, glocal.sessionid, glocal.owner, username, glocal.msg);
if (row[0] != nullptr){
unsigned len = strlen(row[0]);
BOB_TYPE bob (row[0],len,false);
bod_remote_sendtalk_file(glocal.con,glocal.sessionid
,username,glocal.mp,&bob,nullptr
,glocal.nodename,signature,createdby);
}else{
string handle;
FILE *fin = fs_alloc_file_handle (glocal.entry.entryid,glocal.entry.modified,"r",handle,glocal.sessionid);
if (fin == nullptr){
glocal.msg = "Internal error, reading content file";
}else{
bod_remote_sendtalk_file(glocal.con,glocal.sessionid
,username,glocal.mp,nullptr,fin
,glocal.nodename,signature,createdby);
}
fs_delete_handle (handle);
}
glocal.msg = "Internal error, reading table files";
}
}
}
if (glocal.msg.size() > 0){
rep_sendtalk_file(false,glocal.msg);
}else{
rep_sendtalk_file(true,glocal.msg);
}
// to content:o = success:b msg handle
(glocal.con,sessionid,to,content,more);
glocal.bod_client.rep_sendtalk_anon(success,msg,handle);
// sessionid owner groupname groupowner offset:u nb:u firstseen = success:b msg messages:U{MESSAGE}v deletes:b total:u nbnew:u
/*
firstseen is ID of the message which was the first the last time with made this call.
This establish a context for the current call. The result produced won't be influenced by new
messages which may have shown between this call and the previous. nbnew will show how many
new messages are "hidden".
This functionnality make the user interface more predictable. If you are clicking on the second message of the third
page, the UI will refresh the content to show that the message was viewed. But even if new messages came, the current
message will be the second message of the third page.
The UI present the number of new messages in the header. Clicking on it simply resets firstseen (set to empty string).
*/
glocal const char *groupname = groupname;
glocal const char *groupowner = groupowner;
glocal unsigned offset = offset;
glocal unsigned nb = nb;
glocal bool deletes = false; // Is there some deleted entries
glocal unsigned total = 0; // Total number of records
glocal unsigned nbnew = 0;
glocal fulltext;
glocal firstseen;
glocal string msg;
glocal vector msgs;
glocal unsigned userid;
string username;
glocal.userid = bo_writed_get_group_owner (glocal.con_sess, sessionid, owner, username, glocal.msg);
if (glocal.userid != 0){
// Is the current user a member of the group
("select groups.ownerid from groups"
" join id2name on id2name.userid=groups.ownerid"
" join group_members on group_members.groupid = groups.id"
" where id2name.name='%s' and groups.name='%s' and group_members.userid=%u"
,groupowner,groupname,glocal.userid);
glocal.msg = MSG_U(E_GROUPPROBLEMS,"Unknown group name, empty group, or user not a member");
bod_list_talk (glocal.userid,glocal.msgs,atoi(row[0]),glocal.groupowner,glocal.groupname
,glocal.fulltext,glocal.offset,glocal.nb,glocal.firstseen,glocal.deletes,glocal.total,glocal.nbnew,glocal.msg);
}
if (glocal.msg.size()>0){
glocal.msgs.clear();
rep_list_talk(false,glocal.msg,glocal.msgs,false,0,0);
}else{
rep_list_talk(true,glocal.msg,glocal.msgs,glocal.deletes,glocal.total,glocal.nbnew);
}
// sessionid owner user intro = success:b msg
glocal intro;
glocal owner;
glocal sessionid;
glocal bool success = false;
glocal string msg;
glocal string username;
unsigned userid = bo_writed_get_group_owner (glocal.con_sess, sessionid, owner, glocal.username, glocal.msg);
if (userid != 0){
glocal string remote_user;
glocal string remote;
glocal string local_user;
if (bod_check_remote_user(glocal.con,user,glocal.nodename,glocal.msg
,glocal.local_user,glocal.remote_user,glocal.remote)!=-1){
(glocal.con,sessionid,owner,glocal.local_user,intro);
glocal.success = success;
glocal.msg = msg;
if (success && glocal.remote.size() > 0){
if(bod_remote_contact_request (glocal.con,glocal.sessionid
,glocal.username,glocal.remote_user,glocal.intro,glocal.remote
,glocal.nodename,glocal.msg)==-1){
glocal.success = false;
// We have to undo the local contact request
tlmp_warning ("contact_remove %s",glocal.local_user.c_str());
(glocal.con,glocal.sessionid,glocal.owner,glocal.local_user);
}
}
}
}
glocal.bod_client.rep_contact_request (glocal.success,glocal.msg);
// sessionid owner user status = success:b msg
glocal bool success = false;
glocal string msg;
glocal CONTACT_STATUS status = status;
glocal const char *user = user;
glocal const char *sessionid = sessionid;
glocal string username;
unsigned userid = bo_writed_get_group_owner (glocal.con_sess, sessionid, owner, glocal.username, glocal.msg);
if (userid != 0){
(glocal.con,sessionid,owner,user,status);
glocal.success = success;
glocal.msg = msg;
if (success){
// We check if the target user of this call is a remote user.
// If this is the case, we have to execute the contact_manage on his server.
string tmpuser,remote_user,remote;
if (bod_parse_user(glocal.user,tmpuser,remote_user,remote,glocal.nodename)!=-1
&& remote.size() > 0){
bod_remote_contact_manage (glocal.con,glocal.sessionid,glocal.username,remote_user
,glocal.status,remote,glocal.nodename);
}
}
}
rep_contact_manage (glocal.success,glocal.msg);
// sessionid owner user = success:b msg
glocal bool success = false;
glocal string msg;
string username;
unsigned userid = bo_writed_get_group_owner (glocal.con_sess, sessionid, owner, username, glocal.msg);
if (userid != 0){
(glocal.con,sessionid,owner,user);
glocal.success = success;
glocal.msg = msg;
string tmpuser,remote_user,remote;
if (glocal.success
&& bod_parse_user(user,tmpuser,remote_user,remote,glocal.nodename)!=-1
&& remote.size() > 0){
// The contact is remote. We will try to remove it from the server
bod_remote_contact_remove (glocal.con,sessionid,username,remote_user,remote,glocal.nodename);
}
}
rep_contact_remove (glocal.success,glocal.msg);
// sessionid owner to_me:b contact offset:u nb:u = success:b msg contacts:U{CONTACT}v
// CONTACT user reqdate message status
glocal vector contacts;
glocal string msg;
string username;
unsigned userid = bo_writed_get_group_owner (glocal.con_sess, sessionid, owner, username, glocal.msg);
if (userid != 0){
if (to_me){
string where;
if (contact[0] != '\0'){
int contact_id = fs_rec_getid("select userid from id2name where name='%s'",contact);
if (contact_id != -1){
where = string_f (" and contact_requests.reqid = %d",contact_id);
}
}
// Some people made contact request to me
("select id2name.name,message,status,reqdate from contact_requests"
" join id2name on id2name.userid=contact_requests.reqid"
" where contact_requests.userid=%u %s order by id2name.name limit %u,%u"
,userid,where.c_str(),offset,nb);
CONTACT c;
c.user = row[0];
c.message = row[1];
c.status = (CONTACT_STATUS)atoi(row[2]);
c.reqdate = row[3];
glocal.contacts.push_back(c);
}else{
// I made the request
("select id2name.name,message,status,reqdate from contact_requests"
" join id2name on id2name.userid=contact_requests.userid"
" where contact_requests.reqid=%u order by id2name.name limit %u,%u"
,userid,offset,nb);
CONTACT c;
c.user = row[0];
c.message = row[1];
c.status = (CONTACT_STATUS)atoi(row[2]);
c.reqdate = row[3];
glocal.contacts.push_back(c);
}
}
if (glocal.msg.size() != 0){
glocal.contacts.clear();
rep_contact_list (false,glocal.msg,glocal.contacts);
}else{
rep_contact_list (true,"",glocal.contacts);
}
// sessionid owner = success:b msg config:U{CONFIG}
// &CONFIG lang public_view:b public_dir
glocal string msg;
glocal CONFIG config;
unsigned userid = bo_writed_get_group_owner (glocal.con_sess, sessionid, owner, glocal.msg);
if (userid != 0){
("select lang,dateformat,public_view,public_dir,anon_messages,timezone from config where userid=%u",userid);
glocal.msg = "Internal error, no configuration for user";
glocal.config.lang = row[0];
glocal.config.dateformat = atoi(row[1]);
glocal.config.public_view = atoi(row[2]);
glocal.config.public_dir = row[3];
glocal.config.anon_messages = atoi(row[4]);
glocal.config.timezone = row[5];
}
if (glocal.msg.size() > 0){
rep_config_read (false,glocal.msg,glocal.config);
}else{
rep_config_read (true,glocal.msg,glocal.config);
}
// sessionid user = success:b msg
(glocal.con,sessionid,user);
glocal.bod_client.rep_remote_interest_set(success,msg);
// sessionid user = success:b msg
(glocal.con,sessionid,user);
glocal.bod_client.rep_remote_interest_unset(success,msg);
// sessionid user owner = success:b msg
glocal bool success = false;
glocal string msg;
string username;
unsigned userid = bo_writed_get_group_owner (glocal.con_sess, sessionid, owner, username, glocal.msg);
if (userid != 0){
glocal string remote_user;
glocal string remote;
string local_user(user);
// PATCH: The interest_set use user=inbox to set interest to the user own private inbox
if (strcmp(user,"inbox")==0 || bod_check_remote_user(glocal.con,user,glocal.nodename,glocal.msg
,local_user,glocal.remote_user,glocal.remote)!=-1){
(glocal.con,sessionid,local_user,owner);
if (success && glocal.remote.size() > 0){
bod_remote_interest_set (glocal.con,true,glocal.remote_user,glocal.remote,glocal.nodename);
}
glocal.success = success;
glocal.msg = msg;
}
}
rep_interest_set (glocal.success,glocal.msg);
// sessionid user = success:b msg
glocal string msg;
glocal const char *user = user;
(glocal.con,sessionid,user,owner);
if (!success) glocal.msg = msg;
if(glocal.msg.size() > 0){
rep_interest_unset (false,glocal.msg);
}else{
rep_interest_unset (true,"");
// Now we check if this is a remote user.
// If this is the case and there is no more user interested, we tell the remote system
const char *pt = strchr(user,'@');
if (pt != NULL){
glocal string remote_user;
glocal string remote;
glocal.remote_user = string(user,pt-user);
glocal.remote = pt+1;
("select id2name.userid from id2name join interests on check_userid=id2name.userid"
" where id2name.name='%s' limit 1",user);
bod_remote_interest_set (glocal.con,false,glocal.remote_user,glocal.remote,glocal.nodename);
}
}
// sessionid owner = success:b users:v
glocal string msg;
glocal vector users;
glocal string username;
unsigned userid = bo_writed_get_group_owner (glocal.con_sess, sessionid, owner, glocal.username, glocal.msg);
if (userid != 0){
("select id2name.name,public_view,interests.since from interests"
" join id2name on id2name.userid = interests.check_userid"
" left join config on config.userid = interests.check_userid"
" where interests.userid=%u order by id2name.name",userid);
INTUSER u;
if (glocal.username == row[0]){
// PATCH: The table encode interest for the private inbox
// when check_userid == the current user.
u.name = "inbox";
u.visible = false;
}else{
u.name = row[0];
u.visible = row[1] == nullptr ? 1 : atoi(row[1]) != 0;
}
u.since = row[2];
glocal.users.push_back(u);
}
if (glocal.msg.size() > 0){
glocal.users.clear();
rep_interest_list (false,glocal.msg,glocal.users);
}else{
rep_interest_list (true,glocal.msg,glocal.users);
}
// sessionid owner fulltext:v offset:u nb:u firstseen = success:b msg messages:U{SHORTMSG}v total:u nbnew:u
glocal unsigned nb = nb;
glocal string msg;
glocal map mp;
glocal set fulltext;
glocal unsigned userid = bo_writed_get_group_owner (glocal.con_sess, sessionid, owner, glocal.msg);
for (auto f:fulltext) glocal.fulltext.insert(f);
if (glocal.userid != 0){
glocal long long start = bod_getnow();
DATEASC keepdate;
fdpass_asctime(time(NULL)-keepdays*24*60*60,keepdate);
("select id2name.name,dirs2.name,convert_tz(dirs2.modified,'system','%s')"
",files.filetype,dirs2.itemid,files.content,dirs2.type,files.signature,dirs2.eventtime"
",interests.check_userid,id2owner.name"
" from interests"
" join id2name on id2name.userid = interests.check_userid"
" left join dirs_content dirs2 on interests.dirid=dirs2.dirid and dirs2.eventtime > '%s'"
//" left join dirs_content on interests.dirid=dirs_content.itemid"
//" left join marks on marks.userid=%u and marks.itemid=interests.dirid"
//" left join dirs_content dirs2 on interests.dirid = dirs2.dirid and (marks.modified is null or dirs2.modified > marks.modified)"
" join files on dirs2.itemid = files.id and dirs2.modified = files.modified"
" join ids on dirs2.itemid = ids.id"
" join id2name id2owner on ids.ownerid = id2owner.userid"
" where interests.userid=%u order by dirs2.eventtime",user_timezone.c_str(),keepdate.buf,glocal.userid); //,userid);
#ifdef INSTRUMENT
if (rownum==0 && f_instrument != NULL){
long long end = bod_getnow();
fprintf (f_instrument,"%Ld interest_check:row0 %lf\n",glocal.start,(end-glocal.start)/1000000.0);
}
#endif
// If the dirid is -1 in interests, there nothing to show
// row[1] is null
if (row[1] != NULL){
MAINMSG msg;
// PATCH. When interest.userid==interest.check_userid, it is the user own inbox
unsigned check_userid = atoi(row[9]);
if (check_userid == glocal.userid){
msg.from = row[10];
msg.inbox = true;
}else{
msg.from = row[0];
msg.inbox = false;
}
msg.uuid = row[1];
const char *modified = row[2] != nullptr ? row[2] : "";
msg.submit = modified;
ENTRY_TYPE type = row[6] == NULL ? ENTRY_DELETED : (ENTRY_TYPE)atoi(row[6]);
if (type == ENTRY_DELETED){
msg.file_type = FILE_UNKNOWN;
}else{
msg.file_type = row[3] == NULL ? FILE_UNKNOWN : (FILE_TYPE)atoi(row[3]);
}
msg.signature = row[7];
msg.eventdate = row[8];
if (row[5] != NULL){
// Limit the number of lines if more than one message was requested
if (glocal.nb == 1 || glocal.fulltext.count(msg.uuid) > 0){
msg.content = row[5];
msg.size = msg.content.size();
}else{
msg.content = bod_first_lines(row[5]);
msg.size = strlen(row[5]);
}
}else{
msg.size = fs_get_filesize(atoi(row[4]),modified);
}
glocal.mp[msg.uuid] = move(msg);
}
}
vector msgs;
if (glocal.msg.size() > 0){
rep_interest_check (false,glocal.msg,msgs,0,0);
}else{
for (auto &m:glocal.mp){
if (m.second.file_type != FILE_UNKNOWN){
msgs.push_back(move(m.second));
}
}
sort (msgs.begin(),msgs.end(),[](const MAINMSG &s1, const MAINMSG &s2){return s2.eventdate
// sessionid owner config:U{CONFIG} = success:b msg
glocal sessionid;
(glocal.con,sessionid,owner,config);
if (success) bod_sessions_erase (glocal.sessionid,glocal.control);
glocal.bod_client.rep_config_write (success,msg);
// sessionid owner key = success:b msg ui:b active_ui:b email:b digest:b
glocal string msg;
glocal bool ui = true;
glocal bool active_ui = false;
glocal bool email = false;
glocal bool digest = false;
unsigned userid = bo_writed_get_group_owner (glocal.con_sess, sessionid, owner, glocal.msg);
if (userid != 0){
// If not found in the database, just return the defaults.
("select ui,active_ui,email,digest from notifications where userid=%u and notify_key='%s'"
,userid,key);
glocal.ui = atoi(row[0]);
glocal.active_ui = atoi(row[1]);
glocal.email = atoi(row[2]);
glocal.digest = atoi(row[3]);
}
if (glocal.msg.size() > 0){
rep_get_notification (false,glocal.msg,false,false,false,false);
}else{
rep_get_notification (true,"",glocal.ui,glocal.active_ui,glocal.email,glocal.digest);
}
// sessionid owner key ui:b active_ui:b email:b digest:b = success:b msg
(glocal.con,sessionid,owner,key,ui,active_ui,email,digest);
glocal.bod_client.rep_set_notification(success,msg);
// username = success:b msg exist:b
string msg;
bool pubdir;
if (bod_checkpublic(username,msg,pubdir) != -1){
glocal string website;
glocal string interest;
("select publish,website,interest from id2name"
" join userinfo on userinfo.userid=id2name.userid"
" where id2name.name='%s'",username);
if (atoi(row[0])){
glocal.website = row[1];
glocal.interest = row[2];
}
rep_public_checkuser(true,"",true,pubdir,glocal.website,glocal.interest);
}else{
rep_public_checkuser(true,"",false,false,"","");
}
// username dirpath offset:u nb:u = success:b msg files:U{FILEINFO}v
//glocal const char *username = username;
//glocal const char *dirpath = dirpath;
glocal unsigned offset = offset;
glocal unsigned nb = nb;
glocal string msg;
glocal vector files;
glocal map mp;
ENTRY entry;
string abspath;
entry.userid = bod_checkpublic(username,dirpath,glocal.msg,abspath);
if (entry.userid != (unsigned)-1){
if (fs_findentry(abspath,entry,true,"")==-1){
glocal.msg = entry.msg;
}else if (!bolixo_isdir(entry.type)){
glocal.msg = MSG_R(E_NOTDIR);
}else{
("select dirs_content.name,dirs_content.modified,type,id2name.name"
",group_list_id,group_lists.name,ids.listmode"
",length(files.content),dirs_content.itemid,files.title,dirs_content.eventtime"
",files.filetype,files.content is null"
" from dirs_content"
" join ids on dirs_content.itemid=ids.id"
" join id2name on ids.ownerid=id2name.userid"
" left join group_lists on ids.group_list_id=group_lists.id"
" left join files on dirs_content.itemid=files.id and dirs_content.modified=files.modified"
" where dirid=%d and dirs_content.eventtime <= '%s' order by eventtime"
,entry.entryid,END_OF_TIME);
const char *name = row[0];
const char *modified = row[1];
ENTRY_TYPE type = (ENTRY_TYPE) atoi(row[2]);
const char *owner = row[3];
const char *listid = row[4];
const char *listname = row[5];
const char *listmode = row[6] != NULL ? row[6] : "";
if (listname == NULL){
listname = "";
if (listid != NULL){
if (strcmp(listid,"0")==0){
listname = ALL_MAY_READ;
}
}
}
unsigned size=0;
if (row[7] != NULL){
size = atoi(row[7]);
}else if (bolixo_isfile(type)){
// Content stored as a file
size = fs_get_filesize(atoi(row[8]),modified);
}
const char *title = row[9] == NULL ? "" : row[9];
const char *eventdate = row[10];
FILE_TYPE file_type = row[11] == NULL ? FILE_UNKNOWN : (FILE_TYPE)atoi(row[11]);
bool islarge = atoi(row[12])!=0;
const char *modifiedby = "";
glocal.mp[name] = DIRENTRY(eventdate,modified,type,file_type,islarge
,owner,modifiedby,listname,listmode,size,title,VIEWED_NEW);
for (auto &x:glocal.mp){
if (!bolixo_isdeleted(x.second.type)){
FILEINFO info;
info.name = x.first;
info.type = x.second.type;
info.file_type = x.second.file_type;
info.eventdate = x.second.eventdate;
info.modified = x.second.modified;
info.owner = x.second.owner;
info.modifiedby = x.second.modifiedby;
info.listname = x.second.members;
info.listmode = x.second.listmode;
info.size = x.second.size;
info.title = x.second.title;
info.viewed = x.second.viewed;
info.islarge = x.second.islarge;
glocal.files.push_back(info);
}
}
}
}
if (glocal.msg.size() > 0){
glocal.files.clear();
rep_public_listdir (false,glocal.msg,glocal.files);
}else{
rep_public_listdir (true,"",glocal.files);
}
// username filepath offset:u = success:b msg content:o more:b size:u
glocal unsigned offset = offset;
glocal string msg;
glocal ENTRY entry;
string abspath;
glocal string handle;
glocal.entry.userid = bod_checkpublic(username,filepath,glocal.msg,abspath);
if (glocal.entry.userid != (unsigned)-1){
if (fs_findentry(abspath,glocal.entry,true,"")==-1){
glocal.msg = glocal.entry.msg;
}else if (!bolixo_isfile(glocal.entry.type)){
glocal.msg = MSG_R(E_NOTAFILE);
}else{
("select content,signature,modified,filetype from files where id=%d and modified='%s'"
,glocal.entry.entryid,glocal.entry.modified.c_str());
READINFO info;
info.signature = row[1] != NULL ? row[1] : "";
info.modified = row[2];
info.file_type = (FILE_TYPE)atoi(row[3]);
if (row[0] != NULL){
unsigned len = strlen(row[0]);
BOB_TYPE bob (row[0],len,false);
info.size = len;
glocal.bod_client.rep_public_readfile(true,"",bob,false,info,"");
}else{
FILE *fin = fs_alloc_file_handle (glocal.entry.entryid,info.modified,"r",glocal.handle,"public");
//string path = fs_createpath (glocal.entry.entryid,glocal.entry.modified);
//FILE *fin = fopen (path.c_str(),"r");
if (fin == NULL){
glocal.msg = "Internal error, reading content file";
}else{
struct stat64 st;
if (fstat64(fileno(fin),&st)==-1){
glocal.msg = "Internal error, getting file size";
}else if (glocal.offset >= st.st_size){
glocal.msg = MSG_U(E_OFFSETLARGE,"offset too large, no seek");
}else if (fseek(fin,glocal.offset,SEEK_SET)==-1){
glocal.msg = MSG_U(E_SEEKFAIL,"Seek failed");
}else{
char buf[REQ_CONTENT_CHUNK];
int len = fread (buf,1,sizeof(buf),fin);
BOB_TYPE bob (buf,len,false);
bool more = len==REQ_CONTENT_CHUNK;
if (!more){
fs_delete_handle(glocal.handle);
glocal.handle.clear();
}
info.size = st.st_size;
glocal.bod_client.rep_public_readfile (true,""
,bob,more,info,glocal.handle);
}
}
}
glocal.msg = "Internal error, reading table files";
}
}
if (glocal.msg.size() > 0){
BOB_TYPE empty;
READINFO info;
fs_delete_handle (glocal.handle);
rep_public_readfile(false,glocal.msg,empty,false,info,"");
}
// username offset:u nb:u = success:b msg messages:U{SHORTMSG}v
string msg;
vector msgs;
unsigned userid = bod_checkpublic(username,msg);
if (userid != (unsigned)-1){
bool deletes;
unsigned total;
unsigned nbnew;
vector fulltext;
bod_list_talk (userid,msgs,userid,username,"public",fulltext,offset,nb,"",deletes,total,nbnew,msg);
}
if (msg.size() > 0){
msgs.clear();
rep_public_list_talk(false,msg,msgs);
}else{
rep_public_list_talk(true,msg,msgs);
}
// session form:U{FORMVARS} = success:b msg
// &FORMVAR name val
// &FORMVARS id vars:U{FORMVAR}v
glocal const char *sessionid = sessionid;
glocal const FORMVARS_receive *form = &form;
glocal string msg;
unsigned userid = bo_writed_get_group_owner (glocal.con_sess, sessionid, "", glocal.msg);
if (userid != 0){
glocal unsigned id=(unsigned)-1;
("select id from temp.formids where sessionid='%s' and formid='%s'",sessionid,form.id);
if (sql_action("insert into temp.formids (sessionid,formid) values ('%s','%s')"
,glocal.sessionid,glocal.form->id)==-1){
glocal.msg = "Internal error, can insert into formids";
}else{
glocal.id = query_getdefaultdb()->getlastid();
}
glocal.id = atoi(row[0]);
if (sql_action("delete from temp.formvars where id=%u",glocal.id)==-1){
glocal.msg = "Internal error, can't delete in formvars";
}
if (glocal.id != (unsigned)-1){
for (auto &v:form.vars){
if (sql_action("insert into temp.formvars (id,name,val) values (%u,'%s','%s')"
,glocal.id,v.name,v.val)==-1){
glocal.msg = "Internal error, can't insert in table formvars";
break;
}
}
}
}
if (glocal.msg.size() > 0){
rep_form_savevar(false,glocal.msg);
}else{
rep_form_savevar(true,"");
}
// sessionid id = success:b msg vars:U{FORMVAR}v
glocal vector vars;
glocal string msg;
unsigned userid = bo_writed_get_group_owner (glocal.con_sess, sessionid, "", glocal.msg);
if (userid != 0){
// It is not a bug if there is none
("select id from temp.formids where sessionid='%s' and formid='%s'",sessionid,id);
("select name,val from temp.formvars where id=%s",row[0]);
FORMVAR v;
v.name = row[0];
v.val = row[1];
glocal.vars.push_back(v);
}
if (glocal.msg.size() > 0){
glocal.vars.clear();
rep_form_readvar(false,glocal.msg,glocal.vars);
}else{
rep_form_readvar(true,"",glocal.vars);
}
// sessiond id = success:b msg
glocal string msg;
unsigned userid = bo_writed_get_group_owner (glocal.con_sess, sessionid, "", glocal.msg);
if (userid != 0){
("select id from temp.formids where sessionid='%s' and formid='%s'",sessionid,id);
if (sql_action("delete from temp.formvars where id=%s",row[0])==-1){
glocal.msg = "Internal error, can't delete from formvars table";
}else if (sql_action("delete from temp.formids where id=%s",row[0])==-1){
glocal.msg = "Internal error, can't delete from formids table";
}
}
REPLYMSG(rep_form_deletevar,glocal.msg);
// session = success:b msg
glocal string msg;
unsigned userid = bo_writed_get_group_owner (glocal.con_sess, sessionid, "", glocal.msg);
if (userid != 0){
("select id from temp.formids where sessionid='%s'",sessionid);
if (sql_action("delete from temp.formvars where id=%s",row[0])==-1){
glocal.msg = "Internal error, can't delete from formvars table";
}else if (sql_action("delete from temp.formids where id=%s",row[0])==-1){
glocal.msg = "Internal error, can't delete from formids table";
}
}
REPLYMSG(rep_form_deleteall,glocal.msg);
glocal string pubkey;
("select pub_key from id2name where userid=-2");
if (row[0] != NULL) glocal.pubkey = row[0];
rep_systempubkey (glocal.pubkey);
glocal string msg;
string pubkey;
if (bod_getpubkey(nodename,pubkey)==-1){
glocal.msg = "Can't get public key";
}else{
(glocal.con,nodename,pubkey);
}
REPLYMSG(rep_registernode,glocal.msg);
// msg = sign
glocal string sign;
glocal string msg;
glocal bool success = false;
(glocal.con,msg);
glocal.success = success;
glocal.msg = msg;
glocal.sign = sign;
rep_systemsign (glocal.success,glocal.msg, glocal.sign);
// nodename = success:b msg session
(glocal.con,nodename);
glocal.bod_client.rep_nodelogin(success,msg,session);
// session sign = success:b msg
(glocal.con,session,sign);
glocal.bod_client.rep_nodepass(success,msg);
// user = success:b msg sessionid
// We simply create a session, but we check that the user do exist in id2name.
// The caller will sign the session with the private key and get back
// using remotepass.
glocal string msg;
glocal string sessionid;
string remote_user;
string remote;
string local_user;
// If the remote user does not exist, the account is created on the fly. But the remote user
// has to exist on his server and must have a public key.
if (bod_check_remote_user(glocal.con,user,glocal.nodename,glocal.msg
,local_user,remote_user,remote)!=-1){
if (remote.size() == 0){
glocal.msg = MSG_R(E_IVLDUSER);
}else{
(glocal.con);
glocal.sessionid = sessionid;
}
}
if (glocal.msg.size()>0){
rep_remotelogin (false,glocal.msg,"");
}else{
rep_remotelogin (true,"",glocal.sessionid);
}
// sessionid user sign = success:b msg
glocal string msg;
(glocal.con,sessionid,user,sign);
if (!success) glocal.msg = msg;
REPLYMSG(rep_remotepass,glocal.msg);
// sessionid user owner = success:b msg info:U{USERPUBLICINFO}
glocal USERPUBLICINFO info;
glocal string msg;
unsigned userid = bo_writed_get_group_owner (glocal.con_sess, sessionid, owner, glocal.msg);
if (userid != 0){
("select publish,bosite_visible,fullname,address1,address2"
",city,zipcode,state,country,email,phone,fax,website,interest"
",publish_photo,publish_miniphoto"
" from userinfo where userid=%u",userid);
glocal.info.publish = atoi(row[0]);
glocal.info.bosite_visible = atoi(row[1]);
glocal.info.fullname = row[2];
glocal.info.address1 = row[3];
glocal.info.address2 = row[4];
glocal.info.city = row[5];
glocal.info.zipcode = row[6];
glocal.info.state = row[7];
glocal.info.country = row[8];
glocal.info.email = row[9];
glocal.info.phone = row[10];
glocal.info.fax = row[11];
glocal.info.website = row[12];
glocal.info.interest = row[13];
glocal.info.photo = atoi(row[14]);
glocal.info.mini_photo = atoi(row[15]);
}
if (glocal.msg.size() > 0){
glocal.info.clear();
rep_info_read (false,glocal.msg,glocal.info);
}else{
rep_info_read (true,"",glocal.info);
}
// sessionid user owner info:U{USERPUBLICINFO} = success:b msg
glocal string msg;
string username;
unsigned userid = bo_writed_get_group_owner (glocal.con_sess, sessionid, owner, username,glocal.msg);
if (userid != 0){
(glocal.con,sessionid,owner,info);
glocal.msg = msg;
}
if (glocal.msg.size() != 0){
rep_info_write(false,glocal.msg);
}else{
rep_info_write(true,"");
bod_publish (glocal.con,username,info,glocal.dirserver,glocal.nodename);
}
// connectid sessionid owner path steps:U{VARVAL}v width:u height:u mobile:b = success:b msg res:U{VARVAL}v more:b handle
/*
In a single operation, we request the presentation of a document, and apply a command to it.
*/
glocal connectid;
glocal uispecs;
glocal steps;
glocal vector res;
glocal string msg;
glocal owner;
glocal docnum;
glocal bool more = false;
glocal string handle;
if (glocal.slowplaystep != 0) usleep(glocal.slowplaystep*1000);
(glocal.con,glocal.con_sess,sessionid,path,true,"",glocal.nodename,glocal.msg);
glocal entry;
glocal fname;
if (bod_is_document(entry,glocal.msg)){
vector vsteps;
for (auto &s:glocal.steps) vsteps.push_back(s);
// We may have to call playstep twice because the document might be unknown to documentd
glocal unsigned pass = 0;
do{
glocal.pass++;
(glocal.con_doc,glocal.fname,glocal.docnum,vsteps,current_lang
,entry.may_modify,glocal.connectid,session,username,glocal.uispecs);
if (!success){
if (unknown){
// We must load the document first
bod_document_load (glocal.con_doc,glocal.entry.userid,glocal.fname);
}
}else{
glocal.more = more;
glocal.handle = handle;
glocal.pass = 2;
glocal.msg = msg;
for (auto &r:res){
if (is_any_of(r.var,VAR_NOTIFY,VAR_CHANGES)){
glocal vector userids;
(
"select distinct group_members.userid from group_lists"
" join group_list_members on group_lists.id = group_list_members.group_list_id"
" join groups on groups.id = group_list_members.groupid"
" join group_members on group_members.groupid = groups.id"
" where group_lists.id = %u"
,glocal.entry.group_list_id);
glocal.userids.push_back(atoi(row[0]));
string change;
const char *key = glocal.fname;
if (strcmp(r.var,VAR_CHANGES)==0){
// We remove the current user from the notifications as he created the changes
auto found = find(glocal.userids.begin(),glocal.userids.end(),glocal.entry.userid);
if (found != glocal.userids.end()){
glocal.userids.erase(found);
}
change = string_f("Projects:%s",key+10);
key = change.c_str();
}
(glocal.con_sess,glocal.userids,key,r.val);
if (internal_error) tlmp_error ("bo_sessiond_client_setnotify internal error");
}
glocal.res.push_back(r);
}
}
} while (glocal.pass < 2);
}
vector steps;
for (auto &s:glocal.steps) steps.push_back(s);
(con,session,glocal.owner,fname,glocal.docnum,steps,glocal.uispecs);
if (!success){
glocal.msg = msg;
}else{
glocal.more = more;
glocal.handle = handle;
for (auto &r:res) glocal.res.push_back(r);
}
if (glocal.msg.size() != 0){
glocal.res.clear();
rep_playstep(false,glocal.msg,glocal.res,false,"");
}else{
rep_playstep(true,"",glocal.res,glocal.more,glocal.handle);
}
// sessionid owner path handle = success:b msg res:U{VARVAL}v more:b
glocal vector res;
glocal string msg;
glocal owner;
glocal handle;
glocal bool more = false;
(glocal.con,glocal.con_sess,sessionid,path,true,"",glocal.nodename,glocal.msg);
if (bod_is_document(entry,glocal.msg)){
(glocal.con_doc,glocal.handle);
if (!success){
glocal.msg = msg;
}else{
glocal.more = more;
for (auto &r:res) glocal.res.push_back(r);
}
}
(con,session,glocal.owner,fname,glocal.handle);
if (!success){
glocal.msg = msg;
}else{
glocal.more = more;
for (auto &r:res) glocal.res.push_back(r);
}
if (glocal.msg.size() != 0){
glocal.res.clear();
rep_playstep_more(false,glocal.msg,glocal.res,false);
}else{
rep_playstep_more(true,"",glocal.res,glocal.more);
}
glocal connectid;
glocal c;
glocal string msg;
glocal sequence;
glocal no;
glocal uispecs;
(glocal.con,glocal.con_sess,sessionid,path,true,"",glocal.nodename,glocal.msg);
// We use a separate connection to documentd
CONNECT_INFO con_doc;
con_doc.secret = glocal.con_doc.secret;
con_doc.port = glocal.con_doc.port;
if (glocal.c->documentd_no != -1){
con_doc.fd = glocal.c->documentd_no;
}
documentd_client_waitevent_SEND(con_doc,glocal.connectid,fname,username,glocal.sequence);
//tlmp_warning ("waitevent con.fd=%d %d",con.fd,c->documentd_no);
if (glocal.c->documentd_no == -1){
glocal.c->documentd_no = con_doc.fd;
HANDLE_INFO *cc = new HANDLE_INFO;
cc->type = TYPE_DOCUMENTD;
cc->no = glocal.no;
glocal.TCPSERVER.inject (con_doc.fd,cc);
glocal.TCPSERVER.setmonitormode (con_doc.fd,true);
}
con_doc.fd = -1;
// The waitevent for remote is transformed into a websocket.
// bod_localremote has established a connection/session with the remote
// We will reuse this connection to start the websocket.
int fd;
BIO_get_fd(con.bio,&fd);
glocal.c->documentd_no = fd;
glocal.c->type = TYPE_WEBSOCKET;
HANDLE_INFO *cc = new HANDLE_INFO;
cc->type = TYPE_WEBSOCKET;
cc->no = glocal.no;
cc->wss = make_shared();
cc->wss->moveconnectinfo (con);
cc->wss->setsession(session);
cc->wss->addinitmsg (string_f("gameid=\"%s\" %u %u %u %u %u %u",fname
,glocal.uispecs.width,glocal.uispecs.height,glocal.uispecs.content_width
,glocal.uispecs.content_height,glocal.uispecs.mobile,glocal.uispecs.fontsize));
cc->wss->addinitmsg (string_f("gamesequence=%u",glocal.sequence));
donotlogout();
glocal.TCPSERVER.inject (fd,cc);
glocal.TCPSERVER.setmonitormode (fd,true);
cc->wss->sendheader(server);
// We only reply if there was an error.
if (glocal.msg.size() > 0){
rep_waitevent (false,glocal.msg,"",0,false);
}
tlmp_error ("CLIENT: Invalid command: %s\n",line);
endclient = true;
}else if (c->type == TYPE_DOCUMENTD){
glocal endclient;
glocal int no = c->no;
CONNECT_INFO con;
con.fd = no;
// glocal int count = 0;
do{
(con);
if (internal_error){
glocal.endclient = true;
glocal.TCPSERVER.closeclient (glocal.no);
tlmp_warning ("bod waitevent internal_error\n");
}else{
// tlmp_warning ("bod waitevent ok size=%zu count=%d\n",strlen(script),glocal.count);
bod_client_rep_waitevent(glocal.no,success,msg,script,sequence,false);
}
// glocal.count++;
} while(con.has_more());
con.fd = -1;
}else if (c->type == TYPE_WEBSOCKET){
// Once a websocket is established, we simply copy back and forth. bod is not adding any
// messages.
if (c->wss == nullptr){
HANDLE_INFO *cc = (HANDLE_INFO*)getclientdata(c->documentd_no);
// tlmp_warning ("WEBSOCKET documentd_no=%d linelen=%d",c->documentd_no,info.linelen);
cc->wss->write(line,info.linelen);
}else{
bool now_running = false;
// process handle both websocket initialization
// Once running, it copies back to client.
c->wss->process(endclient,now_running,c->no);
if (now_running){
// Initialization is done, informs the client that from now
// on, the c->no socket carries websocket protocol.
//tlmp_warning ("websocket now_running");
bod_client_rep_waitevent(c->no,true,"","",0,true);
}
}
}else if (c->type == TYPE_ADMIN){
glocal.nbrequest_admin++;
(this,c->req,line, info.linelen,endserver, endclient, no,c,c->host.c_str());
glocal bool sessiond1=false;
glocal bool sessiond2=false;
glocal bool writed = false;
glocal bool bdfiles1 = false;
glocal bool bdfiles2 = false;
glocal bool bdusers = false;
glocal bool keysd = false;
glocal bool fsok = false;
glocal bool publish_dbfiles = false;
glocal bool publish_fsok = false;
glocal bool internal_error1 = false;
glocal bool documentd = false;
glocal unsigned admin_sess_valid = 0;
(glocal.con_sess);
glocal.internal_error1 = internal_error;
glocal.sessiond1 = success;
if (glocal.admin_sessionid.size() > 0){
glocal.admin_sess_valid = 1;
if (glocal.sessiond1){
(glocal.con_sess,glocal.admin_sessionid,false);
if (success) glocal.admin_sess_valid = 2;
}
}
(glocal.con);
glocal.writed = !internal_error;
glocal.sessiond2 = sessiond;
glocal.bdusers = bdusers;
glocal.bdfiles2 = bdfiles;
glocal.keysd = keysd;
glocal.fsok = fsok;
glocal.publish_dbfiles = publish_dbfiles;
glocal.publish_fsok = publish_fsok;
("select count(*) from id2name");
glocal.bdfiles1=true;
(glocal.con_doc);
glocal.documentd = success;
glocal.bod_admin.rep_test(glocal.internal_error1
,glocal.writed,glocal.bdfiles1,glocal.bdfiles2,glocal.bdusers
,glocal.sessiond1,glocal.sessiond2,glocal.keysd,glocal.fsok
,glocal.publish_dbfiles,glocal.publish_fsok,glocal.documentd
,glocal.admin_sess_valid);
tlmp_error ("ADMIN: Invalid command: %s\n",line);
endclient = true;
}
bool some_errors = false;
if (control != NULL && fdpass_setcontrol(s,control,user)==-1){
some_errors = true;
}
if (s.listen(NULL,glocal.unixportadmin)==-1){
tlmp_error ("Can't setup socket on %s (%s)\n",glocal.unixportadmin.c_str(),strerror(errno));
some_errors = true;
}
if (!some_errors && s.is_ok()){
chmod (glocal.unixportclient.c_str()+5,0666);
chmod (glocal.unixportadmin.c_str()+5,0666);
s.setrawmode (true);
if (daemon){
daemon_init(pidfile,user);
}
s.loop();
ret = 0;
}
return ret;
}
int main (int argc, char *argv[])
{
glocal int ret = -1;
glocal const char *admin_secretfile = "/etc/bolixo/secrets.admin";
glocal const char *client_secretfile = "/etc/bolixo/secrets.client";
glocal const char *bind = "0.0.0.0";
glocal const char *port = "9000";
glocal const char *control = "/var/run/bod.sock";
glocal const char *adminhost = "127.0.0.3";
glocal const char *adminport = "9100";
glocal const char *sesshost = "127.0.0.4";
glocal const char *sessport = "9200";
glocal const char *docport = "/dev/documentd.sock";
glocal const char *user = "bolixo";
glocal const char *mysecret = NULL;
glocal const char *dbserv = "localhost";
glocal const char *dbname = "trli";
glocal const char *dbuser = NULL;
glocal const char *sql_tcpport = NULL;
glocal bool daemon = false;
glocal unsigned maxaccts = 200;
glocal int workers = 1;
glocal const char *pidfile = "/var/run/bod.pid";
glocal const char *nodename = NULL; // Who am I
glocal const char *dirserver = "https://bolixo.org"; // Directory server for all nodes
signal (SIGPIPE,SIG_IGN);
translat_setlang("eng,fr");
static const char *tbdict[]={"bolixo","tlmpsql",NULL};
glocal.ret = (argc,argv,tbdict);
setproginfo ("bod",VERSION,"Implement all business logic");
setgrouparg ("Networking");
setarg ('b',"bindaddr","Bind to this address (TCP)",glocal.bind,false);
setarg ('p',"tcpport","Listen for command on this TCP port",glocal.port,false);
setarg ('c',"control","Unix socket for trlid-control",glocal.control,false);
setgrouparg ("Admin server");
setarg (' ',"adminhost","Host running the bo-writed server",glocal.adminhost,false);
setarg (' ',"adminport","Port to reach the bo-writed server",glocal.adminport,false);
setarg (' ',"sesshost","Host running the bo-sessiond server",glocal.sesshost,false);
setarg (' ',"sessport",MSG_U(O_SESSPORT,"Port or Unix socket to reach the bo-sessiond server"),glocal.sessport,false);
setarg (' ',"mysecret",MSG_U(O_MYSECRET,"Secret used to communicate with other services"),glocal.mysecret,true);
setgrouparg ("Misc.");
setarg (' ',"admin_secrets",MSG_U(O_ADMINSECRETS,"File holding admin secrets for communication"),glocal.admin_secretfile,false);
setarg (' ',"client_secrets",MSG_U(O_CLIENTSECRETS,"File holding client secrets for communication"),glocal.client_secretfile,false);
setarg (' ',"user","Run the program as this user",glocal.user,false);
setarg (' ',"daemon","Run in background",glocal.daemon,false);
setarg (' ',"workers","Number of sub-process to lauch",glocal.workers,false);
setarg (' ',"pidfile","File holding the PID of the process",glocal.pidfile,false);
setarg (' ',"nodename",MSG_U(O_NODENAME,"My node name (URL)"),glocal.nodename,true);
setarg (' ',"dirserver",MSG_U(O_DIRSERVE,"Directory server"),glocal.dirserver,false);
setarg (' ',"maxaccts",MSG_U(O_MAXACCOUNTS,"Maximum accounts on this server"),glocal.maxaccts,false);
setarg (' ',"nonstrict",MSG_R(O_NONSTRICT),nonstrict,false);
setarg (' ',"usehttp",MSG_U(O_USEHTTP,"Use HTTP (not HTTPS) to reach this node"),usehttp,false);
setarg (' ',"keepmsgs",MSG_U(O_KEEPMSGS,"Show only messages younger than N days in main screen"),keepdays,false);
setarg (' ',"onlylocal",MSG_U(O_ONLYLOCAL,"Perform on local requests"),onlylocal,false);
setgrouparg ("Database");
setarg (' ',"dbserv","Database server",glocal.dbserv,false);
setarg (' ',"dbname","Database name",glocal.dbname,false);
setarg (' ',"dbuser","Database user",glocal.dbuser,true);
setarg (' ',"sqltcpport","Database TCP port",glocal.sql_tcpport,false);
glocal const char *msg = msg;
("/tmp/err.log",true);
fprintf (fout,"%s\n",glocal.msg);
return 0;
if (glocal.daemon){
syslog (LOG_ERR,"%s",msg);
}else{
fprintf (stderr,"%s",msg);
}
if (glocal.daemon){
syslog (LOG_WARNING,"%s",msg);
}else{
fprintf (stderr,"%s",msg);
}
// For testing purpose
if (strcmp(argv[0],"test_firstlines")==0){
if (argc == 1){
for (auto l:{
"hello how are you ?",
"hello how are you ? hello how are you ? hello how are you ? hello how are you ? hello how are you ? hello how are you ?"
}){
printf ("-----%s\n",l);
string ret = bod_first_lines(l);
printf ("ret=%s\n",ret.c_str());
}
}else{
string line;
for (int i=1; i 0) line += ' ';
line += argv[i];
}
printf ("-----%s\n",line.c_str());
string ret = bod_first_lines(line.c_str());
printf ("ret=%s\n",ret.c_str());
}
}else{
usage();
}
return 0;
glocal CONNECT_INFO con;
glocal CONNECT_INFO con_sess;
glocal CONNECT_INFO con_doc;
glocal map admin_secrets;
glocal map client_secrets;
fdpass_readsecrets (glocal.client_secretfile,glocal.client_secrets);
fdpass_readsecrets (glocal.admin_secretfile,glocal.admin_secrets);
glocal.con.port = glocal.adminport;
glocal.con.secret = glocal.mysecret;
glocal.con.bind = glocal.bind;
glocal.con_sess.port = glocal.sessport;
glocal.con_sess.secret = glocal.mysecret;
glocal.con_sess.bind = glocal.bind;
glocal.con_doc.port = glocal.docport;
glocal.con_doc.secret = glocal.mysecret;
if (glocal.sql_tcpport != NULL) nsql_settcpport (atoi(glocal.sql_tcpport));
{
const char *dbpass = getenv("BOD_PWD");
if (dbpass == NULL){
tlmp_error ("Can't get database password from environment,aborting\n");
exit (-1);
}
query_setdefaultdb (glocal.dbserv,glocal.dbname,glocal.dbuser,dbpass);
}
int ret = -1;
if (glocal.workers==1){
ret = bod_main (glocal.bind,glocal.port,glocal.control
,glocal.con,glocal.con_sess,glocal.con_doc,glocal.user,glocal.pidfile,glocal.daemon
,glocal.admin_secrets,glocal.client_secrets,glocal.nodename,glocal.dirserver
,glocal.maxaccts);
}else{
int port = atoi(glocal.port);
for (int w=0; w
return glocal.ret;
}