Blame SOURCES/0037-Bugzilla-pass-Bugzilla_token-in-all-XML-RPC-calls.patch

4b6aa8
From 36010951c38483b4131012ebebd6b1f79c0ae799 Mon Sep 17 00:00:00 2001
4b6aa8
From: Jakub Filak <jfilak@redhat.com>
4b6aa8
Date: Wed, 23 Apr 2014 13:33:52 +0200
4b6aa8
Subject: [LIBREPORT PATCH 37/37] Bugzilla: pass Bugzilla_token in all XML RPC
4b6aa8
 calls
4b6aa8
4b6aa8
Introduce the session parameters for XML RPC calls. These parameters are
4b6aa8
added to every XML RPC call.
4b6aa8
4b6aa8
abrt_xmlrpc_call*() functions expected formatting string in form of
4b6aa8
"({...})" for some good but unknown reason. Since now, the functions
4b6aa8
expects formatting string without the outer brackets.
4b6aa8
4b6aa8
() - means empty array (allowed in xmlrpc-c)
4b6aa8
{} - means empty structure (allowed in xmlrpc-c)
4b6aa8
4b6aa8
Cite:
4b6aa8
4b6aa8
Instead of returning a cookie, the User.login call now returns a token
4b6aa8
that clients must pass in the Bugzilla_token parameter to subsequent
4b6aa8
RPC calls. If the token is not passed, Bugzilla will treat the RPC
4b6aa8
call as unauthenticated and will not allow access to non-public data.
4b6aa8
4b6aa8
See
4b6aa8
https://partner-bugzilla.redhat.com/docs/en/html/api/Bugzilla/WebService.html#LOGGING_IN
4b6aa8
for more details.
4b6aa8
4b6aa8
Client scripts that access Red Hat Bugzilla via XML-RPC or JSON-RPC
4b6aa8
and use login cookies for authentication must be updated to instead
4b6aa8
remember the token received when logging in and pass that token back
4b6aa8
to Bugzilla in subsequent RPC calls.
4b6aa8
4b6aa8
[http://post-office.corp.redhat.com/archives/bugzilla-list/2014-April/msg00005.html]
4b6aa8
4b6aa8
Resolves rhbz#1090465
4b6aa8
4b6aa8
Signed-off-by: Jakub Filak <jfilak@redhat.com>
4b6aa8
4b6aa8
mmilata: fix typo in commit message subject
4b6aa8
4b6aa8
Signed-off-by: Jakub Filak <jfilak@redhat.com>
4b6aa8
---
4b6aa8
 src/lib/abrt_xmlrpc.c           | 94 +++++++++++++++++++++++++++++++++--------
4b6aa8
 src/lib/abrt_xmlrpc.h           |  3 ++
4b6aa8
 src/plugins/reporter-bugzilla.c | 34 ---------------
4b6aa8
 src/plugins/rhbz.c              | 64 ++++++++++++++++++++++++----
4b6aa8
 src/plugins/rhbz.h              |  4 ++
4b6aa8
 5 files changed, 139 insertions(+), 60 deletions(-)
4b6aa8
4b6aa8
diff --git a/src/lib/abrt_xmlrpc.c b/src/lib/abrt_xmlrpc.c
4b6aa8
index 48b556f..84ed50d 100644
4b6aa8
--- a/src/lib/abrt_xmlrpc.c
4b6aa8
+++ b/src/lib/abrt_xmlrpc.c
4b6aa8
@@ -20,6 +20,12 @@
4b6aa8
 #include "abrt_xmlrpc.h"
4b6aa8
 #include "proxies.h"
4b6aa8
 
4b6aa8
+struct abrt_xmlrpc_param_pair
4b6aa8
+{
4b6aa8
+    char *name;
4b6aa8
+    xmlrpc_value *value;
4b6aa8
+};
4b6aa8
+
4b6aa8
 void abrt_xmlrpc_die(xmlrpc_env *env)
4b6aa8
 {
4b6aa8
     error_msg_and_die("fatal: %s", env->fault_string);
4b6aa8
@@ -106,9 +112,77 @@ void abrt_xmlrpc_free_client(struct abrt_xmlrpc *ax)
4b6aa8
     if (ax->ax_client)
4b6aa8
         xmlrpc_client_destroy(ax->ax_client);
4b6aa8
 
4b6aa8
+    for (GList *iter = ax->ax_session_params; iter; iter = g_list_next(iter))
4b6aa8
+    {
4b6aa8
+        struct abrt_xmlrpc_param_pair *param_pair = (struct abrt_xmlrpc_param_pair *)iter->data;
4b6aa8
+        xmlrpc_DECREF(param_pair->value);
4b6aa8
+        free(param_pair->name);
4b6aa8
+        free(param_pair);
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    g_list_free(ax->ax_session_params);
4b6aa8
+
4b6aa8
     free(ax);
4b6aa8
 }
4b6aa8
 
4b6aa8
+void abrt_xmlrpc_client_add_session_param_string(xmlrpc_env *env, struct abrt_xmlrpc *ax,
4b6aa8
+        const char *name, const char *value)
4b6aa8
+{
4b6aa8
+    struct abrt_xmlrpc_param_pair *new_ses_param = xmalloc(sizeof(*new_ses_param));
4b6aa8
+    new_ses_param->name = xstrdup(name);
4b6aa8
+
4b6aa8
+    new_ses_param->value = xmlrpc_string_new(env, value);
4b6aa8
+    if (env->fault_occurred)
4b6aa8
+        abrt_xmlrpc_die(env);
4b6aa8
+
4b6aa8
+    ax->ax_session_params = g_list_append(ax->ax_session_params, new_ses_param);
4b6aa8
+}
4b6aa8
+
4b6aa8
+/* internal helper function */
4b6aa8
+static xmlrpc_value *abrt_xmlrpc_call_params_internal(xmlrpc_env *env, struct abrt_xmlrpc *ax, const char *method, xmlrpc_value *params)
4b6aa8
+{
4b6aa8
+    xmlrpc_value *array = xmlrpc_array_new(env);
4b6aa8
+    if (env->fault_occurred)
4b6aa8
+        abrt_xmlrpc_die(env);
4b6aa8
+
4b6aa8
+    bool destroy_params = false;
4b6aa8
+    if (xmlrpc_value_type(params) == XMLRPC_TYPE_NIL)
4b6aa8
+    {
4b6aa8
+        destroy_params = true;
4b6aa8
+        params = abrt_xmlrpc_params_new(env);
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    if (xmlrpc_value_type(params) == XMLRPC_TYPE_STRUCT)
4b6aa8
+    {
4b6aa8
+        for (GList *iter = ax->ax_session_params; iter; iter = g_list_next(iter))
4b6aa8
+        {
4b6aa8
+            struct abrt_xmlrpc_param_pair *param_pair = (struct abrt_xmlrpc_param_pair *)iter->data;
4b6aa8
+
4b6aa8
+            xmlrpc_struct_set_value(env, params, param_pair->name, param_pair->value);
4b6aa8
+            if (env->fault_occurred)
4b6aa8
+                abrt_xmlrpc_die(env);
4b6aa8
+        }
4b6aa8
+    }
4b6aa8
+    else
4b6aa8
+    {
4b6aa8
+        log_warning("Bug: not yet supported XML RPC call type.");
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    xmlrpc_array_append_item(env, array, params);
4b6aa8
+    if (env->fault_occurred)
4b6aa8
+        abrt_xmlrpc_die(env);
4b6aa8
+
4b6aa8
+    xmlrpc_value *result = NULL;
4b6aa8
+    xmlrpc_client_call2(env, ax->ax_client, ax->ax_server_info, method,
4b6aa8
+                        array, &result);
4b6aa8
+
4b6aa8
+    if (destroy_params)
4b6aa8
+        xmlrpc_DECREF(params);
4b6aa8
+
4b6aa8
+    xmlrpc_DECREF(array);
4b6aa8
+    return result;
4b6aa8
+}
4b6aa8
+
4b6aa8
 /* internal helper function */
4b6aa8
 static
4b6aa8
 xmlrpc_value *abrt_xmlrpc_call_full_va(xmlrpc_env *env, struct abrt_xmlrpc *ax,
4b6aa8
@@ -133,10 +207,8 @@ xmlrpc_value *abrt_xmlrpc_call_full_va(xmlrpc_env *env, struct abrt_xmlrpc *ax,
4b6aa8
             suffix);
4b6aa8
     }
4b6aa8
     else
4b6aa8
-    {
4b6aa8
-        xmlrpc_client_call2(env, ax->ax_client, ax->ax_server_info, method,
4b6aa8
-                            param, &result);
4b6aa8
-    }
4b6aa8
+        result = abrt_xmlrpc_call_params_internal(env, ax, method, param);
4b6aa8
+
4b6aa8
     xmlrpc_DECREF(param);
4b6aa8
 
4b6aa8
     return result;
4b6aa8
@@ -192,25 +264,13 @@ void abrt_xmlrpc_params_add_array(xmlrpc_env *env, xmlrpc_value *params, const c
4b6aa8
     if (env->fault_occurred)
4b6aa8
         abrt_xmlrpc_die(env);
4b6aa8
 }
4b6aa8
-
4b6aa8
 xmlrpc_value *abrt_xmlrpc_call_params(xmlrpc_env *env, struct abrt_xmlrpc *ax, const char *method, xmlrpc_value *params)
4b6aa8
 {
4b6aa8
-    xmlrpc_value *array = xmlrpc_array_new(env);
4b6aa8
-    if (env->fault_occurred)
4b6aa8
-        abrt_xmlrpc_die(env);
4b6aa8
-
4b6aa8
-    xmlrpc_array_append_item(env, array, params);
4b6aa8
-    if (env->fault_occurred)
4b6aa8
-        abrt_xmlrpc_die(env);
4b6aa8
-
4b6aa8
-    xmlrpc_value *result = NULL;
4b6aa8
-    xmlrpc_client_call2(env, ax->ax_client, ax->ax_server_info, method,
4b6aa8
-                        array, &result);
4b6aa8
+    xmlrpc_value *result = abrt_xmlrpc_call_params_internal(env, ax, method, params);
4b6aa8
 
4b6aa8
     if (env->fault_occurred)
4b6aa8
         abrt_xmlrpc_die(env);
4b6aa8
 
4b6aa8
-    xmlrpc_DECREF(array);
4b6aa8
     return result;
4b6aa8
 }
4b6aa8
 
4b6aa8
diff --git a/src/lib/abrt_xmlrpc.h b/src/lib/abrt_xmlrpc.h
4b6aa8
index 945a887..e11dcec 100644
4b6aa8
--- a/src/lib/abrt_xmlrpc.h
4b6aa8
+++ b/src/lib/abrt_xmlrpc.h
4b6aa8
@@ -23,6 +23,7 @@
4b6aa8
  * include/xmlrpc-c/base.h: typedef int32_t xmlrpc_int32;
4b6aa8
  */
4b6aa8
 
4b6aa8
+#include <glib.h>
4b6aa8
 #include <xmlrpc-c/base.h>
4b6aa8
 #include <xmlrpc-c/client.h>
4b6aa8
 
4b6aa8
@@ -33,6 +34,7 @@ extern "C" {
4b6aa8
 struct abrt_xmlrpc {
4b6aa8
     xmlrpc_client *ax_client;
4b6aa8
     xmlrpc_server_info *ax_server_info;
4b6aa8
+    GList *ax_session_params;
4b6aa8
 };
4b6aa8
 
4b6aa8
 xmlrpc_value *abrt_xmlrpc_array_new(xmlrpc_env *env);
4b6aa8
@@ -45,6 +47,7 @@ void abrt_xmlrpc_params_add_array(xmlrpc_env *env, xmlrpc_value *params, const c
4b6aa8
 
4b6aa8
 struct abrt_xmlrpc *abrt_xmlrpc_new_client(const char *url, int ssl_verify);
4b6aa8
 void abrt_xmlrpc_free_client(struct abrt_xmlrpc *ax);
4b6aa8
+void abrt_xmlrpc_client_add_session_param_string(xmlrpc_env *env, struct abrt_xmlrpc *ax, const char *name, const char *value);
4b6aa8
 void abrt_xmlrpc_die(xmlrpc_env *env) __attribute__((noreturn));
4b6aa8
 void abrt_xmlrpc_error(xmlrpc_env *env);
4b6aa8
 
4b6aa8
diff --git a/src/plugins/reporter-bugzilla.c b/src/plugins/reporter-bugzilla.c
4b6aa8
index 0e8b277..45aa2cc 100644
4b6aa8
--- a/src/plugins/reporter-bugzilla.c
4b6aa8
+++ b/src/plugins/reporter-bugzilla.c
4b6aa8
@@ -807,40 +807,6 @@ void login(struct abrt_xmlrpc *client, struct bugzilla_struct *rhbz)
4b6aa8
     }
4b6aa8
 }
4b6aa8
 
4b6aa8
-static
4b6aa8
-xmlrpc_value *rhbz_search_duphash(struct abrt_xmlrpc *ax,
4b6aa8
-                        const char *product,
4b6aa8
-                        const char *version,
4b6aa8
-                        const char *component,
4b6aa8
-                        const char *duphash)
4b6aa8
-{
4b6aa8
-    struct strbuf *query = strbuf_new();
4b6aa8
-
4b6aa8
-    strbuf_append_strf(query, "ALL whiteboard:\"%s\"", duphash);
4b6aa8
-
4b6aa8
-    if (product)
4b6aa8
-        strbuf_append_strf(query, " product:\"%s\"", product);
4b6aa8
-
4b6aa8
-    if (version)
4b6aa8
-        strbuf_append_strf(query, " version:\"%s\"", version);
4b6aa8
-
4b6aa8
-    if (component)
4b6aa8
-        strbuf_append_strf(query, " component:\"%s\"", component);
4b6aa8
-
4b6aa8
-    char *s = strbuf_free_nobuf(query);
4b6aa8
-    log_debug("search for '%s'", s);
4b6aa8
-    xmlrpc_value *search = abrt_xmlrpc_call(ax, "Bug.search", "({s:s})",
4b6aa8
-                                         "quicksearch", s);
4b6aa8
-    free(s);
4b6aa8
-    xmlrpc_value *bugs = rhbz_get_member("bugs", search);
4b6aa8
-    xmlrpc_DECREF(search);
4b6aa8
-
4b6aa8
-    if (!bugs)
4b6aa8
-        error_msg_and_die(_("Bug.search(quicksearch) return value did not contain member 'bugs'"));
4b6aa8
-
4b6aa8
-    return bugs;
4b6aa8
-}
4b6aa8
-
4b6aa8
 int main(int argc, char **argv)
4b6aa8
 {
4b6aa8
     abrt_init(argv);
4b6aa8
diff --git a/src/plugins/rhbz.c b/src/plugins/rhbz.c
4b6aa8
index 534aaed..bad9ed4 100644
4b6aa8
--- a/src/plugins/rhbz.c
4b6aa8
+++ b/src/plugins/rhbz.c
4b6aa8
@@ -85,7 +85,7 @@ static GList *rhbz_comments(struct abrt_xmlrpc *ax, int bug_id)
4b6aa8
      *           <value><array>
4b6aa8
      * ...
4b6aa8
      */
4b6aa8
-    xmlrpc_value *xml_response = abrt_xmlrpc_call(ax, "Bug.comments", "({s:(i)})",
4b6aa8
+    xmlrpc_value *xml_response = abrt_xmlrpc_call(ax, "Bug.comments", "{s:(i)}",
4b6aa8
                                                                       "ids", bug_id);
4b6aa8
     /* bugs
4b6aa8
      *     This is used for bugs specified in ids. This is a hash, where the
4b6aa8
@@ -215,7 +215,7 @@ bool rhbz_login(struct abrt_xmlrpc *ax, const char *login, const char *password)
4b6aa8
     func_entry();
4b6aa8
 
4b6aa8
     xmlrpc_env env;
4b6aa8
-    xmlrpc_value *result = abrt_xmlrpc_call_full(&env, ax, "User.login", "({s:s,s:s})",
4b6aa8
+    xmlrpc_value *result = abrt_xmlrpc_call_full(&env, ax, "User.login", "{s:s,s:s}",
4b6aa8
                                                  "login", login, "password", password);
4b6aa8
 
4b6aa8
     if (env.fault_occurred)
4b6aa8
@@ -228,6 +228,14 @@ bool rhbz_login(struct abrt_xmlrpc *ax, const char *login, const char *password)
4b6aa8
         return false;
4b6aa8
     }
4b6aa8
 
4b6aa8
+    char *token = rhbz_bug_read_item("token", result, RHBZ_READ_STR);
4b6aa8
+    if (token != NULL)
4b6aa8
+    {
4b6aa8
+        log_debug("Adding session param Bugzilla_token");
4b6aa8
+        abrt_xmlrpc_client_add_session_param_string(&env, ax, "Bugzilla_token", token);
4b6aa8
+        free(token);
4b6aa8
+    }
4b6aa8
+
4b6aa8
 //TODO: with URL like http://bugzilla.redhat.com (that is, with http: instead of https:)
4b6aa8
 //we are getting this error:
4b6aa8
 //Logging into Bugzilla at http://bugzilla.redhat.com
4b6aa8
@@ -297,7 +305,7 @@ unsigned rhbz_version(struct abrt_xmlrpc *ax)
4b6aa8
     func_entry();
4b6aa8
 
4b6aa8
     xmlrpc_value *result;
4b6aa8
-    result = abrt_xmlrpc_call(ax, "Bugzilla.version", "()");
4b6aa8
+    result = abrt_xmlrpc_call(ax, "Bugzilla.version", "{}");
4b6aa8
     char *version = NULL;
4b6aa8
     if (result)
4b6aa8
         version = rhbz_bug_read_item("version", result, RHBZ_READ_STR);
4b6aa8
@@ -472,7 +480,7 @@ struct bug_info *rhbz_bug_info(struct abrt_xmlrpc *ax, int bug_id)
4b6aa8
      *        <value><array><data>
4b6aa8
      *        ...
4b6aa8
      */
4b6aa8
-    xmlrpc_value *xml_bug_response = abrt_xmlrpc_call(ax, "Bug.get", "({s:(i)})",
4b6aa8
+    xmlrpc_value *xml_bug_response = abrt_xmlrpc_call(ax, "Bug.get", "{s:(i)}",
4b6aa8
                                                           "ids", bug_id);
4b6aa8
 
4b6aa8
     xmlrpc_value *bugs_memb = rhbz_get_member("bugs", xml_bug_response);
4b6aa8
@@ -668,7 +676,7 @@ int rhbz_attach_blob(struct abrt_xmlrpc *ax, const char *bug_id,
4b6aa8
      *   6 -> base64,  two arguments (char* plain data which will be encoded by xmlrpc-c to base64,
4b6aa8
      *                                size_t number of bytes to encode)
4b6aa8
      */
4b6aa8
-    result = abrt_xmlrpc_call(ax, "Bug.add_attachment", "({s:(s),s:s,s:s,s:s,s:6,s:i})",
4b6aa8
+    result = abrt_xmlrpc_call(ax, "Bug.add_attachment", "{s:(s),s:s,s:s,s:s,s:6,s:i}",
4b6aa8
                 "ids", bug_id,
4b6aa8
                 "summary", fn,
4b6aa8
                 "file_name", filename,
4b6aa8
@@ -737,7 +745,12 @@ void rhbz_logout(struct abrt_xmlrpc *ax)
4b6aa8
 {
4b6aa8
     func_entry();
4b6aa8
 
4b6aa8
-    xmlrpc_value* result = abrt_xmlrpc_call(ax, "User.logout", "(s)", "");
4b6aa8
+    xmlrpc_env env;
4b6aa8
+    xmlrpc_value *result = abrt_xmlrpc_call_full(&env, ax, "User.logout", "{}");
4b6aa8
+
4b6aa8
+    if (env.fault_occurred)
4b6aa8
+        log_warning("xmlrpc fault: (%d) %s", env.fault_code, env.fault_string);
4b6aa8
+
4b6aa8
     if (result)
4b6aa8
         xmlrpc_DECREF(result);
4b6aa8
 }
4b6aa8
@@ -785,7 +798,7 @@ void rhbz_mail_to_cc(struct abrt_xmlrpc *ax, int bug_id, const char *mail, int f
4b6aa8
     );
4b6aa8
 #endif
4b6aa8
     /* Bugzilla 4.0+ uses this API: */
4b6aa8
-    result = abrt_xmlrpc_call(ax, "Bug.update", "({s:i,s:{s:(s),s:i}})",
4b6aa8
+    result = abrt_xmlrpc_call(ax, "Bug.update", "{s:i,s:{s:(s),s:i}}",
4b6aa8
                               "ids", bug_id,
4b6aa8
                               "cc", "add", mail,
4b6aa8
                                     "nomail", nomail_notify
4b6aa8
@@ -822,7 +835,7 @@ void rhbz_add_comment(struct abrt_xmlrpc *ax, int bug_id, const char *comment,
4b6aa8
     int nomail_notify = !!IS_NOMAIL_NOTIFY(flags);
4b6aa8
 
4b6aa8
     xmlrpc_value *result;
4b6aa8
-    result = abrt_xmlrpc_call(ax, "Bug.add_comment", "({s:i,s:s,s:b,s:i})",
4b6aa8
+    result = abrt_xmlrpc_call(ax, "Bug.add_comment", "{s:i,s:s,s:b,s:i}",
4b6aa8
                               "id", bug_id, "comment", comment,
4b6aa8
                               "private", private, "nomail", nomail_notify);
4b6aa8
 
4b6aa8
@@ -835,7 +848,7 @@ void rhbz_set_url(struct abrt_xmlrpc *ax, int bug_id, const char *url, int flags
4b6aa8
     func_entry();
4b6aa8
 
4b6aa8
     const int nomail_notify = !!IS_NOMAIL_NOTIFY(flags);
4b6aa8
-    xmlrpc_value *result = abrt_xmlrpc_call(ax, "Bug.update", "({s:i,s:s,s:i})",
4b6aa8
+    xmlrpc_value *result = abrt_xmlrpc_call(ax, "Bug.update", "{s:i,s:s,s:i}",
4b6aa8
                               "ids", bug_id,
4b6aa8
                               "url", url,
4b6aa8
 
4b6aa8
@@ -848,3 +861,36 @@ void rhbz_set_url(struct abrt_xmlrpc *ax, int bug_id, const char *url, int flags
4b6aa8
     if (result)
4b6aa8
         xmlrpc_DECREF(result);
4b6aa8
 }
4b6aa8
+
4b6aa8
+xmlrpc_value *rhbz_search_duphash(struct abrt_xmlrpc *ax,
4b6aa8
+                        const char *product,
4b6aa8
+                        const char *version,
4b6aa8
+                        const char *component,
4b6aa8
+                        const char *duphash)
4b6aa8
+{
4b6aa8
+    struct strbuf *query = strbuf_new();
4b6aa8
+
4b6aa8
+    strbuf_append_strf(query, "ALL whiteboard:\"%s\"", duphash);
4b6aa8
+
4b6aa8
+    if (product)
4b6aa8
+        strbuf_append_strf(query, " product:\"%s\"", product);
4b6aa8
+
4b6aa8
+    if (version)
4b6aa8
+        strbuf_append_strf(query, " version:\"%s\"", version);
4b6aa8
+
4b6aa8
+    if (component)
4b6aa8
+        strbuf_append_strf(query, " component:\"%s\"", component);
4b6aa8
+
4b6aa8
+    char *s = strbuf_free_nobuf(query);
4b6aa8
+    log_debug("search for '%s'", s);
4b6aa8
+    xmlrpc_value *search = abrt_xmlrpc_call(ax, "Bug.search", "{s:s}", "quicksearch", s);
4b6aa8
+
4b6aa8
+    free(s);
4b6aa8
+    xmlrpc_value *bugs = rhbz_get_member("bugs", search);
4b6aa8
+    xmlrpc_DECREF(search);
4b6aa8
+
4b6aa8
+    if (!bugs)
4b6aa8
+        error_msg_and_die(_("Bug.search(quicksearch) return value did not contain member 'bugs'"));
4b6aa8
+
4b6aa8
+    return bugs;
4b6aa8
+}
4b6aa8
diff --git a/src/plugins/rhbz.h b/src/plugins/rhbz.h
4b6aa8
index 742927a..976d333 100644
4b6aa8
--- a/src/plugins/rhbz.h
4b6aa8
+++ b/src/plugins/rhbz.h
4b6aa8
@@ -112,6 +112,10 @@ struct bug_info *rhbz_find_origin_bug_closed_duplicate(struct abrt_xmlrpc *ax,
4b6aa8
                                                        struct bug_info *bi);
4b6aa8
 unsigned rhbz_version(struct abrt_xmlrpc *ax);
4b6aa8
 
4b6aa8
+xmlrpc_value *rhbz_search_duphash(struct abrt_xmlrpc *ax,
4b6aa8
+                        const char *product, const char *version, const char *component,
4b6aa8
+                        const char *duphash);
4b6aa8
+
4b6aa8
 #ifdef __cplusplus
4b6aa8
 }
4b6aa8
 #endif
4b6aa8
-- 
4b6aa8
1.8.3.1
4b6aa8