Return success after updating peer. Additional debug messages.
[trust_router.git] / trp / trps.c
1 #include <fcntl.h>
2 #include <talloc.h>
3 #include <errno.h>
4 #include <unistd.h>
5 #include <sys/time.h>
6
7 #include <gsscon.h>
8 #include <tr_rp.h>
9 #include <trust_router/tr_name.h>
10 #include <trp_internal.h>
11 #include <tr_gss.h>
12 #include <trp_ptable.h>
13 #include <trp_rtable.h>
14 #include <tr_debug.h>
15
16
17 static int trps_destructor(void *object)
18 {
19   TRPS_INSTANCE *trps=talloc_get_type_abort(object, TRPS_INSTANCE);
20   if (trps->rtable!=NULL)
21     trp_rtable_free(trps->rtable);
22   return 0;
23 }
24
25 TRPS_INSTANCE *trps_new (TALLOC_CTX *mem_ctx)
26 {
27   TRPS_INSTANCE *trps=talloc(mem_ctx, TRPS_INSTANCE);
28   if (trps!=NULL)  {
29     trps->hostname=NULL;
30     trps->port=0;
31     trps->cookie=NULL;
32     trps->conn=NULL;
33     trps->trpc=NULL;
34     trps->update_interval=(struct timeval){0,0};
35     trps->sweep_interval=(struct timeval){0,0};
36     trps->ptable=NULL;
37
38     trps->mq=tr_mq_new(trps);
39     if (trps->mq==NULL) {
40       /* failed to allocate mq */
41       talloc_free(trps);
42       return NULL;
43     }
44
45     trps->rtable=NULL;
46     if (trps_init_rtable(trps) != TRP_SUCCESS) {
47       /* failed to allocate rtable */
48       talloc_free(trps);
49       return NULL;
50     }
51
52     talloc_set_destructor((void *)trps, trps_destructor);
53   }
54   return trps;
55 }
56
57 /* create a new route table, first discarding an old one if necessary */
58 TRP_RC trps_init_rtable(TRPS_INSTANCE *trps)
59 {
60   if (trps->rtable != NULL) {
61     trp_rtable_free(trps->rtable);
62     trps->rtable=NULL;
63   }
64
65   trps->rtable=trp_rtable_new();
66   if (trps->rtable==NULL) {
67     return TRP_NOMEM;
68   }
69   return TRP_SUCCESS;
70 }
71
72 void trps_clear_rtable(TRPS_INSTANCE *trps)
73 {
74   trp_rtable_clear(trps->rtable);
75 }
76
77 void trps_free (TRPS_INSTANCE *trps)
78 {
79   if (trps!=NULL)
80     talloc_free(trps);
81 }
82
83 TR_MQ_MSG *trps_mq_pop(TRPS_INSTANCE *trps)
84 {
85   return tr_mq_pop(trps->mq);
86 }
87
88 void trps_mq_add(TRPS_INSTANCE *trps, TR_MQ_MSG *msg)
89 {
90   tr_mq_add(trps->mq, msg);
91 }
92
93 unsigned int trps_get_connect_interval(TRPS_INSTANCE *trps)
94 {
95   return trps->connect_interval.tv_sec;
96 }
97
98 void trps_set_connect_interval(TRPS_INSTANCE *trps, unsigned int interval)
99 {
100   trps->connect_interval.tv_sec=interval;
101   trps->connect_interval.tv_usec=0;
102 }
103
104 unsigned int trps_get_update_interval(TRPS_INSTANCE *trps)
105 {
106   return trps->update_interval.tv_sec;
107 }
108
109 void trps_set_update_interval(TRPS_INSTANCE *trps, unsigned int interval)
110 {
111   trps->update_interval.tv_sec=interval;
112   trps->update_interval.tv_usec=0;
113 }
114
115 unsigned int trps_get_sweep_interval(TRPS_INSTANCE *trps)
116 {
117   return trps->sweep_interval.tv_sec;
118 }
119
120 void trps_set_sweep_interval(TRPS_INSTANCE *trps, unsigned int interval)
121 {
122   trps->sweep_interval.tv_sec=interval;
123   trps->sweep_interval.tv_usec=0;
124 }
125
126 void trps_set_ptable(TRPS_INSTANCE *trps, TRP_PTABLE *ptable)
127 {
128   if (trps->ptable!=NULL)
129     trp_ptable_free(trps->ptable);
130   trps->ptable=ptable;
131 }
132
133 void trps_set_peer_status_callback(TRPS_INSTANCE *trps, void (*cb)(TRP_PEER *, void *), void *cookie)
134 {
135   TRP_PTABLE_ITER *iter=NULL;
136   TRP_PEER *peer=NULL;
137   if (trps->ptable==NULL)
138     return;
139
140   iter=trp_ptable_iter_new(NULL);
141   for (peer=trp_ptable_iter_first(iter, trps->ptable); peer!=NULL; peer=trp_ptable_iter_next(iter))
142     trp_peer_set_conn_status_cb(peer, cb, cookie);
143   trp_ptable_iter_free(iter);
144 }
145
146 TRPC_INSTANCE *trps_find_trpc(TRPS_INSTANCE *trps, TRP_PEER *peer)
147 {
148   TRPC_INSTANCE *cur=NULL;
149   TR_NAME *name=NULL;
150   TR_NAME *peer_servicename=trp_peer_get_servicename(peer);
151
152   for (cur=trps->trpc; cur!=NULL; cur=trpc_get_next(cur)) {
153     name=trpc_get_gssname(cur);
154     if ((name!=NULL) && (0==tr_name_cmp(peer_servicename, name))) {
155       break;
156     }
157   }
158   return cur;
159 }
160
161 void trps_add_connection(TRPS_INSTANCE *trps, TRP_CONNECTION *new)
162 {
163   if (trps->conn==NULL)
164     trps->conn=new;
165   else
166     trp_connection_append(trps->conn, new);
167
168   talloc_steal(trps, new);
169 }
170
171 /* ok to call more than once; guarantees connection no longer in the list.
172  * Caller is responsible for freeing the removed element afterwards.  */
173 void trps_remove_connection(TRPS_INSTANCE *trps, TRP_CONNECTION *remove)
174 {
175   trps->conn=trp_connection_remove(trps->conn, remove);
176 }
177
178 void trps_add_trpc(TRPS_INSTANCE *trps, TRPC_INSTANCE *trpc)
179 {
180   if (trps->trpc==NULL)
181     trps->trpc=trpc;
182   else
183     trpc_append(trps->trpc, trpc);
184
185   talloc_steal(trps, trpc);
186 }
187
188 /* ok to call more than once; guarantees trpc no longer in the list.
189  * Caller is responsible for freeing the removed element afterwards.  */
190 void trps_remove_trpc(TRPS_INSTANCE *trps, TRPC_INSTANCE *remove)
191 {
192   trps->trpc=trpc_remove(trps->trpc, remove);
193 }
194
195 TRP_RC trps_send_msg(TRPS_INSTANCE *trps, TRP_PEER *peer, const char *msg)
196 {
197   TALLOC_CTX *tmp_ctx=talloc_new(NULL);
198   TR_MQ_MSG *mq_msg=NULL;
199   char *msg_dup=NULL;
200   TRP_RC rc=TRP_ERROR;
201   TRPC_INSTANCE *trpc=NULL;
202
203   /* get the connection for this peer */
204   trpc=trps_find_trpc(trps, peer);
205   /* instead, let's let that happen and then clear the queue when an attempt to
206    * connect fails */
207   if (trpc==NULL) {
208     tr_warning("trps_send_msg: skipping message queued for missing TRP client entry.");
209   } else {
210     mq_msg=tr_mq_msg_new(tmp_ctx, TR_MQMSG_TRPC_SEND, TR_MQ_PRIO_NORMAL);
211     msg_dup=talloc_strdup(mq_msg, msg); /* get local copy in mq_msg context */
212     tr_mq_msg_set_payload(mq_msg, msg_dup, NULL); /* no need for a free() func */
213     trpc_mq_add(trpc, mq_msg);
214     rc=TRP_SUCCESS;
215   }
216   talloc_free(tmp_ctx);
217   return rc;
218 }
219
220 static int trps_listen (TRPS_INSTANCE *trps, int port) 
221 {
222   int rc = 0;
223   int conn = -1;
224   int optval = 1;
225
226   union {
227     struct sockaddr_storage storage;
228     struct sockaddr_in in4;
229   } addr;
230
231   struct sockaddr_in *saddr = (struct sockaddr_in *) &addr.in4;
232
233   saddr->sin_port = htons (port);
234   saddr->sin_family = AF_INET;
235   saddr->sin_addr.s_addr = INADDR_ANY;
236
237   if (0 > (conn = socket (AF_INET, SOCK_STREAM, 0)))
238     return conn;
239
240   setsockopt(conn, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
241
242   if (0 > (rc = bind (conn, (struct sockaddr *) saddr, sizeof(struct sockaddr_in))))
243     return rc;
244
245   if (0 > (rc = listen(conn, 512)))
246     return rc;
247
248   tr_debug("trps_listen: TRP Server listening on port %d", port);
249   return conn;
250 }
251
252 /* get the currently selected route if available */
253 TRP_ROUTE *trps_get_route(TRPS_INSTANCE *trps, TR_NAME *comm, TR_NAME *realm, TR_NAME *peer)
254 {
255   return trp_rtable_get_entry(trps->rtable, comm, realm, peer);
256 }
257
258 TRP_ROUTE *trps_get_selected_route(TRPS_INSTANCE *trps, TR_NAME *comm, TR_NAME *realm)
259 {
260   tr_debug("trps_get_selected_route: entered. trps=%p, comm=%p, realm=%p", trps, comm, realm);
261   return trp_rtable_get_selected_entry(trps->rtable, comm, realm);
262 }
263
264 /* copy the result if you want to keep it */
265 TR_NAME *trps_get_next_hop(TRPS_INSTANCE *trps, TR_NAME *comm, TR_NAME *realm)
266 {
267   TRP_ROUTE *route=trps_get_selected_route(trps, comm, realm);
268   if (route==NULL)
269     return NULL;
270
271   return trp_route_get_next_hop(route);
272 }
273
274
275 /* mark a route as retracted */
276 static void trps_retract_route(TRPS_INSTANCE *trps, TRP_ROUTE *entry)
277 {
278   trp_route_set_metric(entry, TRP_METRIC_INFINITY);
279   trp_route_set_triggered(entry, 1);
280 }
281
282 /* is this route retracted? */
283 static int trps_route_retracted(TRPS_INSTANCE *trps, TRP_ROUTE *entry)
284 {
285   return (trp_metric_is_infinite(trp_route_get_metric(entry)));
286 }
287
288 static TRP_RC trps_read_message(TRPS_INSTANCE *trps, TRP_CONNECTION *conn, TR_MSG **msg)
289 {
290   int err=0;
291   char *buf=NULL;
292   size_t buflen = 0;
293   TRP_PEER *peer=NULL; /* entry in the peer table */
294   TR_NAME *conn_peer=NULL; /* name from the TRP_CONN, which comes from the gss context */
295
296   tr_debug("trps_read_message: started");
297   if (err = gsscon_read_encrypted_token(trp_connection_get_fd(conn),
298                                        *(trp_connection_get_gssctx(conn)), 
299                                        &buf,
300                                        &buflen)) {
301     tr_debug("trps_read_message: error");
302     if (buf)
303       free(buf);
304     return TRP_ERROR;
305   }
306
307   tr_debug("trps_read_message: message received, %u bytes.", (unsigned) buflen);
308   tr_debug("trps_read_message: %.*s", buflen, buf);
309
310   *msg=tr_msg_decode(buf, buflen);
311   free(buf);
312   if (*msg==NULL)
313     return TRP_NOPARSE;
314
315   conn_peer=trp_connection_get_peer(conn);
316   if (conn_peer==NULL) {
317     tr_err("trps_read_message: connection has no peer name");
318     return TRP_ERROR;
319   }
320
321   peer=trps_get_peer_by_gssname(trps, conn_peer);
322   if (peer==NULL) {
323     tr_err("trps_read_message: could not find peer with gssname=%s", trp_connection_get_gssname(conn));
324     return TRP_ERROR;
325   }
326
327   /* verify we received a message we support, otherwise drop it now */
328   switch (tr_msg_get_msg_type(*msg)) {
329   case TRP_UPDATE:
330     trp_upd_set_peer(tr_msg_get_trp_upd(*msg), tr_dup_name(conn_peer));
331     trp_upd_set_next_hop(tr_msg_get_trp_upd(*msg), trp_peer_get_server(peer), 0); /* TODO: 0 should be the configured TID port */
332     break;
333
334   case TRP_REQUEST:
335     trp_req_set_peer(tr_msg_get_trp_req(*msg), tr_dup_name(conn_peer));
336     break;
337
338   default:
339     tr_debug("trps_read_message: received unsupported message from %.*s", conn_peer->len, conn_peer->buf);
340     tr_msg_free_decoded(*msg);
341     *msg=NULL;
342     return TRP_UNSUPPORTED;
343   }
344   
345   return TRP_SUCCESS;
346 }
347
348 int trps_get_listener(TRPS_INSTANCE *trps,
349                       TRPS_MSG_FUNC msg_handler,
350                       TRP_AUTH_FUNC auth_handler,
351                       const char *hostname,
352                       unsigned int port,
353                       void *cookie)
354 {
355   int listen = -1;
356
357   if (0 > (listen = trps_listen(trps, port))) {
358     char errbuf[256];
359     if (0 == strerror_r(errno, errbuf, 256)) {
360       tr_debug("trps_get_listener: Error opening port %d: %s.", port, errbuf);
361     } else {
362       tr_debug("trps_get_listener: Unknown error openining port %d.", port);
363     }
364   } 
365
366   if (listen > 0) {
367     /* opening port succeeded */
368     tr_debug("trps_get_listener: Opened port %d.", port);
369     
370     /* make this socket non-blocking */
371     if (0 != fcntl(listen, F_SETFL, O_NONBLOCK)) {
372       tr_debug("trps_get_listener: Error setting O_NONBLOCK.");
373       close(listen);
374       listen=-1;
375     }
376   }
377
378   if (listen > 0) {
379     /* store the caller's request handler & cookie */
380     trps->msg_handler = msg_handler;
381     trps->auth_handler = auth_handler;
382     trps->hostname = talloc_strdup(trps, hostname);
383     trps->port = port;
384     trps->cookie = cookie;
385   }
386
387   return listen;
388 }
389
390 TRP_RC trps_authorize_connection(TRPS_INSTANCE *trps, TRP_CONNECTION *conn)
391 {
392   /* try to establish a GSS context */
393   if (0!=trp_connection_auth(conn, trps->auth_handler, trps->cookie)) {
394     tr_notice("trps_authorize_connection: failed to authorize connection");
395     trp_connection_close(conn);
396     return TRP_ERROR;
397   }
398   tr_notice("trps_authorize_connection: authorized connection");
399   return TRP_SUCCESS;
400 }
401
402 void trps_handle_connection(TRPS_INSTANCE *trps, TRP_CONNECTION *conn)
403 {
404   TR_MSG *msg=NULL;
405   TRP_RC rc=TRP_ERROR;
406
407   /* loop as long as the connection exists */
408   while (trp_connection_get_status(conn)==TRP_CONNECTION_UP) {
409     rc=trps_read_message(trps, conn, &msg);
410     switch(rc) {
411     case TRP_SUCCESS:
412       trps->msg_handler(trps, conn, msg); /* send the TR_MSG off to the callback */
413       break;
414
415     case TRP_ERROR:
416       trp_connection_close(conn);
417       break;
418
419     default:
420       tr_debug("trps_handle_connection: trps_read_message failed (%d)", rc);
421     }
422   }
423
424   tr_debug("trps_handle_connection: connection closed.");
425 }
426
427 static TRP_RC trps_validate_update(TRPS_INSTANCE *trps, TRP_UPD *upd)
428 {
429   if (upd==NULL) {
430     tr_notice("trps_validate_update: null TRP update.");
431     return TRP_BADARG;
432   }
433
434   if (trp_upd_get_inforec(upd)==NULL) {
435     tr_notice("trps_validate_update: received TRP update with no info records.");
436     return TRP_ERROR;
437   }
438
439   if (trp_upd_get_peer(upd)==NULL) {
440     tr_notice("trps_validate_update: received TRP update without origin peer information.");
441     return TRP_ERROR;
442   }
443   
444   return TRP_SUCCESS;
445 }
446
447 /* ensure that the update could be accepted if feasible */
448 static TRP_RC trps_validate_inforec(TRPS_INSTANCE *trps, TRP_INFOREC *rec)
449 {
450   switch(trp_inforec_get_type(rec)) {
451   case TRP_INFOREC_TYPE_ROUTE:
452     if ((trp_inforec_get_comm(rec)==NULL)
453        || (trp_inforec_get_realm(rec)==NULL)
454        || (trp_inforec_get_trust_router(rec)==NULL)
455        || (trp_inforec_get_next_hop(rec)==NULL)) {
456       tr_debug("trps_validate_inforec: missing record info.");
457       return TRP_ERROR;
458     }
459
460     /* check for valid metric */
461     if (trp_metric_is_invalid(trp_inforec_get_metric(rec))) {
462       tr_debug("trps_validate_inforec: invalid metric (%u).", trp_inforec_get_metric(rec));
463       return TRP_ERROR;
464     }
465
466     /* check for valid interval */
467     if (trp_inforec_get_interval(rec)==TRP_INTERVAL_INVALID) {
468       tr_debug("trps_validate_inforec: invalid interval.");
469       return TRP_ERROR;
470     }
471     break;
472
473   default:
474     tr_notice("trps_validate_inforec: unsupported record type.");
475     return TRP_UNSUPPORTED;
476   }
477
478   return TRP_SUCCESS;
479 }
480
481 /* link cost to a peer */
482 static unsigned int trps_cost(TRPS_INSTANCE *trps, TR_NAME *peer)
483 {
484   return 1;
485 }
486
487 static unsigned int trps_advertised_metric(TRPS_INSTANCE *trps, TR_NAME *comm, TR_NAME *realm, TR_NAME *peer)
488 {
489   TRP_ROUTE *entry=trp_rtable_get_entry(trps->rtable, comm, realm, peer);
490   if (entry==NULL)
491     return TRP_METRIC_INFINITY;
492   return trp_route_get_metric(entry) + trps_cost(trps, peer);
493 }
494
495 static int trps_check_feasibility(TRPS_INSTANCE *trps, TRP_INFOREC *rec)
496 {
497   unsigned int rec_metric=trp_inforec_get_metric(rec);
498   unsigned int new_metric=0;
499   unsigned int current_metric=0;
500   TR_NAME *next_hop=NULL;
501
502   /* we check these in the validation stage, but just in case... */
503   if (trp_metric_is_invalid(rec_metric))
504     return 0;
505
506   /* retractions (aka infinite metrics) are always feasible */
507   if (trp_metric_is_infinite(rec_metric))
508     return 1;
509
510   /* updates from our current next hop are always feasible*/
511   next_hop=trps_get_next_hop(trps,
512                              trp_inforec_get_comm(rec),
513                              trp_inforec_get_realm(rec));;
514   if ((next_hop!=NULL)
515      && (0==tr_name_cmp(next_hop,trp_inforec_get_next_hop(rec)))) {
516     return 1;
517   }
518     
519
520   /* compare the existing metric we advertise to what we would advertise
521    * if we accept this update */
522   current_metric=trps_advertised_metric(trps,
523                                         trp_inforec_get_comm(rec),
524                                         trp_inforec_get_realm(rec),
525                                         trp_inforec_get_next_hop(rec));
526   new_metric=rec_metric + trps_cost(trps, trp_inforec_get_next_hop(rec));
527   if (new_metric <= current_metric)
528     return 1;
529   else
530     return 0;
531 }
532
533 /* uses memory pointed to by *ts, also returns that value. On error, its contents are {0,0} */
534 static struct timespec *trps_compute_expiry(TRPS_INSTANCE *trps, unsigned int interval, struct timespec *ts)
535 {
536   const unsigned int small_factor=3; /* how many intervals we wait before expiring */
537   if (0!=clock_gettime(CLOCK_REALTIME, ts)) {
538     tr_err("trps_compute_expiry: could not read realtime clock.");
539     ts->tv_sec=0;
540     ts->tv_nsec=0;
541   }
542   ts->tv_sec += small_factor*interval;
543   return ts;
544 }
545
546 static TRP_RC trps_accept_update(TRPS_INSTANCE *trps, TRP_UPD *upd, TRP_INFOREC *rec)
547 {
548   TRP_ROUTE *entry=NULL;
549
550   entry=trp_rtable_get_entry(trps->rtable,
551                              trp_inforec_get_comm(rec),
552                              trp_inforec_get_realm(rec),
553                              trp_inforec_get_next_hop(rec));
554   if (entry==NULL) {
555     entry=trp_route_new(NULL);
556     if (entry==NULL) {
557       tr_err("trps_accept_update: unable to allocate new entry.");
558       return TRP_NOMEM;
559     }
560
561     trp_route_set_comm(entry, trp_inforec_dup_comm(rec));
562     trp_route_set_realm(entry, trp_inforec_dup_realm(rec));
563     trp_route_set_peer(entry, trp_upd_dup_peer(upd));
564     trp_route_set_trust_router(entry, trp_inforec_dup_trust_router(rec));
565     trp_route_set_next_hop(entry, trp_inforec_dup_next_hop(rec));
566     /* TODO: pass next hop port (now defaults to TID_PORT) --jlr */
567     if ((trp_route_get_comm(entry)==NULL)
568        ||(trp_route_get_realm(entry)==NULL)
569        ||(trp_route_get_peer(entry)==NULL)
570        ||(trp_route_get_trust_router(entry)==NULL)
571        ||(trp_route_get_next_hop(entry)==NULL)) {
572       /* at least one field could not be allocated */
573       tr_err("trps_accept_update: unable to allocate all fields for entry.");
574       trp_route_free(entry);
575       return TRP_NOMEM;
576     }
577     trp_rtable_add(trps->rtable, entry);
578   }
579
580   /* We now have an entry in the table, whether it's new or not. Update metric and expiry, unless
581    * the metric is infinity. An infinite metric can only occur here if we just retracted an existing
582    * route (we never accept retractions as new routes), so there is no risk of leaving the expiry
583    * time unset on a new route entry. */
584   tr_debug("trps_accept_update: accepting route update.");
585   trp_route_set_metric(entry, trp_inforec_get_metric(rec));
586   trp_route_set_interval(entry, trp_inforec_get_interval(rec));
587
588   /* check whether the trust router has changed */
589   if (0!=tr_name_cmp(trp_route_get_trust_router(entry),
590                      trp_inforec_get_trust_router(rec))) {
591     /* The name changed. Set this route as triggered. */
592     tr_debug("trps_accept_update: trust router for route changed.");
593     trp_route_set_triggered(entry, 1);
594     trp_route_set_trust_router(entry, trp_inforec_dup_trust_router(rec)); /* frees old name */
595   }
596   if (!trps_route_retracted(trps, entry)) {
597     tr_debug("trps_accept_update: route not retracted, setting expiry timer.");
598     trp_route_set_expiry(entry, trps_compute_expiry(trps,
599                                                      trp_route_get_interval(entry),
600                                                      trp_route_get_expiry(entry)));
601   }
602   return TRP_SUCCESS;
603 }
604
605 static TRP_RC trps_handle_update(TRPS_INSTANCE *trps, TRP_UPD *upd)
606 {
607   unsigned int feas=0;
608   TRP_INFOREC *rec=NULL;
609   TRP_ROUTE *route=NULL;
610
611   if (trps_validate_update(trps, upd) != TRP_SUCCESS) {
612     tr_notice("trps_handle_update: received invalid TRP update.");
613     return TRP_ERROR;
614   }
615
616   for (rec=trp_upd_get_inforec(upd); rec!=NULL; rec=trp_inforec_get_next(rec)) {
617     /* validate/sanity check the record update */
618     if (trps_validate_inforec(trps, rec) != TRP_SUCCESS) {
619       tr_notice("trps_handle_update: invalid record in TRP update, discarding entire update.");
620       return TRP_ERROR;
621     }
622   }
623
624   for (rec=trp_upd_get_inforec(upd); rec!=NULL; rec=trp_inforec_get_next(rec)) {
625     /* determine feasibility */
626     feas=trps_check_feasibility(trps, rec);
627     tr_debug("trps_handle_update: record feasibility=%d", feas);
628
629     /* do we have an existing route? */
630     route=trps_get_route(trps,
631                          trp_inforec_get_comm(rec),
632                          trp_inforec_get_realm(rec),
633                          trp_upd_get_peer(upd));
634     if (route!=NULL) {
635       /* there was a route table entry already */
636       tr_debug("trps_handle_updates: route entry already exists.");
637       if (feas) {
638         /* Update is feasible. Accept it. */
639         trps_accept_update(trps, upd, rec);
640       } else {
641         /* Update is infeasible. Ignore it unless the trust router has changed. */
642         if (0!=tr_name_cmp(trp_route_get_trust_router(route),
643                            trp_inforec_get_trust_router(rec))) {
644           /* the trust router associated with the route has changed, treat update as a retraction */
645           trps_retract_route(trps, route);
646         }
647       }
648     } else {
649       /* No existing route table entry. Ignore it unless it is feasible and not a retraction. */
650       tr_debug("trps_handle_update: no route entry exists yet.");
651       if (feas && trp_metric_is_finite(trp_inforec_get_metric(rec)))
652         trps_accept_update(trps, upd, rec);
653     }
654   }
655   return TRP_SUCCESS;
656 }
657
658 static TRP_RC trps_validate_request(TRPS_INSTANCE *trps, TRP_REQ *req)
659 {
660   if (req==NULL) {
661     tr_notice("trps_validate_request: null TRP request.");
662     return TRP_BADARG;
663   }
664
665   if (trp_req_get_comm(req)==NULL) {
666     tr_notice("trps_validate_request: received TRP request with null community.");
667     return TRP_ERROR;
668   }
669   
670   if (trp_req_get_realm(req)==NULL) {
671     tr_notice("trps_validate_request: received TRP request with null realm.");
672     return TRP_ERROR;
673   }
674   
675   if (trp_req_get_peer(req)==NULL) {
676     tr_notice("trps_validate_request: received TRP request without origin peer information.");
677     return TRP_ERROR;
678   }
679   
680   return TRP_SUCCESS;
681 }
682
683 /* choose the best route to comm/realm, optionally excluding routes to a particular peer */
684 static TRP_ROUTE *trps_find_best_route(TRPS_INSTANCE *trps,
685                                         TR_NAME *comm,
686                                         TR_NAME *realm,
687                                         TR_NAME *exclude_peer)
688 {
689   TRP_ROUTE **entry=NULL;
690   TRP_ROUTE *best=NULL;
691   size_t n_entry=0;
692   unsigned int kk=0;
693   unsigned int kk_min=0;
694   unsigned int min_metric=TRP_METRIC_INFINITY;
695
696   entry=trp_rtable_get_realm_entries(trps->rtable, comm, realm, &n_entry);
697   for (kk=0; kk<n_entry; kk++) {
698     if (trp_route_get_metric(entry[kk]) < min_metric) {
699       if ((exclude_peer==NULL) || (0!=tr_name_cmp(trp_route_get_peer(entry[kk]),
700                                                   exclude_peer))) {
701         kk_min=kk;
702         min_metric=trp_route_get_metric(entry[kk]);
703       } 
704     }
705   }
706   if (trp_metric_is_finite(min_metric))
707     best=entry[kk_min];
708   
709   talloc_free(entry);
710   return best;
711 }
712
713 /* TODO: think this through more carefully. At least ought to add hysteresis
714  * to avoid flapping between routers or routes. */
715 TRP_RC trps_update_active_routes(TRPS_INSTANCE *trps)
716 {
717   size_t n_comm=0, ii=0;
718   TR_NAME **comm=trp_rtable_get_comms(trps->rtable, &n_comm);
719   size_t n_realm=0, jj=0;
720   TR_NAME **realm=NULL;
721   TRP_ROUTE *best_route=NULL, *cur_route=NULL;
722   unsigned int best_metric=0, cur_metric=0;
723
724   for (ii=0; ii<n_comm; ii++) {
725     realm=trp_rtable_get_comm_realms(trps->rtable, comm[ii], &n_realm);
726     for (jj=0; jj<n_realm; jj++) {
727       best_route=trps_find_best_route(trps, comm[ii], realm[jj], NULL);
728       if (best_route==NULL)
729         best_metric=TRP_METRIC_INFINITY;
730       else
731         best_metric=trp_route_get_metric(best_route);
732
733       cur_route=trps_get_selected_route(trps, comm[ii], realm[jj]);
734       if (cur_route!=NULL) {
735         cur_metric=trp_route_get_metric(cur_route);
736         if ((best_metric < cur_metric) && (trp_metric_is_finite(best_metric))) {
737           /* The new route has a lower metric than the previous, and is finite. Accept. */
738           trp_route_set_selected(cur_route, 0);
739           trp_route_set_selected(best_route, 1);
740         } else if (!trp_metric_is_finite(cur_metric)) /* rejects infinite or invalid metrics */
741           trp_route_set_selected(cur_route, 0);
742       } else if (trp_metric_is_finite(best_metric)) {
743         trp_route_set_selected(best_route, 1);
744       }
745     }
746     if (realm!=NULL)
747       talloc_free(realm);
748     realm=NULL; n_realm=0;
749   }
750   if (comm!=NULL)
751     talloc_free(comm);
752   comm=NULL; n_comm=0;
753
754   return TRP_SUCCESS;
755 }
756
757 /* true if curtime >= expiry */
758 static int trps_expired(struct timespec *expiry, struct timespec *curtime)
759 {
760   return ((curtime->tv_sec > expiry->tv_sec)
761          || ((curtime->tv_sec == expiry->tv_sec)
762             &&(curtime->tv_nsec > expiry->tv_nsec)));
763 }
764
765 /* Sweep for expired routes. For each expired route, if its metric is infinite, the route is flushed.
766  * If its metric is finite, the metric is set to infinite and the route's expiration time is updated. */
767 TRP_RC trps_sweep_routes(TRPS_INSTANCE *trps)
768 {
769   struct timespec sweep_time={0,0};
770   TRP_ROUTE **entry=NULL;
771   size_t n_entry=0;
772   size_t ii=0;
773
774   /* use a single time for the entire sweep */
775   if (0!=clock_gettime(CLOCK_REALTIME, &sweep_time)) {
776     tr_err("trps_sweep_routes: could not read realtime clock.");
777     sweep_time.tv_sec=0;
778     sweep_time.tv_nsec=0;
779     return TRP_ERROR;
780   }
781
782   entry=trp_rtable_get_entries(trps->rtable, &n_entry); /* must talloc_free *entry */
783
784   /* loop over the entries */
785   for (ii=0; ii<n_entry; ii++) {
786     if (!trp_route_is_local(entry[ii]) && trps_expired(trp_route_get_expiry(entry[ii]), &sweep_time)) {
787       tr_debug("trps_sweep_routes: route expired.");
788       if (!trp_metric_is_finite(trp_route_get_metric(entry[ii]))) {
789         /* flush route */
790         tr_debug("trps_sweep_routes: metric was infinity, flushing route.");
791         trp_rtable_remove(trps->rtable, entry[ii]); /* entry[ii] is no longer valid */
792         entry[ii]=NULL;
793       } else {
794         /* set metric to infinity and reset timer */
795         tr_debug("trps_sweep_routes: setting metric to infinity and resetting expiry.");
796         trp_route_set_metric(entry[ii], TRP_METRIC_INFINITY);
797         trp_route_set_expiry(entry[ii], trps_compute_expiry(trps,
798                                                              trp_route_get_interval(entry[ii]),
799                                                              trp_route_get_expiry(entry[ii])));
800       }
801     }
802   }
803
804   talloc_free(entry);
805   return TRP_SUCCESS;
806 }
807
808 /* select the correct route to comm/realm to be announced to peer */
809 static TRP_ROUTE *trps_select_realm_update(TRPS_INSTANCE *trps, TR_NAME *comm, TR_NAME *realm, TR_NAME *peer_gssname)
810 {
811   TRP_ROUTE *route;
812
813   /* Take the currently selected route unless it is through the peer we're sending the update to.
814    * I.e., enforce the split horizon rule. */
815   route=trp_rtable_get_selected_entry(trps->rtable, comm, realm);
816   if (route==NULL) {
817     /* No selected route, this should only happen if the only route has been retracted,
818      * in which case we do not want to advertise it. */
819     return NULL;
820   }
821   tr_debug("trps_select_realm_update: %s vs %s", peer_gssname->buf,
822            trp_route_get_peer(route)->buf);
823   if (0==tr_name_cmp(peer_gssname, trp_route_get_peer(route))) {
824     tr_debug("trps_select_realm_update: matched, finding alternate route");
825     /* the selected entry goes through the peer we're reporting to, choose an alternate */
826     route=trps_find_best_route(trps, comm, realm, peer_gssname);
827     if ((route==NULL) || (!trp_metric_is_finite(trp_route_get_metric(route))))
828       return NULL; /* don't advertise a nonexistent or retracted route */
829   }
830   return route;
831 }
832
833 /* returns an array of pointers to updates (*not* an array of updates). Returns number of entries
834  * via n_update parameter. (The allocated space will generally be larger than required, see note in
835  * the code.) If triggered is set, sends only triggered updates. */
836 static TRP_ROUTE **trps_select_updates_for_peer(TALLOC_CTX *memctx,
837                                                  TRPS_INSTANCE *trps,
838                                                  TR_NAME *peer_gssname,
839                                                  int triggered,
840                                                  size_t *n_update)
841 {
842   size_t n_comm=0;
843   TR_NAME **comm=trp_rtable_get_comms(trps->rtable, &n_comm);
844   TR_NAME **realm=NULL;
845   size_t n_realm=0;
846   size_t ii=0, jj=0;
847   TRP_ROUTE *best=NULL;
848   TRP_ROUTE **result=NULL;
849   size_t n_used=0;
850
851   /* Need to allocate space for the results. For simplicity, we just allocate a block
852    * with space for every route table entry to be returned. This is guaranteed to be large
853    * enough. If the routing table gets very large, this may be wasteful, but that seems
854    * unlikely to be significant in the near future. */
855   result=talloc_array(memctx, TRP_ROUTE *, trp_rtable_size(trps->rtable));
856   if (result==NULL) {
857     talloc_free(comm);
858     *n_update=0;
859     return NULL;
860   }
861   
862   for (ii=0; ii<n_comm; ii++) {
863     realm=trp_rtable_get_comm_realms(trps->rtable, comm[ii], &n_realm);
864     for (jj=0; jj<n_realm; jj++) {
865       best=trps_select_realm_update(trps, comm[ii], realm[jj], peer_gssname);
866       /* If we found a route, add it to the list. If triggered!=0, then only
867        * add triggered routes. */
868       if ((best!=NULL) && ((!triggered) || trp_route_is_triggered(best)))
869         result[n_used++]=best;
870     }
871     if (realm!=NULL)
872       talloc_free(realm);
873     realm=NULL;
874     n_realm=0;
875   }
876   if (comm!=NULL)
877     talloc_free(comm);
878
879   *n_update=n_used;
880   return result;
881 }
882
883 /* add metrics */
884 static unsigned int trps_metric_add(unsigned int m1, unsigned int m2)
885 {
886   if (trp_metric_is_invalid(m1) || trp_metric_is_invalid(m2))
887     return TRP_METRIC_INVALID;
888
889   if (trp_metric_is_infinite(m1) || trp_metric_is_infinite(m2))
890     return TRP_METRIC_INFINITY;
891
892   if (trp_metric_is_finite(m1+m2))
893     return m1+m2;
894   else
895     return TRP_METRIC_INFINITY;
896 }
897
898 /* convert an rentry into a new trp update info record */
899 static TRP_INFOREC *trps_route_to_inforec(TALLOC_CTX *mem_ctx, TRPS_INSTANCE *trps, TRP_ROUTE *route)
900 {
901   TRP_INFOREC *rec=trp_inforec_new(mem_ctx, TRP_INFOREC_TYPE_ROUTE);
902   unsigned int linkcost=0;
903
904   if (rec!=NULL) {
905     if (trp_route_is_local(route))
906       linkcost=0;
907     else {
908       linkcost=trp_peer_get_linkcost(trps_get_peer_by_gssname(trps,
909                                                               trp_route_get_peer(route)));
910     }
911
912     /* Note that we leave the next hop empty since the recipient fills that in.
913      * This is where we add the link cost (currently always 1) to the next peer. */
914     if ((trp_inforec_set_comm(rec, trp_route_dup_comm(route)) != TRP_SUCCESS)
915        ||(trp_inforec_set_realm(rec, trp_route_dup_realm(route)) != TRP_SUCCESS)
916        ||(trp_inforec_set_trust_router(rec, trp_route_dup_trust_router(route)) != TRP_SUCCESS)
917        ||(trp_inforec_set_metric(rec,
918                                  trps_metric_add(trp_route_get_metric(route),
919                                                  linkcost)) != TRP_SUCCESS)
920        ||(trp_inforec_set_interval(rec, trps_get_update_interval(trps)) != TRP_SUCCESS)) {
921       tr_err("trps_route_to_inforec: error creating route update.");
922       talloc_free(rec);
923       rec=NULL;
924     }
925   }
926   return rec;
927 }
928
929 /* all routes to a single peer, unless comm/realm are specified (both or neither must be NULL) */
930 static TRP_RC trps_update_one_peer(TRPS_INSTANCE *trps,
931                                    TRP_PEER *peer,
932                                    TRP_UPDATE_TYPE update_type,
933                                    TR_NAME *comm,
934                                    TR_NAME *realm)
935 {
936   TALLOC_CTX *tmp_ctx=talloc_new(NULL);
937   TR_MSG msg; /* not a pointer! */
938   TRP_UPD *upd=NULL;
939   TRP_ROUTE **update_list=NULL;
940   TRP_INFOREC *rec=NULL;
941   size_t n_updates=0, ii=0;
942   char *encoded=NULL;
943   TRP_RC rc=TRP_ERROR;
944   TR_NAME *peer_label=trp_peer_get_label(peer);
945
946   switch (update_type) {
947   case TRP_UPDATE_TRIGGERED:
948     tr_debug("trps_update_one_peer: preparing triggered route update for %.*s",
949              peer_label->len, peer_label->buf);
950     break;
951   case TRP_UPDATE_SCHEDULED:
952     tr_debug("trps_update_one_peer: preparing scheduled route update for %.*s",
953              peer_label->len, peer_label->buf);
954     break;
955   case TRP_UPDATE_REQUESTED:
956     tr_debug("trps_update_one_peer: preparing requested route update for %.*s",
957              peer_label->len, peer_label->buf);
958   }
959
960   /* do not fill in peer, recipient does that */
961   if ((comm==NULL) && (realm==NULL)) {
962     /* do all realms */
963     update_list=trps_select_updates_for_peer(tmp_ctx,
964                                              trps,
965                                              peer_label,
966                                              update_type==TRP_UPDATE_TRIGGERED,
967                                             &n_updates);
968   } else if ((comm!=NULL) && (realm!=NULL)) {
969     /* a single community/realm was requested */
970     update_list=talloc(tmp_ctx, TRP_ROUTE *);
971     if (update_list==NULL) {
972       tr_err("trps_update_one_peer: could not allocate update_list.");
973       rc=TRP_NOMEM;
974       goto cleanup;
975     }
976     *update_list=trps_select_realm_update(trps, comm, realm, peer_label);
977     if (*update_list==NULL) {
978       /* we have no actual update to send back, MUST send a retraction */
979       tr_debug("trps_update_one_peer: community/realm without route requested, sending mandatory retraction.");
980       *update_list=trp_route_new(update_list);
981       trp_route_set_comm(*update_list, tr_dup_name(comm));
982       trp_route_set_realm(*update_list, tr_dup_name(realm));
983       trp_route_set_peer(*update_list, tr_new_name(""));
984       trp_route_set_metric(*update_list, TRP_METRIC_INFINITY);
985       trp_route_set_trust_router(*update_list, tr_new_name(""));
986       trp_route_set_next_hop(*update_list, tr_new_name(""));
987     }
988     n_updates=1;
989   } else {
990     tr_err("trps_update_one_peer: error: only comm or realm was specified.");
991     rc=TRP_ERROR;
992     goto cleanup;
993   }
994   if ((n_updates>0) && (update_list!=NULL)) {
995     tr_debug("trps_update_one_peer: sending %u update records.", (unsigned int)n_updates);
996     upd=trp_upd_new(tmp_ctx);
997
998     for (ii=0; ii<n_updates; ii++) {
999       rec=trps_route_to_inforec(tmp_ctx, trps, update_list[ii]);
1000       if (rec==NULL) {
1001         tr_err("trps_update_one_peer: could not create all update records.");
1002         rc=TRP_ERROR;
1003         goto cleanup;
1004       }
1005       trp_upd_add_inforec(upd, rec);
1006     }
1007     talloc_free(update_list);
1008     update_list=NULL;
1009
1010     /* now encode the update message */
1011     tr_msg_set_trp_upd(&msg, upd);
1012     encoded=tr_msg_encode(&msg);
1013     if (encoded==NULL) {
1014       tr_err("trps_update_one_peer: error encoding update.");
1015       rc=TRP_ERROR;
1016       goto cleanup;
1017     }
1018
1019     tr_debug("trps_update_one_peer: adding message to queue.");
1020     if (trps_send_msg(trps, peer, encoded) != TRP_SUCCESS)
1021       tr_err("trps_update_one_peer: error queueing update.");
1022     else
1023       tr_debug("trps_update_one_peer: update queued successfully.");
1024
1025     tr_msg_free_encoded(encoded);
1026     encoded=NULL;
1027     trp_upd_free(upd);
1028     upd=NULL;
1029   } else if (n_updates==0)
1030     tr_debug("trps_update_one_peer: no updates for %.*s", peer_label->len, peer_label->buf);
1031
1032   rc=TRP_SUCCESS;
1033
1034 cleanup:
1035   talloc_free(tmp_ctx);
1036   return rc;
1037 }
1038
1039 /* all routes to all peers */
1040 TRP_RC trps_update(TRPS_INSTANCE *trps, TRP_UPDATE_TYPE update_type)
1041 {
1042   TALLOC_CTX *tmp_ctx=talloc_new(NULL);
1043   TRP_PTABLE_ITER *iter=trp_ptable_iter_new(tmp_ctx);
1044   TRP_PEER *peer=NULL;
1045   TRP_RC rc=TRP_SUCCESS;
1046
1047   if (trps->ptable==NULL)
1048     return TRP_SUCCESS; /* no peers, nothing to do */
1049
1050   if (iter==NULL) {
1051     tr_err("trps_update: failed to allocate peer table iterator.");
1052     talloc_free(tmp_ctx);
1053     return TRP_NOMEM;
1054   }
1055
1056   for (peer=trp_ptable_iter_first(iter, trps->ptable);
1057        peer!=NULL && rc==TRP_SUCCESS;
1058        peer=trp_ptable_iter_next(iter))
1059   {
1060     if (!trps_peer_connected(trps, peer)) {
1061       TR_NAME *peer_label=trp_peer_get_label(peer);
1062       tr_debug("trps_update: no TRP connection to %.*s, skipping.",
1063                peer_label->len, peer_label->buf);
1064       continue;
1065     }
1066     rc=trps_update_one_peer(trps, peer, update_type, NULL, NULL);
1067   }
1068
1069   tr_debug("trps_update: rc=%u after attempting update.", rc);
1070   trp_ptable_iter_free(iter);
1071   trp_rtable_clear_triggered(trps->rtable); /* don't re-send triggered updates */
1072   talloc_free(tmp_ctx);
1073   return rc;
1074 }        
1075
1076 TRP_RC trps_add_route(TRPS_INSTANCE *trps, TRP_ROUTE *route)
1077 {
1078   trp_rtable_add(trps->rtable, route); /* should return status */
1079   return TRP_SUCCESS; 
1080 }
1081
1082 /* steals the peer object */
1083 TRP_RC trps_add_peer(TRPS_INSTANCE *trps, TRP_PEER *peer)
1084 {
1085   if (trps->ptable==NULL) {
1086     trps->ptable=trp_ptable_new(trps);
1087     if (trps->ptable==NULL)
1088       return TRP_NOMEM;
1089   }
1090   return trp_ptable_add(trps->ptable, peer);
1091 }
1092
1093 TRP_PEER *trps_get_peer_by_gssname(TRPS_INSTANCE *trps, TR_NAME *gssname)
1094 {
1095   if (trps->ptable==NULL)
1096     return NULL;
1097
1098   return trp_ptable_find_gss_name(trps->ptable, gssname);
1099 }
1100
1101 TRP_PEER *trps_get_peer_by_servicename(TRPS_INSTANCE *trps, TR_NAME *servicename)
1102 {
1103   if (trps->ptable==NULL)
1104     return NULL;
1105
1106   return trp_ptable_find_servicename(trps->ptable, servicename);
1107 }
1108
1109 int trps_peer_connected(TRPS_INSTANCE *trps, TRP_PEER *peer)
1110 {
1111   TRPC_INSTANCE *trpc=trps_find_trpc(trps, peer);
1112   if (trpc==NULL)
1113     return 0;
1114
1115   if (trpc_get_status(trpc)==TRP_CONNECTION_UP)
1116     return 1;
1117   else
1118     return 0;
1119 }
1120
1121
1122 static TRP_RC trps_handle_request(TRPS_INSTANCE *trps, TRP_REQ *req)
1123 {
1124   TR_NAME *comm=NULL;
1125   TR_NAME *realm=NULL;
1126
1127   tr_debug("trps_handle_request: handling TRP request.");
1128
1129   if (trps_validate_request(trps, req) != TRP_SUCCESS) {
1130     tr_notice("trps_handle_request: received invalid TRP request.");
1131     return TRP_ERROR;
1132   }
1133
1134   if (!trp_req_is_wildcard(req)) {
1135     comm=trp_req_get_comm(req);
1136     realm=trp_req_get_realm(req);
1137     tr_debug("trps_handle_request: route for %.*s/%.*s requested.",
1138              comm->len, comm->buf, realm->len, realm->buf);
1139   } else {
1140     tr_debug("trps_handle_request: all routes requested.");
1141     /* leave comm/realm NULL */
1142   }
1143   return trps_update_one_peer(trps,
1144                               trps_get_peer_by_gssname(trps, trp_req_get_peer(req)),
1145                               TRP_UPDATE_REQUESTED,
1146                               comm,
1147                               realm);
1148 }
1149
1150
1151 TRP_RC trps_handle_tr_msg(TRPS_INSTANCE *trps, TR_MSG *tr_msg)
1152 {
1153   TRP_RC rc=TRP_ERROR;
1154
1155   switch (tr_msg_get_msg_type(tr_msg)) {
1156   case TRP_UPDATE:
1157     rc=trps_handle_update(trps, tr_msg_get_trp_upd(tr_msg));
1158     if (rc==TRP_SUCCESS) {
1159       rc=trps_update_active_routes(trps);
1160       trps_update(trps, TRP_UPDATE_TRIGGERED); /* send any triggered routes */
1161     }
1162     return rc;
1163
1164   case TRP_REQUEST:
1165     rc=trps_handle_request(trps, tr_msg_get_trp_req(tr_msg));
1166     return rc;
1167
1168   default:
1169     /* unknown error or one we don't care about (e.g., TID messages) */
1170     return TRP_ERROR;
1171   }
1172 }
1173
1174 /* send wildcard route request to a peer */
1175 TRP_RC trps_wildcard_route_req(TRPS_INSTANCE *trps, TR_NAME *peer_servicename)
1176 {
1177   TALLOC_CTX *tmp_ctx=talloc_new(NULL);
1178   TRP_PEER *peer=trps_get_peer_by_servicename(trps, peer_servicename);
1179   TR_MSG msg; /* not a pointer */
1180   TRP_REQ *req=trp_req_new(tmp_ctx);
1181   char *encoded=NULL;
1182   TRP_RC rc=TRP_ERROR;
1183
1184   if (peer==NULL) {
1185     tr_err("trps_wildcard_route_req: unknown peer (%.*s).", peer_servicename->len, peer_servicename->buf);
1186     rc=TRP_BADARG;
1187     goto cleanup;
1188   }
1189   if ((req==NULL) || (trp_req_make_wildcard(req)!=TRP_SUCCESS)) {
1190     tr_err("trps_wildcard_route_req: unable to create wildcard TRP request.");
1191     rc=TRP_NOMEM;
1192     goto cleanup;
1193   }
1194
1195   tr_msg_set_trp_req(&msg, req);
1196   encoded=tr_msg_encode(&msg);
1197   if (encoded==NULL) {
1198     tr_err("trps_wildcard_route_req: error encoding wildcard TRP request.");
1199     rc=TRP_ERROR;
1200     goto cleanup;
1201   }
1202
1203   tr_debug("trps_wildcard_route_req: adding message to queue.");
1204   if (trps_send_msg(trps, peer, encoded) != TRP_SUCCESS) {
1205     tr_err("trps_wildcard_route_req: error queueing request.");
1206     rc=TRP_ERROR;
1207   } else {
1208     tr_debug("trps_wildcard_route_req: request queued successfully.");
1209     rc=TRP_SUCCESS;
1210   }
1211
1212 cleanup:
1213   if (encoded!=NULL)
1214     tr_msg_free_encoded(encoded);
1215   if (req!=NULL)
1216     trp_req_free(req);
1217
1218   talloc_free(tmp_ctx);
1219   return rc;
1220 }