dynsec.c 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893
  1. /*
  2. Copyright (c) 2020 Roger Light <roger@atchoo.org>
  3. All rights reserved. This program and the accompanying materials
  4. are made available under the terms of the Eclipse Public License 2.0
  5. and Eclipse Distribution License v1.0 which accompany this distribution.
  6. The Eclipse Public License is available at
  7. https://www.eclipse.org/legal/epl-2.0/
  8. and the Eclipse Distribution License is available at
  9. http://www.eclipse.org/org/documents/edl-v10.php.
  10. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
  11. Contributors:
  12. Roger Light - initial implementation and documentation.
  13. */
  14. #include "config.h"
  15. #include <cjson/cJSON.h>
  16. #include <stdio.h>
  17. #include <stdlib.h>
  18. #include <string.h>
  19. #include "mosquitto_ctrl.h"
  20. #include "mosquitto.h"
  21. #include "password_mosq.h"
  22. #include "get_password.h"
  23. void dynsec__print_usage(void)
  24. {
  25. printf("\nDynamic Security module\n");
  26. printf("=======================\n");
  27. printf("\nInitialisation\n--------------\n");
  28. printf("Create a new configuration file with an admin user:\n");
  29. printf(" mosquitto_ctrl dynsec init <new-config-file> <admin-username> [admin-password]\n");
  30. printf("\nGeneral\n-------\n");
  31. printf("Get ACL default access: getDefaultACLAccess\n");
  32. printf("Set ACL default access: setDefaultACLAccess <acltype> allow|deny\n");
  33. printf("Get group for anonymous clients: getAnonymousGroup\n");
  34. printf("Set group for anonymous clients: setAnonymousGroup <groupname>\n");
  35. printf("\nClients\n-------\n");
  36. printf("Create a new client: createClient <username> [-c clientid] [-p password]\n");
  37. printf("Delete a client: deleteClient <username>\n");
  38. printf("Set a client password: setClientPassword <username> [password]\n");
  39. printf("Set a client id: setClientId <username> [clientid]\n");
  40. printf("Add a role to a client: addClientRole <username> <rolename> [priority]\n");
  41. printf(" Higher priority (larger numerical value) roles are evaluated first.\n");
  42. printf("Remove role from a client: removeClientRole <username> <rolename>\n");
  43. printf("Get client information: getClient <username>\n");
  44. printf("List all clients: listClients [count [offset]]\n");
  45. printf("Enable client: enableClient <username>\n");
  46. printf("Disable client: disableClient <username>\n");
  47. printf("\nGroups\n------\n");
  48. printf("Create a new group: createGroup <groupname>\n");
  49. printf("Delete a group: deleteGroup <groupname>\n");
  50. printf("Add a role to a group: addGroupRole <groupname> <rolename> [priority]\n");
  51. printf(" Higher priority (larger numerical value) roles are evaluated first.\n");
  52. printf("Remove role from a group: removeGroupRole <groupname> <rolename>\n");
  53. printf("Add client to a group: addGroupClient <groupname> <username> [priority]\n");
  54. printf(" Priority sets the group priority for the given client only.\n");
  55. printf(" Higher priority (larger numerical value) groups are evaluated first.\n");
  56. printf("Remove client from a group: removeGroupClient <groupname> <username>\n");
  57. printf("Get group information: getGroup <groupname>\n");
  58. printf("List all groups: listGroups [count [offset]]\n");
  59. printf("\nRoles\n------\n");
  60. printf("Create a new role: createRole <rolename>\n");
  61. printf("Delete a role: deleteRole <rolename>\n");
  62. printf("Add an ACL to a role: addRoleACL <rolename> <aclspec> [priority]\n");
  63. printf(" Higher priority (larger numerical value) ACLs are evaluated first.\n");
  64. printf("Remove ACL from a role: removeRoleACL <rolename> <aclspec>\n");
  65. printf("Get role information: getRole <rolename>\n");
  66. printf("List all roles: listRoles [count [offset]]\n");
  67. printf("\naclspec: <acltype> <topicFilter> allow|deny\n");
  68. printf("acltype: publishClientSend|publishClientReceive\n");
  69. printf(" |subscribeLiteral|subscribePattern\n");
  70. printf(" |unsubscribeLiteral|unsubscribePattern\n");
  71. printf("\nFor more information see:\n");
  72. printf(" https://mosquitto.org/documentation/dynamic-security/\n\n");
  73. }
  74. cJSON *cJSON_AddIntToObject(cJSON * const object, const char * const name, int number)
  75. {
  76. char buf[30];
  77. snprintf(buf, sizeof(buf), "%d", number);
  78. return cJSON_AddRawToObject(object, name, buf);
  79. }
  80. /* ################################################################
  81. * #
  82. * # Payload callback
  83. * #
  84. * ################################################################ */
  85. static void print_list(cJSON *j_response, const char *arrayname, const char *keyname)
  86. {
  87. cJSON *j_data, *j_array, *j_elem, *j_name;
  88. j_data = cJSON_GetObjectItem(j_response, "data");
  89. if(j_data == NULL){
  90. fprintf(stderr, "Error: Invalid response from server.\n");
  91. return;
  92. }
  93. j_array = cJSON_GetObjectItem(j_data, arrayname);
  94. if(j_array == NULL || !cJSON_IsArray(j_array)){
  95. fprintf(stderr, "Error: Invalid response from server.\n");
  96. return;
  97. }
  98. cJSON_ArrayForEach(j_elem, j_array){
  99. if(cJSON_IsObject(j_elem)){
  100. j_name = cJSON_GetObjectItem(j_elem, keyname);
  101. if(j_name && cJSON_IsString(j_name)){
  102. printf("%s\n", j_name->valuestring);
  103. }
  104. }else if(cJSON_IsString(j_elem)){
  105. printf("%s\n", j_elem->valuestring);
  106. }
  107. }
  108. }
  109. static void print_roles(cJSON *j_roles, size_t slen)
  110. {
  111. bool first;
  112. cJSON *j_elem, *jtmp;
  113. if(j_roles && cJSON_IsArray(j_roles)){
  114. first = true;
  115. cJSON_ArrayForEach(j_elem, j_roles){
  116. jtmp = cJSON_GetObjectItem(j_elem, "rolename");
  117. if(jtmp && cJSON_IsString(jtmp)){
  118. if(first){
  119. first = false;
  120. printf("%-*s %s", (int)slen, "Roles:", jtmp->valuestring);
  121. }else{
  122. printf("%-*s %s", (int)slen, "", jtmp->valuestring);
  123. }
  124. jtmp = cJSON_GetObjectItem(j_elem, "priority");
  125. if(jtmp && cJSON_IsNumber(jtmp)){
  126. printf(" (priority: %d)", (int)jtmp->valuedouble);
  127. }else{
  128. printf(" (priority: -1)");
  129. }
  130. printf("\n");
  131. }
  132. }
  133. }else{
  134. printf("Roles:\n");
  135. }
  136. }
  137. static void print_client(cJSON *j_response)
  138. {
  139. cJSON *j_data, *j_client, *j_array, *j_elem, *jtmp;
  140. bool first;
  141. j_data = cJSON_GetObjectItem(j_response, "data");
  142. if(j_data == NULL || !cJSON_IsObject(j_data)){
  143. fprintf(stderr, "Error: Invalid response from server.\n");
  144. return;
  145. }
  146. j_client = cJSON_GetObjectItem(j_data, "client");
  147. if(j_client == NULL || !cJSON_IsObject(j_client)){
  148. fprintf(stderr, "Error: Invalid response from server.\n");
  149. return;
  150. }
  151. jtmp = cJSON_GetObjectItem(j_client, "username");
  152. if(jtmp == NULL || !cJSON_IsString(jtmp)){
  153. fprintf(stderr, "Error: Invalid response from server.\n");
  154. return;
  155. }
  156. printf("Username: %s\n", jtmp->valuestring);
  157. jtmp = cJSON_GetObjectItem(j_client, "clientid");
  158. if(jtmp && cJSON_IsString(jtmp)){
  159. printf("Clientid: %s\n", jtmp->valuestring);
  160. }else{
  161. printf("Clientid:\n");
  162. }
  163. jtmp = cJSON_GetObjectItem(j_client, "disabled");
  164. if(jtmp && cJSON_IsBool(jtmp)){
  165. printf("Disabled: %s\n", cJSON_IsTrue(jtmp)?"true":"false");
  166. }
  167. j_array = cJSON_GetObjectItem(j_client, "roles");
  168. print_roles(j_array, strlen("Username:"));
  169. j_array = cJSON_GetObjectItem(j_client, "groups");
  170. if(j_array && cJSON_IsArray(j_array)){
  171. first = true;
  172. cJSON_ArrayForEach(j_elem, j_array){
  173. jtmp = cJSON_GetObjectItem(j_elem, "groupname");
  174. if(jtmp && cJSON_IsString(jtmp)){
  175. if(first){
  176. printf("Groups: %s", jtmp->valuestring);
  177. first = false;
  178. }else{
  179. printf(" %s", jtmp->valuestring);
  180. }
  181. jtmp = cJSON_GetObjectItem(j_elem, "priority");
  182. if(jtmp && cJSON_IsNumber(jtmp)){
  183. printf(" (priority: %d)", (int)jtmp->valuedouble);
  184. }else{
  185. printf(" (priority: -1)");
  186. }
  187. printf("\n");
  188. }
  189. }
  190. }else{
  191. printf("Groups:\n");
  192. }
  193. }
  194. static void print_group(cJSON *j_response)
  195. {
  196. cJSON *j_data, *j_group, *j_array, *j_elem, *jtmp;
  197. bool first;
  198. j_data = cJSON_GetObjectItem(j_response, "data");
  199. if(j_data == NULL || !cJSON_IsObject(j_data)){
  200. fprintf(stderr, "Error: Invalid response from server.\n");
  201. return;
  202. }
  203. j_group = cJSON_GetObjectItem(j_data, "group");
  204. if(j_group == NULL || !cJSON_IsObject(j_group)){
  205. fprintf(stderr, "Error: Invalid response from server.\n");
  206. return;
  207. }
  208. jtmp = cJSON_GetObjectItem(j_group, "groupname");
  209. if(jtmp == NULL || !cJSON_IsString(jtmp)){
  210. fprintf(stderr, "Error: Invalid response from server.\n");
  211. return;
  212. }
  213. printf("Groupname: %s\n", jtmp->valuestring);
  214. j_array = cJSON_GetObjectItem(j_group, "roles");
  215. print_roles(j_array, strlen("Groupname:"));
  216. j_array = cJSON_GetObjectItem(j_group, "clients");
  217. if(j_array && cJSON_IsArray(j_array)){
  218. first = true;
  219. cJSON_ArrayForEach(j_elem, j_array){
  220. jtmp = cJSON_GetObjectItem(j_elem, "username");
  221. if(jtmp && cJSON_IsString(jtmp)){
  222. if(first){
  223. first = false;
  224. printf("Clients: %s\n", jtmp->valuestring);
  225. }else{
  226. printf(" %s\n", jtmp->valuestring);
  227. }
  228. }
  229. }
  230. }
  231. }
  232. static void print_role(cJSON *j_response)
  233. {
  234. cJSON *j_data, *j_role, *j_array, *j_elem, *jtmp;
  235. bool first;
  236. j_data = cJSON_GetObjectItem(j_response, "data");
  237. if(j_data == NULL || !cJSON_IsObject(j_data)){
  238. fprintf(stderr, "Error: Invalid response from server.\n");
  239. return;
  240. }
  241. j_role = cJSON_GetObjectItem(j_data, "role");
  242. if(j_role == NULL || !cJSON_IsObject(j_role)){
  243. fprintf(stderr, "Error: Invalid response from server.\n");
  244. return;
  245. }
  246. jtmp = cJSON_GetObjectItem(j_role, "rolename");
  247. if(jtmp == NULL || !cJSON_IsString(jtmp)){
  248. fprintf(stderr, "Error: Invalid response from server.\n");
  249. return;
  250. }
  251. printf("Rolename: %s\n", jtmp->valuestring);
  252. j_array = cJSON_GetObjectItem(j_role, "acls");
  253. if(j_array && cJSON_IsArray(j_array)){
  254. first = true;
  255. cJSON_ArrayForEach(j_elem, j_array){
  256. jtmp = cJSON_GetObjectItem(j_elem, "acltype");
  257. if(jtmp && cJSON_IsString(jtmp)){
  258. if(first){
  259. first = false;
  260. printf("ACLs: %-20s", jtmp->valuestring);
  261. }else{
  262. printf(" %-20s", jtmp->valuestring);
  263. }
  264. jtmp = cJSON_GetObjectItem(j_elem, "allow");
  265. if(jtmp && cJSON_IsBool(jtmp)){
  266. printf(" : %s", cJSON_IsTrue(jtmp)?"allow":"deny ");
  267. }
  268. jtmp = cJSON_GetObjectItem(j_elem, "topic");
  269. if(jtmp && cJSON_IsString(jtmp)){
  270. printf(" : %s", jtmp->valuestring);
  271. }
  272. jtmp = cJSON_GetObjectItem(j_elem, "priority");
  273. if(jtmp && cJSON_IsNumber(jtmp)){
  274. printf(" (priority: %d)", (int)jtmp->valuedouble);
  275. }else{
  276. printf(" (priority: -1)");
  277. }
  278. printf("\n");
  279. }
  280. }
  281. }
  282. }
  283. static void print_anonymous_group(cJSON *j_response)
  284. {
  285. cJSON *j_data, *j_group, *j_groupname;
  286. j_data = cJSON_GetObjectItem(j_response, "data");
  287. if(j_data == NULL || !cJSON_IsObject(j_data)){
  288. fprintf(stderr, "Error: Invalid response from server.\n");
  289. return;
  290. }
  291. j_group = cJSON_GetObjectItem(j_data, "group");
  292. if(j_group == NULL || !cJSON_IsObject(j_group)){
  293. fprintf(stderr, "Error: Invalid response from server.\n");
  294. return;
  295. }
  296. j_groupname = cJSON_GetObjectItem(j_group, "groupname");
  297. if(j_groupname == NULL || !cJSON_IsString(j_groupname)){
  298. fprintf(stderr, "Error: Invalid response from server.\n");
  299. return;
  300. }
  301. printf("%s\n", j_groupname->valuestring);
  302. }
  303. static void print_default_acl_access(cJSON *j_response)
  304. {
  305. cJSON *j_data, *j_acls, *j_acl, *j_acltype, *j_allow;
  306. j_data = cJSON_GetObjectItem(j_response, "data");
  307. if(j_data == NULL || !cJSON_IsObject(j_data)){
  308. fprintf(stderr, "Error: Invalid response from server.\n");
  309. return;
  310. }
  311. j_acls = cJSON_GetObjectItem(j_data, "acls");
  312. if(j_acls == NULL || !cJSON_IsArray(j_acls)){
  313. fprintf(stderr, "Error: Invalid response from server.\n");
  314. return;
  315. }
  316. cJSON_ArrayForEach(j_acl, j_acls){
  317. j_acltype = cJSON_GetObjectItem(j_acl, "acltype");
  318. j_allow = cJSON_GetObjectItem(j_acl, "allow");
  319. if(j_acltype == NULL || !cJSON_IsString(j_acltype)
  320. || j_allow == NULL || !cJSON_IsBool(j_allow)
  321. ){
  322. fprintf(stderr, "Error: Invalid response from server.\n");
  323. return;
  324. }
  325. printf("%-20s : %s\n", j_acltype->valuestring, cJSON_IsTrue(j_allow)?"allow":"deny");
  326. }
  327. }
  328. static void dynsec__payload_callback(struct mosq_ctrl *ctrl, long payloadlen, const void *payload)
  329. {
  330. cJSON *tree, *j_responses, *j_response, *j_command, *j_error;
  331. UNUSED(ctrl);
  332. #if CJSON_VERSION_FULL < 1007013
  333. UNUSED(payloadlen);
  334. tree = cJSON_Parse(payload);
  335. #else
  336. tree = cJSON_ParseWithLength(payload, (size_t)payloadlen);
  337. #endif
  338. if(tree == NULL){
  339. fprintf(stderr, "Error: Payload not JSON.\n");
  340. return;
  341. }
  342. j_responses = cJSON_GetObjectItem(tree, "responses");
  343. if(j_responses == NULL || !cJSON_IsArray(j_responses)){
  344. fprintf(stderr, "Error: Payload missing data.\n");
  345. cJSON_Delete(tree);
  346. return;
  347. }
  348. j_response = cJSON_GetArrayItem(j_responses, 0);
  349. if(j_response == NULL){
  350. fprintf(stderr, "Error: Payload missing data.\n");
  351. cJSON_Delete(tree);
  352. return;
  353. }
  354. j_command = cJSON_GetObjectItem(j_response, "command");
  355. if(j_command == NULL){
  356. fprintf(stderr, "Error: Payload missing data.\n");
  357. cJSON_Delete(tree);
  358. return;
  359. }
  360. j_error = cJSON_GetObjectItem(j_response, "error");
  361. if(j_error){
  362. fprintf(stderr, "%s: Error: %s\n", j_command->valuestring, j_error->valuestring);
  363. }else{
  364. if(!strcasecmp(j_command->valuestring, "listClients")){
  365. print_list(j_response, "clients", "username");
  366. }else if(!strcasecmp(j_command->valuestring, "listGroups")){
  367. print_list(j_response, "groups", "groupname");
  368. }else if(!strcasecmp(j_command->valuestring, "listRoles")){
  369. print_list(j_response, "roles", "rolename");
  370. }else if(!strcasecmp(j_command->valuestring, "getClient")){
  371. print_client(j_response);
  372. }else if(!strcasecmp(j_command->valuestring, "getGroup")){
  373. print_group(j_response);
  374. }else if(!strcasecmp(j_command->valuestring, "getRole")){
  375. print_role(j_response);
  376. }else if(!strcasecmp(j_command->valuestring, "getDefaultACLAccess")){
  377. print_default_acl_access(j_response);
  378. }else if(!strcasecmp(j_command->valuestring, "getAnonymousGroup")){
  379. print_anonymous_group(j_response);
  380. }else{
  381. /* fprintf(stderr, "%s: Success\n", j_command->valuestring); */
  382. }
  383. }
  384. cJSON_Delete(tree);
  385. }
  386. /* ################################################################
  387. * #
  388. * # Default ACL access
  389. * #
  390. * ################################################################ */
  391. static int dynsec__set_default_acl_access(int argc, char *argv[], cJSON *j_command)
  392. {
  393. char *acltype, *access;
  394. bool b_access;
  395. cJSON *j_acls, *j_acl;
  396. if(argc == 2){
  397. acltype = argv[0];
  398. access = argv[1];
  399. }else{
  400. return MOSQ_ERR_INVAL;
  401. }
  402. if(strcasecmp(acltype, "publishClientSend")
  403. && strcasecmp(acltype, "publishClientReceive")
  404. && strcasecmp(acltype, "subscribe")
  405. && strcasecmp(acltype, "unsubscribe")){
  406. return MOSQ_ERR_INVAL;
  407. }
  408. if(!strcasecmp(access, "allow")){
  409. b_access = true;
  410. }else if(!strcasecmp(access, "deny")){
  411. b_access = false;
  412. }else{
  413. fprintf(stderr, "Error: access must be \"allow\" or \"deny\".\n");
  414. return MOSQ_ERR_INVAL;
  415. }
  416. if(cJSON_AddStringToObject(j_command, "command", "setDefaultACLAccess") == NULL
  417. || (j_acls = cJSON_AddArrayToObject(j_command, "acls")) == NULL
  418. ){
  419. return MOSQ_ERR_NOMEM;
  420. }
  421. j_acl = cJSON_CreateObject();
  422. if(j_acl == NULL){
  423. return MOSQ_ERR_NOMEM;
  424. }
  425. cJSON_AddItemToArray(j_acls, j_acl);
  426. if(cJSON_AddStringToObject(j_acl, "acltype", acltype) == NULL
  427. || cJSON_AddBoolToObject(j_acl, "allow", b_access) == NULL
  428. ){
  429. return MOSQ_ERR_NOMEM;
  430. }
  431. return MOSQ_ERR_SUCCESS;
  432. }
  433. static int dynsec__get_default_acl_access(int argc, char *argv[], cJSON *j_command)
  434. {
  435. UNUSED(argc);
  436. UNUSED(argv);
  437. if(cJSON_AddStringToObject(j_command, "command", "getDefaultACLAccess") == NULL
  438. ){
  439. return MOSQ_ERR_NOMEM;
  440. }
  441. return MOSQ_ERR_SUCCESS;
  442. }
  443. /* ################################################################
  444. * #
  445. * # Init
  446. * #
  447. * ################################################################ */
  448. static cJSON *init_add_acl_to_role(cJSON *j_acls, const char *type, const char *topic)
  449. {
  450. cJSON *j_acl;
  451. j_acl = cJSON_CreateObject();
  452. if(j_acl == NULL) return NULL;
  453. if(cJSON_AddStringToObject(j_acl, "acltype", type) == NULL
  454. || cJSON_AddStringToObject(j_acl, "topic", topic) == NULL
  455. || cJSON_AddBoolToObject(j_acl, "allow", true) == NULL
  456. ){
  457. cJSON_Delete(j_acl);
  458. return NULL;
  459. }
  460. cJSON_AddItemToArray(j_acls, j_acl);
  461. return j_acl;
  462. }
  463. static cJSON *init_add_role(const char *rolename)
  464. {
  465. cJSON *j_role, *j_acls;
  466. j_role = cJSON_CreateObject();
  467. if(j_role == NULL){
  468. return NULL;
  469. }
  470. if(cJSON_AddStringToObject(j_role, "rolename", rolename) == NULL){
  471. cJSON_Delete(j_role);
  472. return NULL;
  473. }
  474. j_acls = cJSON_CreateArray();
  475. if(j_acls == NULL){
  476. cJSON_Delete(j_role);
  477. return NULL;
  478. }
  479. cJSON_AddItemToObject(j_role, "acls", j_acls);
  480. if(init_add_acl_to_role(j_acls, "publishClientSend", "$CONTROL/dynamic-security/#") == NULL
  481. || init_add_acl_to_role(j_acls, "publishClientReceive", "$CONTROL/dynamic-security/#") == NULL
  482. || init_add_acl_to_role(j_acls, "subscribePattern", "$CONTROL/dynamic-security/#") == NULL
  483. || init_add_acl_to_role(j_acls, "publishClientReceive", "$SYS/#") == NULL
  484. || init_add_acl_to_role(j_acls, "subscribePattern", "$SYS/#") == NULL
  485. || init_add_acl_to_role(j_acls, "publishClientReceive", "#") == NULL
  486. || init_add_acl_to_role(j_acls, "subscribePattern", "#") == NULL
  487. || init_add_acl_to_role(j_acls, "unsubscribePattern", "#") == NULL
  488. ){
  489. cJSON_Delete(j_role);
  490. return NULL;
  491. }
  492. return j_role;
  493. }
  494. static cJSON *init_add_client(const char *username, const char *password, const char *rolename)
  495. {
  496. cJSON *j_client, *j_roles, *j_role;
  497. struct mosquitto_pw pw;
  498. char *salt64 = NULL, *hash64 = NULL;
  499. char buf[10];
  500. memset(&pw, 0, sizeof(pw));
  501. pw.hashtype = pw_sha512_pbkdf2;
  502. if(pw__hash(password, &pw, true, PW_DEFAULT_ITERATIONS) != 0){
  503. return NULL;
  504. }
  505. if(base64__encode(pw.salt, sizeof(pw.salt), &salt64)
  506. || base64__encode(pw.password_hash, sizeof(pw.password_hash), &hash64)
  507. ){
  508. fprintf(stderr, "dynsec init: Internal error while encoding password.\n");
  509. free(salt64);
  510. free(hash64);
  511. return NULL;
  512. }
  513. j_client = cJSON_CreateObject();
  514. if(j_client == NULL){
  515. free(salt64);
  516. free(hash64);
  517. return NULL;
  518. }
  519. snprintf(buf, sizeof(buf), "%d", PW_DEFAULT_ITERATIONS);
  520. if(cJSON_AddStringToObject(j_client, "username", username) == NULL
  521. || cJSON_AddStringToObject(j_client, "textName", "Dynsec admin user") == NULL
  522. || cJSON_AddStringToObject(j_client, "password", hash64) == NULL
  523. || cJSON_AddStringToObject(j_client, "salt", salt64) == NULL
  524. || cJSON_AddRawToObject(j_client, "iterations", buf) == NULL
  525. ){
  526. free(salt64);
  527. free(hash64);
  528. cJSON_Delete(j_client);
  529. return NULL;
  530. }
  531. free(salt64);
  532. free(hash64);
  533. j_roles = cJSON_CreateArray();
  534. if(j_roles == NULL){
  535. cJSON_Delete(j_client);
  536. return NULL;
  537. }
  538. cJSON_AddItemToObject(j_client, "roles", j_roles);
  539. j_role = cJSON_CreateObject();
  540. if(j_role == NULL){
  541. cJSON_Delete(j_client);
  542. return NULL;
  543. }
  544. cJSON_AddItemToArray(j_roles, j_role);
  545. if(cJSON_AddStringToObject(j_role, "rolename", rolename) == NULL){
  546. cJSON_Delete(j_client);
  547. return NULL;
  548. }
  549. return j_client;
  550. }
  551. static cJSON *init_create(const char *username, const char *password, const char *rolename)
  552. {
  553. cJSON *tree, *j_clients, *j_client, *j_roles, *j_role;
  554. cJSON *j_default_access;
  555. tree = cJSON_CreateObject();
  556. if(tree == NULL) return NULL;
  557. if((j_clients = cJSON_AddArrayToObject(tree, "clients")) == NULL
  558. || (j_roles = cJSON_AddArrayToObject(tree, "roles")) == NULL
  559. || (j_default_access = cJSON_AddObjectToObject(tree, "defaultACLAccess")) == NULL
  560. ){
  561. cJSON_Delete(tree);
  562. return NULL;
  563. }
  564. /* Set default behaviour:
  565. * * Client can not publish to the broker by default.
  566. * * Broker *CAN* publish to the client by default.
  567. * * Client con not subscribe to topics by default.
  568. * * Client *CAN* unsubscribe from topics by default.
  569. */
  570. if(cJSON_AddBoolToObject(j_default_access, "publishClientSend", false) == NULL
  571. || cJSON_AddBoolToObject(j_default_access, "publishClientReceive", true) == NULL
  572. || cJSON_AddBoolToObject(j_default_access, "subscribe", false) == NULL
  573. || cJSON_AddBoolToObject(j_default_access, "unsubscribe", true) == NULL
  574. ){
  575. cJSON_Delete(tree);
  576. return NULL;
  577. }
  578. j_client = init_add_client(username, password, rolename);
  579. if(j_client == NULL){
  580. cJSON_Delete(tree);
  581. return NULL;
  582. }
  583. cJSON_AddItemToArray(j_clients, j_client);
  584. j_role = init_add_role(rolename);
  585. if(j_role == NULL){
  586. cJSON_Delete(tree);
  587. return NULL;
  588. }
  589. cJSON_AddItemToArray(j_roles, j_role);
  590. return tree;
  591. }
  592. /* mosquitto_ctrl dynsec init <filename> <admin-user> <admin-password> [role-name] */
  593. static int dynsec_init(int argc, char *argv[])
  594. {
  595. char *filename;
  596. char *admin_user;
  597. char *admin_password;
  598. char *json_str;
  599. cJSON *tree;
  600. FILE *fptr;
  601. char prompt[200], verify_prompt[200];
  602. char password[200];
  603. int rc;
  604. if(argc < 2){
  605. fprintf(stderr, "dynsec init: Not enough arguments - filename, or admin-user missing.\n");
  606. return MOSQ_ERR_INVAL;
  607. }
  608. if(argc > 3){
  609. fprintf(stderr, "dynsec init: Too many arguments.\n");
  610. return MOSQ_ERR_INVAL;
  611. }
  612. filename = argv[0];
  613. admin_user = argv[1];
  614. if(argc == 3){
  615. admin_password = argv[2];
  616. }else{
  617. snprintf(prompt, sizeof(prompt), "New password for %s: ", admin_user);
  618. snprintf(verify_prompt, sizeof(verify_prompt), "Reenter password for %s: ", admin_user);
  619. rc = get_password(prompt, verify_prompt, false, password, sizeof(password));
  620. if(rc){
  621. mosquitto_lib_cleanup();
  622. return -1;
  623. }
  624. admin_password = password;
  625. }
  626. fptr = fopen(filename, "rb");
  627. if(fptr){
  628. fclose(fptr);
  629. fprintf(stderr, "dynsec init: '%s' already exists. Remove the file or use a different location..\n", filename);
  630. return -1;
  631. }
  632. tree = init_create(admin_user, admin_password, "admin");
  633. if(tree == NULL){
  634. fprintf(stderr, "dynsec init: Out of memory.\n");
  635. return MOSQ_ERR_NOMEM;
  636. }
  637. json_str = cJSON_Print(tree);
  638. cJSON_Delete(tree);
  639. fptr = fopen(filename, "wb");
  640. if(fptr){
  641. fprintf(fptr, "%s", json_str);
  642. free(json_str);
  643. fclose(fptr);
  644. }else{
  645. free(json_str);
  646. fprintf(stderr, "dynsec init: Unable to open '%s' for writing.\n", filename);
  647. return -1;
  648. }
  649. printf("The client '%s' has been created in the file '%s'.\n", admin_user, filename);
  650. printf("This client is configured to allow you to administer the dynamic security plugin only.\n");
  651. printf("It does not have access to publish messages to normal topics.\n");
  652. printf("You should create your application clients to do that, for example:\n");
  653. printf(" mosquitto_ctrl <connect options> dynsec createClient <username>\n");
  654. printf(" mosquitto_ctrl <connect options> dynsec createRole <rolename>\n");
  655. printf(" mosquitto_ctrl <connect options> dynsec addRoleACL <rolename> publishClientSend my/topic [priority]\n");
  656. printf(" mosquitto_ctrl <connect options> dynsec addClientRole <username> <rolename> [priority]\n");
  657. printf("See https://mosquitto.org/documentation/dynamic-security/ for details of all commands.\n");
  658. return -1; /* Suppress client connection */
  659. }
  660. /* ################################################################
  661. * #
  662. * # Main
  663. * #
  664. * ################################################################ */
  665. int dynsec__main(int argc, char *argv[], struct mosq_ctrl *ctrl)
  666. {
  667. int rc = -1;
  668. cJSON *j_tree;
  669. cJSON *j_commands, *j_command;
  670. if(!strcasecmp(argv[0], "help")){
  671. dynsec__print_usage();
  672. return -1;
  673. }else if(!strcasecmp(argv[0], "init")){
  674. return dynsec_init(argc-1, &argv[1]);
  675. }
  676. /* The remaining commands need a network connection and JSON command. */
  677. ctrl->payload_callback = dynsec__payload_callback;
  678. ctrl->request_topic = strdup("$CONTROL/dynamic-security/v1");
  679. ctrl->response_topic = strdup("$CONTROL/dynamic-security/v1/response");
  680. if(ctrl->request_topic == NULL || ctrl->response_topic == NULL){
  681. return MOSQ_ERR_NOMEM;
  682. }
  683. j_tree = cJSON_CreateObject();
  684. if(j_tree == NULL) return MOSQ_ERR_NOMEM;
  685. j_commands = cJSON_AddArrayToObject(j_tree, "commands");
  686. if(j_commands == NULL){
  687. cJSON_Delete(j_tree);
  688. j_tree = NULL;
  689. return MOSQ_ERR_NOMEM;
  690. }
  691. j_command = cJSON_CreateObject();
  692. if(j_command == NULL){
  693. cJSON_Delete(j_tree);
  694. j_tree = NULL;
  695. return MOSQ_ERR_NOMEM;
  696. }
  697. cJSON_AddItemToArray(j_commands, j_command);
  698. if(!strcasecmp(argv[0], "setDefaultACLAccess")){
  699. rc = dynsec__set_default_acl_access(argc-1, &argv[1], j_command);
  700. }else if(!strcasecmp(argv[0], "getDefaultACLAccess")){
  701. rc = dynsec__get_default_acl_access(argc-1, &argv[1], j_command);
  702. }else if(!strcasecmp(argv[0], "createClient")){
  703. rc = dynsec_client__create(argc-1, &argv[1], j_command);
  704. }else if(!strcasecmp(argv[0], "deleteClient")){
  705. rc = dynsec_client__delete(argc-1, &argv[1], j_command);
  706. }else if(!strcasecmp(argv[0], "getClient")){
  707. rc = dynsec_client__get(argc-1, &argv[1], j_command);
  708. }else if(!strcasecmp(argv[0], "listClients")){
  709. rc = dynsec_client__list_all(argc-1, &argv[1], j_command);
  710. }else if(!strcasecmp(argv[0], "setClientId")){
  711. rc = dynsec_client__set_id(argc-1, &argv[1], j_command);
  712. }else if(!strcasecmp(argv[0], "setClientPassword")){
  713. rc = dynsec_client__set_password(argc-1, &argv[1], j_command);
  714. }else if(!strcasecmp(argv[0], "addClientRole")){
  715. rc = dynsec_client__add_remove_role(argc-1, &argv[1], j_command, argv[0]);
  716. }else if(!strcasecmp(argv[0], "removeClientRole")){
  717. rc = dynsec_client__add_remove_role(argc-1, &argv[1], j_command, argv[0]);
  718. }else if(!strcasecmp(argv[0], "enableClient")){
  719. rc = dynsec_client__enable_disable(argc-1, &argv[1], j_command, argv[0]);
  720. }else if(!strcasecmp(argv[0], "disableClient")){
  721. rc = dynsec_client__enable_disable(argc-1, &argv[1], j_command, argv[0]);
  722. }else if(!strcasecmp(argv[0], "createGroup")){
  723. rc = dynsec_group__create(argc-1, &argv[1], j_command);
  724. }else if(!strcasecmp(argv[0], "deleteGroup")){
  725. rc = dynsec_group__delete(argc-1, &argv[1], j_command);
  726. }else if(!strcasecmp(argv[0], "getGroup")){
  727. rc = dynsec_group__get(argc-1, &argv[1], j_command);
  728. }else if(!strcasecmp(argv[0], "listGroups")){
  729. rc = dynsec_group__list_all(argc-1, &argv[1], j_command);
  730. }else if(!strcasecmp(argv[0], "addGroupRole")){
  731. rc = dynsec_group__add_remove_role(argc-1, &argv[1], j_command, argv[0]);
  732. }else if(!strcasecmp(argv[0], "removeGroupRole")){
  733. rc = dynsec_group__add_remove_role(argc-1, &argv[1], j_command, argv[0]);
  734. }else if(!strcasecmp(argv[0], "addGroupClient")){
  735. rc = dynsec_group__add_remove_client(argc-1, &argv[1], j_command, argv[0]);
  736. }else if(!strcasecmp(argv[0], "removeGroupClient")){
  737. rc = dynsec_group__add_remove_client(argc-1, &argv[1], j_command, argv[0]);
  738. }else if(!strcasecmp(argv[0], "setAnonymousGroup")){
  739. rc = dynsec_group__set_anonymous(argc-1, &argv[1], j_command);
  740. }else if(!strcasecmp(argv[0], "getAnonymousGroup")){
  741. rc = dynsec_group__get_anonymous(argc-1, &argv[1], j_command);
  742. }else if(!strcasecmp(argv[0], "createRole")){
  743. rc = dynsec_role__create(argc-1, &argv[1], j_command);
  744. }else if(!strcasecmp(argv[0], "deleteRole")){
  745. rc = dynsec_role__delete(argc-1, &argv[1], j_command);
  746. }else if(!strcasecmp(argv[0], "getRole")){
  747. rc = dynsec_role__get(argc-1, &argv[1], j_command);
  748. }else if(!strcasecmp(argv[0], "listRoles")){
  749. rc = dynsec_role__list_all(argc-1, &argv[1], j_command);
  750. }else if(!strcasecmp(argv[0], "addRoleACL")){
  751. rc = dynsec_role__add_acl(argc-1, &argv[1], j_command);
  752. }else if(!strcasecmp(argv[0], "removeRoleACL")){
  753. rc = dynsec_role__remove_acl(argc-1, &argv[1], j_command);
  754. }else{
  755. fprintf(stderr, "Command '%s' not recognised.\n", argv[0]);
  756. return MOSQ_ERR_UNKNOWN;
  757. }
  758. if(rc == MOSQ_ERR_SUCCESS){
  759. ctrl->payload = cJSON_PrintUnformatted(j_tree);
  760. cJSON_Delete(j_tree);
  761. if(ctrl->payload == NULL){
  762. fprintf(stderr, "Error: Out of memory.\n");
  763. return MOSQ_ERR_NOMEM;
  764. }
  765. }
  766. return rc;
  767. }