/* If you do not want to use dc_cmdline(), this file MAY NOT included to your library */ #include #include "../src/dc_context.h" #include "../src/dc_aheader.h" #include "../src/dc_apeerstate.h" #include "../src/dc_key.h" #include "../src/dc_pgp.h" #include "../src/dc_sqlite3.h" #define UTF8_LOCK "\xF0\x9F\x94\x92" #define UTF8_BLACK_STAR "\xE2\x98\x85" #define UTF8_ROUND_PUSHPIN "\xF0\x9F\x93\x8D" /* * Reset database tables. This function is called from Core cmdline. * * Argument is a bitmask, executing single or multiple actions in one call. * * e.g. bitmask 7 triggers actions definded with bits 1, 2 and 4. */ int dc_reset_tables(dc_context_t* context, int bits) { if (context == NULL || context->magic != DC_CONTEXT_MAGIC) { return 0; } dc_log_info(context, 0, "Resetting tables (%i)...", bits); if (bits & 1) { dc_sqlite3_execute(context->sql, "DELETE FROM jobs;"); dc_log_info(context, 0, "(1) Jobs reset."); } if (bits & 2) { dc_sqlite3_execute(context->sql, "DELETE FROM acpeerstates;"); dc_log_info(context, 0, "(2) Peerstates reset."); } if (bits & 4) { dc_sqlite3_execute(context->sql, "DELETE FROM keypairs;"); dc_log_info(context, 0, "(4) Private keypairs reset."); } if (bits & 8) { dc_sqlite3_execute(context->sql, "DELETE FROM contacts WHERE id>" DC_STRINGIFY(DC_CONTACT_ID_LAST_SPECIAL) ";"); /* the other IDs are reserved - leave these rows to make sure, the IDs are not used by normal contacts*/ dc_sqlite3_execute(context->sql, "DELETE FROM chats WHERE id>" DC_STRINGIFY(DC_CHAT_ID_LAST_SPECIAL) ";"); dc_sqlite3_execute(context->sql, "DELETE FROM chats_contacts;"); dc_sqlite3_execute(context->sql, "DELETE FROM msgs WHERE id>" DC_STRINGIFY(DC_MSG_ID_LAST_SPECIAL) ";"); dc_sqlite3_execute(context->sql, "DELETE FROM config WHERE keyname LIKE 'imap.%' OR keyname LIKE 'configured%';"); dc_sqlite3_execute(context->sql, "DELETE FROM leftgrps;"); dc_log_info(context, 0, "(8) Rest but server config reset."); } context->cb(context, DC_EVENT_MSGS_CHANGED, 0, 0); return 1; } /* * Clean up the contacts table. This function is called from Core cmdline. * * All contacts not involved in a chat, not blocked and not being a deaddrop * are removed. * * Deleted contacts from the OS address book normally stay in the contacts * database. With this cleanup, they are also removed, as well as all * auto-added contacts, unless they are used in a chat or for blocking purpose. */ static int dc_cleanup_contacts(dc_context_t* context) { if (context == NULL || context->magic != DC_CONTEXT_MAGIC) { return 0; } dc_log_info(context, 0, "Cleaning up contacts ..."); dc_sqlite3_execute(context->sql, "DELETE FROM contacts WHERE id>" DC_STRINGIFY(DC_CONTACT_ID_LAST_SPECIAL) " AND blocked=0 AND NOT EXISTS (SELECT contact_id FROM chats_contacts where contacts.id = chats_contacts.contact_id) AND NOT EXISTS (select from_id from msgs WHERE msgs.from_id = contacts.id);"); return 1; } static int dc_poke_eml_file(dc_context_t* context, const char* filename) { /* mainly for testing, may be called by dc_import_spec() */ int success = 0; char* data = NULL; size_t data_bytes; if (context == NULL || context->magic != DC_CONTEXT_MAGIC) { return 0; } if (dc_read_file(context, filename, (void**)&data, &data_bytes) == 0) { goto cleanup; } dc_receive_imf(context, data, data_bytes, "import", 0, 0); /* this static function is the reason why this function is not moved to dc_imex.c */ success = 1; cleanup: free(data); return success; } static int poke_public_key(dc_context_t* context, const char* addr, const char* public_key_file) { /* mainly for testing: if the partner does not support Autocrypt, encryption is disabled as soon as the first messages comes from the partner */ dc_aheader_t* header = dc_aheader_new(); dc_apeerstate_t* peerstate = dc_apeerstate_new(context); int success = 0; if (addr==NULL || public_key_file==NULL || peerstate==NULL || header==NULL) { goto cleanup; } /* create a fake autocrypt header */ header->addr = dc_strdup(addr); header->prefer_encrypt = DC_PE_MUTUAL; if (!dc_key_set_from_file(header->public_key, public_key_file, context) || !dc_pgp_is_valid_key(context, header->public_key)) { dc_log_warning(context, 0, "No valid key found in \"%s\".", public_key_file); goto cleanup; } /* update/create peerstate */ if (dc_apeerstate_load_by_addr(peerstate, context->sql, addr)) { dc_apeerstate_apply_header(peerstate, header, time(NULL)); dc_apeerstate_save_to_db(peerstate, context->sql, 0); } else { dc_apeerstate_init_from_header(peerstate, header, time(NULL)); dc_apeerstate_save_to_db(peerstate, context->sql, 1); } success = 1; cleanup: dc_apeerstate_unref(peerstate); dc_aheader_unref(header); return success; } /** * Import a file to the database. * For testing, import a folder with eml-files, a single eml-file, e-mail plus public key and so on. * For normal importing, use dc_imex(). * * @private @memberof dc_context_t * @param context The context as created by dc_context_new(). * @param spec The file or directory to import. NULL for the last command. * @return 1=success, 0=error. */ static int poke_spec(dc_context_t* context, const char* spec) { int success = 0; char* real_spec = NULL; char* suffix = NULL; DIR* dir = NULL; struct dirent* dir_entry; int read_cnt = 0; char* name; if (context == NULL) { return 0; } if (!dc_sqlite3_is_open(context->sql)) { dc_log_error(context, 0, "Import: Database not opened."); goto cleanup; } /* if `spec` is given, remember it for later usage; if it is not given, try to use the last one */ if (spec) { real_spec = dc_strdup(spec); dc_sqlite3_set_config(context->sql, "import_spec", real_spec); } else { real_spec = dc_sqlite3_get_config(context->sql, "import_spec", NULL); /* may still NULL */ if (real_spec == NULL) { dc_log_error(context, 0, "Import: No file or folder given."); goto cleanup; } } suffix = dc_get_filesuffix_lc(real_spec); if (suffix && strcmp(suffix, "eml")==0) { /* import a single file */ if (dc_poke_eml_file(context, real_spec)) { /* errors are logged in any case */ read_cnt++; } } else if (suffix && (strcmp(suffix, "pem")==0||strcmp(suffix, "asc")==0)) { /* import a publix key */ char* separator = strchr(real_spec, ' '); if (separator==NULL) { dc_log_error(context, 0, "Import: Key files must be specified as \" \"."); goto cleanup; } *separator = 0; if (poke_public_key(context, real_spec, separator+1)) { read_cnt++; } *separator = ' '; } else { /* import a directory */ if ((dir=opendir(real_spec))==NULL) { dc_log_error(context, 0, "Import: Cannot open directory \"%s\".", real_spec); goto cleanup; } while ((dir_entry=readdir(dir))!=NULL) { name = dir_entry->d_name; /* name without path; may also be `.` or `..` */ if (strlen(name)>=4 && strcmp(&name[strlen(name)-4], ".eml")==0) { char* path_plus_name = dc_mprintf("%s/%s", real_spec, name); dc_log_info(context, 0, "Import: %s", path_plus_name); if (dc_poke_eml_file(context, path_plus_name)) { /* no abort on single errors errors are logged in any case */ read_cnt++; } free(path_plus_name); } } } dc_log_info(context, 0, "Import: %i items read from \"%s\".", read_cnt, real_spec); if (read_cnt > 0) { context->cb(context, DC_EVENT_MSGS_CHANGED, 0, 0); /* even if read_cnt>0, the number of messages added to the database may be 0. While we regard this issue using IMAP, we ignore it here. */ } success = 1; cleanup: if (dir) { closedir(dir); } free(real_spec); free(suffix); return success; } static void log_msg(dc_context_t* context, const char* prefix, dc_msg_t* msg) { dc_contact_t* contact = dc_get_contact(context, dc_msg_get_from_id(msg)); char* contact_name = dc_contact_get_name(contact); int contact_id = dc_contact_get_id(contact); const char* statestr = ""; switch (dc_msg_get_state(msg)) { case DC_STATE_OUT_PENDING: statestr = " o"; break; case DC_STATE_OUT_DELIVERED: statestr = " √"; break; case DC_STATE_OUT_MDN_RCVD: statestr = " √√"; break; case DC_STATE_OUT_FAILED: statestr = " !!"; break; } char* temp2 = dc_timestamp_to_str(dc_msg_get_timestamp(msg)); char* msgtext = dc_msg_get_text(msg); dc_log_info(context, 0, "%s#%i%s%s: %s (Contact#%i): %s %s%s%s%s [%s]", prefix, (int)dc_msg_get_id(msg), dc_msg_get_showpadlock(msg)? UTF8_LOCK : "", dc_msg_has_location(msg)? UTF8_ROUND_PUSHPIN : "", contact_name, contact_id, msgtext, dc_msg_is_starred(msg)? " " UTF8_BLACK_STAR : "", dc_msg_get_from_id(msg)==1? "" : (dc_msg_get_state(msg)==DC_STATE_IN_SEEN? "[SEEN]" : (dc_msg_get_state(msg)==DC_STATE_IN_NOTICED? "[NOTICED]":"[FRESH]")), dc_msg_is_info(msg)? "[INFO]" : "", statestr, temp2); free(msgtext); free(temp2); free(contact_name); dc_contact_unref(contact); } static void log_msglist(dc_context_t* context, dc_array_t* msglist) { int i, cnt = dc_array_get_cnt(msglist), lines_out = 0; for (i = 0; i < cnt; i++) { uint32_t msg_id = dc_array_get_id(msglist, i); if (msg_id == DC_MSG_ID_DAYMARKER) { dc_log_info(context, 0, "--------------------------------------------------------------------------------"); lines_out++; } else if (msg_id > 0) { if (lines_out==0) { dc_log_info(context, 0, "--------------------------------------------------------------------------------"); lines_out++; } dc_msg_t* msg = dc_get_msg(context, msg_id); log_msg(context, "Msg", msg); dc_msg_unref(msg); } } if (lines_out > 0) { dc_log_info(context, 0, "--------------------------------------------------------------------------------"); } } static void log_contactlist(dc_context_t* context, dc_array_t* contacts) { dc_contact_t* contact = NULL; dc_apeerstate_t* peerstate = dc_apeerstate_new(context); if (!dc_array_search_id(contacts, DC_CONTACT_ID_SELF, NULL)) { dc_array_add_id(contacts, DC_CONTACT_ID_SELF); } for (int i = 0; i < dc_array_get_cnt(contacts); i++) { uint32_t contact_id = dc_array_get_id(contacts, i); char* line = NULL; char* line2 = NULL; if ((contact=dc_get_contact(context, contact_id))!=NULL) { char* name = dc_contact_get_name(contact); char* addr = dc_contact_get_addr(contact); int verified_state = dc_contact_is_verified(contact); const char* verified_str = verified_state? (verified_state==2? " √√":" √"): ""; line = dc_mprintf("%s%s <%s>", (name&&name[0])? name : "", verified_str, (addr&&addr[0])? addr : "addr unset"); int peerstate_ok = dc_apeerstate_load_by_addr(peerstate, context->sql, addr); if (peerstate_ok && contact_id != DC_CONTACT_ID_SELF) { char* pe = NULL; switch (peerstate->prefer_encrypt) { case DC_PE_MUTUAL: pe = dc_strdup("mutual"); break; case DC_PE_NOPREFERENCE: pe = dc_strdup("no-preference"); break; case DC_PE_RESET: pe = dc_strdup("reset"); break; default: pe = dc_mprintf("unknown-value (%i)", peerstate->prefer_encrypt); break; } line2 = dc_mprintf(", prefer-encrypt=%s", pe); free(pe); } dc_contact_unref(contact); free(name); free(addr); dc_log_info(context, 0, "Contact#%i: %s%s", (int)contact_id, line, line2? line2:""); free(line); free(line2); } } dc_apeerstate_unref(peerstate); } static int s_is_auth = 0; void dc_cmdline_skip_auth() { s_is_auth = 1; } static const char* chat_prefix(const dc_chat_t* chat) { if (chat->type == DC_CHAT_TYPE_GROUP) { return "Group"; } else if (chat->type == DC_CHAT_TYPE_VERIFIED_GROUP) { return "VerifiedGroup"; } else { return "Single"; } } char* dc_cmdline(dc_context_t* context, const char* cmdline) { #define COMMAND_FAILED ((char*)1) #define COMMAND_SUCCEEDED ((char*)2) #define COMMAND_UNKNOWN ((char*)3) char* cmd = NULL, *arg1 = NULL, *ret = COMMAND_FAILED; dc_chat_t* sel_chat = NULL; if (context == NULL || cmdline == NULL || cmdline[0]==0) { goto cleanup; } if (context->cmdline_sel_chat_id) { sel_chat = dc_get_chat(context, context->cmdline_sel_chat_id); } /* split commandline into command and first argument (the first argument may contain spaces, if this is undesired we split further arguments form if below. */ cmd = dc_strdup(cmdline); arg1 = strchr(cmd, ' '); if (arg1) { *arg1 = 0; arg1++; } /* execute command */ if (strcmp(cmd, "help")==0 || strcmp(cmd, "?")==0) { if (arg1 && strcmp(arg1, "imex")==0) { ret = dc_strdup( "====================Import/Export commands==\n" "initiate-key-transfer\n" "get-setupcodebegin \n" "continue-key-transfer \n" "has-backup\n" "export-backup\n" "import-backup \n" "export-keys\n" "import-keys\n" "export-setup\n" "poke [|| ]\n" "reset \n" "stop\n" "=============================================" ); } else { ret = dc_strdup( "==========================Database commands==\n" "info\n" "open \n" "close\n" "set []\n" "get \n" "oauth2\n" "configure\n" "connect\n" "disconnect\n" "maybenetwork\n" "housekeeping\n" "help imex (Import/Export)\n" "==============================Chat commands==\n" "listchats []\n" "listarchived\n" "chat [|0]\n" "createchat \n" "createchatbymsg \n" "creategroup \n" "createverified \n" "addmember \n" "removemember \n" "groupname \n" "groupimage []\n" "chatinfo\n" "sendlocations \n" "setlocation \n" "dellocations\n" "getlocations []\n" "send \n" "sendimage []\n" "sendfile \n" "sendpoi \n" "draft []\n" "listmedia\n" "archive \n" "unarchive \n" "delchat \n" "===========================Message commands==\n" "listmsgs \n" "msginfo \n" "listfresh\n" "forward \n" "markseen \n" "star \n" "unstar \n" "delmsg \n" "===========================Contact commands==\n" "listcontacts []\n" "listverified []\n" "addcontact [] \n" "contactinfo \n" "delcontact \n" "cleanupcontacts\n" "======================================Misc.==\n" "getqr []\n" "getbadqr\n" "checkqr \n" "event \n" "fileinfo \n" "emptyserver [mvbox|inbox]\n" "clear -- clear screen\n" /* must be implemented by the caller */ "exit\n" /* must be implemented by the caller */ "=============================================" ); } } else if (!s_is_auth) { if (strcmp(cmd, "auth")==0) { char* is_pw = dc_get_config(context, "mail_pw"); if (strcmp(arg1, is_pw)==0) { s_is_auth = 1; ret = COMMAND_SUCCEEDED; } else { ret = "Bad password."; } } else { ret = dc_strdup("Please authorize yourself using: auth "); } } else if (strcmp(cmd, "auth")==0) { ret = dc_strdup("Already authorized."); } /******************************************************************************* * Database commands ******************************************************************************/ else if (strcmp(cmd, "open")==0) { if (arg1) { dc_close(context); ret = dc_open(context, arg1, NULL)? COMMAND_SUCCEEDED : COMMAND_FAILED; } else { ret = dc_strdup("ERROR: Argument missing."); } } else if (strcmp(cmd, "close")==0) { dc_close(context); ret = COMMAND_SUCCEEDED; } else if (strcmp(cmd, "initiate-key-transfer")==0) { char* setup_code = dc_initiate_key_transfer(context); ret = setup_code? dc_mprintf("Setup code for the transferred setup message: %s", setup_code) : COMMAND_FAILED; free(setup_code); } else if (strcmp(cmd, "get-setupcodebegin")==0) { if (arg1) { uint32_t msg_id = (uint32_t)atoi(arg1); dc_msg_t* msg = dc_get_msg(context, msg_id); if (dc_msg_is_setupmessage(msg)) { char* setupcodebegin = dc_msg_get_setupcodebegin(msg); ret = dc_mprintf("The setup code for setup message Msg#%i starts with: %s", msg_id, setupcodebegin); free(setupcodebegin); } else { ret = dc_mprintf("ERROR: Msg#%i is no setup message.", msg_id); } dc_msg_unref(msg); } else { ret = dc_strdup("ERROR: Argument missing."); } } else if (strcmp(cmd, "continue-key-transfer")==0) { char* arg2 = NULL; if (arg1) { arg2 = strrchr(arg1, ' '); } if (arg1 && arg2) { *arg2 = 0; arg2++; ret = dc_continue_key_transfer(context, atoi(arg1), arg2)? COMMAND_SUCCEEDED : COMMAND_FAILED; } else { ret = dc_strdup("ERROR: Arguments expected."); } } else if (strcmp(cmd, "has-backup")==0) { ret = dc_imex_has_backup(context, context->blobdir); if (ret == NULL) { ret = dc_strdup("No backup found."); } } else if (strcmp(cmd, "export-backup")==0) { dc_imex(context, DC_IMEX_EXPORT_BACKUP, context->blobdir, NULL); ret = COMMAND_SUCCEEDED; } else if (strcmp(cmd, "import-backup")==0) { if (arg1) { dc_imex(context, DC_IMEX_IMPORT_BACKUP, arg1, NULL); ret = COMMAND_SUCCEEDED; } else { ret = dc_strdup("ERROR: Argument missing."); } } else if (strcmp(cmd, "export-keys")==0) { dc_imex(context, DC_IMEX_EXPORT_SELF_KEYS, context->blobdir, NULL); ret = COMMAND_SUCCEEDED; } else if (strcmp(cmd, "import-keys")==0) { dc_imex(context, DC_IMEX_IMPORT_SELF_KEYS, context->blobdir, NULL); ret = COMMAND_SUCCEEDED; } else if (strcmp(cmd, "export-setup")==0) { char* setup_code = dc_create_setup_code(context); char* file_name = dc_mprintf("%s/autocrypt-setup-message.html", context->blobdir); char* file_content = NULL; if ((file_content=dc_render_setup_file(context, setup_code)) != NULL && dc_write_file(context, file_name, file_content, strlen(file_content))) { ret = dc_mprintf("Setup message written to: %s\nSetup code: %s", file_name, setup_code); } else { ret = COMMAND_FAILED; } free(file_content); free(file_name); free(setup_code); } else if (strcmp(cmd, "poke")==0) { ret = poke_spec(context, arg1)? COMMAND_SUCCEEDED : COMMAND_FAILED; } else if (strcmp(cmd, "reset")==0) { if (arg1) { int bits = atoi(arg1); if (bits > 15) { ret = dc_strdup("ERROR: must be lower than 16."); } else { ret = dc_reset_tables(context, bits)? COMMAND_SUCCEEDED : COMMAND_FAILED; } } else { ret = dc_strdup("ERROR: Argument missing: 1=jobs, 2=peerstates, 4=private keys, 8=rest but server config"); } } else if (strcmp(cmd, "stop")==0) { dc_stop_ongoing_process(context); ret = COMMAND_SUCCEEDED; } else if (strcmp(cmd, "set")==0) { if (arg1) { char* arg2 = strchr(arg1, ' '); if (arg2) { *arg2 = 0; arg2++; } ret = dc_set_config(context, arg1, arg2)? COMMAND_SUCCEEDED : COMMAND_FAILED; } else { ret = dc_strdup("ERROR: Argument missing."); } } else if (strcmp(cmd, "get")==0) { if (arg1) { char* val = dc_get_config(context, arg1); ret = dc_mprintf("%s=%s", arg1, val); free(val); } else { ret = dc_strdup("ERROR: Argument missing."); } } else if (strcmp(cmd, "info")==0) { ret = dc_get_info(context); if (ret == NULL) { ret = COMMAND_FAILED; } } else if (strcmp(cmd, "maybenetwork")==0) { dc_maybe_network(context); ret = COMMAND_SUCCEEDED; } else if (strcmp(cmd, "housekeeping")==0) { dc_housekeeping(context); ret = COMMAND_SUCCEEDED; } /******************************************************************************* * Chat commands ******************************************************************************/ else if (strcmp(cmd, "listchats")==0 || strcmp(cmd, "listarchived")==0 || strcmp(cmd, "chats")==0) { int listflags = strcmp(cmd, "listarchived")==0? DC_GCL_ARCHIVED_ONLY : 0; dc_chatlist_t* chatlist = dc_get_chatlist(context, listflags, arg1, 0); if (chatlist) { int i, cnt = dc_chatlist_get_cnt(chatlist); if (cnt>0) { dc_log_info(context, 0, "================================================================================"); for (i = cnt-1; i >= 0; i--) { dc_chat_t* chat = dc_get_chat(context, dc_chatlist_get_chat_id(chatlist, i)); char* temp_subtitle = dc_chat_get_subtitle(chat); char* temp_name = dc_chat_get_name(chat); dc_log_info(context, 0, "%s#%i: %s [%s] [%i fresh]", chat_prefix(chat), (int)dc_chat_get_id(chat), temp_name, temp_subtitle, (int)dc_get_fresh_msg_cnt(context, dc_chat_get_id(chat))); free(temp_subtitle); free(temp_name); dc_lot_t* lot = dc_chatlist_get_summary(chatlist, i, chat); const char* statestr = ""; if (dc_chat_get_archived(chat)) { statestr = " [Archived]"; } else switch (dc_lot_get_state(lot)) { case DC_STATE_OUT_PENDING: statestr = " o"; break; case DC_STATE_OUT_DELIVERED: statestr = " √"; break; case DC_STATE_OUT_MDN_RCVD: statestr = " √√"; break; case DC_STATE_OUT_FAILED: statestr = " !!"; break; } char* timestr = dc_timestamp_to_str(dc_lot_get_timestamp(lot)); char* text1 = dc_lot_get_text1(lot); char* text2 = dc_lot_get_text2(lot); dc_log_info(context, 0, "%s%s%s%s [%s]%s", text1? text1 : "", text1? ": " : "", text2? text2 : "", statestr, timestr, dc_chat_is_sending_locations(chat)? UTF8_ROUND_PUSHPIN : "" ); free(text1); free(text2); free(timestr); dc_lot_unref(lot); dc_chat_unref(chat); dc_log_info(context, 0, "================================================================================"); } } if (dc_is_sending_locations_to_chat(context, 0)) { dc_log_info(context, 0, "Location streaming enabled."); } ret = dc_mprintf("%i chats.", (int)cnt); dc_chatlist_unref(chatlist); } else { ret = COMMAND_FAILED; } } else if (strcmp(cmd, "chat")==0) { if (arg1 && arg1[0]) { /* select a chat (argument 1 = ID of chat to select) */ if (sel_chat) { dc_chat_unref(sel_chat); sel_chat = NULL; } context->cmdline_sel_chat_id = atoi(arg1); sel_chat = dc_get_chat(context, context->cmdline_sel_chat_id); /* may be NULL */ if (sel_chat==NULL) { context->cmdline_sel_chat_id = 0; } } /* show chat */ if (sel_chat) { dc_array_t* msglist = dc_get_chat_msgs(context, dc_chat_get_id(sel_chat), DC_GCM_ADDDAYMARKER, 0); char* temp2 = dc_chat_get_subtitle(sel_chat); char* temp_name = dc_chat_get_name(sel_chat); dc_log_info(context, 0, "%s#%i: %s [%s]%s", chat_prefix(sel_chat), dc_chat_get_id(sel_chat), temp_name, temp2, dc_chat_is_sending_locations(sel_chat)? UTF8_ROUND_PUSHPIN : ""); free(temp_name); free(temp2); if (msglist) { log_msglist(context, msglist); dc_array_unref(msglist); } dc_msg_t* draft = dc_get_draft(context, dc_chat_get_id(sel_chat)); if (draft) { log_msg(context, "Draft", draft); dc_msg_unref(draft); } ret = dc_mprintf("%i messages.", dc_get_msg_cnt(context, dc_chat_get_id(sel_chat))); dc_marknoticed_chat(context, dc_chat_get_id(sel_chat)); } else { ret = dc_strdup("No chat selected."); } } else if (strcmp(cmd, "createchat")==0) { if (arg1) { int contact_id = atoi(arg1); int chat_id = dc_create_chat_by_contact_id(context, contact_id); ret = chat_id!=0? dc_mprintf("Single#%lu created successfully.", chat_id) : COMMAND_FAILED; } else { ret = dc_strdup("ERROR: Argument missing."); } } else if (strcmp(cmd, "createchatbymsg")==0) { if (arg1) { int msg_id = atoi(arg1); int chat_id = dc_create_chat_by_msg_id(context, msg_id); if (chat_id != 0) { dc_chat_t* chat = dc_get_chat(context, chat_id); ret = dc_mprintf("%s#%lu created successfully.", chat_prefix(chat), chat_id); dc_chat_unref(chat); } else { ret = COMMAND_FAILED; } } else { ret = dc_strdup("ERROR: Argument missing."); } } else if (strcmp(cmd, "creategroup")==0) { if (arg1) { int chat_id = dc_create_group_chat(context, 0, arg1); ret = chat_id!=0? dc_mprintf("Group#%lu created successfully.", chat_id) : COMMAND_FAILED; } else { ret = dc_strdup("ERROR: Argument missing."); } } else if (strcmp(cmd, "createverified")==0) { if (arg1) { int chat_id = dc_create_group_chat(context, 1, arg1); ret = chat_id!=0? dc_mprintf("VerifiedGroup#%lu created successfully.", chat_id) : COMMAND_FAILED; } else { ret = dc_strdup("ERROR: Argument missing."); } } else if (strcmp(cmd, "addmember")==0) { if (sel_chat) { if (arg1) { int contact_id = atoi(arg1); if (dc_add_contact_to_chat(context, dc_chat_get_id(sel_chat), contact_id)) { ret = dc_strdup("Contact added to chat."); } else { ret = dc_strdup("ERROR: Cannot add contact to chat."); } } else { ret = dc_strdup("ERROR: Argument missing."); } } else { ret = dc_strdup("No chat selected."); } } else if (strcmp(cmd, "removemember")==0) { if (sel_chat) { if (arg1) { int contact_id = atoi(arg1); if (dc_remove_contact_from_chat(context, dc_chat_get_id(sel_chat), contact_id)) { ret = dc_strdup("Contact added to chat."); } else { ret = dc_strdup("ERROR: Cannot remove member from chat."); } } else { ret = dc_strdup("ERROR: Argument missing."); } } else { ret = dc_strdup("No chat selected."); } } else if (strcmp(cmd, "groupname")==0) { if (sel_chat) { if (arg1 && arg1[0]) { ret = dc_set_chat_name(context, dc_chat_get_id(sel_chat), arg1)? COMMAND_SUCCEEDED : COMMAND_FAILED; } else { ret = dc_strdup("ERROR: Argument missing."); } } else { ret = dc_strdup("No chat selected."); } } else if (strcmp(cmd, "groupimage")==0) { if (sel_chat) { ret = dc_set_chat_profile_image(context, dc_chat_get_id(sel_chat), (arg1&&arg1[0])?arg1:NULL)? COMMAND_SUCCEEDED : COMMAND_FAILED; } else { ret = dc_strdup("No chat selected."); } } else if (strcmp(cmd, "chatinfo")==0) { if (sel_chat) { dc_array_t* contacts = dc_get_chat_contacts(context, dc_chat_get_id(sel_chat)); if (contacts) { dc_log_info(context, 0, "Memberlist:"); log_contactlist(context, contacts); ret = dc_mprintf("%i contacts\nLocation streaming: %i", (int)dc_array_get_cnt(contacts), dc_is_sending_locations_to_chat(context, dc_chat_get_id(sel_chat))); dc_array_unref(contacts); } else { ret = COMMAND_FAILED; } } else { ret = dc_strdup("No chat selected."); } } else if(strcmp(cmd, "getlocations")==0) { int contact_id = arg1? atoi(arg1) : 0; dc_array_t* loc = dc_get_locations(context, dc_chat_get_id(sel_chat), contact_id, 0, 0); for (int j=dc_array_get_cnt(loc)-1; j>=0; j--) { char* timestr = dc_timestamp_to_str(dc_array_get_timestamp(loc, j)); char* marker = dc_array_get_marker(loc, j); dc_log_info(context, 0, "Loc#%i: %s: lat=%f lng=%f acc=%f Chat#%i Contact#%i Msg#%i %s%s", dc_array_get_id(loc, j), timestr, dc_array_get_latitude(loc, j), dc_array_get_longitude(loc, j), dc_array_get_accuracy(loc, j), dc_array_get_chat_id(loc, j), dc_array_get_contact_id(loc, j), dc_array_get_msg_id(loc, j), marker? marker : "-", dc_array_is_independent(loc, j)? " [independent]" : ""); free(timestr); free(marker); } if (dc_array_get_cnt(loc)==0) { dc_log_info(context, 0, "No locations."); } dc_array_unref(loc); ret = COMMAND_SUCCEEDED; } else if (strcmp(cmd, "sendlocations")==0) { if (sel_chat) { if (arg1 && arg1[0]) { int seconds = atoi(arg1); dc_send_locations_to_chat(context, dc_chat_get_id(sel_chat), seconds); ret = dc_mprintf("Locations will be sent to Chat#%i for %i seconds. " "Use 'setlocation ' to play around.", dc_chat_get_id(sel_chat), seconds); } else { ret = dc_strdup("ERROR: No timeout given."); } } else { ret = dc_strdup("No chat selected."); } } else if (strcmp(cmd, "setlocation")==0) { char* arg2 = NULL; if (arg1) { arg2 = strrchr(arg1, ' '); } if (arg1 && arg2) { *arg2 = 0; arg2++; double latitude = atof(arg1); double longitude = atof(arg2); int continue_streaming = dc_set_location(context, latitude, longitude, 0.0); ret = dc_strdup(continue_streaming? "Success, streaming should be continued." : "Success, streaming can be stoppped."); } else { ret = dc_strdup("ERROR: Latitude or longitude not given."); } } else if (strcmp(cmd, "dellocations")==0) { dc_delete_all_locations(context); ret = COMMAND_SUCCEEDED; } else if (strcmp(cmd, "send")==0) { if (sel_chat) { if (arg1 && arg1[0]) { if (dc_send_text_msg(context, dc_chat_get_id(sel_chat), arg1)) { ret = dc_strdup("Message sent."); } else { ret = dc_strdup("ERROR: Sending failed."); } } else { ret = dc_strdup("ERROR: No message text given."); } } else { ret = dc_strdup("No chat selected."); } } else if (strcmp(cmd, "sendempty")==0) { if (sel_chat) { if (dc_send_text_msg(context, dc_chat_get_id(sel_chat), "")) { ret = dc_strdup("Message sent."); } else { ret = dc_strdup("ERROR: Sending failed."); } } else { ret = dc_strdup("No chat selected."); } } else if (strcmp(cmd, "sendimage")==0 || strcmp(cmd, "sendfile")==0) { if (sel_chat) { if (arg1 && arg1[0]) { char* arg2 = strchr(arg1, ' '); if (arg2) { *arg2 = 0; arg2++; } dc_msg_t* msg = dc_msg_new(context, strcmp(cmd, "sendimage")==0? DC_MSG_IMAGE : DC_MSG_FILE); dc_msg_set_file(msg, arg1, NULL); dc_msg_set_text(msg, arg2); dc_send_msg(context, dc_chat_get_id(sel_chat), msg); dc_msg_unref(msg); ret = COMMAND_SUCCEEDED; } else { ret = dc_strdup("ERROR: No file given."); } } else { ret = dc_strdup("No chat selected."); } } else if (strcmp(cmd, "sendpoi")==0) { if (sel_chat) { char* arg2 = arg1? strchr(arg1, ' ') : NULL; if (arg2) { *arg2 = 0; arg2++; } char* arg3 = arg2? strchr(arg2, ' ') : NULL; if (arg3) { *arg3 = 0; arg3++; } if (arg1 && arg2 && arg3) { dc_msg_t* msg = dc_msg_new(context,DC_MSG_TEXT); dc_msg_set_text(msg, arg3); dc_msg_set_location(msg, dc_atof(arg1), dc_atof(arg2)); dc_send_msg(context, dc_chat_get_id(sel_chat), msg); dc_msg_unref(msg); ret = COMMAND_SUCCEEDED; } else { ret = dc_strdup("ERROR: Usage: sendpoi "); } } else { ret = dc_strdup("No chat selected."); } } else if (strcmp(cmd, "listmsgs")==0) { if (arg1) { dc_array_t* msglist = dc_search_msgs(context, sel_chat? dc_chat_get_id(sel_chat) : 0, arg1); if (msglist) { log_msglist(context, msglist); ret = dc_mprintf("%i messages.", (int)dc_array_get_cnt(msglist)); dc_array_unref(msglist); } } else { ret = dc_strdup("ERROR: Argument missing."); } } else if (strcmp(cmd, "draft")==0) { if (sel_chat) { if (arg1 && arg1[0]) { dc_msg_t* draft = dc_msg_new(context, DC_MSG_TEXT); dc_msg_set_text(draft, arg1); dc_set_draft(context, dc_chat_get_id(sel_chat), draft); dc_msg_unref(draft); ret = dc_strdup("Draft saved."); } else { dc_set_draft(context, dc_chat_get_id(sel_chat), NULL); ret = dc_strdup("Draft deleted."); } } else { ret = dc_strdup("No chat selected."); } } else if (strcmp(cmd, "listmedia")==0) { if (sel_chat) { dc_array_t* images = dc_get_chat_media(context, dc_chat_get_id(sel_chat), DC_MSG_IMAGE, DC_MSG_GIF, DC_MSG_VIDEO); int i, icnt = dc_array_get_cnt(images); ret = dc_mprintf("%i images or videos: ", icnt); for (i = 0; i < icnt; i++) { char* temp = dc_mprintf("%s%sMsg#%i", i? ", ":"", ret, (int)dc_array_get_id(images, i)); free(ret); ret = temp; } dc_array_unref(images); } else { ret = dc_strdup("No chat selected."); } } else if (strcmp(cmd, "archive")==0 || strcmp(cmd, "unarchive")==0) { if (arg1) { int chat_id = atoi(arg1); dc_archive_chat(context, chat_id, strcmp(cmd, "archive")==0? 1 : 0); ret = COMMAND_SUCCEEDED; } else { ret = dc_strdup("ERROR: Argument missing."); } } else if (strcmp(cmd, "delchat")==0) { if (arg1) { int chat_id = atoi(arg1); dc_delete_chat(context, chat_id); ret = COMMAND_SUCCEEDED; } else { ret = dc_strdup("ERROR: Argument missing."); } } /******************************************************************************* * Message commands ******************************************************************************/ else if (strcmp(cmd, "msginfo")==0) { if (arg1) { int id = atoi(arg1); ret = dc_get_msg_info(context, id); } else { ret = dc_strdup("ERROR: Argument missing."); } } else if (strcmp(cmd, "listfresh")==0) { dc_array_t* msglist = dc_get_fresh_msgs(context); if (msglist) { log_msglist(context, msglist); ret = dc_mprintf("%i fresh messages.", (int)dc_array_get_cnt(msglist)); dc_array_unref(msglist); } } else if (strcmp(cmd, "forward")==0) { char* arg2 = NULL; if (arg1) { arg2 = strrchr(arg1, ' '); } if (arg1 && arg2) { *arg2 = 0; arg2++; uint32_t msg_ids[1], chat_id = atoi(arg2); msg_ids[0] = atoi(arg1); dc_forward_msgs(context, msg_ids, 1, chat_id); ret = COMMAND_SUCCEEDED; } else { ret = dc_strdup("ERROR: Arguments expected."); } } else if (strcmp(cmd, "markseen")==0) { if (arg1) { uint32_t msg_ids[1]; msg_ids[0] = atoi(arg1); dc_markseen_msgs(context, msg_ids, 1); ret = COMMAND_SUCCEEDED; } else { ret = dc_strdup("ERROR: Argument missing."); } } else if (strcmp(cmd, "star")==0 || strcmp(cmd, "unstar")==0) { if (arg1) { uint32_t msg_ids[1]; msg_ids[0] = atoi(arg1); dc_star_msgs(context, msg_ids, 1, strcmp(cmd, "star")==0? 1 : 0); ret = COMMAND_SUCCEEDED; } else { ret = dc_strdup("ERROR: Argument missing."); } } else if (strcmp(cmd, "delmsg")==0) { if (arg1) { uint32_t ids[1]; ids[0] = atoi(arg1); dc_delete_msgs(context, ids, 1); ret = COMMAND_SUCCEEDED; } else { ret = dc_strdup("ERROR: Argument missing."); } } /******************************************************************************* * Contact commands ******************************************************************************/ else if (strcmp(cmd, "listcontacts")==0 || strcmp(cmd, "contacts")==0 || strcmp(cmd, "listverified")==0) { dc_array_t* contacts = dc_get_contacts(context, strcmp(cmd, "listverified")==0? DC_GCL_VERIFIED_ONLY|DC_GCL_ADD_SELF : DC_GCL_ADD_SELF, arg1); if (contacts) { log_contactlist(context, contacts); ret = dc_mprintf("%i contacts.", (int)dc_array_get_cnt(contacts)); dc_array_unref(contacts); } else { ret = COMMAND_FAILED; } } else if (strcmp(cmd, "addcontact")==0) { char* arg2 = NULL; if (arg1) { arg2 = strrchr(arg1, ' '); } if (arg1 && arg2) { *arg2 = 0; arg2++; char* book = dc_mprintf("%s\n%s", arg1, arg2); dc_add_address_book(context, book); ret = COMMAND_SUCCEEDED; free(book); } else if (arg1) { ret = dc_create_contact(context, NULL, arg1)? COMMAND_SUCCEEDED : COMMAND_FAILED; } else { ret = dc_strdup("ERROR: Arguments [] expected."); } } else if (strcmp(cmd, "contactinfo")==0) { if (arg1) { int contact_id = atoi(arg1); dc_strbuilder_t strbuilder; dc_strbuilder_init(&strbuilder, 0); dc_contact_t* contact = dc_get_contact(context, contact_id); char* nameNaddr = dc_contact_get_name_n_addr(contact); dc_strbuilder_catf(&strbuilder, "Contact info for: %s:\n\n", nameNaddr); free(nameNaddr); dc_contact_unref(contact); char* encrinfo = dc_get_contact_encrinfo(context, contact_id); dc_strbuilder_cat(&strbuilder, encrinfo); free(encrinfo); dc_chatlist_t* chatlist = dc_get_chatlist(context, 0, NULL, contact_id); int chatlist_cnt = dc_chatlist_get_cnt(chatlist); if (chatlist_cnt > 0) { dc_strbuilder_catf(&strbuilder, "\n\n%i chats shared with Contact#%i: ", chatlist_cnt, contact_id); for (int i = 0; i < chatlist_cnt; i++) { if (i) { dc_strbuilder_cat(&strbuilder, ", "); } dc_chat_t* chat = dc_get_chat(context, dc_chatlist_get_chat_id(chatlist, i)); dc_strbuilder_catf(&strbuilder, "%s#%i", chat_prefix(chat), dc_chat_get_id(chat)); dc_chat_unref(chat); } } dc_chatlist_unref(chatlist); ret = strbuilder.buf; } else { ret = dc_strdup("ERROR: Argument missing."); } } else if (strcmp(cmd, "delcontact")==0) { if (arg1) { ret = dc_delete_contact(context, atoi(arg1))? COMMAND_SUCCEEDED : COMMAND_FAILED; } else { ret = dc_strdup("ERROR: Argument missing."); } } else if (strcmp(cmd, "cleanupcontacts")==0) { ret = dc_cleanup_contacts(context)? COMMAND_SUCCEEDED : COMMAND_FAILED; } /******************************************************************************* * Misc. ******************************************************************************/ else if (strcmp(cmd, "getqr")==0) { ret = dc_get_securejoin_qr(context, arg1? atoi(arg1) : 0); if (ret == NULL || ret[0]==0) { free(ret); ret = COMMAND_FAILED; } } else if (strcmp(cmd, "checkqr")==0) { if (arg1) { dc_lot_t* res = dc_check_qr(context, arg1); ret = dc_mprintf("state=%i, id=%i, text1=%s, text2=%s", (int)res->state, res->id, res->text1? res->text1:"", res->text2? res->text2:""); dc_lot_unref(res); } else { ret = dc_strdup("ERROR: Argument missing."); } } else if ( strcmp(cmd, "event")==0) { if (arg1) { int event = atoi(arg1); uintptr_t r = context->cb(context, event, 0, 0); ret = dc_mprintf("Sending event %i, received value %i.", (int)event, (int)r); } else { ret = dc_strdup("ERROR: Argument missing."); } } else if (strcmp(cmd, "fileinfo")==0) { if (arg1) { unsigned char* buf = NULL; size_t buf_bytes; uint32_t w, h; if (dc_read_file(context, arg1, (void**)&buf, &buf_bytes)) { dc_get_filemeta(buf, buf_bytes, &w, &h); ret = dc_mprintf("width=%i, height=%i", (int)w, (int)h); } else { ret = dc_strdup("ERROR: Command failed."); } free(buf); } else { ret = dc_strdup("ERROR: Argument missing."); } } else if (strcmp(cmd, "emptyserver")==0) { int flags = 0; if (arg1) { if (strstr(arg1, "mvbox")!=NULL) { flags|= DC_EMPTY_MVBOX; } if (strstr(arg1, "inbox")!=NULL) { flags|= DC_EMPTY_INBOX; } } if (flags) { dc_empty_server(context, flags); ret = COMMAND_SUCCEEDED; } else { ret = dc_strdup("ERROR: Argument [mvbox|inbox] missing."); } } else { ret = COMMAND_UNKNOWN; } cleanup: if (ret == COMMAND_SUCCEEDED) { ret = dc_strdup("Command executed successfully."); } else if (ret == COMMAND_FAILED) { ret = dc_strdup("ERROR: Command failed."); } else if (ret == COMMAND_UNKNOWN) { ret = dc_mprintf("ERROR: Unknown command \"%s\", type ? for help.", cmd); } if (sel_chat) { dc_chat_unref(sel_chat); sel_chat = NULL; } free(cmd); return ret; }