Imported Upstream version 2.6.0
[debian/amanda] / device-src / activate-devpay.c
1 /* This program creates the token and certificate files for Amazon Devpay's
2  * Simple Token Service (STS). Right now you can then use those files with
3  * the S3 device. */
4
5 #include "amanda.h"
6
7 #include <curl/curl.h>
8 #include <glib.h>
9
10 #include <errno.h>
11 #include <getopt.h>
12
13 #include "base64.h"
14 #include "s3.h"
15
16 #ifndef WANT_DEVPAY
17 # error activate_devpay only works if devpay is enabled.
18 #endif
19
20 #define MAX_RESPONSE_SIZE (1024*1024)
21
22 typedef struct {
23     GString * user_token;
24     GString * access_key;
25     GString * secret_key;
26 } Credentials;
27
28 static void usage(void) {
29     g_fprintf(stderr,
30 "USAGE: activate-devpay KEY [ >> amanda.conf ]\n"
31 "  This tool uses an Amazon Devpay activation key to retrieve an\n"
32 "  user token, access key, and secret key for use with Amazon S3. Output\n"
33 "  is in a form suitable for placement in an Amanda configuration file\n");
34
35     exit(EXIT_FAILURE);
36 }
37
38 /* This function is **not** thread-safe. Sorry. */
39 static const char * parse_commandline(int argc, char ** argv) {
40     if (argc != 2) {
41         usage();
42         return NULL;
43     } else {
44         return argv[1];
45     }
46 }
47
48 static char * activation_url(const char *key) {
49     char * url;
50     char * encoded_key;
51     
52     encoded_key = curl_escape(key, 0);
53     url = g_strdup_printf(STS_BASE_URL "?Action=ActivateDesktopProduct&ActivationKey=%s&ProductToken=" STS_PRODUCT_TOKEN "&Version=2007-06-05", encoded_key);
54     curl_free(encoded_key);
55
56     return url;
57 }
58
59 /* This function is a CURLOPT_WRITEFUNCTION and a wrapper for
60    g_markup_parse_context_parse(). It's not very smart about errors. */
61 static size_t libcurl_gmarkup_glue(void *ptr, size_t size1, size_t size2,
62                                    void *stream) {
63     GMarkupParseContext *context = stream;
64     /* If this overflows, we have real problems, because we are expected to
65      * return the result of this multiplication in a size_t. */
66     size_t read_size = size1 * size2;
67     GError * error = NULL;
68
69     read_size = size1 * size2;
70
71     if (g_markup_parse_context_parse(context, ptr, read_size, &error)) {
72         return read_size;
73     } else {
74         if (error == NULL) {
75             g_fprintf(stderr, "Internal error parsing XML.\n");
76         } else {
77             g_fprintf(stderr, "Error parsing XML: %s\n",
78                     error->message);
79             g_error_free(error);
80         }
81         exit(EXIT_FAILURE);
82     }
83 }
84
85 static void do_server_stuff(const char * key, GMarkupParseContext * parser) {
86     char * url;
87     CURL* handle;
88     char curl_error_buffer[CURL_ERROR_SIZE];
89
90     handle = curl_easy_init();
91     
92     curl_easy_setopt(handle, CURLOPT_NOPROGRESS, TRUE);
93     curl_easy_setopt(handle, CURLOPT_NOSIGNAL, TRUE);
94     curl_easy_setopt(handle, CURLOPT_AUTOREFERER, TRUE);
95     curl_easy_setopt(handle, CURLOPT_ENCODING, ""); /* Support everything. */
96 #ifdef CURLOPT_MAXFILESIZE
97     curl_easy_setopt(handle, CURLOPT_MAXFILESIZE, MAX_RESPONSE_SIZE);
98 #endif
99     curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, curl_error_buffer);
100
101     curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, libcurl_gmarkup_glue);
102     curl_easy_setopt(handle, CURLOPT_WRITEDATA, parser);
103     url = activation_url(key);
104     curl_easy_setopt(handle, CURLOPT_URL, url);
105
106     if (curl_easy_perform(handle) != 0) {
107         g_error("Problem fetching data from server:\n%s",
108                 curl_error_buffer);
109         exit(EXIT_FAILURE);
110     }
111     
112     g_free(url);
113 }
114
115 static void parser_got_text(GMarkupParseContext * context,
116                             const char * text,
117                             size_t text_len,
118                             gpointer user_data,
119                             GError ** error) {
120     Credentials * rval = user_data;
121
122     const char * current_tag = g_markup_parse_context_get_element(context);
123
124     g_assert(rval != NULL);
125     g_assert(*error == NULL);
126
127     /* We use strrstr instead of strcmp because Amazon uses namespaces
128      * that I don't want to deal with. */
129     if (g_strrstr(current_tag, "UserToken")) {
130         g_string_append_len(rval->user_token, text, text_len);
131         return;
132     } else if (g_strrstr(current_tag, "AWSAccessKeyId")) {
133         g_string_append_len(rval->access_key, text, text_len);
134         return;
135     } else if (g_strrstr(current_tag, "SecretAccessKey")) {
136         g_string_append_len(rval->secret_key, text, text_len);
137         return;
138     } else if (g_strrstr(current_tag, "Code")) {
139         /* Is it a code we know? */
140         if (strncmp(text, "ExpiredActivationKey", text_len) == 0) {
141             g_set_error(error, G_MARKUP_ERROR, -1,
142                         "Activation key has expired; get a new one.");
143         } else if (strncmp(text, "InvalidActivationKey", text_len) == 0) {
144             g_set_error(error, G_MARKUP_ERROR, -1,
145                         "Activation key is not valid; double-check.");
146         } else {
147             /* Do nothing; wait for the message. */
148         }
149     } else if (g_strrstr(current_tag, "Message")) {
150         g_set_error(error, G_MARKUP_ERROR, -1, "%.*s", text_len, text);
151     }
152 }               
153
154 static void parser_got_error(GMarkupParseContext * context G_GNUC_UNUSED,
155                              GError * error,
156                              gpointer user_data G_GNUC_UNUSED) {
157     g_fprintf (stderr, "Problem with Amazon response: %s\n", error->message);
158     exit(EXIT_FAILURE);
159 }
160
161 static GMarkupParseContext * parser_init(Credentials * credentials) {
162     static const GMarkupParser parser_settings = {
163         NULL, /* start_element */
164         NULL, /* end_element */
165         parser_got_text, /* text */
166         NULL, /* passthrough */
167         parser_got_error /* error */
168     };
169     bzero(credentials, sizeof(*credentials));
170
171     credentials->user_token = g_string_new("");
172     credentials->access_key = g_string_new("");
173     credentials->secret_key = g_string_new("");
174
175     return g_markup_parse_context_new(&parser_settings, 0, credentials, NULL);
176 }
177
178 static void parser_cleanup(GMarkupParseContext * context) {
179     GError * error = NULL;
180     g_markup_parse_context_end_parse(context, &error);
181     
182     if (error != NULL) {
183         g_fprintf (stderr, "Unexpected end of Amazon response: %s\n",
184                  error->message);
185         exit(EXIT_FAILURE);
186     }
187
188     g_markup_parse_context_free(context);
189 }
190
191 /* This function is responsible for the whole output thing. */
192 static void do_output(Credentials * rare) {
193     if (rare == NULL ||
194         rare->user_token == NULL || !rare->user_token->len ||
195         rare->access_key == NULL || !rare->access_key->len ||
196         rare->secret_key == NULL || !rare->secret_key->len) {
197         g_fprintf(stderr, "Missing authentication data in response!\n");
198         exit(EXIT_FAILURE);
199     }
200
201     g_printf("device_property \"S3_USER_TOKEN\" \"%s\"\n"
202              "device_property \"S3_ACCESS_KEY\" \"%s\"\n"
203              "device_property \"S3_SECRET_KEY\" \"%s\"\n",
204              rare->user_token->str, rare->access_key->str,
205              rare->secret_key->str);
206 }
207
208 int main(int argc, char ** argv) {
209     const char * key;
210     GMarkupParseContext * parser;
211     Credentials credentials;
212
213     key = parse_commandline(argc, argv);
214
215     curl_global_init(CURL_GLOBAL_ALL);
216     parser = parser_init(&credentials);
217
218     do_server_stuff(key, parser);
219
220     curl_global_cleanup();
221     parser_cleanup(parser);
222
223     do_output(&credentials);
224     
225     return 0;
226 }