|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620 |
- diff --git a/src/OS/os.h-Linux b/src/OS/os.h-Linux
- index 3fead17..03fea75 100644
- --- a/src/OS/os.h-Linux
- +++ b/src/OS/os.h-Linux
- @@ -20,7 +20,7 @@ performance on outgoing mail a bit. Note: With older glibc versions
- this setting will conflict with the _FILE_OFFSET_BITS=64 setting
- defined as part of the Linux CFLAGS. */
-
- -/* #define HAVE_LINUX_SENDFILE */
- +#define HAVE_LINUX_SENDFILE
-
- #define F_FREESP O_TRUNC
- typedef struct flock flock_t;
- diff --git a/src/src/expand.c b/src/src/expand.c
- index 1da2225..7da567a 100644
- --- a/src/src/expand.c
- +++ b/src/src/expand.c
- @@ -618,6 +618,7 @@ static var_entry var_table[] = {
- { "sn8", vtype_filter_int, &filter_sn[8] },
- { "sn9", vtype_filter_int, &filter_sn[9] },
- #ifdef WITH_CONTENT_SCAN
- + { "spam_action", vtype_stringptr, &spam_action },
- { "spam_bar", vtype_stringptr, &spam_bar },
- { "spam_report", vtype_stringptr, &spam_report },
- { "spam_score", vtype_stringptr, &spam_score },
- diff --git a/src/src/globals.c b/src/src/globals.c
- index 74b6edb..e6f02fb 100644
- --- a/src/src/globals.c
- +++ b/src/src/globals.c
- @@ -1212,6 +1212,7 @@ BOOL smtp_use_size = FALSE;
- uschar *spamd_address = US"127.0.0.1 783";
- uschar *spam_bar = NULL;
- uschar *spam_report = NULL;
- +uschar *spam_action = NULL;
- uschar *spam_score = NULL;
- uschar *spam_score_int = NULL;
- #endif
- diff --git a/src/src/globals.h b/src/src/globals.h
- index db436c0..fc4d9f6 100644
- --- a/src/src/globals.h
- +++ b/src/src/globals.h
- @@ -769,6 +769,7 @@ extern BOOL smtp_use_size; /* Global for passed connections */
- extern uschar *spamd_address; /* address for the spamassassin daemon */
- extern uschar *spam_bar; /* the spam "bar" (textual representation of spam_score) */
- extern uschar *spam_report; /* the spamd report (multiline) */
- +extern uschar *spam_action; /* the spamd action */
- extern uschar *spam_score; /* the spam score (float) */
- extern uschar *spam_score_int; /* spam_score * 10 (int) */
- #endif
- diff --git a/src/src/spam.c b/src/src/spam.c
- index 63395f2..41fd4ac 100644
- --- a/src/src/spam.c
- +++ b/src/src/spam.c
- @@ -14,12 +14,20 @@
- uschar spam_score_buffer[16];
- uschar spam_score_int_buffer[16];
- uschar spam_bar_buffer[128];
- +uschar spam_action_buffer[32];
- uschar spam_report_buffer[32600];
- uschar prev_user_name[128] = "";
- int spam_ok = 0;
- int spam_rc = 0;
- uschar *prev_spamd_address_work = NULL;
-
- +/* push formatted line into vector */
- +static int spam_push_line(struct iovec *iov, int i, const char *fmt, ...);
- +/* write io vector to the socket */
- +static int spam_write_vector(int sock, size_t size, struct iovec *iov, time_t now);
- +/* poll socket to obtain write readiness */
- +static int spam_poll_socket (int sock, time_t start);
- +
- int spam(uschar **listptr) {
- int sep = 0;
- uschar *list = *listptr;
- @@ -29,10 +37,11 @@ int spam(uschar **listptr) {
- FILE *mbox_file;
- int spamd_sock = -1;
- uschar spamd_buffer[32600];
- - int i, j, offset, result;
- + int i, j, offset, result, is_rspamd;
- uschar spamd_version[8];
- + uschar spamd_short_result[8];
- uschar spamd_score_char;
- - double spamd_threshold, spamd_score;
- + double spamd_threshold, spamd_score, spamd_reject_score;
- int spamd_report_offset;
- uschar *p,*q;
- int override = 0;
- @@ -126,8 +135,15 @@ int spam(uschar **listptr) {
- spamd_address_container *this_spamd =
- (spamd_address_container *)store_get(sizeof(spamd_address_container));
-
- + /* Check for spamd variant */
- + if( Ustrstr(address, "variant=rspamd") != NULL ) {
- + this_spamd->is_rspamd = 1;
- + }
- + else {
- + this_spamd->is_rspamd = 0;
- + }
- /* grok spamd address and port */
- - if( sscanf(CS address, "%s %u", this_spamd->tcp_addr, &(this_spamd->tcp_port)) != 2 ) {
- + if( sscanf(CS address, "%s %hu", this_spamd->tcp_addr, &(this_spamd->tcp_port)) != 2 ) {
- log_write(0, LOG_MAIN,
- "spam acl condition: warning - invalid spamd address: '%s'", address);
- continue;
- @@ -171,6 +187,7 @@ int spam(uschar **listptr) {
- spamd_address_vector[current_server]->tcp_port,
- 5 ) > -1) {
- /* connection OK */
- + is_rspamd = spamd_address_vector[current_server]->is_rspamd;
- break;
- };
-
- @@ -207,12 +224,28 @@ int spam(uschar **listptr) {
- }
-
- server.sun_family = AF_UNIX;
- - Ustrcpy(server.sun_path, spamd_address_work);
- + p = Ustrstr(spamd_address_work, "variant=rspamd");
- + if( p != NULL ) {
- + is_rspamd = TRUE;
- + /* strip spaces */
- + p --;
- + while (p > spamd_address_work && isspace (*p)) {
- + p --;
- + }
- + Ustrncpy(server.sun_path, spamd_address_work, p - spamd_address_work + 1);
- + /* zero terminate */
- + server.sun_path[p - spamd_address_work + 1] = 0;
- + }
- + else {
- + is_rspamd = FALSE;
- + Ustrcpy(server.sun_path, spamd_address_work);
- + }
- +
-
- if (connect(spamd_sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) {
- log_write(0, LOG_MAIN|LOG_PANIC,
- "malware acl condition: spamd: unable to connect to UNIX socket %s (%s)",
- - spamd_address_work, strerror(errno) );
- + server.sun_path, strerror(errno) );
- (void)fclose(mbox_file);
- (void)close(spamd_sock);
- return DEFER;
- @@ -228,22 +261,67 @@ int spam(uschar **listptr) {
- return DEFER;
- }
-
- + (void)fcntl(spamd_sock, F_SETFL, O_NONBLOCK);
- /* now we are connected to spamd on spamd_sock */
- - (void)string_format(spamd_buffer,
- - sizeof(spamd_buffer),
- - "REPORT SPAMC/1.2\r\nUser: %s\r\nContent-length: %ld\r\n\r\n",
- - user_name,
- - mbox_size);
- -
- - /* send our request */
- - if (send(spamd_sock, spamd_buffer, Ustrlen(spamd_buffer), 0) < 0) {
- - (void)close(spamd_sock);
- - log_write(0, LOG_MAIN|LOG_PANIC,
- - "spam acl condition: spamd send failed: %s", strerror(errno));
- - (void)fclose(mbox_file);
- - (void)close(spamd_sock);
- - return DEFER;
- - };
- + if (is_rspamd) {
- + /* rspamd variant */
- + int r, request_p = 0;
- + const char *helo;
- + struct iovec *request_v;
- +
- + request_v = store_get(sizeof(struct iovec) * (8 + recipients_count));
- + if (request_v == NULL) {
- + (void)close(spamd_sock);
- + log_write(0, LOG_MAIN|LOG_PANIC,
- + "spam acl condition: store_get failed: %s", strerror(errno));
- + (void)fclose(mbox_file);
- + (void)close(spamd_sock);
- + return DEFER;
- + }
- + r = 0;
- + r += spam_push_line(request_v, request_p++, "CHECK RSPAMC/1.3\r\n");
- + r += spam_push_line(request_v, request_p++, "Content-length: %lu\r\n", mbox_size);
- + r += spam_push_line(request_v, request_p++, "Queue-Id: %s\r\n", message_id);
- + r += spam_push_line(request_v, request_p++, "From: <%s>\r\n", sender_address);
- + r += spam_push_line(request_v, request_p++, "Recipient-Number: %d\r\n", recipients_count);
- + /* copy all recipients as well */
- + for (i = 0; i < recipients_count; i ++)
- + r += spam_push_line(request_v, request_p++, "Rcpt: <%s>\r\n", recipients_list[i].address);
- + if ((helo = expand_string(US"$sender_helo_name")) != NULL && *helo != '\0')
- + r += spam_push_line(request_v, request_p++, "Helo: %s\r\n", helo);
- + if (sender_host_address != NULL)
- + r += spam_push_line(request_v, request_p++, "IP: %s\r\n", sender_host_address);
- + r += spam_push_line(request_v, request_p++, "\r\n");
- + if (spam_write_vector (spamd_sock, request_p, request_v, start) < 0) {
- + (void)close(spamd_sock);
- + log_write(0, LOG_MAIN|LOG_PANIC,
- + "spam acl condition: spamd (rspamd) send failed: %s", strerror(errno));
- + (void)fclose(mbox_file);
- + (void)close(spamd_sock);
- + return DEFER;
- + }
- + }
- + else {
- + /* spamassassin variant */
- + struct iovec req_iov;
- + (void)string_format(spamd_buffer,
- + sizeof(spamd_buffer),
- + "REPORT SPAMC/1.2\r\nUser: %s\r\nContent-length: %ld\r\n\r\n",
- + user_name,
- + mbox_size);
- + /* send our request */
- + req_iov.iov_len = Ustrlen(spamd_buffer);
- + req_iov.iov_base = spamd_buffer;
- + if (spam_write_vector (spamd_sock, 1, &req_iov, start) < 0) {
- + (void)close(spamd_sock);
- + log_write(0, LOG_MAIN|LOG_PANIC,
- + "spam acl condition: spamd send failed: %s", strerror(errno));
- + (void)fclose(mbox_file);
- + (void)close(spamd_sock);
- + return DEFER;
- + };
- + }
- +
-
- /* now send the file */
- /* spamd sometimes accepts conections but doesn't read data off
- @@ -255,41 +333,53 @@ int spam(uschar **listptr) {
- * Note: poll() is not supported in OSX 10.2 and is reported to be
- * broken in more recent versions (up to 10.4).
- */
- -#ifndef NO_POLL_H
- - pollfd.fd = spamd_sock;
- - pollfd.events = POLLOUT;
- -#endif
- - (void)fcntl(spamd_sock, F_SETFL, O_NONBLOCK);
- +#ifdef HAVE_LINUX_SENDFILE
- + ssize_t copied = 0;
- + int mbox_fd;
- + off_t off = 0, size;
- + struct stat st;
- +
- + mbox_fd = fileno(mbox_file);
- + if( fstat(mbox_fd, &st) == -1 ) {
- + log_write(0, LOG_MAIN|LOG_PANIC,
- + "spam acl condition: %s on scan file", strerror(errno));
- + (void)close(spamd_sock);
- + (void)fclose(mbox_file);
- + return DEFER;
- + }
- + size = st.st_size;
- + for (;;) {
- + if (spam_poll_socket(spamd_sock, start) == -1) {
- + (void)close(spamd_sock);
- + (void)fclose(mbox_file);
- + return DEFER;
- + }
- + copied = sendfile(spamd_sock, mbox_fd, &offset, (size - offset));
- + if (copied < 0) {
- + if (errno == EINTR)
- + continue;
- +
- + log_write(0, LOG_MAIN|LOG_PANIC,
- + "spam acl condition: %s on spamd socket", strerror(errno));
- + (void)close(spamd_sock);
- + (void)fclose(mbox_file);
- + return DEFER;
- + }
- + else {
- + size -= copied;
- + if (size == 0) {
- + /* the whole file has been sent */
- + break;
- + }
- + }
- + }
- +#else
- do {
- read = fread(spamd_buffer,1,sizeof(spamd_buffer),mbox_file);
- if (read > 0) {
- offset = 0;
- again:
- -#ifndef NO_POLL_H
- - result = poll(&pollfd, 1, 1000);
- -
- -/* Patch posted by Erik ? for OS X and applied by PH */
- -#else
- - select_tv.tv_sec = 1;
- - select_tv.tv_usec = 0;
- - FD_ZERO(&select_fd);
- - FD_SET(spamd_sock, &select_fd);
- - result = select(spamd_sock+1, NULL, &select_fd, NULL, &select_tv);
- -#endif
- -/* End Erik's patch */
- -
- - if (result == -1 && errno == EINTR)
- - goto again;
- - else if (result < 1) {
- - if (result == -1)
- - log_write(0, LOG_MAIN|LOG_PANIC,
- - "spam acl condition: %s on spamd socket", strerror(errno));
- - else {
- - if (time(NULL) - start < SPAMD_TIMEOUT)
- - goto again;
- - log_write(0, LOG_MAIN|LOG_PANIC,
- - "spam acl condition: timed out writing spamd socket");
- - }
- + if (spam_poll_socket(spamd_sock, start) == -1) {
- (void)close(spamd_sock);
- (void)fclose(mbox_file);
- return DEFER;
- @@ -318,6 +408,7 @@ again:
- (void)fclose(mbox_file);
- return DEFER;
- }
- +#endif /* HAVE_LINUX_SENDFILE */
-
- (void)fclose(mbox_file);
-
- @@ -346,60 +437,103 @@ again:
- /* reading done */
- (void)close(spamd_sock);
-
- - /* dig in the spamd output and put the report in a multiline header, if requested */
- - if( sscanf(CS spamd_buffer,"SPAMD/%7s 0 EX_OK\r\nContent-length: %*u\r\n\r\n%lf/%lf\r\n%n",
- - spamd_version,&spamd_score,&spamd_threshold,&spamd_report_offset) != 3 ) {
- + if (!is_rspamd) {
- + /* dig in the spamd output and put the report in a multiline header, if requested */
- + if( sscanf(CS spamd_buffer,"SPAMD/%7s 0 EX_OK\r\nContent-length: %*u\r\n\r\n%lf/%lf\r\n%n",
- + spamd_version,&spamd_score,&spamd_threshold,&spamd_report_offset) != 3 ) {
-
- - /* try to fall back to pre-2.50 spamd output */
- - if( sscanf(CS spamd_buffer,"SPAMD/%7s 0 EX_OK\r\nSpam: %*s ; %lf / %lf\r\n\r\n%n",
- - spamd_version,&spamd_score,&spamd_threshold,&spamd_report_offset) != 3 ) {
- - log_write(0, LOG_MAIN|LOG_PANIC,
- - "spam acl condition: cannot parse spamd output");
- - return DEFER;
- + /* try to fall back to pre-2.50 spamd output */
- + if( sscanf(CS spamd_buffer,"SPAMD/%7s 0 EX_OK\r\nSpam: %*s ; %lf / %lf\r\n\r\n%n",
- + spamd_version,&spamd_score,&spamd_threshold,&spamd_report_offset) != 3 ) {
- + log_write(0, LOG_MAIN|LOG_PANIC,
- + "spam acl condition: cannot parse spamd output");
- + return DEFER;
- + };
- };
- - };
-
- - /* Create report. Since this is a multiline string,
- - we must hack it into shape first */
- - p = &spamd_buffer[spamd_report_offset];
- - q = spam_report_buffer;
- - while (*p != '\0') {
- - /* skip \r */
- - if (*p == '\r') {
- - p++;
- - continue;
- - };
- - *q = *p;
- - q++;
- - if (*p == '\n') {
- - /* add an extra space after the newline to ensure
- - that it is treated as a header continuation line */
- - *q = ' ';
- - q++;
- + /* Create report. Since this is a multiline string,
- + we must hack it into shape first */
- + p = &spamd_buffer[spamd_report_offset];
- + q = spam_report_buffer;
- + while (*p != '\0') {
- + /* skip \r */
- + if (*p == '\r') {
- + p++;
- + continue;
- + };
- + *q = *p;
- + q++;
- + if (*p == '\n') {
- + /* add an extra space after the newline to ensure
- + that it is treated as a header continuation line */
- + *q = ' ';
- + q++;
- + };
- + p++;
- };
- - p++;
- - };
- - /* NULL-terminate */
- - *q = '\0';
- - q--;
- - /* cut off trailing leftovers */
- - while (*q <= ' ') {
- + /* NULL-terminate */
- *q = '\0';
- q--;
- - };
- + /* cut off trailing leftovers */
- + while (*q <= ' ') {
- + *q = '\0';
- + q--;
- + };
- + if( spamd_score >= spamd_threshold ) {
- + Ustrcpy(spam_action_buffer, "reject");
- + }
- + else {
- + Ustrcpy(spam_action_buffer, "no action");
- + }
- + }
- + else {
- + /* rspamd variant of reply */
- + int r;
- + if( (r = sscanf(CS spamd_buffer,"RSPAMD/%7s 0 EX_OK\r\nMetric: default; %7s %lf / %lf / %lf\r\n%n",
- + spamd_version,spamd_short_result,&spamd_score,&spamd_threshold,&spamd_reject_score,&spamd_report_offset)) != 5 ) {
- + log_write(0, LOG_MAIN|LOG_PANIC,
- + "spam acl condition: cannot parse spamd output: %d", r);
- + return DEFER;
- + };
- + /* now parse action */
- + p = &spamd_buffer[spamd_report_offset];
- +
- + if( Ustrncmp(p, "Action: ", sizeof("Action: ") - 1) == 0 ) {
- + p += sizeof("Action: ") - 1;
- + q = &spam_action_buffer[0];
- + while (*p && *p != '\r' && (q - spam_action_buffer) < sizeof(spam_action_buffer) - 1) {
- + *q++ = *p++;
- + }
- + *q = '\0';
- + }
- + /* make a simple report */
- + if( spamd_score >= spamd_threshold ) {
- + p = "likely spam";
- + }
- + else if ( Ustrcmp (spam_action_buffer, "no action") == 0 ) {
- + p = "unlikely spam";
- + }
- + else {
- + p = "probably spam";
- + }
- + string_format(spam_report_buffer, sizeof(spam_report_buffer), "This message is %s.", p);
- + }
- +
- + /* common spamd actions */
- spam_report = spam_report_buffer;
- + spam_action = spam_action_buffer;
-
- /* create spam bar */
- spamd_score_char = spamd_score > 0 ? '+' : '-';
- j = abs((int)(spamd_score));
- i = 0;
- if( j != 0 ) {
- - while((i < j) && (i <= MAX_SPAM_BAR_CHARS))
- - spam_bar_buffer[i++] = spamd_score_char;
- + while((i < j) && (i <= MAX_SPAM_BAR_CHARS))
- + spam_bar_buffer[i++] = spamd_score_char;
- }
- else{
- - spam_bar_buffer[0] = '/';
- - i = 1;
- + spam_bar_buffer[0] = '/';
- + i = 1;
- }
- spam_bar_buffer[i] = '\0';
- spam_bar = spam_bar_buffer;
- @@ -415,12 +549,12 @@ again:
-
- /* compare threshold against score */
- if (spamd_score >= spamd_threshold) {
- - /* spam as determined by user's threshold */
- - spam_rc = OK;
- + /* spam as determined by user's threshold */
- + spam_rc = OK;
- }
- else {
- - /* not spam */
- - spam_rc = FAIL;
- + /* not spam */
- + spam_rc = FAIL;
- };
-
- /* remember expanded spamd_address if needed */
- @@ -440,4 +574,126 @@ again:
- };
- }
-
- +#ifdef __GNUC__
- +static int
- +spam_push_line(struct iovec *iov, const int i, const char *fmt, ...) __attribute__ ((format (printf, 3, 4)));
- +#endif
- +static int
- +spam_push_line(struct iovec *iov, const int i, const char *fmt, ...)
- +{
- + va_list ap;
- + size_t len;
- + char buf[512];
- +
- + va_start(ap, fmt);
- + len = vsnprintf(buf, sizeof(buf), fmt, ap);
- + va_end(ap);
- +
- + iov[i].iov_base = string_copy(US buf);
- + iov[i].iov_len = len;
- +
- + if (len >= sizeof(buf)) {
- + log_write(0, LOG_MAIN, "rspam: error, string was longer than %d", (int)sizeof(buf));
- + return (-1);
- + }
- +
- + return 0;
- +}
- +
- +static int
- +spam_write_vector(int sock, size_t size, struct iovec *iov, time_t start)
- +{
- + int r, i;
- +
- + for (;;) {
- + if (spam_poll_socket(sock, start) == -1) {
- + return -1;
- + }
- + r = writev(sock, iov, size);
- + if (r == -1) {
- + if (errno == EINTR)
- + continue;
- +
- + log_write(0, LOG_MAIN|LOG_PANIC,
- + "spam acl condition: %s on spamd socket", strerror(errno));
- + return -1;
- + }
- + else {
- + /* check for partial writev */
- + for (i = 0; i < size; i ++) {
- + if (r >= iov[i].iov_len) {
- + r -= iov[i].iov_len;
- + }
- + else {
- + /* partial iov write */
- + iov[i].iov_base += r;
- + break;
- + }
- + if (r == 0)
- + break;
- + }
- +
- + if (i == size - 1 && r == 0) {
- + /* we have written everything */
- + break;
- + }
- + else {
- + /* move iov to the last unreaded element */
- + iov = &iov[i];
- + size -= i;
- + }
- + }
- + }
- +
- + return 0;
- +
- +}
- +
- +static int
- +spam_poll_socket (int sock, time_t start)
- +{
- +#ifndef NO_POLL_H
- + struct pollfd pollfd;
- +#else /* Patch posted by Erik ? for OS X */
- + struct timeval select_tv; /* and applied by PH */
- + fd_set select_fd;
- +#endif
- + int r;
- +
- +#ifndef NO_POLL_H
- + pollfd.fd = sock;
- + pollfd.events = POLLOUT;
- +#endif
- + for (;;) {
- +#ifndef NO_POLL_H
- + r = poll(&pollfd, 1, 1000);
- +
- +/* Patch posted by Erik ? for OS X and applied by PH */
- +#else
- + select_tv.tv_sec = 1;
- + select_tv.tv_usec = 0;
- + FD_ZERO(&select_fd);
- + FD_SET(sock, &select_fd);
- + r = select(sock+1, NULL, &select_fd, NULL, &select_tv);
- +#endif
- +/* End Erik's patch */
- +
- + if (r == -1 && errno == EINTR)
- + continue;
- + else if (r < 1) {
- + if (r == -1)
- + log_write(0, LOG_MAIN|LOG_PANIC,
- + "spam acl condition: %s on spamd socket", strerror(errno));
- + else {
- + if (time(NULL) - start < SPAMD_TIMEOUT)
- + continue;
- +
- + log_write(0, LOG_MAIN|LOG_PANIC,
- + "spam acl condition: timed out writing spamd socket");
- + }
- + }
- + return r;
- + }
- +}
- +
- #endif
- diff --git a/src/src/spam.h b/src/src/spam.h
- index ba700c8..6047c59 100644
- --- a/src/src/spam.h
- +++ b/src/src/spam.h
- @@ -22,7 +22,8 @@
-
- typedef struct spamd_address_container {
- uschar tcp_addr[24];
- - unsigned int tcp_port;
- + unsigned short int tcp_port;
- + unsigned is_rspamd:1;
- } spamd_address_container;
-
- #endif
|