fix building without gnuTLS support
[claws.git] / src / etpan / nntp-thread.c
1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 2005-2012 DINH Viet Hoa and the Claws Mail team
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  * 
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #include "claws-features.h"
23 #endif
24
25 #ifdef HAVE_LIBETPAN
26
27 #include <glib.h>
28 #include <glib/gi18n.h>
29 #include "nntp-thread.h"
30 #include "news.h"
31 #include <sys/types.h>
32 #include <sys/stat.h>
33 #if (defined(__DragonFly__) || defined (__NetBSD__) || defined (__FreeBSD__) || defined (__OpenBSD__) || defined (__CYGWIN__))
34 #include <sys/socket.h>
35 #endif
36 #include <fcntl.h>
37 #ifndef G_OS_WIN32
38 #include <sys/mman.h>
39 #include <sys/wait.h>
40 #endif
41 #include <gtk/gtk.h>
42 #include <log.h>
43 #include "etpan-thread-manager.h"
44 #include "etpan-ssl.h"
45 #include "utils.h"
46 #include "mainwindow.h"
47 #include "ssl_certificate.h"
48 #include "socket.h"
49 #include "remotefolder.h"
50 #include "main.h"
51 #include "account.h"
52
53 #define DISABLE_LOG_DURING_LOGIN
54
55 static struct etpan_thread_manager * thread_manager = NULL;
56 static chash * nntp_hash = NULL;
57 static chash * session_hash = NULL;
58 static guint thread_manager_signal = 0;
59 static GIOChannel * io_channel = NULL;
60
61 static void (*previous_stream_logger)(int direction,
62     const char * str, size_t size);
63
64 static void nntp_logger(int direction, const char * str, size_t size) 
65 {
66         gchar *buf;
67         gchar **lines;
68         int i = 0;
69
70         if (size > 256) {
71                 log_print(LOG_PROTOCOL, "NNTP%c [data - %zd bytes]\n", direction?'>':'<', size);
72                 return;
73         }
74         buf = malloc(size+1);
75         memset(buf, 0, size+1);
76         strncpy(buf, str, size);
77         buf[size] = '\0';
78
79         if (!strncmp(buf, "<<<<<<<", 7) 
80         ||  !strncmp(buf, ">>>>>>>", 7)) {
81                 free(buf);
82                 return;
83         }
84         while (strstr(buf, "\r"))
85                 *strstr(buf, "\r") = ' ';
86         while (strlen(buf) > 0 && buf[strlen(buf)-1] == '\n')
87                 buf[strlen(buf)-1] = '\0';
88
89         lines = g_strsplit(buf, "\n", -1);
90
91         while (lines[i] && *lines[i]) {
92                 log_print(LOG_PROTOCOL, "NNTP%c %s\n", direction?'>':'<', lines[i]);
93                 i++;
94         }
95         g_strfreev(lines);
96         free(buf);
97 }
98
99 static void delete_nntp(Folder *folder, newsnntp *nntp)
100 {
101         chashdatum key;
102
103         key.data = &folder;
104         key.len = sizeof(folder);
105         chash_delete(session_hash, &key, NULL);
106         
107         key.data = &nntp;
108         key.len = sizeof(nntp);
109         if (nntp && nntp->nntp_stream) {
110                 /* we don't want libetpan to logout */
111                 mailstream_close(nntp->nntp_stream);
112                 nntp->nntp_stream = NULL;
113         }
114         debug_print("removing newsnntp %p\n", nntp);
115         newsnntp_free(nntp);    
116 }
117
118 static gboolean thread_manager_event(GIOChannel * source,
119     GIOCondition condition,
120     gpointer data)
121 {
122 #ifdef G_OS_WIN32
123         gsize bytes_read;
124         gchar ch;
125         
126         if (condition & G_IO_IN)
127                 g_io_channel_read_chars(source, &ch, 1, &bytes_read, NULL);
128 #endif
129         etpan_thread_manager_loop(thread_manager);
130         
131         return TRUE;
132 }
133
134 #define ETPAN_DEFAULT_NETWORK_TIMEOUT 60
135 extern gboolean etpan_skip_ssl_cert_check;
136
137 void nntp_main_init(gboolean skip_ssl_cert_check)
138 {
139         int fd_thread_manager;
140         
141         etpan_skip_ssl_cert_check = skip_ssl_cert_check;
142         
143         nntp_hash = chash_new(CHASH_COPYKEY, CHASH_DEFAULTSIZE);
144         session_hash = chash_new(CHASH_COPYKEY, CHASH_DEFAULTSIZE);
145         
146         thread_manager = etpan_thread_manager_new();
147         
148         fd_thread_manager = etpan_thread_manager_get_fd(thread_manager);
149         
150 #ifndef G_OS_WIN32
151         io_channel = g_io_channel_unix_new(fd_thread_manager);
152 #else
153         io_channel = g_io_channel_win32_new_fd(fd_thread_manager);
154 #endif
155         
156         thread_manager_signal = g_io_add_watch_full(io_channel, 0, G_IO_IN,
157                                                     thread_manager_event,
158                                                     (gpointer) NULL,
159                                                     NULL);
160 }
161
162 void nntp_main_done(gboolean have_connectivity)
163 {
164         nntp_disconnect_all(have_connectivity);
165         etpan_thread_manager_stop(thread_manager);
166 #if defined(__NetBSD__) || defined(__OpenBSD__) || defined(__FreeBSD__)
167         return;
168 #endif
169         etpan_thread_manager_join(thread_manager);
170         
171         g_source_remove(thread_manager_signal);
172         g_io_channel_unref(io_channel);
173         
174         etpan_thread_manager_free(thread_manager);
175         
176         chash_free(session_hash);
177         chash_free(nntp_hash);
178 }
179
180 void nntp_init(Folder * folder)
181 {
182         struct etpan_thread * thread;
183         chashdatum key;
184         chashdatum value;
185         
186         thread = etpan_thread_manager_get_thread(thread_manager);
187         
188         key.data = &folder;
189         key.len = sizeof(folder);
190         value.data = thread;
191         value.len = 0;
192         
193         chash_set(nntp_hash, &key, &value, NULL);
194 }
195
196 void nntp_done(Folder * folder)
197 {
198         struct etpan_thread * thread;
199         chashdatum key;
200         chashdatum value;
201         int r;
202         
203         key.data = &folder;
204         key.len = sizeof(folder);
205         
206         r = chash_get(nntp_hash, &key, &value);
207         if (r < 0)
208                 return;
209         
210         thread = value.data;
211         
212         etpan_thread_unbind(thread);
213         
214         chash_delete(nntp_hash, &key, NULL);
215         
216         debug_print("remove thread");
217 }
218
219 static struct etpan_thread * get_thread(Folder * folder)
220 {
221         struct etpan_thread * thread;
222         chashdatum key;
223         chashdatum value;
224         int r;
225
226         key.data = &folder;
227         key.len = sizeof(folder);
228
229         r = chash_get(nntp_hash, &key, &value);
230         if (r < 0)
231                 return NULL;
232
233         thread = value.data;
234
235         return thread;
236 }
237
238 static newsnntp * get_nntp(Folder * folder)
239 {
240         newsnntp * nntp;
241         chashdatum key;
242         chashdatum value;
243         int r;
244         
245         key.data = &folder;
246         key.len = sizeof(folder);
247         
248         r = chash_get(session_hash, &key, &value);
249         if (r < 0)
250                 return NULL;
251         
252         nntp = value.data;
253         debug_print("found nntp %p\n", nntp);
254         return nntp;
255 }
256
257
258 static void generic_cb(int cancelled, void * result, void * callback_data)
259 {
260         struct etpan_thread_op * op;
261         
262         op = (struct etpan_thread_op *) callback_data;
263
264         debug_print("generic_cb\n");
265         op->finished = 1;
266 }
267
268 static void threaded_run(Folder * folder, void * param, void * result,
269                          void (* func)(struct etpan_thread_op * ))
270 {
271         struct etpan_thread_op * op;
272         struct etpan_thread * thread;
273         
274         nntp_folder_ref(folder);
275
276         op = etpan_thread_op_new();
277         
278         op->nntp = get_nntp(folder);
279         op->param = param;
280         op->result = result;
281         
282         op->cancellable = 0;
283         op->run = func;
284         op->callback = generic_cb;
285         op->callback_data = op;
286         op->cleanup = NULL;
287         
288         op->finished = 0;
289         
290         previous_stream_logger = mailstream_logger;
291         mailstream_logger = nntp_logger;
292
293         thread = get_thread(folder);
294         etpan_thread_op_schedule(thread, op);
295         
296         while (!op->finished) {
297                 gtk_main_iteration();
298         }
299         
300         mailstream_logger = previous_stream_logger;
301
302         etpan_thread_op_free(op);
303
304         nntp_folder_unref(folder);
305 }
306
307
308 /* connect */
309
310 struct connect_param {
311         newsnntp * nntp;
312         PrefsAccount *account;
313         const char * server;
314         int port;
315 };
316
317 struct connect_result {
318         int error;
319 };
320
321 #define CHECK_NNTP() {                                          \
322         if (!param->nntp) {                                     \
323                 result->error = NEWSNNTP_ERROR_BAD_STATE;       \
324                 return;                                         \
325         }                                                       \
326 }
327
328 static void connect_run(struct etpan_thread_op * op)
329 {
330         int r;
331         struct connect_param * param;
332         struct connect_result * result;
333         
334         param = op->param;
335         result = op->result;
336         
337         CHECK_NNTP();
338
339         r = newsnntp_socket_connect(param->nntp,
340                                     param->server, param->port);
341         
342         result->error = r;
343 }
344
345
346 int nntp_threaded_connect(Folder * folder, const char * server, int port)
347 {
348         struct connect_param param;
349         struct connect_result result;
350         chashdatum key;
351         chashdatum value;
352         newsnntp * nntp, * oldnntp;
353         
354         oldnntp = get_nntp(folder);
355
356         nntp = newsnntp_new(0, NULL);
357         
358         if (oldnntp) {
359                 debug_print("deleting old nntp %p\n", oldnntp);
360                 delete_nntp(folder, oldnntp);
361         }
362         
363         key.data = &folder;
364         key.len = sizeof(folder);
365         value.data = nntp;
366         value.len = 0;
367         chash_set(session_hash, &key, &value, NULL);
368         
369         param.nntp = nntp;
370         param.server = server;
371         param.port = port;
372         
373         refresh_resolvers();
374         threaded_run(folder, &param, &result, connect_run);
375         
376         debug_print("connect ok %i with nntp %p\n", result.error, nntp);
377         
378         return result.error;
379 }
380 #ifdef USE_GNUTLS
381 static void connect_ssl_run(struct etpan_thread_op * op)
382 {
383         int r;
384         struct connect_param * param;
385         struct connect_result * result;
386         
387         param = op->param;
388         result = op->result;
389         
390         CHECK_NNTP();
391
392         r = newsnntp_ssl_connect_with_callback(param->nntp,
393                                  param->server, param->port,
394                                  etpan_connect_ssl_context_cb, param->account);
395         result->error = r;
396 }
397
398 int nntp_threaded_connect_ssl(Folder * folder, const char * server, int port)
399 {
400         struct connect_param param;
401         struct connect_result result;
402         chashdatum key;
403         chashdatum value;
404         newsnntp * nntp, * oldnntp;
405         gboolean accept_if_valid = FALSE;
406
407         oldnntp = get_nntp(folder);
408
409         nntp = newsnntp_new(0, NULL);
410
411         if (oldnntp) {
412                 debug_print("deleting old nntp %p\n", oldnntp);
413                 delete_nntp(folder, oldnntp);
414         }
415
416         key.data = &folder;
417         key.len = sizeof(folder);
418         value.data = nntp;
419         value.len = 0;
420         chash_set(session_hash, &key, &value, NULL);
421
422         param.nntp = nntp;
423         param.server = server;
424         param.port = port;
425         param.account = folder->account;
426
427         if (folder->account)
428                 accept_if_valid = folder->account->ssl_certs_auto_accept;
429
430         refresh_resolvers();
431         threaded_run(folder, &param, &result, connect_ssl_run);
432
433         if (result.error == NEWSNNTP_NO_ERROR && !etpan_skip_ssl_cert_check) {
434                 if (etpan_certificate_check(nntp->nntp_stream, server, port,
435                                             accept_if_valid) != TRUE)
436                         return -1;
437         }
438         debug_print("connect %d with nntp %p\n", result.error, nntp);
439         
440         return result.error;
441 }
442 #endif
443
444 void nntp_threaded_disconnect(Folder * folder)
445 {
446         newsnntp * nntp;
447         
448         nntp = get_nntp(folder);
449         if (nntp == NULL) {
450                 debug_print("was disconnected\n");
451                 return;
452         }
453         
454         debug_print("deleting old nntp %p\n", nntp);
455         delete_nntp(folder, nntp);
456         
457         debug_print("disconnect ok\n");
458 }
459
460 void nntp_threaded_cancel(Folder * folder)
461 {
462         newsnntp * nntp;
463         
464         nntp = get_nntp(folder);
465         if (nntp->nntp_stream != NULL)
466                 mailstream_cancel(nntp->nntp_stream);
467 }
468
469
470 struct login_param {
471         newsnntp * nntp;
472         const char * login;
473         const char * password;
474 };
475
476 struct login_result {
477         int error;
478 };
479
480 static void login_run(struct etpan_thread_op * op)
481 {
482         struct login_param * param;
483         struct login_result * result;
484         int r;
485 #ifdef DISABLE_LOG_DURING_LOGIN
486         int old_debug;
487 #endif
488         
489         param = op->param;
490         result = op->result;
491
492         CHECK_NNTP();
493
494 #ifdef DISABLE_LOG_DURING_LOGIN
495         old_debug = mailstream_debug;
496         mailstream_debug = 0;
497 #endif
498
499         r = newsnntp_authinfo_username(param->nntp, param->login);
500         /* libetpan returning NO_ERROR means it received resp.code 281:
501            in this case auth. is already successful, no password is needed. */
502         if (r == NEWSNNTP_WARNING_REQUEST_AUTHORIZATION_PASSWORD) {
503                 r = newsnntp_authinfo_password(param->nntp, param->password);
504         }
505         
506
507
508 #ifdef DISABLE_LOG_DURING_LOGIN
509         mailstream_debug = old_debug;
510 #endif
511         
512         result->error = r;
513         if (param->nntp->nntp_response)
514                 nntp_logger(0, param->nntp->nntp_response, strlen(param->nntp->nntp_response));
515
516         debug_print("nntp login run - end %i\n", r);
517 }
518
519 int nntp_threaded_login(Folder * folder, const char * login, const char * password)
520 {
521         struct login_param param;
522         struct login_result result;
523         
524         debug_print("nntp login - begin\n");
525         
526         param.nntp = get_nntp(folder);
527         param.login = login;
528         param.password = password;
529
530         threaded_run(folder, &param, &result, login_run);
531         
532         debug_print("nntp login - end\n");
533         
534         return result.error;
535 }
536
537 struct date_param {
538         newsnntp * nntp;
539         struct tm * lt;
540 };
541
542 struct date_result {
543         int error;
544 };
545
546 static void date_run(struct etpan_thread_op * op)
547 {
548         struct date_param * param;
549         struct date_result * result;
550         int r;
551         
552         param = op->param;
553         result = op->result;
554
555         CHECK_NNTP();
556
557         r = newsnntp_date(param->nntp, param->lt);
558         
559         result->error = r;
560         debug_print("nntp date run - end %i\n", r);
561 }
562
563 int nntp_threaded_date(Folder * folder, struct tm *lt)
564 {
565         struct date_param param;
566         struct date_result result;
567         
568         debug_print("nntp date - begin\n");
569         
570         param.nntp = get_nntp(folder);
571         param.lt = lt;
572
573         threaded_run(folder, &param, &result, date_run);
574         
575         debug_print("nntp date - end\n");
576         
577         return result.error;
578 }
579
580 struct list_param {
581         newsnntp * nntp;
582         clist **grouplist;
583 };
584
585 struct list_result {
586         int error;
587 };
588
589 static void list_run(struct etpan_thread_op * op)
590 {
591         struct list_param * param;
592         struct list_result * result;
593         int r;
594         
595         param = op->param;
596         result = op->result;
597
598         CHECK_NNTP();
599
600         r = newsnntp_list(param->nntp, param->grouplist);
601         
602         result->error = r;
603         debug_print("nntp list run - end %i\n", r);
604 }
605
606 int nntp_threaded_list(Folder * folder, clist **grouplist)
607 {
608         struct list_param param;
609         struct list_result result;
610         
611         debug_print("nntp list - begin\n");
612         
613         param.nntp = get_nntp(folder);
614         param.grouplist = grouplist;
615
616         threaded_run(folder, &param, &result, list_run);
617         
618         debug_print("nntp list - end\n");
619         
620         return result.error;
621 }
622
623 struct post_param {
624         newsnntp * nntp;
625         char *contents;
626         size_t len;
627 };
628
629 struct post_result {
630         int error;
631 };
632
633 static void post_run(struct etpan_thread_op * op)
634 {
635         struct post_param * param;
636         struct post_result * result;
637         int r;
638         
639         param = op->param;
640         result = op->result;
641
642         CHECK_NNTP();
643
644         r = newsnntp_post(param->nntp, param->contents, param->len);
645         
646         result->error = r;
647         debug_print("nntp post run - end %i\n", r);
648 }
649
650 int nntp_threaded_post(Folder * folder, char *contents, size_t len)
651 {
652         struct post_param param;
653         struct post_result result;
654         
655         debug_print("nntp post - begin\n");
656         
657         param.nntp = get_nntp(folder);
658         param.contents = contents;
659         param.len = len;
660
661         threaded_run(folder, &param, &result, post_run);
662         
663         debug_print("nntp post - end\n");
664         
665         return result.error;
666 }
667
668 struct article_param {
669         newsnntp * nntp;
670         guint32 num;
671         char **contents;
672         size_t *len;
673 };
674
675 struct article_result {
676         int error;
677 };
678
679 static void article_run(struct etpan_thread_op * op)
680 {
681         struct article_param * param;
682         struct article_result * result;
683         int r;
684         
685         param = op->param;
686         result = op->result;
687
688         CHECK_NNTP();
689
690         r = newsnntp_article(param->nntp, param->num, param->contents, param->len);
691         
692         result->error = r;
693         debug_print("nntp article run - end %i\n", r);
694 }
695
696 int nntp_threaded_article(Folder * folder, guint32 num, char **contents, size_t *len)
697 {
698         struct article_param param;
699         struct article_result result;
700         
701         debug_print("nntp article - begin\n");
702         
703         param.nntp = get_nntp(folder);
704         param.num = num;
705         param.contents = contents;
706         param.len = len;
707
708         threaded_run(folder, &param, &result, article_run);
709         
710         debug_print("nntp article - end\n");
711         
712         return result.error;
713 }
714
715 struct group_param {
716         newsnntp * nntp;
717         const char *group;
718         struct newsnntp_group_info **info;
719 };
720
721 struct group_result {
722         int error;
723 };
724
725 static void group_run(struct etpan_thread_op * op)
726 {
727         struct group_param * param;
728         struct group_result * result;
729         int r;
730         
731         param = op->param;
732         result = op->result;
733
734         CHECK_NNTP();
735
736         r = newsnntp_group(param->nntp, param->group, param->info);
737         
738         result->error = r;
739         debug_print("nntp group run - end %i\n", r);
740 }
741
742 int nntp_threaded_group(Folder * folder, const char *group, struct newsnntp_group_info **info)
743 {
744         struct group_param param;
745         struct group_result result;
746         
747         debug_print("nntp group - begin\n");
748         
749         param.nntp = get_nntp(folder);
750         param.group = group;
751         param.info = info;
752
753         threaded_run(folder, &param, &result, group_run);
754         
755         debug_print("nntp group - end\n");
756         
757         return result.error;
758 }
759
760 struct mode_reader_param {
761         newsnntp * nntp;
762 };
763
764 struct mode_reader_result {
765         int error;
766 };
767
768 static void mode_reader_run(struct etpan_thread_op * op)
769 {
770         struct mode_reader_param * param;
771         struct mode_reader_result * result;
772         int r;
773         
774         param = op->param;
775         result = op->result;
776
777         CHECK_NNTP();
778
779         r = newsnntp_mode_reader(param->nntp);
780         
781         result->error = r;
782         debug_print("nntp mode_reader run - end %i\n", r);
783 }
784
785 int nntp_threaded_mode_reader(Folder * folder)
786 {
787         struct mode_reader_param param;
788         struct mode_reader_result result;
789         
790         debug_print("nntp mode_reader - begin\n");
791         
792         param.nntp = get_nntp(folder);
793
794         threaded_run(folder, &param, &result, mode_reader_run);
795         
796         debug_print("nntp mode_reader - end\n");
797         
798         return result.error;
799 }
800
801 struct xover_param {
802         newsnntp * nntp;
803         guint32 beg;
804         guint32 end;
805         struct newsnntp_xover_resp_item **result;
806         clist **msglist;
807 };
808
809 struct xover_result {
810         int error;
811 };
812
813 static void xover_run(struct etpan_thread_op * op)
814 {
815         struct xover_param * param;
816         struct xover_result * result;
817         int r;
818         
819         param = op->param;
820         result = op->result;
821
822         CHECK_NNTP();
823         
824         if (param->result) {
825                 r = newsnntp_xover_single(param->nntp, param->beg, param->result);
826         } else {
827                 r = newsnntp_xover_range(param->nntp, param->beg, param->end, param->msglist);
828         }
829         
830         result->error = r;
831         debug_print("nntp xover run - end %i\n", r);
832 }
833
834 int nntp_threaded_xover(Folder * folder, guint32 beg, guint32 end, struct newsnntp_xover_resp_item **single_result, clist **multiple_result)
835 {
836         struct xover_param param;
837         struct xover_result result;
838         
839         debug_print("nntp xover - begin\n");
840         
841         param.nntp = get_nntp(folder);
842         param.beg = beg;
843         param.end = end;
844         param.result = single_result;
845         param.msglist = multiple_result;
846
847         threaded_run(folder, &param, &result, xover_run);
848         
849         debug_print("nntp xover - end\n");
850         
851         return result.error;
852 }
853
854 struct xhdr_param {
855         newsnntp * nntp;
856         const char *header;
857         guint32 beg;
858         guint32 end;
859         clist **hdrlist;
860 };
861
862 struct xhdr_result {
863         int error;
864 };
865
866 static void xhdr_run(struct etpan_thread_op * op)
867 {
868         struct xhdr_param * param;
869         struct xhdr_result * result;
870         int r;
871         
872         param = op->param;
873         result = op->result;
874
875         CHECK_NNTP();
876         
877         if (param->beg == param->end) {
878                 r = newsnntp_xhdr_single(param->nntp, param->header, param->beg, param->hdrlist);
879         } else {
880                 r = newsnntp_xhdr_range(param->nntp, param->header, param->beg, param->end, param->hdrlist);
881         }
882         
883         result->error = r;
884         debug_print("nntp xhdr run - end %i\n", r);
885 }
886
887 int nntp_threaded_xhdr(Folder * folder, const char *header, guint32 beg, guint32 end, clist **hdrlist)
888 {
889         struct xhdr_param param;
890         struct xhdr_result result;
891         
892         debug_print("nntp xhdr - begin\n");
893         
894         param.nntp = get_nntp(folder);
895         param.header = header;
896         param.beg = beg;
897         param.end = end;
898         param.hdrlist = hdrlist;
899
900         threaded_run(folder, &param, &result, xhdr_run);
901         
902         debug_print("nntp xhdr - end\n");
903         
904         return result.error;
905 }
906
907 void nntp_main_set_timeout(int sec)
908 {
909         mailstream_network_delay.tv_sec = sec;
910         mailstream_network_delay.tv_usec = 0;
911 }
912
913 #else
914
915 void nntp_main_init(void)
916 {
917 }
918 void nntp_main_done(gboolean have_connectivity)
919 {
920 }
921 void nntp_main_set_timeout(int sec)
922 {
923 }
924
925 void nntp_threaded_cancel(Folder * folder);
926 {
927 }
928
929 #endif