Fixed Bug #1044 -- connections should no lomnger be re-used if authent tokens
[pazpar2-moved-to-github.git] / src / connection.c
1 /* $Id: connection.c,v 1.3 2007-06-02 04:32:28 quinn Exp $
2    Copyright (c) 2006-2007, Index Data.
3
4 This file is part of Pazpar2.
5
6 Pazpar2 is free software; you can redistribute it and/or modify it under
7 the terms of the GNU General Public License as published by the Free
8 Software Foundation; either version 2, or (at your option) any later
9 version.
10
11 Pazpar2 is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Pazpar2; see the file LICENSE.  If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19 02111-1307, USA.
20  */
21
22 /** \file connection.c
23     \brief Z39.50 connection (low-level client)
24 */
25
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <sys/time.h>
30 #include <unistd.h>
31 #include <sys/socket.h>
32 #include <netdb.h>
33 #include <signal.h>
34 #include <ctype.h>
35 #include <assert.h>
36
37 #if HAVE_CONFIG_H
38 #include "cconfig.h"
39 #endif
40
41 #include <yaz/log.h>
42 #include <yaz/comstack.h>
43 #include <yaz/tcpip.h>
44 #include "connection.h"
45 #include "eventl.h"
46 #include "pazpar2.h"
47 #include "host.h"
48 #include "client.h"
49 #include "settings.h"
50 #include "parameters.h"
51
52
53 /** \brief Represents a physical, reusable  connection to a remote Z39.50 host
54  */
55 struct connection {
56     IOCHAN iochan;
57     COMSTACK link;
58     struct host *host;
59     struct client *client;
60     char *ibuf;
61     int ibufsize;
62     char *authentication; // Empty string or authentication string if set
63     enum {
64         Conn_Resolving,
65         Conn_Connecting,
66         Conn_Open,
67         Conn_Waiting,
68     } state;
69     struct connection *next; // next for same host or next in free list
70 };
71
72 static struct connection *connection_freelist = 0;
73
74 static void remove_connection_from_host(struct connection *con)
75 {
76     struct connection **conp = &con->host->connections;
77     assert(con);
78     while (*conp)
79     {
80         if (*conp == con)
81         {
82             *conp = (*conp)->next;
83             return;
84         }
85         conp = &(*conp)->next;
86     }
87     assert(*conp == 0);
88 }
89
90 // Close connection and recycle structure
91 void connection_destroy(struct connection *co)
92 {
93     if (co->link)
94     {
95         cs_close(co->link);
96         iochan_destroy(co->iochan);
97     }
98
99     yaz_log(YLOG_DEBUG, "Connection destroy %s", co->host->hostport);
100
101     remove_connection_from_host(co);
102     if (co->client)
103     {
104         client_disconnect(co->client);
105     }
106     co->next = connection_freelist;
107     connection_freelist = co;
108 }
109
110 // Creates a new connection for client, associated with the host of 
111 // client's database
112 struct connection *connection_create(struct client *cl)
113 {
114     struct connection *new;
115     struct host *host = client_get_host(cl);
116
117     if ((new = connection_freelist))
118         connection_freelist = new->next;
119     else
120     {
121         new = xmalloc(sizeof (struct connection));
122         new->ibuf = 0;
123         new->ibufsize = 0;
124     }
125     new->host = host;
126     new->next = new->host->connections;
127     new->host->connections = new;
128     new->client = cl;
129     new->authentication = "";
130     client_set_connection(cl, new);
131     new->link = 0;
132     new->state = Conn_Resolving;
133     if (host->ipport)
134         connection_connect(new);
135     return new;
136 }
137
138 static void connection_handler(IOCHAN i, int event)
139 {
140     struct connection *co = iochan_getdata(i);
141     struct client *cl = co->client;
142     struct session *se = 0;
143
144     if (cl)
145         se = client_get_session(cl);
146     else
147     {
148         yaz_log(YLOG_WARN, "Destroying orphan connection");
149         connection_destroy(co);
150         return;
151     }
152
153     if (co->state == Conn_Connecting && event & EVENT_OUTPUT)
154     {
155         int errcode;
156         socklen_t errlen = sizeof(errcode);
157
158         if (getsockopt(cs_fileno(co->link), SOL_SOCKET, SO_ERROR, &errcode,
159             &errlen) < 0 || errcode != 0)
160         {
161             client_fatal(cl);
162             return;
163         }
164         else
165         {
166             yaz_log(YLOG_DEBUG, "Connect OK");
167             co->state = Conn_Open;
168             if (cl)
169                 client_set_state(cl, Client_Connected);
170         }
171     }
172
173     else if (event & EVENT_INPUT)
174     {
175         int len = cs_get(co->link, &co->ibuf, &co->ibufsize);
176
177         if (len < 0)
178         {
179             yaz_log(YLOG_WARN|YLOG_ERRNO, "Error reading from %s", 
180                     client_get_url(cl));
181             connection_destroy(co);
182             return;
183         }
184         else if (len == 0)
185         {
186             yaz_log(YLOG_WARN, "EOF reading from %s", client_get_url(cl));
187             connection_destroy(co);
188             return;
189         }
190         else if (len > 1) // We discard input if we have no connection
191         {
192             co->state = Conn_Open;
193
194             if (client_is_our_response(cl))
195             {
196                 Z_APDU *a;
197
198                 odr_reset(global_parameters.odr_in);
199                 odr_setbuf(global_parameters.odr_in, co->ibuf, len, 0);
200                 if (!z_APDU(global_parameters.odr_in, &a, 0, 0))
201                 {
202                     client_fatal(cl);
203                     return;
204                 }
205                 switch (a->which)
206                 {
207                     case Z_APDU_initResponse:
208                         client_init_response(cl, a);
209                         break;
210                     case Z_APDU_searchResponse:
211                         client_search_response(cl, a);
212                         break;
213                     case Z_APDU_presentResponse:
214                         client_present_response(cl, a);
215                         break;
216                     case Z_APDU_close:
217                         client_close_response(cl, a);
218                         break;
219                     default:
220                         yaz_log(YLOG_WARN, 
221                                 "Unexpected Z39.50 response from %s",  
222                                 client_get_url(cl));
223                         client_fatal(cl);
224                         return;
225                 }
226                 // We aren't expecting staggered output from target
227                 // if (cs_more(t->link))
228                 //    iochan_setevent(i, EVENT_INPUT);
229             }
230             else  // we throw away response and go to idle mode
231             {
232                 yaz_log(YLOG_DEBUG, "Ignoring result of expired operation");
233                 client_set_state(cl, Client_Idle);
234             }
235         }
236         /* if len==1 we do nothing but wait for more input */
237     }
238     client_continue(cl);
239 }
240
241 // Disassociate connection from client
242 void connection_release(struct connection *co)
243 {
244     struct client *cl = co->client;
245
246     yaz_log(YLOG_DEBUG, "Connection release %s", co->host->hostport);
247     if (!cl)
248         return;
249     client_set_connection(cl, 0);
250     co->client = 0;
251 }
252
253 void connect_resolver_host(struct host *host)
254 {
255     struct connection *con = host->connections;
256     while (con)
257     {
258         if (con->state == Conn_Resolving)
259         {
260             if (!host->ipport) /* unresolved */
261             {
262                 connection_destroy(con);
263                 /* start all over .. at some point it will be NULL */
264                 con = host->connections;
265             }
266             else if (!con->client)
267             {
268                 yaz_log(YLOG_WARN, "connect_unresolved_host : ophan client");
269                 connection_destroy(con);
270                 /* start all over .. at some point it will be NULL */
271                 con = host->connections;
272             }
273             else
274             {
275                 connection_connect(con);
276                 con = con->next;
277             }
278         }
279     }
280 }
281
282 int connection_send_apdu(struct connection *co, Z_APDU *a)
283 {
284     char *buf;
285     int len, r;
286
287     if (!z_APDU(global_parameters.odr_out, &a, 0, 0))
288     {
289         odr_perror(global_parameters.odr_out, "Encoding APDU");
290         abort();
291     }
292     buf = odr_getbuf(global_parameters.odr_out, &len, 0);
293     r = cs_put(co->link, buf, len);
294     if (r < 0)
295     {
296         yaz_log(YLOG_WARN, "cs_put: %s", cs_errmsg(cs_errno(co->link)));
297         return -1;
298     }
299     else if (r == 1)
300     {
301         fprintf(stderr, "cs_put incomplete (ParaZ does not handle that)\n");
302         exit(1);
303     }
304     odr_reset(global_parameters.odr_out); /* release the APDU structure  */
305     co->state = Conn_Waiting;
306     iochan_setflags(co->iochan, EVENT_INPUT);
307     return 0;
308 }
309
310 struct host *connection_get_host(struct connection *con)
311 {
312     return con->host;
313 }
314
315 int connection_connect(struct connection *con)
316 {
317     COMSTACK link = 0;
318     struct host *host = connection_get_host(con);
319     void *addr;
320     int res;
321
322     assert(host->ipport);
323     assert(con);
324
325     if (!(link = cs_create(tcpip_type, 0, PROTO_Z3950)))
326     {
327         yaz_log(YLOG_FATAL|YLOG_ERRNO, "Failed to create comstack");
328         return -1;
329     }
330     
331     if (0 == strlen(global_parameters.zproxy_override)){
332         /* no Z39.50 proxy needed - direct connect */
333         yaz_log(YLOG_DEBUG, "Connection create %s", connection_get_url(con));
334         
335         if (!(addr = cs_straddr(link, host->ipport)))
336         {
337             yaz_log(YLOG_WARN|YLOG_ERRNO, 
338                     "Lookup of IP address %s failed", host->ipport);
339             return -1;
340         }
341         
342     } else {
343         /* Z39.50 proxy connect */
344         yaz_log(YLOG_DEBUG, "Connection create %s proxy %s", 
345                 connection_get_url(con), global_parameters.zproxy_override);
346         
347         if (!(addr = cs_straddr(link, global_parameters.zproxy_override)))
348         {
349             yaz_log(YLOG_WARN|YLOG_ERRNO, 
350                     "Lookup of IP address %s failed", 
351                     global_parameters.zproxy_override);
352             return -1;
353         }
354     }
355     
356     res = cs_connect(link, addr);
357     if (res < 0)
358     {
359         yaz_log(YLOG_WARN|YLOG_ERRNO, "cs_connect %s",
360                 connection_get_url(con));
361         return -1;
362     }
363     con->link = link;
364     con->state = Conn_Connecting;
365     con->iochan = iochan_create(cs_fileno(link), connection_handler, 0);
366     iochan_setdata(con->iochan, con);
367     pazpar2_add_channel(con->iochan);
368
369     /* this fragment is bad DRY: from client_prep_connection */
370     client_set_state(con->client, Client_Connecting);
371     iochan_setflag(con->iochan, EVENT_OUTPUT);
372     return 0;
373 }
374
375 const char *connection_get_url(struct connection *co)
376 {
377     return client_get_url(co->client);
378 }
379
380 void connection_set_authentication(struct connection *co, char *auth)
381 {
382     co->authentication = auth;
383 }
384
385 // Ensure that client has a connection associated
386 int client_prep_connection(struct client *cl)
387 {
388     struct connection *co;
389     struct session *se = client_get_session(cl);
390     struct host *host = client_get_host(cl);
391
392     co = client_get_connection(cl);
393
394     yaz_log(YLOG_DEBUG, "Client prep %s", client_get_url(cl));
395
396     if (!co)
397     {
398         // See if someone else has an idle connection
399         // We should look at timestamps here to select the longest-idle connection
400         for (co = host->connections; co; co = co->next)
401             if (co->state == Conn_Open &&
402                 (!co->client || client_get_session(co->client) != se) &&
403                 !strcmp(co->authentication,
404                     session_setting_oneval(client_get_database(cl),
405                     PZ_AUTHENTICATION)))
406                 break;
407         if (co)
408         {
409             connection_release(co);
410             client_set_connection(cl, co);
411             co->client = cl;
412         }
413         else
414             co = connection_create(cl);
415     }
416     if (co)
417     {
418         if (co->state == Conn_Connecting)
419         {
420             client_set_state(cl, Client_Connecting);
421             iochan_setflag(co->iochan, EVENT_OUTPUT);
422         }
423         else if (co->state == Conn_Open)
424         {
425             if (client_get_state(cl) == Client_Error 
426                 || client_get_state(cl) == Client_Disconnected)
427                 client_set_state(cl, Client_Idle);
428             iochan_setflag(co->iochan, EVENT_OUTPUT);
429         }
430         return 1;
431     }
432     else
433         return 0;
434 }
435
436
437
438 /*
439  * Local variables:
440  * c-basic-offset: 4
441  * indent-tabs-mode: nil
442  * End:
443  * vim: shiftwidth=4 tabstop=8 expandtab
444  */