Kill dtls_state, fix --no-dtls
[platform/upstream/openconnect.git] / mainloop.c
1 /*
2  * Open AnyConnect (SSL + DTLS) client
3  *
4  * © 2008 David Woodhouse <dwmw2@infradead.org>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public License
8  * as published by the Free Software Foundation; either version 2.1 of
9  * the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to:
18  *
19  *   Free Software Foundation, Inc.
20  *   51 Franklin Street, Fifth Floor,
21  *   Boston, MA 02110-1301 USA
22  */
23
24 #include <errno.h>
25 #include <poll.h>
26 #include <limits.h>
27 #include <sys/select.h>
28 #include <signal.h>
29 #include <arpa/inet.h>
30 #include <unistd.h>
31
32 #include "openconnect.h"
33
34 void queue_packet(struct pkt **q, struct pkt *new)
35 {
36         while (*q)
37                 q = &(*q)->next;
38
39         new->next = NULL;
40         *q = new;
41 }
42
43 int queue_new_packet(struct pkt **q, int type, void *buf, int len)
44 {
45         struct pkt *new = malloc(sizeof(struct pkt) + len);
46         if (!new)
47                 return -ENOMEM;
48
49         new->type = type;
50         new->len = len;
51         new->next = NULL;
52         memcpy(new->data, buf, len);
53         queue_packet(q, new);
54         return 0;
55 }
56
57 int vpn_add_pollfd(struct openconnect_info *vpninfo, int fd, short events)
58 {
59         vpninfo->nfds++;
60         vpninfo->pfds = realloc(vpninfo->pfds, sizeof(struct pollfd) * vpninfo->nfds);
61         if (!vpninfo->pfds) {
62                 vpninfo->progress(vpninfo, PRG_ERR, "Failed to reallocate pfds\n");
63                 exit(1);
64         }
65         vpninfo->pfds[vpninfo->nfds - 1].fd = fd;
66         vpninfo->pfds[vpninfo->nfds - 1].events = events;
67
68         return vpninfo->nfds - 1;
69 }
70
71 static int killed;
72
73 static void handle_sigint(int sig)
74 {
75         killed = sig;
76 }
77
78 int vpn_mainloop(struct openconnect_info *vpninfo)
79 {
80         struct sigaction sa;
81         memset(&sa, 0, sizeof(sa));
82         sa.sa_handler = handle_sigint;
83         
84         sigaction(SIGINT, &sa, NULL);
85         sigaction(SIGHUP, &sa, NULL);
86
87         while (!vpninfo->quit_reason) {
88                 int did_work = 0;
89                 int timeout = INT_MAX;
90
91                 if (vpninfo->new_dtls_ssl)
92                         dtls_try_handshake(vpninfo);
93
94                 if (vpninfo->dtls_attempt_period && !vpninfo->dtls_ssl && !vpninfo->new_dtls_ssl &&
95                     vpninfo->new_dtls_started + vpninfo->dtls_attempt_period < time(NULL)) {
96                         vpninfo->progress(vpninfo, PRG_TRACE, "Attempt new DTLS connection\n");
97                         connect_dtls_socket(vpninfo);
98                 }
99                 if (vpninfo->dtls_ssl)
100                         did_work += dtls_mainloop(vpninfo, &timeout);
101
102                 if (vpninfo->quit_reason)
103                         break;
104
105                 did_work += cstp_mainloop(vpninfo, &timeout);
106                 if (vpninfo->quit_reason)
107                         break;
108                 
109                 did_work += tun_mainloop(vpninfo, &timeout);
110                 if (vpninfo->quit_reason)
111                         break;
112
113                 if (killed) {
114                         if (killed == SIGHUP)
115                                 vpninfo->quit_reason = "Client received SIGHUP";
116                         else if (killed == SIGINT)
117                                 vpninfo->quit_reason = "Client received SIGINT";
118                         else
119                                 vpninfo->quit_reason = "Client killed";
120                         break;
121                 }
122
123                 if (did_work)
124                         continue;
125
126                 vpninfo->progress(vpninfo, PRG_TRACE, 
127                                   "Did no work; sleeping for %d ms...\n", timeout);
128
129                 poll(vpninfo->pfds, vpninfo->nfds, timeout);
130                 if (vpninfo->pfds[vpninfo->ssl_pfd].revents & POLL_HUP) {
131                         vpninfo->progress(vpninfo, PRG_ERR, "Server closed connection!\n");
132                         /* OpenSSL doesn't seem to cope properly with this... */
133                         exit(1);
134                 }
135         }
136
137         cstp_bye(vpninfo, vpninfo->quit_reason);
138         vpninfo->progress(vpninfo, PRG_INFO, "Sent quit message: %s\n", vpninfo->quit_reason);
139
140         if (vpninfo->vpnc_script) {
141                 if (vpninfo->script_tun) {
142                         kill(vpninfo->script_tun, SIGHUP);
143                         close(vpninfo->tun_fd);
144                 } else {
145                         setenv("TUNDEV", vpninfo->ifname, 1);
146                         setenv("reason", "disconnect", 1);
147                         system(vpninfo->vpnc_script);
148                 }
149         }
150
151         return 0;
152 }
153
154 /* Called when the socket is unwritable, to get the deadline for DPD.
155    Returns 1 if DPD deadline has already arrived. */
156 int ka_stalled_dpd_time(struct keepalive_info *ka, int *timeout)
157 {
158         time_t now, due;
159
160         if (!ka->dpd)
161                 return 0;
162
163         time(&now);
164         due = ka->last_rx + (2 * ka->dpd);
165
166         if (now > due)
167                 return 1;
168
169         if (*timeout > (due - now) * 1000)
170                 *timeout = (due - now) * 1000;
171
172         return 0;
173 }
174
175
176 int keepalive_action(struct keepalive_info *ka, int *timeout)
177 {
178         time_t now = time(NULL);
179
180         if (ka->rekey) {
181                 time_t due = ka->last_rekey + ka->rekey;
182
183                 if (now >= due)
184                         return KA_REKEY;
185
186                 if (*timeout > (due - now) * 1000)
187                         *timeout = (due - now) * 1000;
188         }
189
190         /* DPD is bidirectional -- PKT 3 out, PKT 4 back */
191         if (ka->dpd) {
192                 time_t due = ka->last_rx + ka->dpd;
193                 time_t overdue = ka->last_rx + (2 * ka->dpd);
194
195                 /* Peer didn't respond */
196                 if (now > overdue)
197                         return KA_DPD_DEAD;
198
199                 /* If we already have DPD outstanding, don't flood. Repeat by
200                    all means, but only after half the DPD period. */
201                 if (ka->last_dpd > ka->last_rx)
202                         due = ka->last_dpd + ka->dpd / 2;
203
204                 /* We haven't seen a packet from this host for $DPD seconds.
205                    Prod it to see if it's still alive */
206                 if (now >= due) {
207                         ka->last_dpd = now;
208                         return KA_DPD;
209                 }
210                 if (*timeout > (due - now) * 1000)
211                         *timeout = (due - now) * 1000;
212         }
213
214         /* Keepalive is just client -> server */
215         if (ka->keepalive) {
216                 time_t due = ka->last_tx + ka->keepalive;
217
218                 /* If we haven't sent anything for $KEEPALIVE seconds, send a
219                    dummy packet (which the server will discard) */
220                 if (now >= due)
221                         return KA_KEEPALIVE;
222
223                 if (*timeout > (due - now) * 1000)
224                         *timeout = (due - now) * 1000;
225         }
226
227         return KA_NONE;
228 }