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

Contents of /trunk/src/OECrypto.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 18 - (show annotations)
Thu Apr 13 07:41:30 2006 UTC (19 years ago) by twoaday
File MIME type: text/plain
File size: 18122 byte(s)
Prepare 0.8.1 release.


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 #include <commctrl.h>
27
28 #include "resource.h"
29 #include "gpgme.h"
30 #include "GPGOE.h"
31
32
33 /* Valid OpenPGP message types. */
34 enum {
35 PGP_MESSAGE = 1,
36 PGP_CLEARSIG = 2,
37 PGP_SIG = 4,
38 PGP_KEY = 8,
39 };
40
41
42 /* Move the keyboard focus to the message window
43 to copy the text to the clipboard. */
44 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 SendMessage (main_hwnd, WM_COMMAND, MAKEWPARAM (ID_OE_SELECTALL, 0), 0);
75 SendMessage (main_hwnd, WM_COMMAND, MAKEWPARAM (ID_OE_COPY, 0), 0);
76
77 /* even so SendMessage() should wait, we wait for safety reasons. */
78 Sleep (200);
79 return get_clip_text (NULL);
80 }
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 SendMessage (main_hwnd, WM_COMMAND, MAKEWPARAM (ID_OE_SELECTALL, 0), 0);
93 SendMessage (main_hwnd, WM_COMMAND, MAKEWPARAM (ID_OE_PASTE, 0), 0);
94 }
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 /* Fill in the key part of the recipient list @addrs if possible. */
194 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 /* do not add invalid keys. */
221 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 /* 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_if_alloc (enc_buf);
246 return err;
247 }
248
249
250 /* 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, gpgme_key_t **r_keys)
269 {
270 gpgme_key_t *keys;
271 recip_list_t addrs = NULL, n;
272 int rcount = 0, nkeys = 0;
273 int i;
274
275 if (ctx->to && strlen (ctx->to) >= 3)
276 rcount += parse_recipients (ctx, &addrs, ctx->to);
277 if (ctx->cc && strlen (ctx->cc) >= 3)
278 rcount += parse_recipients (ctx, &addrs, ctx->cc);
279 if (ctx->bcc && strlen (ctx->bcc) >= 3)
280 rcount += parse_recipients (ctx, &addrs, ctx->bcc);
281
282 nkeys = get_keys_from_addresses (addrs);
283 /* at least some keys were not found, so we offer to select them. */
284 if (nkeys != rcount) {
285 ctx->rset = addrs;
286 rcount = DialogBoxParam (mod_hinst_dll, (LPCTSTR)IDD_ENCRYPT,
287 ctx->main_wnd, encrypt_dlg_proc, (LPARAM)ctx);
288 if (rcount == 0)
289 return gpg_error (GPG_ERR_NO_PUBKEY);
290 nkeys += rcount;
291 }
292
293 keys = xcalloc (nkeys+1, sizeof *keys);
294 for (n = addrs, i=0; n; n = n->next) {
295 if (n->key != NULL)
296 keys[i++] = n->key;
297 }
298 *r_list = addrs;
299 *r_keys = keys;
300 return 0;
301 }
302
303
304 /* Encrypt the given message @r_msg with the recipients from the
305 To, Cc, Bcc fields. In case that a recipient was not found
306 use the recipient dialog so the user can select the missing keys. */
307 static gpgme_error_t
308 encrypt_msg (plugin_ctx_t ctx, char **r_msg)
309 {
310 gpgme_error_t err;
311 gpgme_key_t *keys = NULL;
312 gpgme_ctx_t gctx = NULL;
313 gpgme_data_t in = NULL, out = NULL;
314 recip_list_t list = NULL;
315 char *msg = *r_msg;
316
317 assert (ctx);
318
319 err = get_keys (ctx, &list, &keys);
320 if (err)
321 return err;
322
323 err = gpgme_new (&gctx);
324 if (!err)
325 err = create_in_data (&in, msg, ctx->use_utf8);
326 if (!err)
327 err = gpgme_data_new (&out);
328 if (!err) {
329 gpgme_set_armor (gctx, 1);
330 gpgme_set_textmode (gctx, 1);
331 err = gpgme_op_encrypt (gctx, keys,
332 GPGME_ENCRYPT_ALWAYS_TRUST, in, out);
333 }
334
335 gpgme_release (gctx);
336 gpgme_data_release (in);
337 release_recipient (list);
338 free_if_alloc (keys);
339
340 if (err)
341 gpgme_data_release (out);
342 else
343 map_gpgme_data (out, r_msg);
344 return err;
345 }
346
347
348 /* Sign the message given in @r_msg with the GPG default key. */
349 static gpgme_error_t
350 sign_msg (plugin_ctx_t ctx, char **r_msg)
351 {
352 gpgme_error_t err;
353 gpgme_ctx_t gctx;
354 gpgme_data_t in, out;
355 pass_cb_t cb_val;
356 int cancel;
357
358 cb_val = new_pass_cb (ctx->main_wnd);
359
360 err = gpgme_new (&gctx);
361 if (!err)
362 err = gpgme_data_new_from_mem (&in, *r_msg, strlen (*r_msg), 1);
363 if (!err)
364 err = gpgme_data_new (&out);
365 if (!err) {
366 gpgme_set_passphrase_cb (gctx, passphrase_cb, cb_val);
367 err = gpgme_op_sign (gctx, in, out, GPGME_SIG_MODE_CLEAR);
368 }
369
370 cancel = pass_cb_cancelled (cb_val);
371
372 gpgme_release (gctx);
373 gpgme_data_release (in);
374 free_pass_cb (cb_val);
375
376 if (err || cancel)
377 gpgme_data_release (out);
378 else
379 map_gpgme_data (out, r_msg);
380
381 return cancel? 0 : err;
382 }
383
384
385 /* Sign and encrypt the message @r_msg. */
386 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 cancel;
396
397 err = get_keys (ctx, &list, &keys);
398 if (err)
399 return err;
400
401 cb_val = new_pass_cb (ctx->main_wnd);
402
403 err = gpgme_new (&gctx);
404 if (!err)
405 err = create_in_data (&in, *r_msg, ctx->use_utf8);
406 if (!err)
407 err = gpgme_data_new (&out);
408 if (!err) {
409 gpgme_set_armor (gctx, 1);
410 gpgme_set_textmode (gctx, 1);
411 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 cancel = pass_cb_cancelled (cb_val);
417
418 gpgme_release (gctx);
419 gpgme_data_release (in);
420 release_recipient (list);
421 free_if_alloc (keys);
422 free_pass_cb (cb_val);
423
424 if (err || cancel)
425 gpgme_data_release (out);
426 else
427 map_gpgme_data (out, r_msg);
428 return cancel? 0 : err;
429 }
430
431
432 static gpgme_error_t
433 verify_msg (plugin_ctx_t ctx, char **r_msg)
434 {
435 gpgme_error_t err;
436 gpgme_ctx_t gctx;
437 gpgme_data_t out = NULL;
438 gpgme_data_t sig = NULL;
439 gpgme_verify_result_t res;
440
441 err = gpgme_new (&gctx);
442 if (!err)
443 err = gpgme_data_new_from_mem (&sig, *r_msg, strlen (*r_msg), 1);
444 if (!err)
445 err = gpgme_data_new (&out);
446 if (!err)
447 err = gpgme_op_verify (gctx, sig, NULL, out);
448 if (!err) {
449 res = gpgme_op_verify_result (gctx);
450 if (res && res->signatures)
451 DialogBoxParam (mod_hinst_dll, (LPCTSTR)IDD_VERIFY, ctx->main_wnd,
452 verify_dlg_proc, (LPARAM)res->signatures);
453 }
454
455 gpgme_release (gctx);
456 gpgme_data_release (sig);
457
458 if (!err)
459 map_gpgme_data (out, r_msg);
460 else
461 gpgme_data_release (out);
462 return err;
463 }
464
465
466 /* Detailed error message when the decryption failed. */
467 static void
468 store_decrypt_info (char *buf, size_t buflen,
469 gpgme_decrypt_result_t res)
470 {
471 gpgme_key_t key = NULL;
472 gpgme_ctx_t ctx;
473 int algo;
474
475 if (!res->recipients)
476 return;
477
478 if (!gpgme_new (&ctx)) {
479 gpgme_get_key (ctx, res->recipients->keyid, &key, 0);
480 gpgme_release (ctx);
481 }
482
483 algo = res->recipients->pubkey_algo;
484 if (!key)
485 _snprintf (buf, buflen, _("encrypted with %s key, ID %s\n"
486 "decryption failed: secret key not available"),
487 algo == 1? "RSA" : algo==16? "ELG": "???",
488 res->recipients->keyid+8);
489 else {
490 char *uid = utf8_to_native (key->uids->uid);
491 _snprintf (buf, buflen, _("encrypted with %d-bit %s key, ID %s\n"
492 "\t\"%s\"\n"
493 "decryption failed: secret key not available"),
494 key->subkeys->length,
495 algo == 1? "RSA" : algo==16? "ELG": "???",
496 key->subkeys->keyid+8, uid);
497 free_if_alloc (uid);
498 }
499 if (key)
500 gpgme_key_release (key);
501 }
502
503
504 /* Decrypt the message given in @r_msg.
505 The old message will be freed and replaced with the plaintext. */
506 gpgme_error_t
507 oe_decrypt_msg (HWND main_wnd, char **r_msg)
508 {
509 gpgme_ctx_t gctx = NULL;
510 gpgme_data_t in = NULL, out = NULL;
511 gpgme_error_t err;
512 pass_cb_t cb_val;
513 int cancel;
514 char *msg = *r_msg;
515
516 cb_val = new_pass_cb (main_wnd);
517 err = gpgme_new (&gctx);
518 if (!err)
519 err = gpgme_data_new_from_mem (&in, msg, strlen (msg), 1);
520 if (!err)
521 err = gpgme_data_new (&out);
522 if (!err) {
523 gpgme_set_passphrase_cb (gctx, passphrase_cb, cb_val);
524 err = gpgme_op_decrypt (gctx, in, out);
525 }
526
527 cancel = pass_cb_cancelled (cb_val);
528
529 gpgme_release (gctx);
530 gpgme_data_release (in);
531 free_pass_cb (cb_val);
532
533 if (err || cancel)
534 gpgme_data_release (out);
535 else
536 map_gpgme_data (out, r_msg);
537
538 return cancel? 0 : err;
539 }
540
541
542 /* Decrypt the message @r_msg. If the type @type is actually a signature,
543 the verify function is called instead of decryption. */
544 static gpgme_error_t
545 decrypt_msg (plugin_ctx_t ctx, char **r_msg, int type, int *r_cancel)
546 {
547 gpgme_ctx_t gctx = NULL;
548 gpgme_data_t in = NULL, out = NULL;
549 gpgme_error_t err;
550 gpgme_verify_result_t res;
551 pass_cb_t cb_val;
552 char *msg = *r_msg;
553
554 if ((type & PGP_SIG) || (type & PGP_CLEARSIG))
555 return verify_msg (ctx, r_msg);
556
557 cb_val = new_pass_cb (ctx->main_wnd);
558
559 err = gpgme_new (&gctx);
560 if (!err)
561 err = gpgme_data_new_from_mem (&in, msg, strlen (msg), 1);
562 if (!err)
563 err = gpgme_data_new (&out);
564 if (!err) {
565 gpgme_set_passphrase_cb (gctx, passphrase_cb, cb_val);
566 err = gpgme_op_decrypt_verify (gctx, in, out);
567 res = gpgme_op_verify_result (gctx);
568 if (res && res->signatures)
569 DialogBoxParam (mod_hinst_dll, (LPCTSTR)IDD_VERIFY, ctx->main_wnd,
570 verify_dlg_proc, (LPARAM)res->signatures);
571 }
572 *r_cancel = pass_cb_cancelled (cb_val);
573
574 if (!*r_cancel && err) {
575 gpgme_decrypt_result_t r = gpgme_op_decrypt_result (gctx);
576 store_decrypt_info (ctx->errbuf, sizeof (ctx->errbuf)-1, r);
577 }
578
579 gpgme_release (gctx);
580 gpgme_data_release (in);
581 free_pass_cb (cb_val);
582
583 if (err || *r_cancel)
584 gpgme_data_release (out);
585 else
586 map_gpgme_data (out, r_msg);
587
588 return *r_cancel? 0 : err;
589 }
590
591
592 /* Return what kind of OpenPGP message @msg is. */
593 static int
594 parse_pgp_id (const char *msg)
595 {
596 int id = 0;
597
598 if (strstr (msg, "BEGIN PGP MESSAGE"))
599 id |= PGP_MESSAGE;
600 if (strstr (msg, "BEGIN PGP PUBLIC KEY") ||
601 strstr (msg, "BEGIN PGP SECRET KEY") ||
602 strstr (msg, "BEGIN PGP PRIVATE KEY"))
603 id |= PGP_KEY;
604 if (strstr (msg, "BEGIN PGP SIGNED MESSAGE"))
605 id |= PGP_CLEARSIG;
606 return id;
607 }
608
609
610 static int
611 winpt_key_import (void)
612 {
613 HWND winpt;
614
615 winpt = FindWindow ("WinPT", "WinPT");
616 if (winpt != NULL) {
617 PostMessage (winpt, WM_COMMAND, 40014, 0);
618 return 0;
619 }
620 return -1;
621 }
622
623
624 /* Try to decrypt a PGP/MIME message. */
625 static gpgme_error_t
626 oe_handle_pgp_mime_mail (plugin_ctx_t ctx)
627 {
628 SetEvent (plugin_active);
629
630 /* Select attachment number 0. */
631 AttachThreadInput (GetCurrentThreadId (),
632 GetWindowThreadProcessId (ctx->main_wnd, NULL),
633 TRUE);
634
635 SetFocus (ctx->addr_wnd);
636 SetFocus (ctx->attach);
637 ListView_SetItemState (ctx->attach, 0, LVIS_SELECTED|LVIS_FOCUSED,
638 LVIS_FOCUSED|LVIS_SELECTED);
639
640 AttachThreadInput (GetCurrentThreadId (),
641 GetWindowThreadProcessId (ctx->main_wnd, NULL),
642 FALSE);
643
644 SendMessage (ctx->addr_wnd, WM_COMMAND, ID_OE_SAVE_ATT, 0);
645
646 ResetEvent (plugin_active);
647
648 return 0;
649 }
650
651
652 /* This function can be use for all kind of OE messages.
653 It automatically choose the right procedure to handle the data. */
654 gpgme_error_t
655 oe_handle_mail (plugin_ctx_t ctx)
656 {
657 gpgme_error_t rc = 0;
658 char *msg;
659 int msg_type = 0;
660 int cancel;
661
662 assert (ctx);
663
664 msg = window_get_message (ctx->main_wnd);
665 #if 0
666 if ((!msg || strlen (msg)) < 2 &&
667 ctx->attach && ListView_GetItemCount (ctx->attach) == 2) {
668 free_if_alloc (msg);
669 return oe_handle_pgp_mime_mail (ctx);
670 }
671 #endif
672 if (!msg || strlen (msg) == 2) {
673 free_if_alloc (msg);
674 return 0;
675 }
676
677 if (strstr (msg, "-----BEGIN PGP") &&
678 strstr (msg, "-----END PGP"))
679 msg_type = parse_pgp_id (msg);
680
681 if (msg_type & PGP_KEY) {
682 if (winpt_key_import ())
683 MessageBox (ctx->main_wnd,
684 _("This mail contains one or more public or secret keys.\n\n"
685 "Please save the mail text in a file to use WinPT to import them."),
686 _("GPG Plug-in Info"), MB_ICONINFORMATION|MB_OK);
687 }
688 else if (msg_type) {
689 rc = decrypt_msg (ctx, &msg, msg_type, &cancel);
690 SendMessage (ctx->msg_wnd, WM_CLEAR, 0, 0);
691 SendMessage (ctx->msg_wnd, WM_UNDO, 0, 0);
692 if (!cancel && !rc && (msg_type & PGP_MESSAGE)
693 && msg && strlen (msg) > 0) {
694 struct viewer_ctx_s viewer;
695 viewer.msg = msg;
696 viewer.main_wnd = ctx->main_wnd;
697 DialogBoxParam (mod_hinst_dll, (LPCTSTR)IDD_VIEWER, ctx->main_wnd,
698 viewer_dlg_proc, (LPARAM)&viewer);
699 }
700 }
701 else if (ctx->sign || ctx->encrypt) {
702 if (!ctx->to && !ctx->cc && !ctx->bcc) {
703 free_if_alloc (msg);
704 return gpg_error (GPG_ERR_NO_DATA);
705 }
706 if (ctx->sign && ctx->encrypt)
707 rc = sign_encrypt_msg (ctx, &msg);
708 else if (ctx->sign)
709 rc = sign_msg (ctx, &msg);
710 else if (ctx->encrypt)
711 rc = encrypt_msg (ctx, &msg);
712 window_set_message (ctx->main_wnd, msg);
713 }
714
715 free_if_alloc (msg);
716 return rc;
717 }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26