/[gpgoe]/trunk/src/OECrypto.c
ViewVC logotype

Annotation of /trunk/src/OECrypto.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 12 - (hide annotations)
Fri Apr 7 10:46:41 2006 UTC (19 years, 1 month ago) by twoaday
File MIME type: text/plain
File size: 16957 byte(s)


1 twoaday 1 /* OECrypto.c - OE crypto functions
2     * Copyright (C) 2001, 2002, 2003, 2006 Timo Schulz
3     *
4     * This file is part of GPGOE.
5     *
6     * GPGOE is free software; you can redistribute it and/or modify
7     * it under the terms of the GNU Lesser General Public License as published by
8     * the Free Software Foundation; either version 2.1 of the License, or
9     * (at your option) any later version.
10     *
11     * GPGOE is distributed in the hope that it will be useful,
12     * but WITHOUT ANY WARRANTY; without even the implied warranty of
13     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14     * GNU General Public License for more details.
15     *
16     * You should have received a copy of the GNU Lesser General Public License
17     * along with GPGOE; if not, write to the Free Software Foundation,
18     * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
19     */
20    
21     #ifdef HAVE_CONFIG_H
22     #include <config.h>
23     #endif
24     #include <windows.h>
25     #include <assert.h>
26 twoaday 12 #include <commctrl.h>
27    
28 twoaday 1 #include "resource.h"
29     #include "gpgme.h"
30     #include "GPGOE.h"
31    
32    
33     /* Valid OpenPGP message types. */
34     enum {
35 twoaday 12 PGP_MESSAGE = 1,
36     PGP_CLEARSIG = 2,
37     PGP_SIG = 4,
38     PGP_KEY = 8,
39 twoaday 1 };
40    
41    
42 twoaday 10 /* Move the keyboard focus to the message window
43     to copy the text to the clipboard. */
44 twoaday 1 static void
45     set_focus (HWND main)
46     {
47     HWND doc, mime, msg;
48    
49     doc = FindWindowEx (main, NULL, "ME_DocHost", NULL );
50     mime = FindWindowEx (doc, NULL, "##MimeEdit_Server", NULL);
51     msg = FindWindowEx (mime, NULL, "Internet Explorer_Server", NULL);
52    
53     AttachThreadInput (GetCurrentThreadId (),
54     GetWindowThreadProcessId (main, NULL),
55     TRUE);
56    
57     SetFocus (doc);
58     SetFocus (mime);
59     SetFocus (msg);
60    
61     AttachThreadInput (GetCurrentThreadId (),
62     GetWindowThreadProcessId (main, NULL),
63     FALSE);
64     }
65    
66    
67     /* Copy the message to the clipboard and then return the clipboard
68     data (which is the message). */
69     static char*
70     window_get_message (HWND main_hwnd)
71     {
72     set_focus (main_hwnd);
73     SetForegroundWindow (main_hwnd);
74 twoaday 12 SendMessage (main_hwnd, WM_COMMAND, MAKEWPARAM (ID_OE_SELECTALL, 0), 0);
75     SendMessage (main_hwnd, WM_COMMAND, MAKEWPARAM (ID_OE_COPY, 0), 0);
76 twoaday 1
77 twoaday 10 /* even so SendMessage() should wait, we wait for safety reasons. */
78 twoaday 1 Sleep (200);
79 twoaday 10 return get_clip_text (NULL);
80 twoaday 1 }
81    
82    
83     /* Paste the message given in @msg into the window @main_hwnd.
84     To make sure all existing text is overwritten, "select all"
85     is send to the window first. */
86     static void
87     window_set_message (HWND main_hwnd, const char *msg)
88     {
89     set_clip_text (NULL, msg, strlen (msg));
90     set_focus (main_hwnd);
91     SetForegroundWindow (main_hwnd);
92 twoaday 12 SendMessage (main_hwnd, WM_COMMAND, MAKEWPARAM (ID_OE_SELECTALL, 0), 0);
93     SendMessage (main_hwnd, WM_COMMAND, MAKEWPARAM (ID_OE_PASTE, 0), 0);
94 twoaday 1 }
95    
96    
97     /* Try to find the given recipient by key @key and
98     return it (NULL in case of error). */
99     static recip_list_t
100     find_recipient (recip_list_t rset, gpgme_key_t key)
101     {
102     recip_list_t n;
103     gpgme_user_id_t u;
104    
105     for (n = rset; n; n = n->next) {
106     for (u = key->uids; u; u = u->next) {
107     if (u->email && strstr (u->email, n->addr))
108     return n;
109     }
110     }
111     return NULL;
112     }
113    
114    
115     /* Add recipient with address @addr and key @key to @rset. */
116     void
117     add_recipient (recip_list_t *rset, const char *addr, gpgme_key_t key)
118     {
119     recip_list_t r, n;
120    
121     r = xcalloc (1, sizeof *r);
122     r->addr = xstrdup (addr);
123     r->key = key;
124     if (!*rset)
125     *rset = r;
126     else {
127     for (n=*rset; n->next; n=n->next)
128     ;
129     n->next = r;
130     }
131     }
132    
133    
134     /* Release recipient list @rset. */
135     static void
136     release_recipient (recip_list_t rset)
137     {
138     recip_list_t n;
139    
140     while (rset != NULL) {
141     n = rset->next;
142     if (rset->key)
143     gpgme_key_release (rset->key);
144     free_if_alloc (rset->addr);
145     free_if_alloc (rset);
146     rset = n;
147     }
148     }
149    
150    
151     /* This functions converts the OE recipient fields to GPG compatible fields.
152     Currently the function support the following 'From' styles:
153     John Hacker <[email protected]>; [email protected]
154     or Foo Bar <[email protected]>, [email protected].
155     only the part between '<' '>' are added.
156     */
157     static int
158     parse_recipients (plugin_ctx_t ctx, recip_list_t *rset, char *recpt)
159     {
160     char *token, *p;
161     int count = 0;
162     char *p1, *p2;
163     int n1, n2;
164    
165     assert (recpt && rset);
166    
167     token = strtok (recpt, ",;");
168     while (token != NULL ) {
169     if ((p1=strchr (token, '<')) && (p2=strchr (token, '>'))
170     && strchr (token, '@')) {
171     n1 = p1 - token + 1 ;
172     n2 = p2 - token + 1;
173    
174     p = xcalloc (1, strlen (token) + 1);
175     memcpy (p, token+n1, n2-n1-1);
176     add_recipient (rset, p, NULL);
177     count++;
178     free_if_alloc (p);
179     }
180     else if (strchr (token, '@') && !strchr (token, ' ')) {
181     p = xcalloc (1, strlen (token) + 1);
182     memcpy (p, token, strlen (token));
183     add_recipient (rset, p, NULL);
184     count++;
185     free_if_alloc (p);
186     }
187     token = strtok (NULL, ",;");
188     }
189     return count;
190     }
191    
192    
193 twoaday 10 /* Fill in the key part of the recipient list @addrs if possible. */
194 twoaday 1 static int
195     get_keys_from_addresses (recip_list_t addrs)
196     {
197     gpgme_ctx_t ctx;
198     gpgme_error_t err;
199     gpgme_key_t pk;
200     recip_list_t n, fnd;
201     char *p;
202     int nkeys=0;
203     DWORD len=0;
204    
205     for (n = addrs; n; n = n->next)
206     len += strlen (n->addr) + 3;
207     p = xcalloc (1, len + 1);
208     for (n = addrs; n; n = n->next) {
209     strcat (p, n->addr);
210     strcat (p, " ");
211     }
212    
213     err = gpgme_new (&ctx);
214     if (!err)
215     err = gpgme_op_keylist_start (ctx, p, 0);
216     while (!err) {
217     err = gpgme_op_keylist_next (ctx, &pk);
218     if (err)
219     break;
220 twoaday 10 /* do not add invalid keys. */
221 twoaday 1 if (pk->disabled || pk->expired || pk->revoked)
222     continue;
223     fnd = find_recipient (addrs, pk);
224     if (fnd != NULL && pk->can_encrypt) {
225     fnd->key = pk;
226     nkeys++;
227     }
228     }
229    
230     gpgme_release (ctx);
231     free_if_alloc (p);
232     return nkeys;
233     }
234    
235    
236 twoaday 12 /* Create gpgme input data object. If @encode is 1, use UTF8 conversion. */
237     gpgme_error_t
238     create_in_data (gpgme_data_t *in, const char *buf, int encode)
239     {
240     gpgme_error_t err;
241     char *enc_buf;
242    
243     enc_buf = encode? native_to_utf8 (buf) : xstrdup (buf);
244     err = gpgme_data_new_from_mem (in, enc_buf, strlen (enc_buf), 1);
245     free (enc_buf);
246     return err;
247     }
248    
249    
250 twoaday 1 /* Map gpgme data object to a string. */
251     void
252     map_gpgme_data (gpgme_data_t out, char **r_msg)
253     {
254     size_t len = 0;
255     char *p;
256    
257     p = gpgme_data_release_and_get_mem (out, &len);
258     free_if_alloc (*r_msg);
259     *r_msg = xcalloc (1, len+1);
260     memcpy (*r_msg, p, len);
261     gpgme_free (p);
262     }
263    
264    
265     /* Try to extract the needed key information and retrieve
266     the keys if possible. */
267     static gpgme_error_t
268     get_keys (plugin_ctx_t ctx, recip_list_t *r_list,
269     gpgme_key_t **r_keys, int *r_n)
270     {
271     gpgme_key_t *keys;
272     recip_list_t addrs = NULL, n;
273     int rcount = 0, nkeys = 0;
274     int i;
275    
276     if (ctx->to && strlen (ctx->to) >= 3)
277     rcount += parse_recipients (ctx, &addrs, ctx->to);
278     if (ctx->cc && strlen (ctx->cc) >= 3)
279     rcount += parse_recipients (ctx, &addrs, ctx->cc);
280     if (ctx->bcc && strlen (ctx->bcc) >= 3)
281     rcount += parse_recipients (ctx, &addrs, ctx->bcc);
282    
283     nkeys = get_keys_from_addresses (addrs);
284 twoaday 10 /* at least some keys were not found, so we offer to select them. */
285 twoaday 1 if (nkeys != rcount) {
286     ctx->rset = addrs;
287     rcount = DialogBoxParam (mod_hinst_dll, (LPCTSTR)IDD_ENCRYPT,
288     ctx->main_wnd, encrypt_dlg_proc, (LPARAM)ctx);
289     if (rcount == 0)
290     return gpg_error (GPG_ERR_NO_PUBKEY);
291     nkeys += rcount;
292     }
293    
294     keys = xcalloc (nkeys+1, sizeof *keys);
295     for (n = addrs, i=0; n; n = n->next) {
296     if (n->key != NULL)
297     keys[i++] = n->key;
298     }
299     *r_list = addrs;
300     *r_keys = keys;
301     *r_n = nkeys;
302     return 0;
303     }
304    
305    
306 twoaday 10 /* Encrypt the given message @r_msg with the recipients from the
307     To, Cc, Bcc fields. In case that a recipient was not found
308     use the recipient dialog so the user can select the missing keys. */
309 twoaday 1 static gpgme_error_t
310     encrypt_msg (plugin_ctx_t ctx, char **r_msg)
311     {
312     gpgme_error_t err;
313     gpgme_key_t *keys = NULL;
314     gpgme_ctx_t gctx = NULL;
315     gpgme_data_t in = NULL, out = NULL;
316     recip_list_t list = NULL;
317 twoaday 10 int nkeys=0;
318 twoaday 1 char *msg = *r_msg;
319    
320     assert (ctx);
321    
322 twoaday 10 err = get_keys (ctx, &list, &keys, &nkeys);
323     if (err)
324     return err;
325 twoaday 1
326     err = gpgme_new (&gctx);
327     if (!err)
328 twoaday 12 err = create_in_data (&in, msg, ctx->use_utf8);
329 twoaday 1 if (!err)
330     err = gpgme_data_new (&out);
331     if (!err) {
332     gpgme_set_armor (gctx, 1);
333 twoaday 6 gpgme_set_textmode (gctx, 1);
334 twoaday 10 err = gpgme_op_encrypt (gctx, keys,
335     GPGME_ENCRYPT_ALWAYS_TRUST, in, out);
336 twoaday 1 }
337    
338     gpgme_release (gctx);
339     gpgme_data_release (in);
340     release_recipient (list);
341     free (keys);
342    
343 twoaday 10 if (err)
344 twoaday 1 gpgme_data_release (out);
345 twoaday 10 else
346     map_gpgme_data (out, r_msg);
347     return err;
348 twoaday 1 }
349    
350    
351 twoaday 10 /* Sign the message given in @r_msg with the GPG default key. */
352 twoaday 1 static gpgme_error_t
353     sign_msg (plugin_ctx_t ctx, char **r_msg)
354     {
355     gpgme_error_t err;
356     gpgme_ctx_t gctx;
357     gpgme_data_t in, out;
358     pass_cb_t cb_val;
359    
360     cb_val = new_pass_cb (ctx->main_wnd);
361    
362     err = gpgme_new (&gctx);
363     if (!err)
364     err = gpgme_data_new_from_mem (&in, *r_msg, strlen (*r_msg), 1);
365     if (!err)
366     err = gpgme_data_new (&out);
367     if (!err) {
368     gpgme_set_passphrase_cb (gctx, passphrase_cb, cb_val);
369     err = gpgme_op_sign (gctx, in, out, GPGME_SIG_MODE_CLEAR);
370     }
371    
372     gpgme_release (gctx);
373     gpgme_data_release (in);
374     free_pass_cb (cb_val);
375    
376 twoaday 10 if (err)
377 twoaday 1 gpgme_data_release (out);
378 twoaday 10 else
379     map_gpgme_data (out, r_msg);
380 twoaday 1
381 twoaday 10 return err;
382 twoaday 1 }
383    
384    
385 twoaday 10 /* Sign and encrypt the message @r_msg. */
386 twoaday 1 static gpgme_error_t
387     sign_encrypt_msg (plugin_ctx_t ctx, char **r_msg)
388     {
389     gpgme_ctx_t gctx;
390     gpgme_data_t in, out;
391     gpgme_error_t err;
392     gpgme_key_t *keys;
393     pass_cb_t cb_val;
394     recip_list_t list = NULL;
395     int ec, nkeys = 0;
396    
397     ec = get_keys (ctx, &list, &keys, &nkeys);
398     if (ec)
399     return ec;
400    
401     cb_val = new_pass_cb (ctx->main_wnd);
402    
403     err = gpgme_new (&gctx);
404     if (!err)
405 twoaday 12 err = create_in_data (&in, *r_msg, ctx->use_utf8);
406 twoaday 1 if (!err)
407     err = gpgme_data_new (&out);
408     if (!err) {
409     gpgme_set_armor (gctx, 1);
410 twoaday 6 gpgme_set_textmode (gctx, 1);
411 twoaday 1 gpgme_set_passphrase_cb (gctx, passphrase_cb, cb_val);
412     err = gpgme_op_encrypt_sign (gctx, keys, GPGME_ENCRYPT_ALWAYS_TRUST,
413     in, out);
414     }
415    
416     gpgme_release (gctx);
417     gpgme_data_release (in);
418     release_recipient (list);
419     free (keys);
420     free_pass_cb (cb_val);
421    
422 twoaday 10 if (err)
423 twoaday 1 gpgme_data_release (out);
424 twoaday 10 else
425     map_gpgme_data (out, r_msg);
426     return err;
427 twoaday 1 }
428    
429    
430     static gpgme_error_t
431     verify_msg (plugin_ctx_t ctx, char **r_msg)
432     {
433     gpgme_error_t err;
434     gpgme_ctx_t gctx;
435     gpgme_data_t out = NULL;
436     gpgme_data_t sig = NULL;
437     gpgme_verify_result_t res;
438    
439     err = gpgme_new (&gctx);
440     if (!err)
441     err = gpgme_data_new_from_mem (&sig, *r_msg, strlen (*r_msg), 1);
442     if (!err)
443     err = gpgme_data_new (&out);
444     if (!err)
445     err = gpgme_op_verify (gctx, sig, NULL, out);
446     if (!err) {
447     res = gpgme_op_verify_result (gctx);
448     if (res && res->signatures)
449     DialogBoxParam (mod_hinst_dll, (LPCTSTR)IDD_VERIFY, ctx->main_wnd,
450     verify_dlg_proc, (LPARAM)res->signatures);
451     }
452    
453     gpgme_release (gctx);
454     gpgme_data_release (sig);
455 twoaday 10
456     if (!err)
457     map_gpgme_data (out, r_msg);
458     else
459     gpgme_data_release (out);
460 twoaday 1 return err;
461     }
462    
463    
464     /* Detailed error message when the decryption failed. */
465     static void
466     store_decrypt_info (char *buf, size_t buflen,
467     gpgme_decrypt_result_t res)
468     {
469     gpgme_key_t key = NULL;
470     gpgme_ctx_t ctx;
471     int algo;
472    
473     if (!res->recipients)
474     return;
475    
476     if (!gpgme_new (&ctx)) {
477     gpgme_get_key (ctx, res->recipients->keyid, &key, 0);
478     gpgme_release (ctx);
479     }
480    
481     algo = res->recipients->pubkey_algo;
482     if (!key)
483     _snprintf (buf, buflen, _("encrypted with %s key, ID %s\n"
484 twoaday 12 "decryption failed: secret key not available"),
485 twoaday 1 algo == 1? "RSA" : algo==16? "ELG": "???",
486     res->recipients->keyid+8);
487     else {
488     char *uid = utf8_to_native (key->uids->uid);
489     _snprintf (buf, buflen, _("encrypted with %d-bit %s key, ID %s\n"
490     "\t\"%s\"\n"
491 twoaday 12 "decryption failed: secret key not available"),
492 twoaday 1 key->subkeys->length,
493     algo == 1? "RSA" : algo==16? "ELG": "???",
494     key->subkeys->keyid+8, uid);
495     free_if_alloc (uid);
496     }
497     if (key)
498     gpgme_key_release (key);
499     }
500    
501    
502 twoaday 10 /* Decrypt the message @r_msg. If the type @type is actually a signature,
503     the verify function is called instead of decryption. */
504 twoaday 1 static gpgme_error_t
505     decrypt_msg (plugin_ctx_t ctx, char **r_msg, int type)
506     {
507     gpgme_ctx_t gctx = NULL;
508     gpgme_data_t in = NULL, out = NULL;
509     gpgme_error_t err;
510     gpgme_verify_result_t res;
511     pass_cb_t cb_val;
512     char *msg = *r_msg;
513    
514     if ((type & PGP_SIG) || (type & PGP_CLEARSIG))
515     return verify_msg (ctx, r_msg);
516    
517     cb_val = new_pass_cb (ctx->main_wnd);
518    
519     err = gpgme_new (&gctx);
520     if (!err)
521     err = gpgme_data_new_from_mem (&in, msg, strlen (msg), 1);
522     if (!err)
523     err = gpgme_data_new (&out);
524     if (!err) {
525     gpgme_set_passphrase_cb (gctx, passphrase_cb, cb_val);
526     err = gpgme_op_decrypt_verify (gctx, in, out);
527     res = gpgme_op_verify_result (gctx);
528     if (res && res->signatures)
529     DialogBoxParam (mod_hinst_dll, (LPCTSTR)IDD_VERIFY, ctx->main_wnd,
530     verify_dlg_proc, (LPARAM)res->signatures);
531     }
532     if (err) {
533     gpgme_decrypt_result_t r = gpgme_op_decrypt_result (gctx);
534     store_decrypt_info (ctx->errbuf, sizeof (ctx->errbuf)-1, r);
535     }
536    
537     gpgme_release (gctx);
538     gpgme_data_release (in);
539     free_pass_cb (cb_val);
540    
541 twoaday 10 if (err)
542 twoaday 1 gpgme_data_release (out);
543 twoaday 10 else
544     map_gpgme_data (out, r_msg);
545 twoaday 1
546 twoaday 10 return err;
547 twoaday 1 }
548    
549    
550     /* Return what kind of OpenPGP message @msg is. */
551     static int
552     parse_pgp_id (const char *msg)
553     {
554     int id = 0;
555    
556     if (strstr (msg, "BEGIN PGP MESSAGE"))
557     id |= PGP_MESSAGE;
558     if (strstr (msg, "BEGIN PGP PUBLIC KEY") ||
559     strstr (msg, "BEGIN PGP SECRET KEY") ||
560     strstr (msg, "BEGIN PGP PRIVATE KEY"))
561     id |= PGP_KEY;
562     if (strstr (msg, "BEGIN PGP SIGNED MESSAGE"))
563     id |= PGP_CLEARSIG;
564     return id;
565     }
566    
567    
568 twoaday 12 static int
569     winpt_key_import (void)
570     {
571     HWND winpt;
572    
573     winpt = FindWindow ("WinPT", "WinPT");
574     if (winpt != NULL) {
575     PostMessage (winpt, WM_COMMAND, 40014, 0);
576     return 0;
577     }
578     return -1;
579     }
580    
581    
582     /* Try to decrypt a PGP/MIME message. */
583     static gpgme_error_t
584     oe_handle_pgp_mime_mail (plugin_ctx_t ctx)
585     {
586     SetEvent (plugin_active);
587    
588     /* Select attachment number 0. */
589     AttachThreadInput (GetCurrentThreadId (),
590     GetWindowThreadProcessId (ctx->main_wnd, NULL),
591     TRUE);
592    
593     SetFocus (ctx->addr_wnd);
594     SetFocus (ctx->attach);
595     ListView_SetItemState (ctx->attach, 0, LVIS_SELECTED|LVIS_FOCUSED,
596     LVIS_FOCUSED|LVIS_SELECTED);
597    
598     AttachThreadInput (GetCurrentThreadId (),
599     GetWindowThreadProcessId (ctx->main_wnd, NULL),
600     FALSE);
601    
602     SendMessage (ctx->addr_wnd, WM_COMMAND, ID_OE_SAVE_ATT, 0);
603    
604     ResetEvent (plugin_active);
605    
606     return 0;
607     }
608    
609    
610 twoaday 1 /* This function can be use for all kind of OE messages.
611     It automatically choose the right procedure to handle the data. */
612     gpgme_error_t
613     oe_handle_mail (plugin_ctx_t ctx)
614     {
615 twoaday 3 gpgme_error_t rc = 0;
616 twoaday 10 char *msg;
617 twoaday 1 int msg_type = 0;
618    
619     assert (ctx);
620 twoaday 11
621 twoaday 1 msg = window_get_message (ctx->main_wnd);
622 twoaday 12 #if 0
623     if ((!msg || strlen (msg)) < 2 &&
624     ctx->attach && ListView_GetItemCount (ctx->attach) == 2) {
625     free_if_alloc (msg);
626     return oe_handle_pgp_mime_mail (ctx);
627     }
628     #endif
629 twoaday 1 if (!msg || strlen (msg) == 2) {
630     free_if_alloc (msg);
631     return 0;
632     }
633    
634     if (strstr (msg, "-----BEGIN PGP") &&
635     strstr (msg, "-----END PGP"))
636     msg_type = parse_pgp_id (msg);
637    
638     if (msg_type & PGP_KEY) {
639 twoaday 12 if (winpt_key_import ())
640     MessageBox (ctx->main_wnd,
641 twoaday 1 _("This mail contains one or more public or secret keys.\n\n"
642     "Please save the mail text in a file to use WinPT to import them."),
643     _("GPG Plug-in Info"), MB_ICONINFORMATION|MB_OK);
644     }
645     else if (msg_type) {
646     rc = decrypt_msg (ctx, &msg, msg_type);
647     SendMessage (ctx->msg_wnd, WM_CLEAR, 0, 0);
648     SendMessage (ctx->msg_wnd, WM_UNDO, 0, 0);
649     if (!rc && (msg_type & PGP_MESSAGE) && msg && strlen (msg) > 0) {
650     struct viewer_ctx_s viewer;
651     viewer.msg = msg;
652     DialogBoxParam (mod_hinst_dll, (LPCTSTR)IDD_VIEWER, ctx->main_wnd,
653     viewer_dlg_proc, (LPARAM)&viewer);
654     }
655     }
656 twoaday 11 else if (ctx->sign || ctx->encrypt) {
657 twoaday 1 if (!ctx->to && !ctx->cc && !ctx->bcc) {
658     free_if_alloc (msg);
659     return gpg_error (GPG_ERR_NO_DATA);
660     }
661     if (ctx->sign && ctx->encrypt)
662     rc = sign_encrypt_msg (ctx, &msg);
663     else if (ctx->sign)
664     rc = sign_msg (ctx, &msg);
665     else if (ctx->encrypt)
666     rc = encrypt_msg (ctx, &msg);
667     window_set_message (ctx->main_wnd, msg);
668     }
669    
670     free_if_alloc (msg);
671     return rc;
672     }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26