Example #1
0
/*
 * Name: env_gen_extra
 *       (and via a not-so-tricky #define, env_gen)
 * This routine calls malloc: please free the memory when you are done
 */
static char *env_gen_extra(const char *key, const char *value, int extra)
{
    char *result;
    int key_len, value_len;

    if (value == NULL)          /* ServerAdmin may not be defined, eg */
        value = "";
    key_len = strlen(key);
    value_len = strlen(value);
    /* leave room for '=' sign and null terminator */
    result = malloc(extra + key_len + value_len + 2);
    if (result) {
        memcpy(result + extra, key, key_len);
        *(result + extra + key_len) = '=';
        memcpy(result + extra + key_len + 1, value, value_len);
        *(result + extra + key_len + value_len + 1) = '\0';
    } else {
        log_error_time();
        log_error_mesg(__FILE__, __LINE__, "not enough memory for env_gen_extra! Not fatal.\n");
        log_error_time();
        fprintf(stderr,
                "tried to allocate (key=value) extra=%d: %s=%s\n",
                extra, key, value);
    }
    return result;
}
Example #2
0
/*
 * Name: env_gen_extra
 *       (and via a not-so-tricky #define, env_gen)
 * This routine calls malloc: please free the memory when you are done
 */
static char *env_gen_extra(const char *key, const char *value, int extra)
{
    char *result;
    int key_len, value_len;

    if (value == NULL)          
        value = "";
    key_len = strlen(key);
    value_len = strlen(value);
    
    result = malloc(extra + key_len + value_len + 2);
    if (result) {
        memcpy(result + extra, key, key_len);
        *(result + extra + key_len) = '=';
        memcpy(result + extra + key_len + 1, value, value_len);
        *(result + extra + key_len + value_len + 1) = '\0';
    } else {
        log_error_time();
        perror("malloc");
        log_error_time();
        fprintf(stderr,
                "tried to allocate (key=value) extra=%d: %s=%s\n",
                extra, key, value);
    }
    return result;
}
Example #3
0
static void c_set_group(char *v1, char *v2, void *t)
{
    struct group *groupbuf;
    char *endptr;
    int i;
    DEBUG(DEBUG_CONFIG) {
        log_error_time();
        printf("Group %s = ", v1);
    }
    i = strtol(v1, &endptr, 0);
    if (*v1 != '\0' && *endptr == '\0') {
        server_gid = i;
    } else {
        groupbuf = getgrnam(v1);
        if (!groupbuf) {
            log_error_time();
            fprintf(stderr, "No such group: %s\n", v1);
            if (current_uid)
                return;
            exit(EXIT_FAILURE);
        }
        server_gid = groupbuf->gr_gid;
    }
    DEBUG(DEBUG_CONFIG) {
        printf("%d\n", server_gid);
    }
}
Example #4
0
int add_cgi_env(request * req, char *key, char *value, int http_prefix)
{
	char *p;
	int prefix_len;

	if (http_prefix) {
		prefix_len = 5;
	} else {
		prefix_len = 0;
	}

	if (req->cgi_env_index < CGI_ENV_MAX) {
		p = env_gen_extra(key, value, prefix_len);
		if (!p) {
			log_error_time();
			fprintf(stderr, "Unable to generate additional CGI Environment"
					"variable -- ran out of memory!\n");
		}
		if (prefix_len)
			memcpy(p, "HTTP_", 5);
		req->cgi_env[req->cgi_env_index++] = p;
		return 1;
	} else {
		log_error_time();
		fprintf(stderr, "Unable to generate additional CGI Environment"
				"variable -- not enough space!\n");
	}
	return 0;
}
Example #5
0
static void c_set_user(char *v1, char *v2, void *t)
{
    struct passwd *passwdbuf;
    char *endptr;
    int i;

    DEBUG(DEBUG_CONFIG) {
        log_error_time();
        printf("User %s = ", v1);
    }
    i = strtol(v1, &endptr, 0);
    if (*v1 != '\0' && *endptr == '\0') {
        server_uid = i;
    } else {
        passwdbuf = getpwnam(v1);
        if (!passwdbuf) {
            log_error_time();
            fprintf(stderr, "No such user: %s\n", v1);
            if (current_uid)
                return;
            exit(EXIT_FAILURE);
        }
        server_uid = passwdbuf->pw_uid;
    }
    DEBUG(DEBUG_CONFIG) {
        printf("%d\n", server_uid);
    }
}
Example #6
0
static
hash_struct *hash_find(hash_struct * table[], const char *key,
                          const unsigned int hash)
{
    hash_struct *current;

    current = table[hash];

    if (!key) {
        log_error_time();
        fprintf(stderr, "Yipes! Null value sent as key! [hash_find]!\n");
        return NULL;
    } else if (key[0] == '\0') {
        log_error_time();
        fprintf(stderr,
                "Attempt to locate empty string in hash! [hash_find]!\n");
        return NULL;
    }

    while (current) {
        if (!strcmp(current->key, key)) /* hit */
            return current;
        current = current->next;
    }

    return NULL;
}
Example #7
0
void sighup_run(void)
{
    sighup_flag = 0;
    time(&current_time);
    log_error_time();
    fputs("caught SIGHUP, restarting\n", stderr);

    /* Philosophy change for 0.92: don't close and attempt reopen of logfiles,
     * since usual permission structure prevents such reopening.
     */

    FD_ZERO(&block_read_fdset);
    FD_ZERO(&block_write_fdset);
    /* clear_common_env(); NEVER DO THIS */
    dump_mime();
    dump_passwd();
    dump_alias();
    free_requests();

    log_error_time();
    fputs("re-reading configuration files\n", stderr);
    read_config_files();

    log_error_time();
    fputs("successful restart\n", stderr);

}
Example #8
0
/*
 * Name: env_gen_extra
 *       (and via a not-so-tricky #define, env_gen)
 * This routine calls malloc: please free the memory when you are done
 */
static char *env_gen_extra(const char *key, const char *value,
                           unsigned int extra)
{
    char *result;
    unsigned int key_len, value_len;

    if (value == NULL)          /* ServerAdmin may not be defined, eg */
        value = "";
    key_len = strlen(key);
    value_len = strlen(value);
    /* leave room for '=' sign and null terminator */
    result = malloc(extra + key_len + value_len + 2);
    if (result) {
        memcpy(result + extra, key, key_len);
        *(result + extra + key_len) = '=';
        memcpy(result + extra + key_len + 1, value, value_len);
        *(result + extra + key_len + value_len + 1) = '\0';
    } else {
        log_error_time();
        perror("malloc");
        log_error_time();
        fprintf(stderr, "tried to allocate (key=value) extra=%u: %s=%s\n",
                extra, key, value);
    }
    return result;
}
Example #9
0
void create_common_env()
{
	int index = 0, i;


	/* NOTE NOTE NOTE:
	   If you (the reader) someday modify this chunk of code to
	   handle more "common" CGI environment variables, then bump the
	   value COMMON_CGI_COUNT in defines.h UP

	   Also, in the case of document_root and server_admin, two variables
	   that may or may not be defined depending on the way the server
	   is configured, we check for null values and use an empty
	   string to denote a NULL value to the environment, as per the
	   specification. The quote for which follows:

	   "In all cases, a missing environment variable is
	   equivalent to a zero-length (NULL) value, and vice versa."
	 */
	common_cgi_env[index++] = env_gen_extra("PATH",
			((cgi_path != NULL) ? cgi_path : DEFAULT_PATH), 0);
	common_cgi_env[index++] = env_gen_extra("SERVER_SOFTWARE", SERVER_VERSION, 0);
	common_cgi_env[index++] = env_gen_extra("SERVER_NAME", server_name, 0);
	common_cgi_env[index++] = env_gen_extra("GATEWAY_INTERFACE", CGI_VERSION, 0);

	common_cgi_env[index++] =
		env_gen_extra("SERVER_PORT", simple_itoa(server_port), 0);

	/* NCSA and APACHE added -- not in CGI spec */
	/* common_cgi_env[index++] = env_gen_extra("DOCUMENT_ROOT", document_root); */

	/* NCSA added */
	/* common_cgi_env[index++] = env_gen_extra("SERVER_ROOT", server_root); */

	/* APACHE added */
	common_cgi_env[index++] = env_gen_extra("SERVER_ADMIN", server_admin, 0);
	common_cgi_env[index] = NULL;

	/* Sanity checking -- make *sure* the memory got allocated */
	if (index > COMMON_CGI_COUNT) {
		log_error_time();
		fprintf(stderr, "COMMON_CGI_COUNT not high enough.\n");
		exit(1);
	}

	for(i = 0;i < index;++i) {
		if (common_cgi_env[i] == NULL) {
			log_error_time();
			fprintf(stderr, "Unable to allocate a component of common_cgi_env - out of memory.\n");
			exit(1);
		}
	}
}
Example #10
0
static void set_root()
{
	if (chroot_dir != NULL) {
		if (chdir(chroot_dir) == -1) {
			log_error_time();
			perror("chdir (to chroot)");
			exit(EXIT_FAILURE);
		}
		if (chroot(chroot_dir) == -1) {
			log_error_time();
			perror("chroot");
			exit(EXIT_FAILURE);
		}
		if (chdir("/") == -1) {
			log_error_time();
			perror("chdir (after chroot)");
			exit(EXIT_FAILURE);
		}
		free(chroot_dir);
	}
	
	/* server_root is a very useful variable, we get it from
	* document_root (e.g. given /var/www, server_root is set to /var/)
	* or from getcwd
	*/
	if (document_root != NULL) {
		if (document_root[0] == '/') { /* absolute path */
			unsigned int c = 0, i = 1, len = strlen(document_root);
			if (document_root[len - 1] == '/')
				len--;
			for (;i < len; i++) {
				if (document_root[i] == '/')
					c = i;
			}
			server_root = strdup(document_root);
			server_root[c + 1] = '\0';
		} else { /* relative path */
			char cpt[1025];
			if (getcwd(cpt, sizeof(cpt)) == cpt) {
				unsigned int len = strlen(cpt);
				cpt[len++] = '/';
				cpt[len] = '\0';
				server_root = strdup(cpt);
				/* server_root[len] = '\0'; */
			} else {
				perror("getcwd");
				DIE("could not set server_root\n");
			}
		}
		server_root_len = strlen (server_root);
	}	
}
Example #11
0
void show_hash_stats(void)
{
    int i;
    hash_struct *temp;
    int total = 0;
    int count;

    for (i = 0; i < MIME_HASHTABLE_SIZE; ++i) { 
        if (mime_hashtable[i]) {
            count = 0;
            temp = mime_hashtable[i];
            while (temp) {
                temp = temp->next;
                ++count;
            }
#ifdef NOISY_SIGALRM
            log_error_time();
            fprintf(stderr, "mime_hashtable[%d] has %d entries\n",
                    i, count);
#endif
            total += count;
        }
    }
    log_error_time();
    fprintf(stderr, "mime_hashtable has %d total entries\n",
            total);

    total = 0;
    for (i = 0; i < PASSWD_HASHTABLE_SIZE; ++i) { 
        if (passwd_hashtable[i]) {
            temp = passwd_hashtable[i];
            count = 0;
            while (temp) {
                temp = temp->next;
                ++count;
            }
#ifdef NOISY_SIGALRM
            log_error_time();
            fprintf(stderr, "passwd_hashtable[%d] has %d entries\n",
                    i, count);
#endif
            total += count;
        }
    }

    log_error_time();
    fprintf(stderr, "passwd_hashtable has %d total entries\n",
            total);

}
Example #12
0
void sigchld_run(void)
{
    int status;
    pid_t pid;
#ifdef BOA_DEBUG
    struct timeval tv;
#endif
    sigchld_flag = 0;

    while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
    {
#ifdef BOA_DEBUG
        if (verbose_cgi_logs) {
            time(&current_time);
            log_error_time();
            gettimeofday(&tv,NULL);
            fprintf(stderr, "reaping child %d: status %d;%u,%u\n", (int) pid, status,tv.tv_usec,tv.tv_sec);

        }
#endif

    }



    return;
}
Example #13
0
static void sanitize_request(request * req, int new_req)
{
    static unsigned int bytes_to_zero = offsetof(request, fd);

    if (new_req) {
        req->kacount = ka_max;
        req->time_last = current_time;
        req->client_stream_pos = 0;
    } else {
        unsigned int bytes_to_move =
            req->client_stream_pos - req->parse_pos;

        if (bytes_to_move) {
            memmove(req->client_stream,
                    req->client_stream + req->parse_pos, bytes_to_move);
        }
        req->client_stream_pos = bytes_to_move;
    }

    /* bzero */
    /* we want to clear a middle part of the request:
     */

    DEBUG(DEBUG_REQUEST) {
        log_error_time();
        fprintf(stderr, "req: %p, offset: %d\n", (void *) req,
                bytes_to_zero);
    }

    memset(req, 0, bytes_to_zero);

    req->status = READ_HEADER;
    req->header_line = req->client_stream;
}
Example #14
0
File: signals.c Project: gpg/boa
void sigterm_stage1_run(void)
{                               /* lame duck mode */
    time(&current_time);
    log_error_time();
    fputs("caught SIGTERM, starting shutdown\n", stderr);
    sigterm_flag = 2;
}
Example #15
0
static void drop_privs(void)
{
    
    if (getuid() == 0) {
        struct passwd *passwdbuf;
        passwdbuf = getpwuid(server_uid);
        if (passwdbuf == NULL) {
            DIE("getpwuid");
        }
        if (initgroups(passwdbuf->pw_name, passwdbuf->pw_gid) == -1) {
            DIE("initgroups");
        }
        if (setgid(server_gid) == -1) {
            DIE("setgid");
        }
        if (setuid(server_uid) == -1) {
            DIE("setuid");
        }
        /* test for failed-but-return-was-successful setuid
         * http://www.securityportal.com/list-archive/bugtraq/2000/Jun/0101.html
         */
        if (setuid(0) != -1) {
            DIE("icky Linux kernel bug!");
        }
    } else {
        if (server_gid || server_uid) {
            log_error_time();
            fprintf(stderr, "Warning: "
                    "Not running as root: no attempt to change"
                    " to uid %d gid %d\n", server_uid, server_gid);
        }
        server_gid = getgid();
        server_uid = getuid();
    }
}
Example #16
0
void sigint(int dummy)
{
    time(&current_time);
    log_error_time();
    fputs("caught SIGINT: shutting down\n", stderr);
    fclose(stderr);
    chdir(tempdir);
    exit(1);
}
Example #17
0
void sigsegv(int dummy)
{
    time(&current_time);
    log_error_time();
    fprintf(stderr, "caught SIGSEGV, dumping core in %s\n", tempdir);
    fclose(stderr);
    chdir(tempdir);
    abort();
}
Example #18
0
void sigterm_stage1_run(int server_s) /* lame duck mode */
{
    time(&current_time);
    log_error_time();
    fputs("caught SIGTERM, starting shutdown\n", stderr);
    FD_CLR(server_s, &block_read_fdset);
    close(server_s);
    sigterm_flag = 2;
}
Example #19
0
File: signals.c Project: gpg/boa
void sigint(int dummy)
{
    time(&current_time);
    log_error_time();
    fputs("caught SIGINT: shutting down\n", stderr);
    if (chdir(tempdir) == -1)
        perror ("chdir (tempdir) failed");
    exit(EXIT_FAILURE);
}
Example #20
0
File: signals.c Project: gpg/boa
void sigsegv(int dummy)
{
    time(&current_time);
    log_error_time();
    fprintf(stderr, "caught SIGSEGV, dumping core in %s\n", tempdir);
    if (chdir(tempdir) == -1)
        perror ("chdir (tempdir) failed");
    abort();
}
Example #21
0
void sigalrm_run(void)
{
    time(&current_time);
    log_error_time();
    fprintf(stderr, "%ld requests, %ld errors\n",
            status.requests, status.errors);
    show_hash_stats();
    sigalrm_flag = 0;
}
Example #22
0
int modified_since(time_t * mtime, char *if_modified_since)
{
    struct tm *file_gmt;
    char *ims_info;
    char monthname[10 + 1];
    int day, month, year, hour, minute, second;
    int comp;

    ims_info = if_modified_since;
    while (*ims_info != ' ' && *ims_info != '\0')
        ++ims_info;
    if (*ims_info != ' ')
        return -1;

    
    if (sscanf(ims_info, "%d %3s %d %d:%d:%d GMT", 
               &day, monthname, &year, &hour, &minute, &second) == 6);
    else if (sscanf(ims_info, "%d-%3s-%d %d:%d:%d GMT", 
                    &day, monthname, &year, &hour, &minute, &second) == 6)
        year += 1900;
    else if (sscanf(ims_info, " %3s %d %d:%d:%d %d", 
                    monthname, &day, &hour, &minute, &second, &year) == 6);
    
    /* NOTE: Use if_modified_since here, because the date *starts*
     *       with the day, versus a throwaway item
     */
    else if (sscanf(if_modified_since, "%d %10s %d %d:%d:%d GMT",
                    &day, monthname, &year, &hour, &minute, &second) == 6);
    else {
        log_error_time();
        fprintf(stderr, "Error in %s, line %d: Unable to sscanf \"%s\"\n",
                __FILE__, __LINE__, ims_info);
        return -1;              
    }

    file_gmt = gmtime(mtime);
    month = month2int(monthname);

    /* Go through from years to seconds -- if they are ever unequal,
     we know which one is newer and can return */

    if ((comp = 1900 + file_gmt->tm_year - year))
        return (comp > 0);
    if ((comp = file_gmt->tm_mon - month))
        return (comp > 0);
    if ((comp = file_gmt->tm_mday - day))
        return (comp > 0);
    if ((comp = file_gmt->tm_hour - hour))
        return (comp > 0);
    if ((comp = file_gmt->tm_min - minute))
        return (comp > 0);
    if ((comp = file_gmt->tm_sec - second))
        return (comp > 0);

    return 0;                   
}
Example #23
0
File: util.c Project: wjx0912/boa
int modified_since(time_t * mtime, char *if_modified_since)
{
    struct tm *file_gmt;
    char *ims_info;
    char monthname[10 + 1];
    int day, month, year, hour, minute, second;
    int comp;

    ims_info = if_modified_since;
    while (*ims_info != ' ' && *ims_info != '\0')
        ++ims_info;
    if (*ims_info != ' ')
        return -1;

    /* the pre-space in the third scanf skips whitespace for the string */
    if (sscanf(ims_info, "%d %3s %d %d:%d:%d GMT", /* RFC 1123 */
               &day, monthname, &year, &hour, &minute, &second) == 6);
    else if (sscanf(ims_info, "%d-%3s-%d %d:%d:%d GMT", /* RFC 1036 */
                    &day, monthname, &year, &hour, &minute, &second) == 6)
        year += 1900;
    else if (sscanf(ims_info, " %3s %d %d:%d:%d %d", /* asctime() format */
                    monthname, &day, &hour, &minute, &second, &year) == 6);
    /*  allow this non-standard date format: 31 September 2000 23:59:59 GMT */
    /* NOTE: Use if_modified_since here, because the date *starts*
     *       with the day, versus a throwaway item
     */
    else if (sscanf(if_modified_since, "%d %10s %d %d:%d:%d GMT",
                    &day, monthname, &year, &hour, &minute, &second) == 6);
    else {
        log_error_time();
        fprintf(stderr, "Error in %s, line %d: Unable to sscanf \"%s\"\n",
                __FILE__, __LINE__, ims_info);
        return -1;              /* error */
    }

    file_gmt = gmtime(mtime);
    month = month2int(monthname);

    /* Go through from years to seconds -- if they are ever unequal,
     we know which one is newer and can return */

    if ((comp = 1900 + file_gmt->tm_year - year))
        return (comp > 0);
    if ((comp = file_gmt->tm_mon - month))
        return (comp > 0);
    if ((comp = file_gmt->tm_mday - day))
        return (comp > 0);
    if ((comp = file_gmt->tm_hour - hour))
        return (comp > 0);
    if ((comp = file_gmt->tm_min - minute))
        return (comp > 0);
    if ((comp = file_gmt->tm_sec - second))
        return (comp > 0);

    return 0;                   /* this person must really be into the latest/greatest */
}
Example #24
0
static unsigned boa_hash(const char *str)
{
    if (str == NULL || str[0] == '\0') {
        log_error_time();
        fprintf(stderr,
                "Attempt to hash NULL or empty string! [boa_hash]!\n");
        return 0;
    }
    return _boa_hash(str);
}
Example #25
0
File: util.c Project: wjx0912/boa
int create_temporary_file(short want_unlink, char *storage, int size)
{
    static char boa_tempfile[MAX_PATH_LENGTH + 1];
    int fd;

    snprintf(boa_tempfile, MAX_PATH_LENGTH,
             "%s/boa-temp.XXXXXX", tempdir);

    /* open temp file */
    fd = mkstemp(boa_tempfile);
    if (fd == -1) {
        log_error_time();
        perror("mkstemp");
        return 0;
    }

    if (storage != NULL) {
        int len = strlen(boa_tempfile);

        if (len < size) {
            memcpy(storage, boa_tempfile, len + 1);
        } else {
            close(fd);
            fd = 0;
            log_error_time();
            fprintf(stderr, "not enough memory for memcpy in storage\n");
            want_unlink = 1;
        }
    }

    if (want_unlink) {
        if (unlink(boa_tempfile) == -1) {
            close(fd);
            fd = 0;
            log_error_time();
            fprintf(stderr, "unlink temp file\n");
        }
    }

    return (fd);
}
Example #26
0
static unsigned four_char_hash(const char *buf)
{
    unsigned int hash = (buf[0] +
                         (buf[1] ? buf[1] : 241 +
                          (buf[2] ? buf[2] : 251 +
                           (buf[3] ? buf[3] : 257))));
    DEBUG(DEBUG_HASH) {
        log_error_time();
        fprintf(stderr, "four_char_hash(%s) = %u\n", buf, hash);
    }
    return hash;
}
Example #27
0
static unsigned four_char_hash(char *buf)
{
    unsigned int hash = (buf[0] +
                     (buf[1] ? buf[1] : 241 +
                     (buf[2] ? buf[2] : 251 +
                      (buf[3] ? buf[3] : 257))));
#ifdef DEBUG_HASH
    log_error_time();
    fprintf(stderr, "four_char_hash(%s) = %u\n", buf, hash);
#endif
    return hash;
}
Example #28
0
void sigbus(int dummy)
{
    if (handle_sigbus) {
        longjmp(env, dummy);
    }
    time(&current_time);
    log_error_time();
    fprintf(stderr, "caught SIGBUS, dumping core in %s\n", tempdir);
    fclose(stderr);
    chdir(tempdir);
    abort();
}
Example #29
0
int process_option_line(request * req)
{
    char c, *value, *line = req->header_line;

    /* Start by aggressively hacking the in-place copy of the header line */

#ifdef FASCIST_LOGGING
    log_error_time();
    fprintf(stderr, "%s:%d - Parsing \"%s\"\n", __FILE__, __LINE__, line);
#endif

    value = strchr(line, ':');
    if (value == NULL)
        return 0;
    *value++ = '\0';            /* overwrite the : */
    to_upper(line);             /* header types are case-insensitive */
    while ((c = *value) && (c == ' ' || c == '\t'))
        value++;

    if (!memcmp(line, "IF_MODIFIED_SINCE", 18) && !req->if_modified_since)
        req->if_modified_since = value;

    else if (!memcmp(line, "CONTENT_TYPE", 13) && !req->content_type)
        req->content_type = value;

    else if (!memcmp(line, "CONTENT_LENGTH", 15) && !req->content_length)
        req->content_length = value;

    else if (!memcmp(line, "CONNECTION", 11) &&
            ka_max && req->keepalive != KA_STOPPED) {
        req->keepalive = (!strncasecmp(value, "Keep-Alive", 10) ?
                KA_ACTIVE : KA_STOPPED);
    }
    /* #ifdef ACCEPT_ON */
    else if (!memcmp(line, "ACCEPT", 7))
        add_accept_header(req, value);
    /* #endif */

    /* Need agent and referer for logs */
    else if (!memcmp(line, "REFERER", 8)) {
        req->header_referer = value;
        if (!add_cgi_env(req, "REFERER", value, 1))
            return 0;
    } else if (!memcmp(line, "USER_AGENT", 11)) {
        req->header_user_agent = value;
        if (!add_cgi_env(req, "USER_AGENT", value, 1))
            return 0;
    } else {
        if (!add_cgi_env(req, line, value, 1))
            return 0;
    }
    return 1;
}
Example #30
0
void sigterm_stage2_run() /* lame duck mode */
{
    log_error_time();
    fprintf(stderr,
            "exiting Boa normally (uptime %d seconds)\n",
            (int) (current_time - start_time));
    chdir(tempdir);
    clear_common_env();
    dump_mime();
    dump_passwd();
    dump_alias();
    free_requests();
    exit(0);
}