| /* |
| * 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(¶ms, ",")) != 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(¶ms, ",")) != 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; |
| } |