1 /* Author: Benoît Rouits ( brouits@free.fr ) thanks to Neil Spring.
2 from LicqClient by Yong-iL Joh ( tolkien@mizi.com )
3 and Jorge García ( Jorge.Garcia@uv.es )
5 * generic Shell command support
7 * Last Updated : Tue Mar 5 15:23:35 CET 2002
24 #include "MessageList.h"
29 #define SH_DM(pc, lvl, args...) DM(pc, lvl, "shell: " args)
31 /* kind_popen bumps off the sigchld handler - we care whether
32 a checking program fails. */
35 void (*old_signal_handler
) (int);
37 RETSIGTYPE(*old_signal_handler
) (int);
41 FILE *kind_popen(const char *command
, const char *type
)
44 assert(strcmp(type
, "r") == 0);
45 assert(old_signal_handler
== NULL
);
46 old_signal_handler
= signal(SIGCHLD
, SIG_DFL
);
47 ret
= popen(command
, type
);
49 DMA(DEBUG_ERROR
, "popen: error while reading '%s': %s\n",
50 command
, strerror(errno
));
51 (void) signal(SIGCHLD
, old_signal_handler
);
52 old_signal_handler
= NULL
;
57 /* kind_pclose checks the return value from pclose and prints
58 some nice error messages about it. ordinarily, this would be
59 a good idea, but wmbiff has a sigchld handler that reaps
60 children immediately (needed when spawning other child processes),
61 so no error checking can be done here until that's disabled */
63 /* returns as a mailcheck function does: -1 on fail, 0 on success */
64 static int kind_pclose( /*@only@ */ FILE * F
,
68 int exit_status
= pclose(F
);
70 if (old_signal_handler
!= NULL
) {
71 (void) signal(SIGCHLD
, old_signal_handler
);
72 old_signal_handler
= NULL
;
75 if (exit_status
!= 0) {
76 if (exit_status
== -1) {
77 /* wmbiff has a sigchld handler already, so wait is likely
79 SH_DM(pc
, DEBUG_ERROR
, "pclose '%s' failed: %s\n",
80 command
, strerror(errno
));
82 SH_DM(pc
, DEBUG_ERROR
,
83 "'%s' exited with non-zero status %d\n", command
,
90 int grabCommandOutput(Pop3 pc
, const char *command
, /*@out@ */
91 char **output
, /*@out@ *//*@null@ */ char **details
)
95 SH_DM(pc
, DEBUG_INFO
, "Executing '%s'\n", command
);
97 if ((F
= kind_popen(command
, "r")) == NULL
) {
100 if (fgets(linebuf
, 512, F
) == NULL
) {
101 SH_DM(pc
, DEBUG_ERROR
,
102 "fgets: unable to read the output of '%s': %s\n", command
,
105 chomp(linebuf
); /* remove trailing newline */
106 *output
= strdup_ordie(linebuf
);
109 static char allbuf
[4096];
111 if (fread(allbuf
, 1, 4095, F
) == 0 && !feof(F
)) {
112 SH_DM(pc
, DEBUG_ERROR
,
113 "fread: unable to read the detailed output of '%s': %s\n",
114 command
, strerror(errno
));
117 *details
= strdup_ordie(allbuf
);
119 return (kind_pclose(F
, command
, pc
));
122 /* returns null on failure */
124 char *backtickExpand(Pop3 pc
, const char *path
)
126 char bigbuffer
[1024];
127 const char *tickstart
;
130 while ((tickstart
= strchr(path
, '`')) != NULL
) {
133 tickend
= strchr(tickstart
+ 1, '`');
134 if (tickend
== NULL
) {
135 SH_DM(pc
, DEBUG_ERROR
, "unbalanced \' in %s\n", path
);
138 strncat(bigbuffer
, path
, tickstart
- path
);
139 command
= strdup_ordie(tickstart
+ 1);
140 command
[tickend
- tickstart
- 1] = '\0';
141 (void) grabCommandOutput(pc
, command
, &commandoutput
, NULL
);
143 if (commandoutput
!= NULL
) {
144 strcat(bigbuffer
, commandoutput
);
150 strcat(bigbuffer
, path
);
151 SH_DM(pc
, DEBUG_INFO
, "expanded to %s\n", bigbuffer
);
152 return (strdup_ordie(bigbuffer
));
155 int shellCmdCheck(Pop3 pc
)
157 int count_status
= 0;
162 SH_DM(pc
, DEBUG_INFO
, ">Mailbox: '%s'\n", pc
->path
);
164 /* fetch the first line of input */
165 pc
->TextStatus
[0] = '\0';
166 if (pc
->u
.shell
.detail
!= NULL
) {
167 free(pc
->u
.shell
.detail
);
168 pc
->u
.shell
.detail
= NULL
;
170 (void) grabCommandOutput(pc
, pc
->path
, &commandOutput
,
171 &pc
->u
.shell
.detail
);
172 if (commandOutput
== NULL
) {
175 SH_DM(pc
, DEBUG_INFO
, "'%s' returned '%s'\n", pc
->path
, commandOutput
);
177 /* see if it's numeric; the numeric check is somewhat
178 useful, as wmbiff renders 4-digit numbers, but not
179 4-character strings. */
180 if (sscanf(commandOutput
, "%d", &(count_status
)) == 1) {
181 if (strstr(commandOutput
, "new")) {
182 pc
->UnreadMsgs
= count_status
;
184 } else if (strstr(commandOutput
, "old")) {
186 pc
->TotalMsgs
= count_status
;
188 /* this default should be configurable. */
190 pc
->TotalMsgs
= count_status
;
192 } else if (strcasestr(commandOutput
, "unable")) {
194 } else if (sscanf(commandOutput
, "%9s\n", pc
->TextStatus
) == 1) {
195 /* validate the string input */
197 for (i
= 0; pc
->TextStatus
[i
] != '\0' && isalnum(pc
->TextStatus
[i
])
199 if (pc
->TextStatus
[i
] != '\0') {
200 SH_DM(pc
, DEBUG_ERROR
,
201 "wmbiff only supports alphanumeric (isalnum) strings:\n"
202 " '%s' is not ok\n", pc
->TextStatus
);
203 /* null terminate it at the first bad char: */
204 pc
->TextStatus
[i
] = '\0';
206 /* see if we should print as new or not */
207 pc
->UnreadMsgs
= (strstr(commandOutput
, "new")) ? 1 : 0;
208 pc
->TotalMsgs
= -1; /* we might alternat numeric /string */
210 SH_DM(pc
, DEBUG_ERROR
,
211 "'%s' returned something other than an integer message count"
212 " or short string.\n", pc
->path
);
217 SH_DM(pc
, DEBUG_INFO
, "from: %s status: %s %d %d\n",
218 pc
->path
, pc
->TextStatus
, pc
->TotalMsgs
, pc
->UnreadMsgs
);
223 struct msglst
*shell_getHeaders( /*@notnull@ */ Pop3 pc
)
225 struct msglst
*message_list
= NULL
;
227 char *ln
= pc
->u
.shell
.detail
;
232 for (j
= 0; ln
[j
] != '\0';) {
233 struct msglst
*m
= malloc(sizeof(struct msglst
));
234 m
->next
= message_list
;
238 for (i
= 0; i
< SUBJ_LEN
- 1 && ln
[j
+ i
] != '\n'; i
++) {
239 m
->subj
[i
] = ln
[j
+ i
];
250 shell_releaseHeaders(Pop3 pc
__attribute__ ((unused
)), struct msglst
*h
)
253 struct msglst
*n
= h
->next
;
259 int shellCreate( /*@notnull@ */ Pop3 pc
, const char *str
)
261 /* SHELL format: shell:::/path/to/script */
262 const char *reserved1
, *reserved2
, *commandline
;
267 pc
->OldUnreadMsgs
= -1;
268 pc
->checkMail
= shellCmdCheck
;
269 pc
->getHeaders
= shell_getHeaders
;
270 reserved1
= str
+ 6; /* shell:>:: */
272 assert(strncasecmp("shell:", str
, 6) == 0);
274 reserved2
= index(reserved1
, ':');
275 if (reserved2
== NULL
) {
276 SH_DM(pc
, DEBUG_ERROR
, "unable to parse '%s', expecting ':'", str
);
279 reserved2
++; /* shell::>: */
281 commandline
= index(reserved2
, ':');
282 if (commandline
== NULL
) {
283 SH_DM(pc
, DEBUG_ERROR
,
284 "unable to parse '%s', expecting another ':'", str
);
287 commandline
++; /* shell:::> */
289 /* strcpy is not specified to handle overlapping regions */
290 SH_DM(pc
, DEBUG_INFO
, "path= '%s'\n", commandline
);
292 char *tmp
= strdup(commandline
);
293 if (strlen(tmp
) + 1 > BUF_BIG
) {
294 SH_DM(pc
, DEBUG_ERROR
, "commandline '%s' is too long.\n",
296 memset(pc
->path
, 0, BUF_BIG
);
298 strcpy(pc
->path
, tmp
);