1 /*
2  * linux-cmd-handler.c
3  *
4  * A test/example program that receives a command sent to LUN 2,
5  * prints it out, and sends a response.
6  *
7  * Author: MontaVista Software, Inc.
8  *         Corey Minyard <minyard@mvista.com>
9  *         source@mvista.com
10  *
11  * Copyright 2017 MontaVista Software Inc.
12  *
13  *  This program is free software; you can redistribute it and/or modify it
14  *  under the terms of the GNU General Public License as published by the
15  *  Free Software Foundation; either version 2 of the License, or (at your
16  *  option) any later version.
17  *
18  *
19  *  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
20  *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
21  *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22  *  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23  *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24  *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
25  *  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26  *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
27  *  TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
28  *  USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  *
30  *  You should have received a copy of the GNU General Public License along
31  *  with this program; if not, write to the Free Software Foundation, Inc.,
32  *  675 Mass Ave, Cambridge, MA 02139, USA.
33  */
34 
35 /*
36  * This program provides an example of how to receive a command from
37  * the BMC and send a response.  This can be used to extend IPMI in the
38  * host system, if you want to do that.
39  *
40  * This works by sending a sending a command to the BMC on LUN 2.  The
41  * BMC should route this to the receive queue, the driver will pick it
42  * up and if something is registered to that particular netfn/cmd, it
43  * will route it to that.
44  *
45  * Generally you are sending these commands over a lan interface.  Here is
46  * an example ipmitool command to do this:
47  *
48  *  ipmitool -I lan -A MD5 -U <user> -P <pw) -l 2 -H t-langley-1 raw 2 3 1 2 3 4
49  * You should get the response:
50  *  01 02 03 04
51  * Note that older versions on ipmitool may have a broken -l option.
52  *
53  * In openipmicmd, you would do the following:
54  *  => f 2 2 3 1 2 3 4
55  *  => Got message:
56  *    type      = response
57  *    addr_type = SI
58  *    channel   = 0xf
59  *    lun       = 0x2
60  *    netfn     = 0x3
61  *    cmd       = 0x3
62  *    data      =00 01 02 03 04
63  *
64  */
65 
66 #include <stdio.h>
67 #include <stdlib.h>
68 #include <sys/types.h>
69 #include <sys/stat.h>
70 #include <fcntl.h>
71 #include <errno.h>
72 #include <string.h>
73 #include <stdbool.h>
74 #include <sys/select.h>
75 #include <sys/ioctl.h>
76 #include <linux/ipmi.h>
77 
78 static char *progname;
79 static char *devname = "/dev/ipmi0";
80 static int netfn = 2;
81 static int cmd = 3;
82 
83 static void
usage(int exitcode)84 usage(int exitcode)
85 {
86     printf("Wait for incoming commands on an IPMI interface, print then,\n");
87     printf("and send a response back\n\n");
88     printf("%s [-d|--device <device file>] [-n|--netfn <netfn>]\n", progname);
89     printf("        [-c|--command] <command>\n\n");
90     printf(" -d|--device - Set the IPMI device to use."
91 	   "  Default is /dev/ipmi0\n");
92     printf(" -n|--netfn - Set the netfn to listen for.  Default is 2\n");
93     printf(" -c|--command - Set the command to listen for.  Default is 3\n");
94 
95     exit(exitcode);
96 }
97 
98 static int
parse_num(const char * str,const char * optname)99 parse_num(const char *str, const char *optname)
100 {
101     char *end;
102     int num;
103 
104     if (*str == '\0') {
105 	fprintf(stderr, "Empty value given for %s, must be an integer\n",
106 		optname);
107 	exit(1);
108     }
109     num = strtoul(str, &end, 0);
110     if (*end != '\0') {
111 	fprintf(stderr, "Invalid value given for %s, must be an integer\n",
112 		optname);
113 	exit(1);
114     }
115 
116     return num;
117 }
118 
119 static void
parse_args(int argc,char * argv[])120 parse_args(int argc, char *argv[])
121 {
122     int argn;
123 
124     progname = argv[0];
125 
126     for (argn = 1; argn < argc; argn++) {
127 	int p = argn;
128 
129 	if (argv[p][0] != '-')
130 	    break;
131 
132 	if (strcmp(argv[p], "-h") == 0 || strcmp(argv[p], "--help") == 0)
133 	    usage(0);
134 
135 	/* All options here down take a value. */
136 	argn++;
137 	if (strcmp(argv[p], "-d") == 0 || strcmp(argv[p], "--device") == 0) {
138 	    if (argn >= argc)
139 		goto no_parm;
140 	    devname = argv[argn];
141 	    continue;
142 	}
143 	if (strcmp(argv[p], "-n") == 0 || strcmp(argv[p], "--netfn") == 0) {
144 	    if (argn >= argc)
145 		goto no_parm;
146 	    netfn = parse_num(argv[argn], argv[p]);
147 	    continue;
148 	}
149 	if (strcmp(argv[p], "-c") == 0 || strcmp(argv[p], "--command") == 0) {
150 	    if (argn >= argc)
151 		goto no_parm;
152 	    cmd = parse_num(argv[argn], argv[p]);
153 	    continue;
154 	}
155 
156 	fprintf(stderr, "Unknown option given: %s\n", argv[p]);
157 	usage(1);
158     no_parm:
159 	fprintf(stderr, "Option %s must have a value\n", argv[p]);
160 	exit(1);
161     }
162 
163     if (argn < argc) {
164 	fprintf(stderr, "This program takes only options, no parameters\n");
165 	exit(1);
166     }
167 
168     if (netfn & 1) {
169 	fprintf(stderr, "The netfn must be an even number\n");
170 	exit(1);
171     }
172 }
173 
174 int
main(int argc,char * argv[])175 main(int argc, char *argv[])
176 {
177     int fd, rv;
178     struct ipmi_cmdspec cmdspec;
179 
180     parse_args(argc, argv);
181 
182     fd = open(devname, O_RDWR);
183     if (fd == -1) {
184 	fprintf(stderr, "Error opening %s: %s\n", devname, strerror(errno));
185 	exit(1);
186     }
187 
188     cmdspec.netfn = netfn;
189     cmdspec.cmd = cmd;
190     rv = ioctl(fd, IPMICTL_REGISTER_FOR_CMD, &cmdspec);
191     if (rv == -1) {
192 	fprintf(stderr, "Error registering for command %2.2x:%2.2x: %s\n",
193 		netfn, cmd, strerror(errno));
194 	exit(1);
195     }
196 
197     while (true) {
198 	fd_set readfds;
199 	struct ipmi_recv recv;
200 	struct ipmi_addr addr;
201 	struct ipmi_req resp;
202 	unsigned char data[IPMI_MAX_MSG_LENGTH];
203 	unsigned char rspdata[IPMI_MAX_MSG_LENGTH];
204 	unsigned int i;
205 
206 	/* Wait for something. */
207 	FD_ZERO(&readfds);
208 	FD_SET(fd, &readfds);
209 	rv = select(fd + 1, &readfds, NULL, NULL, NULL);
210 	if (rv == -1) {
211 	    fprintf(stderr, "Error from select: %s\n", strerror(errno));
212 	    exit(1);
213 	}
214 
215 	/* Receive the message. */
216 	recv.addr = (unsigned char *) &addr;
217 	recv.addr_len = sizeof(addr);
218 	recv.msg.data = data;
219 	recv.msg.data_len = sizeof(data);
220 	rv = ioctl(fd, IPMICTL_RECEIVE_MSG, &recv);
221 	if (rv == -1) {
222 	    fprintf(stderr, "Error receiving message: %s\n", strerror(errno));
223 	    continue;
224 	}
225 
226 	if (recv.recv_type == IPMI_RESPONSE_RESPONSE_TYPE) {
227 	    /*
228 	     * This is a response to the response we sent.  Kind of
229 	     * weird sounding, but this lets the driver report errors
230 	     * in sending the response.
231 	     */
232 	    if (recv.msg.data_len < 1)
233 		fprintf(stderr,
234 			"Response response didn't contain a return code\n");
235 	    else if (recv.msg.data[0] != 0)
236 		fprintf(stderr,
237 			"Response response had an error: %2.2x\n",
238 			recv.msg.data[0]);
239 	    continue;
240 	}
241 
242 	if (recv.recv_type != IPMI_CMD_RECV_TYPE) {
243 	    /*
244 	     * This should never happen, we haven't registered for events or
245 	     * sent any commands to get responses for.
246 	     */
247 	    fprintf(stderr, "Got invalid message type: %d\n", recv.recv_type);
248 	    continue;
249 	}
250 
251 	/* Got a valid message.  Print it. */
252 	printf("Got command %2.2x:%2.2x, data:", recv.msg.netfn, recv.msg.cmd);
253 	for (i = 0; i < recv.msg.data_len; i++) {
254 	    if ((i % 16) == 0)
255 		printf("\n ");
256 	    printf(" %2.2x", recv.msg.data[i]);
257 	}
258 	printf("\n");
259 
260 	/* Send echo response back to the address we got it from. */
261 	resp.addr = recv.addr;
262 	resp.addr_len = recv.addr_len;
263 	resp.msgid = recv.msgid;
264 	resp.msg.netfn = recv.msg.netfn | 1; /* Set to a response. */
265 	resp.msg.cmd = recv.msg.cmd;
266 	/*
267 	 * All the strange finagling is is adding the error byte at
268 	 * the beginning of the response.
269 	 */
270 	if (recv.msg.data_len > sizeof(rspdata) - 1)
271 	    recv.msg.data_len = sizeof(rspdata) - 1;
272 	memcpy(rspdata + 1, recv.msg.data, recv.msg.data_len);
273 	rspdata[0] = 0;
274 	resp.msg.data = rspdata;
275 	resp.msg.data_len = recv.msg.data_len + 1;
276 	rv = ioctl(fd, IPMICTL_SEND_COMMAND, &resp);
277 	if (rv == -1)
278 	    fprintf(stderr, "Error sending response: %s\n", strerror(errno));
279     }
280 
281     exit(0);
282 }
283