/* 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 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include "filesystem.h" #include "bolixo.m" using namespace std; static int noproc = 0; void fs_set_noproc (int _noproc) { noproc = _noproc; } /* Create a uniq ID combining the process number of this instance, a random value and the time in micro-seconds */ string fs_makeid () { string ret; //char tmp[100]; struct timeval t; if (gettimeofday(&t,NULL)!=-1){ static int fd = -1; static bool error_shown = false; if (fd == -1){ fd = open ("/dev/urandom",O_RDONLY,0); } if (fd == -1){ if (!error_shown){ tlmp_error ("Can't open /dev/urandom (%s)\n",strerror(errno)); error_shown = true; } }else{ struct { char buf[8]; unsigned tv_sec; unsigned tv_usec; } data; data.tv_sec = t.tv_sec; data.tv_usec = t.tv_usec; //tlmp_error ("sizeof data=%lu %lu\n",sizeof(data),sizeof(data.tv_sec)); if (read(fd,data.buf,8)!=8){ close (fd); fd = -1; if (!error_shown){ tlmp_error ("Can't read 8 bytes from /dev/urandom (%s)\n",strerror(errno)); error_shown = true; } }else{ #if 0 for (int i=0; i<8; i++) snprintf (tmp+i*2,3,"%02x",buf[i]); int n = snprintf (tmp+16,sizeof(tmp)-16-1,"%08lx%08lx",t.tv_sec,t.tv_usec); snprintf (tmp+16+n,sizeof(tmp)-16-n-1,"-%d",noproc); #elif 0 // Base64 is no good because it puts / in the result char *enc = base64_encode ((const char *)&data,17); //sizeof(data)); ret = enc; free (enc); #else static const char *lk = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789_="; const unsigned char *pt = (unsigned char*)&data; char out[17*2]; char *ptout = out; // tv_usec is an unsigned, but microseconds goes to 1,000,000. // So the mist significant byte is always 0. // So we processed only the first 15 bytes of the structure // and add noproc at the end. for (unsigned i=0; i<15; i+=3){ // First 6 bits unsigned char byte = *pt++; unsigned byte64 = byte >>2; *ptout++ = lk[byte64]; // Last 2 bits and 4 first bits of the next byte byte64 = (byte&3)<<4; byte = *pt++; byte64 |= byte >> 4; *ptout++ = lk[byte64]; // Last 4 bits and 2 first bits of the next byte byte64 = (byte&15)<<2; byte = *pt++; byte64 |= (byte >> 6); *ptout++ = lk[byte64]; // Last 6 bits of the byte byte64 = byte & 63; *ptout++ = lk[byte64]; } *ptout++ = lk[noproc]; *ptout = '\0'; ret = out; #endif } } } return ret; } static int fs_locate_entry (int dirid, PARAM_STRING fname, ENTRY_TYPE &type, string &modified, unsigned &ownerid, unsigned &group_list_id, char &listmode, const char *threshold) { glocal int ret = -1; glocal ENTRY_TYPE type=ENTRY_NONE; glocal unsigned ownerid=(unsigned)-1; glocal unsigned group_list_id=(unsigned)-1; glocal char listmode = ' '; glocal string *modified = &modified; ("select itemid,type,modified,ownerid,group_list_id,listmode from dirs_content" " join ids on dirs_content.itemid=ids.id" " where dirid=%d and name='%s' and eventtime <= '%s' order by eventtime desc limit 1" ,dirid,fname.ptr,threshold); ENTRY_TYPE type = (ENTRY_TYPE)atoi(row[1]); if (type != ENTRY_DELETED){ glocal.ret = atoi(row[0]); glocal.type = type; (*glocal.modified)=row[2]; glocal.ownerid = atoi(row[3]); glocal.group_list_id = row[4] != NULL ? atoi(row[4]) : (unsigned)-1; glocal.listmode = row[5][0]; } type = glocal.type; ownerid = glocal.ownerid; group_list_id = glocal.group_list_id; listmode = glocal.listmode; return glocal.ret; } static bool fs_check_access(unsigned userid, unsigned ownerid, unsigned group_list_id, const char listmode, bool is_admin, bool &may_modify) { glocal bool ret = false; glocal bool may_modify = false; if (is_admin){ glocal.ret = true; glocal.may_modify = true; }else if (userid == ownerid){ glocal.ret = true; glocal.may_modify = true; }else if (group_list_id == 0){ glocal.ret = true; if (listmode != 'p' && listmode != 'r') glocal.may_modify = true; }else if (group_list_id != (unsigned)-1){ ("select group_list_members.defaultaccess,group_members.access from group_list_members" " join group_members on group_list_members.groupid = group_members.groupid" " where group_list_members.group_list_id=%u and userid=%u" ,group_list_id,userid); glocal.ret = true; const char defaultaccess = toupper(row[0][0]); const char access = toupper(row[1][0]); if (access != '\0'){ if (access != 'R') glocal.may_modify = true; }else if (defaultaccess != 'R'){ glocal.may_modify = true; } if (listmode == 'r') glocal.may_modify = false; } may_modify = glocal.may_modify; return glocal.ret; } int fs_insert_entry (int parentid,int id,PARAM_STRING modified,PARAM_STRING name, ENTRY_TYPE type, unsigned copiedby) { return sql_action("insert into dirs_content (dirid,itemid,modified,type,name,copiedby) values (%d,%d,'%s',%u,'%s',%u)" ,parentid,id,modified.ptr,type,name.ptr,copiedby); } int fs_insert_entry (int parentid,int id,PARAM_STRING modified,PARAM_STRING name, ENTRY_TYPE type) { return sql_action("insert into dirs_content (dirid,itemid,modified,type,name) values (%d,%d,'%s',%u,'%s')" ,parentid,id,modified.ptr,type,name.ptr); } int fs_insert_file (int parentid,int fileid,PARAM_STRING modified,PARAM_STRING name) { return sql_action("insert into dirs_content (dirid,itemid,modified,type,name) values (%d,%d,'%s',%u,'%s')" ,parentid,fileid,modified.ptr,ENTRY_FILE,name.ptr); } int fs_insert_file (int parentid,int fileid,PARAM_STRING name) { return sql_action("insert into dirs_content (dirid,itemid,modified,type,name) values (%d,%d,now(),%u,'%s')" ,parentid,fileid,ENTRY_FILE,name.ptr); } int fs_insert_dir (int parentid,int dirid,PARAM_STRING modified,PARAM_STRING name) { return sql_action("insert into dirs_content (dirid,itemid,modified,type,name) values (%d,%d,'%s',%u,'%s')" ,parentid,dirid,modified.ptr,ENTRY_DIR,name.ptr); } int fs_insert_dir (int parentid,int dirid,PARAM_STRING name) { return sql_action("insert into dirs_content (dirid,itemid,modified,type,name) values (%d,%d,now(),%u,'%s')" ,parentid,dirid,ENTRY_DIR,name.ptr); } int fs_insert_deleted (int parentid,int id,PARAM_STRING name, PARAM_STRING modified) { return sql_action("insert into dirs_content (dirid,itemid,modified,type,name) values (%d,%d,'%s',%u,'%s')" ,parentid,id,modified.ptr,ENTRY_DELETED,name.ptr); } struct DIRINFO{ string name; int parentid; int dirid; unsigned ownerid; unsigned group_list_id; char listmode; ENTRY_TYPE type; // ENTRY_DIR or ENTRY_DELETED DIRINFO(const char *_name, int _parentid, int _dirid, unsigned _ownerid, unsigned _group_list_id, char _listmode, ENTRY_TYPE _type){ name = _name; parentid = _parentid; dirid = _dirid; ownerid = _ownerid; group_list_id = _group_list_id; listmode = _listmode; type = _type; } }; /* Locate a directory and retuns its id */ int fs_locate_dir ( const vector &tb, unsigned userid, // Check access for this userid bool is_admin, string &msg, const char *threshold, bool &may_add, bool create_missing, const vector *listids, const vector *listmodes) { int ret = -1; if (tb.size()==0){ // root dir ret = 0; if (is_admin) may_add = true; }else{ glocal vector dirs; int parentid = 0; string tmp; NSQL_REQ req; //string owner_cmp; // = is_admin ? "" : string_f ("(ownerid=1 or ownerid=%u) and",userid); req.appendf ("select name,dirid,itemid,ids.ownerid,ids.group_list_id,ids.listmode,type" " from dirs_content join ids on ids.id=itemid where eventtime <= '%s' and (type=%u or type=%u) and name in " ,threshold,ENTRY_DIR,ENTRY_DELETED); req.appendlist(tb); req.append (" order by eventtime"); (req); unsigned group_list_id = row[4] == NULL ? (unsigned)-1 : atoi(row[4]); glocal.dirs.emplace_back(row[0],atoi(row[1]),atoi(row[2]),atoi(row[3]),group_list_id,row[5][0],(ENTRY_TYPE)atoi(row[6])); // We have to remove entries in glocal.dirs which are followed by a deleted entry // This is ineficient. One day, a special flag will be added to DIRS_CONTENT to indicate if this entry has been // overriden. for (unsigned i=0; i=0; j--){ DIRINFO &info2 = glocal.dirs[j]; if (info.parentid == info2.parentid && info.dirid == info2.dirid){ info.name.clear(); info2.name.clear(); break; } } } } for (unsigned i=0; i static int fs_locate_dir ( const vector &tb, unsigned userid, // Check access for this userid bool is_admin, string &msg, const char *threshold, bool &may_add) { return fs_locate_dir (tb,userid,is_admin,msg,threshold,may_add,false,NULL,NULL); } static int fs_parse_fname (const char *name, vector &parentdirs, string &basename) { int ret = -1; parentdirs.clear(); basename.clear(); if (name[0] == '/'){ const char *start = name +1; while (1){ const char *pt = start; while (*pt != '\0' && *pt != '/') pt++; if (pt == start) break; if (*pt == '/'){ parentdirs.push_back(string(start,pt-start)); start = pt+1; }else{ basename = start; ret = 0; break; } } } return ret; } int fs_findentry (PARAM_STRING path, ENTRY &entry, bool expect_exist, const char *threshold) { int ret = -1; if (threshold[0] == '\0'){ threshold = END_OF_TIME; } vector parents; const char *name = path.ptr; if (name[0] == '/' && name[1] == '\0'){ entry.dirid = 0; entry.entryid = 0; entry.type = ENTRY_DIR; entry.listmode = 'p'; entry.ownerid = 0; entry.group_list_id = 0; if (!expect_exist){ entry.msg = MSG_U(E_ALREADY,"Entry with that name exists"); }else{ ret = 0; } }else if (fs_parse_fname (name,parents,entry.basename) == -1){ entry.msg = MSG_U(E_IVLDNAME,"Invalid name"); }else{ entry.dirid = fs_locate_dir (parents,entry.userid,entry.is_admin,entry.msg,threshold,entry.may_add); if (entry.dirid != -1){ const char *fname = entry.basename.c_str(); entry.entryid = fs_locate_entry (entry.dirid,fname,entry.type,entry.modified,entry.ownerid,entry.group_list_id,entry.listmode,threshold); if (entry.entryid == -1){ if (expect_exist){ entry.msg = string_f(MSG_R(E_DOESNOTEXIST),fname); }else if (!entry.may_add){ entry.msg = MSG_U(E_NOTALLOWCREATE,"File does not exist, not allowed to create"); }else{ ret = 0; } }else if (!expect_exist){ entry.msg = MSG_R(E_ALREADY); }else if (!fs_check_access(entry.userid,entry.ownerid,entry.group_list_id,entry.listmode,entry.is_admin,entry.may_modify)){ entry.msg = MSG_U(E_NOMODIFY,"File exist, not allowed to modify"); }else{ ret = 0; } } } return ret; } struct HANDLE{ int fileid; string modified; FILE *f; string sessionid; // A handle may only be used by the same sessionid // Extra information kept when talking to publishd int ownerid; int dirid; string name; HANDLE (int _fileid, PARAM_STRING _modified, FILE *_f, PARAM_STRING _sessionid){ fileid = _fileid; modified = _modified.ptr; f = _f; sessionid = _sessionid.ptr; dirid = -1; ownerid = -1; } HANDLE(){ fileid = -1; f = NULL; dirid = -1; ownerid = -1; } void close(){ if (f != NULL) fclose (f); } }; static map handles; unsigned fs_getnbhandle() { return handles.size(); } string fs_createpath (int fileid, PARAM_STRING modified) { const char *src = modified.ptr; char tmp[strlen(src)+1]; char *dst = tmp; while (*src != '\0'){ char car = *src++; if (car == '/' || car == '-'){ *dst = '-'; }else if (car == ' ' || car == '_'){ *dst = '-'; }else{ *dst = car; } dst++; } *dst = '\0'; return string_f ("/var/lib/bolixo/%d-%s",fileid,tmp); } /* Assign a file handle for one instance of a file */ FILE *fs_alloc_file_handle (int fileid, PARAM_STRING modified, const char *mode, string &handle, const char *sessionid) { handle = fs_makeid (); string path = fs_createpath (fileid,modified); FILE *ret = fopen (path.c_str(),mode); //tlmp_error ("alloc_file_handle ret=%p path=%s (%s)\n",ret,path.c_str(),strerror(errno)); if (ret != NULL){ handles[handle] = HANDLE(fileid,modified,ret,sessionid); }else{ handle.clear(); } return ret; } void fs_file_handle_addextra (PARAM_STRING handle, int ownerid, int dirid, PARAM_STRING name) { auto h = handles.find(handle.ptr); if (h != handles.end()){ h->second.ownerid = ownerid; h->second.dirid = dirid; h->second.name = name.ptr; }else{ tlmp_error ("Adding to unknown file handle\n"); } } int fs_file_handle_getextra (PARAM_STRING handle, int &ownerid, int &dirid, int &fileid, string &modified, string &name) { int ret = -1; auto h = handles.find(handle.ptr); if (h != handles.end()){ ret = 0; ownerid = h->second.ownerid; dirid = h->second.dirid; fileid = h->second.fileid; modified = h->second.modified; name = h->second.name; } return ret; } long fs_get_filesize (int fileid, PARAM_STRING modified) { long ret = -1; string path = fs_createpath (fileid,modified); struct stat64 st; if (stat64(path.c_str(),&st)!=-1) ret = st.st_size; return ret; } FILE *fs_get_file (const std::string &handle, const char *sessionid) { FILE *ret = NULL; auto x = handles.find(handle); if (x != handles.end() && strcmp(x->second.sessionid.c_str(),sessionid)==0){ ret = x->second.f; } return ret; } void fs_delete_handle (PARAM_STRING handle) { auto x = handles.find(handle.ptr); if (x != handles.end()){ x->second.close(); handles.erase (x); } } void fs_list_inboxes (unsigned userid, vector &inboxes, vector &listids, bool showroles) { glocal vector *inboxes = &inboxes; glocal vector *listids = &listids; glocal bool showroles = showroles; const char *rolestr = showroles ? ",group_members.role" : ""; ("select distinct id2name.name,group_lists.name,group_lists.id%s" " from group_members" " join group_list_members on group_list_members.groupid = group_members.groupid" " join group_lists on group_list_members.group_list_id=group_lists.id" " join id2name on group_lists.ownerid=id2name.userid" " join groups on groups.id = group_members.groupid" " where group_members.userid = %u and group_lists.name not like '#%%' and groups.name != 'contacts'" " order by id2name.name,group_lists.name,group_members.role" ,rolestr,userid); INBOX inb; inb.manager = row[0]; inb.project = row[1]; if (glocal.showroles && row[3] != NULL) inb.role = row[3]; glocal.inboxes->push_back(inb); glocal.listids->push_back(atoi(row[2])); } /* Initialise now with the current date and time */ void fs_set_now (DATEASC &now) { fdpass_asctime(time(NULL),now); } /* Allocate a new entry in table ids. */ int fs_newid (unsigned userid, unsigned listid, char listmode, string &msg, string &uuid) { int ret = -1; uuid = fs_makeid(); char listid_str[30]; if (listid == (unsigned)-1){ strcpy (listid_str,"NULL"); }else{ snprintf (listid_str,sizeof(listid_str)-1,"%u",listid); } if (sql_action("insert into ids (ownerid,group_list_id,listmode,uuid) values (%u,%s,'%c','%s')",userid,listid_str,listmode,uuid.c_str())==-1){ msg = "Internal error (ids table)"; }else{ ret = sql_getlastid(); } return ret; } int fs_newid (unsigned userid, unsigned listid, char listmode, string &msg) { string uuid; return fs_newid(userid,listid,listmode,msg,uuid); } int fs_newid (unsigned userid, string &msg, string &uuid) { return fs_newid (userid,(unsigned)-1,' ',msg,uuid); } int fs_newid (unsigned userid, string &msg) { string uuid; return fs_newid (userid,(unsigned)-1,' ',msg,uuid); } /* Find the directory ID of the outbox for this user. The directory is created as needed. */ unsigned fs_find_outbox (unsigned ownerid, PARAM_STRING name, string &msg) { vector dirs = {"msgs",name.ptr,"outbox"}; vector listids = {0,0,(unsigned)-1}; vector listmodes = {'p','p','p'}; bool may_add; return fs_locate_dir (dirs,ownerid,true,msg,END_OF_TIME,may_add,true,&listids,&listmodes); } /* Find the directory ID of the inbox for this user. The directory is created as needed. */ unsigned fs_find_inbox (unsigned ownerid, PARAM_STRING name, bool create, string &msg) { vector dirs = {"msgs",name.ptr,"inbox"}; vector listids = {0,0,(unsigned)-1}; vector listmodes = {'p','p','p'}; bool may_add; return fs_locate_dir (dirs,ownerid,true,msg,END_OF_TIME,may_add,create,&listids,&listmodes); } /* Find the directory ID of the short message inbox for this user and group. The directory is created as needed. */ unsigned fs_find_short_inbox (unsigned ownerid, PARAM_STRING username, PARAM_STRING groupname, string &msg, bool create, bool &created) { bool may_add; glocal unsigned ownerid = ownerid; glocal string *msg = &msg; glocal const char *groupname = groupname.ptr; glocal vector dirs = {"msgs",username.ptr,"short-inbox",groupname.ptr}; glocal unsigned ret = fs_locate_dir (glocal.dirs,ownerid,true,msg,END_OF_TIME,may_add,false,NULL,NULL); created = false; if (glocal.ret == (unsigned)-1 && create){ // We must lookup the ID of the special list called #groupname, holding only the group groupname ("select id from group_lists where ownerid=%u and name='#%s'",ownerid,groupname.ptr); vector listids = {0,0,0,(unsigned)atoi(row[0])}; vector listmodes = {'p','p','p','r'}; bool may_add; glocal.msg->clear(); glocal.ret = fs_locate_dir (glocal.dirs,glocal.ownerid,true,*glocal.msg,END_OF_TIME,may_add,true,&listids,&listmodes); (*glocal.msg) = string_f("Internal error, no #%s list",glocal.groupname); if (glocal.ret != (unsigned)-1) created = true; } return glocal.ret; } unsigned fs_find_short_inbox (unsigned ownerid, PARAM_STRING username, PARAM_STRING groupname, string &msg) { bool created; return fs_find_short_inbox(ownerid,username,groupname,msg,false,created); } unsigned fs_find_project_inbox (unsigned ownerid, unsigned listid, const char *name, const char *project, const char *role, bool create, string &msg) { vector dirs = {"msg-projects",name,project}; if (role[0] != '\0') dirs.push_back(role); vector listids = {0,0,listid,listid}; vector listmodes = {'p','p','r','r'}; bool may_add; return fs_locate_dir (dirs,ownerid,true,msg,END_OF_TIME,may_add,create,&listids,&listmodes); } /* Return the numerical id if a record exist Return -1 if the record does not exist */ int fs_rec_getid(const char *query, ...) { va_list list; va_start (list,query); glocal int ret = -1; (query,list); glocal.ret = atoi(row[0]); va_end (list); return glocal.ret; } string toupper (PARAM_STRING ss) { const char *s = ss.ptr; string ret; while (*s != '\0'){ ret += toupper(*s); s++; } return ret; }