1/*-
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright (c) 1988, 1992 The University of Utah and the Center
5 *	for Software Science (CSS).
6 * Copyright (c) 1992, 1993
7 *	The Regents of the University of California.  All rights reserved.
8 *
9 * This code is derived from software contributed to Berkeley by
10 * the Center for Software Science of the University of Utah Computer
11 * Science Department.  CSS requests users of this software to return
12 * to css-dist@cs.utah.edu any improvements that they make and grant
13 * CSS redistribution rights.
14 *
15 * Redistribution and use in source and binary forms, with or without
16 * modification, are permitted provided that the following conditions
17 * are met:
18 * 1. Redistributions of source code must retain the above copyright
19 *    notice, this list of conditions and the following disclaimer.
20 * 2. Redistributions in binary form must reproduce the above copyright
21 *    notice, this list of conditions and the following disclaimer in the
22 *    documentation and/or other materials provided with the distribution.
23 * 3. Neither the name of the University nor the names of its contributors
24 *    may be used to endorse or promote products derived from this software
25 *    without specific prior written permission.
26 *
27 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 * SUCH DAMAGE.
38 *
39 *	from: @(#)rmpproto.c	8.1 (Berkeley) 6/4/93
40 *
41 * From: Utah Hdr: rmpproto.c 3.1 92/07/06
42 * Author: Jeff Forys, University of Utah CSS
43 */
44
45#ifndef lint
46#if 0
47static const char sccsid[] = "@(#)rmpproto.c	8.1 (Berkeley) 6/4/93";
48#endif
49static const char rcsid[] =
50  "$FreeBSD$";
51#endif /* not lint */
52
53#include <sys/param.h>
54#include <sys/time.h>
55#include <netinet/in.h>
56
57#include <errno.h>
58#include <fcntl.h>
59#include <stdio.h>
60#include <string.h>
61#include <syslog.h>
62#include <unistd.h>
63#include "defs.h"
64
65/*
66**  ProcessPacket -- determine packet type and do what's required.
67**
68**	An RMP BOOT packet has been received.  Look at the type field
69**	and process Boot Requests, Read Requests, and Boot Complete
70**	packets.  Any other type will be dropped with a warning msg.
71**
72**	Parameters:
73**		rconn - the new connection
74**		client - list of files available to this host
75**
76**	Returns:
77**		Nothing.
78**
79**	Side Effects:
80**		- If this is a valid boot request, it will be added to
81**		  the linked list of outstanding requests (RmpConns).
82**		- If this is a valid boot complete, its associated
83**		  entry in RmpConns will be deleted.
84**		- Also, unless we run out of memory, a reply will be
85**		  sent to the host that sent the packet.
86*/
87void
88ProcessPacket(RMPCONN *rconn, CLIENT *client)
89{
90	struct rmp_packet *rmp;
91	RMPCONN *rconnout;
92
93	rmp = &rconn->rmp;		/* cache pointer to RMP packet */
94
95	switch(rmp->r_type) {		/* do what we came here to do */
96		case RMP_BOOT_REQ:		/* boot request */
97			if ((rconnout = NewConn(rconn)) == NULL)
98				return;
99
100			/*
101			 *  If the Session ID is 0xffff, this is a "probe"
102			 *  packet and we do not want to add the connection
103			 *  to the linked list of active connections.  There
104			 *  are two types of probe packets, if the Sequence
105			 *  Number is 0 they want to know our host name, o/w
106			 *  they want the name of the file associated with
107			 *  the number spec'd by the Sequence Number.
108			 *
109			 *  If this is an actual boot request, open the file
110			 *  and send a reply.  If SendBootRepl() does not
111			 *  return 0, add the connection to the linked list
112			 *  of active connections, otherwise delete it since
113			 *  an error was encountered.
114			 */
115			if (ntohs(rmp->r_brq.rmp_session) == RMP_PROBESID) {
116				if (WORDZE(rmp->r_brq.rmp_seqno))
117					(void) SendServerID(rconnout);
118				else
119					(void) SendFileNo(rmp, rconnout,
120					                  client? client->files:
121					                          BootFiles);
122				FreeConn(rconnout);
123			} else {
124				if (SendBootRepl(rmp, rconnout,
125				    client? client->files: BootFiles))
126					AddConn(rconnout);
127				else
128					FreeConn(rconnout);
129			}
130			break;
131
132		case RMP_BOOT_REPL:		/* boot reply (not valid) */
133			syslog(LOG_WARNING, "%s: sent a boot reply",
134			       EnetStr(rconn));
135			break;
136
137		case RMP_READ_REQ:		/* read request */
138			/*
139			 *  Send a portion of the boot file.
140			 */
141			(void) SendReadRepl(rconn);
142			break;
143
144		case RMP_READ_REPL:		/* read reply (not valid) */
145			syslog(LOG_WARNING, "%s: sent a read reply",
146			       EnetStr(rconn));
147			break;
148
149		case RMP_BOOT_DONE:		/* boot complete */
150			/*
151			 *  Remove the entry from the linked list of active
152			 *  connections.
153			 */
154			(void) BootDone(rconn);
155			break;
156
157		default:			/* unknown RMP packet type */
158			syslog(LOG_WARNING, "%s: unknown packet type (%u)",
159			       EnetStr(rconn), rmp->r_type);
160	}
161}
162
163/*
164**  SendServerID -- send our host name to who ever requested it.
165**
166**	Parameters:
167**		rconn - the reply packet to be formatted.
168**
169**	Returns:
170**		1 on success, 0 on failure.
171**
172**	Side Effects:
173**		none.
174*/
175int
176SendServerID(RMPCONN *rconn)
177{
178	struct rmp_packet *rpl;
179	char *src, *dst;
180	u_int8_t *size;
181
182	rpl = &rconn->rmp;			/* cache ptr to RMP packet */
183
184	/*
185	 *  Set up assorted fields in reply packet.
186	 */
187	rpl->r_brpl.rmp_type = RMP_BOOT_REPL;
188	rpl->r_brpl.rmp_retcode = RMP_E_OKAY;
189	ZEROWORD(rpl->r_brpl.rmp_seqno);
190	rpl->r_brpl.rmp_session = 0;
191	rpl->r_brpl.rmp_version = htons(RMP_VERSION);
192
193	size = &rpl->r_brpl.rmp_flnmsize;	/* ptr to length of host name */
194
195	/*
196	 *  Copy our host name into the reply packet incrementing the
197	 *  length as we go.  Stop at RMP_HOSTLEN or the first dot.
198	 */
199	src = MyHost;
200	dst = (char *) &rpl->r_brpl.rmp_flnm;
201	for (*size = 0; *size < RMP_HOSTLEN; (*size)++) {
202		if (*src == '.' || *src == '\0')
203			break;
204		*dst++ = *src++;
205	}
206
207	rconn->rmplen = RMPBOOTSIZE(*size);	/* set packet length */
208
209	return(SendPacket(rconn));		/* send packet */
210}
211
212/*
213**  SendFileNo -- send the name of a bootable file to the requester.
214**
215**	Parameters:
216**		req - RMP BOOT packet containing the request.
217**		rconn - the reply packet to be formatted.
218**		filelist - list of files available to the requester.
219**
220**	Returns:
221**		1 on success, 0 on failure.
222**
223**	Side Effects:
224**		none.
225*/
226int
227SendFileNo(struct rmp_packet *req, RMPCONN *rconn, char *filelist[])
228{
229	struct rmp_packet *rpl;
230	char *src, *dst;
231	u_int8_t *size;
232	int i;
233
234	GETWORD(req->r_brpl.rmp_seqno, i);	/* SeqNo is really FileNo */
235	rpl = &rconn->rmp;			/* cache ptr to RMP packet */
236
237	/*
238	 *  Set up assorted fields in reply packet.
239	 */
240	rpl->r_brpl.rmp_type = RMP_BOOT_REPL;
241	PUTWORD(i, rpl->r_brpl.rmp_seqno);
242	i--;
243	rpl->r_brpl.rmp_session = 0;
244	rpl->r_brpl.rmp_version = htons(RMP_VERSION);
245
246	size = &rpl->r_brpl.rmp_flnmsize;	/* ptr to length of filename */
247	*size = 0;				/* init length to zero */
248
249	/*
250	 *  Copy the file name into the reply packet incrementing the
251	 *  length as we go.  Stop at end of string or when RMPBOOTDATA
252	 *  characters have been copied.  Also, set return code to
253	 *  indicate success or "no more files".
254	 */
255	if (i < C_MAXFILE && filelist[i] != NULL) {
256		src = filelist[i];
257		dst = (char *)&rpl->r_brpl.rmp_flnm;
258		for (; *src && *size < RMPBOOTDATA; (*size)++) {
259			if (*src == '\0')
260				break;
261			*dst++ = *src++;
262		}
263		rpl->r_brpl.rmp_retcode = RMP_E_OKAY;
264	} else
265		rpl->r_brpl.rmp_retcode = RMP_E_NODFLT;
266
267	rconn->rmplen = RMPBOOTSIZE(*size);	/* set packet length */
268
269	return(SendPacket(rconn));		/* send packet */
270}
271
272/*
273**  SendBootRepl -- open boot file and respond to boot request.
274**
275**	Parameters:
276**		req - RMP BOOT packet containing the request.
277**		rconn - the reply packet to be formatted.
278**		filelist - list of files available to the requester.
279**
280**	Returns:
281**		1 on success, 0 on failure.
282**
283**	Side Effects:
284**		none.
285*/
286int
287SendBootRepl(struct rmp_packet *req, RMPCONN *rconn, char *filelist[])
288{
289	int retval;
290	char *filename, filepath[RMPBOOTDATA+1];
291	RMPCONN *oldconn;
292	struct rmp_packet *rpl;
293	char *src, *dst1, *dst2;
294	u_int8_t i;
295
296	/*
297	 *  If another connection already exists, delete it since we
298	 *  are obviously starting again.
299	 */
300	if ((oldconn = FindConn(rconn)) != NULL) {
301		syslog(LOG_WARNING, "%s: dropping existing connection",
302		       EnetStr(oldconn));
303		RemoveConn(oldconn);
304	}
305
306	rpl = &rconn->rmp;			/* cache ptr to RMP packet */
307
308	/*
309	 *  Set up assorted fields in reply packet.
310	 */
311	rpl->r_brpl.rmp_type = RMP_BOOT_REPL;
312	COPYWORD(req->r_brq.rmp_seqno, rpl->r_brpl.rmp_seqno);
313	rpl->r_brpl.rmp_session = htons(GenSessID());
314	rpl->r_brpl.rmp_version = htons(RMP_VERSION);
315	rpl->r_brpl.rmp_flnmsize = req->r_brq.rmp_flnmsize;
316
317	/*
318	 *  Copy file name to `filepath' string, and into reply packet.
319	 */
320	src = &req->r_brq.rmp_flnm;
321	dst1 = filepath;
322	dst2 = &rpl->r_brpl.rmp_flnm;
323	for (i = 0; i < req->r_brq.rmp_flnmsize; i++)
324		*dst1++ = *dst2++ = *src++;
325	*dst1 = '\0';
326
327	/*
328	 *  If we are booting HP-UX machines, their secondary loader will
329	 *  ask for files like "/hp-ux".  As a security measure, we do not
330	 *  allow boot files to lay outside the boot directory (unless they
331	 *  are purposely link'd out.  So, make `filename' become the path-
332	 *  stripped file name and spoof the client into thinking that it
333	 *  really got what it wanted.
334	 */
335	filename = strrchr(filepath,'/');
336	filename = filename? filename + 1: filepath;
337
338	/*
339	 *  Check that this is a valid boot file name.
340	 */
341	for (i = 0; i < C_MAXFILE && filelist[i] != NULL; i++)
342		if (STREQN(filename, filelist[i]))
343			goto match;
344
345	/*
346	 *  Invalid boot file name, set error and send reply packet.
347	 */
348	rpl->r_brpl.rmp_retcode = RMP_E_NOFILE;
349	retval = 0;
350	goto sendpkt;
351
352match:
353	/*
354	 *  This is a valid boot file.  Open the file and save the file
355	 *  descriptor associated with this connection and set success
356	 *  indication.  If the file couldnt be opened, set error:
357	 *  	"no such file or dir" - RMP_E_NOFILE
358	 *	"file table overflow" - RMP_E_BUSY
359	 *	"too many open files" - RMP_E_BUSY
360	 *	anything else         - RMP_E_OPENFILE
361	 */
362	if ((rconn->bootfd = open(filename, O_RDONLY, 0600)) < 0) {
363		rpl->r_brpl.rmp_retcode = (errno == ENOENT)? RMP_E_NOFILE:
364			(errno == EMFILE || errno == ENFILE)? RMP_E_BUSY:
365			RMP_E_OPENFILE;
366		retval = 0;
367	} else {
368		rpl->r_brpl.rmp_retcode = RMP_E_OKAY;
369		retval = 1;
370	}
371
372sendpkt:
373	syslog(LOG_INFO, "%s: request to boot %s (%s)",
374	       EnetStr(rconn), filename, retval? "granted": "denied");
375
376	rconn->rmplen = RMPBOOTSIZE(rpl->r_brpl.rmp_flnmsize);
377
378	return (retval & SendPacket(rconn));
379}
380
381/*
382**  SendReadRepl -- send a portion of the boot file to the requester.
383**
384**	Parameters:
385**		rconn - the reply packet to be formatted.
386**
387**	Returns:
388**		1 on success, 0 on failure.
389**
390**	Side Effects:
391**		none.
392*/
393int
394SendReadRepl(RMPCONN *rconn)
395{
396	int retval = 0;
397	RMPCONN *oldconn;
398	struct rmp_packet *rpl, *req;
399	int size = 0;
400	int madeconn = 0;
401
402	/*
403	 *  Find the old connection.  If one doesn't exist, create one only
404	 *  to return the error code.
405	 */
406	if ((oldconn = FindConn(rconn)) == NULL) {
407		if ((oldconn = NewConn(rconn)) == NULL)
408			return(0);
409		syslog(LOG_ERR, "SendReadRepl: no active connection (%s)",
410		       EnetStr(rconn));
411		madeconn++;
412	}
413
414	req = &rconn->rmp;		/* cache ptr to request packet */
415	rpl = &oldconn->rmp;		/* cache ptr to reply packet */
416
417	if (madeconn) {			/* no active connection above; abort */
418		rpl->r_rrpl.rmp_retcode = RMP_E_ABORT;
419		retval = 1;
420		goto sendpkt;
421	}
422
423	/*
424	 *  Make sure Session ID's match.
425	 */
426	if (ntohs(req->r_rrq.rmp_session) !=
427	    ((rpl->r_type == RMP_BOOT_REPL)? ntohs(rpl->r_brpl.rmp_session):
428	                                     ntohs(rpl->r_rrpl.rmp_session))) {
429		syslog(LOG_ERR, "SendReadRepl: bad session id (%s)",
430		       EnetStr(rconn));
431		rpl->r_rrpl.rmp_retcode = RMP_E_BADSID;
432		retval = 1;
433		goto sendpkt;
434	}
435
436	/*
437	 *  If the requester asks for more data than we can fit,
438	 *  silently clamp the request size down to RMPREADDATA.
439	 *
440	 *  N.B. I do not know if this is "legal", however it seems
441	 *  to work.  This is necessary for bpfwrite() on machines
442	 *  with MCLBYTES less than 1514.
443	 */
444	if (ntohs(req->r_rrq.rmp_size) > RMPREADDATA)
445		req->r_rrq.rmp_size = htons(RMPREADDATA);
446
447	/*
448	 *  Position read head on file according to info in request packet.
449	 */
450	GETWORD(req->r_rrq.rmp_offset, size);
451	if (lseek(oldconn->bootfd, (off_t)size, SEEK_SET) < 0) {
452		syslog(LOG_ERR, "SendReadRepl: lseek: %m (%s)",
453		       EnetStr(rconn));
454		rpl->r_rrpl.rmp_retcode = RMP_E_ABORT;
455		retval = 1;
456		goto sendpkt;
457	}
458
459	/*
460	 *  Read data directly into reply packet.
461	 */
462	if ((size = read(oldconn->bootfd, &rpl->r_rrpl.rmp_data,
463	                 (int) ntohs(req->r_rrq.rmp_size))) <= 0) {
464		if (size < 0) {
465			syslog(LOG_ERR, "SendReadRepl: read: %m (%s)",
466			       EnetStr(rconn));
467			rpl->r_rrpl.rmp_retcode = RMP_E_ABORT;
468		} else {
469			rpl->r_rrpl.rmp_retcode = RMP_E_EOF;
470		}
471		retval = 1;
472		goto sendpkt;
473	}
474
475	/*
476	 *  Set success indication.
477	 */
478	rpl->r_rrpl.rmp_retcode = RMP_E_OKAY;
479
480sendpkt:
481	/*
482	 *  Set up assorted fields in reply packet.
483	 */
484	rpl->r_rrpl.rmp_type = RMP_READ_REPL;
485	COPYWORD(req->r_rrq.rmp_offset, rpl->r_rrpl.rmp_offset);
486	rpl->r_rrpl.rmp_session = req->r_rrq.rmp_session;
487
488	oldconn->rmplen = RMPREADSIZE(size);	/* set size of packet */
489
490	retval &= SendPacket(oldconn);		/* send packet */
491
492	if (madeconn)				/* clean up after ourself */
493		FreeConn(oldconn);
494
495	return (retval);
496}
497
498/*
499**  BootDone -- free up memory allocated for a connection.
500**
501**	Parameters:
502**		rconn - incoming boot complete packet.
503**
504**	Returns:
505**		1 on success, 0 on failure.
506**
507**	Side Effects:
508**		none.
509*/
510int
511BootDone(RMPCONN *rconn)
512{
513	RMPCONN *oldconn;
514	struct rmp_packet *rpl;
515
516	/*
517	 *  If we can't find the connection, ignore the request.
518	 */
519	if ((oldconn = FindConn(rconn)) == NULL) {
520		syslog(LOG_ERR, "BootDone: no existing connection (%s)",
521		       EnetStr(rconn));
522		return(0);
523	}
524
525	rpl = &oldconn->rmp;			/* cache ptr to RMP packet */
526
527	/*
528	 *  Make sure Session ID's match.
529	 */
530	if (ntohs(rconn->rmp.r_rrq.rmp_session) !=
531	    ((rpl->r_type == RMP_BOOT_REPL)? ntohs(rpl->r_brpl.rmp_session):
532	                                    ntohs(rpl->r_rrpl.rmp_session))) {
533		syslog(LOG_ERR, "BootDone: bad session id (%s)",
534		       EnetStr(rconn));
535		return(0);
536	}
537
538	RemoveConn(oldconn);			/* remove connection */
539
540	syslog(LOG_INFO, "%s: boot complete", EnetStr(rconn));
541
542	return(1);
543}
544
545/*
546**  SendPacket -- send an RMP packet to a remote host.
547**
548**	Parameters:
549**		rconn - packet to be sent.
550**
551**	Returns:
552**		1 on success, 0 on failure.
553**
554**	Side Effects:
555**		none.
556*/
557int
558SendPacket(RMPCONN *rconn)
559{
560	/*
561	 *  Set Ethernet Destination address to Source (BPF and the enet
562	 *  driver will take care of getting our source address set).
563	 */
564	memmove((char *)&rconn->rmp.hp_hdr.daddr[0],
565	        (char *)&rconn->rmp.hp_hdr.saddr[0], RMP_ADDRLEN);
566	rconn->rmp.hp_hdr.len = htons(rconn->rmplen - sizeof(struct hp_hdr));
567
568	/*
569	 *  Reverse 802.2/HP Extended Source & Destination Access Pts.
570	 */
571	rconn->rmp.hp_llc.dxsap = htons(HPEXT_SXSAP);
572	rconn->rmp.hp_llc.sxsap = htons(HPEXT_DXSAP);
573
574	/*
575	 *  Last time this connection was active.
576	 */
577	(void)gettimeofday(&rconn->tstamp, NULL);
578
579	if (DbgFp != NULL)			/* display packet */
580		DispPkt(rconn,DIR_SENT);
581
582	/*
583	 *  Send RMP packet to remote host.
584	 */
585	return(BpfWrite(rconn));
586}
587