root/common/trunk/src/mbus.c @ 156

Revision 156, 35.0 KB (checked in by ucaccsp, 15 years ago)

Some platforms don't have vsnprintf(), use vsprintf() as a replacement and
don't worry about buffer overflows.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1/*
2 * FILE:    mbus.c
3 * AUTHORS: Colin Perkins
4 *
5 * Copyright (c) 1997-99 University College London
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, is permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 * 3. All advertising materials mentioning features or use of this software
17 *    must display the following acknowledgement:
18 *      This product includes software developed by the Computer Science
19 *      Department at University College London
20 * 4. Neither the name of the University nor of the Department may be used
21 *    to endorse or promote products derived from this software without
22 *    specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 */
35
36#define MBUS_ENCRYPT_BY_DEFAULT
37
38#include "config_unix.h"
39#include "config_win32.h"
40#include "debug.h"
41#include "memory.h"
42#include "net_udp.h"
43#include "hmac.h"
44#include "base64.h"
45#include "crypt_random.h"
46#include "qfDES.h"
47#include "mbus.h"
48
49#define MBUS_BUF_SIZE     1500
50#define MBUS_ACK_BUF_SIZE 1500
51#define MBUS_MAX_ADDR       10
52#define MBUS_MAX_PD         10
53#define MBUS_MAX_QLEN       50 /* Number of messages we can queue with mbus_qmsg() */
54
55#ifdef NEED_VSNPRINTF
56int vsnprintf(char *s, int buf_size, const char *format, va_list ap)
57{
58        /* Quick hack replacement for vsnprintf... note that this */
59        /* doesm't check for buffer overflows, and so it open to  */
60        /* many really nasty attacks!                             */
61        return vsprintf(s,format,ap);
62}
63#endif
64
65struct mbus_key{
66        char    *algorithm;
67        char    *key;
68        int      key_len;
69};
70
71struct mbus_msg {
72        struct mbus_msg *next;
73        struct timeval   time;  /* Time the message was sent, to trigger a retransmit */
74        struct timeval   ts;    /* Time the message was composed, the timestamp in the packet header */
75        char            *dest;
76        int              reliable;
77        int              complete;      /* Indicates that we've finished adding cmds to this message */
78        int              seqnum;
79        int              retransmit_count;
80        int              message_size;
81        int              num_cmds;
82        char            *cmd_list[MBUS_MAX_QLEN];
83        char            *arg_list[MBUS_MAX_QLEN];
84};
85
86struct mbus {
87        socket_udp       *s;
88        int               num_addr;
89        char             *addr[MBUS_MAX_ADDR];  /* Addresses we respond to.                     */
90        int               max_other_addr;
91        int               num_other_addr;
92        char            **other_addr;           /* Addresses of other entities on the mbus.     */
93        char             *parse_buffer[MBUS_MAX_PD];
94        int               parse_depth;
95        int               seqnum;
96        void (*cmd_handler)(char *src, char *cmd, char *arg, void *dat);
97        void (*err_handler)(int seqnum, int reason);
98        struct mbus_msg  *cmd_queue;            /* Queue of messages waiting to be sent */
99        struct mbus_msg  *waiting_ack;          /* The last reliable message sent, if we have not yet got the ACK */
100        char             *hashkey;
101        int               hashkeylen;
102        char             *encrkey;
103        int               encrkeylen;
104#ifdef WIN32
105        HKEY              cfgKey;
106#else
107        fd_t              cfgfd;                /* The file descriptor for the $HOME/.mbus config file, on Unix */
108#endif
109        int               cfg_locked;
110        struct timeval    last_heartbeat;       /* Last time we sent a heartbeat message */
111};
112
113#define SECS_PER_WEEK    604800
114#define MBUS_ENCRKEY_LEN      7
115#define MBUS_HASHKEY_LEN     12
116
117static char *mbus_new_encrkey(void)
118{
119        char            *key;   /* The key we are going to return... */
120#ifdef MBUS_ENCRYPT_BY_DEFAULT
121        /* Create a new key, for use by the hashing routines. Returns */
122        /* a key of the form (DES,MTIzMTU2MTg5MTEyMQ==)               */
123        char             random_string[MBUS_ENCRKEY_LEN];
124        char             encoded_string[(MBUS_ENCRKEY_LEN*4/3)+4];
125        int              encoded_length;
126        int              i;
127
128        /* Step 1: generate a random string for the key... */
129        for (i = 0; i < MBUS_ENCRKEY_LEN; i++) {
130                random_string[i] = ((int32)lbl_random() | 0x000ff000) >> 24;
131        }
132        /* Step 2: base64 encode that string... */
133        memset(encoded_string, 0, (MBUS_ENCRKEY_LEN*4/3)+4);
134        encoded_length = base64encode(random_string, MBUS_ENCRKEY_LEN, encoded_string, (MBUS_ENCRKEY_LEN*4/3)+4);
135
136        /* Step 3: put it all together to produce the key... */
137        key = (char *) xmalloc(encoded_length + 18);
138        sprintf(key, "(DES,%s)", encoded_string);
139#else
140        key = (char *) xmalloc(9);
141        sprintf(key, "(NOENCR)");
142#endif
143        return key;
144}
145
146static char *mbus_new_hashkey(void)
147{
148        /* Create a new key, for use by the hashing routines. Returns  */
149        /* a key of the form (HMAC-MD5,MTIzMTU2MTg5MTEyMQ==)           */
150        char             random_string[MBUS_HASHKEY_LEN];
151        char             encoded_string[(MBUS_HASHKEY_LEN*4/3)+4];
152        int              encoded_length;
153        int              i;
154        char            *key;
155
156        /* Step 1: generate a random string for the key... */
157        for (i = 0; i < MBUS_HASHKEY_LEN; i++) {
158                random_string[i] = ((int32)lbl_random() | 0x000ff000) >> 24;
159        }
160        /* Step 2: base64 encode that string... */
161        memset(encoded_string, 0, (MBUS_HASHKEY_LEN*4/3)+4);
162        encoded_length = base64encode(random_string, MBUS_HASHKEY_LEN, encoded_string, (MBUS_HASHKEY_LEN*4/3)+4);
163
164        /* Step 3: put it all together to produce the key... */
165        key = (char *) xmalloc(encoded_length + 23);
166        sprintf(key, "(HMAC-MD5,%s)", encoded_string);
167
168        return key;
169}
170
171static void mbus_lock_config_file(struct mbus *m)
172{
173#ifdef WIN32
174        /* Open the registry and create the mbus entries if they don't exist   */
175        /* already. The default contents of the registry are random encryption */
176        /* and authentication keys, and node local scope.                      */
177        HKEY                    key    = HKEY_CURRENT_USER;
178        LPCTSTR                 subKey = "Software\\Mbone Applications\\mbus";
179        DWORD                   disp;
180        char                    buffer[MBUS_BUF_SIZE];
181        LONG                    status;
182
183        status = RegCreateKeyEx(key, subKey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &(m->cfgKey), &disp);
184        if (status != ERROR_SUCCESS) {
185                FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, status, 0, buffer, MBUS_BUF_SIZE, NULL);
186                debug_msg("Unable to open registry: %s\n", buffer);
187                abort();
188        }
189        if (disp == REG_CREATED_NEW_KEY) {
190                char    *hashkey = mbus_new_hashkey();
191                char    *encrkey = mbus_new_encrkey();
192                char    *scope   = "HOSTLOCAL";
193
194                status = RegSetValueEx(m->cfgKey, "HASHKEY", 0, REG_SZ, hashkey, strlen(hashkey) + 1);
195                if (status != ERROR_SUCCESS) {
196                        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, status, 0, buffer, MBUS_BUF_SIZE, NULL);
197                        debug_msg("Unable to set hashkey: %s\n", buffer);
198                        abort();
199                }       
200                status = RegSetValueEx(m->cfgKey, "ENCRYPTIONKEY", 0, REG_SZ, encrkey, strlen(encrkey) + 1);
201                if (status != ERROR_SUCCESS) {
202                        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, status, 0, buffer, MBUS_BUF_SIZE, NULL);
203                        debug_msg("Unable to set encrkey: %s\n", buffer);
204                        abort();
205                }       
206                status = RegSetValueEx(m->cfgKey, "SCOPE", 0, REG_SZ, scope, strlen(scope) + 1);
207                if (status != ERROR_SUCCESS) {
208                        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, status, 0, buffer, MBUS_BUF_SIZE, NULL);
209                        debug_msg("Unable to set scope: %s\n", buffer);
210                        abort();
211                }       
212                debug_msg("Created new registry entry...\n");
213        } else {
214                debug_msg("Opened existing registry entry...\n");
215        }
216#else
217        /* Obtain a valid lock on the mbus configuration file. This function */
218        /* creates the file, if one does not exist. The default contents of  */
219        /* this file are random authentication and encryption keys, and node */
220        /* local scope.                                                      */
221        struct flock     l;
222        struct stat      s;
223        struct passwd   *p;     
224        char            *buf;
225        char            *cfg_file;
226
227        /* The getpwuid() stuff is to determine the users home directory, into which we */
228        /* write a .mbus config file. The struct returned by getpwuid() is statically   */
229        /* allocated, so it's not necessary to free it afterwards.                      */
230        p = getpwuid(getuid());
231        if (p == NULL) {
232                perror("Unable to get passwd entry");
233                abort();
234        }
235        cfg_file = (char *) xmalloc(strlen(p->pw_dir) + 6);
236        sprintf(cfg_file, "%s/.mbus", p->pw_dir);
237        m->cfgfd = open(cfg_file, O_RDWR | O_CREAT, 0600);
238        if (m->cfgfd == -1) {
239                perror("Unable to open mbus configuration file");
240                abort();
241        }
242        xfree(cfg_file);
243
244        /* We attempt to get a lock on the config file, blocking until  */
245        /* the lock can be obtained. The only time this should block is */
246        /* when another instance of this code has a write lock on the   */
247        /* file, because the contents are being updated.                */
248        l.l_type   = F_WRLCK;
249        l.l_start  = 0;
250        l.l_whence = SEEK_SET;
251        l.l_len    = 0;
252        if (fcntl(m->cfgfd, F_SETLKW, &l) == -1) {
253                perror("Unable to lock mbus configuration file");
254                abort();
255        }
256
257        if (fstat(m->cfgfd, &s) != 0) {
258                perror("Unable to stat config file\n");
259                abort();
260        }
261        if (s.st_size == 0) {
262                /* Empty file, create with sensible defaults... */
263                char    *hashkey = mbus_new_hashkey();
264                char    *encrkey = mbus_new_encrkey();
265                char    *scope   = "HOSTLOCAL";
266                int      len;
267
268                len = strlen(hashkey) + strlen(encrkey) + strlen(scope) + 39;
269                buf = (char *) xmalloc(len);
270                sprintf(buf, "[MBUS]\nHASHKEY=%s\nENCRYPTIONKEY=%s\nSCOPE=%s\n", hashkey, encrkey, scope);
271                write(m->cfgfd, buf, strlen(buf));
272                xfree(buf);
273                free(hashkey);
274                xfree(encrkey);
275                debug_msg("Wrote config file\n");
276        } else {
277                /* Read in the contents of the config file... */
278                buf = (char *) xmalloc(s.st_size+1);
279                memset(buf, '\0', s.st_size+1);
280                if (read(m->cfgfd, buf, s.st_size) != s.st_size) {
281                        perror("Unable to read config file\n");
282                        abort();
283                }
284                /* Check that the file contains sensible information...   */
285                /* This is rather a pathetic check, but it'll do for now! */
286                if (strncmp(buf, "[MBUS]", 6) != 0) {
287                        debug_msg("Mbus config file has incorrect header\n");
288                        abort();
289                }
290                xfree(buf);
291        }
292#endif
293        m->cfg_locked = TRUE;
294}
295
296static void mbus_unlock_config_file(struct mbus *m)
297{
298#ifdef WIN32
299        LONG status;
300        char buffer[MBUS_BUF_SIZE];
301       
302        status = RegCloseKey(m->cfgKey);
303        if (status != ERROR_SUCCESS) {
304                FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, status, 0, buffer, MBUS_BUF_SIZE, NULL);
305                debug_msg("Unable to close registry: %s\n", buffer);
306                abort();
307        }
308        debug_msg("Closed registry entry...\n");
309#else
310        struct flock    l;
311
312        l.l_type   = F_UNLCK;
313        l.l_start  = 0;
314        l.l_whence = SEEK_SET;
315        l.l_len    = 0;
316        if (fcntl(m->cfgfd, F_SETLKW, &l) == -1) {
317                perror("Unable to unlock mbus configuration file");
318                abort();
319        }
320        close(m->cfgfd);
321        m->cfgfd = -1;
322#endif
323        m->cfg_locked = FALSE;
324}
325
326#ifndef WIN32
327static void mbus_get_key(struct mbus *m, struct mbus_key *key, char *id)
328{
329        struct stat      s;
330        char            *buf;
331        char            *line;
332        char            *tmp;
333        int              pos;
334
335        assert(m->cfg_locked);
336
337        if (lseek(m->cfgfd, 0, SEEK_SET) == -1) {
338                perror("Can't seek to start of config file");
339                abort();
340        }
341        if (fstat(m->cfgfd, &s) != 0) {
342                perror("Unable to stat config file\n");
343                abort();
344        }
345        /* Read in the contents of the config file... */
346        buf = (char *) xmalloc(s.st_size+1);
347        memset(buf, '\0', s.st_size+1);
348        if (read(m->cfgfd, buf, s.st_size) != s.st_size) {
349                perror("Unable to read config file\n");
350                abort();
351        }
352       
353        line = (char *) xmalloc(s.st_size+1);
354        sscanf(buf, "%s", line);
355        if (strcmp(line, "[MBUS]") != 0) {
356                debug_msg("Invalid .mbus file\n");
357                abort();
358        }
359        pos = strlen(line) + 1;
360        while (pos < s.st_size) {
361                sscanf(buf+pos, "%s", line);
362                pos += strlen(line) + 1;
363                if (strncmp(line, id, strlen(id)) == 0) {
364                        key->algorithm   = (char *) strdup(strtok(line+strlen(id), ",)"));
365                        if (strcmp(key->algorithm, "NOENCR") != 0) {
366                                key->key     = (char *) strtok(NULL  , ")");
367                                key->key_len = strlen(key->key);
368                                tmp = (char *) xmalloc(key->key_len);
369                                key->key_len = base64decode(key->key, key->key_len, tmp, key->key_len);
370                                key->key = tmp;
371                        } else {
372                                key->key     = NULL;
373                                key->key_len = 0;
374                        }
375                        xfree(buf);
376                        xfree(line);
377                        return;
378                }
379        }
380        debug_msg("Unable to read hashkey from config file\n");
381        xfree(buf);
382        xfree(line);
383}
384#endif
385
386static void mbus_get_encrkey(struct mbus *m, struct mbus_key *key)
387{
388        /* This MUST be called while the config file is locked! */
389        unsigned char   *des_key;
390        int              i, j, k;
391#ifdef WIN32
392        long             status;
393        DWORD            type;
394        char            *buffer;
395        int              buflen = MBUS_BUF_SIZE;
396        char            *tmp;
397
398        assert(m->cfg_locked);
399       
400        /* Read the key from the registry... */
401        buffer = (char *) xmalloc(MBUS_BUF_SIZE);
402        status = RegQueryValueEx(m->cfgKey, "ENCRYPTIONKEY", 0, &type, buffer, &buflen);
403        if (status != ERROR_SUCCESS) {
404                FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, status, 0, buffer, MBUS_BUF_SIZE, NULL);
405                debug_msg("Unable to get encrkey: %s\n", buffer);
406                abort();
407        }
408        assert(type == REG_SZ);
409        assert(buflen > 0);
410
411        /* Parse the key... */
412        key->algorithm   = strdup(strtok(buffer+1, ",)"));
413        if (strcmp(key->algorithm, "NOENCR") != 0) {
414                key->key     = (char *) strtok(NULL  , ")");
415                key->key_len = strlen(key->key);
416                tmp = (char *) xmalloc(key->key_len);
417                key->key_len = base64decode(key->key, key->key_len, tmp, key->key_len);
418                key->key = tmp;
419        } else {
420                key->key     = NULL;
421                key->key_len = 0;
422        }
423
424        debug_msg("alg=%s key=%s keylen=%d\n", key->algorithm, key->key, key->key_len);
425
426        xfree(buffer);
427#else
428        mbus_get_key(m, key, "ENCRYPTIONKEY=(");
429#endif
430        if (strcmp(key->algorithm, "DES") == 0) {
431                assert(key->key != NULL);
432                assert(key->key_len == 7);
433                /* Take the first 56-bits of the input key and spread it across   */
434                /* the 64-bit DES key space inserting a bit-space of garbage      */
435                /* (for parity) every 7 bits. The garbage will be taken care of   */
436                /* below. The library we're using expects the key and parity bits */
437                /* in the following MSB order: K0 K1...K6 P0 K8 K9...K14 P1...    */
438                des_key = (unsigned char *) xmalloc(8);
439                des_key[0] = key->key[0];
440                des_key[1] = key->key[0] << 7 | key->key[1] >> 1;
441                des_key[2] = key->key[1] << 6 | key->key[2] >> 2;
442                des_key[3] = key->key[2] << 5 | key->key[3] >> 3;
443                des_key[4] = key->key[3] << 4 | key->key[4] >> 4;
444                des_key[5] = key->key[4] << 3 | key->key[5] >> 5;
445                des_key[6] = key->key[5] << 2 | key->key[6] >> 6;
446                des_key[7] = key->key[6] << 1;
447
448                /* fill in parity bits to make DES library happy */
449                for (i = 0; i < 8; ++i)
450                {
451                        k = des_key[i] & 0xfe;
452                        j = k;
453                        j ^= j >> 4;
454                        j ^= j >> 2;
455                        j ^= j >> 1;
456                        j = (j & 1) ^ 1;
457                        des_key[i] = k | j;
458                }
459                xfree(key->key);
460                key->key     = des_key;
461                key->key_len = 8;
462        }
463}
464
465static void mbus_get_hashkey(struct mbus *m, struct mbus_key *key)
466{
467        /* This MUST be called while the config file is locked! */
468#ifdef WIN32
469        long     status;
470        DWORD    type;
471        char    *buffer;
472        int      buflen = MBUS_BUF_SIZE;
473        char    *tmp;
474
475        assert(m->cfg_locked);
476       
477        /* Read the key from the registry... */
478        buffer = (char *) xmalloc(MBUS_BUF_SIZE);
479        status = RegQueryValueEx(m->cfgKey, "HASHKEY", 0, &type, buffer, &buflen);
480        if (status != ERROR_SUCCESS) {
481                FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, status, 0, buffer, MBUS_BUF_SIZE, NULL);
482                debug_msg("Unable to get encrkey: %s\n", buffer);
483                abort();
484        }
485        assert(type == REG_SZ);
486        assert(buflen > 0);
487
488        /* Parse the key... */
489        key->algorithm   = strdup(strtok(buffer+1, ","));
490        key->key         = strtok(NULL  , ")");
491        key->key_len     = strlen(key->key);
492
493        debug_msg("alg=%s key=%s keylen=%d\n", key->algorithm, key->key, key->key_len);
494
495        /* Decode the key... */
496        tmp = (char *) xmalloc(key->key_len);
497        key->key_len = base64decode(key->key, key->key_len, tmp, key->key_len);
498        key->key = tmp;
499
500        xfree(buffer);
501#else
502        mbus_get_key(m, key, "HASHKEY=(");
503#endif
504}
505
506static int mbus_addr_match(char *a, char *b)
507{
508        /* Compare the addresses "a" and "b". These may optionally be */
509        /* surrounded by "(" and ")" and may have an arbitrary amount */
510        /* of white space between components of the addresses. There  */
511        /* is a match if every word of address b is in address a.     */
512        /* NOTE: The strings passed to this function are stored for   */
513        /* later use and MUST NOT be modified by this routine.        */
514        char    *y, c;
515
516        assert(a != NULL);
517        assert(b != NULL);
518
519        /* Skip leading whitespace and '('... */
520        while (isspace((unsigned char)*a) || (*a == '(')) a++;
521        while (isspace((unsigned char)*b) || (*b == '(')) b++;
522
523        while ((*b != '\0') && (*b != ')')) {
524                while (isspace((unsigned char)*b)) b++;
525                for (y = b; ((*y != ' ') && (*y != ')') && (*y != '\0')); y++) {
526                        /* do nothing */
527                }
528                c = *y;
529                *y = '\0';
530                if (strstr(a, b) == NULL) {
531                        /* ...this word not found */
532                        *y = c;
533                        return FALSE;
534                }
535                *y = c;
536                b = y;
537        }               
538        return TRUE;
539}
540
541static void store_other_addr(struct mbus *m, char *a)
542{
543        /* This takes the address a and ensures it is stored in the   */
544        /* m->other_addr field of the mbus structure. The other_addr  */
545        /* field should probably be a hash table, but for now we hope */
546        /* that there are not too many entities on the mbus, so the   */
547        /* list is small.                                             */
548        int     i;
549
550        for (i = 0; i < m->num_other_addr; i++) {
551                if (mbus_addr_match(m->other_addr[i], a)) {
552                        /* Already in the list... */
553                        return;
554                }
555        }
556
557        if (m->num_other_addr == m->max_other_addr) {
558                /* Expand the list... */
559                m->max_other_addr *= 2;
560                m->other_addr = (char **) xrealloc(m->other_addr, m->max_other_addr * sizeof(char *));
561        }
562        m->other_addr[m->num_other_addr++] = xstrdup(a);
563}
564
565static int addr_known(struct mbus *m, char *a)
566{
567        int     i;
568
569        for (i = 0; i < m->num_other_addr; i++) {
570                if (mbus_addr_match(m->other_addr[i], a)) {
571                        return TRUE;
572                }
573        }
574        return FALSE;
575}
576
577/* The tx_* functions are used to build an mbus message up in the */
578/* tx_buffer, and to add authentication and encryption before the */
579/* message is sent.                                               */
580static char      tx_cryptbuf[MBUS_BUF_SIZE];
581static char      tx_buffer[MBUS_BUF_SIZE];
582static char     *tx_bufpos;
583
584#define MBUS_AUTH_LEN 25
585
586static void tx_header(int seqnum, int ts, char reliable, char *src, char *dst, int ackseq)
587{
588        memset(tx_buffer,   0, MBUS_BUF_SIZE);
589        memset(tx_buffer, ' ', MBUS_AUTH_LEN);
590        tx_bufpos = tx_buffer + MBUS_AUTH_LEN;
591        sprintf(tx_bufpos, "\nmbus/1.0 %6d %9d %c (%s) %s ", seqnum, ts, reliable, src, dst);
592        tx_bufpos += 33 + strlen(src) + strlen(dst);
593        if (ackseq == -1) {
594                sprintf(tx_bufpos, "()\n");
595                tx_bufpos += 3;
596        } else {
597                sprintf(tx_bufpos, "(%6d)\n", ackseq);
598                tx_bufpos += 9;
599        }
600}
601
602static void tx_add_command(char *cmnd, char *args)
603{
604        sprintf(tx_bufpos, "%s (%s)\n", cmnd, args);
605        tx_bufpos += strlen(cmnd) + strlen(args) + 4;
606}
607
608static void tx_send(struct mbus *m)
609{
610        char            digest[16];
611        int             len;
612        unsigned char   initVec[8] = {0,0,0,0,0,0,0,0};
613
614        while (((tx_bufpos - tx_buffer) % 8) != 0) {
615                /* Pad to a multiple of 8 bytes, so the encryption can work... */
616                *(tx_bufpos++) = ' ';
617        }
618        *tx_bufpos = '\0';
619        len = tx_bufpos - tx_buffer;
620
621        if (m->hashkey != NULL) {
622                /* Authenticate... */
623                hmac_md5(tx_buffer + MBUS_AUTH_LEN, strlen(tx_buffer) - MBUS_AUTH_LEN, m->hashkey, m->hashkeylen, digest);
624                base64encode(digest, 16, tx_buffer, MBUS_AUTH_LEN - 1);
625        }
626        if (m->encrkey != NULL) {
627                /* Encrypt... */
628                memset(tx_cryptbuf, 0, MBUS_BUF_SIZE);
629                memcpy(tx_cryptbuf, tx_buffer, len);
630                assert((len % 8) == 0);
631                assert(len < MBUS_BUF_SIZE);
632                assert(m->encrkeylen == 8);
633                qfDES_CBC_e(m->encrkey, tx_cryptbuf, len, initVec);
634                memcpy(tx_buffer, tx_cryptbuf, len);
635        }
636        udp_send(m->s, tx_buffer, len);
637}
638
639static void resend(struct mbus *m, struct mbus_msg *curr)
640{
641        /* Don't need to check for buffer overflows: this was done in mbus_send() when */
642        /* this message was first transmitted. If it was okay then, it's okay now.     */
643        int      i;
644
645        tx_header(curr->seqnum, curr->ts.tv_sec, (char)(curr->reliable?'R':'U'), m->addr[0], curr->dest, -1);
646        for (i = 0; i < curr->num_cmds; i++) {
647                tx_add_command(curr->cmd_list[i], curr->arg_list[i]);
648        }
649        tx_send(m);
650        curr->retransmit_count++;
651}
652
653void mbus_retransmit(struct mbus *m)
654{
655        struct mbus_msg *curr = m->waiting_ack;
656        struct timeval  time;
657        long            diff;
658
659        if (!mbus_waiting_ack(m)) {
660                return;
661        }
662
663        gettimeofday(&time, NULL);
664
665        /* diff is time in milliseconds that the message has been awaiting an ACK */
666        diff = ((time.tv_sec * 1000) + (time.tv_usec / 1000)) - ((curr->time.tv_sec * 1000) + (curr->time.tv_usec / 1000));
667        if (diff > 10000) {
668                debug_msg("Reliable mbus message failed!\n");
669                if (m->err_handler == NULL) {
670                        abort();
671                }
672                m->err_handler(curr->seqnum, MBUS_MESSAGE_LOST);
673                return;
674        }
675        /* Note: We only send one retransmission each time, to avoid
676         * overflowing the receiver with a burst of requests...
677         */
678        if ((diff > 750) && (curr->retransmit_count == 2)) {
679                resend(m, curr);
680                return;
681        }
682        if ((diff > 500) && (curr->retransmit_count == 1)) {
683                resend(m, curr);
684                return;
685        }
686        if ((diff > 250) && (curr->retransmit_count == 0)) {
687                resend(m, curr);
688                return;
689        }
690        curr = curr->next;
691}
692
693void mbus_heartbeat(struct mbus *m, int interval)
694{
695        struct timeval  curr_time;
696
697        gettimeofday(&curr_time, NULL);
698
699        if (curr_time.tv_sec - m->last_heartbeat.tv_sec > interval) {
700                mbus_qmsg(m, "()", "mbus.hello", "", FALSE);
701                m->last_heartbeat = curr_time;
702        }
703}
704
705int mbus_waiting_ack(struct mbus *m)
706{
707        return m->waiting_ack != NULL;
708}
709
710struct mbus *mbus_init(void  (*cmd_handler)(char *src, char *cmd, char *arg, void *dat),
711                       void  (*err_handler)(int seqnum, int reason))
712{
713        struct mbus     *m;
714        struct mbus_key  k;
715        int              i;
716
717        m = (struct mbus *) xmalloc(sizeof(struct mbus));
718        if (m == NULL) {
719                debug_msg("Unable to allocate memory for mbus\n");
720                return NULL;
721        }
722
723        mbus_lock_config_file(m);
724        m->s              = udp_init("224.255.222.239", (u_int16) 47000, 0);
725        m->seqnum         = 0;
726        m->cmd_handler    = cmd_handler;
727        m->err_handler    = err_handler;
728        m->num_addr       = 0;
729        m->num_other_addr = 0;
730        m->max_other_addr = 10;
731        m->other_addr     = (char **) xmalloc(sizeof(char *) * 10);
732        m->parse_depth    = 0;
733        m->cmd_queue      = NULL;
734        m->waiting_ack    = NULL;
735
736        gettimeofday(&(m->last_heartbeat), NULL);
737
738        mbus_get_encrkey(m, &k);
739        m->encrkey    = k.key;
740        m->encrkeylen = k.key_len;
741
742        mbus_get_hashkey(m, &k);
743        m->hashkey    = k.key;
744        m->hashkeylen = k.key_len;
745
746        for (i = 0; i < MBUS_MAX_ADDR; i++) m->addr[i]         = NULL;
747        for (i = 0; i < MBUS_MAX_PD;   i++) m->parse_buffer[i] = NULL;
748        mbus_unlock_config_file(m);
749
750        return m;
751}
752
753static void mbus_flush_msgs(struct mbus_msg *queue)
754{
755        struct mbus_msg *curr, *next;
756        int i;
757
758        curr = queue;
759        while(curr) {
760                next = curr->next;
761                xfree(curr->dest);
762                for(i = 0; i < curr->num_cmds; i++) {
763                        xfree(curr->cmd_list[i]);
764                        xfree(curr->arg_list[i]);
765                }
766                curr = next;
767        }
768}
769
770void mbus_exit(struct mbus *m)
771{
772        assert(m != NULL);
773
774        while(m->parse_depth--) {
775                xfree(m->parse_buffer[m->parse_depth]);
776        }
777
778        mbus_flush_msgs(m->cmd_queue);
779        mbus_flush_msgs(m->waiting_ack);
780
781        if (m->encrkey != NULL) {
782                xfree(m->encrkey);
783        }
784        xfree(m->hashkey);
785        xfree(m);
786}
787
788void mbus_addr(struct mbus *m, char *addr)
789{
790        assert(m->num_addr < MBUS_MAX_ADDR);
791        mbus_parse_init(m, xstrdup(addr));
792        if (mbus_parse_lst(m, &(m->addr[m->num_addr]))) {
793                m->num_addr++;
794        }
795        mbus_parse_done(m);
796}
797
798void mbus_send(struct mbus *m)
799{
800        /* Send one, or more, messages previosly queued with mbus_qmsg(). */
801        /* Messages for the same destination are batched together. Stops  */
802        /* when a reliable message is sent, until the ACK is received.    */
803        struct mbus_msg *curr = m->cmd_queue;
804        int              i;
805
806        if (m->waiting_ack != NULL) {
807                return;
808        }
809
810        while (curr != NULL) {
811                /* Create the message... */
812                tx_header(curr->seqnum, curr->ts.tv_sec, (char)(curr->reliable?'R':'U'), m->addr[0], curr->dest, -1);
813                for (i = 0; i < curr->num_cmds; i++) {
814                        tx_add_command(curr->cmd_list[i], curr->arg_list[i]);
815                }
816                tx_send(m);
817               
818                m->cmd_queue = curr->next;
819                if (curr->reliable) {
820                        /* Reliable message, wait for the ack... */
821                        gettimeofday(&(curr->time), NULL);
822                        m->waiting_ack = curr;
823                        return;
824                } else {
825                        while (curr->num_cmds > 0) {
826                                curr->num_cmds--;
827                                xfree(curr->cmd_list[curr->num_cmds]);
828                                xfree(curr->arg_list[curr->num_cmds]);
829                        }
830                        xfree(curr->dest);
831                        xfree(curr);
832                }
833                curr = m->cmd_queue;
834        }
835}
836
837void mbus_qmsg(struct mbus *m, char *dest, const char *cmnd, const char *args, int reliable)
838{
839        /* Queue up a message for sending. The message is not */
840        /* actually sent until mbus_send() is called.         */
841        struct mbus_msg *curr = m->cmd_queue;
842        struct mbus_msg *prev = NULL;
843        int              alen = strlen(cmnd) + strlen(args) + 4;
844
845        if (reliable && !addr_known(m, dest)) {
846                debug_msg("Trying to send reliably to an unknown address...\n");
847#ifdef NDEF
848                if (m->err_handler == NULL) {
849                        abort();
850                }
851                m->err_handler(curr->seqnum, MBUS_DESTINATION_UNKNOWN);
852#endif
853        }
854
855        while (curr != NULL) {
856                if ((!curr->complete)
857                && mbus_addr_match(curr->dest, dest)
858                && (curr->num_cmds < MBUS_MAX_QLEN)
859                && ((curr->message_size + alen) < (MBUS_BUF_SIZE - 8))) {
860                        /* Slots message in if it fits, but this breaks ordering.  Msg
861                         * X+1 maybe shorter than X that is in next packet, so X+1 jumps
862                         * ahead.
863                         */
864                        curr->num_cmds++;
865                        curr->reliable |= reliable;
866                        curr->cmd_list[curr->num_cmds-1] = xstrdup(cmnd);
867                        curr->arg_list[curr->num_cmds-1] = xstrdup(args);
868                        curr->message_size += alen;
869                        return;
870                } else {
871                        curr->complete = TRUE;
872                }
873                prev = curr;
874                curr = curr->next;
875        }
876        curr = (struct mbus_msg *) xmalloc(sizeof(struct mbus_msg));
877        curr->next             = NULL;
878        curr->dest             = xstrdup(dest);
879        curr->retransmit_count = 0;
880        curr->message_size     = alen + 60 + strlen(dest) + strlen(m->addr[0]);
881        curr->seqnum           = m->seqnum++;
882        curr->reliable         = reliable;
883        curr->complete         = FALSE;
884        curr->num_cmds         = 1;
885        curr->cmd_list[0]      = xstrdup(cmnd);
886        curr->arg_list[0]      = xstrdup(args);
887        if (prev == NULL) {
888                m->cmd_queue = curr;
889        } else {
890                prev->next = curr;
891        }
892        gettimeofday(&(curr->time), NULL);
893        gettimeofday(&(curr->ts),   NULL);
894}
895
896void mbus_qmsgf(struct mbus *m, char *dest, int reliable, const char *cmnd, const char *format, ...)
897{
898        /* This is a wrapper around mbus_qmsg() which does a printf() style format into  */
899        /* a buffer. Saves the caller from having to a a malloc(), write the args string */
900        /* and then do a free(), and also saves worring about overflowing the buffer, so */
901        /* removing a common source of bugs!                                             */
902        char    buffer[MBUS_BUF_SIZE];
903        va_list ap;
904
905        va_start(ap, format);
906#ifdef WIN32
907        _vsnprintf(buffer, MBUS_BUF_SIZE, format, ap);
908#else
909        vsnprintf(buffer, MBUS_BUF_SIZE, format, ap);
910#endif
911        va_end(ap);
912        mbus_qmsg(m, dest, cmnd, buffer, reliable);
913}
914
915void mbus_parse_init(struct mbus *m, char *str)
916{
917        assert(m->parse_depth < (MBUS_MAX_PD - 1));
918        m->parse_buffer[++m->parse_depth] = str;
919}
920
921void mbus_parse_done(struct mbus *m)
922{
923        m->parse_depth--;
924        assert(m->parse_depth >= 0);
925}
926
927int mbus_parse_lst(struct mbus *m, char **l)
928{
929        int instr = FALSE;
930        int inlst = FALSE;
931
932        *l = m->parse_buffer[m->parse_depth];
933        while (isspace((unsigned char)*m->parse_buffer[m->parse_depth])) {
934                m->parse_buffer[m->parse_depth]++;
935        }
936        if (*m->parse_buffer[m->parse_depth] != '(') {
937                return FALSE;
938        }
939        *(m->parse_buffer[m->parse_depth]) = ' ';
940        while (*m->parse_buffer[m->parse_depth] != '\0') {
941                if ((*m->parse_buffer[m->parse_depth] == '"') && (*(m->parse_buffer[m->parse_depth]-1) != '\\')) {
942                        instr = !instr;
943                }
944                if ((*m->parse_buffer[m->parse_depth] == '(') && (*(m->parse_buffer[m->parse_depth]-1) != '\\') && !instr) {
945                        inlst = !inlst;
946                }
947                if ((*m->parse_buffer[m->parse_depth] == ')') && (*(m->parse_buffer[m->parse_depth]-1) != '\\') && !instr) {
948                        if (inlst) {
949                                inlst = !inlst;
950                        } else {
951                                *m->parse_buffer[m->parse_depth] = '\0';
952                                m->parse_buffer[m->parse_depth]++;
953                                return TRUE;
954                        }
955                }
956                m->parse_buffer[m->parse_depth]++;
957        }
958        return FALSE;
959}
960
961int mbus_parse_str(struct mbus *m, char **s)
962{
963        while (isspace((unsigned char)*m->parse_buffer[m->parse_depth])) {
964                m->parse_buffer[m->parse_depth]++;
965        }
966        if (*m->parse_buffer[m->parse_depth] != '"') {
967                return FALSE;
968        }
969        *s = m->parse_buffer[m->parse_depth]++;
970        while (*m->parse_buffer[m->parse_depth] != '\0') {
971                if ((*m->parse_buffer[m->parse_depth] == '"') && (*(m->parse_buffer[m->parse_depth]-1) != '\\')) {
972                        m->parse_buffer[m->parse_depth]++;
973                        *m->parse_buffer[m->parse_depth] = '\0';
974                        m->parse_buffer[m->parse_depth]++;
975                        return TRUE;
976                }
977                m->parse_buffer[m->parse_depth]++;
978        }
979        return FALSE;
980}
981
982static int mbus_parse_sym(struct mbus *m, char **s)
983{
984        while (isspace((unsigned char)*m->parse_buffer[m->parse_depth])) {
985                m->parse_buffer[m->parse_depth]++;
986        }
987        if (!isgraph((unsigned char)*m->parse_buffer[m->parse_depth])) {
988                return FALSE;
989        }
990        *s = m->parse_buffer[m->parse_depth]++;
991        while (!isspace((unsigned char)*m->parse_buffer[m->parse_depth]) && (*m->parse_buffer[m->parse_depth] != '\0')) {
992                m->parse_buffer[m->parse_depth]++;
993        }
994        *m->parse_buffer[m->parse_depth] = '\0';
995        m->parse_buffer[m->parse_depth]++;
996        return TRUE;
997}
998
999int mbus_parse_int(struct mbus *m, int *i)
1000{
1001        char    *p;
1002
1003        while (isspace((unsigned char)*m->parse_buffer[m->parse_depth])) {
1004                m->parse_buffer[m->parse_depth]++;
1005        }
1006
1007        *i = strtol(m->parse_buffer[m->parse_depth], &p, 10);
1008        if (((*i == LONG_MAX) || (*i == LONG_MIN)) && (errno == ERANGE)) {
1009                debug_msg("integer out of range\n");
1010                return FALSE;
1011        }
1012
1013        if (p == m->parse_buffer[m->parse_depth]) {
1014                return FALSE;
1015        }
1016        if (!isspace((unsigned char)*p) && (*p != '\0')) {
1017                return FALSE;
1018        }
1019        m->parse_buffer[m->parse_depth] = p;
1020        return TRUE;
1021}
1022
1023int mbus_parse_flt(struct mbus *m, double *d)
1024{
1025        char    *p;
1026        while (isspace((unsigned char)*m->parse_buffer[m->parse_depth])) {
1027                m->parse_buffer[m->parse_depth]++;
1028        }
1029
1030        *d = strtod(m->parse_buffer[m->parse_depth], &p);
1031        if (errno == ERANGE) {
1032                debug_msg("float out of range\n");
1033                return FALSE;
1034        }
1035
1036        if (p == m->parse_buffer[m->parse_depth]) {
1037                return FALSE;
1038        }
1039        if (!isspace((unsigned char)*p) && (*p != '\0')) {
1040                return FALSE;
1041        }
1042        m->parse_buffer[m->parse_depth] = p;
1043        return TRUE;
1044}
1045
1046char *mbus_decode_str(char *s)
1047{
1048        int     l = strlen(s);
1049        int     i, j;
1050
1051        /* Check that this an encoded string... */
1052        assert(s[0]   == '\"');
1053        assert(s[l-1] == '\"');
1054
1055        for (i=1,j=0; i < l - 1; i++,j++) {
1056                if (s[i] == '\\') {
1057                        i++;
1058                }
1059                s[j] = s[i];
1060        }
1061        s[j] = '\0';
1062        return s;
1063}
1064
1065char *mbus_encode_str(const char *s)
1066{
1067        int      i, j;
1068        int      len = strlen(s);
1069        char    *buf = (char *) xmalloc((len * 2) + 3);
1070
1071        for (i = 0, j = 1; i < len; i++,j++) {
1072                if (s[i] == ' ') {
1073                        buf[j] = '\\';
1074                        buf[j+1] = ' ';
1075                        j++;
1076                } else if (s[i] == '\"') {
1077                        buf[j] = '\\';
1078                        buf[j+1] = '\"';
1079                        j++;
1080                } else {
1081                        buf[j] = s[i];
1082                }
1083        }
1084        buf[0]   = '\"';
1085        buf[j]   = '\"';
1086        buf[j+1] = '\0';
1087        return buf;
1088}
1089
1090int mbus_recv(struct mbus *m, void *data)
1091{
1092        char            *auth, *ver, *src, *dst, *ack, *r, *cmd, *param;
1093        char            buffer[MBUS_BUF_SIZE];
1094        int             buffer_len, seq, i, a, rx, ts, authlen;
1095        char            ackbuf[MBUS_ACK_BUF_SIZE];
1096        char            digest[16];
1097        struct timeval  t;
1098        unsigned char   initVec[8] = {0,0,0,0,0,0,0,0};
1099
1100        rx = FALSE;
1101        while (1) {
1102                memset(buffer, 0, MBUS_BUF_SIZE);
1103                assert(m->s != NULL);
1104                udp_fd_zero();
1105                udp_fd_set(m->s);
1106                t.tv_sec  = 0; /* timeout appears to get corrupted on w32 */
1107                t.tv_usec = 0; /* sometimes, reset to zero everytime.     */ 
1108                if ((udp_select(&t) > 0) && udp_fd_isset(m->s)) {
1109                        buffer_len = udp_recv(m->s, buffer, MBUS_BUF_SIZE);
1110                        if (buffer_len > 0) {
1111                                rx = TRUE;
1112                        } else {
1113                                return rx;
1114                        }
1115                } else {
1116                        return FALSE;
1117                }
1118
1119                if (m->encrkey != NULL) {
1120                        /* Decrypt the message... */
1121                        if ((buffer_len % 8) != 0) {
1122                                debug_msg("Encrypted message not a multiple of 8 bytes in length\n");
1123                                continue;
1124                        }
1125                        memcpy(tx_cryptbuf, buffer, buffer_len);
1126                        memset(initVec, 0, 8);
1127                        qfDES_CBC_d(m->encrkey, tx_cryptbuf, buffer_len, initVec);
1128                        if (strncmp(tx_cryptbuf + MBUS_AUTH_LEN + 1, "mbus/1.0", 8) != 0) {
1129                                debug_msg("Message did not correctly decrypt\n");
1130                                continue;
1131                        }
1132                        memcpy(buffer, tx_cryptbuf, buffer_len);
1133                }
1134
1135                mbus_parse_init(m, buffer);
1136                /* Parse the authentication header */
1137                if (!mbus_parse_sym(m, &auth)) {
1138                        debug_msg("Failed to parse authentication header\n");
1139                        mbus_parse_done(m);
1140                        continue;
1141                }
1142
1143                /* Check that the packet authenticates correctly... */
1144                authlen = strlen(auth);
1145                hmac_md5(buffer + authlen + 1, buffer_len - authlen - 1, m->hashkey, m->hashkeylen, digest);
1146                base64encode(digest, 16, ackbuf, 24);
1147                if ((strlen(auth) != 24) || (strncmp(auth, ackbuf, 24) != 0)) {
1148                        debug_msg("Failed to authenticate message...\n");
1149                        mbus_parse_done(m);
1150                        continue;
1151                }
1152
1153                /* Parse the header */
1154                if (!mbus_parse_sym(m, &ver)) {
1155                        mbus_parse_done(m);
1156                        debug_msg("Parser failed version (1): %s\n",ver);
1157                        continue;
1158                }
1159                if (strcmp(ver, "mbus/1.0") != 0) {
1160                        mbus_parse_done(m);
1161                        debug_msg("Parser failed version (2): %s\n",ver);
1162                        continue;
1163                }
1164                if (!mbus_parse_int(m, &seq)) {
1165                        mbus_parse_done(m);
1166                        debug_msg("Parser failed seq\n");
1167                        continue;
1168                }
1169                if (!mbus_parse_int(m, &ts)) {
1170                        mbus_parse_done(m);
1171                        debug_msg("Parser failed ts\n");
1172                        continue;
1173                }
1174                if (!mbus_parse_sym(m, &r)) {
1175                        mbus_parse_done(m);
1176                        debug_msg("Parser failed reliable\n");
1177                        continue;
1178                }
1179                if (!mbus_parse_lst(m, &src)) {
1180                        mbus_parse_done(m);
1181                        debug_msg("Parser failed src\n");
1182                        continue;
1183                }
1184                if (!mbus_parse_lst(m, &dst)) {
1185                        mbus_parse_done(m);
1186                        debug_msg("Parser failed dst\n");
1187                        continue;
1188                }
1189                if (!mbus_parse_lst(m, &ack)) {
1190                        mbus_parse_done(m);
1191                        debug_msg("Parser failed ack\n");
1192                        continue;
1193                }
1194
1195                store_other_addr(m, src);
1196
1197                /* Check if the message was addressed to us... */
1198                for (i = 0; i < m->num_addr; i++) {
1199                        if (mbus_addr_match(m->addr[i], dst)) {
1200                                /* ...if so, process any ACKs received... */
1201                                mbus_parse_init(m, ack);
1202                                while (mbus_parse_int(m, &a)) {
1203                                        if (mbus_waiting_ack(m) && (m->waiting_ack->seqnum == a)) {
1204                                                while (m->waiting_ack->num_cmds > 0) {
1205                                                        m->waiting_ack->num_cmds--;
1206                                                        xfree(m->waiting_ack->cmd_list[m->waiting_ack->num_cmds]);
1207                                                        xfree(m->waiting_ack->arg_list[m->waiting_ack->num_cmds]);
1208                                                }
1209                                                xfree(m->waiting_ack->dest);
1210                                                xfree(m->waiting_ack);
1211                                                m->waiting_ack = NULL;
1212                                        }
1213                                }
1214                                mbus_parse_done(m);
1215                                /* ...if an ACK was requested, send one... */
1216                                if (strcmp(r, "R") == 0) {
1217                                        char *newsrc = (char *) xmalloc(strlen(src) + 3);
1218                                        sprintf(newsrc, "(%s)", src);   /* Yes, this is a kludge. */
1219                                        gettimeofday(&t, NULL);
1220                                        tx_header(++m->seqnum, (int) t.tv_sec, 'U', m->addr[0], newsrc, seq);
1221                                        tx_send(m);
1222                                        xfree(newsrc);
1223                                }
1224                                /* ...and process the commands contained in the message */
1225                                while (mbus_parse_sym(m, &cmd)) {
1226                                        if (mbus_parse_lst(m, &param)) {
1227                                                m->cmd_handler(src, cmd, param, data);
1228                                        } else {
1229                                                debug_msg("Unable to parse mbus command:\n");
1230                                                debug_msg("cmd = %s\n", cmd);
1231                                                debug_msg("arg = %s\n", param);
1232                                                break;
1233                                        }
1234                                }
1235                        }
1236                }
1237                mbus_parse_done(m);
1238        }
1239}
Note: See TracBrowser for help on using the browser.