/* * $Id: text.c,v 0.56 1998/10/22 22:08:47 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. */ /* * text.c * * All atomic calls that deals with texts. */ static const char * rcsid = "$Id: text.c,v 0.56 1998/10/22 22:08:47 ceder Exp $"; #include "rcs.h" USE(rcsid); #include #include #include #include #include #ifdef HAVE_STDARG_H # include #endif #include #include "server/smalloc.h" #include "s-string.h" #include "misc-types.h" #include "kom-types.h" #include "services.h" #include "manipulate.h" #include "lyskomd.h" #include "kom-errno.h" #include "config.h" #include "com.h" #include "async.h" #include "connections.h" #include "internal-connections.h" #include "cache.h" #include "log.h" #include "minmax.h" #include "admin.h" #include "send-async.h" #include "param.h" #include "kom-memory.h" #include "aux-items.h" #include "internal-services.h" #include "local-to-global.h" /* * Forward declarations */ static Text_no do_create_text(const String message, u_short no_of_misc, Misc_info * misc, Aux_item_list * aux, Bool anonymous, Text_stat **ret_stat); /* * Static functions */ /* * Add text_no to the list of texts in a conference. Return the local number * the text gets in this conference. */ static Local_text_no add_text_in_conf(Conference * conf_c, Text_no text_no) { Local_text_no res; time( & conf_c->last_written ); /* Add number last on the text_list */ res = l2g_first_appendable_key(&conf_c->texts); l2g_append(&conf_c->texts, res, text_no); return res; } /* * Count how many recipients and cc_recipients a text has. */ static int count_recipients( Text_stat *t_stat ) { int n = 0; Misc_info * misc; for ( misc = t_stat->misc_items; misc < t_stat->misc_items + t_stat->no_of_misc; misc++ ) { if ( misc->type == recpt || misc->type == cc_recpt || misc->type == bcc_recpt ) { n++; } } return n; } /* * Count how many footnotes a text has. */ static int count_footn( Text_stat *t_stat ) { int n = 0; Misc_info * misc; for ( misc = t_stat->misc_items; misc < t_stat->misc_items + t_stat->no_of_misc; misc++ ) { if ( misc->type == footn_in ) { n++; } } return n; } /* * Count how many commments a text has. */ static int count_comment( Text_stat *t_stat ) { int n = 0; Misc_info * misc; for ( misc = t_stat->misc_items; misc < t_stat->misc_items + t_stat->no_of_misc; misc++ ) { if ( misc->type == comm_in ) { n++; } } return n; } /* * Check if ACTPERS is allowed to read this text. * Returns TRUE if he is allowed to read it. */ /* * Check if CONF_NO is a recipient of the text whose text_stat is given. */ static int find_recipient(Conf_no conf_no, Text_stat * t_stat) { int i; for ( i = 0; i < t_stat->no_of_misc; i++ ) { switch( t_stat->misc_items[ i ].type ) { case recpt: if ( t_stat->misc_items[ i ].datum.recipient == conf_no ) { return i; } break; case cc_recpt: if ( t_stat->misc_items[ i ].datum.cc_recipient == conf_no ) { return i; } break; case bcc_recpt: if ( t_stat->misc_items[ i ].datum.bcc_recipient == conf_no ) { return i; } break; case rec_time: case comm_to: case comm_in: case footn_to: case footn_in: case sent_by: case sent_at: case loc_no: break; #ifndef COMPILE_CHECKS default: restart_kom("find_recipient(): illegal misc_item\n"); #endif } } return -1; } /* * Check if comment is a comment to parent. */ static Bool is_comment_to(Text_no comment, Text_stat *parent) { int i; for ( i = 0; i < parent->no_of_misc; i++ ) { switch( parent->misc_items[ i ].type ) { case comm_in: if ( parent->misc_items[ i ].datum.commented_in == comment ) return TRUE; break; default: break; } } return FALSE; } /* * Check if footnote is a footnote to parent. */ static Bool is_footnote_to(Text_no footnote, Text_stat *parent) { int i; for ( i = 0; i < parent->no_of_misc; i++ ) { switch( parent->misc_items[ i ].type ) { case footn_in: if ( parent->misc_items[ i ].datum.footnoted_in == footnote ) return TRUE; break; default: break; } } return FALSE; } /* * Return the conference which the text goes to. This is normally conf_no, but * it may be a super_conf. If ACTPERS is not allowed to submit a text to * conf_no or a super_conf, return 0. */ static Conf_no submit_to(Conf_no conf_no, /* The conference the user is trying to */ /* submit a text to. */ Conference * conf_c) /* May be NULL */ { int i; Access acc; CHK_LOGIN(0); if ( conf_c == NULL ) GET_C_STAT(conf_c, conf_no, 0); for ( i=0; i < param.max_super_conf_loop; i++) { acc = access_perm (conf_no, conf_c, ACTPERS, ACT_P); if ( acc <= none ) return 0; if (conf_c->permitted_submitters == 0 || acc == unlimited || locate_membership( conf_c->permitted_submitters, ACT_P) != NULL) { return conf_no; } if ((conf_no = conf_c->super_conf) == 0) return 0; GET_C_STAT(conf_c, conf_no, 0); } return 0; } /* * Say that FOOTNOTE is a footnote to TEXT. */ static Success do_add_footnote(Text_no footnote, Text_no text) { Text_stat *foot_s, *text_s; GET_T_STAT(foot_s, footnote, FAILURE); GET_T_STAT(text_s, text, FAILURE); ADD_MISC(foot_s, footn_to, footnote_to, text); ADD_MISC(text_s, footn_in, footnoted_in, footnote); mark_text_as_changed( footnote ); mark_text_as_changed( text ); return OK; } /* * Say that COMMENT is a comment to TEXT. */ static Success do_add_comment(Text_no comment, Text_no text) { Text_stat *comm_s, *text_s; GET_T_STAT(comm_s, comment, FAILURE); GET_T_STAT(text_s, text, FAILURE); ADD_MISC(comm_s, comm_to, comment_to, text); ADD_MISC(text_s, comm_in, commented_in, comment); mark_text_as_changed( comment ); mark_text_as_changed( text ); return OK; } /* * Say that RECEIVER is a recipient of TEXT. */ static Success do_add_recpt(Text_no text, Text_stat * text_s, /* May be NULL */ Conf_no receiver) { Conference *rece_c; if ( text_s == NULL ) { GET_T_STAT(text_s, text, FAILURE); } GET_C_STAT(rece_c, receiver, FAILURE); ADD_MISC(text_s, recpt, recipient, receiver); ADD_MISC(text_s, loc_no, local_no, add_text_in_conf(rece_c, text)); mark_text_as_changed(text); mark_conference_as_changed(receiver); return OK; } static Success do_add_bcc_recpt(Text_no new_text, Text_stat *text_s, /* May be NULL */ Conf_no receiver) { Conference *rece_c; if ( text_s == NULL ) GET_T_STAT(text_s, new_text, FAILURE); GET_C_STAT(rece_c, receiver, FAILURE); ADD_MISC(text_s, bcc_recpt, bcc_recipient, receiver); ADD_MISC(text_s, loc_no, local_no, add_text_in_conf(rece_c, new_text)); mark_text_as_changed( new_text ); mark_conference_as_changed( receiver ); return OK; } static Success do_add_cc_recpt(Text_no new_text, Text_stat *text_s, /* May be NULL */ Conf_no receiver) { Conference *rece_c; if ( text_s == NULL ) GET_T_STAT(text_s, new_text, FAILURE); GET_C_STAT(rece_c, receiver, FAILURE); ADD_MISC(text_s, cc_recpt, cc_recipient, receiver); ADD_MISC(text_s, loc_no, local_no, add_text_in_conf(rece_c, new_text)); mark_text_as_changed( new_text ); mark_conference_as_changed( receiver ); return OK; } /* * Return number of lines in a text */ static u_short count_lines( String str ) { u_short l = 0; while ( str.len-- > 0 ) { if( *str.string++ == '\n' ) { l++; } } return l; } /* * Delete misc_info at location loc. * If it is a recpt, cc_recpt, comm_to or footn_to delete any * loc_no, rec_time, sent_by or sent_at that might follow it. * * Note that the Misc_info is not reallocated. */ static void do_delete_misc (u_short * no_of_misc, Misc_info * misc, int loc) { int del = 1; /* Number of items to delete. */ /* Always delete at least one item. */ Bool ready; /* Check range of loc */ if ( loc < 0 || loc >= *no_of_misc ) { restart_kom("do_delete_misc() - loc out of range"); } ready = FALSE; while ( ready == FALSE && loc + del < *no_of_misc ) { switch ( misc[ loc + del ].type ) { case loc_no: case rec_time: case sent_by: case sent_at: del++; break; case recpt: case cc_recpt: case bcc_recpt: case footn_to: case footn_in: case comm_to: case comm_in: ready = TRUE; break; #ifndef COMPILE_CHECKS default: restart_kom("do_delete_misc() - illegal misc"); #endif } } *no_of_misc -= del; /* Move items beyond the deleted ones. */ while ( loc < *no_of_misc ) { misc[ loc ] = misc[ loc + del ]; loc++; } } /* * Delete a recipient from a text. Does not fail if the recipient doesn't * exist - that is not an error. */ static Success do_sub_recpt (Text_no text_no, Text_stat * text_s, /* May be NULL */ Conf_no conf_no, Conference * conf_s ) /* May be NULL */ { int i; if ( text_s == NULL ) GET_T_STAT(text_s, text_no, FAILURE); if ( conf_s == NULL ) { conf_s = cached_get_conf_stat( conf_no ); /* Might still be NULL. */ } for ( i = 0; i < text_s->no_of_misc; i++ ) { switch ( text_s->misc_items[ i ].type) { case recpt: if ( text_s->misc_items[ i ].datum.recipient == conf_no ) { if ( conf_s != NULL ) { /* Only if the conference exists: */ l2g_delete(&conf_s->texts, text_s->misc_items[ i+1 ].datum.local_no); mark_conference_as_changed (conf_no); } do_delete_misc ( &text_s->no_of_misc, text_s->misc_items, i ); mark_text_as_changed (text_no); return OK; } break; case cc_recpt: if ( text_s->misc_items[ i ].datum.cc_recipient == conf_no ) { if ( conf_s != NULL ) { l2g_delete(&conf_s->texts, text_s->misc_items[ i+1 ].datum.local_no); mark_conference_as_changed( conf_no ); } do_delete_misc( &text_s->no_of_misc, text_s->misc_items, i ); mark_text_as_changed( text_no ); return OK; } break; case bcc_recpt: if ( text_s->misc_items[ i ].datum.bcc_recipient == conf_no ) { if ( conf_s != NULL ) { l2g_delete(&conf_s->texts, text_s->misc_items[ i+1 ].datum.local_no); mark_conference_as_changed( conf_no ); } do_delete_misc( &text_s->no_of_misc, text_s->misc_items, i ); mark_text_as_changed( text_no ); return OK; } 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; #ifndef COMPILE_CHECKS default: log("%s: do_sub_recpt(): bad misc_item.\n", __FILE__); break; #endif } } err_stat = conf_no; kom_errno = KOM_NOT_RECIPIENT; return FAILURE; } /* * Delete the link between comment and comment_to. */ static void do_sub_comment (Text_no comment, /* The comment. */ Text_stat * text_s, Text_no comment_to, /* The commented. */ Text_stat * parent_s ) { int i; Bool ready; if ( text_s == NULL ) VOID_GET_T_STAT(text_s, comment); if ( parent_s == NULL ) VOID_GET_T_STAT(parent_s, comment_to); for ( ready = FALSE, i = 0; !ready && i < text_s->no_of_misc; i++ ) { switch ( text_s->misc_items[ i ].type) { case comm_to: if ( text_s->misc_items[ i ].datum.comment_to == comment_to ) { do_delete_misc( &text_s->no_of_misc, text_s->misc_items, i ); mark_text_as_changed( comment ); ready = TRUE; } break; default: break; } } if ( !ready ) restart_kom("do_sub_comment(): part 1 failed.\n"); for ( ready = FALSE, i = 0; !ready && i < parent_s->no_of_misc; i++ ) { switch ( parent_s->misc_items[ i ].type) { case comm_in: if ( parent_s->misc_items[ i ].datum.commented_in == comment ) { do_delete_misc (&parent_s->no_of_misc, parent_s->misc_items, i ); mark_text_as_changed( comment_to ); ready = TRUE; } break; default: break; } } if ( !ready ) restart_kom("do_sub_comment(): part 2 failed.\n"); return; } /* * Delete the link between footnote and footnote_to. */ static void do_sub_footnote (Text_no footnote, Text_stat * text_s, Text_no footnote_to, Text_stat * parent_s ) { int i; Bool ready; if ( text_s == NULL ) VOID_GET_T_STAT(text_s, footnote); if ( parent_s == NULL ) VOID_GET_T_STAT(parent_s, footnote_to); for ( ready = FALSE, i = 0; !ready && i < text_s->no_of_misc; i++ ) { switch ( text_s->misc_items[ i ].type) { case footn_to: if ( text_s->misc_items[ i ].datum.footnote_to == footnote_to ) { do_delete_misc( &text_s->no_of_misc, text_s->misc_items, i ); mark_text_as_changed( footnote ); ready = TRUE; } break; default: break; } } if ( !ready ) restart_kom("do_sub_footnote(): part 1 failed.\n"); for ( ready = FALSE, i = 0; !ready && i < parent_s->no_of_misc; i++ ) { switch ( parent_s->misc_items[ i ].type) { case footn_in: if ( parent_s->misc_items[ i ].datum.footnoted_in == footnote ) { do_delete_misc (&parent_s->no_of_misc, parent_s->misc_items, i ); mark_text_as_changed( footnote_to ); ready = TRUE; } break; default: break; } } if ( !ready ) restart_kom("do_sub_footnote(): part 2 failed.\n"); return; } /* * Who is sender of misc_item I? Returns 0 if there is no sent_by misc_item. */ static Pers_no sender(Text_stat * t_stat, int i) { for (i++ ; i < t_stat->no_of_misc; i++) { switch ( t_stat->misc_items[ i ].type ) { case sent_by: return t_stat->misc_items[ i ].datum.sender; case recpt: case cc_recpt: case bcc_recpt: case comm_to: case comm_in: case footn_to: case footn_in: case sent_at: return 0; /* No sender. */ case loc_no: case rec_time: break; /* These may come before a sent_by. */ #ifndef COMPILE_CHECKS default: log("ERROR: sender(): Illegal misc_item found.\n"); return 0; #endif } } return 0; /* No sender. */ } /* * Check if ACTPERS has sent this text to conference CONF_NO */ static Bool is_sender(Text_stat * text_s, Conf_no conf_no) { int i; if ( !ACTPERS ) { return FALSE; } for ( i = 0; i < text_s->no_of_misc; i++ ) { switch( text_s->misc_items[ i ].type ) { case recpt: if ( text_s->misc_items[ i ].datum.recipient == conf_no && sender ( text_s, i ) == ACTPERS ) { return TRUE; } break; case cc_recpt: if ( text_s->misc_items[ i ].datum.cc_recipient == conf_no && sender ( text_s, i ) == ACTPERS ) { return TRUE; } break; case bcc_recpt: if ( text_s->misc_items[ i ].datum.bcc_recipient == conf_no && sender ( text_s, i ) == ACTPERS ) { return TRUE; } break; case loc_no: case rec_time: case comm_to: case comm_in: case footn_to: case footn_in: case sent_by: case sent_at: break; #ifndef COMPILE_CHECKS default: restart_kom("is_sender(): Illegal misc_item found.\n"); #endif } } return FALSE; } /* * Check if ACTPERS has sent this text as a comment to parent. */ static Bool is_comm_sender(Text_stat * text_s, Text_no parent) { int i; if ( !ACTPERS ) { return FALSE; } for ( i = 0; i < text_s->no_of_misc; i++ ) { switch( text_s->misc_items[ i ].type ) { case comm_to: if ( text_s->misc_items[ i ].datum.comment_to == parent && sender ( text_s, i ) == ACTPERS ) { return TRUE; } break; case recpt: case cc_recpt: case bcc_recpt: case loc_no: case rec_time: case comm_in: case footn_to: case footn_in: case sent_by: case sent_at: break; #ifndef COMPILE_CHECKS default: restart_kom("is_comm_sender(): Illegal misc_item found.\n"); #endif } } return FALSE; } /* * Check if ACTPERS is allowed to add a footnote to a text. Sets errno if * there is an error. * * Note: Caller must set err_stat */ static Success check_footn(Text_stat * t_stat) { if ( t_stat->author != ACTPERS ) { kom_errno = KOM_NOT_AUTHOR; return FAILURE; } if ( count_footn( t_stat ) >= param.max_foot ) { err_stat = 0; kom_errno = KOM_FOOT_LIMIT; return FAILURE; } return OK; } /* * Check if ACTPERS is allowed to add a comment to a text. Sets errno if * there is an error. Note that it is allowed to comment a text even if * you are not allowed to read it. */ static Success check_comm(Text_stat * t_stat) { if ( count_comment( t_stat ) >= param.max_comm ) { err_stat = 0; kom_errno = KOM_COMM_LIMIT; return FAILURE; } return OK; } /* * Return a pointer to a Mark if pers_no has marked the text text_no. * Otherwise, return NULL. */ static Mark * locate_mark(Pers_no pers_no, Person *pers_p, /* May be NULL. */ Text_no text_no) { Mark *mp; Mark *result = NULL; int i; Mark_list mlist; if ( pers_p == NULL ) GET_P_STAT(pers_p, pers_no, NULL); mlist = pers_p->marks; for ( i = mlist.no_of_marks, mp = mlist.marks; i > 0 && result == NULL; i--, mp++ ) { if ( mp->text_no == text_no ) result = mp; } return result; } /* * Skip one misc-item with all additional data. * * This is only used from get_text_stat. */ static void skip_recp (Misc_info ** misc, Text_stat * text_stat ) { Bool ready = FALSE; ++(*misc); while ( !ready && *misc < text_stat->misc_items + text_stat->no_of_misc ) { switch ( (*misc)->type ) { case loc_no: case rec_time: case sent_by: case sent_at: ++(*misc); break; case recpt: case cc_recpt: case bcc_recpt: case comm_to: case comm_in: case footn_to: case footn_in: ready = TRUE; break; #ifndef COMPILE_CHECKS default: restart_kom("skip_recp() - illegal misc\n"); #endif } } } /* * Check if person is a member in any of the recipients or cc_recipients * of text_s */ static Bool is_member_in_recpt(Person *person, Text_stat *text_s) { int i; for ( i = 0; i < text_s->no_of_misc; i++ ) { switch(text_s->misc_items[ i ].type) { case recpt: if ( locate_membership(text_s->misc_items[ i ].datum.recipient, person) != NULL ) { return TRUE; } break; case cc_recpt: if ( locate_membership(text_s->misc_items[ i ].datum.cc_recipient, person) != NULL ) { return TRUE; } break; case bcc_recpt: if ( locate_membership(text_s->misc_items[ i ].datum.bcc_recipient, person) != NULL ) { return 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; #ifndef COMPILE_CHECKS default: log("%s: is_member_in_recpt(): bad misc_item.\n", __FILE__); break; #endif } } return FALSE; } /* * Copy the text_stat original into result, but only those part * that viewer is allowed to see. (Censor away information about * conferences that viewer is not allowed to know about). * * All memory is allocated via tmp_alloc(). */ static void filter_secret_info(Text_stat *result, Text_stat *original, Pers_no viewer, Person *viewer_p, /* May be NULL. */ Bool output_bcc) { Misc_info * orig; Misc_info * copy; /* Censored Misc_infos */ /* ^^^ Possible optimisation: No need to copy unless there is a secret conf among the recipients. */ *result = *original; filter_aux_item_list(&original->aux_item_list, &result->aux_item_list, viewer, viewer_p); result->misc_items = tmp_alloc(result->no_of_misc * sizeof ( Misc_info)); result->no_of_misc = 0; copy = result->misc_items; orig = original->misc_items; while ( orig < original->misc_items + original->no_of_misc ) { switch( orig->type ) { case recpt: if ( (fast_access_perm (orig->datum.recipient, viewer, viewer_p) <= none ) && !ENA(admin, 4)) { skip_recp ( &orig, original ); } else { *copy++ = *orig++; ++result->no_of_misc; } break; case cc_recpt: if ( (fast_access_perm (orig->datum.cc_recipient, viewer, viewer_p) <= none) && !ENA(admin, 4)) { skip_recp ( &orig, original ); } else { *copy++ = *orig++; ++result->no_of_misc; } break; case bcc_recpt: if (viewer_p == NULL) viewer_p = cached_get_person_stat(viewer); if (viewer_p == NULL || (!is_supervisor(orig->datum.bcc_recipient, NULL, viewer, viewer_p) && locate_membership(orig->datum.bcc_recipient, viewer_p) == NULL && !ENA(admin, 4))) { skip_recp ( &orig, original ); } else if (!output_bcc) { *copy = *orig; copy->type = cc_recpt; copy += 1; orig += 1; ++result->no_of_misc; } else { *copy++ = *orig++; ++result->no_of_misc; } break; case loc_no: case rec_time: case comm_to: case comm_in: case footn_to: case footn_in: case sent_by: case sent_at: *copy++ = *orig++; ++result->no_of_misc; break; #ifndef COMPILE_CHECKS default: restart_kom("filter_secret_info() - illegal misc_item!\n"); #endif } } } /* * Send message to all recipients that a text has been deleted */ static void send_async_deleted_text (Text_no text_no, Text_stat *text_s) { Connection *cptr; Text_stat filtered; Session_no i = 0; init_text_stat(&filtered); while ((i = traverse_connections(i)) != 0) { cptr = get_conn_by_number(i); filter_secret_info(&filtered, text_s, cptr->pers_no, cptr->person, TRUE); if (cptr->person != NULL && is_member_in_recpt(cptr->person, &filtered) == TRUE) { async_deleted_text(cptr, text_no, &filtered); } } /* * This is not strictly necessary since the text-stat is sent * in the previous message, and the clients should be able to * update their caches from that information. */ /* for (misc = text_s->misc_items; misc < text_stat->misc_items + text_stat->no_of_misc; misc++) { if (misc->type == footn_in || misc->type == comm_in || misc->type == comm_to || misc->type == footn_to) { i = 0; c_text_no = 0; if (misc->type == comm_to) c_text_no = misc->datum.comment_to; else if (misc->type == comm_in) c_text_no = misc->datum.comment_in; else if (misc->type == footn_in) c_text_no = misc->datum.footnote_in; else if (misc->type == footn_to) c_text_no = misc->datum.footnote_to; c_text = get_text_stat(c_text_no); if (c_text) { while ((i = traverse_connections(i)) != 0) { cptr = get_conn_by_number(i); if (cptr->person != NULL && is_member_in_recpt(cptr->person, c_text) == TRUE) { async_invalidate_text(cptr, c_text_no); } } } } } */ } /* * End of static functions. */ /* * Functions that are exported to the rest of the server. */ /* * Check if ACTPERS is allowed to read this text. * Returns TRUE if he is allowed to read it. */ Bool text_read_access(Text_no text_no, Text_stat * text_stat) { int i; Misc_info *misc; Conference *recipient; /* * Nope, people who aren't logged in, should NOT be * allowed to read any texts but motd_of_lyskom! */ if ( text_no == kom_info.motd_of_lyskom ) return TRUE; CHK_LOGIN(FALSE); if ( text_stat == NULL ) GET_T_STAT(text_stat, text_no, FALSE); if ( ENA(wheel, 10)) return TRUE; if ( ACTPERS ) { if ( text_stat->author == ACTPERS ) return TRUE; /* Check if ACTPERS or current working conference is a recipient */ for (i = text_stat->no_of_misc, misc = text_stat->misc_items; i; i--, misc++) { if ( misc->type == recpt && (misc->datum.recipient == active_connection->cwc || misc->datum.recipient == ACTPERS )) { return TRUE; } if ( misc->type == cc_recpt && (misc->datum.cc_recipient == active_connection->cwc || misc->datum.cc_recipient == ACTPERS )) { return TRUE; } if ( misc->type == bcc_recpt && (misc->datum.bcc_recipient == active_connection->cwc || misc->datum.bcc_recipient == ACTPERS )) { return TRUE; } } /* Check if ACTPERS is member in any of the recipients */ for (i = text_stat->no_of_misc, misc = text_stat->misc_items; i; i--, misc++) { if ( misc->type == recpt && locate_membership( misc->datum.recipient, ACT_P) != NULL) { return TRUE; } if ( misc->type == cc_recpt && locate_membership( misc->datum.cc_recipient, ACT_P) != NULL) { return TRUE; } if ( misc->type == bcc_recpt && locate_membership( misc->datum.bcc_recipient, ACT_P) != NULL) { return TRUE; } } if ( locate_mark(ACTPERS, ACT_P, text_no) != NULL ) { return TRUE; } } /* Check if any of the recipients is an open conference, */ /* or if ACTPERS is a supervisor. (Note: ACTPERS is not */ /* supervisor of anything if he isn't logged in.) */ for (i = text_stat->no_of_misc, misc = text_stat->misc_items; i; i--, misc++) { if ( misc->type == recpt && (recipient = cached_get_conf_stat( misc->datum.recipient )) != NULL ) { if ( !recipient->type.rd_prot || ENA(wheel, 8) || is_supervisor(misc->datum.recipient, recipient, ACTPERS, ACT_P) == TRUE) { return TRUE; } } if ( misc->type == cc_recpt && (recipient = cached_get_conf_stat( misc->datum.cc_recipient )) != NULL ) { if ( !recipient->type.rd_prot || ENA(wheel, 8) || is_supervisor(misc->datum.cc_recipient, recipient, ACTPERS, ACT_P) == TRUE) { return TRUE; } } if (misc->type == bcc_recpt && (recipient = cached_get_conf_stat( misc->datum.bcc_recipient )) != NULL ) { if ( ENA(wheel, 8) || is_supervisor(misc->datum.bcc_recipient, recipient, ACTPERS, ACT_P) == TRUE ) { return TRUE; } } } return FALSE; } /* * Delete a text from the database. Deletes all "links" to/from other * texts/persons/conferences. (e. g. comm_to/comm_in) */ Success do_delete_text(Text_no text_no, Text_stat *text_s) { Person *author; if ( text_s == NULL ) GET_T_STAT(text_s, text_no, FAILURE); send_async_deleted_text(text_no, text_s); if ((author = cached_get_person_stat (text_s->author)) != NULL) l2g_delete_global_in_sorted(&author->created_texts, text_no); while ( text_s->no_of_misc > 0 ) { switch ( text_s->misc_items[ 0 ].type ) { case recpt: if ( do_sub_recpt(text_no, text_s, text_s->misc_items[ 0 ].datum.recipient, NULL) != OK ) restart_kom("do_delete_text(): error pos 1.\n"); break; case cc_recpt: if ( do_sub_recpt(text_no, text_s, text_s->misc_items[ 0 ].datum.cc_recipient, NULL) != OK ) restart_kom("do_delete_text(): error pos 2.\n"); break; case bcc_recpt: if ( do_sub_recpt(text_no, text_s, text_s->misc_items[ 0 ].datum.bcc_recipient, NULL) != OK ) restart_kom("do_delete_text(): error pos 2.\n"); break; case comm_to: do_sub_comment(text_no, text_s, text_s->misc_items[ 0 ].datum.comment_to, NULL); break; case comm_in: do_sub_comment(text_s->misc_items[ 0 ].datum.commented_in, NULL, text_no, text_s); break; case footn_to: do_sub_footnote(text_no, text_s, text_s->misc_items[ 0 ].datum.footnote_to, NULL); break; case footn_in: do_sub_footnote(text_s->misc_items[ 0 ].datum.footnoted_in, NULL, text_no, text_s); break; case loc_no: case rec_time: case sent_by: case sent_at: restart_kom("do_delete_text(): Illegal misc-item syntax.\n"); #ifndef COMPILE_CHECKS default: restart_kom("do_delete_text(): Illegal misc-item.\n"); #endif } } cached_delete_text(text_no); return OK; } /* * Atomic calls. */ /* * Calls to handle marks. * * Marks are secret. No else can know what you have marked. */ /* * Get text_nos of all marked texts. */ extern Success get_marks( Mark_list *result ) { CHK_LOGIN(FAILURE); *result = ACT_P->marks; return OK; } /* * Get the text. The text will not be marked as read until you * explicitly mark_as_read() it. start_char = 0 && end_char = END_OF_STRING * gives the entire text. */ extern Success get_text (Text_no text_no, String_size start_char, String_size end_char, String * result) { Text_stat * text_s; GET_T_STAT(text_s, text_no, FAILURE); /* Check if ACTPERS has read acess to the text */ if ( text_read_access( text_no, text_s ) != TRUE ) { kom_errno = KOM_NO_SUCH_TEXT; err_stat = text_no; return FAILURE; } *result = cached_get_text( text_no ); if ( start_char > result->len ) { err_stat = start_char; kom_errno = KOM_INDEX_OUT_OF_RANGE; return FAILURE; } /* Should use something in s-string.c to do this. +++ */ result->string += start_char; result->len = min ( result->len - 1, end_char ) + 1 - start_char; if ( ACTPERS ) { ++ACT_P->no_of_text_fetches; mark_person_as_changed( ACTPERS ); } return OK; } /* * Get text status. * * If there are recipients of the text that are secret confs * those misc-items will be censored. */ extern Success get_text_stat_old (Text_no text_no, Text_stat *result) { Text_stat * text_stat; GET_T_STAT(text_stat, text_no, FAILURE); if ( !text_read_access(text_no, text_stat) && !ENA(admin, 2)) { kom_errno = KOM_NO_SUCH_TEXT; err_stat = text_no; return FAILURE; } filter_secret_info(result, text_stat, ACTPERS, ACT_P, FALSE); return OK; } extern Success get_text_stat (Text_no text_no, Text_stat *result ) { Text_stat * text_stat; GET_T_STAT(text_stat, text_no, FAILURE); if ( !text_read_access(text_no, text_stat) && !ENA(admin, 2)) { kom_errno = KOM_NO_SUCH_TEXT; err_stat = text_no; return FAILURE; } filter_secret_info(result, text_stat, ACTPERS, ACT_P, TRUE); return OK; } /* * Functions local to create_text: */ /* * Check that the text can be submitted anonymously */ static Success check_anonymous_subm(Conf_no addressee) { Conf_type conf_type; conf_type = cached_get_conf_type(addressee); if (conf_type.allow_anon) return OK; else return FAILURE; } /* * Check that the recipient or cc_recipient at LOC is not already a * recipient or cc_recipient of this text. */ static Success check_double_subm (Misc_info * misc, int loc, Conf_no addressee) { int j; for ( j = 0; j < loc; j++) { switch ( misc[ j ].type ) { case recpt: if ( misc[ j ].datum.recipient == addressee ) return FAILURE; break; case cc_recpt: if ( misc[ j ].datum.cc_recipient == addressee ) return FAILURE; break; case bcc_recpt: if ( misc[ j ].datum.bcc_recipient == addressee ) return FAILURE; 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; #ifndef COMPILE_CHECKS default: log("%s: check_double_subm(): bad misc_item.\n", __FILE__); break; #endif } } return OK; } /* * Check that none of the first 'pos' misc_items pointed to by misc * is a comment or footnote to text forbidden. */ static Success check_double_comm (Misc_info *misc, int pos, Text_no forbidden) { for(;pos > 0; pos--, misc++) { switch(misc->type) { case comm_to: if ( misc->datum.comment_to == forbidden ) return FAILURE; break; case footn_to: if ( misc->datum.footnote_to == forbidden ) return FAILURE; break; case recpt: case cc_recpt: case bcc_recpt: case comm_in: case footn_in: case loc_no: case rec_time: case sent_by: case sent_at: break; #ifndef COMPILE_CHECKS default: log("%s: check_double_subm(): bad misc_item.\n", __FILE__); break; #endif } } return OK; } /* * Check that all misc_items are legal. Return OK / FAILURE. * Update recpt & cc_recpt fields if the text goes to a super_conf. * Signal an error if a conference is recipient more than once, or if * a text is a comment to the same text more than once. */ static Success create_text_check_misc (u_short * no_of_misc, Misc_info * misc, Bool anonymous ) { int i; Text_stat * parent; Conf_no addressee; for ( i = 0; i < *no_of_misc; i++) { err_stat = i; /* In case of an error, err_stat indicates which misc_item caused the error. */ switch( misc[ i ].type ) { case footn_to: /* Check that ACTPERS is the author to the text. */ GET_T_STAT( parent, misc[ i ].datum.footnote_to, FAILURE); if ( check_footn(parent) == FAILURE ) return FAILURE; if ( check_double_comm(misc, i, misc[ i ].datum.footnote_to) != OK ) { kom_errno = KOM_ILL_MISC; return FAILURE; } break; case comm_to: /* Check that the text exists and is readable. */ GET_T_STAT( parent, misc[ i ].datum.comment_to, FAILURE); if ( check_comm(parent) == FAILURE ) return FAILURE; if ( check_double_comm(misc, i, misc[ i ].datum.comment_to) != OK ) { kom_errno = KOM_ILL_MISC; return FAILURE; } if (!text_read_access(misc[i].datum.comment_to, parent)) { kom_errno = KOM_NO_SUCH_TEXT; return FAILURE; } break; case recpt: /* Check that ACTPERS has write access to the conference or a superconference. */ /* Superconfs are recursive */ addressee = submit_to( misc[ i ].datum.recipient, NULL); /* Update in case of super_conf */ if ((misc[ i ].datum.recipient = addressee) == 0) { kom_errno = KOM_ACCESS; return FAILURE; } /* Check that this recipient is not already a recipient. */ if ( check_double_subm(misc, i, addressee) != OK ) { kom_errno = KOM_ILL_MISC; return FAILURE; } /* Check that an anonymous text can be created */ if (anonymous && check_anonymous_subm(addressee) != OK) { kom_errno = KOM_ANON_REJECTED; return FAILURE; } break; case cc_recpt: /* Check that ACTPERS has write access to the conference or a superconference. */ /* Superconfs are recursive */ addressee = submit_to( misc[ i ].datum.cc_recipient, NULL); /* Update in case of super_conf */ if ((misc[ i ].datum.cc_recipient = addressee) == 0) { kom_errno = KOM_ACCESS; return FAILURE; } /* Check that this recipient is not already a recipient. */ if (check_double_subm(misc, i, addressee) != OK) { kom_errno = KOM_ILL_MISC; return FAILURE; } /* Check that an anonymous text can be created */ if (anonymous && check_anonymous_subm(addressee) != OK) { kom_errno = KOM_ANON_REJECTED; return FAILURE; } break; case bcc_recpt: /* Check that ACTPERS has write access to the conference or a superconference. */ /* Superconfs are recursive */ addressee = submit_to( misc[ i ].datum.bcc_recipient, NULL); /* Update in case of super_conf */ if ((misc[ i ].datum.bcc_recipient = addressee) == 0) { kom_errno = KOM_ACCESS; return FAILURE; } /* Check that this recipient is not already a recipient. */ if (check_double_subm(misc, i, addressee) != OK) { kom_errno = KOM_ILL_MISC; return FAILURE; } /* Check that an anonymous text can be created */ if (anonymous && check_anonymous_subm(addressee) != OK) { kom_errno = KOM_ANON_REJECTED; return FAILURE; } break; case loc_no: /* Ignore loc_no */ break; case comm_in: case footn_in: case rec_time: case sent_by: case sent_at: /* Fall through */ #ifndef COMPILE_CHECKS default: #endif kom_errno = KOM_ILL_MISC; return FAILURE; } } return OK; } /* * Add the aux_infos to the new text stat */ static Success create_text_add_aux(Text_stat *t_stat, Text_no text_no, Aux_item_list *aux, Pers_no creator) { u_short i; Text_stat *parent; text_stat_add_aux_item_list(t_stat, text_no, aux, creator); for (i = 0; i < t_stat->no_of_misc; i++) { switch(t_stat->misc_items[i].type) { case footn_to: parent = cached_get_text_stat(t_stat->misc_items[i].datum.footnote_to); if (parent != NULL && text_read_access(t_stat->misc_items[i].datum.footnote_to, parent)) { aux_inherit_items(&t_stat->aux_item_list, &parent->aux_item_list, &t_stat->highest_aux, t_stat->author, TRUE, TEXT_OBJECT_TYPE, text_no, t_stat); } break; case comm_to: parent = cached_get_text_stat(t_stat->misc_items[i].datum.comment_to); if (parent != NULL && text_read_access(t_stat->misc_items[i].datum.comment_to, parent)) { aux_inherit_items(&t_stat->aux_item_list, &parent->aux_item_list, &t_stat->highest_aux, t_stat->author, TRUE, TEXT_OBJECT_TYPE, text_no, t_stat); } break; default: break; } } return OK; } /* * Fix all double references. Eg if misc contains comm_to, add a comm_in field. * No access-permission checking is done. */ static Success create_text_add_miscs(Text_no new_text, int no_of_misc, Misc_info * misc) { int i; for ( i = 0; i < no_of_misc; i++) { err_stat = i; /* In case of an error, err_stat indicates which misc_item caused the error. */ switch( misc[ i ].type ) { case footn_to: if ( do_add_footnote(new_text, misc[ i ].datum.footnote_to) != OK) return FAILURE; break; case comm_to: if ( do_add_comment(new_text, misc[ i ].datum.comment_to) != OK ) return FAILURE; break; case recpt: if ( do_add_recpt(new_text, NULL, misc[ i ].datum.recipient) != OK) return FAILURE; break; case cc_recpt: if ( do_add_cc_recpt(new_text, NULL, misc[ i ].datum.cc_recipient) !=OK ) return FAILURE; break; case bcc_recpt: if ( do_add_bcc_recpt(new_text, NULL, misc[ i ].datum.bcc_recipient) !=OK ) return FAILURE; break; case loc_no: /* Ignore loc_no. */ break; case comm_in: case footn_in: case rec_time: case sent_by: case sent_at: /* Fall through */ #ifndef COMPILE_CHECKS default: #endif restart_kom("create_text_add_misc() - illegal Info_type"); } } return OK; } /* * Send an asynchronous message, but filter any secret information. */ static void send_async_new_text_old(Text_no text_no, Text_stat *text_s) { Connection *cptr; Session_no i = 0; Text_stat filtered; init_text_stat(&filtered); while ( (i = traverse_connections(i)) != 0 ) { cptr = get_conn_by_number(i); /* * filter_secret_info copies the text-stat to filtered, but * since it is allocated with tmp_alloc we do not need to free * the memory now. */ filter_secret_info(&filtered, text_s, cptr->pers_no, cptr->person, FALSE); if ( cptr->person != NULL && is_member_in_recpt (cptr->person, &filtered) == TRUE) { async_new_text_old (cptr, text_no, &filtered); } } } static void send_async_new_text (Text_no text_no, Text_stat *text_s) { Connection *cptr; Session_no i = 0; Text_stat filtered; init_text_stat(&filtered); while ( (i = traverse_connections(i)) != 0 ) { cptr = get_conn_by_number(i); /* * filter_secret_info copies the text-stat to filtered, but * since it is allocated with tmp_alloc we do not need to free * the memory now. */ filter_secret_info(&filtered, text_s, cptr->pers_no, cptr->person, TRUE); if ( cptr->person != NULL && is_member_in_recpt (cptr->person, &filtered) == TRUE) { async_new_text (cptr, text_no, &filtered); } } } /* * Special case handling of special text numbers. * * In the KOMmunity, there has arisen an odd phenomenon known as * "jubel" to the local citizens of LysKOM. There are several kinds * of "jubel"; a text number divisible by 1000 is one famous such. * * There is a feeling that jubels, at least the more prominent jubels, * should not be created by automatic postings. This code is * primarily aimed at stopping such things, but it can have other, eh, * interesting uses as well... */ struct jubel { struct jubel *next; Pers_no bad_guy; Text_no divisor; /* 0 is used to represent infinity */ Text_no remainder; }; static struct jubel *jubel_root = NULL; /* * register_jubel(char*) - tell lyskomd about a jubel, and a * person which is not allowed to create that jubel. The argument is * a string consisting of two or three numbers: * persno textno * persno divisor remainder * meaning that PERSNO is not allowed to create text TEXTNO, or any * text number which fulfills the relation textno%DIVISOR==REMAINDER. */ void register_jubel(Pers_no pno, Text_no divis, /* 0 if no division should be made. */ Text_no tno) { struct jubel *j = smalloc(sizeof(struct jubel)); j->next = jubel_root; j->bad_guy = pno; j->divisor = divis; j->remainder = tno; jubel_root = j; } /* Free the jubel list */ void free_all_jubel(void) { struct jubel *a; struct jubel *b; a = jubel_root; while (a != NULL) { b = a; a = a->next; sfree(b); } jubel_root = NULL; } /* Check if it is ok for ACTPERS to create the next text. */ static Bool ok_to_create_next_text(void) { struct jubel *j; Text_no next_tno; Bool is_jubel = FALSE; next_tno = query_next_text_num(); for (j = jubel_root; j != NULL; j = j->next) { if ((j->divisor == 0 && next_tno == j->remainder) || (j->divisor != 0 && next_tno%j->divisor == j->remainder)) { if (ACTPERS == j->bad_guy) { log("Stopped person %d from creating jubel %lu.\n", ACTPERS, next_tno); return FALSE; } else is_jubel = TRUE; } } if (is_jubel) log("Granted jubel %lu to person %d.\n", next_tno, ACTPERS); return TRUE; } /* * Create a text. * * The recipients may change. See doc for set_permitted_submitters. * * The only allowed Misc_items are recpt, cc_recpt, comm_to and footn_to. * loc_no are allowed, but ignored. * * Returns text_no of the created text, or 0 if there was an error. */ extern Text_no create_text_old(const String message, u_short no_of_misc, Misc_info * misc) { Text_stat *t_stat; Text_no text; text = do_create_text(message, no_of_misc, misc, NULL, FALSE, &t_stat); if (text != 0) { send_async_new_text_old (text, t_stat); /* Send asynchronous message. */ send_async_new_text (text, t_stat); /* Send asynchronous message. */ } return text; } extern Text_no create_text(const String message, u_short no_of_misc, Misc_info * misc, Aux_item_list * aux) { Text_stat *t_stat; Text_no text; text = do_create_text(message, no_of_misc, misc, aux, FALSE, &t_stat); if (text != 0) { send_async_new_text_old (text, t_stat); /* Send asynchronous message.*/ send_async_new_text (text, t_stat); /* Send asynchronous message. */ } return text; } /* * Create an anonymous text. * * This is just like create_text, but the author of the text is set to * Pers_no 0, to guarantee that the author is anonymous. * * Returns text_no of the created text, or 0 if there was an error. */ extern Text_no create_anonymous_text(const String message, u_short no_of_misc, Misc_info * misc, Aux_item_list * aux) { Text_no text; Text_stat * t_stat; text = do_create_text(message, no_of_misc, misc, aux, TRUE, &t_stat); if (text != 0) { send_async_new_text_old (text, t_stat); send_async_new_text (text, t_stat); } return text; } extern Text_no create_anonymous_text_old(const String message, u_short no_of_misc, Misc_info * misc ) { Text_no text; Text_stat * t_stat; text = do_create_text(message, no_of_misc, misc, NULL, TRUE, &t_stat); if (text != 0) { send_async_new_text_old (text, t_stat); send_async_new_text (text, t_stat); } return text; } /* * Generic create_text call. This is used by all the other create_text * calls, old and new, anonymous and not */ static Text_no do_create_text(const String message, u_short no_of_misc, Misc_info * misc, Aux_item_list * aux, Bool anonymous, Text_stat **ret_stat) { Text_no text; Text_stat * t_stat; CHK_LOGIN(0); /* Check the length of the text. */ if ( s_strlen (message) > param.text_len ) { err_stat = 0; kom_errno = KOM_LONG_STR; return 0; } /* Check that the author is allowed to write this text number */ if (ok_to_create_next_text() == FALSE) { err_stat = 0; kom_errno = KOM_TEMPFAIL; return 0; } /* Check all misc-items and aux-items */ prepare_aux_item_list(aux, anonymous?0:ACTPERS); if ( create_text_check_misc(&no_of_misc, misc, anonymous) != OK || text_stat_check_add_aux_item_list(NULL, aux, ACTPERS, TRUE) != OK || (text = cached_create_text( message )) == 0) { return 0; } if ( (t_stat = cached_get_text_stat( text )) == NULL ) { restart_kom("%s.\nText == %lu, kom_errno == %lu, errno == %lu\n", "create_text: can't get text-stat of newly created text", (unsigned long)text, (unsigned long)kom_errno, (unsigned long)errno); } *ret_stat = t_stat; t_stat->author = anonymous?0:ACTPERS; t_stat->creation_time = time(NULL); t_stat->no_of_lines = count_lines( message ); t_stat->no_of_chars = s_strlen( message ); if ( create_text_add_miscs(text, no_of_misc, misc) != OK ) { log("ERROR: create_text(): can't add miscs.\n"); return 0; } if ( create_text_add_aux(t_stat, text, aux, anonymous?0:ACTPERS ) != OK ) { log("ERROR: create_text(): can't att aux.\n"); return 0; } if (!anonymous) { l2g_append(&ACT_P->created_texts, l2g_first_appendable_key(&ACT_P->created_texts), text); ACT_P->created_lines += t_stat->no_of_lines; ACT_P->created_bytes += t_stat->no_of_chars; mark_person_as_changed (ACTPERS); } mark_text_as_changed (text); return text; } /* * Delete a text. * * Only a supervisor of the author may delete a text. */ extern Success delete_text( Text_no text_no ) { Text_stat *text_s; CHK_LOGIN(FAILURE); GET_T_STAT(text_s, text_no, FAILURE); if ( !is_supervisor(text_s->author, NULL, ACTPERS, ACT_P) && !ENA(wheel, 8) && !ENA(admin, 5) ) { err_stat = text_no; kom_errno = (text_read_access(text_no, text_s) ? KOM_NOT_AUTHOR : KOM_NO_SUCH_TEXT); return FAILURE; } return do_delete_text(text_no, text_s); } /* Return TRUE if ``a'' is greater than ``b'. */ static Bool greater(const struct tm *a, const struct tm *b) { if (a->tm_year < b->tm_year) return FALSE; if (a->tm_year > b->tm_year) return TRUE; if (a->tm_mon < b->tm_mon) return FALSE; if (a->tm_mon > b->tm_mon) return TRUE; if (a->tm_mday < b->tm_mday) return FALSE; if (a->tm_mday > b->tm_mday) return TRUE; if (a->tm_hour < b->tm_hour) return FALSE; if (a->tm_hour > b->tm_hour) return TRUE; if (a->tm_min < b->tm_min ) return FALSE; if (a->tm_min > b->tm_min ) return TRUE; if (a->tm_sec < b->tm_sec) return FALSE; if (a->tm_sec > b->tm_sec) return TRUE; /* ``a'' is not greater if it is equal to ``b''. */ return FALSE; } /* * Lookup a text according to creation-time. * The text-no of the text created closest before WANTED_TIME is returned. * The returned text is not necessarily readable. */ extern Success get_last_text(struct tm *wanted_time, Text_no *result) { struct tm *texttime; Text_no lower = 0; Text_no higher = query_next_text_num() - 1; Text_stat *text_stat = NULL; Text_no try; Text_no middle; /* * We search for the text in the interval [lower, higher] * (inclusive). Middle is set to the middle (rounded towards * infinity), and we search from there and upward until we find a * text. */ while (lower < higher) { middle = (lower + higher)/2 + 1; /* Binary search */ try = middle; text_stat = NULL; /* FIXME: Once there is a more efficient structure that maps Text_nos to the internal cache_node this loop could probably be rewritten in a more efficient way. */ while (text_stat == NULL && try <= higher) text_stat = cached_get_text_stat (try++); if ( text_stat == NULL ) higher = middle - 1; else { texttime = localtime(&text_stat->creation_time); if (greater(wanted_time, texttime)) lower = try - 1; else higher = middle - 1; } } *result = lower; return OK; } /* * Return next existing text-no, which ACTPERS is allowed to read. */ extern Success find_next_text_no (Text_no start, Text_no *result) { Text_no highest = query_next_text_num(); Text_stat *text_s; err_stat = start; while (++start < highest) { text_s = cached_get_text_stat(start); if (text_s != NULL && text_read_access(start, text_s) == TRUE) { *result = start; return OK; } } kom_errno = KOM_NO_SUCH_TEXT; return FAILURE; } /* * Return previous existing text-no, which ACTPERS is allowed to read. */ extern Success find_previous_text_no (Text_no start, Text_no *result) { Text_stat *text_s; Text_no next_tno; const Text_no saved_start = start; if (start > (next_tno = query_next_text_num())) start = next_tno; while (start-- > 0) { text_s = cached_get_text_stat(start); if (text_s != NULL && text_read_access(start, text_s) == TRUE) { *result = start; return OK; } } kom_errno = KOM_NO_SUCH_TEXT; err_stat = saved_start; return FAILURE; } /* * Add a recipient to a text. */ extern Success add_recipient( Text_no text_no, Conf_no conf_no, Info_type type ) /* recpt or cc_recpt */ { Text_stat * t_stat; Conference * conf_c; int rcpt_index; CHK_LOGIN(FAILURE); GET_T_STAT(t_stat, text_no, FAILURE); GET_C_STAT(conf_c, conf_no, FAILURE); if ( !text_read_access(text_no, t_stat ) && !ENA(admin, 4)) { err_stat = text_no; kom_errno = KOM_NO_SUCH_TEXT; return FAILURE; } rcpt_index = find_recipient(conf_no, t_stat); if ( rcpt_index != -1 ) { /* You cannot add the same conference twice. */ if (t_stat->misc_items[rcpt_index].type == type) { err_stat = conf_no; kom_errno = KOM_ALREADY_RECIPIENT; return FAILURE; } if (type != recpt && type != cc_recpt && type != bcc_recpt) { err_stat = type; kom_errno = KOM_ILLEGAL_INFO_TYPE; return FAILURE; } if (!is_supervisor(conf_no, conf_c, ACTPERS, ACT_P) && !ENA(wheel, 8)) { err_stat = conf_no; kom_errno = KOM_PERM; return FAILURE; } /* Change a recpt to a cc_recpt or a cc_recpt to a recpt. The ability to do this was introduced after a request from Lisa Hallingstrom, who wanted to do this repeatedly when managing the conference "Fragor och svar". */ t_stat->misc_items[rcpt_index].type = type; #if 0 /* This should possibly be done, but differently. ADD_MISC adds things lasts in the misc-info, and that is not the correct thing to do. The change is now performed silently instead. Also note that these fields may already be present... FIXME. */ if ( t_stat->author != ACTPERS ) ADD_MISC(t_stat, sent_by, sender, ACTPERS); ADD_MISC(t_stat, sent_at, sent_at, time(NULL)); #endif mark_text_as_changed( text_no ); return OK; } if ( count_recipients( t_stat ) >= param.max_recipients ) { err_stat = text_no; kom_errno = KOM_RECIPIENT_LIMIT; return FAILURE; } /* FIXME: Should extract the "is allowed to write to this conference" test from submit_to(). */ if (submit_to(conf_no, conf_c) != conf_no) { err_stat = conf_no; kom_errno = KOM_PERM; return FAILURE; } if (!conf_c->type.allow_anon && t_stat->author == 0) { kom_errno = KOM_ANON_REJECTED; err_stat = 0; return FAILURE; } switch ( type ) { case recpt: if ( do_add_recpt(text_no, t_stat, conf_no) != OK) return FAILURE; break; case cc_recpt: if ( do_add_cc_recpt(text_no, t_stat, conf_no) != OK) return FAILURE; break; case bcc_recpt: if ( do_add_bcc_recpt(text_no, t_stat, conf_no) != OK) return FAILURE; 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: /* Fall through */ #ifndef COMPILE_CHECKS default: #endif err_stat = type; kom_errno = KOM_ILLEGAL_INFO_TYPE; return FAILURE; } if ( t_stat->author != ACTPERS ) ADD_MISC(t_stat, sent_by, sender, ACTPERS); ADD_MISC(t_stat, sent_at, sent_at, time(NULL)); mark_text_as_changed( text_no ); return OK; } /* * Subtract a recipient from a text. * * This may be done by * a) a supervisor of the author. * b) a supervisor of the recipient. * c) the sender of the text to the recipient */ extern Success sub_recipient( Text_no text_no, Conf_no conf_no) { Text_stat * text_s; Conference * conf_c; CHK_LOGIN(FAILURE); GET_T_STAT(text_s, text_no, FAILURE); if ( !text_read_access(text_no, text_s ) && !ENA(admin, 4)) { err_stat = text_no; kom_errno = KOM_NO_SUCH_TEXT; return FAILURE; } if (find_recipient(conf_no, text_s) == -1) { err_stat = conf_no; kom_errno = KOM_NOT_RECIPIENT; return FAILURE; } GET_C_STAT(conf_c, conf_no, FAILURE); if ( !is_supervisor (text_s->author, NULL, ACTPERS, ACT_P) && !is_supervisor (conf_no, conf_c, ACTPERS, ACT_P) && !ENA(wheel, 8) && !is_sender (text_s, conf_no) ) { err_stat = text_no; kom_errno = KOM_PERM; return FAILURE; } return do_sub_recpt( text_no, text_s, conf_no, conf_c ); } /* * Add a comment-link between two existing texts. */ extern Success add_comment(Text_no comment, Text_no comment_to) { Text_stat *child_s, *parent_s; CHK_LOGIN(FAILURE); /* The following code can not cope with a text that is a comment to itself. That is considered to be a bug. Work around it for now, until a proper misc-info-list handling package is written. FIXME +++ /// */ if (comment == comment_to) { err_stat = comment; kom_errno = KOM_INDEX_OUT_OF_RANGE; return FAILURE; } GET_T_STAT(child_s, comment, FAILURE); GET_T_STAT(parent_s, comment_to, FAILURE); err_stat = comment_to; if ( check_comm(parent_s) != OK ) return FAILURE; if ( !text_read_access(comment, child_s) ) { err_stat = comment; kom_errno = KOM_NO_SUCH_TEXT; return FAILURE; } if (!text_read_access(comment_to, parent_s)) { err_stat = comment_to; kom_errno = KOM_NO_SUCH_TEXT; return FAILURE; } /* Check if already comment */ if ( is_comment_to(comment, parent_s) ) { err_stat = comment; kom_errno = KOM_ALREADY_COMMENT; return FAILURE; } if ( do_add_comment(comment, comment_to) != OK) return FAILURE; if ( child_s->author != ACTPERS ) ADD_MISC(child_s, sent_by, sender, ACTPERS); ADD_MISC(child_s, sent_at, sent_at, time(NULL)); mark_text_as_changed (comment); return OK; } /* * Delete a comment-link between two texts. * * This can be done by: * a) a supervisor of the author of the comment. * b) a supervisor of the creator of the comment link. */ extern Success sub_comment( Text_no comment, /* 'comment' is no longer a comment */ Text_no parent ) /* to 'parent' */ { Text_stat * text_s; Text_stat * parent_s; CHK_LOGIN(FAILURE); GET_T_STAT(text_s, comment, FAILURE); GET_T_STAT(parent_s, parent, FAILURE); if ( !is_comment_to( comment, parent_s ) ) { err_stat = comment; kom_errno = KOM_NOT_COMMENT; return FAILURE; } if ( !is_supervisor (text_s->author, NULL, ACTPERS, ACT_P) && !ENA(wheel, 8) && !is_comm_sender (text_s, parent) ) { err_stat = comment; kom_errno = KOM_PERM; return FAILURE; } do_sub_comment( comment, text_s, parent, parent_s ); return OK; } /* * Add a footnote-link between two existing texts. Only the author * may do this. The texts must have the same author. */ extern Success add_footnote( Text_no footnote, Text_no footnote_to ) { Text_stat *text_s, *parent_s; CHK_LOGIN(FAILURE); GET_T_STAT(text_s, footnote, FAILURE); GET_T_STAT(parent_s, footnote_to, FAILURE); /* The following code can not cope with a text that is a footnote to itself. That is considered to be a bug. Work around it for now, until a proper misc-info-list handling package is written. FIXME /// +++ */ if (footnote == footnote_to) { err_stat = footnote; kom_errno = KOM_INDEX_OUT_OF_RANGE; return FAILURE; } if ( !text_read_access(footnote, text_s) ) { kom_errno = KOM_NO_SUCH_TEXT; err_stat = footnote; return FAILURE; } if ( !text_read_access(footnote_to, parent_s) ) { kom_errno = KOM_NO_SUCH_TEXT; err_stat = footnote_to; return FAILURE; } err_stat = footnote_to; /* preventative */ if ( check_footn(parent_s) != OK ) return FAILURE; if ( text_s->author != parent_s->author ) { err_stat = footnote_to; kom_errno = KOM_NOT_AUTHOR; return FAILURE; } /* Check if already footnote */ if ( is_footnote_to(footnote, parent_s) ) { err_stat = footnote; kom_errno = KOM_ALREADY_FOOTNOTE; return FAILURE; } if ( do_add_footnote(footnote, footnote_to) != OK) return FAILURE; ADD_MISC(text_s, sent_at, sent_at, time(NULL)); mark_text_as_changed( footnote ); return OK; } /* * Delete a footnote-link between two texts. * Only the author may do this. */ extern Success sub_footnote( Text_no footnote, /* 'footnote' is no longer a */ Text_no parent ) /* footnote to 'parent' */ { Text_stat * text_s; Text_stat * parent_s; CHK_LOGIN(FAILURE); GET_T_STAT(text_s, footnote, FAILURE); GET_T_STAT(parent_s, parent, FAILURE); if ( !is_footnote_to( footnote, parent_s ) ) { err_stat = footnote; kom_errno = KOM_NOT_FOOTNOTE; return FAILURE; } if (!is_supervisor (text_s->author, NULL, ACTPERS, ACT_P) && !ENA(wheel,8)) { err_stat = footnote; kom_errno = KOM_PERM; return FAILURE; } do_sub_footnote( footnote, text_s, parent, parent_s ); return OK; } /* * Get mapping from Local_text_no to (global) Text_no for part of * a conference. * * BUG: You should be allowed to get the mapping if it is a letterbox * which is not unread_is_secret. +++ (Really? I don't think so. /ceder) */ extern Success get_map(Conf_no conf_no, Local_text_no first_local_no, unsigned long no_of_texts, L2g_iterator_as_text_list *result) { Conference * conf_c; Local_text_no highest; Local_text_no first_unwanted; Local_text_no res_first; int res_nr; Access acc; CHK_LOGIN(FAILURE); GET_C_STAT(conf_c, conf_no, FAILURE); acc = access_perm(conf_no, conf_c, ACTPERS, ACT_P); if (acc <= none) { err_stat = conf_no; kom_errno = KOM_UNDEF_CONF; return FAILURE; } if ( acc == read_protected ) { err_stat = conf_no; kom_errno = KOM_ACCESS; return FAILURE; } highest = l2g_first_appendable_key(&conf_c->texts) - 1; if (first_local_no > highest) { err_stat = first_local_no; kom_errno = KOM_NO_SUCH_LOCAL_TEXT; return FAILURE; } /* Get the lowest existing text, or, if no text exists, the next text number that will be used. */ res_first = l2g_next_key(&conf_c->texts, 0); if (res_first == 0) res_first = l2g_first_appendable_key(&conf_c->texts); /* Start where the user wants to start, unless that is too small. */ res_first = max(res_first, first_local_no); /* Find the endpoint. */ first_unwanted = min(highest+1, first_local_no + no_of_texts); if (first_unwanted >= res_first) res_nr = first_unwanted - res_first; else res_nr = 0; l2gi_searchsome(result, &conf_c->texts, res_first, res_first + res_nr); return OK; } extern Success local_to_global(Conf_no conf_no, Local_text_no first_local_no, unsigned long no_of_texts, Text_mapping *result) { Conference * conf_c; Local_text_no res_first; Access acc; if (first_local_no == 0) { err_stat = first_local_no; kom_errno = KOM_LOCAL_TEXT_ZERO; return FAILURE; } if (no_of_texts > 255) { err_stat = 255; kom_errno = KOM_LONG_ARRAY; return FAILURE; } CHK_LOGIN(FAILURE); GET_C_STAT(conf_c, conf_no, FAILURE); acc = access_perm (conf_no, conf_c, ACTPERS, ACT_P); if (acc <= none) { err_stat = conf_no; kom_errno = KOM_UNDEF_CONF; return FAILURE; } if (acc == read_protected) { err_stat = conf_no; kom_errno = KOM_ACCESS; return FAILURE; } if (first_local_no >= l2g_first_appendable_key(&conf_c->texts)) { err_stat = first_local_no; kom_errno = KOM_NO_SUCH_LOCAL_TEXT; return FAILURE; } res_first = l2g_next_key(&conf_c->texts, 0); result->first = max(res_first, first_local_no); result->no_of_texts = no_of_texts; result->l2g = &conf_c->texts; return OK; } extern Success modify_text_info(Text_no text, Number_list *items_to_delete, Aux_item_list *aux) { Text_stat *text_s; CHK_LOGIN(FAILURE); GET_T_STAT(text_s, text, FAILURE); /* Check if we may delete and add the items */ prepare_aux_item_list(aux, ACTPERS); if (check_delete_aux_item_list(items_to_delete,&text_s->aux_item_list)!=OK) return FAILURE; delete_aux_item_list(items_to_delete, &text_s->aux_item_list, TEXT_OBJECT_TYPE, text, text_s); if (text_stat_check_add_aux_item_list(text_s, aux, ACTPERS, FALSE) != OK) { undelete_aux_item_list(items_to_delete, &text_s->aux_item_list, TEXT_OBJECT_TYPE, text, text_s); return FAILURE; } text_stat_add_aux_item_list(text_s, text, aux, ACTPERS); /* FIXME: async_text_changed(text, text_s); */ mark_text_as_changed(text); return OK; }