1 /* $OpenBSD: iscsid.c,v 1.25 2025/01/22 16:06:36 claudio Exp $ */
2
3 /*
4 * Copyright (c) 2009 Claudio Jeker <claudio@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 #include <sys/queue.h>
20 #include <sys/socket.h>
21 #include <sys/sysctl.h>
22 #include <sys/time.h>
23 #include <sys/uio.h>
24
25 #include <err.h>
26 #include <event.h>
27 #include <pwd.h>
28 #include <signal.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <unistd.h>
33
34 #include "iscsid.h"
35 #include "log.h"
36
37 void main_sig_handler(int, short, void *);
38 __dead void usage(void);
39 void shutdown_cb(int, short, void *);
40
41 struct event exit_ev;
42 int exit_rounds;
43 #define ISCSI_EXIT_WAIT 5
44
45 const struct session_params iscsi_sess_defaults = {
46 .MaxBurstLength = 262144,
47 .FirstBurstLength = 65536,
48 .DefaultTime2Wait = 2,
49 .DefaultTime2Retain = 20,
50 .MaxOutstandingR2T = 1,
51 .MaxConnections = 1,
52 .InitialR2T = 1,
53 .ImmediateData = 1,
54 .DataPDUInOrder = 1,
55 .DataSequenceInOrder = 1,
56 .ErrorRecoveryLevel = 0,
57 };
58
59 const struct connection_params iscsi_conn_defaults = {
60 .MaxRecvDataSegmentLength = 8192,
61 .HeaderDigest = DIGEST_NONE,
62 .DataDigest = DIGEST_NONE,
63 };
64
65 int
main(int argc,char * argv[])66 main(int argc, char *argv[])
67 {
68 struct event ev_sigint, ev_sigterm, ev_sighup;
69 struct passwd *pw;
70 char *ctrlsock = ISCSID_CONTROL;
71 char *vscsidev = ISCSID_DEVICE;
72 int name[] = { CTL_KERN, KERN_PROC_NOBROADCASTKILL, 0 };
73 int ch, debug = 0, verbose = 0, nobkill = 1;
74
75 log_procname = getprogname();
76
77 log_init(1); /* log to stderr until daemonized */
78 log_verbose(1);
79
80 while ((ch = getopt(argc, argv, "dn:s:v")) != -1) {
81 switch (ch) {
82 case 'd':
83 debug = 1;
84 break;
85 case 'n':
86 vscsidev = optarg;
87 break;
88 case 's':
89 ctrlsock = optarg;
90 break;
91 case 'v':
92 verbose = 1;
93 break;
94 default:
95 usage();
96 /* NOTREACHED */
97 }
98 }
99
100 argc -= optind;
101 argv += optind;
102
103 if (argc > 0)
104 usage();
105
106 /* check for root privileges */
107 if (geteuid())
108 errx(1, "need root privileges");
109
110 log_init(debug);
111 log_verbose(verbose);
112
113 if (control_init(ctrlsock) == -1)
114 fatalx("control socket setup failed");
115
116 if (!debug)
117 daemon(1, 0);
118 log_info("startup");
119
120 name[2] = getpid();
121 if (sysctl(name, 3, NULL, 0, &nobkill, sizeof(nobkill)) != 0)
122 fatal("sysctl");
123
124 event_init();
125 vscsi_open(vscsidev);
126
127 /* chroot and drop to iscsid user */
128 if ((pw = getpwnam(ISCSID_USER)) == NULL)
129 errx(1, "unknown user %s", ISCSID_USER);
130
131 if (chroot(pw->pw_dir) == -1)
132 fatal("chroot");
133 if (chdir("/") == -1)
134 fatal("chdir(\"/\")");
135 if (setgroups(1, &pw->pw_gid) ||
136 setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
137 setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
138 fatal("can't drop privileges");
139
140 /* setup signal handler */
141 signal_set(&ev_sigint, SIGINT, main_sig_handler, NULL);
142 signal_set(&ev_sigterm, SIGTERM, main_sig_handler, NULL);
143 signal_set(&ev_sighup, SIGHUP, main_sig_handler, NULL);
144 signal_add(&ev_sigint, NULL);
145 signal_add(&ev_sigterm, NULL);
146 signal_add(&ev_sighup, NULL);
147 signal(SIGPIPE, SIG_IGN);
148
149 control_event_init();
150 initiator_init();
151
152 event_dispatch();
153
154 /* do some cleanup on the way out */
155 control_cleanup(ctrlsock);
156 initiator_cleanup();
157 log_info("exiting.");
158 return 0;
159 }
160
161 void
shutdown_cb(int fd,short event,void * arg)162 shutdown_cb(int fd, short event, void *arg)
163 {
164 struct timeval tv;
165
166 if (exit_rounds++ >= ISCSI_EXIT_WAIT || initiator_isdown())
167 event_loopexit(NULL);
168
169 timerclear(&tv);
170 tv.tv_sec = 1;
171
172 if (evtimer_add(&exit_ev, &tv) == -1)
173 fatal("shutdown_cb");
174 }
175
176 void
main_sig_handler(int sig,short event,void * arg)177 main_sig_handler(int sig, short event, void *arg)
178 {
179 struct timeval tv;
180
181 /* signal handler rules don't apply, libevent decouples for us */
182 switch (sig) {
183 case SIGTERM:
184 case SIGINT:
185 case SIGHUP:
186 initiator_shutdown();
187 evtimer_set(&exit_ev, shutdown_cb, NULL);
188 timerclear(&tv);
189 if (evtimer_add(&exit_ev, &tv) == -1)
190 fatal("main_sig_handler");
191 break;
192 default:
193 fatalx("unexpected signal");
194 /* NOTREACHED */
195 }
196 }
197
198 __dead void
usage(void)199 usage(void)
200 {
201 extern char *__progname;
202
203 fprintf(stderr, "usage: %s [-dv] [-n device] [-s socket]\n",
204 __progname);
205 exit(1);
206 }
207
208 void
iscsid_ctrl_dispatch(void * ch,struct pdu * pdu)209 iscsid_ctrl_dispatch(void *ch, struct pdu *pdu)
210 {
211 struct ctrlmsghdr *cmh;
212 struct initiator_config *ic;
213 struct session_head *sh;
214 struct session_config *sc;
215 struct session *s;
216 struct session_poll p = { 0 };
217 int *valp;
218
219 cmh = pdu_getbuf(pdu, NULL, 0);
220 if (cmh == NULL)
221 goto done;
222
223 switch (cmh->type) {
224 case CTRL_INITIATOR_CONFIG:
225 if (cmh->len[0] != sizeof(*ic)) {
226 log_warnx("CTRL_INITIATOR_CONFIG bad size");
227 control_compose(ch, CTRL_FAILURE, NULL, 0);
228 break;
229 }
230 ic = pdu_getbuf(pdu, NULL, 1);
231 initiator_set_config(ic);
232 control_compose(ch, CTRL_SUCCESS, NULL, 0);
233 break;
234 case CTRL_SESSION_CONFIG:
235 if (cmh->len[0] != sizeof(*sc)) {
236 log_warnx("CTRL_SESSION_CONFIG bad size");
237 control_compose(ch, CTRL_FAILURE, NULL, 0);
238 break;
239 }
240 sc = pdu_getbuf(pdu, NULL, 1);
241 if (cmh->len[1])
242 sc->TargetName = pdu_getbuf(pdu, NULL, 2);
243 else if (sc->SessionType != SESSION_TYPE_DISCOVERY) {
244 control_compose(ch, CTRL_FAILURE, NULL, 0);
245 goto done;
246 } else
247 sc->TargetName = NULL;
248 if (cmh->len[2])
249 sc->InitiatorName = pdu_getbuf(pdu, NULL, 3);
250 else
251 sc->InitiatorName = NULL;
252
253 s = initiator_find_session(sc->SessionName);
254 if (s == NULL) {
255 s = initiator_new_session(sc->SessionType);
256 if (s == NULL) {
257 control_compose(ch, CTRL_FAILURE, NULL, 0);
258 goto done;
259 }
260 }
261
262 session_config(s, sc);
263 if (s->state == SESS_INIT)
264 session_fsm(&s->sev, SESS_EV_START, 0);
265
266 control_compose(ch, CTRL_SUCCESS, NULL, 0);
267 break;
268 case CTRL_LOG_VERBOSE:
269 if (cmh->len[0] != sizeof(int)) {
270 log_warnx("CTRL_LOG_VERBOSE bad size");
271 control_compose(ch, CTRL_FAILURE, NULL, 0);
272 break;
273 }
274 valp = pdu_getbuf(pdu, NULL, 1);
275 log_verbose(*valp);
276 control_compose(ch, CTRL_SUCCESS, NULL, 0);
277 break;
278 case CTRL_VSCSI_STATS:
279 control_compose(ch, CTRL_VSCSI_STATS, vscsi_stats(),
280 sizeof(struct vscsi_stats));
281 break;
282 case CTRL_SHOW_SUM:
283 ic = initiator_get_config();
284 control_compose(ch, CTRL_INITIATOR_CONFIG, ic, sizeof(*ic));
285
286 sh = initiator_get_sessions();
287 TAILQ_FOREACH(s, sh, entry) {
288 struct ctrldata cdv[3];
289 bzero(cdv, sizeof(cdv));
290
291 cdv[0].buf = &s->config;
292 cdv[0].len = sizeof(s->config);
293
294 if (s->config.TargetName) {
295 cdv[1].buf = s->config.TargetName;
296 cdv[1].len =
297 strlen(s->config.TargetName) + 1;
298 }
299 if (s->config.InitiatorName) {
300 cdv[2].buf = s->config.InitiatorName;
301 cdv[2].len =
302 strlen(s->config.InitiatorName) + 1;
303 }
304
305 control_build(ch, CTRL_SESSION_CONFIG,
306 nitems(cdv), cdv);
307 }
308
309 control_compose(ch, CTRL_SUCCESS, NULL, 0);
310 break;
311 case CTRL_SESS_POLL:
312 sh = initiator_get_sessions();
313 TAILQ_FOREACH(s, sh, entry)
314 poll_session(&p, s);
315 poll_finalize(&p);
316 control_compose(ch, CTRL_SESS_POLL, &p, sizeof(p));
317 break;
318 default:
319 log_warnx("unknown control message type %d", cmh->type);
320 control_compose(ch, CTRL_FAILURE, NULL, 0);
321 break;
322 }
323
324 done:
325 pdu_free(pdu);
326 }
327
328 #define MERGE_MIN(r, a, b, v) \
329 r->v = (a->v < b->v ? a->v : b->v)
330 #define MERGE_MAX(r, a, b, v) \
331 r->v = (a->v > b->v ? a->v : b->v)
332 #define MERGE_OR(r, a, b, v) \
333 r->v = (a->v || b->v)
334 #define MERGE_AND(r, a, b, v) \
335 r->v = (a->v && b->v)
336
337 void
iscsi_merge_sess_params(struct session_params * res,struct session_params * mine,struct session_params * his)338 iscsi_merge_sess_params(struct session_params *res,
339 struct session_params *mine, struct session_params *his)
340 {
341 memset(res, 0, sizeof(*res));
342
343 MERGE_MIN(res, mine, his, MaxBurstLength);
344 MERGE_MIN(res, mine, his, FirstBurstLength);
345 MERGE_MAX(res, mine, his, DefaultTime2Wait);
346 MERGE_MIN(res, mine, his, DefaultTime2Retain);
347 MERGE_MIN(res, mine, his, MaxOutstandingR2T);
348 res->TargetPortalGroupTag = his->TargetPortalGroupTag;
349 MERGE_MIN(res, mine, his, MaxConnections);
350 MERGE_OR(res, mine, his, InitialR2T);
351 MERGE_AND(res, mine, his, ImmediateData);
352 MERGE_OR(res, mine, his, DataPDUInOrder);
353 MERGE_OR(res, mine, his, DataSequenceInOrder);
354 MERGE_MIN(res, mine, his, ErrorRecoveryLevel);
355
356 }
357
358 void
iscsi_merge_conn_params(struct connection_params * res,struct connection_params * mine,struct connection_params * his)359 iscsi_merge_conn_params(struct connection_params *res,
360 struct connection_params *mine, struct connection_params *his)
361 {
362 int mask;
363
364 memset(res, 0, sizeof(*res));
365
366 res->MaxRecvDataSegmentLength = his->MaxRecvDataSegmentLength;
367
368 /* for digest select first bit that is set in both his and mine */
369 mask = mine->HeaderDigest & his->HeaderDigest;
370 mask = ffs(mask) - 1;
371 if (mask == -1)
372 res->HeaderDigest = 0;
373 else
374 res->HeaderDigest = 1 << mask;
375
376 mask = mine->DataDigest & his->DataDigest;
377 mask = ffs(mask) - 1;
378 if (mask == -1)
379 res->DataDigest = 0;
380 else
381 res->DataDigest = 1 << mask;
382 }
383
384 #undef MERGE_MIN
385 #undef MERGE_MAX
386 #undef MERGE_OR
387 #undef MERGE_AND
388