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