Fix GSASL in imap4d.
* auth/gsasl.c (_gsasl_readline): Prevent stucking in blocking streams. * imap4d/auth_gsasl.c: Remove deprecated functions and data types. * imap4d/auth_gss.c (auth_gssapi): Fix signature. * imap4d/copy.c (imap4d_copy0): Fix return value. * imap4d/imap4d.c (imap4d_cfg_param): Fix type of login-disabled. * include/mailutils/gsasl.h (struct mu_gsasl_module_data): New members: service, realm, hostname, anon_user. * libcfg/gsasl.c (mu_gsasl_param): New keywords: service, realm, hostname, anonymous-user.
Showing
8 changed files
with
155 additions
and
120 deletions
1 | 2008-08-14 Sergey Poznyakoff <gray@gnu.org.ua> | ||
2 | |||
3 | Fix GSASL in imap4d. | ||
4 | * auth/gsasl.c (_gsasl_readline): Prevent stucking in | ||
5 | blocking streams. | ||
6 | * imap4d/auth_gsasl.c: Remove deprecated functions and data types. | ||
7 | * imap4d/auth_gss.c (auth_gssapi): Fix signature. | ||
8 | * imap4d/copy.c (imap4d_copy0): Fix return value. | ||
9 | * imap4d/imap4d.c (imap4d_cfg_param): Fix type of login-disabled. | ||
10 | * include/mailutils/gsasl.h (struct mu_gsasl_module_data): New | ||
11 | members: service, realm, hostname, anon_user. | ||
12 | * libcfg/gsasl.c (mu_gsasl_param): New keywords: service, | ||
13 | realm, hostname, anonymous-user. | ||
14 | |||
1 | 2008-08-13 Sergey Poznyakoff <gray@gnu.org.ua> | 15 | 2008-08-13 Sergey Poznyakoff <gray@gnu.org.ua> |
2 | 16 | ||
3 | * imap4d/util.c (imap4d_readline): Fix loop break condition. | 17 | * imap4d/util.c (imap4d_readline): Fix loop break condition. | ... | ... |
1 | /* GNU Mailutils -- a suite of utilities for electronic mail | 1 | /* GNU Mailutils -- a suite of utilities for electronic mail |
2 | Copyright (C) 2003, 2004, 2005 Free Software Foundation, Inc. | 2 | Copyright (C) 2003, 2004, 2005, 2008 Free Software Foundation, Inc. |
3 | 3 | ||
4 | This library is free software; you can redistribute it and/or | 4 | This library is free software; you can redistribute it and/or |
5 | modify it under the terms of the GNU Lesser General Public | 5 | modify it under the terms of the GNU Lesser General Public |
... | @@ -90,11 +90,11 @@ _gsasl_readline (mu_stream_t stream, char *optr, size_t osize, | ... | @@ -90,11 +90,11 @@ _gsasl_readline (mu_stream_t stream, char *optr, size_t osize, |
90 | 90 | ||
91 | do | 91 | do |
92 | { | 92 | { |
93 | char buf[80]; | 93 | char c; |
94 | size_t sz; | 94 | size_t sz; |
95 | int status; | 95 | int status; |
96 | 96 | ||
97 | status = mu_stream_sequential_read (s->stream, buf, sizeof (buf), &sz); | 97 | status = mu_stream_sequential_read (s->stream, &c, 1, &sz); |
98 | if (status == EINTR) | 98 | if (status == EINTR) |
99 | continue; | 99 | continue; |
100 | else if (status) | 100 | else if (status) |
... | @@ -102,7 +102,7 @@ _gsasl_readline (mu_stream_t stream, char *optr, size_t osize, | ... | @@ -102,7 +102,7 @@ _gsasl_readline (mu_stream_t stream, char *optr, size_t osize, |
102 | free (bufp); | 102 | free (bufp); |
103 | return status; | 103 | return status; |
104 | } | 104 | } |
105 | rc = _auth_lb_grow (s->lb, buf, sz); | 105 | rc = _auth_lb_grow (s->lb, &c, sz); |
106 | if (rc) | 106 | if (rc) |
107 | return rc; | 107 | return rc; |
108 | 108 | ... | ... |
1 | /* GNU Mailutils -- a suite of utilities for electronic mail | 1 | /* GNU Mailutils -- a suite of utilities for electronic mail |
2 | Copyright (C) 2003, 2004, 2005, 2007 Free Software Foundation, Inc. | 2 | Copyright (C) 2003, 2004, 2005, 2007, 2008 Free Software Foundation, Inc. |
3 | 3 | ||
4 | GNU Mailutils is free software; you can redistribute it and/or modify | 4 | GNU Mailutils is free software; you can redistribute it and/or modify |
5 | it under the terms of the GNU General Public License as published by | 5 | it under the terms of the GNU General Public License as published by |
... | @@ -23,8 +23,8 @@ | ... | @@ -23,8 +23,8 @@ |
23 | # include <mailutils/sql.h> | 23 | # include <mailutils/sql.h> |
24 | #endif | 24 | #endif |
25 | 25 | ||
26 | static Gsasl_ctx *ctx; | 26 | static Gsasl *ctx; |
27 | static Gsasl_session_ctx *sess_ctx; | 27 | static Gsasl_session *sess_ctx; |
28 | 28 | ||
29 | static void auth_gsasl_capa_init (int disable); | 29 | static void auth_gsasl_capa_init (int disable); |
30 | 30 | ||
... | @@ -69,21 +69,18 @@ gsasl_replace_streams (void *self, void *data) | ... | @@ -69,21 +69,18 @@ gsasl_replace_streams (void *self, void *data) |
69 | static void | 69 | static void |
70 | finish_session (void) | 70 | finish_session (void) |
71 | { | 71 | { |
72 | gsasl_server_finish (sess_ctx); | 72 | gsasl_finish (sess_ctx); |
73 | } | 73 | } |
74 | 74 | ||
75 | static int | 75 | static int |
76 | auth_gsasl (struct imap4d_command *command, | 76 | auth_gsasl (struct imap4d_command *command, char *auth_type, char **username) |
77 | char *auth_type, char *arg, char **username) | ||
78 | { | 77 | { |
79 | char *input = NULL; | 78 | char *input_str = NULL; |
79 | size_t input_size = 0; | ||
80 | size_t input_len; | ||
80 | char *output; | 81 | char *output; |
81 | char *s; | ||
82 | int rc; | 82 | int rc; |
83 | 83 | ||
84 | input = util_getword (arg, &s); | ||
85 | util_unquote (&input); | ||
86 | |||
87 | rc = gsasl_server_start (ctx, auth_type, &sess_ctx); | 84 | rc = gsasl_server_start (ctx, auth_type, &sess_ctx); |
88 | if (rc != GSASL_OK) | 85 | if (rc != GSASL_OK) |
89 | { | 86 | { |
... | @@ -92,18 +89,21 @@ auth_gsasl (struct imap4d_command *command, | ... | @@ -92,18 +89,21 @@ auth_gsasl (struct imap4d_command *command, |
92 | return 0; | 89 | return 0; |
93 | } | 90 | } |
94 | 91 | ||
95 | gsasl_server_application_data_set (sess_ctx, username); | 92 | gsasl_callback_hook_set (ctx, username); |
96 | 93 | ||
97 | output = NULL; | 94 | output = NULL; |
98 | while ((rc = gsasl_step64 (sess_ctx, input, &output)) == GSASL_NEEDS_MORE) | 95 | while ((rc = gsasl_step64 (sess_ctx, input_str, &output)) |
96 | == GSASL_NEEDS_MORE) | ||
99 | { | 97 | { |
100 | util_send ("+ %s\r\n", output); | 98 | util_send ("+ %s\r\n", output); |
101 | input = imap4d_readline_ex (); | 99 | imap4d_getline (&input_str, &input_size, &input_len); |
102 | } | 100 | } |
103 | 101 | ||
102 | free (input_str); | ||
104 | if (rc != GSASL_OK) | 103 | if (rc != GSASL_OK) |
105 | { | 104 | { |
106 | mu_diag_output (MU_DIAG_NOTICE, _("GSASL error: %s"), gsasl_strerror (rc)); | 105 | mu_diag_output (MU_DIAG_NOTICE, _("GSASL error: %s"), |
106 | gsasl_strerror (rc)); | ||
107 | free (output); | 107 | free (output); |
108 | return RESP_NO; | 108 | return RESP_NO; |
109 | } | 109 | } |
... | @@ -169,133 +169,142 @@ auth_gsasl_capa_init (int disable) | ... | @@ -169,133 +169,142 @@ auth_gsasl_capa_init (int disable) |
169 | free (listmech); | 169 | free (listmech); |
170 | } | 170 | } |
171 | 171 | ||
172 | /* This is for DIGEST-MD5 */ | 172 | #define IMAP_GSSAPI_SERVICE "imap" |
173 | |||
173 | static int | 174 | static int |
174 | cb_realm (Gsasl_session_ctx *ctx, char *out, size_t *outlen, size_t nth) | 175 | retrieve_password (Gsasl *ctx, Gsasl_session *sctx) |
175 | { | 176 | { |
176 | char *realm = util_localname (); | 177 | char **username = gsasl_callback_hook_get (ctx); |
178 | char *authid = gsasl_property_get (sctx, GSASL_AUTHID); | ||
177 | 179 | ||
178 | if (nth > 0) | 180 | if (username && *username == 0) |
179 | return GSASL_NO_MORE_REALMS; | 181 | *username = strdup (authid); |
180 | 182 | ||
181 | if (out) | 183 | if (mu_gsasl_module_data.cram_md5_pwd |
184 | && access (mu_gsasl_module_data.cram_md5_pwd, R_OK) == 0) | ||
185 | { | ||
186 | char *key; | ||
187 | int rc = gsasl_simple_getpass (mu_gsasl_module_data.cram_md5_pwd, | ||
188 | authid, &key); | ||
189 | if (rc == GSASL_OK) | ||
182 | { | 190 | { |
183 | if (*outlen < strlen (realm)) | 191 | gsasl_property_set (sctx, GSASL_PASSWORD, key); |
184 | return GSASL_TOO_SMALL_BUFFER; | 192 | free (key); |
185 | memcpy (out, realm, strlen (realm)); | 193 | return rc; |
194 | } | ||
186 | } | 195 | } |
187 | 196 | ||
188 | *outlen = strlen (realm); | 197 | #ifdef USE_SQL |
189 | 198 | if (mu_sql_module_config.password_type == password_plaintext) | |
199 | { | ||
200 | char *passwd; | ||
201 | int status = mu_sql_getpass (*username, &passwd); | ||
202 | if (status == 0) | ||
203 | { | ||
204 | gsasl_property_set (sctx, GSASL_PASSWORD, passwd); | ||
205 | free (passwd); | ||
190 | return GSASL_OK; | 206 | return GSASL_OK; |
207 | } | ||
208 | } | ||
209 | #endif | ||
210 | |||
211 | return GSASL_AUTHENTICATION_ERROR; | ||
191 | } | 212 | } |
192 | 213 | ||
193 | static int | 214 | static int |
194 | cb_validate (Gsasl_session_ctx *ctx, | 215 | cb_validate (Gsasl *ctx, Gsasl_session *sctx) |
195 | const char *authorization_id, | ||
196 | const char *authentication_id, | ||
197 | const char *password) | ||
198 | { | 216 | { |
199 | int rc; | 217 | int rc; |
200 | struct mu_auth_data *auth; | 218 | struct mu_auth_data *auth; |
201 | char **username = gsasl_server_application_data_get (ctx); | 219 | char **username = gsasl_callback_hook_get (ctx); |
220 | const char *authid = gsasl_property_get (sctx, GSASL_AUTHID); | ||
221 | const char *pass = gsasl_property_get (sctx, GSASL_PASSWORD); | ||
202 | 222 | ||
203 | *username = strdup (authentication_id ? | 223 | if (!authid) |
204 | authentication_id : authorization_id); | 224 | return GSASL_NO_AUTHID; |
225 | if (!pass) | ||
226 | return GSASL_NO_PASSWORD; | ||
205 | 227 | ||
206 | auth_data = mu_get_auth_by_name (*username); | 228 | *username = strdup (authid); |
207 | 229 | ||
208 | if (auth_data == NULL) | 230 | auth = mu_get_auth_by_name (*username); |
231 | |||
232 | if (auth == NULL) | ||
209 | return GSASL_AUTHENTICATION_ERROR; | 233 | return GSASL_AUTHENTICATION_ERROR; |
210 | 234 | ||
211 | rc = mu_authenticate (auth, password); | 235 | rc = mu_authenticate (auth, pass); |
212 | mu_auth_data_free (auth); | 236 | mu_auth_data_free (auth); |
213 | 237 | ||
214 | return rc == 0 ? GSASL_OK : GSASL_AUTHENTICATION_ERROR; | 238 | return rc == 0 ? GSASL_OK : GSASL_AUTHENTICATION_ERROR; |
215 | } | 239 | } |
216 | 240 | ||
217 | #define GSSAPI_SERVICE "imap" | ||
218 | |||
219 | static int | 241 | static int |
220 | cb_service (Gsasl_session_ctx *ctx, char *srv, size_t *srvlen, | 242 | callback (Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop) |
221 | char *host, size_t *hostlen) | ||
222 | { | 243 | { |
223 | char *hostname = util_localname (); | 244 | int rc = GSASL_OK; |
224 | 245 | ||
225 | if (srv) | 246 | switch (prop) { |
226 | { | 247 | case GSASL_PASSWORD: |
227 | if (*srvlen < strlen (GSSAPI_SERVICE)) | 248 | rc = retrieve_password (ctx, sctx); |
228 | return GSASL_TOO_SMALL_BUFFER; | 249 | break; |
229 | 250 | ||
230 | memcpy (srv, GSSAPI_SERVICE, strlen (GSSAPI_SERVICE)); | 251 | case GSASL_SERVICE: |
231 | } | 252 | gsasl_property_set (sctx, prop, |
253 | mu_gsasl_module_data.service ? | ||
254 | mu_gsasl_module_data.service :IMAP_GSSAPI_SERVICE); | ||
255 | break; | ||
256 | |||
257 | case GSASL_REALM: | ||
258 | gsasl_property_set (sctx, prop, | ||
259 | mu_gsasl_module_data.realm ? | ||
260 | mu_gsasl_module_data.realm : util_localname ()); | ||
261 | break; | ||
262 | |||
263 | case GSASL_HOSTNAME: | ||
264 | gsasl_property_set (sctx, prop, | ||
265 | mu_gsasl_module_data.hostname ? | ||
266 | mu_gsasl_module_data.hostname : util_localname ()); | ||
267 | break; | ||
268 | |||
269 | #if 0 | ||
270 | FIXME: | ||
271 | case GSASL_VALIDATE_EXTERNAL: | ||
272 | case GSASL_VALIDATE_SECURID: | ||
273 | #endif | ||
232 | 274 | ||
233 | if (srvlen) | 275 | case GSASL_VALIDATE_SIMPLE: |
234 | *srvlen = strlen (GSSAPI_SERVICE); | 276 | rc = cb_validate (ctx, sctx); |
277 | break; | ||
235 | 278 | ||
236 | if (host) | 279 | case GSASL_VALIDATE_ANONYMOUS: |
280 | if (mu_gsasl_module_data.anon_user) | ||
237 | { | 281 | { |
238 | if (*hostlen < strlen (hostname)) | 282 | char **username = gsasl_callback_hook_get (ctx); |
239 | return GSASL_TOO_SMALL_BUFFER; | 283 | mu_diag_output (MU_DIAG_INFO, _("Anonymous user %s logged in"), |
240 | 284 | gsasl_property_get (sctx, GSASL_ANONYMOUS_TOKEN)); | |
241 | memcpy (host, hostname, strlen (hostname)); | 285 | *username = strdup (mu_gsasl_module_data.anon_user); |
242 | } | 286 | } |
243 | 287 | else | |
244 | if (hostlen) | ||
245 | *hostlen = strlen (hostname); | ||
246 | |||
247 | return GSASL_OK; | ||
248 | } | ||
249 | |||
250 | /* This gets called when SASL mechanism EXTERNAL is invoked */ | ||
251 | static int | ||
252 | cb_external (Gsasl_session_ctx *ctx) | ||
253 | { | ||
254 | return GSASL_AUTHENTICATION_ERROR; | ||
255 | } | ||
256 | |||
257 | /* This gets called when SASL mechanism CRAM-MD5 or DIGEST-MD5 is invoked */ | ||
258 | |||
259 | static int | ||
260 | cb_retrieve (Gsasl_session_ctx *ctx, | ||
261 | const char *authentication_id, | ||
262 | const char *authorization_id, | ||
263 | const char *realm, | ||
264 | char *key, | ||
265 | size_t *keylen) | ||
266 | { | ||
267 | char **username = gsasl_server_application_data_get (ctx); | ||
268 | |||
269 | if (username && *username == 0 && authentication_id) | ||
270 | *username = strdup (authentication_id); | ||
271 | |||
272 | if (mu_gsasl_module_data.cram_md5_pwd | ||
273 | && access (mu_gsasl_module_data.cram_md5_pwd, R_OK) == 0) | ||
274 | { | 288 | { |
275 | int rc = gsasl_md5pwd_get_password (mu_gsasl_module_data.cram_md5_pwd, | 289 | mu_diag_output (MU_DIAG_ERR, |
276 | authentication_id, | 290 | _("Attempt to log in as anonymous user denied")); |
277 | key, keylen); | ||
278 | if (rc == GSASL_OK) | ||
279 | return rc; | ||
280 | } | 291 | } |
292 | break; | ||
281 | 293 | ||
282 | #ifdef USE_SQL | 294 | case GSASL_VALIDATE_GSSAPI: |
283 | if (mu_sql_module_config.password_type == password_plaintext) | ||
284 | { | ||
285 | char *passwd; | ||
286 | int status = mu_sql_getpass (username, &passwd); | ||
287 | if (status == 0) | ||
288 | { | 295 | { |
289 | *keylen = strlen (passwd); | 296 | char **username = gsasl_callback_hook_get (ctx); |
290 | if (key) | 297 | *username = strdup (gsasl_property_get(sctx, GSASL_AUTHZID)); |
291 | memcpy (key, passwd, *keylen); | 298 | break; |
292 | free (passwd); | ||
293 | return GSASL_OK; | ||
294 | } | 299 | } |
300 | |||
301 | default: | ||
302 | rc = GSASL_NO_CALLBACK; | ||
303 | mu_error (_("Unsupported callback property %d"), prop); | ||
304 | break; | ||
295 | } | 305 | } |
296 | #endif | ||
297 | 306 | ||
298 | return GSASL_AUTHENTICATION_ERROR; | 307 | return rc; |
299 | } | 308 | } |
300 | 309 | ||
301 | void | 310 | void |
... | @@ -310,12 +319,7 @@ auth_gsasl_init () | ... | @@ -310,12 +319,7 @@ auth_gsasl_init () |
310 | gsasl_strerror (rc)); | 319 | gsasl_strerror (rc)); |
311 | } | 320 | } |
312 | 321 | ||
313 | gsasl_server_callback_realm_set (ctx, cb_realm); | 322 | gsasl_callback_set (ctx, callback); |
314 | gsasl_server_callback_external_set (ctx, cb_external); | ||
315 | gsasl_server_callback_validate_set (ctx, cb_validate); | ||
316 | gsasl_server_callback_service_set (ctx, cb_service); | ||
317 | |||
318 | gsasl_server_callback_retrieve_set (ctx, cb_retrieve); | ||
319 | 323 | ||
320 | auth_gsasl_capa_init (0); | 324 | auth_gsasl_capa_init (0); |
321 | } | 325 | } | ... | ... |
... | @@ -110,7 +110,7 @@ imap4d_gss_userok (gss_buffer_t client_name, char *name) | ... | @@ -110,7 +110,7 @@ imap4d_gss_userok (gss_buffer_t client_name, char *name) |
110 | 110 | ||
111 | static int | 111 | static int |
112 | auth_gssapi (struct imap4d_command *command, | 112 | auth_gssapi (struct imap4d_command *command, |
113 | char *auth_type_unused, char *arg_unused, char **username) | 113 | char *auth_type_unused, char **username) |
114 | { | 114 | { |
115 | gss_buffer_desc tokbuf, outbuf; | 115 | gss_buffer_desc tokbuf, outbuf; |
116 | OM_uint32 maj_stat, min_stat, min_stat2; | 116 | OM_uint32 maj_stat, min_stat, min_stat2; | ... | ... |
... | @@ -130,6 +130,6 @@ imap4d_copy0 (imap4d_tokbuf_t tok, int isuid, char **err_text) | ... | @@ -130,6 +130,6 @@ imap4d_copy0 (imap4d_tokbuf_t tok, int isuid, char **err_text) |
130 | of the text of the tagged NO response. This gives a hint to the | 130 | of the text of the tagged NO response. This gives a hint to the |
131 | client that it can attempt a CREATE command and retry the copy if | 131 | client that it can attempt a CREATE command and retry the copy if |
132 | the CREATE is successful. */ | 132 | the CREATE is successful. */ |
133 | *err_text = "NO [TRYCREATE] failed"; | 133 | *err_text = "[TRYCREATE] failed"; |
134 | return RESP_NONE; | 134 | return RESP_NO; |
135 | } | 135 | } | ... | ... |
... | @@ -294,7 +294,7 @@ static struct mu_cfg_param imap4d_cfg_param[] = { | ... | @@ -294,7 +294,7 @@ static struct mu_cfg_param imap4d_cfg_param[] = { |
294 | { "shared-namespace", mu_cfg_callback, NULL, 0, cb_shared, | 294 | { "shared-namespace", mu_cfg_callback, NULL, 0, cb_shared, |
295 | N_("Set shared namespace. Argument is a colon-separated list " | 295 | N_("Set shared namespace. Argument is a colon-separated list " |
296 | "of directories comprising the namespace.") }, | 296 | "of directories comprising the namespace.") }, |
297 | { "login-disabled", mu_cfg_int, &login_disabled, 0, NULL, | 297 | { "login-disabled", mu_cfg_bool, &login_disabled, 0, NULL, |
298 | N_("Disable LOGIN command.") }, | 298 | N_("Disable LOGIN command.") }, |
299 | { "create-home-dir", mu_cfg_bool, &create_home_dir, 0, NULL, | 299 | { "create-home-dir", mu_cfg_bool, &create_home_dir, 0, NULL, |
300 | N_("If true, create non-existing user home directories.") }, | 300 | N_("If true, create non-existing user home directories.") }, | ... | ... |
1 | /* GNU Mailutils -- a suite of utilities for electronic mail | 1 | /* GNU Mailutils -- a suite of utilities for electronic mail |
2 | Copyright (C) 2003, 2004, 2005, 2007 Free Software Foundation, Inc. | 2 | Copyright (C) 2003, 2004, 2005, 2007, 2008 Free Software Foundation, Inc. |
3 | 3 | ||
4 | This library is free software; you can redistribute it and/or | 4 | This library is free software; you can redistribute it and/or |
5 | modify it under the terms of the GNU Lesser General Public | 5 | modify it under the terms of the GNU Lesser General Public |
... | @@ -21,6 +21,10 @@ | ... | @@ -21,6 +21,10 @@ |
21 | 21 | ||
22 | struct mu_gsasl_module_data | 22 | struct mu_gsasl_module_data |
23 | { | 23 | { |
24 | char *service; | ||
25 | char *realm; | ||
26 | char *hostname; | ||
27 | char *anon_user; | ||
24 | char *cram_md5_pwd; | 28 | char *cram_md5_pwd; |
25 | }; | 29 | }; |
26 | 30 | ... | ... |
... | @@ -28,6 +28,19 @@ static struct mu_cfg_param mu_gsasl_param[] = { | ... | @@ -28,6 +28,19 @@ static struct mu_cfg_param mu_gsasl_param[] = { |
28 | { "cram-passwd", mu_cfg_string, &gsasl_settings.cram_md5_pwd, 0, NULL, | 28 | { "cram-passwd", mu_cfg_string, &gsasl_settings.cram_md5_pwd, 0, NULL, |
29 | N_("Name of GSASL password file."), | 29 | N_("Name of GSASL password file."), |
30 | N_("file") }, | 30 | N_("file") }, |
31 | { "service", mu_cfg_string, &gsasl_settings.service, 0, NULL, | ||
32 | N_("SASL service name."), | ||
33 | N_("name") }, | ||
34 | { "realm", mu_cfg_string, &gsasl_settings.realm, 0, NULL, | ||
35 | N_("SASL realm name."), | ||
36 | N_("name") }, | ||
37 | { "hostname", mu_cfg_string, &gsasl_settings.hostname, 0, NULL, | ||
38 | N_("SASL host name."), | ||
39 | N_("name") }, | ||
40 | { "anonymous-user", mu_cfg_string, &gsasl_settings.anon_user, 0, NULL, | ||
41 | N_("Anonymous user name."), | ||
42 | N_("name") }, | ||
43 | |||
31 | { NULL } | 44 | { NULL } |
32 | }; | 45 | }; |
33 | 46 | ... | ... |
-
Please register or sign in to post a comment