00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029 #include "asterisk.h"
00030
00031 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 409886 $")
00032
00033 #include "asterisk/module.h"
00034 #include "asterisk/channel.h"
00035 #include "asterisk/pbx.h"
00036 #include "asterisk/utils.h"
00037 #include "asterisk/linkedlists.h"
00038 #include "asterisk/presencestate.h"
00039 #include "asterisk/cli.h"
00040 #include "asterisk/astdb.h"
00041 #include "asterisk/app.h"
00042 #ifdef TEST_FRAMEWORK
00043 #include "asterisk/test.h"
00044 #include "asterisk/event.h"
00045 #include <semaphore.h>
00046 #endif
00047
00048
00049
00050
00051
00052
00053
00054
00055
00056
00057
00058
00059
00060
00061
00062
00063
00064
00065
00066
00067
00068
00069
00070
00071
00072
00073
00074
00075
00076
00077
00078
00079
00080
00081
00082
00083
00084
00085
00086
00087
00088
00089
00090
00091
00092
00093
00094
00095
00096
00097
00098
00099
00100
00101 static const char astdb_family[] = "CustomPresence";
00102
00103 static int presence_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
00104 {
00105 int state;
00106 char *message = NULL;
00107 char *subtype = NULL;
00108 char *parse;
00109 int base64encode = 0;
00110 AST_DECLARE_APP_ARGS(args,
00111 AST_APP_ARG(provider);
00112 AST_APP_ARG(field);
00113 AST_APP_ARG(options);
00114 );
00115
00116 if (ast_strlen_zero(data)) {
00117 ast_log(LOG_WARNING, "PRESENCE_STATE reading requires an argument \n");
00118 return -1;
00119 }
00120
00121 parse = ast_strdupa(data);
00122
00123 AST_STANDARD_APP_ARGS(args, parse);
00124
00125 if (ast_strlen_zero(args.provider) || ast_strlen_zero(args.field)) {
00126 ast_log(LOG_WARNING, "PRESENCE_STATE reading requires both presence provider and presence field arguments. \n");
00127 return -1;
00128 }
00129
00130 state = ast_presence_state_nocache(args.provider, &subtype, &message);
00131 if (state == AST_PRESENCE_INVALID) {
00132 ast_log(LOG_WARNING, "PRESENCE_STATE unknown \n");
00133 return -1;
00134 }
00135
00136 if (!(ast_strlen_zero(args.options)) && (strchr(args.options, 'e'))) {
00137 base64encode = 1;
00138 }
00139
00140 if (!ast_strlen_zero(subtype) && !strcasecmp(args.field, "subtype")) {
00141 if (base64encode) {
00142 ast_base64encode(buf, (unsigned char *) subtype, strlen(subtype), len);
00143 } else {
00144 ast_copy_string(buf, subtype, len);
00145 }
00146 } else if (!ast_strlen_zero(message) && !strcasecmp(args.field, "message")) {
00147 if (base64encode) {
00148 ast_base64encode(buf, (unsigned char *) message, strlen(message), len);
00149 } else {
00150 ast_copy_string(buf, message, len);
00151 }
00152
00153 } else if (!strcasecmp(args.field, "value")) {
00154 ast_copy_string(buf, ast_presence_state2str(state), len);
00155 }
00156
00157 ast_free(message);
00158 ast_free(subtype);
00159
00160 return 0;
00161 }
00162
00163 static int parse_data(char *data, enum ast_presence_state *state, char **subtype, char **message, char **options)
00164 {
00165 char *state_str;
00166
00167
00168 *subtype = "";
00169 *message = "";
00170 *options = "";
00171
00172 state_str = strsep(&data, ",");
00173 if (ast_strlen_zero(state_str)) {
00174 return -1;
00175 }
00176
00177 *state = ast_presence_state_val(state_str);
00178
00179
00180 if (*state == AST_PRESENCE_INVALID) {
00181 ast_log(LOG_WARNING, "Unknown presence state value %s\n", state_str);
00182 return -1;
00183 }
00184
00185 if (!(*subtype = strsep(&data,","))) {
00186 *subtype = "";
00187 return 0;
00188 }
00189
00190 if (!(*message = strsep(&data, ","))) {
00191 *message = "";
00192 return 0;
00193 }
00194
00195 if (!(*options = strsep(&data, ","))) {
00196 *options = "";
00197 return 0;
00198 }
00199
00200 if (!ast_strlen_zero(*options) && !(strchr(*options, 'e'))) {
00201 ast_log(LOG_NOTICE, "Invalid options '%s'\n", *options);
00202 return -1;
00203 }
00204
00205 return 0;
00206 }
00207
00208 static int presence_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
00209 {
00210 size_t len = strlen("CustomPresence:");
00211 char *tmp = data;
00212 char *args = ast_strdupa(value);
00213 enum ast_presence_state state;
00214 char *options, *message, *subtype;
00215
00216 if (strncasecmp(data, "CustomPresence:", len)) {
00217 ast_log(LOG_WARNING, "The PRESENCE_STATE function can only set CustomPresence: presence providers.\n");
00218 return -1;
00219 }
00220 data += len;
00221 if (ast_strlen_zero(data)) {
00222 ast_log(LOG_WARNING, "PRESENCE_STATE function called with no custom device name!\n");
00223 return -1;
00224 }
00225
00226 if (parse_data(args, &state, &subtype, &message, &options)) {
00227 ast_log(LOG_WARNING, "Invalid arguments to PRESENCE_STATE\n");
00228 return -1;
00229 }
00230
00231 ast_db_put(astdb_family, data, value);
00232
00233 ast_presence_state_changed_literal(state, subtype, message, tmp);
00234
00235 return 0;
00236 }
00237
00238 static enum ast_presence_state custom_presence_callback(const char *data, char **subtype, char **message)
00239 {
00240 char buf[1301] = "";
00241 enum ast_presence_state state;
00242 char *_options;
00243 char *_message;
00244 char *_subtype;
00245
00246 ast_db_get(astdb_family, data, buf, sizeof(buf));
00247
00248 if (parse_data(buf, &state, &_subtype, &_message, &_options)) {
00249 return AST_PRESENCE_INVALID;
00250 }
00251
00252 if ((strchr(_options, 'e'))) {
00253 char tmp[1301];
00254 if (ast_strlen_zero(_subtype)) {
00255 *subtype = NULL;
00256 } else {
00257 memset(tmp, 0, sizeof(tmp));
00258 ast_base64decode((unsigned char *) tmp, _subtype, sizeof(tmp) - 1);
00259 *subtype = ast_strdup(tmp);
00260 }
00261
00262 if (ast_strlen_zero(_message)) {
00263 *message = NULL;
00264 } else {
00265 memset(tmp, 0, sizeof(tmp));
00266 ast_base64decode((unsigned char *) tmp, _message, sizeof(tmp) - 1);
00267 *message = ast_strdup(tmp);
00268 }
00269 } else {
00270 *subtype = ast_strlen_zero(_subtype) ? NULL : ast_strdup(_subtype);
00271 *message = ast_strlen_zero(_message) ? NULL : ast_strdup(_message);
00272 }
00273 return state;
00274 }
00275
00276 static struct ast_custom_function presence_function = {
00277 .name = "PRESENCE_STATE",
00278 .read = presence_read,
00279 .write = presence_write,
00280 };
00281
00282 static char *handle_cli_presencestate_list(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
00283 {
00284 struct ast_db_entry *db_entry, *db_tree;
00285
00286 switch (cmd) {
00287 case CLI_INIT:
00288 e->command = "presencestate list";
00289 e->usage =
00290 "Usage: presencestate list\n"
00291 " List all custom presence states that have been set by using\n"
00292 " the PRESENCE_STATE dialplan function.\n";
00293 return NULL;
00294 case CLI_GENERATE:
00295 return NULL;
00296 }
00297
00298 if (a->argc != e->args) {
00299 return CLI_SHOWUSAGE;
00300 }
00301
00302 ast_cli(a->fd, "\n"
00303 "---------------------------------------------------------------------\n"
00304 "--- Custom Presence States ------------------------------------------\n"
00305 "---------------------------------------------------------------------\n"
00306 "---\n");
00307
00308 db_entry = db_tree = ast_db_gettree(astdb_family, NULL);
00309 if (!db_entry) {
00310 ast_cli(a->fd, "No custom presence states defined\n");
00311 return CLI_SUCCESS;
00312 }
00313 for (; db_entry; db_entry = db_entry->next) {
00314 const char *object_name = strrchr(db_entry->key, '/') + 1;
00315 char state_info[1301];
00316 enum ast_presence_state state;
00317 char *subtype;
00318 char *message;
00319 char *options;
00320
00321 ast_copy_string(state_info, db_entry->data, sizeof(state_info));
00322 if (parse_data(state_info, &state, &subtype, &message, &options)) {
00323 ast_log(LOG_WARNING, "Invalid CustomPresence entry %s encountered\n", db_entry->data);
00324 continue;
00325 }
00326
00327 if (object_name <= (const char *) 1) {
00328 continue;
00329 }
00330 ast_cli(a->fd, "--- Name: 'CustomPresence:%s'\n"
00331 " --- State: '%s'\n"
00332 " --- Subtype: '%s'\n"
00333 " --- Message: '%s'\n"
00334 " --- Base64 Encoded: '%s'\n"
00335 "---\n",
00336 object_name,
00337 ast_presence_state2str(state),
00338 subtype,
00339 message,
00340 AST_CLI_YESNO(strchr(options, 'e')));
00341 }
00342 ast_db_freetree(db_tree);
00343 db_tree = NULL;
00344
00345 ast_cli(a->fd,
00346 "---------------------------------------------------------------------\n"
00347 "---------------------------------------------------------------------\n"
00348 "\n");
00349
00350 return CLI_SUCCESS;
00351 }
00352
00353 static char *handle_cli_presencestate_change(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
00354 {
00355 size_t len;
00356 const char *dev, *state, *full_dev;
00357 enum ast_presence_state state_val;
00358 char *message;
00359 char *subtype;
00360 char *options;
00361 char *args;
00362
00363 switch (cmd) {
00364 case CLI_INIT:
00365 e->command = "presencestate change";
00366 e->usage =
00367 "Usage: presencestate change <entity> <state>[,<subtype>[,message[,options]]]\n"
00368 " Change a custom presence to a new state.\n"
00369 " The possible values for the state are:\n"
00370 "NOT_SET | UNAVAILABLE | AVAILABLE | AWAY | XA | CHAT | DND\n"
00371 "Optionally, a custom subtype and message may be provided, along with any options\n"
00372 "accepted by func_presencestate. If the subtype or message provided contain spaces,\n"
00373 "be sure to enclose the data in quotation marks (\"\")\n"
00374 "\n"
00375 "Examples:\n"
00376 " presencestate change CustomPresence:mystate1 AWAY\n"
00377 " presencestate change CustomPresence:mystate1 AVAILABLE\n"
00378 " presencestate change CustomPresence:mystate1 \"Away,upstairs,eating lunch\"\n"
00379 " \n";
00380 return NULL;
00381 case CLI_GENERATE:
00382 {
00383 static const char * const cmds[] = { "NOT_SET", "UNAVAILABLE", "AVAILABLE", "AWAY",
00384 "XA", "CHAT", "DND", NULL };
00385
00386 if (a->pos == e->args + 1) {
00387 return ast_cli_complete(a->word, cmds, a->n);
00388 }
00389
00390 return NULL;
00391 }
00392 }
00393
00394 if (a->argc != e->args + 2) {
00395 return CLI_SHOWUSAGE;
00396 }
00397
00398 len = strlen("CustomPresence:");
00399 full_dev = dev = a->argv[e->args];
00400 state = a->argv[e->args + 1];
00401
00402 if (strncasecmp(dev, "CustomPresence:", len)) {
00403 ast_cli(a->fd, "The presencestate command can only be used to set 'CustomPresence:' presence state!\n");
00404 return CLI_FAILURE;
00405 }
00406
00407 dev += len;
00408 if (ast_strlen_zero(dev)) {
00409 return CLI_SHOWUSAGE;
00410 }
00411
00412 args = ast_strdupa(state);
00413 if (parse_data(args, &state_val, &subtype, &message, &options)) {
00414 return CLI_SHOWUSAGE;
00415 }
00416
00417 if (state_val == AST_PRESENCE_NOT_SET) {
00418 return CLI_SHOWUSAGE;
00419 }
00420
00421 ast_cli(a->fd, "Changing %s to %s\n", dev, args);
00422
00423 ast_db_put(astdb_family, dev, state);
00424
00425 ast_presence_state_changed_literal(state_val, subtype, message, full_dev);
00426
00427 return CLI_SUCCESS;
00428 }
00429
00430 static struct ast_cli_entry cli_funcpresencestate[] = {
00431 AST_CLI_DEFINE(handle_cli_presencestate_list, "List currently know custom presence states"),
00432 AST_CLI_DEFINE(handle_cli_presencestate_change, "Change a custom presence state"),
00433 };
00434
00435 #ifdef TEST_FRAMEWORK
00436
00437 struct test_string {
00438 char *parse_string;
00439 struct {
00440 int value;
00441 const char *subtype;
00442 const char *message;
00443 const char *options;
00444 } outputs;
00445 };
00446
00447 AST_TEST_DEFINE(test_valid_parse_data)
00448 {
00449 int i;
00450 enum ast_presence_state state;
00451 char *subtype;
00452 char *message;
00453 char *options;
00454 enum ast_test_result_state res = AST_TEST_PASS;
00455
00456 struct test_string tests [] = {
00457 { "away",
00458 { AST_PRESENCE_AWAY,
00459 "",
00460 "",
00461 ""
00462 }
00463 },
00464 { "not_set",
00465 { AST_PRESENCE_NOT_SET,
00466 "",
00467 "",
00468 ""
00469 }
00470 },
00471 { "unavailable",
00472 { AST_PRESENCE_UNAVAILABLE,
00473 "",
00474 "",
00475 ""
00476 }
00477 },
00478 { "available",
00479 { AST_PRESENCE_AVAILABLE,
00480 "",
00481 "",
00482 ""
00483 }
00484 },
00485 { "xa",
00486 { AST_PRESENCE_XA,
00487 "",
00488 "",
00489 ""
00490 }
00491 },
00492 { "chat",
00493 { AST_PRESENCE_CHAT,
00494 "",
00495 "",
00496 ""
00497 }
00498 },
00499 { "dnd",
00500 { AST_PRESENCE_DND,
00501 "",
00502 "",
00503 ""
00504 }
00505 },
00506 { "away,down the hall",
00507 { AST_PRESENCE_AWAY,
00508 "down the hall",
00509 "",
00510 ""
00511 }
00512 },
00513 { "away,down the hall,Quarterly financial meeting",
00514 { AST_PRESENCE_AWAY,
00515 "down the hall",
00516 "Quarterly financial meeting",
00517 ""
00518 }
00519 },
00520 { "away,,Quarterly financial meeting",
00521 { AST_PRESENCE_AWAY,
00522 "",
00523 "Quarterly financial meeting",
00524 ""
00525 }
00526 },
00527 { "away,,,e",
00528 { AST_PRESENCE_AWAY,
00529 "",
00530 "",
00531 "e",
00532 }
00533 },
00534 { "away,down the hall,,e",
00535 { AST_PRESENCE_AWAY,
00536 "down the hall",
00537 "",
00538 "e"
00539 }
00540 },
00541 { "away,down the hall,Quarterly financial meeting,e",
00542 { AST_PRESENCE_AWAY,
00543 "down the hall",
00544 "Quarterly financial meeting",
00545 "e"
00546 }
00547 },
00548 { "away,,Quarterly financial meeting,e",
00549 { AST_PRESENCE_AWAY,
00550 "",
00551 "Quarterly financial meeting",
00552 "e"
00553 }
00554 }
00555 };
00556
00557 switch (cmd) {
00558 case TEST_INIT:
00559 info->name = "parse_valid_presence_data";
00560 info->category = "/funcs/func_presence/";
00561 info->summary = "PRESENCESTATE parsing test";
00562 info->description =
00563 "Ensure that parsing function accepts proper values, and gives proper outputs";
00564 return AST_TEST_NOT_RUN;
00565 case TEST_EXECUTE:
00566 break;
00567 }
00568
00569 for (i = 0; i < ARRAY_LEN(tests); ++i) {
00570 int parse_result;
00571 char *parse_string = ast_strdup(tests[i].parse_string);
00572 if (!parse_string) {
00573 res = AST_TEST_FAIL;
00574 break;
00575 }
00576 parse_result = parse_data(parse_string, &state, &subtype, &message, &options);
00577 if (parse_result == -1) {
00578 res = AST_TEST_FAIL;
00579 ast_free(parse_string);
00580 break;
00581 }
00582 if (tests[i].outputs.value != state ||
00583 strcmp(tests[i].outputs.subtype, subtype) ||
00584 strcmp(tests[i].outputs.message, message) ||
00585 strcmp(tests[i].outputs.options, options)) {
00586 res = AST_TEST_FAIL;
00587 ast_free(parse_string);
00588 break;
00589 }
00590 ast_free(parse_string);
00591 }
00592
00593 return res;
00594 }
00595
00596 AST_TEST_DEFINE(test_invalid_parse_data)
00597 {
00598 int i;
00599 enum ast_presence_state state;
00600 char *subtype;
00601 char *message;
00602 char *options;
00603 enum ast_test_result_state res = AST_TEST_PASS;
00604
00605 char *tests[] = {
00606 "",
00607 "bored",
00608 "away,,,i",
00609
00610
00611
00612
00613 };
00614
00615 switch (cmd) {
00616 case TEST_INIT:
00617 info->name = "parse_invalid_presence_data";
00618 info->category = "/funcs/func_presence/";
00619 info->summary = "PRESENCESTATE parsing test";
00620 info->description =
00621 "Ensure that parsing function rejects improper values";
00622 return AST_TEST_NOT_RUN;
00623 case TEST_EXECUTE:
00624 break;
00625 }
00626
00627 for (i = 0; i < ARRAY_LEN(tests); ++i) {
00628 int parse_result;
00629 char *parse_string = ast_strdup(tests[i]);
00630 if (!parse_string) {
00631 res = AST_TEST_FAIL;
00632 break;
00633 }
00634 parse_result = parse_data(parse_string, &state, &subtype, &message, &options);
00635 if (parse_result == 0) {
00636 ast_log(LOG_WARNING, "Invalid string parsing failed on %s\n", tests[i]);
00637 res = AST_TEST_FAIL;
00638 ast_free(parse_string);
00639 break;
00640 }
00641 ast_free(parse_string);
00642 }
00643
00644 return res;
00645 }
00646
00647 struct test_cb_data {
00648 enum ast_presence_state presence;
00649 const char *provider;
00650 const char *subtype;
00651 const char *message;
00652
00653 sem_t sem;
00654 };
00655
00656 static void test_cb(const struct ast_event *event, void *userdata)
00657 {
00658 struct test_cb_data *cb_data = userdata;
00659 cb_data->presence = ast_event_get_ie_uint(event, AST_EVENT_IE_PRESENCE_STATE);
00660 cb_data->provider = ast_strdup(ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_PROVIDER));
00661 cb_data->subtype = ast_strdup(ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_SUBTYPE));
00662 cb_data->message = ast_strdup(ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_MESSAGE));
00663 sem_post(&cb_data->sem);
00664 }
00665
00666
00667
00668
00669
00670
00671 AST_TEST_DEFINE(test_presence_state_change)
00672 {
00673 struct ast_event_sub *test_sub;
00674 struct test_cb_data *cb_data;
00675
00676 switch (cmd) {
00677 case TEST_INIT:
00678 info->name = "test_presence_state_change";
00679 info->category = "/funcs/func_presence/";
00680 info->summary = "presence state change subscription";
00681 info->description =
00682 "Ensure that presence state changes are communicated to subscribers";
00683 return AST_TEST_NOT_RUN;
00684 case TEST_EXECUTE:
00685 break;
00686 }
00687
00688 cb_data = ast_calloc(1, sizeof(*cb_data));
00689 if (!cb_data) {
00690 return AST_TEST_FAIL;
00691 }
00692
00693 if (!(test_sub = ast_event_subscribe(AST_EVENT_PRESENCE_STATE,
00694 test_cb, "Test presence state callbacks", cb_data, AST_EVENT_IE_END))) {
00695 return AST_TEST_FAIL;
00696 }
00697
00698 if (sem_init(&cb_data->sem, 0, 0)) {
00699 return AST_TEST_FAIL;
00700 }
00701
00702 presence_write(NULL, "PRESENCESTATE", "CustomPresence:TestPresenceStateChange", "away,down the hall,Quarterly financial meeting");
00703 sem_wait(&cb_data->sem);
00704 if (cb_data->presence != AST_PRESENCE_AWAY ||
00705 strcmp(cb_data->provider, "CustomPresence:TestPresenceStateChange") ||
00706 strcmp(cb_data->subtype, "down the hall") ||
00707 strcmp(cb_data->message, "Quarterly financial meeting")) {
00708 return AST_TEST_FAIL;
00709 }
00710
00711 ast_free((char *)cb_data->provider);
00712 ast_free((char *)cb_data->subtype);
00713 ast_free((char *)cb_data->message);
00714 ast_free((char *)cb_data);
00715
00716 ast_db_del("CustomPresence", "TestPresenceStateChange");
00717
00718 return AST_TEST_PASS;
00719 }
00720
00721 #endif
00722
00723 static int unload_module(void)
00724 {
00725 int res = 0;
00726
00727 res |= ast_custom_function_unregister(&presence_function);
00728 res |= ast_presence_state_prov_del("CustomPresence");
00729 res |= ast_cli_unregister_multiple(cli_funcpresencestate, ARRAY_LEN(cli_funcpresencestate));
00730 #ifdef TEST_FRAMEWORK
00731 AST_TEST_UNREGISTER(test_valid_parse_data);
00732 AST_TEST_UNREGISTER(test_invalid_parse_data);
00733 AST_TEST_UNREGISTER(test_presence_state_change);
00734 #endif
00735 return res;
00736 }
00737
00738 static int load_module(void)
00739 {
00740 int res = 0;
00741 struct ast_db_entry *db_entry, *db_tree;
00742
00743
00744
00745 db_entry = db_tree = ast_db_gettree(astdb_family, NULL);
00746 for (; db_entry; db_entry = db_entry->next) {
00747 const char *dev_name = strrchr(db_entry->key, '/') + 1;
00748 char state_info[1301];
00749 enum ast_presence_state state;
00750 char *message;
00751 char *subtype;
00752 char *options;
00753 if (dev_name <= (const char *) 1) {
00754 continue;
00755 }
00756 ast_copy_string(state_info, db_entry->data, sizeof(state_info));
00757 if (parse_data(state_info, &state, &subtype, &message, &options)) {
00758 ast_log(LOG_WARNING, "Invalid CustomPresence entry %s encountered\n", db_entry->data);
00759 continue;
00760 }
00761 ast_presence_state_changed(state, subtype, message, "CustomPresence:%s", dev_name);
00762 }
00763 ast_db_freetree(db_tree);
00764 db_tree = NULL;
00765
00766 res |= ast_custom_function_register(&presence_function);
00767 res |= ast_presence_state_prov_add("CustomPresence", custom_presence_callback);
00768 res |= ast_cli_register_multiple(cli_funcpresencestate, ARRAY_LEN(cli_funcpresencestate));
00769 #ifdef TEST_FRAMEWORK
00770 AST_TEST_REGISTER(test_valid_parse_data);
00771 AST_TEST_REGISTER(test_invalid_parse_data);
00772 AST_TEST_REGISTER(test_presence_state_change);
00773 #endif
00774
00775 return res;
00776 }
00777
00778 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Gets or sets a presence state in the dialplan",
00779 .load = load_module,
00780 .unload = unload_module,
00781 .load_pri = AST_MODPRI_DEVSTATE_PROVIDER,
00782 );
00783