wmbiff: Raise sleep interval to 20 secs
[dockapps.git] / wmbiff-0.4.27 / wmbiff / wmbiff.c
blob25ecfbe29eca72aa0fbedf894a02f4f3b0f8325a
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/keysym.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;
73 /* it could be argued that a better default exists. */
74 #define DEFAULT_FONT "-*-fixed-*-r-*-*-10-*-*-*-*-*-*-*"
75 static const char *font = NULL;
77 int debug_default = DEBUG_ERROR;
79 /* color from wmbiff's xpm, down to 24 bits. */
80 const char *foreground = "#21B3AF"; /* foreground cyan */
81 const char *background = "#202020"; /* background gray */
82 static const char *highlight = "yellow";
83 int SkipCertificateCheck = 0;
84 int Relax = 0; /* be not paranoid */
85 static int notWithdrawn = 0;
87 static unsigned int num_mailboxes = 1;
88 static const int x_origin = 5;
89 static const int y_origin = 5;
90 static int forever = 1; /* keep running. */
92 extern Window win;
93 extern Window iconwin;
95 Cursor busy_cursor, ready_cursor;
97 static __inline /*@out@ */ void *
98 malloc_ordie(size_t len)
100 void *ret = malloc(len);
101 if (ret == NULL) {
102 fprintf(stderr, "unable to allocate %d bytes\n", (int) len);
103 abort();
105 return (ret);
108 /* where vertically the mailbox sits for blitting characters. */
109 static int mbox_y(unsigned int mboxnum)
111 return ((11 * mboxnum) + y_origin);
114 /* special shortcuts for longer shell client commands */
115 static int gicuCreate( /*@notnull@ */ Pop3 pc, const char *path)
117 char buf[255];
118 if (isdigit(path[5])) {
119 sprintf(buf,
120 "shell:::echo `gnomeicu-client -u%s msgcount` new",
121 path + 5);
122 } else {
123 sprintf(buf, "shell:::echo `gnomeicu-client msgcount` new");
125 return (shellCreate(pc, buf));
128 static int fingerCreate( /*@notnull@ */ Pop3 pc, const char *path)
130 char buf[255];
131 sprintf(buf, "shell:::finger -lm %s | "
132 "perl -ne '(/^new mail/i && print \"new\");' "
133 "-e '(/^mail last read/i && print \"old\");' "
134 "-e '(/^no( unread)? mail/i && print \"no\");'", path + 7);
135 return (shellCreate(pc, buf));
138 /* Read a line from a file to obtain a pair setting=value
139 skips # and leading spaces
140 NOTE: if setting finish with 0, 1, 2, 3 or 4 last char are deleted and
141 index takes its value... if not index will get -1
142 Returns -1 if no setting=value
144 static int ReadLine(FILE * fp, /*@out@ */ char *setting,
145 /*@out@ */ char *value, /*@out@ */ int *mbox_index)
147 char buf[BUF_SIZE];
148 char *p, *q;
149 int len;
151 *setting = '\0';
152 *value = '\0';
153 *mbox_index = -1;
155 if (!fp || feof(fp))
156 return -1;
158 if (!fgets(buf, BUF_SIZE - 1, fp))
159 return -1;
161 len = strlen(buf);
163 if (buf[len - 1] == '\n') {
164 buf[len - 1] = '\0'; /* strip linefeed */
167 StripComment(buf);
169 if (!(p = strtok(buf, "=")))
170 return -1;
171 if (!(q = strtok(NULL, "\n")))
172 return -1;
174 /* Chg - Mark Hurley
175 * Date: May 8, 2001
176 * Removed for loop (which removed leading spaces)
177 * Leading & Trailing spaces need to be removed
178 * to Fix Debian bug #95849
180 FullTrim(p);
181 FullTrim(q);
183 /* strcpy(setting, p); nspring replaced with sscanf dec 2002 */
184 strcpy(value, q);
186 if (sscanf(p, "%[_a-z.]%d", setting, mbox_index) == 2) {
187 /* mailbox-specific configuration, ends in a digit */
188 if (*mbox_index < 0 || *mbox_index >= MAX_NUM_MAILBOXES) {
189 DMA(DEBUG_ERROR, "invalid mailbox number %d\n", *mbox_index);
190 exit(EXIT_FAILURE);
192 } else if (sscanf(p, "%[a-z]", setting) == 1) {
193 /* global configuration, all text. */
194 *mbox_index = -1;
195 } else {
196 /* we found an uncommented line that has an equals,
197 but is non-alphabetic. */
198 DMA(DEBUG_INFO, "unparsed setting %s\n", p);
199 return -1;
202 DMA(DEBUG_INFO, "@%s.%d=%s@\n", setting, *mbox_index, value);
203 return 1;
206 struct path_demultiplexer {
207 const char *id; /* followed by a colon */
208 int (*creator) ( /*@notnull@ */ Pop3 pc, const char *path);
211 static struct path_demultiplexer paths[] = {
212 {"pop3:", pop3Create},
213 {"pop3s:", pop3Create},
214 {"shell:", shellCreate},
215 {"gicu:", gicuCreate},
216 {"licq:", licqCreate},
217 {"finger:", fingerCreate},
218 {"imap:", imap4Create},
219 {"imaps:", imap4Create},
220 {"sslimap:", imap4Create},
221 {"maildir:", maildirCreate},
222 {"mbox:", mboxCreate},
223 {NULL, NULL}
227 static void parse_mbox_path(unsigned int item)
229 int i;
230 /* find the creator */
231 for (i = 0;
232 paths[i].id != NULL
233 && strncasecmp(mbox[item].path, paths[i].id, strlen(paths[i].id));
234 i++);
235 /* if found, execute */
236 if (paths[i].id != NULL) {
237 if (paths[i].creator((&mbox[item]), mbox[item].path) != 0) {
238 DMA(DEBUG_ERROR, "creator for mailbox %u returned failure\n",
239 item);
241 } else {
242 /* default are mbox */
243 mboxCreate((&mbox[item]), mbox[item].path);
247 static int Read_Config_File(char *filename, int *loopinterval)
249 FILE *fp;
250 char setting[BUF_SMALL], value[BUF_SIZE];
251 int mbox_index;
252 unsigned int i;
254 if (!(fp = fopen(filename, "r"))) {
255 DMA(DEBUG_ERROR, "Unable to open %s, no settings read: %s\n",
256 filename, strerror(errno));
257 return 0;
259 while (!feof(fp)) {
260 /* skanky: -1 can represent an unparsed line
261 or an error */
262 if (ReadLine(fp, setting, value, &mbox_index) == -1)
263 continue;
265 /* settings that can be global go here. */
266 if (!strcmp(setting, "interval")) {
267 *loopinterval = atoi(value);
268 continue;
269 } else if (!strcmp(setting, "askpass")) {
270 const char *askpass = strdup_ordie(value);
271 if (mbox_index == -1) {
272 DMA(DEBUG_INFO, "setting all to askpass %s\n", askpass);
273 for (i = 0; i < MAX_NUM_MAILBOXES; i++)
274 mbox[i].askpass = askpass;
275 } else {
276 mbox[mbox_index].askpass = askpass;
278 continue;
279 } else if (!strcmp(setting, "skinfile")) {
280 skin_filename = strdup_ordie(value);
281 continue;
282 } else if (!strcmp(setting, "certfile")) { /* not yet supported */
283 certificate_filename = strdup_ordie(value);
284 continue;
285 } else if (!strcmp(setting, "globalnotify")) {
286 globalnotify = strdup_ordie(value);
287 continue;
288 } else if (mbox_index == -1) {
289 DMA(DEBUG_INFO, "Unknown global setting '%s'\n", setting);
290 continue; /* Didn't read any setting.[0-5] value */
293 if (mbox_index >= MAX_NUM_MAILBOXES) {
294 DMA(DEBUG_ERROR, "Don't have %d mailboxes.\n", mbox_index);
295 continue;
298 if (1U + mbox_index > num_mailboxes
299 && mbox_index + 1 <= MAX_NUM_MAILBOXES) {
300 num_mailboxes = 1U + mbox_index;
303 /* now only local settings */
304 if (!strcmp(setting, "label.")) {
305 if (strlen(value) + 1 > BUF_SMALL) {
306 DMA(DEBUG_ERROR,
307 "Mailbox %i label string '%s' is too long.\n",
308 mbox_index, value);
309 continue;
310 } else {
311 strncpy(mbox[mbox_index].label, value, BUF_SMALL - 1);
313 } else if (!strcmp(setting, "path.")) {
314 if (strlen(value) + 1 > BUF_BIG) {
315 DMA(DEBUG_ERROR,
316 "Mailbox %i path string '%s' is too long.\n",
317 mbox_index, value);
318 continue;
319 } else {
320 strncpy(mbox[mbox_index].path, value, BUF_BIG - 1);
322 } else if (!strcmp(setting, "notify.")) {
323 if (strlen(value) + 1 > BUF_BIG) {
324 DMA(DEBUG_ERROR,
325 "Mailbox %i notify string '%s' is too long.\n",
326 mbox_index, value);
327 continue;
328 } else {
329 strncpy(mbox[mbox_index].notify, value, BUF_BIG - 1);
331 } else if (!strcmp(setting, "action.")) {
332 if (strlen(value) + 1 > BUF_BIG) {
333 DMA(DEBUG_ERROR,
334 "Mailbox %i action string '%s' is too long.\n",
335 mbox_index, value);
336 continue;
337 } else {
338 strncpy(mbox[mbox_index].action, value, BUF_BIG - 1);
340 } else if (!strcmp(setting, "action_disconnected.")) {
341 if (strlen(value) + 1 > BUF_BIG) {
342 DMA(DEBUG_ERROR,
343 "Mailbox %i action_disconnected string '%s' is too long.\n",
344 mbox_index, value);
345 continue;
346 } else {
347 strncpy(mbox[mbox_index].actiondc, value, BUF_BIG - 1);
349 } else if (!strcmp(setting, "action_new_mail.")) {
350 if (strlen(value) + 1 > BUF_BIG) {
351 DMA(DEBUG_ERROR,
352 "Mailbox %i action_new_mail string '%s' is too long.\n",
353 mbox_index, value);
354 continue;
355 } else {
356 strncpy(mbox[mbox_index].actionnew, value, BUF_BIG - 1);
358 } else if (!strcmp(setting, "action_no_new_mail.")) {
359 if (strlen(value) + 1 > BUF_BIG) {
360 DMA(DEBUG_ERROR,
361 "Mailbox %i action_no_new_mail string '%s' is too long.\n",
362 mbox_index, value);
363 continue;
364 } else {
365 strncpy(mbox[mbox_index].actionnonew, value, BUF_BIG - 1);
367 } else if (!strcmp(setting, "interval.")) {
368 mbox[mbox_index].loopinterval = atoi(value);
369 } else if (!strcmp(setting, "buttontwo.")) {
370 if (strlen(value) + 1 > BUF_BIG) {
371 DMA(DEBUG_ERROR,
372 "Mailbox %i buttontwo string '%s' is too long.\n",
373 mbox_index, value);
374 continue;
375 } else {
376 strncpy(mbox[mbox_index].button2, value, BUF_BIG - 1);
378 } else if (!strcmp(setting, "fetchcmd.")) {
379 if (strlen(value) + 1 > BUF_BIG) {
380 DMA(DEBUG_ERROR,
381 "Mailbox %i fetchcmd string '%s' is too long.\n",
382 mbox_index, value);
383 continue;
384 } else {
385 strncpy(mbox[mbox_index].fetchcmd, value, BUF_BIG - 1);
387 } else if (!strcmp(setting, "fetchinterval.")) {
388 mbox[mbox_index].fetchinterval = atoi(value);
389 } else if (!strcmp(setting, "debug.")) {
390 int debug_value = debug_default;
391 if (strcasecmp(value, "all") == 0) {
392 debug_value = DEBUG_ALL;
394 /* could disable debugging, but I want the command
395 line argument to provide all information
396 possible. */
397 mbox[mbox_index].debug = debug_value;
398 } else {
399 DMA(DEBUG_INFO, "Unknown setting '%s'\n", setting);
402 (void) fclose(fp);
403 for (i = 0; i < num_mailboxes; i++)
404 if (mbox[i].label[0] != '\0')
405 parse_mbox_path(i);
406 return 1;
410 static void init_biff(char *config_file)
412 #ifdef HAVE_GCRYPT_H
413 gcry_error_t rc;
414 #endif
415 int loopinterval = DEFAULT_LOOP;
416 unsigned int i;
418 for (i = 0; i < MAX_NUM_MAILBOXES; i++) {
419 memset(mbox[i].label, 0, BUF_SMALL);
420 memset(mbox[i].path, 0, BUF_BIG);
421 memset(mbox[i].notify, 0, BUF_BIG);
422 memset(mbox[i].action, 0, BUF_BIG);
423 memset(mbox[i].actiondc, 0, BUF_BIG);
424 memset(mbox[i].actionnew, 0, BUF_BIG);
425 memset(mbox[i].actionnonew, 0, BUF_BIG);
426 memset(mbox[i].button2, 0, BUF_BIG);
427 memset(mbox[i].fetchcmd, 0, BUF_BIG);
428 mbox[i].loopinterval = 0;
429 mbox[i].getHeaders = NULL;
430 mbox[i].releaseHeaders = NULL;
431 mbox[i].debug = debug_default;
432 mbox[i].askpass = DEFAULT_ASKPASS;
435 #ifdef HAVE_GCRYPT_H
436 /* gcrypt is a little strange, in that it doesn't
437 * seem to initialize its memory pool by itself.
438 * I believe we *expect* "Warning: using insecure memory!"
440 /* gcryctl_disable_secmem gets called before check_version -- in a message on
441 gcrypt-devel august 17 2004. */
442 if ((rc = gcry_control(GCRYCTL_DISABLE_SECMEM, 0)) != 0) {
443 DMA(DEBUG_ERROR,
444 "Error: tried to disable gcrypt warning and failed: %d\n"
445 " Message: %s\n" " libgcrypt version: %s\n", rc,
446 gcry_strerror(rc), gcry_check_version(NULL));
449 /* recently made a requirement, section 2.4 of gcrypt manual */
450 if (gcry_check_version("1.1.42") == NULL) {
451 DMA(DEBUG_ERROR, "Error: incompatible gcrypt version.\n");
453 #endif
455 DMA(DEBUG_INFO, "config_file = %s.\n", config_file);
456 if (!Read_Config_File(config_file, &loopinterval)) {
457 char *m;
458 /* setup defaults if there's no config */
459 if ((m = getenv("MAIL")) != NULL) {
460 /* we are using MAIL environment var. type mbox */
461 if (strlen(m) + 1 > BUF_BIG) {
462 DMA(DEBUG_ERROR,
463 "MAIL environment var '%s' is too long.\n", m);
464 } else {
465 DMA(DEBUG_INFO, "Using MAIL environment var '%s'.\n", m);
466 strncpy(mbox[0].path, m, BUF_BIG - 1);
468 } else if ((m = getenv("USER")) != NULL) {
469 /* we will use the USER env var to find an mbox name */
470 if (strlen(m) + 10 + 1 > BUF_BIG) {
471 DMA(DEBUG_ERROR,
472 "USER environment var '%s' is too long.\n", m);
473 } else {
474 DMA(DEBUG_INFO, "Using /var/mail/%s.\n", m);
475 strcpy(mbox[0].path, "/var/mail/");
476 strncat(mbox[0].path, m, BUF_BIG - 1 - 10);
477 if (mbox[0].path[9] != '/') {
478 DMA(DEBUG_ERROR,
479 "Unexpected failure to construct /var/mail/username, please "
480 "report this with your operating system info and the version of wmbiff.");
483 } else {
484 DMA(DEBUG_ERROR, "Cannot open config file '%s' nor use the "
485 "MAIL environment var.\n", config_file);
486 exit(EXIT_FAILURE);
488 if (!exists(mbox[0].path)) {
489 DMA(DEBUG_ERROR, "Cannot open config file '%s', and the "
490 "default %s doesn't exist.\n", config_file, mbox[0].path);
491 exit(EXIT_FAILURE);
493 strcpy(mbox[0].label, "Spool");
494 mboxCreate((&mbox[0]), mbox[0].path);
497 /* Make labels look right */
498 for (i = 0; i < num_mailboxes; i++) {
499 if (mbox[i].label[0] != '\0') {
500 /* append a colon, but skip if we're using fonts. */
501 if (font == NULL) {
502 int j = strlen(mbox[i].label);
503 if (j < 5) {
504 memset(mbox[i].label + j, ' ', 5 - j);
506 mbox[i].label[5] = ':';
508 /* but always end after 5 characters */
509 mbox[i].label[6] = '\0';
510 /* set global loopinterval to boxes with 0 loop */
511 if (!mbox[i].loopinterval) {
512 mbox[i].loopinterval = loopinterval;
518 static char **LoadXPM(const char *pixmap_filename)
520 char **xpm;
521 int success;
522 success = XpmReadFileToData((char *) pixmap_filename, &xpm);
523 switch (success) {
524 case XpmOpenFailed:
525 DMA(DEBUG_ERROR, "Unable to open %s\n", pixmap_filename);
526 break;
527 case XpmFileInvalid:
528 DMA(DEBUG_ERROR, "%s is not a valid pixmap\n", pixmap_filename);
529 break;
530 case XpmNoMemory:
531 DMA(DEBUG_ERROR, "Insufficient memory to read %s\n",
532 pixmap_filename);
533 break;
534 default:
535 break;
537 return (xpm);
540 /* tests as "test -f" would */
541 int exists(const char *filename)
543 struct stat st_buf;
544 DMA(DEBUG_INFO, "looking for %s\n", filename);
545 if (stat(filename, &st_buf) == 0 && S_ISREG(st_buf.st_mode)) {
546 DMA(DEBUG_INFO, "found %s\n", filename);
547 return (1);
549 return (0);
552 /* acts like execvp, with code inspired by it */
553 /* mustfree */
554 static char *search_path(const char *path, const char *find_me)
556 char *buf;
557 const char *p;
558 int len, pathlen;
559 if (strchr(find_me, '/') != NULL) {
560 return (strdup_ordie(find_me));
562 pathlen = strlen(path);
563 len = strlen(find_me) + 1;
564 buf = malloc_ordie(pathlen + len + 1);
565 memcpy(buf + pathlen + 1, find_me, len);
566 buf[pathlen] = '/';
568 for (p = path; p != NULL; path = p, path++) {
569 char *startp;
570 p = strchr(path, ':');
571 if (p == NULL) {
572 /* not found; p should point to the null char at the end */
573 startp =
574 memcpy(buf + pathlen - strlen(path), path, strlen(path));
575 } else if (p == path) {
576 /* double colon in a path apparently means try here */
577 startp = &buf[pathlen + 1];
578 } else {
579 /* copy the part between the colons to the buffer */
580 startp = memcpy(buf + pathlen - (p - path), path, p - path);
582 if (exists(startp) != 0) {
583 char *ret = strdup_ordie(startp);
584 free(buf);
585 return (ret);
588 free(buf);
589 return (NULL);
592 /* verifies that .wmbiffrc, is a file, is owned by the user,
593 is not world writeable, and is not world readable. This
594 is just to help keep passwords secure */
595 static int wmbiffrc_permissions_check(const char *wmbiffrc_fname)
597 struct stat st;
598 if (stat(wmbiffrc_fname, &st) != 0) {
599 DMA(DEBUG_ERROR, "Can't stat wmbiffrc: '%s'\n", wmbiffrc_fname);
600 return (1); /* well, it's not a bad permission
601 problem: if you can't find it,
602 neither can the bad guys.. */
604 if ((st.st_mode & S_IFDIR) != 0) {
605 DMA(DEBUG_ERROR, ".wmbiffrc '%s' is a directory!\n"
606 "exiting. don't do that.", wmbiffrc_fname);
607 exit(EXIT_FAILURE);
609 if (st.st_uid != getuid()) {
610 char *user = getenv("USER");
611 DMA(DEBUG_ERROR,
612 ".wmbiffrc '%s' isn't owned by you.\n"
613 "Verify its contents, then 'chown %s %s'\n",
614 wmbiffrc_fname, ((user != NULL) ? user : "(your username)"),
615 wmbiffrc_fname);
616 return (0);
618 if ((st.st_mode & S_IWOTH) != 0) {
619 DMA(DEBUG_ERROR, ".wmbiffrc '%s' is world writable.\n"
620 "Verify its contents, then 'chmod 0600 %s'\n",
621 wmbiffrc_fname, wmbiffrc_fname);
622 return (0);
624 if ((st.st_mode & S_IROTH) != 0) {
625 DMA(DEBUG_ERROR, ".wmbiffrc '%s' is world readable.\n"
626 "Please run 'chmod 0600 %s' and consider changing your passwords.\n",
627 wmbiffrc_fname, wmbiffrc_fname);
628 return (0);
630 return (1);
633 static void ClearDigits(unsigned int i)
635 if (font) {
636 eraseRect(39, mbox_y(i), 58, mbox_y(i + 1) - 1, background);
637 } else {
638 /* overwrite the colon */
639 copyXPMArea((10 * (CHAR_WIDTH + 1)), 64, (CHAR_WIDTH + 1),
640 (CHAR_HEIGHT + 1), 35, mbox_y(i));
641 /* blank out the number fields. */
642 copyXPMArea(39, 84, (3 * (CHAR_WIDTH + 1)), (CHAR_HEIGHT + 1), 39,
643 mbox_y(i));
647 /* Blits a string at given co-ordinates. If a ``new''
648 parameter is nonzero, all digits will be yellow */
649 static void BlitString(const char *name, int x, int y, int new)
651 if (font != NULL) {
652 /* an alternate behavior - draw the string using a font
653 instead of the pixmap. should allow pretty colors */
654 drawString(x, y + CHAR_HEIGHT + 1, name,
655 new ? highlight : foreground, background, 0);
656 } else {
657 /* normal, LED-like behavior. */
658 int i, c, k = x;
659 for (i = 0; name[i] != '\0'; i++) {
660 c = toupper(name[i]);
661 if (c >= 'A' && c <= 'Z') { /* it's a letter */
662 c -= 'A';
663 copyXPMArea(c * (CHAR_WIDTH + 1), (new ? 95 : 74),
664 (CHAR_WIDTH + 1), (CHAR_HEIGHT + 1), k, y);
665 k += (CHAR_WIDTH + 1);
666 } else { /* it's a number or symbol */
667 c -= '0';
668 if (new) {
669 copyXPMArea((c * (CHAR_WIDTH + 1)) + 65, 0,
670 (CHAR_WIDTH + 1), (CHAR_HEIGHT + 1), k, y);
671 } else {
672 copyXPMArea((c * (CHAR_WIDTH + 1)), 64,
673 (CHAR_WIDTH + 1), (CHAR_HEIGHT + 1), k, y);
675 k += (CHAR_WIDTH + 1);
682 /* Blits number to give coordinates.. two 0's, right justified */
683 static void BlitNum(int num, int x, int y, int new)
685 char buf[32];
687 sprintf(buf, "%02i", num);
689 if (font != NULL) {
690 const char *color = (new) ? highlight : foreground;
691 drawString(x + (CHAR_WIDTH * 2 + 4), y + CHAR_HEIGHT + 1, buf,
692 color, background, 1);
693 } else {
694 int newx = x;
696 if (num > 99)
697 newx -= (CHAR_WIDTH + 1);
698 if (num > 999)
699 newx -= (CHAR_WIDTH + 1);
701 BlitString(buf, newx, y, new);
705 /* helper function for displayMsgCounters, which has outgrown its name */
706 static void blitMsgCounters(unsigned int i)
708 int y_row = mbox_y(i); /* constant for each mailbox */
709 ClearDigits(i); /* Clear digits */
710 if ((mbox[i].blink_stat & 0x01) == 0) {
711 int newmail = (mbox[i].UnreadMsgs > 0) ? 1 : 0;
712 if (mbox[i].TextStatus[0] != '\0') {
713 BlitString(mbox[i].TextStatus, 39, y_row, newmail);
714 } else {
715 int mailcount =
716 (newmail) ? mbox[i].UnreadMsgs : 0;
717 BlitNum(mailcount, 45, y_row, newmail);
723 * void execnotify(1) : runs notify command, if given (not null)
725 static void execnotify( /*@null@ */ const char *notifycmd)
727 if (notifycmd != NULL) { /* need to call notify() ? */
728 if (!strcasecmp(notifycmd, "true")) {
729 /* Yes, nothing */
730 } else {
731 /* Else call external notifyer, ignoring the pid */
732 (void) execCommand(notifycmd);
738 static void
739 displayMsgCounters(unsigned int i, int mail_stat, int *Blink_Mode)
741 switch (mail_stat) {
742 case 2: /* New mail has arrived */
743 /* Enter blink-mode for digits */
744 mbox[i].blink_stat = BLINK_TIMES * 2;
745 *Blink_Mode |= (1 << i); /* Global blink flag set for this mailbox */
746 blitMsgCounters(i);
747 execnotify(mbox[i].notify);
748 break;
749 case 1: /* mailbox has been rescanned/changed */
750 blitMsgCounters(i);
751 break;
752 case 0:
753 break;
754 case -1: /* Error was detected */
755 ClearDigits(i); /* Clear digits */
756 BlitString("XX", 45, mbox_y(i), 0);
757 break;
761 /** counts mail in spool-file
762 Returned value:
763 -1 : Error was encountered
764 0 : mailbox status wasn't changed
765 1 : mailbox was changed (NO new mail)
766 2 : mailbox was changed AND new mail has arrived
768 static int count_mail(unsigned int item)
770 int rc = 0;
772 if (!mbox[item].checkMail) {
773 return -1;
776 if (mbox[item].checkMail(&(mbox[item])) < 0) {
777 /* we failed to obtain any numbers therefore set
778 * them to -1's ensuring the next pass (even if
779 * zero) will be captured correctly
781 mbox[item].TotalMsgs = -1;
782 mbox[item].UnreadMsgs = -1;
783 mbox[item].OldMsgs = -1;
784 mbox[item].OldUnreadMsgs = -1;
785 return -1;
788 if (mbox[item].UnreadMsgs > mbox[item].OldUnreadMsgs &&
789 mbox[item].UnreadMsgs > 0) {
790 rc = 2; /* New mail detected */
791 } else if (mbox[item].UnreadMsgs < mbox[item].OldUnreadMsgs ||
792 mbox[item].TotalMsgs != mbox[item].OldMsgs) {
793 rc = 1; /* mailbox was changed - NO new mail */
794 } else {
795 rc = 0; /* mailbox wasn't changed */
797 mbox[item].OldMsgs = mbox[item].TotalMsgs;
798 mbox[item].OldUnreadMsgs = mbox[item].UnreadMsgs;
799 return rc;
802 static int periodic_mail_check(void)
804 int NeedRedraw = 0;
805 static int Blink_Mode = 0; /* Bit mask, digits are in blinking
806 mode or not. Each bit for separate
807 mailbox */
808 int Sleep_Interval; /* either DEFAULT_SLEEP_INTERVAL or
809 BLINK_SLEEP_INTERVAL */
810 int NewMail = 0; /* flag for global notify */
811 unsigned int i;
812 time_t curtime = time(0);
813 for (i = 0; i < num_mailboxes; i++) {
814 if (mbox[i].label[0] != '\0') {
815 if (curtime >= mbox[i].prevtime + mbox[i].loopinterval) {
816 int mailstat = 0;
817 NeedRedraw = 1;
818 DM(&mbox[i], DEBUG_INFO,
819 "working on [%u].label=>%s< [%u].path=>%s<\n", i,
820 mbox[i].label, i, mbox[i].path);
821 DM(&mbox[i], DEBUG_INFO,
822 "curtime=%d, prevtime=%d, interval=%d\n",
823 (int) curtime, (int) mbox[i].prevtime,
824 mbox[i].loopinterval);
825 mbox[i].prevtime = curtime;
827 XDefineCursor(display, iconwin, busy_cursor);
828 RedrawWindow();
830 mailstat = count_mail(i);
832 XUndefineCursor(display, iconwin);
834 /* Global notify */
835 if (mailstat == 2)
836 NewMail = 1;
838 displayMsgCounters(i, mailstat, &Blink_Mode);
839 /* update our idea of current time, as it
840 may have changed as we check. */
841 curtime = time(0);
843 if (mbox[i].blink_stat > 0) {
844 if (--mbox[i].blink_stat <= 0) {
845 Blink_Mode &= ~(1 << i);
846 mbox[i].blink_stat = 0;
848 displayMsgCounters(i, 1, &Blink_Mode);
849 NeedRedraw = 1;
851 if (mbox[i].fetchinterval > 0 && mbox[i].fetchcmd[0] != '\0'
852 && curtime >=
853 mbox[i].prevfetch_time + mbox[i].fetchinterval) {
855 XDefineCursor(display, iconwin, busy_cursor);
856 RedrawWindow();
858 (void) execCommand(mbox[i].fetchcmd);
860 XUndefineCursor(display, iconwin);
862 mbox[i].prevfetch_time = curtime;
867 /* exec globalnotify if there was any new mail */
868 if (NewMail == 1)
869 execnotify(globalnotify);
871 if (Blink_Mode == 0) {
872 for (i = 0; i < num_mailboxes; i++) {
873 mbox[i].blink_stat = 0;
875 Sleep_Interval = DEFAULT_SLEEP_INTERVAL;
876 } else {
877 Sleep_Interval = BLINK_SLEEP_INTERVAL;
880 if (NeedRedraw) {
881 NeedRedraw = 0;
882 RedrawWindow();
885 return Sleep_Interval;
888 static int findTopOfMasterXPM(const char **skin_xpm)
890 int i;
891 for (i = 0; skin_xpm[i] != NULL; i++) {
892 if (strstr(skin_xpm[i], "++++++++") != NULL)
893 return i;
895 DMA(DEBUG_ERROR,
896 "couldn't find the top of the xpm file using the simple method\n");
897 exit(EXIT_FAILURE);
900 static char **CreateBackingXPM(int width, int height,
901 const char **skin_xpm)
903 char **ret = malloc_ordie(sizeof(char *) * (height + 6)
904 + sizeof(void *) /* trailing null space */ );
905 const int colors = 5;
906 const int base = colors + 1;
907 const int margin = 4;
908 int i;
909 int top = findTopOfMasterXPM(skin_xpm);
910 ret[0] = malloc_ordie(30);
911 sprintf(ret[0], "%d %d %d %d", width, height, colors, 1);
912 ret[1] = (char *) " \tc #0000FF"; /* no color */
913 ret[2] = (char *) ".\tc #202020"; /* background gray */
914 ret[2] = malloc_ordie(30);
915 sprintf(ret[2], ".\tc %s", background);
916 ret[3] = (char *) "+\tc #000000"; /* shadowed */
917 ret[4] = (char *) "@\tc #C7C3C7"; /* highlight */
918 ret[5] = (char *) ":\tc #004941"; /* led off */
919 for (i = base; i < base + height; i++) {
920 ret[i] = malloc_ordie(width);
922 for (i = base; i < base + margin; i++) {
923 memset(ret[i], ' ', width);
925 for (i = base + margin; i < height + base - margin; i++) {
926 memset(ret[i], ' ', margin);
928 if (i == base + margin) {
929 memset(ret[i] + margin, '+', width - margin - margin);
930 } else if (i == base + height - margin - 1) {
931 memset(ret[i] + margin, '@', width - margin - margin);
932 } else {
933 // " +..:::...:::...:::...:::...:::.......:::...:::...:::...@ "
934 // " +.:...:.:...:.:...:.:...:.:...:..:..:...:.:...:.:...:..@ " ",
935 ret[i][margin] = '+';
936 memset(ret[i] + margin + 1, '.', width - margin - margin - 1);
937 ret[i][width - margin - 1] = '@';
938 memcpy(ret[i],
939 skin_xpm[((i - (base + margin) - 1) % 11) + top + 1],
940 width);
943 memset(ret[i] + width - margin, ' ', margin);
945 for (i = base + height - margin; i < height + base; i++) {
946 memset(ret[i], ' ', width);
948 ret[height + base] = NULL; /* not sure if this is necessary, it just
949 seemed like a good idea */
950 return (ret);
954 * NOTE: this function assumes that the ConnectionNumber() macro
955 * will return the file descriptor of the Display struct
956 * (it does under XFree86 and solaris' openwin X)
958 static void XSleep(int millisec)
960 #ifdef HAVE_POLL
961 struct pollfd timeout;
963 timeout.fd = ConnectionNumber(display);
964 timeout.events = POLLIN;
966 poll(&timeout, 1, millisec);
967 #else
968 struct timeval to;
969 struct timeval *timeout = NULL;
970 fd_set readfds;
971 int max_fd;
973 if (millisec >= 0) {
974 timeout = &to;
975 to.tv_sec = millisec / 1000;
976 to.tv_usec = (millisec % 1000) * 1000;
978 FD_ZERO(&readfds);
979 FD_SET(ConnectionNumber(display), &readfds);
980 max_fd = ConnectionNumber(display);
982 select(max_fd + 1, &readfds, NULL, NULL, timeout);
983 #endif
986 const char **restart_args;
988 static void restart_wmbiff(int sig
989 #ifdef HAVE___ATTRIBUTE__
990 __attribute__ ((unused))
991 #endif
994 DMA(DEBUG_ERROR, "exec()'ing %s\n", restart_args[0]);
995 sleep(1);
996 execvp(restart_args[0], (char *const *) restart_args);
997 DMA(DEBUG_ERROR, "exec of %s failed: %s\n",
998 restart_args[0], strerror(errno));
999 exit(EXIT_FAILURE);
1002 extern int x_socket(void)
1004 return ConnectionNumber(display);
1006 extern void ProcessPendingEvents(void)
1008 static int but_pressed_region = -1; /* static so click can be determined */
1009 int but_released_region = -1;
1010 /* X Events */
1011 while (XPending(display)) {
1012 XEvent Event;
1013 const char *press_action;
1015 XNextEvent(display, &Event);
1017 switch (Event.type) {
1018 case Expose:
1019 if (Event.xany.window != win && Event.xany.window != iconwin) {
1020 msglst_redraw();
1021 } else {
1022 RedrawWindow();
1024 break;
1025 case DestroyNotify:
1026 XCloseDisplay(display);
1027 exit(EXIT_SUCCESS);
1028 break;
1029 case ButtonPress:
1030 but_pressed_region =
1031 CheckMouseRegion(Event.xbutton.x, Event.xbutton.y);
1032 switch (Event.xbutton.button) {
1033 case 1:
1034 press_action = mbox[but_pressed_region].action;
1035 break;
1036 case 2:
1037 press_action = mbox[but_pressed_region].button2;
1038 break;
1039 case 3:
1040 press_action = mbox[but_pressed_region].fetchcmd;
1041 break;
1042 default:
1043 press_action = NULL;
1044 break;
1047 if (press_action && strcmp(press_action, "msglst") == 0) {
1048 msglst_show(&mbox[but_pressed_region],
1049 Event.xbutton.x_root, Event.xbutton.y_root);
1051 break;
1052 case ButtonRelease:
1053 but_released_region =
1054 CheckMouseRegion(Event.xbutton.x, Event.xbutton.y);
1055 if (but_released_region == but_pressed_region
1056 && but_released_region >= 0) {
1057 const char *click_action, *extra_click_action = NULL;
1059 switch (Event.xbutton.button) {
1060 case 1: /* Left mouse-click */
1061 /* C-S-left will restart wmbiff. */
1062 if ((Event.xbutton.state & ControlMask) &&
1063 (Event.xbutton.state & ShiftMask)) {
1064 restart_wmbiff(0);
1066 /* do we need to run an extra action? */
1067 if (mbox[but_released_region].UnreadMsgs == -1) {
1068 extra_click_action =
1069 mbox[but_released_region].actiondc;
1070 } else if (mbox[but_released_region].UnreadMsgs > 0) {
1071 extra_click_action =
1072 mbox[but_released_region].actionnew;
1073 } else {
1074 extra_click_action =
1075 mbox[but_released_region].actionnonew;
1077 click_action = mbox[but_released_region].action;
1078 break;
1079 case 2: /* Middle mouse-click */
1080 click_action = mbox[but_released_region].button2;
1081 break;
1082 case 3: /* Right mouse-click */
1083 click_action = mbox[but_released_region].fetchcmd;
1084 break;
1085 default:
1086 click_action = NULL;
1087 break;
1089 if (extra_click_action != NULL
1090 && extra_click_action[0] != 0
1091 && strcmp(extra_click_action, "msglst")) {
1092 DM(&mbox[but_released_region], DEBUG_INFO,
1093 "runing: %s", extra_click_action);
1094 (void) execCommand(extra_click_action);
1096 if (click_action != NULL
1097 && click_action[0] != '\0'
1098 && strcmp(click_action, "msglst")) {
1099 DM(&mbox[but_released_region], DEBUG_INFO,
1100 "running: %s", click_action);
1101 (void) execCommand(click_action);
1105 /* a button was released, hide the message list if open */
1106 msglst_hide();
1108 but_pressed_region = -1;
1109 /* RedrawWindow(); */
1110 break;
1111 case MotionNotify:
1112 break;
1113 case KeyPress:{
1114 XKeyPressedEvent *xkpe = (XKeyPressedEvent *) & Event;
1115 KeySym ks = XKeycodeToKeysym(display, xkpe->keycode, 0);
1116 if (ks > XK_0 && ks < XK_0 + min(9U, num_mailboxes)) {
1117 const char *click_action = mbox[ks - XK_1].action;
1118 if (click_action != NULL
1119 && click_action[0] != '\0'
1120 && strcmp(click_action, "msglst")) {
1121 DM(&mbox[but_released_region], DEBUG_INFO,
1122 "running: %s", click_action);
1123 (void) execCommand(click_action);
1128 break;
1129 default:
1130 break;
1135 static void do_biff(int argc, const char **argv)
1137 unsigned int i;
1138 time_t curtime;
1139 int Sleep_Interval;
1140 const char **skin_xpm = NULL;
1141 const char **bkg_xpm = NULL;
1142 char *skin_file_path = search_path(skin_search_path, skin_filename);
1143 int wmbiff_mask_height = mbox_y(num_mailboxes) + 4;
1145 DMA(DEBUG_INFO, "running %u mailboxes w %d h %d\n", num_mailboxes,
1146 wmbiff_mask_width, wmbiff_mask_height);
1148 if (skin_file_path != NULL) {
1149 skin_xpm = (const char **) LoadXPM(skin_file_path);
1150 free(skin_file_path);
1152 if (skin_xpm == NULL) {
1153 DMA(DEBUG_ERROR, "using built-in xpm; %s wasn't found in %s\n",
1154 skin_filename, skin_search_path);
1155 skin_xpm = wmbiff_master_xpm;
1158 bkg_xpm = (const char **) CreateBackingXPM(wmbiff_mask_width, wmbiff_mask_height, skin_xpm);
1160 createXBMfromXPM(wmbiff_mask_bits, bkg_xpm,
1161 wmbiff_mask_width, wmbiff_mask_height);
1163 openXwindow(argc, argv, bkg_xpm, skin_xpm, wmbiff_mask_bits,
1164 wmbiff_mask_width, wmbiff_mask_height, notWithdrawn);
1166 /* now that display is set, we can create the cursors
1167 (mouse pointer shapes) */
1168 busy_cursor = XCreateFontCursor(display, XC_watch);
1169 ready_cursor = XCreateFontCursor(display, XC_left_ptr);
1171 if (font != NULL) {
1172 if (loadFont(font) < 0) {
1173 DMA(DEBUG_ERROR, "unable to load font. exiting.\n");
1174 exit(EXIT_FAILURE);
1178 /* First time setup of button regions and labels */
1179 curtime = time(0);
1180 for (i = 0; i < num_mailboxes; i++) {
1181 /* make it easy to recover the mbox index from a mouse click */
1182 AddMouseRegion(i, x_origin, mbox_y(i), 58, mbox_y(i + 1) - 1);
1183 if (mbox[i].label[0] != '\0') {
1184 mbox[i].prevtime = mbox[i].prevfetch_time = 0;
1185 BlitString(mbox[i].label, x_origin, mbox_y(i), 0);
1189 do {
1191 Sleep_Interval = periodic_mail_check();
1192 ProcessPendingEvents();
1193 XSleep(Sleep_Interval);
1195 while (forever); /* forever is usually true,
1196 but not when debugging with -exit */
1197 if (skin_xpm != NULL && skin_xpm != wmbiff_master_xpm) {
1198 free(skin_xpm); // added 3 jul 02, appeasing valgrind
1200 if (bkg_xpm != NULL) {
1201 free(bkg_xpm);
1205 static void sigchld_handler(int sig
1206 #ifdef HAVE___ATTRIBUTE__
1207 __attribute__ ((unused))
1208 #endif
1211 while (waitpid(0, NULL, WNOHANG) > 0);
1212 signal(SIGCHLD, sigchld_handler);
1215 static void usage(void)
1217 printf("\nwmBiff v%s"
1218 " - incoming mail checker\n"
1219 "Gennady Belyakov and others (see the README file)\n"
1220 "Please report bugs to %s\n"
1221 "\n"
1222 "usage:\n"
1223 " -bg <color> background color\n"
1224 " -c <filename> use specified config file\n"
1225 " -debug enable debugging\n"
1226 " -display <display name> use specified X display\n"
1227 " -fg <color> foreground color\n"
1228 " -font <font> font instead of LED\n"
1229 " -geometry +XPOS+YPOS initial window position\n"
1230 " -h this help screen\n"
1231 " -hi <color> highlight color for new mail\n"
1232 #ifdef USE_GNUTLS
1233 " -skip-certificate-check using TLS, don't validate the\n"
1234 " server's certificate\n"
1235 #endif
1236 " -relax assume the configuration is \n"
1237 " correct, parse it without paranoia, \n"
1238 " and assume hostnames are okay.\n"
1239 " -v print the version number\n"
1240 " +w not withdrawn: run as a window\n"
1241 "\n", PACKAGE_VERSION, PACKAGE_BUGREPORT);
1244 static void printversion(void)
1246 printf("wmbiff v%s\n", PACKAGE_VERSION);
1250 static void parse_cmd(int argc, const char **argv, char *config_file)
1252 int i;
1254 config_file[0] = '\0';
1256 /* Parse Command Line */
1258 for (i = 1; i < argc; i++) {
1259 const char *arg = argv[i];
1261 if (*arg == '-') {
1262 switch (arg[1]) {
1263 case 'b':
1264 if (strcmp(arg + 1, "bg") == 0) {
1265 if (argc > (i + 1)) {
1266 background = strdup_ordie(argv[i + 1]);
1267 DMA(DEBUG_INFO, "new background: %s", foreground);
1268 i++;
1269 if (font == NULL)
1270 font = DEFAULT_FONT;
1273 break;
1274 case 'd':
1275 if (strcmp(arg + 1, "debug") == 0) {
1276 debug_default = DEBUG_ALL;
1277 } else if (strcmp(arg + 1, "display") == 0) {
1278 /* passed to X's command line parser */
1279 } else {
1280 usage();
1281 exit(EXIT_FAILURE);
1283 break;
1284 case 'f':
1285 if (strcmp(arg + 1, "fg") == 0) {
1286 if (argc > (i + 1)) {
1287 foreground = strdup_ordie(argv[i + 1]);
1288 DMA(DEBUG_INFO, "new foreground: %s", foreground);
1289 i++;
1290 if (font == NULL)
1291 font = DEFAULT_FONT;
1293 } else if (strcmp(arg + 1, "font") == 0) {
1294 if (argc > (i + 1)) {
1295 if (strcmp(argv[i + 1], "default") == 0) {
1296 font = DEFAULT_FONT;
1297 } else {
1298 font = strdup_ordie(argv[i + 1]);
1300 DMA(DEBUG_INFO, "new font: %s", font);
1301 i++;
1303 } else {
1304 usage();
1305 exit(EXIT_FAILURE);
1307 break;
1308 case 'g':
1309 if (strcmp(arg + 1, "geometry") != 0) {
1310 usage();
1311 exit(EXIT_FAILURE);
1312 } else {
1313 i++; /* gobble the argument */
1314 if (i >= argc) { /* fail if there's nothing to gobble */
1315 usage();
1316 exit(EXIT_FAILURE);
1319 break;
1320 case 'h':
1321 if (strcmp(arg + 1, "hi") == 0) {
1322 if (argc > (i + 1)) {
1323 highlight = strdup_ordie(argv[i + 1]);
1324 DMA(DEBUG_INFO, "new highlight: %s", highlight);
1325 i++;
1326 if (font == NULL)
1327 font = DEFAULT_FONT;
1329 } else if (strcmp(arg + 1, "h") == 0) {
1330 usage();
1331 exit(EXIT_SUCCESS);
1333 break;
1334 case 'v':
1335 printversion();
1336 exit(EXIT_SUCCESS);
1337 break;
1338 case 's':
1339 if (strcmp(arg + 1, "skip-certificate-check") == 0) {
1340 SkipCertificateCheck = 1;
1341 } else {
1342 usage();
1343 exit(EXIT_SUCCESS);
1346 break;
1347 case 'r':
1348 if (strcmp(arg + 1, "relax") == 0) {
1349 Relax = 1;
1350 } else {
1351 usage();
1352 exit(EXIT_SUCCESS);
1355 break;
1356 case 'c':
1357 if (argc > (i + 1)) {
1358 strncpy(config_file, argv[i + 1], 255);
1359 i++;
1361 break;
1362 case 'e': /* undocumented for debugging */
1363 if (strcmp(arg + 1, "exit") == 0) {
1364 forever = 0;
1366 break;
1367 default:
1368 usage();
1369 exit(EXIT_SUCCESS);
1370 break;
1372 } else if (*arg == '+') {
1373 switch (arg[1]) {
1374 case 'w':
1375 notWithdrawn = 1;
1376 break;
1377 default:
1378 usage();
1379 exit(EXIT_SUCCESS);
1380 break;
1386 int main(int argc, const char *argv[])
1388 char uconfig_file[256];
1390 /* hold on to the arguments we were started with; we
1391 will need them if we have to restart on sigusr1 */
1392 restart_args =
1393 (const char **) malloc((argc + 1) * sizeof(const char *));
1394 memcpy(restart_args, argv, (argc) * sizeof(const char *));
1395 restart_args[argc] = NULL;
1397 parse_cmd(argc, argv, uconfig_file);
1399 /* decide what the config file is */
1400 if (uconfig_file[0] != '\0') { /* user-specified config file */
1401 DMA(DEBUG_INFO, "Using user-specified config file '%s'.\n",
1402 uconfig_file);
1403 } else {
1404 const char *home = getenv("HOME");
1405 if (home == NULL) {
1406 DMA(DEBUG_ERROR,
1407 "$HOME undefined. Use the -c option to specify a wmbiffrc\n");
1408 exit(EXIT_FAILURE);
1410 sprintf(uconfig_file, "%s/.wmbiffrc", home);
1413 if (wmbiffrc_permissions_check(uconfig_file) == 0) {
1414 DMA(DEBUG_ERROR,
1415 "WARNING: In future versions of WMBiff, .wmbiffrc MUST be\n"
1416 "owned by the user, and not readable or writable by others.\n\n");
1418 init_biff(uconfig_file);
1419 signal(SIGCHLD, sigchld_handler);
1420 signal(SIGUSR1, restart_wmbiff);
1421 signal(SIGPIPE, SIG_IGN); /* write() may fail */
1423 do_biff(argc, argv);
1424 return 0;