/*************************************************************************** FILE: cl_board.cc LVU : 1.3.9 DESC: This contains the code to maintain and run the message boards belonging to the various groups. A lot of the code here is identical to cl_board.cc but there is enough difference to make code sharing difficult. Copyright (C) Neil Robertson 2003-2005 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ***************************************************************************/ #include "globals.h" /*** Constructor ***/ cl_board::cl_board(cl_group *grp) { cl_msginfo *minfo; cl_splitline *sl; struct tm *tms; FILE *fp; char path[MAXPATHLEN]; char line[ARR_SIZE]; char *comlist[] = { "msg", "expire", "write level", "admin level" }; enum { B_MSG, B_EXPIRE, B_WRITE_LEVEL, B_ADMIN_LEVEL, B_END }; int com,linenum,level,err; group = grp; msgcnt = 0; todays_msgcnt = 0; write_level = USER_LEVEL_USER; admin_level = USER_LEVEL_OPERATOR; expire_secs = 0; first_msg = NULL; last_msg = NULL; err = 0; // Create directory. It its already there this will error so it does no harm. sprintf(path,"%s/%04X/",BOARD_DIR,group->id); mkdir(path,0700); // Open board info file. If its not there there are no messages. strcat(path,BOARD_INFO_FILE); if (!(fp = fopen(path,"r"))) return; // Loop through info file. See cl_mail.cc for comments. minfo = NULL; linenum = 0; fgets(line,ARR_SIZE-1,fp); while(!feof(fp) && !(err = ferror(fp))) { linenum++; if (!minfo) minfo = new cl_msginfo; sl = new cl_splitline(1); if (sl->parse(line) != OK) goto ERROR; if (sl->wcnt) { // Find info command for(com=0;com != B_END;++com) if (!strcasecmp(sl->word[0],comlist[com])) break; if (com == B_END) goto ERROR; switch(com) { case B_MSG: if (sl->wcnt < 7) goto ERROR; minfo->set(msgcnt+1,sl); msgcnt++; tms = localtime(&minfo->create_time); todays_msgcnt += (tms->tm_year == server_time_tms.tm_year && tms->tm_yday == server_time_tms.tm_yday); // Add to list add_list_item(first_msg,last_msg,minfo); minfo = NULL; // We don't want to delete splitline object as its // used for message storage. fgets(line,ARR_SIZE-1,fp); continue; case B_EXPIRE: if (sl->wcnt != 2 || !is_integer(sl->word[1])) goto ERROR; expire_secs = atoi(sl->word[1]); break; case B_WRITE_LEVEL: if (sl->wcnt != 2 || (level = get_level(sl->word[1])) == -1) goto ERROR; write_level = level; break; case B_ADMIN_LEVEL: if (sl->wcnt != 2 || (level = get_level(sl->word[1])) == -1) goto ERROR; admin_level = level; } } delete sl; fgets(line,ARR_SIZE-1,fp); continue; ERROR: if (sl->wcnt) { // Owner will be NULL if its a persistent group if (group->type == GROUP_TYPE_USER) { if (group->owner) group->owner->warnprintf("Invalid configuration on boardinfo file line %d.\n",linenum); log(1,"ERROR: Invalid configuration for user board %04X on info file line %d.\n",group->id,linenum); } else // Just print since we're booting if this is public group printf(" ERROR: Invalid configuration for board %04X on info file line %d.\n",group->id,linenum); fgets(line,ARR_SIZE-1,fp); } } fclose(fp); if (err) log(1,"ERROR: Read failure while loading board %04X configuration: %s\n",strerror(err)); if (minfo) delete minfo; if (write_level > admin_level) { log(1,"ERROR: Write level for board %04X was greater than admin level, changing it.\n",group->id); write_level = admin_level; } } /*** Destructor ***/ cl_board::~cl_board() { cl_msginfo *minfo,*next; save(); // Destroy all msginfo structures in list for(minfo = first_msg;minfo;minfo = next) { next = minfo->next; delete minfo; } } /*** List the messages ***/ void cl_board::list(cl_user *u) { cl_msginfo *minfo; struct tm *tms; char sft[15]; int flag,lcnt,mcnt; if (!msgcnt) { u->uprintf("There are no messages on the board.\n"); return; } if (u->com_page_line == -1) u->uprintf("\n~BB*** Message board of group %04X, %s~RS~BB ***\n\n", group->id,group->name); lcnt = 0; mcnt = 0; flag = 0; FOR_ALL_MSGS(minfo) { if (!flag && u->com_page_line == -1) { u->uprintf("~OL~ULMsg From Posted on Bytes Subject\n"); flag = 1; } ++mcnt; if (u->com_page_line != -1 && mcnt <= u->com_page_line) continue; tms = localtime(&minfo->create_time); strftime(sft,sizeof(sft),"%b %d %H:%M",tms); sprintf(text,"%s, %s",minfo->id,minfo->name); // If id and name too long put some dots at end if (strlen(text) > 34) { text[31]='.'; text[32]='.'; text[33]='.'; text[34]='\0'; } u->uprintf("~FY%3d~RS ~FT%-34s~RS %s %5d %s\n", minfo->mnum,text,sft,minfo->size,minfo->subject); if (O_FLAGISSET(u,USER_FLAG_PAGING) && ++lcnt == u->term_rows - 2) { u->com_page_line = mcnt; return; } } if (O_FLAGISSET(u,USER_FLAG_PAGING) && lcnt && lcnt >= u->term_rows - 4) { u->com_page_line = mcnt; return; } u->uprintf("\nTotal of %d messages.\n\n",msgcnt); u->com_page_line = -1; } /*** Read the given message ***/ int cl_board::mread(cl_user *u, cl_msginfo *minfo) { struct stat ss; char path[MAXPATHLEN]; int ret; if (u->stage == USER_STAGE_BOARD_READ_FROM) { u->uprintf("\n~FYMesg num:~RS %d\n",minfo->mnum); u->page_header_lines = 5; } else { u->uprintf("\n"); u->page_header_lines = 4; } u->uprintf("~FYFrom :~RS %s, %s\n",minfo->id,minfo->name); u->uprintf("~FYSubject :~RS %s\n",minfo->subject); u->uprintf("~FYPosted :~RS %s",ctime(&minfo->create_time)); u->uprintf("~FYBytes :~RS %d",minfo->size); // Check if expected and actual file sizes match sprintf(path,"%s/%04X/%s",BOARD_DIR,group->id,minfo->filename); ss.st_size = 0; stat(path,&ss); if (ss.st_size != minfo->size) u->uprintf(" (~OL~FRFile corrupted!~RS Actual file size = %d bytes)\n\n",ss.st_size); else u->uprintf("\n\n"); ret = u->page_file(path,1); if (!u->page_pos) u->uprintf("\n"); return ret; } /*** Write new message ***/ int cl_board::mwrite(cl_user *u, char *subj, char *msg) { cl_msginfo *minfo; cl_remote_user *ru; char filename[MAXPATHLEN]; char path[MAXPATHLEN]; char *subj2; int cnt,nl,ret; FILE *fp; if (!user_can_post(u)) return ERR_WRITE; if (O_FLAGISSET(u,USER_FLAG_MUZZLED)) return ERR_MUZZLED; if ((int)strlen(msg) > max_board_chars) return ERR_MSG_TOO_LONG; // Pick a filename based on message count cnt = msgcnt+1; do { sprintf(filename,"msg_%d",cnt); FOR_ALL_MSGS(minfo) if (!strcmp(minfo->filename,filename)) break; cnt++; } while(minfo); // Write message file sprintf(path,"%s/%04X/%s",BOARD_DIR,group->id,filename); if (!(fp=fopen(path,"w"))) return ERR_CANT_OPEN_FILE; fputs(msg,fp); if (msg[strlen(msg)-1] != '\n') { nl=1; fputc('\n',fp); } else nl=0; fclose(fp); // Add entry to list. if (u->type == USER_TYPE_REMOTE) { ru = (cl_remote_user *)u; // If remote user is on hop 1 then use their id and server name // else use id and server ip (since we won't have the name). if (ru->hop_count == 1) sprintf(text,"%04X (%04X@%s)", u->id,ru->remote_id,ru->server_from->name); else { if (ru->home_svrname) sprintf(text,"%04X (%04X@%s)", u->id,ru->orig_id,ru->home_svrname); else sprintf(text,"%04X (%04X@%s:%d)", u->id, ru->orig_id, u->ipnumstr, ntohs(u->ip_addr.sin_port)); } } else sprintf(text,"%04X",u->id); subj2 = subj ? subj : (char *)"<No subject>"; minfo = new cl_msginfo(); if ((ret=minfo->set( last_msg ? last_msg->mnum+1 : 1, text, u->name, subj2, filename, strlen(msg) + nl)) != OK) { u->errprintf("Unable to set message!\n"); delete minfo; return ret; } msgcnt++; todays_msgcnt++; add_list_item(first_msg,last_msg,minfo); if ((ret = save()) != OK) return ret; // Inform everyone of success u->uprintf("Message posted as ~OL~FM#%d\n",minfo->mnum); group->geprintf( MSG_MISC,u,NULL, "~FM~OLBOARD POST #%d:~RS ~FYFrom:~RS ~FT%04X~RS (%s), ~FYSubject:~RS %s\n", minfo->mnum,u->id,u->name,subj2); return OK; } /*** Delete a message ***/ int cl_board::mdelete(cl_msginfo *minfo) { struct tm *tms; char path[MAXPATHLEN]; sprintf(path,"%s/%04X/%s",BOARD_DIR,group->id,minfo->filename); unlink(path); remove_list_item(first_msg,last_msg,minfo); tms = localtime(&minfo->create_time); todays_msgcnt -= (tms->tm_year == server_time_tms.tm_year && tms->tm_yday == server_time_tms.tm_yday); msgcnt--; delete minfo; return save(); } /*** Save message info to info file ***/ int cl_board::save() { char path[MAXPATHLEN]; cl_msginfo *minfo; FILE *fp; sprintf(path,"%s/%04X/%s",BOARD_DIR,group->id,BOARD_INFO_FILE); if (!(fp=fopen(path,"w"))) return ERR_CANT_OPEN_FILE; fprintf(fp,"\"admin level\" = %s\n",user_level[admin_level]); fprintf(fp,"\"write level\" = %s\n",user_level[write_level]); fprintf(fp,"expire = %d\n",expire_secs); FOR_ALL_MSGS(minfo) { fprintf(fp,"msg = \"%s\", \"%s\", \"%s\", %s, %u, %u\n", minfo->id, minfo->name, minfo->subject, minfo->filename, (uint)minfo->create_time, minfo->size); } fclose(fp); return OK; } /*** Renumber the messages ***/ void cl_board::renumber() { cl_msginfo *minfo; int i=1; FOR_ALL_MSGS(minfo) minfo->mnum = i++; } /*** Set the admin or write level ***/ int cl_board::set_level(int levtype, int lev) { cl_user *u; switch(levtype) { case ADMIN_LEVEL: admin_level = lev; if (lev < write_level) write_level = lev; break; case WRITE_LEVEL: write_level = lev; if (lev > admin_level) admin_level = lev; // Any users editing message for board who's level is now less // than write level must be stopped! ;) FOR_ALL_USERS(u) { if (u->group == group && u->level < lev && (u->stage == USER_STAGE_BOARD_SUBJECT || (u->stage == USER_STAGE_BOARD && u->editor))) { u->uprintf("~NP\n\n~FRYour message is being cancelled.\n\n"); if (u->editor) delete u->editor; u->stage = USER_STAGE_BOARD; u->prompt(); } } break; default: return ERR_INTERNAL; } return save(); } /*** Set the expiry time ***/ int cl_board::set_expire(int secs) { expire_secs = secs; return save(); } /*** Check if user can modify this board ***/ int cl_board::user_can_modify(cl_user *user) { return (user->level >= admin_level || (group->type == GROUP_TYPE_USER && group == user->home_group)); } /*** Check if user can post messages on this board ***/ int cl_board::user_can_post(cl_user *user) { return (user->level >= write_level || (group->type == GROUP_TYPE_USER && group == user->home_group)); }