summaryrefslogtreecommitdiff
path: root/src/rfc3315.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/rfc3315.c')
-rw-r--r--src/rfc3315.c408
1 files changed, 369 insertions, 39 deletions
diff --git a/src/rfc3315.c b/src/rfc3315.c
index 364277d..70768a2 100644
--- a/src/rfc3315.c
+++ b/src/rfc3315.c
@@ -24,7 +24,7 @@ struct state {
int clid_len, iaid, ia_type, interface, hostname_auth, lease_allocate;
char *client_hostname, *hostname, *domain, *send_domain;
struct dhcp_context *context;
- struct in6_addr *link_address, *fallback, *ll_addr, *ula_addr;
+ struct in6_addr *link_address, peer_address, *fallback, *ll_addr, *ula_addr;
unsigned int xid, fqdn_flags;
char *iface_name;
void *packet_options, *end;
@@ -57,11 +57,16 @@ static void mark_config_used(struct dhcp_context *context, struct in6_addr *addr
static int check_address(struct state *state, struct in6_addr *addr);
static void add_address(struct state *state, struct dhcp_context *context, unsigned int lease_time, void *ia_option,
unsigned int *min_time, struct in6_addr *addr, time_t now);
-static void update_leases(struct state *state, struct dhcp_context *context, struct in6_addr *addr, unsigned int lease_time, time_t now);
+static struct dhcp_lease *update_leases(struct state *state, struct dhcp_context *context, struct dhcp_lease *lease,
+ struct in6_addr *addr, int prefix_len, unsigned int lease_time, time_t now);
static int add_local_addrs(struct dhcp_context *context);
static struct dhcp_netid *add_options(struct state *state, int do_refresh);
+static int check_context(struct state *state);
static void calculate_times(struct dhcp_context *context, unsigned int *min_time, unsigned int *valid_timep,
unsigned int *preferred_timep, unsigned int lease_time);
+#ifdef HAVE_PD
+static int handle_pd(int msg_type, struct state *state, struct dhcp_netid *tags, void *ia_option, void *ia_end, time_t now);
+#endif
#define opt6_len(opt) ((int)(opt6_uint(opt, -2, 2)))
#define opt6_type(opt) (opt6_uint(opt, -4, 2))
@@ -99,7 +104,9 @@ unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *if
state.mac_len = 0;
state.tags = NULL;
state.link_address = NULL;
-
+
+ memcpy(&state.peer_address, client_addr, IN6ADDRSZ);
+
if (dhcp6_maybe_relay(&state, daemon->dhcp_packet.iov_base, sz, client_addr,
IN6_IS_ADDR_MULTICAST(client_addr), now))
return msg_type == DHCP6RELAYFORW ? DHCPV6_SERVER_PORT : DHCPV6_CLIENT_PORT;
@@ -150,7 +157,7 @@ static int dhcp6_maybe_relay(struct state *state, void *inbuff, size_t sz,
state->context = c;
}
- if (!state->context)
+ if (0 && !state->context) /* TODO */
{
inet_ntop(AF_INET6, state->link_address, daemon->addrbuff, ADDRSTRLEN);
my_syslog(MS_DHCP | LOG_WARNING,
@@ -160,7 +167,7 @@ static int dhcp6_maybe_relay(struct state *state, void *inbuff, size_t sz,
}
}
- if (!state->context)
+ if (0 && !state->context) /* TODO */
{
my_syslog(MS_DHCP | LOG_WARNING,
_("no address range available for DHCPv6 request via %s"), state->iface_name);
@@ -220,6 +227,7 @@ static int dhcp6_maybe_relay(struct state *state, void *inbuff, size_t sz,
/* the packet data is unaligned, copy to aligned storage */
memcpy(&align, inbuff + 2, IN6ADDRSZ);
state->link_address = &align;
+ memcpy(&state->peer_address, inbuff + 18, IN6ADDRSZ);
/* zero is_unicast since that is now known to refer to the
relayed packet, not the original sent by the client */
if (!dhcp6_maybe_relay(state, opt6_ptr(opt, 0), opt6_len(opt), client_addr, 0, now))
@@ -314,7 +322,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_
return 0;
/* server-id must match except for SOLICIT and CONFIRM messages */
- if (msg_type != DHCP6SOLICIT && msg_type != DHCP6CONFIRM && msg_type != DHCP6IREQ &&
+ if (msg_type != DHCP6SOLICIT && msg_type != DHCP6CONFIRM && msg_type != DHCP6IREQ && msg_type != DHCP6REBIND &&
(!(opt = opt6_find(state->packet_options, state->end, OPTION6_SERVER_ID, 1)) ||
opt6_len(opt) != daemon->duid_len ||
memcmp(opt6_ptr(opt, 0), daemon->duid, daemon->duid_len) != 0))
@@ -630,7 +638,18 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_
if (!check_ia(state, opt, &ia_end, &ia_option))
continue;
+
+#ifdef HAVE_PD
+ if (handle_pd(msg_type, state, tagif, ia_option, ia_end, now))
+ {
+ address_assigned = 1;
+ continue;
+ }
+#endif
+ if (!check_context(state))
+ return 0;
+
/* reset USED bits in contexts - one address per prefix per IAID */
for (c = state->context; c; c = c->current)
c->flags &= ~CONTEXT_USED;
@@ -831,19 +850,29 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_
unsigned int min_time = 0xffffffff;
int t1cntr;
- if (!check_ia(state, opt, &ia_end, &ia_option))
- continue;
-
- if (!ia_option)
- {
- /* If we get a request with a IA_*A without addresses, treat it exactly like
- a SOLICT with rapid commit set. */
- save_counter(start);
- goto request_no_address;
- }
+ if (!check_ia(state, opt, &ia_end, &ia_option))
+ continue;
+
+ if (!ia_option)
+ {
+ /* If we get a request with a IA_*A without addresses, treat it exactly like
+ a SOLICT with rapid commit set. */
+ save_counter(start);
+ goto request_no_address;
+ }
+
+#ifdef HAVE_PD
+ if (handle_pd(msg_type, state, tagif, ia_option, ia_end, now))
+ {
+ address_assigned = 1;
+ continue;
+ }
+#endif
+ if (!check_context(state))
+ return 0;
o = build_ia(state, &t1cntr);
-
+
for (; ia_option; ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24))
{
struct in6_addr *req_addr = opt6_ptr(ia_option, 0);
@@ -929,11 +958,12 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_
case DHCP6RENEW:
+ case DHCP6REBIND:
{
/* set reply message type */
*outmsgtypep = DHCP6REPLY;
- log6_quiet(state, "DHCPRENEW", NULL, NULL);
+ log6_quiet(state, msg_type == DHCP6RENEW ? "DHCPRENEW" : "DHCPREBIND", NULL, NULL);
for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end))
{
@@ -944,6 +974,14 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_
if (!check_ia(state, opt, &ia_end, &ia_option))
continue;
+#ifdef HAVE_PD
+ if (handle_pd(msg_type, state, tagif, ia_option, ia_end, now))
+ continue;
+#endif
+
+ if (!check_context(state))
+ return 0;
+
o = build_ia(state, &t1cntr);
iacntr = save_counter(-1);
@@ -1044,6 +1082,9 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_
log6_quiet(state, "DHCPCONFIRM", NULL, NULL);
+ if (!check_context(state))
+ return 0;
+
for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end))
{
void *ia_option, *ia_end;
@@ -1107,12 +1148,20 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_
*outmsgtypep = DHCP6REPLY;
log6_quiet(state, "DHCPRELEASE", NULL, NULL);
-
+
+ if (!check_context(state))
+ return 0;
+
for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end))
{
void *ia_option, *ia_end;
int made_ia = 0;
-
+
+#ifdef HAVE_PD
+ if (handle_pd(msg_type, state, tagif, ia_option, ia_end, now))
+ continue;
+#endif
+
for (check_ia(state, opt, &ia_end, &ia_option);
ia_option;
ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24))
@@ -1169,6 +1218,9 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_
*outmsgtypep = DHCP6REPLY;
log6_quiet(state, "DHCPDECLINE", NULL, NULL);
+
+ if (!check_context(state))
+ return 0;
for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end))
{
@@ -1525,20 +1577,20 @@ static int check_ia(struct state *state, void *opt, void **endp, void **ia_optio
{
state->ia_type = opt6_type(opt);
*ia_option = NULL;
-
- if (state->ia_type != OPTION6_IA_NA && state->ia_type != OPTION6_IA_TA)
- return 0;
+ *endp = opt6_ptr(opt, opt6_len(opt));
- if (state->ia_type == OPTION6_IA_NA && opt6_len(opt) < 12)
- return 0;
-
- if (state->ia_type == OPTION6_IA_TA && opt6_len(opt) < 4)
+ if (state->ia_type == OPTION6_IA_NA && opt6_len(opt) >= 12)
+ *ia_option = opt6_find(opt6_ptr(opt, 12), *endp, OPTION6_IAADDR, 24);
+ else if (state->ia_type == OPTION6_IA_TA && opt6_len(opt) >= 4)
+ *ia_option = opt6_find(opt6_ptr(opt, 4), *endp, OPTION6_IAADDR, 24);
+#ifdef HAVE_PD
+ else if (state->ia_type == OPTION6_IA_PD && opt6_len(opt) >= 12)
+ *ia_option = opt6_find(opt6_ptr(opt, 12), *endp, OPTION6_IAPREFIX, 25);
+#endif
+ else
return 0;
- *endp = opt6_ptr(opt, opt6_len(opt));
state->iaid = opt6_uint(opt, 0, 4);
- *ia_option = opt6_find(opt6_ptr(opt, state->ia_type == OPTION6_IA_NA ? 12 : 4), *endp, OPTION6_IAADDR, 24);
-
return 1;
}
@@ -1550,7 +1602,7 @@ static int build_ia(struct state *state, int *t1cntr)
put_opt6_long(state->iaid);
*t1cntr = 0;
- if (state->ia_type == OPTION6_IA_NA)
+ if (state->ia_type == OPTION6_IA_NA || state->ia_type == OPTION6_IA_PD)
{
/* save pointer */
*t1cntr = save_counter(-1);
@@ -1617,10 +1669,12 @@ static void add_address(struct state *state, struct dhcp_context *context, unsig
end_opt6(o);
+ lease = lease6_find_by_addr(addr, 128, 0);
+
if (state->lease_allocate)
- update_leases(state, context, addr, valid_time, now);
+ lease = update_leases(state, context, lease, addr, 0, valid_time, now);
- if ((lease = lease6_find_by_addr(addr, 128, 0)))
+ if (lease)
lease->flags |= LEASE_USED;
/* get tags from context if we've not used it before */
@@ -1735,8 +1789,9 @@ static void calculate_times(struct dhcp_context *context, unsigned int *min_time
}
/* deprecate (preferred == 0) which configured, or when local address
- is deprecated */
- if ((context->flags & CONTEXT_DEPRECATE) || context->preferred == 0)
+ is deprecated, not valid for PD */
+ if (!(context->flags & CONTEXT_PREFIX) &&
+ ((context->flags & CONTEXT_DEPRECATE) || context->preferred == 0))
preferred_time = 0;
if (preferred_time != 0 && preferred_time < *min_time)
@@ -1749,9 +1804,9 @@ static void calculate_times(struct dhcp_context *context, unsigned int *min_time
*preferred_timep = preferred_time;
}
-static void update_leases(struct state *state, struct dhcp_context *context, struct in6_addr *addr, unsigned int lease_time, time_t now)
+static struct dhcp_lease *update_leases(struct state *state, struct dhcp_context *context, struct dhcp_lease *lease,
+ struct in6_addr *addr, int prefix_len, unsigned int lease_time, time_t now)
{
- struct dhcp_lease *lease = lease6_find_by_addr(addr, 128, 0);
#ifdef HAVE_SCRIPT
struct dhcp_netid *tagif = run_tag_if(state->tags);
#endif
@@ -1759,7 +1814,7 @@ static void update_leases(struct state *state, struct dhcp_context *context, str
(void)context;
if (!lease)
- lease = lease6_allocate(addr, state->ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA);
+ lease = lease6_allocate(addr, prefix_len, state->ia_type == OPTION6_IA_NA ? LEASE_NA : (state->ia_type == OPTION6_IA_TA ? LEASE_TA : LEASE_PD));
if (lease)
{
@@ -1832,7 +1887,7 @@ static void update_leases(struct state *state, struct dhcp_context *context, str
inet_ntop(AF_INET6, state->link_address, daemon->addrbuff, ADDRSTRLEN);
lease_add_extradata(lease, (unsigned char *)daemon->addrbuff, state->link_address ? strlen(daemon->addrbuff) : 0, 0);
-
+
if ((class_opt = opt6_find(state->packet_options, state->end, OPTION6_USER_CLASS, 2)))
{
void *enc_opt, *enc_end = opt6_ptr(class_opt, opt6_len(class_opt));
@@ -1843,6 +1898,8 @@ static void update_leases(struct state *state, struct dhcp_context *context, str
#endif
}
+
+ return lease;
}
@@ -1995,6 +2052,25 @@ static void *opt6_next(void *opts, void *end)
return opts + opt_len;
}
+static int check_context(struct state *state)
+{
+ if (state->context)
+ return 1;
+
+ if (state->link_address)
+ {
+ inet_ntop(AF_INET6, state->link_address, daemon->addrbuff, ADDRSTRLEN);
+ my_syslog(MS_DHCP | LOG_WARNING,
+ _("no address range available for DHCPv6 request from relay at %s"),
+ daemon->addrbuff);
+ }
+ else
+ my_syslog(MS_DHCP | LOG_WARNING,
+ _("no address range available for DHCPv6 request via %s"), state->iface_name);
+
+ return 0;
+}
+
static unsigned int opt6_uint(unsigned char *opt, int offset, int size)
{
/* this worries about unaligned data and byte order */
@@ -2134,4 +2210,258 @@ unsigned short relay_reply6(struct sockaddr_in6 *peer, ssize_t sz, char *arrival
return 0;
}
+
+#ifdef HAVE_PD
+static int handle_pd(int msg_type, struct state *state, struct dhcp_netid *tags, void *ia_option, void *ia_end, time_t now)
+{
+ unsigned int min_time = 0xffffffff;
+ int o, o1;
+ int t1cntr, iacntr;
+
+ if (state->ia_type != OPTION6_IA_PD)
+ return 0;
+
+ switch (msg_type)
+ {
+ case DHCP6SOLICIT:
+ {
+ /* Go through hints, and fullfil all we can. If no prefixes found that way (or no hints)
+ then find exactly one prefix */
+
+ struct dhcp_context *context;
+ struct dhcp_lease *lease = lease6_find_by_client(NULL, LEASE_PD, state->clid, state->clid_len, state->iaid);
+ unsigned int preferred_time = 0, valid_time = 0;
+
+ /* for (; ia_option; ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAPREFIX, 25))
+ {
+ int prefix_len = opt6_uint(ia_option, 8, 1);
+ struct in6_addr *req_addr = opt6_ptr(ia_option, 9);
+ }
+ */
+
+ /* Client has existing lease, offer that prefix again, else find available prefix */
+ if (!lease || !(context = find_prefix_context(tags, &lease->addr6, lease->prefix_len)))
+ for (context = daemon->prefix_contexts; context; context = context->next)
+ if (match_netid(context->filter, tags, 1) && !lease6_find_by_prefix(&context->start6, context->prefix))
+ break;
+
+ /* Now context is non-NULL is we have a prefix, and lease is non-NULL is it's already leased to us. */
+ o = build_ia(state, &t1cntr);
+
+ if (context)
+ {
+ o1 = new_opt6(OPTION6_IAPREFIX);
+ calculate_times(context, &min_time, &valid_time, &preferred_time, context->lease_time);
+ put_opt6_long(preferred_time);
+ put_opt6_long(valid_time);
+ put_opt6_char(context->prefix);
+ put_opt6(&context->start6, IN6ADDRSZ);
+ end_opt6(o1);
+ o1 = new_opt6(OPTION6_STATUS_CODE);
+ put_opt6_short(DHCP6SUCCESS);
+ put_opt6_string(_("success"));
+ end_opt6(o1);
+ log6_quiet(state, "DHCPADVERTISE", &context->start6, "<prefix>");
+ }
+ else
+ {
+ o1 = new_opt6(OPTION6_STATUS_CODE);
+ put_opt6_short(DHCP6NOPREFIX);
+ put_opt6_string(_("no prefixes available"));
+ end_opt6(o1);
+ log6_packet(state, "DHCPADVERTISE", NULL, "no prefixes available");
+ }
+
+ end_ia(t1cntr, min_time, 0);
+ end_opt6(o);
+
+ break;
+ }
+
+ case DHCP6REQUEST:
+ /* Ensure requests are fulfilable and create leases */
+ o = build_ia(state, &t1cntr);
+
+ for (; ia_option; ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAPREFIX, 25))
+ {
+ int prefix_len = opt6_uint(ia_option, 8, 1);
+ struct in6_addr *req_addr = opt6_ptr(ia_option, 9);
+ unsigned int preferred_time = opt6_uint(ia_option, 0, 4);
+ unsigned int valid_time = opt6_uint(ia_option, 4, 4);
+ struct dhcp_context *context = find_prefix_context(tags, req_addr, prefix_len);
+ struct dhcp_lease *lease = lease6_find_by_prefix(req_addr, prefix_len);
+
+ if (!context)
+ {
+ /* Prefix not in pool for this client*/
+ o1 = new_opt6(OPTION6_STATUS_CODE);
+ put_opt6_short(DHCP6UNSPEC);
+ put_opt6_string(_("prefix unavailable"));
+ end_opt6(o1);
+ log6_packet(state, "DHCPREPLY", req_addr, "prefix unavailable");
+ }
+ else if (lease && (lease->iaid != state->iaid ||
+ lease->clid_len != state->clid_len ||
+ memcmp(lease->clid, state->clid, state->clid_len) != 0))
+ {
+ /* Prefix leased to another DUID/IAID */
+ o1 = new_opt6(OPTION6_STATUS_CODE);
+ put_opt6_short(DHCP6UNSPEC);
+ put_opt6_string(_("prefix in use"));
+ end_opt6(o1);
+ log6_packet(state, "DHCPREPLY", req_addr, "prefix in use");
+ }
+ else
+ {
+ o1 = new_opt6(OPTION6_IAPREFIX);
+ calculate_times(context, &min_time, &valid_time, &preferred_time, context->lease_time);
+ put_opt6_long(preferred_time);
+ put_opt6_long(valid_time);
+ put_opt6_char(prefix_len);
+ put_opt6(req_addr, sizeof(*req_addr));
+ end_opt6(o1);
+ o1 = new_opt6(OPTION6_STATUS_CODE);
+ put_opt6_short(DHCP6SUCCESS);
+ put_opt6_string(_("success"));
+ end_opt6(o1);
+ if ((lease = update_leases(state, context, lease, req_addr, prefix_len, valid_time, now)))
+ lease->client_addr = state->peer_address;
+ log6_quiet(state, "DHCPREPLY", req_addr, "<prefix>");
+ }
+ }
+
+ end_ia(t1cntr, min_time, 0);
+ end_opt6(o);
+
+ break;
+
+ case DHCP6RENEW:
+ case DHCP6REBIND:
+
+ o = build_ia(state, &t1cntr);
+ iacntr = save_counter(-1);
+
+ for (; ia_option; ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAPREFIX, 25))
+ {
+ struct dhcp_lease *lease = NULL;
+ struct in6_addr *req_addr = opt6_ptr(ia_option, 9);
+ unsigned int preferred_time = opt6_uint(ia_option, 0, 4);
+ unsigned int valid_time = opt6_uint(ia_option, 4, 4);
+ int prefix_len = opt6_uint(ia_option, 8, 1);
+ char *message = NULL;
+ struct dhcp_context *context = find_prefix_context(tags, req_addr, prefix_len);
+
+ /* May have more than one lease, find the correct one. */
+ while ((lease = lease6_find_by_client(lease, LEASE_PD, state->clid, state->clid_len, state->iaid)))
+ if (IN6_ARE_ADDR_EQUAL(&lease->addr6, req_addr) && prefix_len == lease->prefix_len)
+ break;
+
+ if (!lease)
+ {
+ /* If the server cannot find a client entry for the IA the server
+ returns the IA containing no addresses with a Status Code option set
+ to NoBinding in the Reply message. */
+ save_counter(iacntr);
+ t1cntr = 0;
+
+ log6_packet(state, "DHCPREPLY", req_addr, _("lease not found"));
+
+ o1 = new_opt6(OPTION6_STATUS_CODE);
+ put_opt6_short(DHCP6NOBINDING);
+ put_opt6_string(_("no binding found"));
+ end_opt6(o1);
+
+ preferred_time = valid_time = 0;
+ break;
+ }
+
+ if (context)
+ {
+ calculate_times(context, &min_time, &valid_time, &preferred_time, context->lease_time);
+
+ lease_set_expires(lease, valid_time, now);
+ /* Update MAC record in case it's new information. */
+ if (state->mac_len != 0)
+ lease_set_hwaddr(lease, state->mac, state->clid, state->mac_len, state->mac_type, state->clid_len, now, 0);
+
+ if (preferred_time == 0)
+ message = _("deprecated");
+ }
+ else
+ {
+ preferred_time = valid_time = 0;
+ message = _("address invalid");
+ }
+
+ if (message)
+ log6_packet(state, "DHCPREPLY", req_addr, message);
+ else
+ log6_quiet(state, "DHCPREPLY", req_addr, message);
+
+ o1 = new_opt6(OPTION6_IAPREFIX);
+ put_opt6_long(preferred_time);
+ put_opt6_long(valid_time);
+ put_opt6_char(context->prefix);
+ put_opt6(req_addr, sizeof(*req_addr));
+ end_opt6(o1);
+ }
+
+ end_ia(t1cntr, min_time, 1);
+ end_opt6(o);
+ break;
+
+ case DHCP6RELEASE:
+ {
+ int made_ia = 0;
+
+ for (; ia_option; ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAPREFIX, 25))
+ {
+ struct dhcp_lease *lease;
+
+ if ((lease = lease6_find(state->clid, state->clid_len, LEASE_PD, state->iaid, opt6_ptr(ia_option, 0))))
+ lease_prune(lease, now);
+ else
+ {
+ if (!made_ia)
+ {
+ o = new_opt6(state->ia_type);
+ put_opt6_long(state->iaid);
+ put_opt6_long(0);
+ put_opt6_long(0);
+ made_ia = 1;
+ }
+
+ o1 = new_opt6(OPTION6_IAPREFIX);
+ put_opt6_long(0);
+ put_opt6_long(0);
+ /* copy prefix-len and address */
+ put_opt6(opt6_ptr(ia_option, 8), IN6ADDRSZ+1);
+ end_opt6(o1);
+ }
+ }
+
+ if (made_ia)
+ {
+ o1 = new_opt6(OPTION6_STATUS_CODE);
+ put_opt6_short(DHCP6NOBINDING);
+ put_opt6_string(_("no binding found"));
+ end_opt6(o1);
+
+ end_opt6(o);
+ }
+ }
+
+ o1 = new_opt6(OPTION6_STATUS_CODE);
+ put_opt6_short(DHCP6SUCCESS);
+ put_opt6_string(_("release received"));
+ end_opt6(o1);
+
+ break;
+ }
+
+ return 1;
+
+}
+#endif
+
#endif