/* * check_control - check the control record has sensible values for message, user and room numbers */ void check_control(void) { syslog(LOG_INFO, "Checking/re-building control record\n"); get_control(); // Find highest room number and message number. CtdlForEachRoom(control_find_highest, NULL); ForEachUser(control_find_user, NULL); put_control(); }
/* * get_new_room_number() - Obtain a new, unique ID to be used for a room. */ long get_new_room_number(void) { long retval = 0L; begin_critical_section(S_CONTROL); get_control(); retval = ++CitControl.MMnextroom; put_control(); end_critical_section(S_CONTROL); return(retval); }
/* * get_new_message_number() - Obtain a new, unique ID to be used for a message. */ long get_new_message_number(void) { long retval = 0L; begin_critical_section(S_CONTROL); get_control(); retval = ++CitControl.MMhighest; put_control(); end_critical_section(S_CONTROL); return(retval); }
/*********** * Put a packet into the jitterbuffers * Only the timestamps of voicepackets are put in the history * this because the jitterbuffer only works for voicepackets * don't put packets twice in history and queue (e.g. transmitting every frame twice) * keep track of statistics */ void jb_put(jitterbuffer *jb, void *data, int type, long ms, long ts, long now, int codec) { long pointer, max_index; if (jb == NULL) { jb_err("no jitterbuffer in jb_put()\n"); return; } jb->info.frames_received++; if (type == JB_TYPE_CONTROL) { //put the packet into the contol-queue of the jitterbuffer jb_dbg("pC"); put_control(jb,data,type,ts); } else if (type == JB_TYPE_VOICE) { // only add voice that aren't already in the buffer max_index = (jb->hist_pointer < JB_HISTORY_SIZE) ? jb->hist_pointer : JB_HISTORY_SIZE-1; pointer = find_pointer(&jb->hist_sorted_timestamp[0], max_index, ts); if (jb->hist_sorted_timestamp[pointer]==ts) { //timestamp already in queue jb_dbg("pT"); free(data); jb->info.frames_dropped_twice++; } else { //add jb_dbg("pV"); /* add voicepacket to history */ put_history(jb,ts,now,ms,codec); /*calculate jitterbuffer size*/ calculate_info(jb, ts, now, codec); /*put the packet into the queue of the jitterbuffer*/ put_voice(jb,data,type,ms,ts,codec); } } else if (type == JB_TYPE_SILENCE){ //silence jb_dbg("pS"); put_voice(jb,data,type,ms,ts,codec); } else {//should NEVER happen jb_err("jb_put(): type not known\n"); free(data); } }
/* * Get or set global configuration options * * IF YOU ADD OR CHANGE FIELDS HERE, YOU *MUST* DOCUMENT YOUR CHANGES AT: * http://www.citadel.org/doku.php?id=documentation:applicationprotocol * */ void cmd_conf(char *argbuf) { char cmd[16]; char buf[256]; int a; char *confptr; char confname[128]; if (CtdlAccessCheck(ac_aide)) return; extract_token(cmd, argbuf, 0, '|', sizeof cmd); if (!strcasecmp(cmd, "GET")) { cprintf("%d Configuration...\n", LISTING_FOLLOWS); cprintf("%s\n", config.c_nodename); cprintf("%s\n", config.c_fqdn); cprintf("%s\n", config.c_humannode); cprintf("%s\n", config.c_phonenum); cprintf("%d\n", config.c_creataide); cprintf("%d\n", config.c_sleeping); cprintf("%d\n", config.c_initax); cprintf("%d\n", config.c_regiscall); cprintf("%d\n", config.c_twitdetect); cprintf("%s\n", config.c_twitroom); cprintf("%s\n", config.c_moreprompt); cprintf("%d\n", config.c_restrict); cprintf("%s\n", config.c_site_location); cprintf("%s\n", config.c_sysadm); cprintf("%d\n", config.c_maxsessions); cprintf("xxx\n"); /* placeholder -- field no longer in use */ cprintf("%d\n", config.c_userpurge); cprintf("%d\n", config.c_roompurge); cprintf("%s\n", config.c_logpages); cprintf("%d\n", config.c_createax); cprintf("%ld\n", config.c_maxmsglen); cprintf("%d\n", config.c_min_workers); cprintf("%d\n", config.c_max_workers); cprintf("%d\n", config.c_pop3_port); cprintf("%d\n", config.c_smtp_port); cprintf("%d\n", config.c_rfc822_strict_from); cprintf("%d\n", config.c_aide_zap); cprintf("%d\n", config.c_imap_port); cprintf("%ld\n", config.c_net_freq); cprintf("%d\n", config.c_disable_newu); cprintf("1\n"); /* niu */ cprintf("%d\n", config.c_purge_hour); #ifdef HAVE_LDAP cprintf("%s\n", config.c_ldap_host); cprintf("%d\n", config.c_ldap_port); cprintf("%s\n", config.c_ldap_base_dn); cprintf("%s\n", config.c_ldap_bind_dn); cprintf("%s\n", config.c_ldap_bind_pw); #else cprintf("\n"); cprintf("0\n"); cprintf("\n"); cprintf("\n"); cprintf("\n"); #endif cprintf("%s\n", config.c_ip_addr); cprintf("%d\n", config.c_msa_port); cprintf("%d\n", config.c_imaps_port); cprintf("%d\n", config.c_pop3s_port); cprintf("%d\n", config.c_smtps_port); cprintf("%d\n", config.c_enable_fulltext); cprintf("%d\n", config.c_auto_cull); cprintf("1\n"); cprintf("%d\n", config.c_allow_spoofing); cprintf("%d\n", config.c_journal_email); cprintf("%d\n", config.c_journal_pubmsgs); cprintf("%s\n", config.c_journal_dest); cprintf("%s\n", config.c_default_cal_zone); cprintf("%d\n", config.c_pftcpdict_port); cprintf("%d\n", config.c_managesieve_port); cprintf("%d\n", config.c_auth_mode); cprintf("%s\n", config.c_funambol_host); cprintf("%d\n", config.c_funambol_port); cprintf("%s\n", config.c_funambol_source); cprintf("%s\n", config.c_funambol_auth); cprintf("%d\n", config.c_rbl_at_greeting); cprintf("%s\n", config.c_master_user); cprintf("%s\n", config.c_master_pass); cprintf("%s\n", config.c_pager_program); cprintf("%d\n", config.c_imap_keep_from); cprintf("%d\n", config.c_xmpp_c2s_port); cprintf("%d\n", config.c_xmpp_s2s_port); cprintf("%ld\n", config.c_pop3_fetch); cprintf("%ld\n", config.c_pop3_fastest); cprintf("%d\n", config.c_spam_flag_only); cprintf("%d\n", config.c_guest_logins); cprintf("%d\n", config.c_port_number); cprintf("%d\n", config.c_ctdluid); cprintf("%d\n", config.c_nntp_port); cprintf("%d\n", config.c_nntps_port); cprintf("000\n"); } else if (!strcasecmp(cmd, "SET")) { unbuffer_output(); cprintf("%d Send configuration...\n", SEND_LISTING); a = 0; while (client_getln(buf, sizeof buf) >= 0 && strcmp(buf, "000")) { switch (a) { case 0: configlen.c_nodename = safestrncpy(config.c_nodename, buf, sizeof config.c_nodename); break; case 1: configlen.c_fqdn = safestrncpy(config.c_fqdn, buf, sizeof config.c_fqdn); break; case 2: configlen.c_humannode = safestrncpy(config.c_humannode, buf, sizeof config.c_humannode); break; case 3: configlen.c_phonenum = safestrncpy(config.c_phonenum, buf, sizeof config.c_phonenum); break; case 4: config.c_creataide = atoi(buf); break; case 5: config.c_sleeping = atoi(buf); break; case 6: config.c_initax = atoi(buf); if (config.c_initax < 1) config.c_initax = 1; if (config.c_initax > 6) config.c_initax = 6; break; case 7: config.c_regiscall = atoi(buf); if (config.c_regiscall != 0) config.c_regiscall = 1; break; case 8: config.c_twitdetect = atoi(buf); if (config.c_twitdetect != 0) config.c_twitdetect = 1; break; case 9: configlen.c_twitroom = safestrncpy(config.c_twitroom, buf, sizeof config.c_twitroom); break; case 10: configlen.c_moreprompt = safestrncpy(config.c_moreprompt, buf, sizeof config.c_moreprompt); break; case 11: config.c_restrict = atoi(buf); if (config.c_restrict != 0) config.c_restrict = 1; break; case 12: configlen.c_site_location = safestrncpy( config.c_site_location, buf, sizeof config.c_site_location); break; case 13: configlen.c_sysadm = safestrncpy(config.c_sysadm, buf, sizeof config.c_sysadm); break; case 14: config.c_maxsessions = atoi(buf); if (config.c_maxsessions < 0) config.c_maxsessions = 0; break; case 15: /* placeholder -- field no longer in use */ break; case 16: config.c_userpurge = atoi(buf); break; case 17: config.c_roompurge = atoi(buf); break; case 18: configlen.c_logpages = safestrncpy(config.c_logpages, buf, sizeof config.c_logpages); break; case 19: config.c_createax = atoi(buf); if (config.c_createax < 1) config.c_createax = 1; if (config.c_createax > 6) config.c_createax = 6; break; case 20: if (atoi(buf) >= 8192) config.c_maxmsglen = atoi(buf); break; case 21: if (atoi(buf) >= 2) config.c_min_workers = atoi(buf); case 22: if (atoi(buf) >= config.c_min_workers) config.c_max_workers = atoi(buf); case 23: config.c_pop3_port = atoi(buf); break; case 24: config.c_smtp_port = atoi(buf); break; case 25: config.c_rfc822_strict_from = atoi(buf); break; case 26: config.c_aide_zap = atoi(buf); if (config.c_aide_zap != 0) config.c_aide_zap = 1; break; case 27: config.c_imap_port = atoi(buf); break; case 28: config.c_net_freq = atol(buf); break; case 29: config.c_disable_newu = atoi(buf); if (config.c_disable_newu != 0) config.c_disable_newu = 1; break; case 30: /* niu */ break; case 31: if ((config.c_purge_hour >= 0) && (config.c_purge_hour <= 23)) { config.c_purge_hour = atoi(buf); } break; #ifdef HAVE_LDAP case 32: configlen.c_ldap_host = safestrncpy(config.c_ldap_host, buf, sizeof config.c_ldap_host); break; case 33: config.c_ldap_port = atoi(buf); break; case 34: configlen.c_ldap_base_dn = safestrncpy(config.c_ldap_base_dn, buf, sizeof config.c_ldap_base_dn); break; case 35: configlen.c_ldap_bind_dn = safestrncpy(config.c_ldap_bind_dn, buf, sizeof config.c_ldap_bind_dn); break; case 36: configlen.c_ldap_bind_pw = safestrncpy(config.c_ldap_bind_pw, buf, sizeof config.c_ldap_bind_pw); break; #endif case 37: configlen.c_ip_addr = safestrncpy(config.c_ip_addr, buf, sizeof config.c_ip_addr); case 38: config.c_msa_port = atoi(buf); break; case 39: config.c_imaps_port = atoi(buf); break; case 40: config.c_pop3s_port = atoi(buf); break; case 41: config.c_smtps_port = atoi(buf); break; case 42: config.c_enable_fulltext = atoi(buf); break; case 43: config.c_auto_cull = atoi(buf); break; case 44: /* niu */ break; case 45: config.c_allow_spoofing = atoi(buf); break; case 46: config.c_journal_email = atoi(buf); break; case 47: config.c_journal_pubmsgs = atoi(buf); break; case 48: configlen.c_journal_dest = safestrncpy(config.c_journal_dest, buf, sizeof config.c_journal_dest); case 49: configlen.c_default_cal_zone = safestrncpy( config.c_default_cal_zone, buf, sizeof config.c_default_cal_zone); break; case 50: config.c_pftcpdict_port = atoi(buf); break; case 51: config.c_managesieve_port = atoi(buf); break; case 52: config.c_auth_mode = atoi(buf); case 53: configlen.c_funambol_host = safestrncpy( config.c_funambol_host, buf, sizeof config.c_funambol_host); break; case 54: config.c_funambol_port = atoi(buf); break; case 55: configlen.c_funambol_source = safestrncpy( config.c_funambol_source, buf, sizeof config.c_funambol_source); break; case 56: configlen.c_funambol_auth = safestrncpy( config.c_funambol_auth, buf, sizeof config.c_funambol_auth); break; case 57: config.c_rbl_at_greeting = atoi(buf); break; case 58: configlen.c_master_user = safestrncpy( config.c_master_user, buf, sizeof config.c_master_user); break; case 59: configlen.c_master_pass = safestrncpy( config.c_master_pass, buf, sizeof config.c_master_pass); break; case 60: configlen.c_pager_program = safestrncpy( config.c_pager_program, buf, sizeof config.c_pager_program); break; case 61: config.c_imap_keep_from = atoi(buf); break; case 62: config.c_xmpp_c2s_port = atoi(buf); break; case 63: config.c_xmpp_s2s_port = atoi(buf); break; case 64: config.c_pop3_fetch = atol(buf); break; case 65: config.c_pop3_fastest = atol(buf); break; case 66: config.c_spam_flag_only = atoi(buf); break; case 67: config.c_guest_logins = atoi(buf); break; case 68: config.c_port_number = atoi(buf); break; case 69: config.c_ctdluid = atoi(buf); break; case 70: config.c_nntp_port = atoi(buf); break; case 71: config.c_nntps_port = atoi(buf); break; } ++a; } put_config(); snprintf(buf, sizeof buf, "The global system configuration has been edited by %s.\n", (CC->logged_in ? CC->curr_user : "******") ); CtdlAideMessage(buf,"Citadel Configuration Manager Message"); if (!IsEmptyStr(config.c_logpages)) CtdlCreateRoom(config.c_logpages, 3, "", 0, 1, 1, VIEW_BBS); /* If full text indexing has been disabled, invalidate the * index so it doesn't try to use it later. */ if (config.c_enable_fulltext == 0) { CitControl.fulltext_wordbreaker = 0; put_control(); } } else if (!strcasecmp(cmd, "GETSYS")) { extract_token(confname, argbuf, 1, '|', sizeof confname); confptr = CtdlGetSysConfig(confname); if (confptr != NULL) { long len; len = strlen(confptr); cprintf("%d %s\n", LISTING_FOLLOWS, confname); client_write(confptr, len); if ((len > 0) && (confptr[len - 1] != 10)) client_write("\n", 1); cprintf("000\n"); free(confptr); } else { cprintf("%d No such configuration.\n", ERROR + ILLEGAL_VALUE); } } else if (!strcasecmp(cmd, "PUTSYS")) { extract_token(confname, argbuf, 1, '|', sizeof confname); unbuffer_output(); cprintf("%d %s\n", SEND_LISTING, confname); confptr = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0); CtdlPutSysConfig(confname, confptr); free(confptr); } else { cprintf("%d Illegal option(s) specified.\n", ERROR + ILLEGAL_VALUE); } }
/* * Begin the fulltext indexing process. */ void do_fulltext_indexing(void) { int i; static time_t last_index = 0L; static time_t last_progress = 0L; time_t run_time = 0L; time_t end_time = 0L; static int is_running = 0; if (is_running) return; /* Concurrency check - only one can run */ is_running = 1; /* * Don't do this if the site doesn't have it enabled. */ if (!config.c_enable_fulltext) { return; } /* * Make sure we don't run the indexer too frequently. * FIXME move the setting into config */ if ( (time(NULL) - last_index) < 300L) { return; } /* * Check to see whether the fulltext index is up to date; if there * are no messages to index, don't waste any more time trying. */ if ((CitControl.MMfulltext >= CitControl.MMhighest) && (CitControl.fulltext_wordbreaker == FT_WORDBREAKER_ID)) { return; /* nothing to do! */ } run_time = time(NULL); syslog(LOG_DEBUG, "do_fulltext_indexing() started (%ld)", run_time); /* * If we've switched wordbreaker modules, burn the index and start * over. */ begin_critical_section(S_CONTROL); if (CitControl.fulltext_wordbreaker != FT_WORDBREAKER_ID) { syslog(LOG_DEBUG, "wb ver on disk = %d, code ver = %d", CitControl.fulltext_wordbreaker, FT_WORDBREAKER_ID ); syslog(LOG_INFO, "(re)initializing full text index"); cdb_trunc(CDB_FULLTEXT); CitControl.MMfulltext = 0L; put_control(); } end_critical_section(S_CONTROL); /* * Now go through each room and find messages to index. */ ft_newhighest = CitControl.MMhighest; CtdlForEachRoom(ft_index_room, NULL); /* load all msg pointers */ if (ft_num_msgs > 0) { qsort(ft_newmsgs, ft_num_msgs, sizeof(long), longcmp); for (i=0; i<(ft_num_msgs-1); ++i) { /* purge dups */ if (ft_newmsgs[i] == ft_newmsgs[i+1]) { memmove(&ft_newmsgs[i], &ft_newmsgs[i+1], ((ft_num_msgs - i - 1)*sizeof(long))); --ft_num_msgs; --i; } } /* Here it is ... do each message! */ for (i=0; i<ft_num_msgs; ++i) { if (time(NULL) != last_progress) { syslog(LOG_DEBUG, "Indexed %d of %d messages (%d%%)", i, ft_num_msgs, ((i*100) / ft_num_msgs) ); last_progress = time(NULL); } ft_index_message(ft_newmsgs[i], 1); /* Check to see if we need to quit early */ if (server_shutting_down) { syslog(LOG_DEBUG, "Indexer quitting early"); ft_newhighest = ft_newmsgs[i]; break; } /* Check to see if we have to maybe flush to disk */ if (i >= FT_MAX_CACHE) { syslog(LOG_DEBUG, "Time to flush."); ft_newhighest = ft_newmsgs[i]; break; } } free(ft_newmsgs); ft_num_msgs = 0; ft_num_alloc = 0; ft_newmsgs = NULL; } end_time = time(NULL); if (server_shutting_down) { is_running = 0; return; } syslog(LOG_DEBUG, "do_fulltext_indexing() duration (%ld)", end_time - run_time); /* Save our place so we don't have to do this again */ ft_flush_cache(); begin_critical_section(S_CONTROL); CitControl.MMfulltext = ft_newhighest; CitControl.fulltext_wordbreaker = FT_WORDBREAKER_ID; put_control(); end_critical_section(S_CONTROL); last_index = time(NULL); syslog(LOG_DEBUG, "do_fulltext_indexing() finished"); is_running = 0; return; }
/* * Open the various databases we'll be using. Any database which * does not exist should be created. Note that we don't need a * critical section here, because there aren't any active threads * manipulating the database yet. */ void open_databases(void) { int ret; int i; char dbfilename[32]; u_int32_t flags = 0; int dbversion_major, dbversion_minor, dbversion_patch; int current_dbversion = 0; syslog(LOG_DEBUG, "bdb(): open_databases() starting"); syslog(LOG_DEBUG, "Compiled db: %s", DB_VERSION_STRING); syslog(LOG_INFO, " Linked db: %s", db_version(&dbversion_major, &dbversion_minor, &dbversion_patch)); current_dbversion = (dbversion_major * 1000000) + (dbversion_minor * 1000) + dbversion_patch; syslog(LOG_DEBUG, "Calculated dbversion: %d", current_dbversion); syslog(LOG_DEBUG, " Previous dbversion: %d", CitControl.MMdbversion); if ( (getenv("SUPPRESS_DBVERSION_CHECK") == NULL) && (CitControl.MMdbversion > current_dbversion) ) { syslog(LOG_EMERG, "You are attempting to run the Citadel server using a version"); syslog(LOG_EMERG, "of Berkeley DB that is older than that which last created or"); syslog(LOG_EMERG, "updated the database. Because this would probably cause data"); syslog(LOG_EMERG, "corruption or loss, the server is aborting execution now."); exit(CTDLEXIT_DB); } CitControl.MMdbversion = current_dbversion; put_control(); syslog(LOG_INFO, "Linked zlib: %s\n", zlibVersion()); /* * Silently try to create the database subdirectory. If it's * already there, no problem. */ if ((mkdir(ctdl_data_dir, 0700) != 0) && (errno != EEXIST)){ syslog(LOG_EMERG, "unable to create database directory [%s]: %s", ctdl_data_dir, strerror(errno)); } if (chmod(ctdl_data_dir, 0700) != 0){ syslog(LOG_EMERG, "unable to set database directory accessrights [%s]: %s", ctdl_data_dir, strerror(errno)); } if (chown(ctdl_data_dir, CTDLUID, (-1)) != 0){ syslog(LOG_EMERG, "unable to set the owner for [%s]: %s", ctdl_data_dir, strerror(errno)); } syslog(LOG_DEBUG, "bdb(): Setting up DB environment\n"); /* db_env_set_func_yield((int (*)(u_long, u_long))sched_yield); */ ret = db_env_create(&dbenv, 0); if (ret) { syslog(LOG_EMERG, "bdb(): db_env_create: %s\n", db_strerror(ret)); syslog(LOG_EMERG, "exit code %d\n", ret); exit(CTDLEXIT_DB); } dbenv->set_errpfx(dbenv, "citserver"); dbenv->set_paniccall(dbenv, dbpanic); dbenv->set_errcall(dbenv, cdb_verbose_err); dbenv->set_errpfx(dbenv, "ctdl"); #if (DB_VERSION_MAJOR == 4) && (DB_VERSION_MINOR >= 3) dbenv->set_msgcall(dbenv, cdb_verbose_log); #endif dbenv->set_verbose(dbenv, DB_VERB_DEADLOCK, 1); dbenv->set_verbose(dbenv, DB_VERB_RECOVERY, 1); /* * We want to specify the shared memory buffer pool cachesize, * but everything else is the default. */ ret = dbenv->set_cachesize(dbenv, 0, 64 * 1024, 0); if (ret) { syslog(LOG_EMERG, "bdb(): set_cachesize: %s\n", db_strerror(ret)); dbenv->close(dbenv, 0); syslog(LOG_EMERG, "exit code %d\n", ret); exit(CTDLEXIT_DB); } if ((ret = dbenv->set_lk_detect(dbenv, DB_LOCK_DEFAULT))) { syslog(LOG_EMERG, "bdb(): set_lk_detect: %s\n", db_strerror(ret)); dbenv->close(dbenv, 0); syslog(LOG_EMERG, "exit code %d\n", ret); exit(CTDLEXIT_DB); } flags = DB_CREATE | DB_INIT_MPOOL | DB_PRIVATE | DB_INIT_TXN | DB_INIT_LOCK | DB_THREAD | DB_RECOVER; syslog(LOG_DEBUG, "dbenv->open(dbenv, %s, %d, 0)\n", ctdl_data_dir, flags); ret = dbenv->open(dbenv, ctdl_data_dir, flags, 0); if (ret == DB_RUNRECOVERY) { syslog(LOG_ALERT, "dbenv->open: %s\n", db_strerror(ret)); syslog(LOG_ALERT, "Attempting recovery...\n"); flags |= DB_RECOVER; ret = dbenv->open(dbenv, ctdl_data_dir, flags, 0); } if (ret == DB_RUNRECOVERY) { syslog(LOG_ALERT, "dbenv->open: %s\n", db_strerror(ret)); syslog(LOG_ALERT, "Attempting catastrophic recovery...\n"); flags &= ~DB_RECOVER; flags |= DB_RECOVER_FATAL; ret = dbenv->open(dbenv, ctdl_data_dir, flags, 0); } if (ret) { syslog(LOG_EMERG, "dbenv->open: %s\n", db_strerror(ret)); dbenv->close(dbenv, 0); syslog(LOG_EMERG, "exit code %d\n", ret); exit(CTDLEXIT_DB); } syslog(LOG_INFO, "Starting up DB\n"); for (i = 0; i < MAXCDB; ++i) { /* Create a database handle */ ret = db_create(&dbp[i], dbenv, 0); if (ret) { syslog(LOG_EMERG, "db_create: %s\n", db_strerror(ret)); syslog(LOG_EMERG, "exit code %d\n", ret); exit(CTDLEXIT_DB); } /* Arbitrary names for our tables -- we reference them by * number, so we don't have string names for them. */ snprintf(dbfilename, sizeof dbfilename, "cdb.%02x", i); ret = dbp[i]->open(dbp[i], NULL, dbfilename, NULL, DB_BTREE, DB_CREATE | DB_AUTO_COMMIT | DB_THREAD, 0600 ); if (ret) { syslog(LOG_EMERG, "db_open[%02x]: %s\n", i, db_strerror(ret)); if (ret == ENOMEM) { syslog(LOG_EMERG, "You may need to tune your database; please read http://www.citadel.org/doku.php?id=faq:troubleshooting:out_of_lock_entries for more information."); } syslog(LOG_EMERG, "exit code %d\n", ret); exit(CTDLEXIT_DB); } } }