blob: 74682509a46b12e0d401a17a61d17cdc92a33fdf [file] [log] [blame]
/*
* iscsi_adm - manage iSCSI-SCST Target software.
*
* Copyright (C) 2004 - 2005 FUJITA Tomonori <tomof@acm.org>
* Copyright (C) 2007 - 2018 Vladislav Bolkhovitin
* Copyright (C) 2007 - 2018 Western Digital Corporation
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include "iscsid.h"
#include "iscsi_adm.h"
#define SET_TARGET (1 << 0)
#define SET_SESSION (1 << 1)
#define SET_CONNECTION (1 << 2)
#define SET_USER (1 << 4)
typedef int (user_handle_fn_t)(struct iscsi_adm_req *req, char *user, char *pass);
enum iscsi_adm_op {
OP_NEW,
OP_DELETE,
OP_UPDATE,
OP_SHOW,
};
static const char program_name[] = "iscsi-scst-adm";
static struct option const long_options[] = {
{"op", required_argument, NULL, 'o'},
{"tid", required_argument, NULL, 't'},
{"sid", required_argument, NULL, 's'},
{"cid", required_argument, NULL, 'c'},
{"params", required_argument, NULL, 'p'},
{"user", no_argument, NULL, 'u'},
{"version", no_argument, NULL, 'v'},
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0},
};
static void usage(int status)
{
if (status != 0)
fprintf(stderr, "Try `%s --help' for more information.\n", program_name);
else {
printf("Usage: %s [OPTION]\n", program_name);
printf("\
iSCSI-SCST Target Administration Utility.\n\
\n\
--op new --tid=[id] --params Name=[name]\n\
add a new target with [id]. [id] must not be zero.\n\
--op delete --tid=[id]\n\
delete specific target with [id]. The target must\n\
have no active sessions.\n\
--op show --tid=[id]\n\
show target parameters of target with [id].\n\
--op show --tid=[id] --sid=[sid]\n\
show iSCSI parameters in effect for session [sid]. If\n\
[sid] is \"0\" (zero), the configured parameters\n\
will be displayed.\n\
--op show --tid=[id] --user\n\
show list of Discovery (--tid omitted / id=0 (zero))\n\
or target CHAP accounts.\n\
--op show --tid=[id] --user --params=[user]=[name]\n\
show CHAP account information. [user] can be\n\
\"IncomingUser\" or \"OutgoingUser\". If --tid is\n\
omitted / id=0 (zero), [user] is treated as Discovery\n\
user.\n\
--op delete --tid=[id] --sid=[sid] --cid=[cid]\n\
delete specific connection with [cid] in a session\n\
with [sid] that the target with [id] has.\n\
If the session has no connections after\n\
the operation, the session will be deleted\n\
automatically.\n\
--op update --tid=[id] --params=key1=value1,key2=value2,...\n\
change iSCSI target parameters of specific\n\
target with [id]. You can use parameters in iscsi-scstd.conf\n\
as a key.\n\
--op new --tid=[id] --user --params=[user]=[name],Password=[pass]\n\
add a new account with [pass] for specific target.\n\
[user] could be [IncomingUser] or [OutgoingUser].\n\
If you don't specify a target (omit --tid option),\n\
you add a new account for discovery sessions.\n\
--op delete --tid=[id] --user --params=[user]=[name]\n\
delete specific account having [name] of specific\n\
target. [user] could be [IncomingUser] or\n\
[OutgoingUser].\n\
If you don't specify a target (omit --tid option),\n\
you delete the account for discovery sessions.\n\
--version display version and exit\n\
--help display this help and exit\n\
\n\
Report bugs to <scst-devel@lists.sourceforge.net>.\n");
}
exit(status == 0 ? 0 : -1);
}
static int str_to_op(char *str)
{
int op;
if (!strcmp("new", str))
op = OP_NEW;
else if (!strcmp("delete", str))
op = OP_DELETE;
else if (!strcmp("update", str))
op = OP_UPDATE;
else if (!strcmp("show", str))
op = OP_SHOW;
else
op = -1;
return op;
}
static int iscsid_request_send(int fd, struct iscsi_adm_req *req)
{
int err, ret;
do {
ret = write(fd, req, sizeof(*req));
} while (ret < 0 && errno == EINTR);
if (ret != sizeof(*req)) {
err = (ret < 0) ? -errno : -EIO;
fprintf(stderr, "%s failed: written %d, to write %d, "
"error: %s\n", __func__, ret, err, strerror(-err));
} else
err = 0;
return err;
}
static int iscsid_response_recv(int fd, struct iscsi_adm_req *req, void *rsp_data,
int rsp_data_sz)
{
int err, ret;
struct iovec iov[2];
struct iscsi_adm_rsp rsp;
iov[0].iov_base = req;
iov[0].iov_len = sizeof(*req);
iov[1].iov_base = &rsp;
iov[1].iov_len = sizeof(rsp);
do {
ret = readv(fd, iov, 2);
} while (ret < 0 && errno == EINTR);
if (ret != sizeof(rsp) + sizeof(*req)) {
err = (ret < 0) ? -errno : -EIO;
fprintf(stderr, "readv failed: read %d instead of %d (%s)\n",
ret, (int)(sizeof(rsp) + sizeof(*req)), strerror(-err));
} else
err = rsp.err;
if (!err && rsp_data_sz && rsp_data) {
ret = read(fd, rsp_data, rsp_data_sz);
if (ret != rsp_data_sz) {
err = (ret < 0) ? -errno : -EIO;
fprintf(stderr, "read failed: read %d instead of %d (%s)\n",
ret, (int)rsp_data_sz, strerror(-err));
}
}
return err;
}
static int iscsid_connect(void)
{
int fd;
struct sockaddr_un addr;
fd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (fd < 0) {
perror("socket() failed");
fd = -errno;
goto out;
}
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_LOCAL;
memcpy((char *) &addr.sun_path + 1, ISCSI_ADM_NAMESPACE, strlen(ISCSI_ADM_NAMESPACE));
if (connect(fd, (struct sockaddr *) &addr, sizeof(addr))) {
close(fd);
fd = -errno;
fprintf(stderr, "Unable to connect to iscsid: %s\n",
strerror(-fd));
goto out;
}
out:
return fd;
}
static int iscsid_request(struct iscsi_adm_req *req, void *rsp_data,
int rsp_data_sz)
{
int fd = -1, err = -EIO;
if ((fd = iscsid_connect()) < 0) {
err = fd;
goto out_close;
}
if ((err = iscsid_request_send(fd, req)) < 0)
goto out_report;
err = iscsid_response_recv(fd, req, rsp_data, rsp_data_sz);
out_report:
if (err < 0) {
if (err == -ENOENT)
err = -EINVAL;
fprintf(stderr, "Request to iscsid failed: %s\n", strerror(-err));
}
out_close:
if (fd >= 0)
close(fd);
return err;
}
static void show_iscsi_params(int type, struct iscsi_param *param)
{
int i, nr, len;
char buf[1024], *p;
struct iscsi_key *keys;
if (type == key_session) {
nr = session_key_last;
keys = session_keys;
} else {
nr = target_key_last;
keys = target_keys;
}
for (i = 0; i < nr; i++) {
memset(buf, 0, sizeof(buf));
strlcpy(buf, keys[i].name, sizeof(buf));
len = strlen(buf);
p = buf + len;
*p++ = '=';
params_val_to_str(keys, i, param[i].val, p, sizeof(buf) - (len + 1));
printf("%s\n", buf);
}
}
static int parse_trgt_params(struct msg_trgt *msg, char *params)
{
char *p, *q;
while ((p = strsep(&params, ",")) != NULL) {
int idx;
u32 val;
if (!*p)
continue;
if (!(q = strchr(p, '=')))
continue;
*q++ = '\0';
if (!((idx = params_index_by_name(p, target_keys)) < 0)) {
if (params_str_to_val(target_keys, idx, q, &val)) {
fprintf(stderr,
"Invalid %s value \"%s\".\n",
target_keys[idx].name, q);
return -EINVAL;
}
if (!params_check_val(target_keys, idx, &val))
msg->target_partial |= (1 << idx);
msg->target_params[idx].val = val;
msg->type |= 1 << key_target;
continue;
}
if (!((idx = params_index_by_name(p, session_keys)) < 0)) {
if (params_str_to_val(session_keys, idx, q, &val)) {
fprintf(stderr,
"Invalid %s value \"%s\".\n",
session_keys[idx].name, q);
return -EINVAL;
}
if (!params_check_val(session_keys, idx, &val))
msg->session_partial |= (1 << idx);
msg->session_params[idx].val = val;
msg->type |= 1 << key_session;
continue;
}
fprintf(stderr, "Unknown parameter \"%s\".\n", p);
return -EINVAL;
}
return 0;
}
static int trgt_handle(int op, u32 set, u32 tid, char *params)
{
int err = -EINVAL;
struct iscsi_adm_req req;
if (!(set & SET_TARGET))
goto out;
memset(&req, 0, sizeof(req));
req.tid = tid;
switch (op) {
case OP_NEW:
{
char *p = params;
if (!params || !(p = strchr(params, '='))) {
fprintf(stderr, "Target name required\n");
err = -EINVAL;
goto out;
}
*p++ = '\0';
if (strcmp(params, "Name")) {
fprintf(stderr, "Target name required\n");
err = -EINVAL;
goto out;
}
req.rcmnd = C_TRGT_NEW;
strlcpy(req.u.trgt.name, p, sizeof(req.u.trgt.name));
break;
}
case OP_DELETE:
req.rcmnd = C_TRGT_DEL;
break;
case OP_UPDATE:
req.rcmnd = C_TRGT_UPDATE;
if ((err = parse_trgt_params(&req.u.trgt, params)) < 0)
goto out;
break;
case OP_SHOW:
req.rcmnd = C_TRGT_SHOW;
break;
}
err = iscsid_request(&req, NULL, 0);
if (!err && req.rcmnd == C_TRGT_SHOW)
show_iscsi_params(key_target, req.u.trgt.target_params);
out:
return err;
}
static int sess_handle(int op, u32 set, u32 tid, u64 sid, char *params)
{
int err = -EINVAL;
struct iscsi_adm_req req;
if (op == OP_NEW || op == OP_UPDATE) {
fprintf(stderr, "Unsupported.\n");
goto out;
}
if (!((set & SET_TARGET) && (set & SET_SESSION)))
goto out;
req.tid = tid;
req.sid = sid;
req.u.trgt.type = key_session;
switch (op) {
case OP_DELETE:
/* close all connections */
break;
case OP_SHOW:
req.rcmnd = C_SESS_SHOW;
err = iscsid_request(&req, NULL, 0);
if (!err)
show_iscsi_params(key_session, req.u.trgt.session_params);
break;
}
out:
return err;
}
static int parse_user_params(char *params, u32 *auth_dir, char **user,
char **pass)
{
char *p, *q;
while ((p = strsep(&params, ",")) != NULL) {
if (!*p)
continue;
if (!(q = strchr(p, '=')))
continue;
*q++ = '\0';
if (isspace(*q))
q++;
if (!strcasecmp(p, "IncomingUser")) {
if (*user)
fprintf(stderr,
"Already specified IncomingUser %s\n",
q);
*user = q;
*auth_dir = ISCSI_USER_DIR_INCOMING;
} else if (!strcasecmp(p, "OutgoingUser")) {
if (*user)
fprintf(stderr,
"Already specified OutgoingUser %s\n",
q);
*user = q;
*auth_dir = ISCSI_USER_DIR_OUTGOING;
} else if (!strcasecmp(p, "Password")) {
if (*pass)
fprintf(stderr,
"Already specified Password %s\n", q);
*pass = q;
} else {
fprintf(stderr, "Unknown parameter \"%s\"\n", p);
return -EINVAL;
}
}
return 0;
}
static void show_account(int auth_dir, char *user, char *pass)
{
char buf[(ISCSI_NAME_LEN + 1) * 2] = {0};
snprintf(buf, ISCSI_NAME_LEN, "%s", user);
if (pass)
snprintf(buf + strlen(buf), ISCSI_NAME_LEN, " %s", pass);
printf("%sUser %s\n", (auth_dir == ISCSI_USER_DIR_INCOMING) ?
"Incoming" : "Outgoing", buf);
}
static int user_handle_show_user(struct iscsi_adm_req *req, char *user)
{
int err;
req->rcmnd = C_ACCT_SHOW;
strlcpy(req->u.acnt.u.user.name, user, sizeof(req->u.acnt.u.user.name));
err = iscsid_request(req, NULL, 0);
if (!err)
show_account(req->u.acnt.auth_dir, req->u.acnt.u.user.name,
req->u.acnt.u.user.pass);
return err;
}
static int user_handle_show_list(struct iscsi_adm_req *req)
{
int i, err, retry;
size_t buf_sz = 0;
char *buf;
req->u.acnt.auth_dir = ISCSI_USER_DIR_INCOMING;
req->rcmnd = C_ACCT_LIST;
do {
retry = 0;
buf_sz = buf_sz ? buf_sz : ISCSI_NAME_LEN;
buf = calloc(buf_sz, sizeof(*buf));
if (!buf) {
fprintf(stderr, "Memory allocation failed\n");
return -ENOMEM;
}
req->u.acnt.u.list.alloc_len = buf_sz;
err = iscsid_request(req, buf, buf_sz);
if (err) {
free(buf);
break;
}
if (req->u.acnt.u.list.overflow) {
buf_sz = ISCSI_NAME_LEN * (req->u.acnt.u.list.count +
req->u.acnt.u.list.overflow);
retry = 1;
free(buf);
continue;
}
for (i = 0; i < req->u.acnt.u.list.count; i++)
show_account(req->u.acnt.auth_dir,
&buf[i * ISCSI_NAME_LEN], NULL);
if (req->u.acnt.auth_dir == ISCSI_USER_DIR_INCOMING) {
req->u.acnt.auth_dir = ISCSI_USER_DIR_OUTGOING;
buf_sz = 0;
retry = 1;
}
free(buf);
} while (retry);
return err;
}
static int user_handle_show(struct iscsi_adm_req *req, char *user, char *pass)
{
if (pass)
fprintf(stderr, "Ignoring specified password\n");
if (user)
return user_handle_show_user(req, user);
else
return user_handle_show_list(req);
}
static int user_handle_new(struct iscsi_adm_req *req, char *user, char *pass)
{
if (!user || !pass) {
fprintf(stderr, "Username and password must be specified\n");
return -EINVAL;
}
req->rcmnd = C_ACCT_NEW;
strlcpy(req->u.acnt.u.user.name, user, sizeof(req->u.acnt.u.user.name));
strlcpy(req->u.acnt.u.user.pass, pass, sizeof(req->u.acnt.u.user.pass));
return iscsid_request(req, NULL, 0);
}
static int user_handle_del(struct iscsi_adm_req *req, char *user, char *pass)
{
if (!user) {
fprintf(stderr, "Username must be specified\n");
return -EINVAL;
}
if (pass)
fprintf(stderr, "Ignoring specified password\n");
req->rcmnd = C_ACCT_DEL;
strlcpy(req->u.acnt.u.user.name, user, sizeof(req->u.acnt.u.user.name));
return iscsid_request(req, NULL, 0);
}
static int user_handle(int op, u32 set, u32 tid, char *params)
{
int err = -EINVAL;
char *user = NULL, *pass = NULL;
struct iscsi_adm_req req;
static user_handle_fn_t *user_handle_fn[] = {
user_handle_new,
user_handle_del,
NULL,
user_handle_show,
}, *fn;
if (set & ~(SET_TARGET | SET_USER))
goto out;
memset(&req, 0, sizeof(req));
req.tid = tid;
err = parse_user_params(params, &req.u.acnt.auth_dir, &user, &pass);
if (err)
goto out;
if ((op >= sizeof(user_handle_fn)/sizeof(user_handle_fn[0])) ||
((fn = user_handle_fn[op]) == NULL)) {
fprintf(stderr, "Unsupported\n");
goto out;
}
err = fn(&req, user, pass);
out:
return err;
}
static int conn_handle(int op, u32 set, u32 tid, u64 sid, u32 cid, char *params)
{
int err = -EINVAL;
struct iscsi_adm_req req;
if (op == OP_NEW || op == OP_UPDATE) {
fprintf(stderr, "Unsupported.\n");
goto out;
}
if (!((set & SET_TARGET) && (set & SET_SESSION) && (set & SET_CONNECTION)))
goto out;
memset(&req, 0, sizeof(req));
req.tid = tid;
req.sid = sid;
req.cid = cid;
switch (op) {
case OP_DELETE:
req.rcmnd = C_CONN_DEL;
break;
case OP_SHOW:
req.rcmnd = C_CONN_SHOW;
/* TODO */
break;
}
err = iscsid_request(&req, NULL, 0);
out:
return err;
}
int main(int argc, char **argv)
{
int ch, longindex;
int err = -EINVAL, op = -1;
u32 tid = 0, cid = 0, set = 0;
u64 sid = 0;
char *params = NULL;
while ((ch = getopt_long(argc, argv, "o:t:s:c:l:p:uvh",
long_options, &longindex)) >= 0) {
switch (ch) {
case 'o':
op = str_to_op(optarg);
break;
case 't':
tid = strtoul(optarg, NULL, 0);
set |= SET_TARGET;
break;
case 's':
sid = strtoull(optarg, NULL, 0);
set |= SET_SESSION;
break;
case 'c':
cid = strtoul(optarg, NULL, 0);
set |= SET_CONNECTION;
break;
case 'p':
params = optarg;
break;
case 'u':
set |= SET_USER;
break;
case 'v':
printf("%s version %s\n", program_name, ISCSI_VERSION_STRING);
exit(0);
break;
case 'h':
usage(0);
break;
default:
usage(-1);
}
}
if (op < 0) {
fprintf(stderr, "You must specify the operation type\n");
goto out;
}
if (optind < argc) {
fprintf(stderr, "unrecognized: ");
while (optind < argc)
fprintf(stderr, "%s", argv[optind++]);
fprintf(stderr, "\n");
usage(-1);
}
if (set & SET_USER)
err = user_handle(op, set, tid, params);
else if (set & SET_CONNECTION)
err = conn_handle(op, set, tid, sid, cid, params);
else if (set & SET_SESSION)
err = sess_handle(op, set, tid, sid, params);
else if (set & SET_TARGET)
err = trgt_handle(op, set, tid, params);
else
usage(-1);
out:
return err;
}