#define _GNU_SOURCE #include #include #include #include #include #include #include "broadcast.h" #include "command.h" #include "db.h" #include "event.h" #include "net.h" #include "firebase.h" #include "user_data.h" #include "util.h" static bool cmd_register(struct conn_data *data,const char *tag,const char **args){ i64 userid=db_find_user(args[0]); if(userid!=-1){ net_send_error(data->fd,tag,"Username already exists"); return false; } db_create_user(args[0],args[1]); return net_send_ok(data->fd,tag); } static bool cmd_login(struct conn_data *data,const char *tag,const char **args){ i64 userid=db_find_user(args[0]); if(userid==-1){ net_send_error(data->fd,tag,"User not found"); if(data->userid!=-1){ userdata_unregister(data->userid,data->fd); broadcast_online_change(data->userid); data->userid=-1; } return false; } char *pass=db_get_pass(userid); bool success=strcmp(args[1],pass)==0; free(pass); if(data->userid!=-1){ userdata_unregister(data->userid,data->fd); broadcast_online_change(data->userid); } if(success){ data->userid=userid; userdata_register(userid,data->fd); net_send_ok(data->fd,tag); broadcast_online_change(userid); } else { data->userid=-1; net_send_error(data->fd,tag,"Incorrect password"); } return false; } static bool cmd_logout(struct conn_data *data,const char *tag,const char **args){ (void)args; if(data->userid!=-1){ userdata_unregister(data->userid,data->fd); broadcast_online_change(data->userid); data->userid=-1; } net_send_ok(data->fd,tag); return false; } static bool cmd_list_rooms(struct conn_data *data,const char *tag,const char **args){ (void)args; if(data->userid==-1){ net_send_error(data->fd,tag,"Not logged in"); return false; } struct db_room_list rl=db_list_rooms(data->userid); if(rl.count<=0){ db_nullify_room_list(rl); return net_send_list(data->fd,tag,0,NULL); } const char *names[rl.count]; for(i64 i=0;ifd,tag,rl.count,names); db_nullify_room_list(rl); return closed; } static bool cmd_list_members(struct conn_data *data,const char *tag,const char **args){ if(data->userid==-1){ net_send_error(data->fd,tag,"Not logged in"); return false; } i64 roomid=db_find_room(args[0]); if(roomid==-1){ net_send_error(data->fd,tag,"Room not found"); return false; } if(!db_is_member(roomid,data->userid)){ net_send_error(data->fd,tag,"Not in that room"); return false; } struct db_user_list ul=db_list_members(roomid); if(ul.count<=0){ db_nullify_user_list(ul); return net_send_list(data->fd,tag,0,NULL); } const char *names[ul.count]; for(i64 i=0;ifd,tag,ul.count,names); db_nullify_user_list(ul); return closed; } static bool cmd_create_room(struct conn_data *data,const char *tag,const char **args){ (void)args; if(data->userid==-1){ net_send_error(data->fd,tag,"Not logged in"); return false; } userdata_mark_active(data->userid,data->fd,true); struct db_name_id room=db_create_room(); db_add_member(room.id,data->userid); bool closed=net_send_name(data->fd,tag,room.name); db_nullify_name_id(room); return closed; } static bool cmd_invite(struct conn_data *data,const char *tag,const char **args){ if(data->userid==-1){ net_send_error(data->fd,tag,"Not logged in"); return false; } userdata_mark_active(data->userid,data->fd,true); const char *roomname=args[0]; i64 roomid=db_find_room(roomname); if(roomid==-1){ net_send_error(data->fd,tag,"Room not found"); return false; } i64 user2=db_find_user(args[1]); if(user2==-1){ net_send_error(data->fd,tag,"User not found"); return false; } if(!db_is_member(roomid,data->userid)){ net_send_error(data->fd,tag,"Not in that room"); return false; } if(db_is_member(roomid,user2)){ net_send_error(data->fd,tag,"User already in that room"); return false; } db_add_member(roomid,user2); const char *username=args[1]; char *joinbuf=NULL; i64 joinbuflen=asprintf(&joinbuf,"_push join %s %s\n",roomname,username); char *invitebuf=NULL; i64 invitebuflen=asprintf(&invitebuf,"_push invite %s\n",roomname); event_emit_join(make_timestamp(),username,roomname); struct db_user_list members=db_list_members(roomid); for(i64 i=0;ifd){ if(members.list[i].id==user2){ net_send_raw_text(fds[j],invitebuf,invitebuflen); } else { net_send_raw_text(fds[j],joinbuf,joinbuflen); } } } } db_nullify_user_list(members); free(joinbuf); free(invitebuf); return net_send_ok(data->fd,tag); } static bool cmd_send(struct conn_data *data,const char *tag,const char **args){ if(data->userid==-1){ net_send_error(data->fd,tag,"Not logged in"); return false; } userdata_mark_active(data->userid,data->fd,true); const char *roomname=args[0]; const char *message=args[1]; i64 roomid=db_find_room(roomname); if(roomid==-1){ net_send_error(data->fd,tag,"Room not found"); return false; } if(!db_is_member(roomid,data->userid)){ net_send_error(data->fd,tag,"Not in that room"); return false; } i64 timestamp=make_timestamp(); db_create_message(roomid,data->userid,make_timestamp(),message); bool closed=net_send_ok(data->fd,tag); char *pushbuf=NULL; char *username=db_get_username(data->userid); i64 pushbuflen=asprintf(&pushbuf,"_push message %s %s %" PRIi64 " %s\n", roomname,username,timestamp,message); event_emit_message(timestamp,message,username,roomname); firebase_send_message(roomname,roomid,username,message); free(username); struct db_user_list members=db_list_members(roomid); for(i64 i=0;ifd){ net_send_raw_text(fds[j],pushbuf,pushbuflen); } } } db_nullify_user_list(members); free(pushbuf); return closed; } static bool history_cmd_helper( struct conn_data *data,const char *tag,const char **args, const char *cmdname,i64 beforeid){ char *endp; i64 nrequested=strtoll(args[1],&endp,10); if(args[1][0]=='\0'||*endp!='\0'||nrequested<0){ debug("Connection fd=%d sent an invalid number for '%s': '%s'", data->fd,cmdname,args[1]); return true; } if(data->userid==-1){ net_send_error(data->fd,tag,"Not logged in"); return false; } const char *roomname=args[0]; i64 roomid=db_find_room(roomname); if(roomid==-1){ net_send_error(data->fd,tag,"Room not found"); return false; } if(!db_is_member(roomid,data->userid)){ net_send_error(data->fd,tag,"Not in that room"); return false; } struct db_message_list ml=db_get_messages_before(roomid,nrequested,beforeid); char *buf=NULL; i64 len=asprintf(&buf,"%s history %" PRIi64 "\n",tag,ml.count); bool closed=net_send_raw_text(data->fd,buf,len); free(buf); if(closed){ db_nullify_message_list(ml); return true; } for(i64 i=ml.count-1;i>=0;i--){ char *username=db_get_username(ml.list[i].userid); len=asprintf(&buf,"%s history_message %" PRIi64 " %s %s %" PRIi64 " %" PRIi64 " %s\n", tag,ml.count-1-i,roomname,username,ml.list[i].timestamp, ml.list[i].msgid,ml.list[i].message); closed=net_send_raw_text(data->fd,buf,len); free(buf); if(closed)break; } db_nullify_message_list(ml); return closed; } static bool cmd_history(struct conn_data *data,const char *tag,const char **args){ return history_cmd_helper(data,tag,args,"history",-1); } static bool cmd_history_before(struct conn_data *data,const char *tag,const char **args){ char *endp; i64 beforeid=strtoll(args[2],&endp,10); if(args[2][0]=='\0'||*endp!='\0'){ debug("Connection fd=%d sent an invalid id for 'history_before': '%s'", data->fd,args[2]); return true; } if(beforeid<0)beforeid=INT64_MAX; return history_cmd_helper(data,tag,args,"history_before",beforeid); } static bool cmd_ping(struct conn_data *data,const char *tag,const char **args){ (void)args; return net_send_pong(data->fd,tag); } static bool cmd_is_online(struct conn_data *data,const char *tag,const char **args){ i64 userid=db_find_user(args[0]); if(userid==-1){ net_send_error(data->fd,tag,"User not found"); return false; } i64 nfds; (void)userdata_online(userid,&nfds); return net_send_number(data->fd,tag,nfds); } static bool cmd_firebase_token(struct conn_data *data,const char *tag,const char **args){ if(data->userid==-1){ net_send_error(data->fd,tag,"Not logged in"); return false; } db_add_token(data->userid,args[0]); return net_send_ok(data->fd,tag); } static bool cmd_delete_firebase_token(struct conn_data *data,const char *tag,const char **args){ if(data->userid==-1){ net_send_error(data->fd,tag,"Not logged in"); return false; } db_delete_token(data->userid,args[0]); return net_send_ok(data->fd,tag); } static bool cmd_user_active(struct conn_data *data,const char *tag,const char **args){ if(data->userid==-1){ net_send_error(data->fd,tag,"Not logged in"); return false; } char *endp; i64 active=strtoll(args[0],&endp,10); if(args[0][0]=='\0'||*endp!='\0'||active<0){ debug("Connection fd=%d sent an invalid number for 'user_active': '%s'",data->fd,args[0]); return true; } userdata_mark_active(data->userid,data->fd,active>0); return net_send_ok(data->fd,tag); } struct cmd_info{ const char *cmdname; int nargs; bool longlast; // whether the last argument should span the rest of the input line bool (*handler)(struct conn_data *data,const char *tag,const char **args); }; static const struct cmd_info commands[]={ {"register",2,true,cmd_register}, {"login",2,true,cmd_login}, {"logout",0,false,cmd_logout}, {"list_rooms",0,false,cmd_list_rooms}, {"list_members",1,false,cmd_list_members}, {"create_room",0,false,cmd_create_room}, {"invite",2,false,cmd_invite}, {"send",2,true,cmd_send}, {"history",2,false,cmd_history}, {"history_before",3,false,cmd_history_before}, {"ping",0,false,cmd_ping}, {"is_online",1,false,cmd_is_online}, {"firebase_token",1,false,cmd_firebase_token}, {"delete_firebase_token",1,false,cmd_delete_firebase_token}, {"user_active",1,false,cmd_user_active}, }; #define NCOMMANDS (sizeof(commands)/sizeof(commands[0])) bool handle_input_line(struct conn_data *data,char *line,size_t linelen){ line[linelen]='\0'; char *sepp=memchr(line,' ',linelen); if(sepp==NULL){ debug("No space in input line from connection %d",data->fd); return true; } char *tag=line; size_t taglen=sepp-tag; *sepp='\0'; line+=taglen+1; linelen-=taglen+1; sepp=memchr(line,' ',linelen); if(sepp==NULL)sepp=line+linelen; size_t cmdlen=sepp-line; size_t cmdi; for(cmdi=0;cmdifd); return true; } int nargs=commands[cmdi].nargs; char *args[nargs]; size_t cursor=cmdlen+1; for(int i=0;ilinelen){ debug("Connection %d sent too few parameters to command %s",data->fd,commands[cmdi].cmdname); return true; } if(i==nargs-1&&commands[cmdi].longlast){ sepp=line+linelen; } else { sepp=memchr(line+cursor,' ',linelen-cursor); if(sepp==NULL)sepp=line+linelen; } *sepp='\0'; args[i]=line+cursor; cursor=sepp-line+1; } if(sepp-line<(i64)linelen){ debug("Connection %d sent too many parameters to command %s",data->fd,commands[cmdi].cmdname); return true; } return commands[cmdi].handler(data,tag,(const char**)args); }