xref: /openbsd-src/usr.sbin/amd/amd/rpc_fwd.c (revision df69c215c7c66baf660f3f65414fd34796c96152)
1df930be7Sderaadt /*
2df930be7Sderaadt  * Copyright (c) 1989 Jan-Simon Pendry
3df930be7Sderaadt  * Copyright (c) 1989 Imperial College of Science, Technology & Medicine
4df930be7Sderaadt  * Copyright (c) 1989, 1993
5df930be7Sderaadt  *	The Regents of the University of California.  All rights reserved.
6df930be7Sderaadt  *
7df930be7Sderaadt  * This code is derived from software contributed to Berkeley by
8df930be7Sderaadt  * Jan-Simon Pendry at Imperial College, London.
9df930be7Sderaadt  *
10df930be7Sderaadt  * Redistribution and use in source and binary forms, with or without
11df930be7Sderaadt  * modification, are permitted provided that the following conditions
12df930be7Sderaadt  * are met:
13df930be7Sderaadt  * 1. Redistributions of source code must retain the above copyright
14df930be7Sderaadt  *    notice, this list of conditions and the following disclaimer.
15df930be7Sderaadt  * 2. Redistributions in binary form must reproduce the above copyright
16df930be7Sderaadt  *    notice, this list of conditions and the following disclaimer in the
17df930be7Sderaadt  *    documentation and/or other materials provided with the distribution.
1829295d1cSmillert  * 3. Neither the name of the University nor the names of its contributors
19df930be7Sderaadt  *    may be used to endorse or promote products derived from this software
20df930be7Sderaadt  *    without specific prior written permission.
21df930be7Sderaadt  *
22df930be7Sderaadt  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23df930be7Sderaadt  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24df930be7Sderaadt  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25df930be7Sderaadt  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26df930be7Sderaadt  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27df930be7Sderaadt  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28df930be7Sderaadt  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29df930be7Sderaadt  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30df930be7Sderaadt  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31df930be7Sderaadt  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32df930be7Sderaadt  * SUCH DAMAGE.
33df930be7Sderaadt  *
34df930be7Sderaadt  *	from: @(#)rpc_fwd.c	8.1 (Berkeley) 6/6/93
35*df69c215Sderaadt  *	$Id: rpc_fwd.c,v 1.11 2019/06/28 13:32:46 deraadt Exp $
36df930be7Sderaadt  */
37df930be7Sderaadt 
38df930be7Sderaadt /*
39df930be7Sderaadt  * RPC packet forwarding
40df930be7Sderaadt  */
41df930be7Sderaadt 
42df930be7Sderaadt #include "am.h"
43df930be7Sderaadt #include <sys/ioctl.h>
44df930be7Sderaadt 
45df930be7Sderaadt /*
46df930be7Sderaadt  * Note that the ID field in the external packet is only
47df930be7Sderaadt  * ever treated as a 32 bit opaque data object, so there
48df930be7Sderaadt  * is no need to convert to and from network byte ordering.
49df930be7Sderaadt  */
50df930be7Sderaadt 
51df930be7Sderaadt /*
52df930be7Sderaadt  * Each pending reply has an rpc_forward structure
53df930be7Sderaadt  * associated with it.  These have a 15 second lifespan.
54df930be7Sderaadt  * If a new structure is required, then an expired
55df930be7Sderaadt  * one will be re-allocated if available, otherwise a fresh
56df930be7Sderaadt  * one is allocated.  Whenever a reply is received the
57df930be7Sderaadt  * structure is discarded.
58df930be7Sderaadt  */
59df930be7Sderaadt typedef struct rpc_forward rpc_forward;
60df930be7Sderaadt struct rpc_forward {
61df930be7Sderaadt 	qelem	rf_q;		/* Linked list */
62df930be7Sderaadt 	time_t	rf_ttl;		/* Time to live */
63df930be7Sderaadt 	u_int	rf_xid;		/* Packet id */
64df930be7Sderaadt 	u_int	rf_oldid;	/* Original packet id */
65df930be7Sderaadt 	fwd_fun	rf_fwd;		/* Forwarding function */
665c8362e1Spvalchev 	void *	rf_ptr;
67df930be7Sderaadt 	struct sockaddr_in rf_sin;
68df930be7Sderaadt };
69df930be7Sderaadt 
70df930be7Sderaadt /*
71df930be7Sderaadt  * Head of list of pending replies
72df930be7Sderaadt  */
73df930be7Sderaadt extern qelem rpc_head;
74df930be7Sderaadt qelem rpc_head = { &rpc_head, &rpc_head };
75df930be7Sderaadt 
76df930be7Sderaadt static u_int xid;
77df930be7Sderaadt #define	XID_ALLOC()	(xid++)
78df930be7Sderaadt 
79df930be7Sderaadt #define	MAX_PACKET_SIZE	8192	/* Maximum UDP packet size */
80df930be7Sderaadt 
81df930be7Sderaadt int fwd_sock;
82df930be7Sderaadt 
83df930be7Sderaadt /*
84df930be7Sderaadt  * Allocate a rely structure
85df930be7Sderaadt  */
869ad2d6d5Spvalchev static rpc_forward *
fwd_alloc()879ad2d6d5Spvalchev fwd_alloc()
88df930be7Sderaadt {
89df930be7Sderaadt 	time_t now = clocktime();
90df930be7Sderaadt 	rpc_forward *p = 0, *p2;
91df930be7Sderaadt 
92df930be7Sderaadt #ifdef DEBUG
93df930be7Sderaadt 	/*dlog("fwd_alloca: rpc_head = %#x", rpc_head.q_forw);*/
94df930be7Sderaadt #endif /* DEBUG */
95df930be7Sderaadt 	/*
96df930be7Sderaadt 	 * First search for an existing expired one.
97df930be7Sderaadt 	 */
98df930be7Sderaadt 	ITER(p2, rpc_forward, &rpc_head) {
99df930be7Sderaadt 		if (p2->rf_ttl <= now) {
100df930be7Sderaadt 			p = p2;
101df930be7Sderaadt 			break;
102df930be7Sderaadt 		}
103df930be7Sderaadt 	}
104df930be7Sderaadt 
105df930be7Sderaadt 	/*
106df930be7Sderaadt 	 * If one couldn't be found then allocate
107df930be7Sderaadt 	 * a new structure and link it at the
108df930be7Sderaadt 	 * head of the list.
109df930be7Sderaadt 	 */
110df930be7Sderaadt 	if (p) {
111df930be7Sderaadt 		/*
112df930be7Sderaadt 		 * Call forwarding function to say that
113df930be7Sderaadt 		 * this message was junked.
114df930be7Sderaadt 		 */
115df930be7Sderaadt #ifdef DEBUG
116df930be7Sderaadt 		dlog("Re-using packet forwarding slot - id %#x", p->rf_xid);
117df930be7Sderaadt #endif /* DEBUG */
118df930be7Sderaadt 		if (p->rf_fwd)
119df930be7Sderaadt 			(*p->rf_fwd)(0, 0, 0, &p->rf_sin, p->rf_ptr, FALSE);
120df930be7Sderaadt 		rem_que(&p->rf_q);
121df930be7Sderaadt 	} else {
122df930be7Sderaadt 		p = ALLOC(rpc_forward);
123df930be7Sderaadt 	}
124df930be7Sderaadt 	ins_que(&p->rf_q, &rpc_head);
125df930be7Sderaadt 
126df930be7Sderaadt 	/*
127df930be7Sderaadt 	 * Set the time to live field
128df930be7Sderaadt 	 * Timeout in 43 seconds
129df930be7Sderaadt 	 */
130df930be7Sderaadt 	p->rf_ttl = now + 43;
131df930be7Sderaadt 
132df930be7Sderaadt #ifdef DEBUG
133df930be7Sderaadt 	/*dlog("fwd_alloca: rpc_head = %#x", rpc_head.q_forw);*/
134df930be7Sderaadt #endif /* DEBUG */
135df930be7Sderaadt 	return p;
136df930be7Sderaadt }
137df930be7Sderaadt 
138df930be7Sderaadt /*
139df930be7Sderaadt  * Free an allocated reply structure.
140df930be7Sderaadt  * First unlink it from the list, then
141df930be7Sderaadt  * discard it.
142df930be7Sderaadt  */
1439ad2d6d5Spvalchev static void
fwd_free(rpc_forward * p)1449ad2d6d5Spvalchev fwd_free(rpc_forward *p)
145df930be7Sderaadt {
146df930be7Sderaadt #ifdef DEBUG
147df930be7Sderaadt 	/*dlog("fwd_free: rpc_head = %#x", rpc_head.q_forw);*/
148df930be7Sderaadt #endif /* DEBUG */
149df930be7Sderaadt 	rem_que(&p->rf_q);
150df930be7Sderaadt #ifdef DEBUG
151df930be7Sderaadt 	/*dlog("fwd_free: rpc_head = %#x", rpc_head.q_forw);*/
152df930be7Sderaadt #endif /* DEBUG */
153819011edSguenther 	free(p);
154df930be7Sderaadt }
155df930be7Sderaadt 
156df930be7Sderaadt /*
157df930be7Sderaadt  * Initialise the RPC forwarder
158df930be7Sderaadt  */
fwd_init()159df930be7Sderaadt int fwd_init()
160df930be7Sderaadt {
161df930be7Sderaadt 	/*
162df930be7Sderaadt 	 * Create ping socket
163df930be7Sderaadt 	 */
1644f68b03bSguenther 	fwd_sock = socket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0);
165*df69c215Sderaadt 	if (fwd_sock == -1) {
166df930be7Sderaadt 		plog(XLOG_ERROR, "Unable to create RPC forwarding socket: %m");
167df930be7Sderaadt 		return errno;
168df930be7Sderaadt 	}
169df930be7Sderaadt 
170df930be7Sderaadt 	/*
171df930be7Sderaadt 	 * Some things we talk to require a priv port - so make one here
172df930be7Sderaadt 	 */
173*df69c215Sderaadt 	if (bind_resv_port(fwd_sock, (unsigned short *) 0) == -1)
174df930be7Sderaadt 		plog(XLOG_ERROR, "can't bind privileged port");
175df930be7Sderaadt 
176df930be7Sderaadt 	return 0;
177df930be7Sderaadt }
178df930be7Sderaadt 
179df930be7Sderaadt /*
180df930be7Sderaadt  * Locate a packet in the forwarding list
181df930be7Sderaadt  */
1829ad2d6d5Spvalchev static rpc_forward *
fwd_locate(u_int id)1839ad2d6d5Spvalchev fwd_locate(u_int id)
184df930be7Sderaadt {
185df930be7Sderaadt 	rpc_forward *p;
186df930be7Sderaadt 
187df930be7Sderaadt 	ITER(p, rpc_forward, &rpc_head) {
188df930be7Sderaadt 		if (p->rf_xid == id)
189df930be7Sderaadt 			return p;
190df930be7Sderaadt 	}
191df930be7Sderaadt 
192df930be7Sderaadt 	return 0;
193df930be7Sderaadt }
194df930be7Sderaadt 
195df930be7Sderaadt /*
196df930be7Sderaadt  * This is called to forward a packet to another
197df930be7Sderaadt  * RPC server.  The message id is changed and noted
198df930be7Sderaadt  * so that when a reply appears we can tie it up
199df930be7Sderaadt  * correctly.  Just matching the reply's source address
200df930be7Sderaadt  * would not work because it might come from a
201df930be7Sderaadt  * different address.
202df930be7Sderaadt  */
2039ad2d6d5Spvalchev int
fwd_packet(int type_id,void * pkt,int len,struct sockaddr_in * fwdto,struct sockaddr_in * replyto,void * i,fwd_fun cb)2045c8362e1Spvalchev fwd_packet(int type_id, void *pkt, int len, struct sockaddr_in *fwdto,
2055c8362e1Spvalchev     struct sockaddr_in *replyto, void *i, fwd_fun cb)
206df930be7Sderaadt {
207df930be7Sderaadt 	rpc_forward *p;
208df930be7Sderaadt 	u_int *pkt_int;
209df930be7Sderaadt 	int error;
210df930be7Sderaadt 
211df930be7Sderaadt 	if ((int)amd_state >= (int)Finishing)
212df930be7Sderaadt 		return ENOENT;
213df930be7Sderaadt 
214df930be7Sderaadt 	/*
215df930be7Sderaadt 	 * See if the type_id is fully specified.
216df930be7Sderaadt 	 * If so, then discard any old entries
217df930be7Sderaadt 	 * for this id.
218df930be7Sderaadt 	 * Otherwise make sure the type_id is
219df930be7Sderaadt 	 * fully qualified by allocating an id here.
220df930be7Sderaadt 	 */
221df930be7Sderaadt #ifdef DEBUG
222df930be7Sderaadt 	switch (type_id & RPC_XID_MASK) {
223df930be7Sderaadt 	case RPC_XID_PORTMAP: dlog("Sending PORTMAP request"); break;
224df930be7Sderaadt 	case RPC_XID_MOUNTD: dlog("Sending MOUNTD request %#x", type_id); break;
225df930be7Sderaadt 	case RPC_XID_NFSPING: dlog("Sending NFS ping"); break;
226df930be7Sderaadt 	default: dlog("UNKNOWN RPC XID"); break;
227df930be7Sderaadt 	}
228df930be7Sderaadt #endif /* DEBUG */
229df930be7Sderaadt 
230df930be7Sderaadt 	if (type_id & ~RPC_XID_MASK) {
231df930be7Sderaadt #ifdef DEBUG
232df930be7Sderaadt 		/*dlog("Fully qualified rpc type provided");*/
233df930be7Sderaadt #endif /* DEBUG */
234df930be7Sderaadt 		p = fwd_locate(type_id);
235df930be7Sderaadt 		if (p) {
236df930be7Sderaadt #ifdef DEBUG
237df930be7Sderaadt 			dlog("Discarding earlier rpc fwd handle");
238df930be7Sderaadt #endif /* DEBUG */
239df930be7Sderaadt 			fwd_free(p);
240df930be7Sderaadt 		}
241df930be7Sderaadt 	} else {
242df930be7Sderaadt #ifdef DEBUG
243df930be7Sderaadt 		dlog("Allocating a new xid...");
244df930be7Sderaadt #endif /* DEBUG */
245df930be7Sderaadt 		type_id = MK_RPC_XID(type_id, XID_ALLOC());
246df930be7Sderaadt 	}
247df930be7Sderaadt 
248df930be7Sderaadt 	p = fwd_alloc();
249df930be7Sderaadt 	if (!p)
250df930be7Sderaadt 		return ENOBUFS;
251df930be7Sderaadt 
252df930be7Sderaadt 	error = 0;
253df930be7Sderaadt 
254df930be7Sderaadt 	pkt_int = (u_int *) pkt;
255df930be7Sderaadt 
256df930be7Sderaadt 	/*
257df930be7Sderaadt 	 * Get the original packet id
258df930be7Sderaadt 	 */
259df930be7Sderaadt 	p->rf_oldid = *pkt_int;
260df930be7Sderaadt 
261df930be7Sderaadt 	/*
262df930be7Sderaadt 	 * Replace with newly allocated id
263df930be7Sderaadt 	 */
264df930be7Sderaadt 	p->rf_xid = *pkt_int = type_id;
265df930be7Sderaadt 
266df930be7Sderaadt 	/*
267df930be7Sderaadt 	 * The sendto may fail if, for example, the route
268df930be7Sderaadt 	 * to a remote host is lost because an intermediate
269df930be7Sderaadt 	 * gateway has gone down.  Important to fill in the
270df930be7Sderaadt 	 * rest of "p" otherwise nasty things happen later...
271df930be7Sderaadt 	 */
272df930be7Sderaadt #ifdef DEBUG
273df930be7Sderaadt 	{ char dq[20];
2745bd67209Sderaadt 
2755bd67209Sderaadt 	dlog("Sending packet id %#x to %s.%d", p->rf_xid,
2765bd67209Sderaadt 	    inet_dquad(dq, sizeof(dq), fwdto->sin_addr.s_addr),
2775bd67209Sderaadt 	    ntohs(fwdto->sin_port));
278df930be7Sderaadt 	}
279df930be7Sderaadt #endif /* DEBUG */
280df930be7Sderaadt 	if (sendto(fwd_sock, (char *) pkt, len, 0,
281*df69c215Sderaadt 	    (struct sockaddr *) fwdto, sizeof(*fwdto)) == -1)
282df930be7Sderaadt 		error = errno;
283df930be7Sderaadt 
284df930be7Sderaadt 	/*
285df930be7Sderaadt 	 * Save callback function and return address
286df930be7Sderaadt 	 */
287df930be7Sderaadt 	p->rf_fwd = cb;
288df930be7Sderaadt 	if (replyto)
289df930be7Sderaadt 		p->rf_sin = *replyto;
290df930be7Sderaadt 	else
29126d0c865Sguenther 		bzero(&p->rf_sin, sizeof(p->rf_sin));
292df930be7Sderaadt 	p->rf_ptr = i;
293df930be7Sderaadt 
294df930be7Sderaadt 	return error;
295df930be7Sderaadt }
296df930be7Sderaadt 
297df930be7Sderaadt /*
298df930be7Sderaadt  * Called when some data arrives on the forwarding socket
299df930be7Sderaadt  */
3009ad2d6d5Spvalchev void
fwd_reply()3019ad2d6d5Spvalchev fwd_reply()
302df930be7Sderaadt {
303df930be7Sderaadt 	int len;
304df930be7Sderaadt #ifdef DYNAMIC_BUFFERS
3055c8362e1Spvalchev 	void *pkt;
306df930be7Sderaadt #else
307df930be7Sderaadt 	u_int pkt[MAX_PACKET_SIZE/sizeof(u_int)+1];
308df930be7Sderaadt #endif /* DYNAMIC_BUFFERS */
309df930be7Sderaadt 	u_int *pkt_int;
310df930be7Sderaadt 	int rc;
311df930be7Sderaadt 	rpc_forward *p;
312df930be7Sderaadt 	struct sockaddr_in src_addr;
31308e0da89Sderaadt 	socklen_t src_addr_len;
314df930be7Sderaadt 
315df930be7Sderaadt 	/*
316df930be7Sderaadt 	 * Determine the length of the packet
317df930be7Sderaadt 	 */
318df930be7Sderaadt #ifdef DYNAMIC_BUFFERS
319*df69c215Sderaadt 	if (ioctl(fwd_sock, FIONREAD, &len) == -1 || len < 0) {
320df930be7Sderaadt 		plog(XLOG_ERROR, "Error reading packet size: %m");
321df930be7Sderaadt 		return;
322df930be7Sderaadt 	}
323df930be7Sderaadt 
324df930be7Sderaadt 	/*
325df930be7Sderaadt 	 * Allocate a buffer
326df930be7Sderaadt 	 */
32726d0c865Sguenther 	pkt = malloc(len);
328df930be7Sderaadt 	if (!pkt) {
329df930be7Sderaadt 		plog(XLOG_ERROR, "Out of buffers in fwd_reply");
330df930be7Sderaadt 		return;
331df930be7Sderaadt 	}
332df930be7Sderaadt #else
333df930be7Sderaadt 	len = MAX_PACKET_SIZE;
334df930be7Sderaadt #endif /* DYNAMIC_BUFFERS */
335df930be7Sderaadt 
336df930be7Sderaadt 	/*
337df930be7Sderaadt 	 * Read the packet and check for validity
338df930be7Sderaadt 	 */
339df930be7Sderaadt again:
340df930be7Sderaadt 	src_addr_len = sizeof(src_addr);
341df930be7Sderaadt 	rc = recvfrom(fwd_sock, (char *) pkt, len, 0,
342df930be7Sderaadt 	    (struct sockaddr *) &src_addr, &src_addr_len);
343*df69c215Sderaadt 	if (rc == -1 || src_addr_len != sizeof(src_addr) ||
344df930be7Sderaadt 			src_addr.sin_family != AF_INET) {
345*df69c215Sderaadt 		if (rc == -1 && errno == EINTR)
346df930be7Sderaadt 			goto again;
347df930be7Sderaadt 		plog(XLOG_ERROR, "Error reading RPC reply: %m");
348df930be7Sderaadt 		goto out;
349df930be7Sderaadt 	}
350df930be7Sderaadt 
351df930be7Sderaadt #ifdef DYNAMIC_BUFFERS
352df930be7Sderaadt 	if (rc != len) {
353df930be7Sderaadt 		plog(XLOG_ERROR, "Short read in fwd_reply");
354df930be7Sderaadt 		goto out;
355df930be7Sderaadt 	}
356df930be7Sderaadt #endif /* DYNAMIC_BUFFERS */
357df930be7Sderaadt 
358df930be7Sderaadt 	/*
359df930be7Sderaadt 	 * Do no more work if finishing soon
360df930be7Sderaadt 	 */
361df930be7Sderaadt 	if ((int)amd_state >= (int)Finishing)
362df930be7Sderaadt 		goto out;
363df930be7Sderaadt 
364df930be7Sderaadt 	/*
365df930be7Sderaadt 	 * Find packet reference
366df930be7Sderaadt 	 */
367df930be7Sderaadt 	pkt_int = (u_int *) pkt;
368df930be7Sderaadt 
369df930be7Sderaadt #ifdef DEBUG
370df930be7Sderaadt 	switch (*pkt_int & RPC_XID_MASK) {
371df930be7Sderaadt 	case RPC_XID_PORTMAP: dlog("Receiving PORTMAP reply"); break;
372df930be7Sderaadt 	case RPC_XID_MOUNTD: dlog("Receiving MOUNTD reply %#x", *pkt_int); break;
373df930be7Sderaadt 	case RPC_XID_NFSPING: dlog("Receiving NFS ping %#x", *pkt_int); break;
374df930be7Sderaadt 	default: dlog("UNKNOWN RPC XID"); break;
375df930be7Sderaadt 	}
376df930be7Sderaadt #endif /* DEBUG */
377df930be7Sderaadt 
378df930be7Sderaadt 	p = fwd_locate(*pkt_int);
379df930be7Sderaadt 	if (!p) {
380df930be7Sderaadt #ifdef DEBUG
381df930be7Sderaadt 		dlog("Can't forward reply id %#x", *pkt_int);
382df930be7Sderaadt #endif /* DEBUG */
383df930be7Sderaadt 		goto out;
384df930be7Sderaadt 	}
385df930be7Sderaadt 
386df930be7Sderaadt 	if (p->rf_fwd) {
387df930be7Sderaadt 		/*
388df930be7Sderaadt 		 * Put the original message id back
389df930be7Sderaadt 		 * into the packet.
390df930be7Sderaadt 		 */
391df930be7Sderaadt 		*pkt_int = p->rf_oldid;
392df930be7Sderaadt 
393df930be7Sderaadt 		/*
394df930be7Sderaadt 		 * Call forwarding function
395df930be7Sderaadt 		 */
39626d0c865Sguenther 		(*p->rf_fwd)(pkt, rc, &src_addr, &p->rf_sin, p->rf_ptr, TRUE);
397df930be7Sderaadt 	}
398df930be7Sderaadt 
399df930be7Sderaadt 	/*
400df930be7Sderaadt 	 * Free forwarding info
401df930be7Sderaadt 	 */
402df930be7Sderaadt 	fwd_free(p);
403df930be7Sderaadt 
404df930be7Sderaadt out:;
405df930be7Sderaadt #ifdef DYNAMIC_BUFFERS
406df930be7Sderaadt 	/*
407df930be7Sderaadt 	 * Free the packet
408df930be7Sderaadt 	 */
409819011edSguenther 	free(pkt);
410df930be7Sderaadt #endif /* DYNAMIC_BUFFERS */
411df930be7Sderaadt }
412