X-Git-Url: http://git.silcnet.org/gitweb/?a=blobdiff_plain;f=apps%2Firssi%2Fsrc%2Fsilc%2Fcore%2Fclientutil.c;h=9a04df914c79175e29f8a773baf52db31340d202;hb=9747251aea338d4c29ffbb6bff45f81ea4a493a6;hp=836347338a3457deaa8af281f5d37ef4d6775dc1;hpb=8ad07bf7fa3f4a579935e59c8832315e19d75908;p=silc.git diff --git a/apps/irssi/src/silc/core/clientutil.c b/apps/irssi/src/silc/core/clientutil.c index 83634733..9a04df91 100644 --- a/apps/irssi/src/silc/core/clientutil.c +++ b/apps/irssi/src/silc/core/clientutil.c @@ -370,6 +370,205 @@ int silc_client_load_keys(SilcClient client) return ret; } +static bool silc_keyboard_prompt_pending; + +typedef struct +{ + SilcAsyncOperation async_context; + SILC_KEYBOARD_PROMPT_PROC user_prompt_proc; + void *user_context; + bool aborted; + bool *immediate_completion; +} * SilcKeyboardEntryRedirectContext; + +static void silc_keyboard_entry_redirect_abort( + SilcAsyncOperation op, + void *context) +{ + SilcKeyboardEntryRedirectContext ctx = (SilcKeyboardEntryRedirectContext)context; + + /* + * Flag ourselves as aborted so the irssi callback doesn't do any real + * work here. + */ + + ctx->aborted = TRUE; + + /* + * Call the user routine to notify it that we are aborting, so that it may + * clean up anything that needs cleaning up, e.g. references. The user + * may not reference the SilcAsyncOperation beyond this abort call. The + * recommended procedure is for the user prompt routine to null out its + * reference to the SilcAsyncOperation context. The underlying context + * structure will be released when the actual wrappered callback fires, + * though the wrappered callback will not call into user code now that + * the operation has been aborted. + */ + + ctx->user_prompt_proc(NULL, ctx->user_context, KeyboardCompletionAborted); +} + +static void silc_keyboard_entry_redirect_completion( + const char *line, + void *context) +{ + SilcKeyboardEntryRedirectContext ctx = (SilcKeyboardEntryRedirectContext)context; + + /* + * If we are aborted, then don't call the user routine. Note that we + * already notified the user that they were aborted when the abort + * call was made in the first place, so the user should not have any + * dangling references at this point. + * + * Otherwise, call the user routine. + */ + + if (!ctx->aborted) + { + ctx->user_prompt_proc(line, ctx->user_context, + KeyboardCompletionSuccess); + } + + /* + * If there's a flag to set on completion, such that we can detect when the + * operation finished immediately instead of being processed as a callback, + * then set that now. + */ + + if (ctx->immediate_completion) + *ctx->immediate_completion = TRUE; + + /* + * Clean up our internal context structures. Note that we are considered + * responsible for handling the SilcAsyncOperation release in this model, + * unless we were aborted, in which case the abort request has released it. + */ + + if (!ctx->aborted) + silc_async_free(ctx->async_context); + + silc_free(ctx); + + /* + * Mark us as not having a keyboard prompt pending. + */ + + silc_keyboard_prompt_pending = FALSE; +} + +/* Prompt for user input. */ +bool silc_keyboard_entry_redirect( + SILC_KEYBOARD_PROMPT_PROC prompt_func, + const char *entry, + int flags, + void *data, + SilcAsyncOperation *async) +{ + SilcKeyboardEntryRedirectContext ctx; + bool completed_now; + + /* + * Check if we already have a keyboard prompt pending. This sucks, but + * irssi stores the keyboard prompt data in a global, and if we request + * a prompt while there is already a prompt in progress, the old prompt + * data is leaked. If irssi gets its act together, this can (and should) + * go away. + */ + + if (silc_keyboard_prompt_pending) + { + prompt_func(NULL, data, KeyboardCompletionFailed); + return FALSE; + } + + /* + * Allocate our context blocks. + */ + + ctx = (SilcKeyboardEntryRedirectContext)silc_calloc(1, sizeof(*ctx)); + + if (!ctx) + { + prompt_func(NULL, data, KeyboardCompletionFailed); + return FALSE; + } + + ctx->async_context = silc_async_alloc(silc_keyboard_entry_redirect_abort, + NULL, ctx); + + if (!ctx->async_context) + { + silc_free(ctx); + prompt_func(NULL, data, KeyboardCompletionFailed); + return FALSE; + } + + /* + * Initially, we don't consider ourselves as having finished. + */ + + completed_now = FALSE; + + /* + * Since irssi can't handle overlapping keyboard prompt requests, block + * future requests until we are finished. N.B. This should really be + * handled inside of irssi, but this requires a breaking change to how + * keyboard callbacks are processed from an API perspective. A problem + * exists where another user could call a keyboard redirect request + * external to silc while we have one pending, and cause ours to get + * lost, in which case we will get stuck denying future prompt requests. + * + * Fortunately, nobody else seems to use keyboard prompt requests, at least + * not that I can tell. + */ + + silc_keyboard_prompt_pending = TRUE; + + /* + * Set up the call to the irssi keyboard entry redirection facility. + */ + + ctx->user_prompt_proc = prompt_func; + ctx->user_context = data; + ctx->aborted = FALSE; + ctx->immediate_completion = &completed_now; + + keyboard_entry_redirect((SIGNAL_FUNC)silc_keyboard_entry_redirect_completion, + entry, 0, ctx); + + ctx->immediate_completion = NULL; + + /* + * If we completed immediately, then there is nothing to return as the async + * context has already been released. In this case we have completed with a + * success status, but there is no SilcAsyncOperation context to return. + */ + + if (completed_now) + { + *async = NULL; + return TRUE; + } + + /* + * Otherwise, we must return an async operation context to the caller, and + * we must unset the immediate_completion flag as we don't want to be + * notified anymore since we're returning out. Note that this is not safe + * if keyboard_entry_redirect can call from a different thread, but we are + * assuming that it doesn't as there's already many other things that seem + * to make this assumption. + */ + + *async = ctx->async_context; + + /* + * All done. Irssi will invoke the callback on this thread at a later point + * in time. + */ + + return TRUE; +} + #ifdef SILC_PLUGIN void create_key_passphrase(const char *answer, CREATE_KEY_REC *rec) { @@ -379,6 +578,12 @@ void create_key_passphrase(const char *answer, CREATE_KEY_REC *rec) if ((rec->passphrase == NULL) && (answer) && (*answer != '\0')) { rec->passphrase = g_strdup(answer); + /* + * This can continue to use keyboard_entry_redirect as it's a one-time at + * initialization function. If create_key_passphrase is used + * somewhere else, then this needs to be ripped out and changed to use + * the new, more correct silc_keyboard_entry_redirect. + */ keyboard_entry_redirect((SIGNAL_FUNC) create_key_passphrase, format_get_text("fe-common/silc", NULL, NULL, NULL, SILCTXT_CONFIG_PASS_ASK2), @@ -424,6 +629,12 @@ void change_private_key_passphrase(const char *answer, CREATE_KEY_REC *rec) if (rec->old == NULL) { rec->old = g_strdup((answer == NULL ? "" : answer)); + /* + * This can continue to use keyboard_entry_redirect as it's a one-time at + * initialization function. If change_private_key_passphrase is used + * somewhere else, then this needs to be ripped out and changed to use + * the new, more correct silc_keyboard_entry_redirect. + */ keyboard_entry_redirect((SIGNAL_FUNC) change_private_key_passphrase, format_get_text("fe-common/silc", NULL, NULL, NULL, SILCTXT_CONFIG_PASS_ASK2), @@ -433,6 +644,12 @@ void change_private_key_passphrase(const char *answer, CREATE_KEY_REC *rec) if ((rec->passphrase == NULL) && (answer) && (*answer != '\0')) { rec->passphrase = g_strdup(answer); + /* + * This can continue to use keyboard_entry_redirect as it's a one-time at + * initialization function. If change_private_key_passphrase is used + * somewhere else, then this needs to be ripped out and changed to use + * the new, more correct silc_keyboard_entry_redirect. + */ keyboard_entry_redirect((SIGNAL_FUNC) change_private_key_passphrase, format_get_text("fe-common/silc", NULL, NULL, NULL, SILCTXT_CONFIG_PASS_ASK3),