/* * $Id: membership.c,v 0.33 1998/07/08 16:35:27 ceder Exp $ * Copyright (C) 1991, 1992, 1993, 1994, 1995, 1996 Lysator Academic Computer Association. * * This file is part of the LysKOM server. * * LysKOM 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 1, or (at your option) * any later version. * * LysKOM 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 LysKOM; see the file COPYING. If not, write to * Lysator, c/o ISY, Linkoping University, S-581 83 Linkoping, SWEDEN, * or the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, * MA 02139, USA. * * Please mail bug reports to bug-lyskom@lysator.liu.se. */ /* * membership.c * * All atomic calls that controlls who is a member in what. * (The person/conf relation). */ #define DEBUG_MARK_AS_READ static const char * rcsid = "$Id: membership.c,v 0.33 1998/07/08 16:35:27 ceder Exp $"; #include "rcs.h" USE(rcsid); #include #ifdef HAVE_STRING_H # include #endif #include #include #ifdef DEBUG_MARK_AS_READ # ifdef HAVE_STDARG_H # include # endif #endif #include #include "misc-types.h" #include "s-string.h" #include "kom-types.h" #include "services.h" #include "server/smalloc.h" #include "lyskomd.h" #include "com.h" #include "async.h" #include "connections.h" #include "internal-connections.h" #include "kom-errno.h" #include "manipulate.h" #include "cache.h" #include "send-async.h" #include "minmax.h" #include "kom-memory.h" #include "param.h" #ifdef DEBUG_MARK_AS_READ # include "log.h" # include "ram-output.h" #endif static void set_membership_type_bits(Membership_type *type, Bool invitation, Bool passive, Bool secret, Bool reserved1, Bool reserved2, Bool reserved3, Bool reserved4, Bool reserved5) { type->invitation = invitation; type->passive = passive; type->secret = secret; type->reserved1 = reserved1; type->reserved2 = reserved2; type->reserved3 = reserved3; type->reserved4 = reserved4; type->reserved5 = reserved5; } /* * Copy all information that ACTPERS is authorized to know about ORIG_P's * membership in all conferences to CENSOR_P. * * This function is used in get_membership(). */ static void copy_public_confs (Person * censor_p, /* The censored Person-struct */ Person * orig_p, /* The uncensored Person-struct */ Bool want_read, Bool copy_secret) /* Does ACTPERS want to know * which texts are read?*/ { int i; /* Number of mships lefte in ORIG_P */ Membership * censor_m; /* Pointer in CENSOR_P */ Membership * orig_m; /* Pointer in ORIG_P */ /* Copy all information except the secret. */ censor_p->conferences.confs = tmp_alloc( orig_p->conferences.no_of_confs * sizeof(Membership)); censor_p->conferences.no_of_confs = 0; censor_m = censor_p->conferences.confs; orig_m = orig_p->conferences.confs; for ( i = 0; i < orig_p->conferences.no_of_confs; i++, orig_m++ ) { if ( fast_access_perm (orig_m->conf_no, ACTPERS, ACT_P) > none && (copy_secret || !orig_m->type.secret) ) { *censor_m = *orig_m; if ( orig_p->flags.unread_is_secret || !want_read ) { censor_m->no_of_read = 0; censor_m->read_texts = NULL; } if ( orig_p->flags.unread_is_secret ) { censor_m->last_time_read = NO_TIME; censor_m->last_text_read = 0; } ++censor_m; ++censor_p->conferences.no_of_confs; } } } /* * Change the priority of a certain conference in a person. */ static void do_change_priority (Membership * mship, Conf_no conf_no, Conference * conf_c, unsigned char priority, unsigned short where, Pers_no pers_no, Person * pers_p, Membership_type * type) { Membership tmp_conf; unsigned short i; mship->priority = priority; mship->type = *type; /* Check range of where */ if ( where >= pers_p->conferences.no_of_confs ) { where = pers_p->conferences.no_of_confs - 1; } /* And now move the conference to slot number 'where' */ if ( mship < pers_p->conferences.confs + where ) { tmp_conf = *mship; while ( mship < pers_p->conferences.confs + where) { *mship = *(mship + 1); mship++; } *mship = tmp_conf; } else { tmp_conf = *mship; while ( mship > pers_p->conferences.confs + where) { *mship = *(mship - 1); mship--; } *mship = tmp_conf; } for (i = 0; i < conf_c->members.no_of_members; i++) { if (conf_c->members.members[i].member == pers_no) { conf_c->members.members[i].type = *type; mark_conference_as_changed( conf_no ); } } mark_person_as_changed( pers_no ); } /* * Insert a rec_time misc item to a text_status. * The item is put at position POS on the list. (0 == first) * Take no action if the misc_item at POS is a rec_time. * This function is only used when a person marks his letters as read. * * Text_stat * text_stat_pointer Textstatus to modify * int pos Where to insert rec_time */ static void do_add_rec_time (Text_stat * text_stat_ptr, int pos) { int i; /* Defensive checks */ if ( pos < 0 || pos > text_stat_ptr->no_of_misc ) { restart_kom("do_add_rec_time() - illegal pos\n"); } /* Check that no rec_time exists */ if ( pos < text_stat_ptr->no_of_misc && text_stat_ptr->misc_items[ pos ].type == rec_time ) { return; } /* Allocate space */ text_stat_ptr->misc_items = srealloc(text_stat_ptr->misc_items, (++(text_stat_ptr->no_of_misc)) * sizeof(Misc_info)); /* Move items. */ for ( i = text_stat_ptr->no_of_misc - 1; i > pos; i-- ) { text_stat_ptr->misc_items[ i ] = text_stat_ptr->misc_items[ i - 1 ]; } /* Set type */ text_stat_ptr->misc_items[ pos ].type = rec_time; /* Set value */ time( & text_stat_ptr->misc_items[ pos ].datum.received_at); } /* * add_rec_time adds a 'rec_time' misc_item to text number LOC_NO in * conference CONF_C. The item will follow a recpt or cc_recpt to ACTPERS. * No action is taken if ACTPERS is not a recipient of the text, or the text * no longer exists, or the text has already been received. */ static void add_rec_time(Conference * conf_c, Local_text_no local_no) { Bool found; Text_no text_no; Text_stat * t_stat; int i; if ( local_no >= conf_c->texts.first_local_no + conf_c->texts.no_of_texts || local_no < conf_c->texts.first_local_no ) { return; /* No longer exists in conf. */ } text_no = conf_c->texts.texts[ local_no - conf_c->texts.first_local_no ]; if ( text_no == 0 ) { return; /* Text is deleted. */ } VOID_GET_T_STAT(t_stat, text_no); /* locate the misc_item which says that ACTPERS is a recipient */ for ( found = FALSE, i = 0; !found && i < t_stat->no_of_misc; i++ ) { switch ( t_stat->misc_items[ i ].type ) { case recpt: if ( t_stat->misc_items[ i ].datum.recipient == ACTPERS ) { do_add_rec_time( t_stat, i + 2 ); /* Add after loc_no */ found = TRUE; } break; case cc_recpt: if ( t_stat->misc_items[ i ].datum.cc_recipient == ACTPERS ) { do_add_rec_time( t_stat, i + 2 ); found = TRUE; } break; case bcc_recpt: if ( t_stat->misc_items[ i ].datum.bcc_recipient == ACTPERS ) { do_add_rec_time( t_stat, i + 2 ); found = TRUE; } break; case comm_to: case comm_in: case footn_to: case footn_in: case loc_no: case rec_time: case sent_by: case sent_at: break; } } if( found == FALSE ) { log("ERROR: add_rec_time(): found==FALSE\n"); } mark_text_as_changed( text_no); return; } /* * Check if there are some texts immediately following last_text_read * that are read or deleted. If so, update last_text_read and delete them * from read_texts. * * This is only used from mark_as_read(). */ static void adjust_read( Membership * m, const Conference * conf) { /* The first HANDLED texts in read_texts in M are already included in last_text_read. */ unsigned short handled; unsigned short i; Local_text_no * locp; Local_text_no conf_max; /* Highest used local_text_no in conf */ Local_text_no conf_min; /* Lowest used local_text_no in conf */ #ifndef NDEFENSIVE_CHECKS Local_text_no prev; #endif /* (conf_min <= x <= conf_max) if x is an existing local_text_no */ conf_max = conf->texts.first_local_no + conf->texts.no_of_texts - 1; conf_min = conf->texts.first_local_no; /* Flag all removed texts as read, if that is not already done. */ if (m->last_text_read < conf_min - 1) m->last_text_read = conf_min - 1; /* Skip any texts in read_texts which are already handled. */ for (handled = 0; handled < m->no_of_read && m->read_texts[handled] < m->last_text_read + 1; ) { handled++; } /* This loops advances m->last_text_read as far as possible, advancing handled along when appropriate. m->last_text_read can be increased for one of two reasons: · The text is present in read_texts. · The text is deleted. This loop handles both cases. */ for ( ; ; ) { if (handled < m->no_of_read && m->read_texts[handled] == m->last_text_read + 1) { /* This text is present in read_texts. */ m->last_text_read++; handled++; } else if (m->last_text_read < conf_max && conf->texts.texts[m->last_text_read + 1 - conf_min] == 0) { /* This text is deleted. */ m->last_text_read++; } else break; } /* Delete all handled entries in read_texts. */ if (handled > 0) { m->no_of_read -= handled; for (locp = m->read_texts; locp < m->read_texts + m->no_of_read; locp++) { *locp = *(locp + handled); } } #ifndef NDEFENSIVE_CHECKS /* Check that the items in read_texts really ARE sorted in ascending order. If not, there is probably a bug in this routine or in mark_as_read */ prev = m->last_text_read; for ( i = 0; i < m->no_of_read; i++) { if ( prev >= m->read_texts[ i ] ) { log("Bug in adjust_read. Conference %lu, Priority %lu\n" "\tprev = %lu, i = %lu, m->read_texts[i] = %lu\n", (unsigned long)m->conf_no, (unsigned long)m->priority, (unsigned long)prev, (unsigned long)i, (unsigned long)m->read_texts[i]); } prev = m->read_texts[ i ]; } #endif } /* * insert TEXT in the list of read_texts in M. The texts are sorted. * m->no_of_read is updated. m->read_texts is never reallocated, and must * thus be big enough to hold the new number. * * Returns FAILURE if the text is already read. * * This is only used from mark_as_read(). */ static Success insert_loc_no(Local_text_no text, Membership * m) { Local_text_no * seek, * move; if ( text <= m->last_text_read ) { return FAILURE; /* This text was already read. */ } for ( seek = m->read_texts; seek < m->read_texts + m->no_of_read; seek++) { if ( text == *seek ) { return FAILURE; /* This text was already read. */ } if ( text < *seek ) { /* The text should be entered here. */ for ( move = m->read_texts + m->no_of_read; move > seek; move--) { *move = *(move - 1); } *seek = text; ++(m->no_of_read); return OK; } } *seek = text; /* The text had a higher number than any */ ++(m->no_of_read); /* previously read text. */ return OK; } /* * End of static functions */ /* * Functions that are exported to the server. */ /* * Add a member to a conference. All errorchecking should already * be done when this function is called. The person must not already * be a member of the conference. It is _not_ an error to make WHERE bigger * than the number of conferences the person is a member in. */ void do_add_member(Conf_no conf_no, /* Conference to add a new member to. */ Conference * conf_c, /* Conf. status. Must NOT be NULL. */ Pers_no pers_no, /* Person to be added. */ Person * pers_p, /* Pers. status. Must NOT be NULL. */ unsigned char priority, /* Prioritylevel to assign to this conf */ unsigned short where, /* Sequence number in the list */ Membership_type * type ) { Membership * mship; Member * mbr; /* First add the conference in the person-struct. * Make room for it. */ pers_p->conferences.confs = srealloc( pers_p->conferences.confs, ++(pers_p->conferences.no_of_confs) * sizeof(Membership)); /* Fill in the room */ /* Find last slot */ mship = pers_p->conferences.confs + pers_p->conferences.no_of_confs - 1 ; /* Move all data beyond WHERE */ while ( mship > pers_p->conferences.confs + where ) { *mship = *(mship - 1); mship--; } init_membership(mship); mship->added_by = ACTPERS; mship->added_at = time(NULL); mship->conf_no = conf_no; mship->priority = priority; mship->last_time_read = time(NULL); mship->last_text_read = 0; mship->no_of_read = 0; mship->read_texts = NULL; mship->type = *type; /* Make room for the person in the conference */ conf_c->members.members = srealloc( conf_c->members.members, ++(conf_c->members.no_of_members) * sizeof(Member)); /* New members go to the end of the list */ mbr = (conf_c->members.members + conf_c->members.no_of_members - 1); mbr->member = pers_no; mbr->added_by = ACTPERS; mbr->added_at = time(NULL); mbr->type = *type; mark_conference_as_changed( conf_no ); mark_person_as_changed( pers_no ); return; } /* * Send an asynchronous message to person pers_no (if he is logged on) * and tell him that he is no longer a member of conf_no. Also calls * leave_conf(). */ extern void forced_leave_conf(Pers_no pers_no, Conf_no conf_no) { Connection *real_active_connection; Session_no i = 0; real_active_connection = active_connection; while ( (i = traverse_connections(i)) != 0 ) { active_connection = get_conn_by_number(i); if ( active_connection->pers_no == pers_no ) { async_forced_leave_conf(active_connection, conf_no); if ( active_connection->cwc == conf_no ) leave_conf(); } } active_connection = real_active_connection; } /* * Delete a member from a conference. * No checks are made on the parameters. * The dynamically allocated areas conf_c->members.members and * pers_p->confs are NOT reallocated since they will anyhow sooner or later * be flushed from core. */ Success do_sub_member(Conf_no conf_no, /* Conf to delete member from. */ Conference * conf_c, /* May be NULL */ Member * mbr, /* May be NULL */ Pers_no pers_no, /* Person to be deleted. */ Person * pers_p, /* May be NULL */ Membership * mship) /* Pointer to the persons membership in conf., or NULL if not known. */ { if ( conf_c == NULL ) GET_C_STAT(conf_c, conf_no, FAILURE); if ( pers_p == NULL ) GET_P_STAT(pers_p, pers_no, FAILURE); if ( mship == NULL && (mship = locate_membership(conf_no, pers_p)) == NULL) restart_kom("do_sub_member() - can't find mship\n"); if ( mbr == NULL && (mbr = locate_member(pers_no, conf_c)) == NULL) restart_kom("do_sub_member() - can't find member.\n"); forced_leave_conf(pers_no, conf_no); /* Delete from Person */ sfree( mship->read_texts ); --pers_p->conferences.no_of_confs; while ( mship < pers_p->conferences.confs + pers_p->conferences.no_of_confs ) { *mship = *(mship + 1); ++mship; } /* Delete from Conference */ --conf_c->members.no_of_members; while ( mbr < conf_c->members.members + conf_c->members.no_of_members ) { *mbr = *(mbr + 1); ++mbr; } mark_person_as_changed( pers_no ); mark_conference_as_changed( conf_no ); return OK; } /* * VICTIM is a person or a conference. * Meaning of return values: * unlimited: ACTPERS is supervisor of VICTIM, or ACTPERS is admin, * or ACTPERS is VICTIM * none: VICTIM is secret, and ACTPERS is not a member * member: ACTPERS is a member in VICTIM, but doesn't have unlimited * access. * read_protected: The conference is rd_prot and ACTPERS is not a member. * limited: otherwise. * error: see kom_errno */ Access access_perm(Conf_no victim, Conference * victim_c, /* May be NULL */ Pers_no viewer, Person * viewer_p) /* May be NULL */ { if (victim == viewer) return unlimited; if (victim_c == NULL) GET_C_STAT(victim_c, victim, error); if ( viewer != 0 && ENA(admin, 2) ) return unlimited; if ( is_supervisor(victim, victim_c, viewer, viewer_p) || ENA(wheel, 8)) return unlimited; if ( viewer != 0 ) { if ( viewer_p == NULL ) GET_P_STAT(viewer_p, viewer, error); if ( locate_membership( victim, viewer_p ) != NULL ) return member; } if ( victim_c->type.secret ) return none; if ( victim_c->type.rd_prot ) return read_protected; return limited; } /* * Fast version of access_perm. See comment in file server/manipulate.h * where Access is defined. * * Check if viewer is allowed to look at victiom. viewer_p, if * supplied, should be a pointer to the pers-stat of viewer. */ Access fast_access_perm(Conf_no victim, Pers_no viewer, Person *viewer_p) /* May be NULL. */ { Conf_type conf_type; if ( !cached_conf_exists(victim) ) return error; if ( viewer != 0 && (ENA(admin, 2) || ENA(wheel,8) || viewer == victim) ) return unlimited; if ( viewer != 0 ) { if ( viewer_p == NULL ) GET_P_STAT(viewer_p, viewer, error); if ( locate_membership( victim, viewer_p ) != NULL ) return member; } /* Only read in conference struct when really necessary. */ conf_type = cached_get_conf_type (victim); if ( conf_type.secret ) return access_perm(victim, NULL, viewer, viewer_p); if ( conf_type.rd_prot ) return read_protected; return limited; } /* * Locate the Member struct in CONF_C for person PERS_NO */ Member * locate_member(Pers_no pers_no, Conference * conf_c) { Member * mbr; int i; for(mbr = conf_c->members.members, i = conf_c->members.no_of_members; i > 0; i--, mbr++) { if ( mbr->member == pers_no ) { return mbr; } } return NULL; } /* * Find the data about PERS_P:s membership in CONF_NO. * Return NULL if not found */ Membership * locate_membership(Conf_no conf_no, Person * pers_p) { Membership * confp; int i; for(confp = pers_p->conferences.confs, i = pers_p->conferences.no_of_confs; i > 0; i--, confp++) { if ( confp->conf_no == conf_no ) { return confp; } } return NULL; } /* * Atomic functions. */ /* * Unsubscribe from a conference. * * You must be supervisor of either conf_no or pers_no to be allowed to * do this. * * BUGS: There is no passive membership. */ extern Success sub_member( Conf_no conf_no, Pers_no pers_no ) { Conference * conf_c; Membership * mship; Person * pers_p; CHK_LOGIN(FAILURE); GET_C_STAT(conf_c, conf_no, FAILURE); GET_P_STAT(pers_p, pers_no, FAILURE); if( (mship = locate_membership(conf_no, pers_p) ) == NULL) { err_stat = conf_no; kom_errno = conf_c->type.secret ? KOM_UNDEF_CONF : KOM_NOT_MEMBER; return FAILURE; } if ( !is_supervisor(conf_no, conf_c, ACTPERS, ACT_P) && !is_supervisor(pers_no, NULL, ACTPERS, ACT_P) && !ENA(wheel,8) && !ENA(admin, 4) ) { err_stat = conf_no; if (conf_c->type.secret) kom_errno = KOM_UNDEF_CONF; else if (mship->type.secret) kom_errno = KOM_NOT_MEMBER; else kom_errno = KOM_PERM; return FAILURE; } return do_sub_member(conf_no, conf_c, NULL, pers_no, pers_p, mship); } /* * Add a member to a conference (join a conference) or * Change the priority of a conference. * * Anyone may add anyone as a member as long as the new member is not * secret and the conference is not rd_prot. This might be a bug. * * PRIORITY is the assigned priority for the conference. WHERE says * where on the list the person wants the conference. 0 is first. WHERE * is automatically set to the number of conferences that PERS_NO is member * in if WHERE is too big, so it is not an error to give WHERE == ~0 as * a parameter. * * You can only re-prioritize if you are supervisor of pers_no. */ extern Success add_member_old(Conf_no conf_no, Pers_no pers_no, unsigned char priority, unsigned short where) { Membership_type type; set_membership_type_bits(&type, param.invite_by_default,0,0,0,0,0,0,0); return add_member(conf_no, pers_no, priority, where, &type); } extern Success add_member(Conf_no conf_no, Pers_no pers_no, unsigned char priority, unsigned short where, /* Range of where is [0..] */ Membership_type *type ) { Conference * conf_c, * pers_c; Person * pers_p; Membership * mship; CHK_LOGIN(FAILURE); GET_C_STAT(conf_c, conf_no, FAILURE); GET_C_STAT(pers_c, pers_no, FAILURE); GET_P_STAT(pers_p, pers_no, FAILURE); if (param.invite_by_default && !is_supervisor(pers_no, pers_c, ACTPERS, ACT_P) && !ENA(admin, 4)) { type->invitation = 1; } if (!param.secret_memberships && type->secret) { err_stat = 0; kom_errno = KOM_INVALID_MEMBERSHIP_TYPE; return FAILURE; } if ( access_perm(conf_no, conf_c, ACTPERS, ACT_P) < limited && !ENA(wheel, 8) ) { err_stat = conf_no; kom_errno = (conf_c->type).secret ? KOM_UNDEF_CONF : KOM_ACCESS; return FAILURE; } /* Is he already a member? */ if ( (mship = locate_membership( conf_no, pers_p )) != NULL) { /* He is already a member. Only change the priority. */ GET_C_STAT(pers_c, pers_no, FAILURE); if( !is_supervisor (pers_no, pers_c , ACTPERS, ACT_P) && !ENA(wheel, 8) ) { /* Noone else can change one's priorities. */ err_stat = pers_no; kom_errno = KOM_PERM; return FAILURE; } do_change_priority( mship, conf_no, conf_c, priority, where, pers_no, pers_p, type); } else { if (pers_no != ACTPERS) log("Person %lu added to conference %lu by %lu.\n", (unsigned long)pers_no, (unsigned long)conf_no, (unsigned long)ACTPERS); do_add_member(conf_no, conf_c, pers_no, pers_p, priority, where, type); } return OK; } #ifdef DEBUG_MARK_AS_READ static int check_membership(Pers_no pno, const Conference *conf, const Membership *mship) { int errors=0; int i; Local_text_no last=0; int log_no=0; /* Check read texts */ if ( mship->last_text_read > conf->texts.first_local_no + conf->texts.no_of_texts - 1) { if ( log_no++ < 80 ) log("%s%d) Person %lu has read text %lu in conf %lu%s%lu texts.\n", "membership.c: check_membership(): (", log_no, (unsigned long)pno, (unsigned long)mship->last_text_read, (unsigned long)mship->conf_no, ", which only has ", (unsigned long)(conf->texts.first_local_no + conf->texts.no_of_texts - 1)); errors++; } last = mship->last_text_read; for ( i = 0; i < mship->no_of_read; i++) { if ( mship->read_texts[i] <= last ) { errors++; } last = mship->read_texts[i]; } return errors; } #endif /* * mark_as_read() is used to tell LysKOM which texts you have read. * You can mark several texts in one chunk, but the chunk should not * be too big to prevent users from having to re-read texts in case of * a [server/client/network]-crash. * * The texts are marked per conference. If there are several recipients * to a text it should be mark_as_read() in all the recipients. * * If conference is ACTPERS mailbox it will add a rec_time item in the * misc_items field. * * It is only possible to mark texts as read in a conference you are * member in. * * Attempts to mark non-existing texts as read are ignored if the text * has existed. If the text has not yet been created KOM_NO_SUCH_LOCAL_TEXT * will be returned in kom_errno. * * If CONFERENCE is the current working conference of ACTPERS, and the * text has not previously been marked as read, ACT_P->read_texts will * be increased. If the client cooperates this will be correct. If the * client change_conference()s to all recipients of a text before * marking it as read the read_texts field will be too big. (If anyone * cares about that, feel free to rewrite this code as long as it * doen't get too CPU- intensive.) */ extern Success mark_as_read (Conf_no conference, int no_of_texts, const Local_text_no * text_arr ) { int i; Membership * m; int allocflg = 0; /* read_texts is not re-allocated yet */ Conference * conf_c; Success retval = OK; #ifdef DEBUG_MARK_AS_READ const Local_text_no * const text_arr_start = text_arr; Membership original; int loop; static int log_no = 0; #endif CHK_LOGIN(FAILURE); GET_C_STAT(conf_c, conference, FAILURE); if ( (m = locate_membership( conference, ACT_P)) == NULL) { err_stat = conference; kom_errno = KOM_NOT_MEMBER; return FAILURE; } #ifdef DEBUG_MARK_AS_READ if ( m->read_texts == NULL && m->no_of_read != 0 ) { log("mark_as_read(): m->read_texts == NULL && m->no_of_read == %lu (corrected).", (unsigned long)m->no_of_read); m->no_of_read = 0; } original = *m; original.read_texts = smalloc(m->no_of_read * sizeof(Local_text_no)); memcpy(original.read_texts, m->read_texts, m->no_of_read * sizeof(Local_text_no)); #endif for( i = no_of_texts; i > 0; i--, text_arr++ ) { if ( *text_arr >= ( conf_c->texts.first_local_no + conf_c->texts.no_of_texts )) { kom_errno = KOM_NO_SUCH_LOCAL_TEXT; err_stat = no_of_texts - i; retval = FAILURE; break; /* Exit for-loop */ } if ( *text_arr == 0 ) { kom_errno = KOM_LOCAL_TEXT_ZERO; err_stat = no_of_texts - i; retval = FAILURE; break; /* Exit for-loop */ } /* Is it a letter to ACTPERS? If so, add a rec_time item. */ if ( conference == ACTPERS ) add_rec_time (conf_c, *text_arr); /* Update the Membership struct */ if ( *text_arr == m->last_text_read + 1 ) { ++m->last_text_read; if ( active_connection->cwc == conference ) ++ACT_P->read_texts; } else { if ( allocflg == 0 ) { /* Realloc as much as is needed, and probably more. */ /* Better than to execute srealloc 100 times... */ m->read_texts = srealloc( m->read_texts, (m->no_of_read + i) * sizeof(Local_text_no)); allocflg = 1; } if ( insert_loc_no( *text_arr, m ) == OK && active_connection->cwc == conference ) { ++ACT_P->read_texts; } } } adjust_read( m, conf_c ); /* Delete initial part of read_texts in the membership. */ /* Realloc to correct size */ m->read_texts = srealloc( m->read_texts, (m->no_of_read) * sizeof(Local_text_no)); mark_conference_as_changed ( conference ); if ( active_connection->cwc == conference ) mark_person_as_changed( ACTPERS ); #ifdef DEBUG_MARK_AS_READ /* Check that the membership is correct. Otherwise log all info. */ if ( check_membership(ACTPERS, conf_c, m) > 0 && log_no++ < 40 ) { log("mark_as_read(): (Msg no %d) Person %lu %s:\n", log_no, (unsigned long)ACTPERS, "has a corrupt membership"); log("Dump of data follows: %s\n", " "); foutput_membership(stderr, &original); putc('\n', stderr); foutput_membership(stderr, m); fprintf(stderr, "\n%lu { ", (unsigned long)no_of_texts); for ( loop = 0; loop < no_of_texts; loop++ ) { fprintf(stderr, "\n%lu ", (unsigned long)text_arr_start[loop]); } fprintf(stderr, "}\n"); } sfree(original.read_texts); #endif return retval; } /* * Ask what conferences a person is a member of. * * /// The following might be slightly misleading: /// /ceder * Only ask for information about at most NO_OF_CONFS conferences * starting with conferece FIRST. WANT_READ_TEXTS has the same * function as GETP_READ_TEXTS in the get_person_stat call. * /// */ extern Success get_membership (Pers_no pers_no, unsigned short first, unsigned short no_of_confs, Bool want_read_texts, Membership_list * memberships ) { return get_membership_old (pers_no, first, no_of_confs, want_read_texts, memberships ); } extern Success get_membership_old (Pers_no pers_no, unsigned short first, unsigned short no_of_confs, Bool want_read_texts, Membership_list * memberships ) { Person * p_orig; Person temp_pers; Conference * pers_c; Access acc; int i; CHK_LOGIN (FAILURE); GET_P_STAT (p_orig, pers_no, FAILURE); GET_C_STAT (pers_c, pers_no, FAILURE); acc = access_perm (pers_no, pers_c, ACTPERS, ACT_P); if (acc == error) return FAILURE; if (acc == none) { err_stat = pers_no; kom_errno = KOM_UNDEF_PERS; return FAILURE; } /* Make a copy of the struct. */ temp_pers = *p_orig; /* Delete all secret information. */ if ( acc != unlimited ) copy_public_confs (&temp_pers, p_orig, want_read_texts, FALSE); else if ( !want_read_texts ) { /* Delete info about read texts. */ temp_pers.conferences.confs = tmp_alloc(temp_pers.conferences.no_of_confs * sizeof(Membership)); memcpy(temp_pers.conferences.confs, p_orig->conferences.confs, (temp_pers.conferences.no_of_confs * sizeof(Membership) )); for ( i = 0; i < temp_pers.conferences.no_of_confs; i++ ) temp_pers.conferences.confs[ i ].read_texts = NULL; } *memberships = temp_pers.conferences; if ( first >= memberships->no_of_confs ) { err_stat = first; kom_errno = KOM_INDEX_OUT_OF_RANGE; return FAILURE; } memberships->confs += first; memberships->no_of_confs = min( memberships->no_of_confs - first, no_of_confs); return OK; } /* * +++/// * * first starts at 0. */ extern Success get_members_old (Conf_no conf_no, unsigned short first, unsigned short no_of_members, Member_list * members) { return get_members (conf_no, first, no_of_members, members); } extern Success get_members (Conf_no conf_no, unsigned short first, unsigned short no_of_members, Member_list * members ) { Conference * conf_c; Access acc; unsigned long src, dst; GET_C_STAT(conf_c, conf_no, FAILURE); acc = access_perm (conf_no, conf_c, ACTPERS, ACT_P); if ( acc == error ) return FAILURE; if ( acc == none ) { err_stat = conf_no; kom_errno = KOM_UNDEF_CONF; return FAILURE; } if ( first >= (conf_c->members).no_of_members ) { err_stat = first; kom_errno = KOM_INDEX_OUT_OF_RANGE; return FAILURE; } *members = conf_c->members; members->members += first; members->no_of_members = min (no_of_members, members->no_of_members - first); members->members = tmp_alloc(members->no_of_members * sizeof(Member)); src = first; dst = 0; while (dst < members->no_of_members) { if (!(conf_c->members).members[src].type.secret || is_supervisor((conf_c->members).members[src].member, NULL, ACTPERS, ACT_P)) { members->members[dst] = (conf_c->members).members[src]; } else { members->members[dst].member = 0; members->members[dst].added_by = 0; members->members[dst].added_at = 0; set_membership_type_bits(&members->members[dst].type, 0,0,0,0,0,0,0,0); } src += 1; dst += 1; } return OK; } /* * Get a list of all conferences where it is possible that a person has * unread articles. */ Success get_unread_confs(Pers_no pers_no, Conf_no_list *result) { Person *pers_p; Membership *confs; unsigned short n; CHK_LOGIN(FAILURE); GET_P_STAT(pers_p, pers_no, FAILURE); result->conf_nos = tmp_alloc (pers_p->conferences.no_of_confs * sizeof(Conf_no)); result->no_of_confs = 0; for ( n = 0, confs = pers_p->conferences.confs; n < pers_p->conferences.no_of_confs; n++, confs++ ) { if ((confs->last_text_read < cached_get_highest_local_no (confs->conf_no)) && (fast_access_perm(confs->conf_no, ACTPERS, ACT_P) > read_protected)) { result->conf_nos[ result->no_of_confs++ ] = confs->conf_no; } } return OK; } /* * Tell the server that I want to mark/unmark texts as read so that I * get (approximately) no_of_unread unread texts in conf_no. */ extern Success set_unread (Conf_no conf_no, Text_no no_of_unread) { Membership *mship; Conference *conf_c; Local_text_no highest; CHK_LOGIN(FAILURE); GET_C_STAT(conf_c, conf_no, FAILURE); if ( (mship = locate_membership(conf_no, ACT_P)) == NULL ) { err_stat = conf_no; kom_errno = KOM_NOT_MEMBER; return FAILURE; } highest = conf_c->texts.first_local_no + conf_c->texts.no_of_texts - 1; mship->last_text_read = ((highest > no_of_unread) ? (highest - no_of_unread) : 0); sfree(mship->read_texts); mship->read_texts = NULL; mship->no_of_read = 0; mark_person_as_changed(ACTPERS); return OK; } /* * Tell the server that I want to mark/unmark texts as read so that * last_read is the last read text in conf_no. */ extern Success set_last_read (Conf_no conf_no, Local_text_no last_read) { Membership *mship; Conference *conf_c; Local_text_no last; CHK_LOGIN(FAILURE); GET_C_STAT(conf_c, conf_no, FAILURE); if ( (mship = locate_membership(conf_no, ACT_P)) == NULL ) { err_stat = conf_no; kom_errno = KOM_NOT_MEMBER; return FAILURE; } last = conf_c->texts.first_local_no + conf_c->texts.no_of_texts - 1; mship->last_text_read = ((last_read <= last) ? last_read : last); sfree(mship->read_texts); mship->read_texts = NULL; mship->no_of_read = 0; mark_person_as_changed(ACTPERS); return OK; } extern Success set_membership_type(Pers_no pers_no, Conf_no conf_no, Membership_type *type) { Conference *conf_c, *pers_c; Person *pers_p; Membership *membership; Member *mbr; Access acc; /* Check for logon */ CHK_LOGIN(FAILURE); /* Find the conference and person in question */ GET_C_STAT(conf_c, conf_no, FAILURE); GET_C_STAT(pers_c, pers_no, FAILURE); GET_P_STAT(pers_p, pers_no, FAILURE); /* Make sure that ACTPERS may know about conf */ acc = access_perm(conf_no, conf_c, ACTPERS, ACT_P); if (acc == error) return FAILURE; if (acc == none) { err_stat = conf_no; kom_errno = KOM_UNDEF_CONF; return FAILURE; } /* Check that ACTPERS may modify memberships of person */ acc = access_perm(pers_no, pers_c, ACTPERS, ACT_P); if (acc != unlimited && !ENA(wheel, 8) && !ENA(admin, 6)) { err_stat = pers_no; kom_errno = conf_c->type.secret ? KOM_UNDEF_CONF : KOM_PERM; return FAILURE; } /* Find person's membership in the conference */ membership = locate_membership(conf_no, pers_p); if (membership == NULL) { err_stat = conf_no; kom_errno = KOM_NOT_MEMBER; return FAILURE; } /* Find entry in conference's member list */ /* FIXME: If this happens we should do something more drastic since it indicates that the database is fucked up. */ mbr = locate_member(pers_no, conf_c); if (mbr == NULL) { err_stat = conf_no; kom_errno = KOM_NOT_MEMBER; return FAILURE; } /* Check type restrictions */ if ((!param.secret_memberships && type->secret) || (!param.allow_reinvite && type->invitation && !membership->type.invitation)) { err_stat = 0; kom_errno = KOM_INVALID_MEMBERSHIP_TYPE; return FAILURE; } /* Modify member and membership */ membership->type = *type; mbr->type = *type; /* Mark the conference and person as changed */ mark_conference_as_changed(conf_no); mark_person_as_changed(pers_no); /* Return success */ return OK; }