Update gnutls code (require at least 2.2.0).
[dockapps.git] / wmbiff / wmbiff / wmbiff.c
blobf0b8136f18138a13389ce631d50943b1f2b75d76
1 /* $Id: wmbiff.c,v 1.70 2005/10/07 03:07:58 bluehal Exp $ */
3 // typedef int gcry_error_t;
5 #ifdef HAVE_CONFIG_H
6 #include <config.h>
7 #endif
9 #include <time.h>
10 #include <ctype.h>
12 #ifdef HAVE_POLL
13 #include <poll.h>
14 #else
15 #include <sys/time.h>
16 #endif
18 #include <sys/wait.h>
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 #include <signal.h>
23 #include <X11/Xlib.h>
24 #include <X11/xpm.h>
25 #include <X11/cursorfont.h>
26 #include <X11/XKBlib.h>
28 #include <errno.h>
29 #include <string.h>
31 #include "../wmgeneral/wmgeneral.h"
32 #include "../wmgeneral/misc.h"
34 #include "Client.h"
35 #include "charutil.h"
36 #include "MessageList.h"
38 #ifdef USE_DMALLOC
39 #include <dmalloc.h>
40 #endif
43 #include "wmbiff-master-led.xpm"
44 static char wmbiff_mask_bits[64 * 64];
45 static const int wmbiff_mask_width = 64;
46 // const int wmbiff_mask_height = 64;
48 #define CHAR_WIDTH 5
49 #define CHAR_HEIGHT 7
51 #define BLINK_TIMES 8
52 #define DEFAULT_SLEEP_INTERVAL 20000
53 #define BLINK_SLEEP_INTERVAL 200
54 #define DEFAULT_LOOP 5
56 #define MAX_NUM_MAILBOXES 40
57 static mbox_t mbox[MAX_NUM_MAILBOXES];
59 /* this is the normal pixmap. */
60 static const char *skin_filename = "wmbiff-master-led.xpm";
61 /* global notify action taken (if globalnotify option is set) */
62 static const char *globalnotify = NULL;
64 /* path to search for pixmaps */
65 /* /usr/share/wmbiff should have the default pixmap. */
66 /* /usr/local/share/wmbiff if compiled locally. */
67 /* / is there in case a user wants to specify a complete path */
68 /* . is there for development. */
69 static const char *skin_search_path = DEFAULT_SKIN_PATH;
70 /* for gnutls */
71 const char *certificate_filename = NULL;
72 /* gnutls: specify the priorities to use on the ciphers, key exchange methods,
73 macs and compression methods. */
74 const char *tls = NULL;
77 /* it could be argued that a better default exists. */
78 #define DEFAULT_FONT "-*-fixed-*-r-*-*-10-*-*-*-*-*-*-*"
79 static const char *font = NULL;
81 int debug_default = DEBUG_ERROR;
83 /* color from wmbiff's xpm, down to 24 bits. */
84 const char *foreground = "white"; /* foreground white */
85 const char *background = "#505075"; /* background blue */
86 static const char *highlight = "red";
87 int SkipCertificateCheck = 0;
88 int Relax = 0; /* be not paranoid */
89 static int notWithdrawn = 0;
91 static unsigned int num_mailboxes = 1;
92 static const int x_origin = 5;
93 static const int y_origin = 5;
94 static int forever = 1; /* keep running. */
95 unsigned int custom_skin = 0; /* user has choose a custom skin */
97 extern Window win;
98 extern Window iconwin;
100 Cursor busy_cursor, ready_cursor;
102 static __inline /*@out@ */ void *
103 malloc_ordie(size_t len)
105 void *ret = malloc(len);
106 if (ret == NULL) {
107 fprintf(stderr, "unable to allocate %d bytes\n", (int) len);
108 abort();
110 return (ret);
113 /* where vertically the mailbox sits for blitting characters. */
114 static int mbox_y(unsigned int mboxnum)
116 return ((11 * mboxnum) + y_origin);
119 /* Read a line from a file to obtain a pair setting=value
120 skips # and leading spaces
121 NOTE: if setting finish with 0, 1, 2, 3 or 4 last char are deleted and
122 index takes its value... if not index will get -1
123 Returns -1 if no setting=value
125 static int ReadLine(FILE * fp, /*@out@ */ char *setting,
126 /*@out@ */ char *value, /*@out@ */ int *mbox_index)
128 char buf[BUF_SIZE];
129 char *p, *q;
130 int len;
132 *setting = '\0';
133 *value = '\0';
134 *mbox_index = -1;
136 if (!fp || feof(fp))
137 return -1;
139 if (!fgets(buf, BUF_SIZE - 1, fp))
140 return -1;
142 len = strlen(buf);
144 if (buf[len - 1] == '\n') {
145 buf[len - 1] = '\0'; /* strip linefeed */
148 StripComment(buf);
150 if (!(p = strtok(buf, "=")))
151 return -1;
152 if (!(q = strtok(NULL, "\n")))
153 return -1;
155 /* Chg - Mark Hurley
156 * Date: May 8, 2001
157 * Removed for loop (which removed leading spaces)
158 * Leading & Trailing spaces need to be removed
159 * to Fix Debian bug #95849
161 FullTrim(p);
162 FullTrim(q);
164 /* strcpy(setting, p); nspring replaced with sscanf dec 2002 */
165 strcpy(value, q);
167 if (sscanf(p, "%[_a-z.]%d", setting, mbox_index) == 2) {
168 /* mailbox-specific configuration, ends in a digit */
169 if (*mbox_index < 0 || *mbox_index >= MAX_NUM_MAILBOXES) {
170 DMA(DEBUG_ERROR, "invalid mailbox number %d\n", *mbox_index);
171 exit(EXIT_FAILURE);
173 } else if (sscanf(p, "%[a-z]", setting) == 1) {
174 /* global configuration, all text. */
175 *mbox_index = -1;
176 } else {
177 /* we found an uncommented line that has an equals,
178 but is non-alphabetic. */
179 DMA(DEBUG_INFO, "unparsed setting %s\n", p);
180 return -1;
183 DMA(DEBUG_INFO, "@%s.%d=%s@\n", setting, *mbox_index, value);
184 return 1;
187 struct path_demultiplexer {
188 const char *id; /* followed by a colon */
189 int (*creator) ( /*@notnull@ */ Pop3 pc, const char *path);
192 static struct path_demultiplexer paths[] = {
193 {"pop3:", pop3Create},
194 {"pop3s:", pop3Create},
195 {"shell:", shellCreate},
196 {"imap:", imap4Create},
197 {"imaps:", imap4Create},
198 {"sslimap:", imap4Create},
199 {"maildir:", maildirCreate},
200 {"mbox:", mboxCreate},
201 {NULL, NULL}
205 static void parse_mbox_path(unsigned int item)
207 int i;
208 /* find the creator */
209 for (i = 0;
210 paths[i].id != NULL
211 && strncasecmp(mbox[item].path, paths[i].id, strlen(paths[i].id));
212 i++);
213 /* if found, execute */
214 if (paths[i].id != NULL) {
215 if (paths[i].creator((&mbox[item]), mbox[item].path) != 0) {
216 DMA(DEBUG_ERROR, "creator for mailbox %u returned failure\n",
217 item);
219 } else {
220 /* default are mbox */
221 mboxCreate((&mbox[item]), mbox[item].path);
225 static int Read_Config_File(char *filename, int *loopinterval)
227 FILE *fp;
228 char setting[BUF_SMALL], value[BUF_SIZE];
229 int mbox_index;
230 unsigned int i;
232 if (!(fp = fopen(filename, "r"))) {
233 DMA(DEBUG_ERROR, "Unable to open %s, no settings read: %s\n",
234 filename, strerror(errno));
235 return 0;
237 while (!feof(fp)) {
238 /* skanky: -1 can represent an unparsed line
239 or an error */
240 if (ReadLine(fp, setting, value, &mbox_index) == -1)
241 continue;
243 /* settings that can be global go here. */
244 if (!strcmp(setting, "interval")) {
245 *loopinterval = atoi(value);
246 continue;
247 } else if (!strcmp(setting, "askpass")) {
248 const char *askpass = strdup_ordie(value);
249 if (mbox_index == -1) {
250 DMA(DEBUG_INFO, "setting all to askpass %s\n", askpass);
251 for (i = 0; i < MAX_NUM_MAILBOXES; i++)
252 mbox[i].askpass = askpass;
253 } else {
254 mbox[mbox_index].askpass = askpass;
256 continue;
257 } else if (!strcmp(setting, "skinfile")) {
258 skin_filename = strdup_ordie(value);
259 custom_skin = 1;
260 continue;
261 } else if (!strcmp(setting, "certfile")) { /* not yet supported */
262 certificate_filename = strdup_ordie(value);
263 continue;
264 } else if (!strcmp(setting, "globalnotify")) {
265 globalnotify = strdup_ordie(value);
266 continue;
267 } else if (!strcmp(setting, "tls")) {
268 tls = strdup_ordie(value);
269 continue;
270 } else if (mbox_index == -1) {
271 DMA(DEBUG_INFO, "Unknown global setting '%s'\n", setting);
272 continue; /* Didn't read any setting.[0-5] value */
275 if (mbox_index >= MAX_NUM_MAILBOXES) {
276 DMA(DEBUG_ERROR, "Don't have %d mailboxes.\n", mbox_index);
277 continue;
280 if (1U + mbox_index > num_mailboxes
281 && mbox_index + 1 <= MAX_NUM_MAILBOXES) {
282 num_mailboxes = 1U + mbox_index;
285 /* now only local settings */
286 if (!strcmp(setting, "label.")) {
287 if (strlen(value) + 1 > BUF_SMALL) {
288 DMA(DEBUG_ERROR,
289 "Mailbox %i label string '%s' is too long.\n",
290 mbox_index, value);
291 continue;
292 } else {
293 strncpy(mbox[mbox_index].label, value, BUF_SMALL - 1);
295 } else if (!strcmp(setting, "path.")) {
296 if (strlen(value) + 1 > BUF_BIG) {
297 DMA(DEBUG_ERROR,
298 "Mailbox %i path string '%s' is too long.\n",
299 mbox_index, value);
300 continue;
301 } else {
302 strncpy(mbox[mbox_index].path, value, BUF_BIG - 1);
304 } else if (!strcmp(setting, "notify.")) {
305 if (strlen(value) + 1 > BUF_BIG) {
306 DMA(DEBUG_ERROR,
307 "Mailbox %i notify string '%s' is too long.\n",
308 mbox_index, value);
309 continue;
310 } else {
311 strncpy(mbox[mbox_index].notify, value, BUF_BIG - 1);
313 } else if (!strcmp(setting, "action.")) {
314 if (strlen(value) + 1 > BUF_BIG) {
315 DMA(DEBUG_ERROR,
316 "Mailbox %i action string '%s' is too long.\n",
317 mbox_index, value);
318 continue;
319 } else {
320 strncpy(mbox[mbox_index].action, value, BUF_BIG - 1);
322 } else if (!strcmp(setting, "action_disconnected.")) {
323 if (strlen(value) + 1 > BUF_BIG) {
324 DMA(DEBUG_ERROR,
325 "Mailbox %i action_disconnected string '%s' is too long.\n",
326 mbox_index, value);
327 continue;
328 } else {
329 strncpy(mbox[mbox_index].actiondc, value, BUF_BIG - 1);
331 } else if (!strcmp(setting, "action_new_mail.")) {
332 if (strlen(value) + 1 > BUF_BIG) {
333 DMA(DEBUG_ERROR,
334 "Mailbox %i action_new_mail string '%s' is too long.\n",
335 mbox_index, value);
336 continue;
337 } else {
338 strncpy(mbox[mbox_index].actionnew, value, BUF_BIG - 1);
340 } else if (!strcmp(setting, "action_no_new_mail.")) {
341 if (strlen(value) + 1 > BUF_BIG) {
342 DMA(DEBUG_ERROR,
343 "Mailbox %i action_no_new_mail string '%s' is too long.\n",
344 mbox_index, value);
345 continue;
346 } else {
347 strncpy(mbox[mbox_index].actionnonew, value, BUF_BIG - 1);
349 } else if (!strcmp(setting, "interval.")) {
350 mbox[mbox_index].loopinterval = atoi(value);
351 } else if (!strcmp(setting, "buttontwo.")) {
352 if (strlen(value) + 1 > BUF_BIG) {
353 DMA(DEBUG_ERROR,
354 "Mailbox %i buttontwo string '%s' is too long.\n",
355 mbox_index, value);
356 continue;
357 } else {
358 strncpy(mbox[mbox_index].button2, value, BUF_BIG - 1);
360 } else if (!strcmp(setting, "fetchcmd.")) {
361 if (strlen(value) + 1 > BUF_BIG) {
362 DMA(DEBUG_ERROR,
363 "Mailbox %i fetchcmd string '%s' is too long.\n",
364 mbox_index, value);
365 continue;
366 } else {
367 strncpy(mbox[mbox_index].fetchcmd, value, BUF_BIG - 1);
369 } else if (!strcmp(setting, "fetchinterval.")) {
370 mbox[mbox_index].fetchinterval = atoi(value);
371 } else if (!strcmp(setting, "debug.")) {
372 int debug_value = debug_default;
373 if (strcasecmp(value, "all") == 0) {
374 debug_value = DEBUG_ALL;
376 /* could disable debugging, but I want the command
377 line argument to provide all information
378 possible. */
379 mbox[mbox_index].debug = debug_value;
380 } else {
381 DMA(DEBUG_INFO, "Unknown setting '%s'\n", setting);
384 (void) fclose(fp);
386 if (!tls)
387 // use GnuTLS's default ciphers.
388 tls = "NORMAL";
390 for (i = 0; i < num_mailboxes; i++)
391 if (mbox[i].label[0] != '\0')
392 parse_mbox_path(i);
393 return 1;
397 static void init_biff(char *config_file)
399 #ifdef HAVE_GCRYPT_H
400 gcry_error_t rc;
401 #endif
402 int loopinterval = DEFAULT_LOOP;
403 unsigned int i;
405 for (i = 0; i < MAX_NUM_MAILBOXES; i++) {
406 memset(mbox[i].label, 0, BUF_SMALL);
407 memset(mbox[i].path, 0, BUF_BIG);
408 memset(mbox[i].notify, 0, BUF_BIG);
409 memset(mbox[i].action, 0, BUF_BIG);
410 memset(mbox[i].actiondc, 0, BUF_BIG);
411 memset(mbox[i].actionnew, 0, BUF_BIG);
412 memset(mbox[i].actionnonew, 0, BUF_BIG);
413 memset(mbox[i].button2, 0, BUF_BIG);
414 memset(mbox[i].fetchcmd, 0, BUF_BIG);
415 mbox[i].loopinterval = 0;
416 mbox[i].getHeaders = NULL;
417 mbox[i].releaseHeaders = NULL;
418 mbox[i].debug = debug_default;
419 mbox[i].askpass = DEFAULT_ASKPASS;
422 #ifdef HAVE_GCRYPT_H
423 /* gcrypt is a little strange, in that it doesn't
424 * seem to initialize its memory pool by itself.
425 * I believe we *expect* "Warning: using insecure memory!"
427 /* gcryctl_disable_secmem gets called before check_version -- in a message on
428 gcrypt-devel august 17 2004. */
429 if ((rc = gcry_control(GCRYCTL_DISABLE_SECMEM, 0)) != 0) {
430 DMA(DEBUG_ERROR,
431 "Error: tried to disable gcrypt warning and failed: %d\n"
432 " Message: %s\n" " libgcrypt version: %s\n", rc,
433 gcry_strerror(rc), gcry_check_version(NULL));
436 /* recently made a requirement, section 2.4 of gcrypt manual */
437 if (gcry_check_version("1.1.42") == NULL) {
438 DMA(DEBUG_ERROR, "Error: incompatible gcrypt version.\n");
440 #endif
442 DMA(DEBUG_INFO, "config_file = %s.\n", config_file);
443 if (!Read_Config_File(config_file, &loopinterval)) {
444 char *m;
445 /* setup defaults if there's no config */
446 if ((m = getenv("MAIL")) != NULL) {
447 /* we are using MAIL environment var. type mbox */
448 if (strlen(m) + 1 > BUF_BIG) {
449 DMA(DEBUG_ERROR,
450 "MAIL environment var '%s' is too long.\n", m);
451 } else {
452 DMA(DEBUG_INFO, "Using MAIL environment var '%s'.\n", m);
453 strncpy(mbox[0].path, m, BUF_BIG - 1);
455 } else if ((m = getenv("USER")) != NULL) {
456 /* we will use the USER env var to find an mbox name */
457 if (strlen(m) + 10 + 1 > BUF_BIG) {
458 DMA(DEBUG_ERROR,
459 "USER environment var '%s' is too long.\n", m);
460 } else {
461 DMA(DEBUG_INFO, "Using /var/mail/%s.\n", m);
462 strcpy(mbox[0].path, "/var/mail/");
463 strncat(mbox[0].path, m, BUF_BIG - 1 - 10);
464 if (mbox[0].path[9] != '/') {
465 DMA(DEBUG_ERROR,
466 "Unexpected failure to construct /var/mail/username, please "
467 "report this with your operating system info and the version of wmbiff.");
470 } else {
471 DMA(DEBUG_ERROR, "Cannot open config file '%s' nor use the "
472 "MAIL environment var.\n", config_file);
473 exit(EXIT_FAILURE);
475 if (!exists(mbox[0].path)) {
476 DMA(DEBUG_ERROR, "Cannot open config file '%s', and the "
477 "default %s doesn't exist.\n", config_file, mbox[0].path);
478 exit(EXIT_FAILURE);
480 strcpy(mbox[0].label, "Spool");
481 mboxCreate((&mbox[0]), mbox[0].path);
484 /* Make labels look right */
485 for (i = 0; i < num_mailboxes; i++) {
486 if (mbox[i].label[0] != '\0') {
487 /* append a colon, but skip if we're using fonts. */
488 if (font == NULL) {
489 int j = strlen(mbox[i].label);
490 if (j < 5) {
491 memset(mbox[i].label + j, ' ', 5 - j);
494 /* but always end after 5 characters */
495 mbox[i].label[6] = '\0';
496 /* set global loopinterval to boxes with 0 loop */
497 if (!mbox[i].loopinterval) {
498 mbox[i].loopinterval = loopinterval;
504 static char **LoadXPM(const char *pixmap_filename)
506 char **xpm;
507 int success;
508 success = XpmReadFileToData((char *) pixmap_filename, &xpm);
509 switch (success) {
510 case XpmOpenFailed:
511 DMA(DEBUG_ERROR, "Unable to open %s\n", pixmap_filename);
512 break;
513 case XpmFileInvalid:
514 DMA(DEBUG_ERROR, "%s is not a valid pixmap\n", pixmap_filename);
515 break;
516 case XpmNoMemory:
517 DMA(DEBUG_ERROR, "Insufficient memory to read %s\n",
518 pixmap_filename);
519 break;
520 default:
521 break;
523 return (xpm);
526 /* tests as "test -f" would */
527 int exists(const char *filename)
529 struct stat st_buf;
530 DMA(DEBUG_INFO, "looking for %s\n", filename);
531 if (stat(filename, &st_buf) == 0 && S_ISREG(st_buf.st_mode)) {
532 DMA(DEBUG_INFO, "found %s\n", filename);
533 return (1);
535 return (0);
538 /* acts like execvp, with code inspired by it */
539 /* mustfree */
540 static char *search_path(const char *path, const char *find_me)
542 char *buf;
543 const char *p;
544 int len, pathlen;
545 if (strchr(find_me, '/') != NULL) {
546 return (strdup_ordie(find_me));
548 pathlen = strlen(path);
549 len = strlen(find_me) + 1;
550 buf = malloc_ordie(pathlen + len + 1);
551 memcpy(buf + pathlen + 1, find_me, len);
552 buf[pathlen] = '/';
554 for (p = path; p != NULL; path = p, path++) {
555 char *startp;
556 p = strchr(path, ':');
557 if (p == NULL) {
558 /* not found; p should point to the null char at the end */
559 startp =
560 memcpy(buf + pathlen - strlen(path), path, strlen(path));
561 } else if (p == path) {
562 /* double colon in a path apparently means try here */
563 startp = &buf[pathlen + 1];
564 } else {
565 /* copy the part between the colons to the buffer */
566 startp = memcpy(buf + pathlen - (p - path), path, p - path);
568 if (exists(startp) != 0) {
569 char *ret = strdup_ordie(startp);
570 free(buf);
571 return (ret);
574 free(buf);
575 return (NULL);
578 /* verifies that .wmbiffrc, is a file, is owned by the user,
579 is not world writeable, and is not world readable. This
580 is just to help keep passwords secure */
581 static int wmbiffrc_permissions_check(const char *wmbiffrc_fname)
583 struct stat st;
584 if (stat(wmbiffrc_fname, &st) != 0) {
585 DMA(DEBUG_ERROR, "Can't stat wmbiffrc: '%s'\n", wmbiffrc_fname);
586 return (1); /* well, it's not a bad permission
587 problem: if you can't find it,
588 neither can the bad guys.. */
590 if ((st.st_mode & S_IFDIR) != 0) {
591 DMA(DEBUG_ERROR, ".wmbiffrc '%s' is a directory!\n"
592 "exiting. don't do that.", wmbiffrc_fname);
593 exit(EXIT_FAILURE);
595 if (st.st_uid != getuid()) {
596 char *user = getenv("USER");
597 DMA(DEBUG_ERROR,
598 ".wmbiffrc '%s' isn't owned by you.\n"
599 "Verify its contents, then 'chown %s %s'\n",
600 wmbiffrc_fname, ((user != NULL) ? user : "(your username)"),
601 wmbiffrc_fname);
602 return (0);
604 if ((st.st_mode & S_IWOTH) != 0) {
605 DMA(DEBUG_ERROR, ".wmbiffrc '%s' is world writable.\n"
606 "Verify its contents, then 'chmod 0600 %s'\n",
607 wmbiffrc_fname, wmbiffrc_fname);
608 return (0);
610 if ((st.st_mode & S_IROTH) != 0) {
611 DMA(DEBUG_ERROR, ".wmbiffrc '%s' is world readable.\n"
612 "Please run 'chmod 0600 %s' and consider changing your passwords.\n",
613 wmbiffrc_fname, wmbiffrc_fname);
614 return (0);
616 return (1);
619 static void ClearDigits(unsigned int i)
621 if (font) {
622 eraseRect(39, mbox_y(i), 58, mbox_y(i + 1) - 1, background);
623 } else {
624 /* overwrite the colon */
625 copyXPMArea((10 * (CHAR_WIDTH + 1)), 64, (CHAR_WIDTH + 1),
626 (CHAR_HEIGHT + 1), 35, mbox_y(i));
627 /* blank out the number fields. */
628 copyXPMArea(39, 84, (3 * (CHAR_WIDTH + 1)), (CHAR_HEIGHT + 1), 39,
629 mbox_y(i));
633 /* Blits a string at given co-ordinates. If a ``new''
634 parameter is nonzero, all digits will be yellow */
635 static void BlitString(const char *name, int x, int y, int new)
637 if (font != NULL) {
638 /* an alternate behavior - draw the string using a font
639 instead of the pixmap. should allow pretty colors */
640 drawString(x, y + CHAR_HEIGHT + 1, name,
641 new ? highlight : foreground, background, 0);
642 } else {
643 /* normal, LED-like behavior. */
644 int i, c, k = x;
645 for (i = 0; name[i] != '\0'; i++) {
646 c = toupper(name[i]);
647 if (c >= 'A' && c <= 'Z') { /* it's a letter */
648 c -= 'A';
649 copyXPMArea(c * (CHAR_WIDTH + 1), (new ? 95 : 74),
650 (CHAR_WIDTH + 1), (CHAR_HEIGHT + 1), k, y);
651 k += (CHAR_WIDTH + 1);
652 } else { /* it's a number or symbol */
653 c -= '0';
654 if (new) {
655 copyXPMArea((c * (CHAR_WIDTH + 1)) + 65, 0,
656 (CHAR_WIDTH + 1), (CHAR_HEIGHT + 1), k, y);
657 } else {
658 copyXPMArea((c * (CHAR_WIDTH + 1)), 64,
659 (CHAR_WIDTH + 1), (CHAR_HEIGHT + 1), k, y);
661 k += (CHAR_WIDTH + 1);
668 /* Blits number to give coordinates.. two 0's, right justified */
669 static void BlitNum(int num, int x, int y, int new)
671 char buf[32];
673 sprintf(buf, "%02i", num);
675 if (font != NULL) {
676 const char *color = (new) ? highlight : foreground;
677 drawString(x + (CHAR_WIDTH * 2 + 4), y + CHAR_HEIGHT + 1, buf,
678 color, background, 1);
679 } else {
680 int newx = x;
682 if (num > 99)
683 newx -= (CHAR_WIDTH + 1);
684 if (num > 999)
685 newx -= (CHAR_WIDTH + 1);
687 BlitString(buf, newx, y, new);
691 /* helper function for displayMsgCounters, which has outgrown its name */
692 static void blitMsgCounters(unsigned int i)
694 int y_row = mbox_y(i); /* constant for each mailbox */
695 ClearDigits(i); /* Clear digits */
696 if ((mbox[i].blink_stat & 0x01) == 0) {
697 int newmail = (mbox[i].UnreadMsgs > 0) ? 1 : 0;
698 if (mbox[i].TextStatus[0] != '\0') {
699 BlitString(mbox[i].TextStatus, 39, y_row, newmail);
700 } else {
701 int mailcount =
702 (newmail) ? mbox[i].UnreadMsgs : 0;
703 BlitNum(mailcount, 45, y_row, newmail);
709 * void execnotify(1) : runs notify command, if given (not null)
711 static void execnotify( /*@null@ */ const char *notifycmd)
713 if (notifycmd != NULL) { /* need to call notify() ? */
714 if (!strcasecmp(notifycmd, "true")) {
715 /* Yes, nothing */
716 } else {
717 /* Else call external notifyer, ignoring the pid */
718 (void) execCommand(notifycmd);
724 static void
725 displayMsgCounters(unsigned int i, int mail_stat, int *Blink_Mode)
727 switch (mail_stat) {
728 case 2: /* New mail has arrived */
729 /* Enter blink-mode for digits */
730 mbox[i].blink_stat = BLINK_TIMES * 2;
731 *Blink_Mode |= (1 << i); /* Global blink flag set for this mailbox */
732 blitMsgCounters(i);
733 execnotify(mbox[i].notify);
734 break;
735 case 1: /* mailbox has been rescanned/changed */
736 blitMsgCounters(i);
737 break;
738 case 0:
739 break;
740 case -1: /* Error was detected */
741 ClearDigits(i); /* Clear digits */
742 BlitString("XX", 45, mbox_y(i), 0);
743 break;
747 /** counts mail in spool-file
748 Returned value:
749 -1 : Error was encountered
750 0 : mailbox status wasn't changed
751 1 : mailbox was changed (NO new mail)
752 2 : mailbox was changed AND new mail has arrived
754 static int count_mail(unsigned int item)
756 int rc = 0;
758 if (!mbox[item].checkMail) {
759 return -1;
762 if (mbox[item].checkMail(&(mbox[item])) < 0) {
763 /* we failed to obtain any numbers therefore set
764 * them to -1's ensuring the next pass (even if
765 * zero) will be captured correctly
767 mbox[item].TotalMsgs = -1;
768 mbox[item].UnreadMsgs = -1;
769 mbox[item].OldMsgs = -1;
770 mbox[item].OldUnreadMsgs = -1;
771 return -1;
774 if (mbox[item].UnreadMsgs > mbox[item].OldUnreadMsgs &&
775 mbox[item].UnreadMsgs > 0) {
776 rc = 2; /* New mail detected */
777 } else if (mbox[item].UnreadMsgs < mbox[item].OldUnreadMsgs ||
778 mbox[item].TotalMsgs != mbox[item].OldMsgs) {
779 rc = 1; /* mailbox was changed - NO new mail */
780 } else {
781 rc = 0; /* mailbox wasn't changed */
783 mbox[item].OldMsgs = mbox[item].TotalMsgs;
784 mbox[item].OldUnreadMsgs = mbox[item].UnreadMsgs;
785 return rc;
788 static int periodic_mail_check(void)
790 int NeedRedraw = 0;
791 static int Blink_Mode = 0; /* Bit mask, digits are in blinking
792 mode or not. Each bit for separate
793 mailbox */
794 int Sleep_Interval; /* either DEFAULT_SLEEP_INTERVAL or
795 BLINK_SLEEP_INTERVAL */
796 int NewMail = 0; /* flag for global notify */
797 unsigned int i;
798 time_t curtime = time(0);
799 for (i = 0; i < num_mailboxes; i++) {
800 if (mbox[i].label[0] != '\0') {
801 if (curtime >= mbox[i].prevtime + mbox[i].loopinterval) {
802 int mailstat = 0;
803 NeedRedraw = 1;
804 DM(&mbox[i], DEBUG_INFO,
805 "working on [%u].label=>%s< [%u].path=>%s<\n", i,
806 mbox[i].label, i, mbox[i].path);
807 DM(&mbox[i], DEBUG_INFO,
808 "curtime=%d, prevtime=%d, interval=%d\n",
809 (int) curtime, (int) mbox[i].prevtime,
810 mbox[i].loopinterval);
811 mbox[i].prevtime = curtime;
813 XDefineCursor(display, iconwin, busy_cursor);
814 RedrawWindow();
816 mailstat = count_mail(i);
818 XUndefineCursor(display, iconwin);
820 /* Global notify */
821 if (mailstat == 2)
822 NewMail = 1;
824 displayMsgCounters(i, mailstat, &Blink_Mode);
825 /* update our idea of current time, as it
826 may have changed as we check. */
827 curtime = time(0);
829 if (mbox[i].blink_stat > 0) {
830 if (--mbox[i].blink_stat <= 0) {
831 Blink_Mode &= ~(1 << i);
832 mbox[i].blink_stat = 0;
834 displayMsgCounters(i, 1, &Blink_Mode);
835 NeedRedraw = 1;
837 if (mbox[i].fetchinterval > 0 && mbox[i].fetchcmd[0] != '\0'
838 && curtime >=
839 mbox[i].prevfetch_time + mbox[i].fetchinterval) {
841 XDefineCursor(display, iconwin, busy_cursor);
842 RedrawWindow();
844 (void) execCommand(mbox[i].fetchcmd);
846 XUndefineCursor(display, iconwin);
848 mbox[i].prevfetch_time = curtime;
853 /* exec globalnotify if there was any new mail */
854 if (NewMail == 1)
855 execnotify(globalnotify);
857 if (Blink_Mode == 0) {
858 for (i = 0; i < num_mailboxes; i++) {
859 mbox[i].blink_stat = 0;
861 Sleep_Interval = DEFAULT_SLEEP_INTERVAL;
862 } else {
863 Sleep_Interval = BLINK_SLEEP_INTERVAL;
866 if (NeedRedraw) {
867 NeedRedraw = 0;
868 RedrawWindow();
871 return Sleep_Interval;
874 static int findTopOfMasterXPM(const char **skin_xpm)
876 int i;
877 for (i = 0; skin_xpm[i] != NULL; i++) {
878 if (strstr(skin_xpm[i], "++++++++") != NULL)
879 return i;
881 DMA(DEBUG_ERROR,
882 "couldn't find the top of the xpm file using the simple method\n");
883 exit(EXIT_FAILURE);
886 static char **CreateBackingXPM(int width, int height,
887 const char **skin_xpm)
889 char **ret = malloc_ordie(sizeof(char *) * (height + 6)
890 + sizeof(void *) /* trailing null space */ );
891 const int colors = 5;
892 const int base = colors + 1;
893 const int margin = 4;
894 int i;
895 int top = findTopOfMasterXPM(skin_xpm);
896 ret[0] = malloc_ordie(30);
897 sprintf(ret[0], "%d %d %d %d", width, height, colors, 1);
898 ret[1] = (char *) " \tc #0000FF"; /* no color */
899 ret[2] = (char *) ".\tc #505075"; /* background gray */
900 ret[2] = malloc_ordie(30);
901 sprintf(ret[2], ".\tc %s", background);
902 ret[3] = (char *) "+\tc #000000"; /* shadowed */
903 ret[4] = (char *) "@\tc #C7C3C7"; /* highlight */
904 ret[5] = (char *) ":\tc #004941"; /* led off */
905 for (i = base; i < base + height; i++) {
906 ret[i] = malloc_ordie(width);
908 for (i = base; i < base + margin; i++) {
909 memset(ret[i], ' ', width);
911 for (i = base + margin; i < height + base - margin; i++) {
912 memset(ret[i], ' ', margin);
914 if (i == base + margin) {
915 memset(ret[i] + margin, '+', width - margin - margin);
916 } else if (i == base + height - margin - 1) {
917 memset(ret[i] + margin, '@', width - margin - margin);
918 } else {
919 // " +..:::...:::...:::...:::...:::.......:::...:::...:::...@ "
920 // " +.:...:.:...:.:...:.:...:.:...:..:..:...:.:...:.:...:..@ " ",
921 ret[i][margin] = '+';
922 memset(ret[i] + margin + 1, '.', width - margin - margin - 1);
923 ret[i][width - margin - 1] = '@';
924 memcpy(ret[i],
925 skin_xpm[((i - (base + margin) - 1) % 11) + top + 1],
926 width);
929 memset(ret[i] + width - margin, ' ', margin);
931 for (i = base + height - margin; i < height + base; i++) {
932 memset(ret[i], ' ', width);
934 ret[height + base] = NULL; /* not sure if this is necessary, it just
935 seemed like a good idea */
936 return (ret);
940 * NOTE: this function assumes that the ConnectionNumber() macro
941 * will return the file descriptor of the Display struct
942 * (it does under XFree86 and solaris' openwin X)
944 static void XSleep(int millisec)
946 #ifdef HAVE_POLL
947 struct pollfd timeout;
949 timeout.fd = ConnectionNumber(display);
950 timeout.events = POLLIN;
952 poll(&timeout, 1, millisec);
953 #else
954 struct timeval to;
955 struct timeval *timeout = NULL;
956 fd_set readfds;
957 int max_fd;
959 if (millisec >= 0) {
960 timeout = &to;
961 to.tv_sec = millisec / 1000;
962 to.tv_usec = (millisec % 1000) * 1000;
964 FD_ZERO(&readfds);
965 FD_SET(ConnectionNumber(display), &readfds);
966 max_fd = ConnectionNumber(display);
968 select(max_fd + 1, &readfds, NULL, NULL, timeout);
969 #endif
972 const char **restart_args;
974 static void restart_wmbiff(int sig
975 #ifdef HAVE___ATTRIBUTE__
976 __attribute__ ((unused))
977 #endif
980 if (restart_args) {
981 DMA(DEBUG_ERROR, "exec()'ing %s\n", restart_args[0]);
982 sleep(1);
983 execvp(restart_args[0], (char *const *) restart_args);
984 DMA(DEBUG_ERROR, "exec of %s failed: %s\n",
985 restart_args[0], strerror(errno));
986 exit(EXIT_FAILURE);
988 else
989 fprintf(stderr, "Unable to restart wmbiff: missing restart arguments (NULL)!\n");
992 extern int x_socket(void)
994 return ConnectionNumber(display);
996 extern void ProcessPendingEvents(void)
998 static int but_pressed_region = -1; /* static so click can be determined */
999 int but_released_region = -1;
1000 /* X Events */
1001 while (XPending(display)) {
1002 XEvent Event;
1003 const char *press_action;
1005 XNextEvent(display, &Event);
1007 switch (Event.type) {
1008 case Expose:
1009 if (Event.xany.window != win && Event.xany.window != iconwin) {
1010 msglst_redraw();
1011 } else {
1012 RedrawWindow();
1014 break;
1015 case DestroyNotify:
1016 XCloseDisplay(display);
1017 exit(EXIT_SUCCESS);
1018 break;
1019 case ButtonPress:
1020 but_pressed_region =
1021 CheckMouseRegion(Event.xbutton.x, Event.xbutton.y);
1022 switch (Event.xbutton.button) {
1023 case 1:
1024 press_action = mbox[but_pressed_region].action;
1025 break;
1026 case 2:
1027 press_action = mbox[but_pressed_region].button2;
1028 break;
1029 case 3:
1030 press_action = mbox[but_pressed_region].fetchcmd;
1031 break;
1032 default:
1033 press_action = NULL;
1034 break;
1037 if (press_action && strcmp(press_action, "msglst") == 0) {
1038 msglst_show(&mbox[but_pressed_region],
1039 Event.xbutton.x_root, Event.xbutton.y_root);
1041 break;
1042 case ButtonRelease:
1043 but_released_region =
1044 CheckMouseRegion(Event.xbutton.x, Event.xbutton.y);
1045 if (but_released_region == but_pressed_region
1046 && but_released_region >= 0) {
1047 const char *click_action, *extra_click_action = NULL;
1049 switch (Event.xbutton.button) {
1050 case 1: /* Left mouse-click */
1051 /* C-S-left will restart wmbiff. */
1052 if ((Event.xbutton.state & ControlMask) &&
1053 (Event.xbutton.state & ShiftMask)) {
1054 restart_wmbiff(0);
1056 /* do we need to run an extra action? */
1057 if (mbox[but_released_region].UnreadMsgs == -1) {
1058 extra_click_action =
1059 mbox[but_released_region].actiondc;
1060 } else if (mbox[but_released_region].UnreadMsgs > 0) {
1061 extra_click_action =
1062 mbox[but_released_region].actionnew;
1063 } else {
1064 extra_click_action =
1065 mbox[but_released_region].actionnonew;
1067 click_action = mbox[but_released_region].action;
1068 break;
1069 case 2: /* Middle mouse-click */
1070 click_action = mbox[but_released_region].button2;
1071 break;
1072 case 3: /* Right mouse-click */
1073 click_action = mbox[but_released_region].fetchcmd;
1074 break;
1075 default:
1076 click_action = NULL;
1077 break;
1079 if (extra_click_action != NULL
1080 && extra_click_action[0] != 0
1081 && strcmp(extra_click_action, "msglst")) {
1082 DM(&mbox[but_released_region], DEBUG_INFO,
1083 "runing: %s", extra_click_action);
1084 (void) execCommand(extra_click_action);
1086 if (click_action != NULL
1087 && click_action[0] != '\0'
1088 && strcmp(click_action, "msglst")) {
1089 DM(&mbox[but_released_region], DEBUG_INFO,
1090 "running: %s", click_action);
1091 (void) execCommand(click_action);
1095 /* a button was released, hide the message list if open */
1096 msglst_hide();
1098 but_pressed_region = -1;
1099 /* RedrawWindow(); */
1100 break;
1101 case MotionNotify:
1102 break;
1103 case KeyPress:{
1104 XKeyPressedEvent *xkpe = (XKeyPressedEvent *) & Event;
1105 KeySym ks = XkbKeycodeToKeysym(display, xkpe->keycode, 0, 0);
1106 if (ks > XK_0 && ks < XK_0 + min(9U, num_mailboxes)) {
1107 const char *click_action = mbox[ks - XK_1].action;
1108 if (click_action != NULL
1109 && click_action[0] != '\0'
1110 && strcmp(click_action, "msglst")) {
1111 DM(&mbox[but_released_region], DEBUG_INFO,
1112 "running: %s", click_action);
1113 (void) execCommand(click_action);
1118 break;
1119 default:
1120 break;
1125 static void do_biff(int argc, const char **argv)
1127 unsigned int i;
1128 int Sleep_Interval;
1129 const char **skin_xpm = NULL;
1130 const char **bkg_xpm = NULL;
1131 char *skin_file_path = search_path(skin_search_path, skin_filename);
1132 int wmbiff_mask_height = mbox_y(num_mailboxes) + 4;
1134 DMA(DEBUG_INFO, "running %u mailboxes w %d h %d\n", num_mailboxes,
1135 wmbiff_mask_width, wmbiff_mask_height);
1137 if (skin_file_path != NULL) {
1138 skin_xpm = (const char **) LoadXPM(skin_file_path);
1139 free(skin_file_path);
1141 if (skin_xpm == NULL) {
1142 DMA(DEBUG_ERROR, "using built-in xpm; %s wasn't found in %s\n",
1143 skin_filename, skin_search_path);
1144 skin_xpm = wmbiff_master_xpm;
1147 bkg_xpm = (const char **) CreateBackingXPM(wmbiff_mask_width, wmbiff_mask_height, skin_xpm);
1149 createXBMfromXPM(wmbiff_mask_bits, bkg_xpm,
1150 wmbiff_mask_width, wmbiff_mask_height);
1152 openXwindow(argc, argv, bkg_xpm, skin_xpm, wmbiff_mask_bits,
1153 wmbiff_mask_width, wmbiff_mask_height, notWithdrawn);
1155 /* now that display is set, we can create the cursors
1156 (mouse pointer shapes) */
1157 busy_cursor = XCreateFontCursor(display, XC_watch);
1158 ready_cursor = XCreateFontCursor(display, XC_left_ptr);
1160 if (font != NULL) {
1161 if (loadFont(font) < 0) {
1162 DMA(DEBUG_ERROR, "unable to load font. exiting.\n");
1163 exit(EXIT_FAILURE);
1167 /* First time setup of button regions and labels */
1168 for (i = 0; i < num_mailboxes; i++) {
1169 /* make it easy to recover the mbox index from a mouse click */
1170 AddMouseRegion(i, x_origin, mbox_y(i), 58, mbox_y(i + 1) - 1);
1171 if (mbox[i].label[0] != '\0') {
1172 mbox[i].prevtime = mbox[i].prevfetch_time = 0;
1173 BlitString(mbox[i].label, x_origin, mbox_y(i), 0);
1177 do {
1179 Sleep_Interval = periodic_mail_check();
1180 ProcessPendingEvents();
1181 XSleep(Sleep_Interval);
1183 while (forever); /* forever is usually true,
1184 but not when debugging with -exit */
1185 if (skin_xpm != NULL && skin_xpm != wmbiff_master_xpm) {
1186 free(skin_xpm); // added 3 jul 02, appeasing valgrind
1188 if (bkg_xpm != NULL) {
1189 // Allocated in CreateBackingXPM()
1190 free((void *)bkg_xpm[0]);
1191 free((void *)bkg_xpm[2]);
1192 int mem_block;
1193 for (mem_block = 6; mem_block < 6 + wmbiff_mask_height; mem_block++)
1194 free((void *)bkg_xpm[mem_block]);
1195 free(bkg_xpm);
1199 static void sigchld_handler(int sig
1200 #ifdef HAVE___ATTRIBUTE__
1201 __attribute__ ((unused))
1202 #endif
1205 while (waitpid(0, NULL, WNOHANG) > 0);
1206 signal(SIGCHLD, sigchld_handler);
1209 static void usage(void)
1211 printf("\nwmBiff v%s"
1212 " - incoming mail checker\n"
1213 "Gennady Belyakov and others (see the README file)\n"
1214 "Please report bugs to %s\n"
1215 "\n"
1216 "usage:\n"
1217 " -bg <color> background color\n"
1218 " -c <filename> use specified config file\n"
1219 " -debug enable debugging\n"
1220 " -display <display name> use specified X display\n"
1221 " -fg <color> foreground color\n"
1222 " -font <font> font instead of LED\n"
1223 " -geometry +XPOS+YPOS initial window position\n"
1224 " -h this help screen\n"
1225 " -hi <color> highlight color for new mail\n"
1226 #ifdef USE_GNUTLS
1227 " -skip-certificate-check using TLS, don't validate the\n"
1228 " server's certificate\n"
1229 #endif
1230 " -relax assume the configuration is \n"
1231 " correct, parse it without paranoia, \n"
1232 " and assume hostnames are okay.\n"
1233 " -v print the version number\n"
1234 " +w not withdrawn: run as a window\n"
1235 "\n", PACKAGE_VERSION, PACKAGE_BUGREPORT);
1238 static void printversion(void)
1240 printf("wmbiff v%s\n", PACKAGE_VERSION);
1244 static void parse_cmd(int argc, const char **argv, char *config_file)
1246 int i;
1248 config_file[0] = '\0';
1250 /* Parse Command Line */
1252 for (i = 1; i < argc; i++) {
1253 const char *arg = argv[i];
1255 if (*arg == '-') {
1256 switch (arg[1]) {
1257 case 'b':
1258 if (strcmp(arg + 1, "bg") == 0) {
1259 if (argc > (i + 1)) {
1260 background = strdup_ordie(argv[i + 1]);
1261 DMA(DEBUG_INFO, "new background: %s", foreground);
1262 i++;
1263 if (font == NULL)
1264 font = DEFAULT_FONT;
1267 break;
1268 case 'd':
1269 if (strcmp(arg + 1, "debug") == 0) {
1270 debug_default = DEBUG_ALL;
1271 } else if (strcmp(arg + 1, "display") == 0) {
1272 /* passed to X's command line parser */
1273 } else {
1274 usage();
1275 exit(EXIT_FAILURE);
1277 break;
1278 case 'f':
1279 if (strcmp(arg + 1, "fg") == 0) {
1280 if (argc > (i + 1)) {
1281 foreground = strdup_ordie(argv[i + 1]);
1282 DMA(DEBUG_INFO, "new foreground: %s", foreground);
1283 i++;
1284 if (font == NULL)
1285 font = DEFAULT_FONT;
1287 } else if (strcmp(arg + 1, "font") == 0) {
1288 if (argc > (i + 1)) {
1289 if (strcmp(argv[i + 1], "default") == 0) {
1290 font = DEFAULT_FONT;
1291 } else {
1292 font = strdup_ordie(argv[i + 1]);
1294 DMA(DEBUG_INFO, "new font: %s", font);
1295 i++;
1297 } else {
1298 usage();
1299 exit(EXIT_FAILURE);
1301 break;
1302 case 'g':
1303 if (strcmp(arg + 1, "geometry") != 0) {
1304 usage();
1305 exit(EXIT_FAILURE);
1306 } else {
1307 i++; /* gobble the argument */
1308 if (i >= argc) { /* fail if there's nothing to gobble */
1309 usage();
1310 exit(EXIT_FAILURE);
1313 break;
1314 case 'h':
1315 if (strcmp(arg + 1, "hi") == 0) {
1316 if (argc > (i + 1)) {
1317 highlight = strdup_ordie(argv[i + 1]);
1318 DMA(DEBUG_INFO, "new highlight: %s", highlight);
1319 i++;
1320 if (font == NULL)
1321 font = DEFAULT_FONT;
1323 } else if (strcmp(arg + 1, "h") == 0) {
1324 usage();
1325 exit(EXIT_SUCCESS);
1327 break;
1328 case 'v':
1329 printversion();
1330 exit(EXIT_SUCCESS);
1331 break;
1332 case 's':
1333 if (strcmp(arg + 1, "skip-certificate-check") == 0) {
1334 SkipCertificateCheck = 1;
1335 } else {
1336 usage();
1337 exit(EXIT_SUCCESS);
1340 break;
1341 case 'r':
1342 if (strcmp(arg + 1, "relax") == 0) {
1343 Relax = 1;
1344 } else {
1345 usage();
1346 exit(EXIT_SUCCESS);
1349 break;
1350 case 'c':
1351 if (argc > (i + 1)) {
1352 strncpy(config_file, argv[i + 1], 255);
1353 i++;
1355 break;
1356 case 'e': /* undocumented for debugging */
1357 if (strcmp(arg + 1, "exit") == 0) {
1358 forever = 0;
1360 break;
1361 default:
1362 usage();
1363 exit(EXIT_SUCCESS);
1364 break;
1366 } else if (*arg == '+') {
1367 switch (arg[1]) {
1368 case 'w':
1369 notWithdrawn = 1;
1370 break;
1371 default:
1372 usage();
1373 exit(EXIT_SUCCESS);
1374 break;
1380 int main(int argc, const char *argv[])
1382 char uconfig_file[256];
1384 /* hold on to the arguments we were started with; we
1385 will need them if we have to restart on sigusr1 */
1386 restart_args =
1387 (const char **) malloc((argc + 1) * sizeof(const char *));
1388 if (restart_args) {
1389 memcpy(restart_args, argv, (argc) * sizeof(const char *));
1390 restart_args[argc] = NULL;
1393 parse_cmd(argc, argv, uconfig_file);
1395 /* decide what the config file is */
1396 if (uconfig_file[0] != '\0') { /* user-specified config file */
1397 DMA(DEBUG_INFO, "Using user-specified config file '%s'.\n",
1398 uconfig_file);
1399 } else {
1400 const char *home = getenv("HOME");
1401 if (home == NULL) {
1402 DMA(DEBUG_ERROR,
1403 "$HOME undefined. Use the -c option to specify a wmbiffrc\n");
1404 exit(EXIT_FAILURE);
1406 sprintf(uconfig_file, "%s/.wmbiffrc", home);
1409 if (wmbiffrc_permissions_check(uconfig_file) == 0) {
1410 DMA(DEBUG_ERROR,
1411 "WARNING: In future versions of WMBiff, .wmbiffrc MUST be\n"
1412 "owned by the user, and not readable or writable by others.\n\n");
1414 init_biff(uconfig_file);
1415 signal(SIGCHLD, sigchld_handler);
1416 signal(SIGUSR1, restart_wmbiff);
1417 signal(SIGPIPE, SIG_IGN); /* write() may fail */
1419 do_biff(argc, argv);
1421 // free resources
1422 if (restart_args)
1423 free(restart_args);
1424 if (custom_skin)
1425 free((void *)skin_filename);
1427 return 0;