stringbuf.c: random idle style cleanup.
[citadel.git] / textclient / routines.c
1 // Client-side support functions.
2 //
3 // Copyright (c) 1987-2016 by the citadel.org team
4 //
5 // This program is open source software.  Use, duplication, or disclosure is subject to the GNU General Public License version 3.
6
7 #include "textclient.h"
8
9 #define IFAIDE if(axlevel>=AxAideU)
10 #define IFNAIDE if (axlevel<AxAideU)
11
12 extern unsigned userflags;
13 extern char sigcaught;
14 extern char rc_floor_mode;
15 extern int rc_ansi_color;
16 extern int rc_prompt_control;
17
18 /* Destructive backspace */
19 void back(int spaces) {
20         int a;
21         for (a = 0; a < spaces; ++a) {
22                 scr_putc(8);
23                 scr_putc(32);
24                 scr_putc(8);
25         }
26 }
27
28 /*
29  * Edit a user's Internet email addresses
30  */
31 void edit_user_internet_email_addresses(CtdlIPC * ipc, char *who) {
32         char buf[SIZ];
33         char *resp = NULL;
34         int num_recs = 0;
35         char **recs = NULL;
36         char ch;
37         int i, j;
38         int quitting = 0;
39         int modified = 0;
40         int r;
41         char emailaddrs[512];
42
43         r = CtdlIPCAideGetEmailAddresses(ipc, who, emailaddrs, buf);
44         if (r / 100 == 1) {
45                 while (!IsEmptyStr(emailaddrs)) {
46                         extract_token(buf, emailaddrs, 0, '\n', sizeof buf);
47                         remove_token(emailaddrs, 0, '\n');
48                         if (!IsEmptyStr(buf)) {
49                                 ++num_recs;
50                                 if (num_recs == 1)
51                                         recs = malloc(sizeof(char *));
52                                 else
53                                         recs = realloc(recs, (sizeof(char *)) * num_recs);
54                                 recs[num_recs - 1] = malloc(strlen(buf) + 1);
55                                 strcpy(recs[num_recs - 1], buf);
56                         }
57                 }
58         }
59
60         do {
61                 scr_printf("\n");
62                 color(BRIGHT_WHITE);
63                 scr_printf("    Internet email addresses for %s\n", who);
64                 color(DIM_WHITE);
65                 scr_printf("--- --------------------------------------------------\n");
66                 for (i = 0; i < num_recs; ++i) {
67                         color(DIM_WHITE);
68                         scr_printf("%3d ", i + 1);
69                         color(BRIGHT_CYAN);
70                         scr_printf("%s\n", recs[i]);
71                         color(DIM_WHITE);
72                 }
73
74                 ch = keymenu("", "<A>dd|<D>elete|<S>ave|<Q>uit");
75                 switch (ch) {
76                 case 'a':
77                         newprompt("Enter new email address: ", buf, 50);
78                         string_trim(buf);
79                         if (!IsEmptyStr(buf)) {
80                                 // FIXME validate the email address (format, our own domain, addr does not belong to another user)
81                                 ++num_recs;
82                                 if (num_recs == 1) {
83                                         recs = malloc(sizeof(char *));
84                                 }
85                                 else {
86                                         recs = realloc(recs, (sizeof(char *)) * num_recs);
87                                 }
88                                 recs[num_recs - 1] = strdup(buf);
89                         }
90                         modified = 1;
91                         break;
92                 case 'd':
93                         i = intprompt("Delete which address", 1, 1, num_recs) - 1;
94                         free(recs[i]);
95                         --num_recs;
96                         for (j = i; j < num_recs; ++j) {
97                                 recs[j] = recs[j + 1];
98                         }
99                         modified = 1;
100                         break;
101                 case 's':
102                         r = 1;
103                         for (i = 0; i < num_recs; i++)
104                                 r += 1 + strlen(recs[i]);
105                         resp = (char *) calloc(1, r);
106                         if (!resp) {
107                                 scr_printf("Can't save config - out of memory!\n");
108                                 logoff(ipc, 1);
109                         }
110                         if (num_recs)
111                                 for (i = 0; i < num_recs; i++) {
112                                         strcat(resp, recs[i]);
113                                         strcat(resp, "\n");
114                                 }
115                         r = CtdlIPCAideSetEmailAddresses(ipc, who, resp, buf);
116                         if (r / 100 != 4) {
117                                 scr_printf("%s\n", buf);
118                         }
119                         else {
120                                 scr_printf("Saved %d addresses.\n", num_recs);
121                                 modified = 0;
122                                 quitting = 1;
123                         }
124                         free(resp);
125                         break;
126                 case 'q':
127                         quitting = !modified || boolprompt("Quit without saving", 0);
128                         break;
129                 default:
130                         break;
131                 }
132         } while (!quitting);
133
134         if (recs != NULL) {
135                 for (i = 0; i < num_recs; ++i)
136                         free(recs[i]);
137                 free(recs);
138         }
139 }
140
141
142 /*
143  * Edit or delete a user (cmd=25 to edit/create, 96 to delete)
144  */
145 void edituser(CtdlIPC * ipc, int cmd) {
146         char buf[SIZ];
147         char who[USERNAME_SIZE];
148         char newname[USERNAME_SIZE];
149         struct ctdluser *user = NULL;
150         int newnow = 0;
151         int r;                  /* IPC response code */
152         int change_name = 0;
153
154         strcpy(newname, "");
155
156         newprompt("User name: ", who, 29);
157         while ((r = CtdlIPCAideGetUserParameters(ipc, who, &user, buf)) / 100 != 2) {
158                 scr_printf("%s\n", buf);
159                 if (cmd == 25) {
160                         scr_printf("Do you want to create this user? ");
161                         if (yesno()) {
162                                 r = CtdlIPCCreateUser(ipc, who, 0, buf);
163                                 if (r / 100 == 2) {
164                                         newnow = 1;
165                                         continue;
166                                 }
167                                 scr_printf("%s\n", buf);
168                         }
169                 }
170                 free(user);
171                 return;
172         }
173
174         if (cmd == 25) {        // user edit
175
176                 /* val_user(ipc, user->fullname, 0); we used to display the vCard here but there's really no need */
177
178                 if (!newnow) {
179                         change_name = 1;
180                         while (change_name == 1) {
181                                 if (boolprompt("Change name", 0)) {
182                                         strprompt("New name", newname, USERNAME_SIZE - 1);
183                                         r = CtdlIPCRenameUser(ipc, user->fullname, newname, buf);
184                                         if (r / 100 != 2) {
185                                                 scr_printf("%s\n", buf);
186                                         }
187                                         else {
188                                                 strcpy(user->fullname, newname);
189                                                 change_name = 0;
190                                         }
191                                 }
192                                 else {
193                                         change_name = 0;
194                                 }
195                         }
196                 }
197
198                 if (newnow || boolprompt("Change password", 0)) {
199                         strprompt("Password", user->password, -19);
200                 }
201
202                 user->axlevel = intprompt("Access level", user->axlevel, 0, 6);
203                 if (boolprompt("Permission to send Internet mail", (user->flags & US_INTERNET))) {
204                         user->flags |= US_INTERNET;
205                 }
206                 else {
207                         user->flags &= ~US_INTERNET;
208                 }
209                 if (boolprompt("Ask user to register again", !(user->flags & US_REGIS))) {
210                         user->flags &= ~US_REGIS;
211                 }
212                 else {
213                         user->flags |= US_REGIS;
214                 }
215                 user->lastcall = boolprompt("Set last login to now", 0) ? time(NULL) : user->lastcall;
216                 user->USuserpurge = intprompt("Purge time (in days, 0 for system default", user->USuserpurge, 0, INT_MAX);
217         }
218
219         if (cmd == 96) {
220                 scr_printf("Do you want to delete this user? ");
221                 if (!yesno()) {
222                         free(user);
223                         return;
224                 }
225                 user->axlevel = AxDeleted;
226         }
227
228         r = CtdlIPCAideSetUserParameters(ipc, user, buf);
229         if (r / 100 != 2) {
230                 scr_printf("%s\n", buf);
231         }
232         free(user);
233
234         if (cmd == 25) {        // user edit
235                 if (boolprompt("Edit this user's Internet email addresses", 0)) {
236                         edit_user_internet_email_addresses(ipc, who);
237                 }
238         }
239
240 }
241
242
243 /* Display a prompt and flip a bit based on whether the user answers
244  * yes or no.  Yes=1 and No=0, unless 'backwards' is set to a nonzero value
245  * in which case No=1 and Yes=0.
246  */
247 int set_attr(CtdlIPC * ipc, unsigned int sval, char *prompt, unsigned int sbit, int backwards) {
248         int a;
249         int temp;
250
251         temp = sval;
252         color(DIM_WHITE);
253         scr_printf("%50s ", prompt);
254         color(DIM_MAGENTA);
255         scr_printf("[");
256         color(BRIGHT_MAGENTA);
257
258         if (backwards) {
259                 scr_printf("%3s", ((temp & sbit) ? "No" : "Yes"));
260         }
261         else {
262                 scr_printf("%3s", ((temp & sbit) ? "Yes" : "No"));
263         }
264
265         color(DIM_MAGENTA);
266         scr_printf("]? ");
267         color(BRIGHT_CYAN);
268         a = (temp & sbit);
269         if (a != 0)
270                 a = 1;
271         if (backwards)
272                 a = 1 - a;
273         a = yesno_d(a);
274         if (backwards)
275                 a = 1 - a;
276         color(DIM_WHITE);
277         temp = (temp | sbit);
278         if (!a)
279                 temp = (temp ^ sbit);
280         return (temp);
281 }
282
283 /*
284  * modes are:  0 - .EC command, 1 - .EC for new user,
285  *             2 - toggle Xpert mode  3 - toggle floor mode
286  */
287 void enter_config(CtdlIPC * ipc, int mode) {
288         char buf[SIZ];
289         struct ctdluser *user = NULL;
290         int r;                  /* IPC response code */
291
292         r = CtdlIPCGetConfig(ipc, &user, buf);
293         if (r / 100 != 2) {
294                 scr_printf("%s\n", buf);
295                 free(user);
296                 return;
297         }
298
299         if (mode == 0 || mode == 1) {
300
301                 user->flags = set_attr(ipc, user->flags, "Are you an experienced Citadel user", US_EXPERT, 0);
302                 if ((user->flags & US_EXPERT) == 0 && mode == 1) {
303                         free(user);
304                         return;
305                 }
306
307                 user->flags = set_attr(ipc, user->flags, "Print last old message on New message request", US_LASTOLD, 0);
308
309                 user->flags = set_attr(ipc, user->flags, "Prompt after each message", US_NOPROMPT, 1);
310
311                 if ((user->flags & US_NOPROMPT) == 0) {
312                         user->flags = set_attr(ipc, user->flags, "Use 'disappearing' prompts", US_DISAPPEAR, 0);
313                 }
314
315                 user->flags = set_attr(ipc, user->flags, "Pause after each screenful of text", US_PAGINATOR, 0);
316
317                 if (rc_prompt_control == 3 && (user->flags & US_PAGINATOR)) {
318                         user->flags = set_attr(ipc, user->flags, "<N>ext and <S>top work at paginator prompt", US_PROMPTCTL, 0);
319                 }
320
321                 if (rc_floor_mode == RC_DEFAULT) {
322                         user->flags = set_attr(ipc, user->flags, "View rooms by floor", US_FLOORS, 0);
323                 }
324
325                 if (rc_ansi_color == 3) {
326                         user->flags = set_attr(ipc, user->flags, "Enable color support", US_COLOR, 0);
327                 }
328
329                 if ((user->flags & US_EXPERT) == 0) {
330                         formout(ipc, "unlisted");
331                 }
332
333                 user->flags = set_attr(ipc, user->flags, "Be unlisted in userlog", US_UNLISTED, 0);
334
335                 if (!IsEmptyStr(editor_path)) {
336                         user->flags = set_attr(ipc,
337                                                user->flags, "Always enter messages with the full-screen editor", US_EXTEDIT, 0);
338                 }
339
340         }
341
342         if (mode == 2) {
343                 if (user->flags & US_EXPERT) {
344                         user->flags ^= US_EXPERT;
345                         scr_printf("Expert mode now OFF\n");
346                 }
347                 else {
348                         user->flags |= US_EXPERT;
349                         scr_printf("Expert mode now ON\n");
350                 }
351         }
352
353         if (mode == 3) {
354                 if (user->flags & US_FLOORS) {
355                         user->flags ^= US_FLOORS;
356                         scr_printf("Floor mode now OFF\n");
357                 }
358                 else {
359                         user->flags |= US_FLOORS;
360                         scr_printf("Floor mode now ON\n");
361                 }
362         }
363
364         r = CtdlIPCSetConfig(ipc, user, buf);
365         if (r / 100 != 2)
366                 scr_printf("%s\n", buf);
367         userflags = user->flags;
368         free(user);
369 }
370
371 /*
372  * getstring()  -  get a line of text from a file
373  *                 ignores lines beginning with "#"
374  */
375 int getstring(FILE * fp, char *string) {
376         int a, c;
377         do {
378                 strcpy(string, "");
379                 a = 0;
380                 do {
381                         c = getc(fp);
382                         if (c < 0) {
383                                 string[a] = 0;
384                                 return (-1);
385                         }
386                         string[a++] = c;
387                 } while (c != 10);
388                 string[a - 1] = 0;
389         } while (string[0] == '#');
390         return (strlen(string));
391 }
392
393
394 /* Searches for patn in search string */
395 int pattern(char *search, char *patn) {
396         int a, b, len;
397
398         len = strlen(patn);
399         for (a = 0; !IsEmptyStr(&search[a]); ++a) {
400                 b = strncasecmp(&search[a], patn, len);
401                 if (b == 0)
402                         return (b);
403         }
404         return (-1);
405 }
406
407
408 void strproc(char *string) {
409         int a;
410
411         if (IsEmptyStr(string))
412                 return;
413
414         /* Convert non-printable characters to blanks */
415         for (a = 0; !IsEmptyStr(&string[a]); ++a) {
416                 if (string[a] < 32)
417                         string[a] = 32;
418                 if (string[a] > 126)
419                         string[a] = 32;
420         }
421
422         /* Remove leading and trailing blanks */
423         while (string[0] < 33)
424                 strcpy(string, &string[1]);
425         while (string[strlen(string) - 1] < 33)
426                 string[strlen(string) - 1] = 0;
427
428         /* Remove double blanks */
429         for (a = 0; a < strlen(string); ++a) {
430                 if ((string[a] == 32) && (string[a + 1] == 32)) {
431                         strcpy(&string[a], &string[a + 1]);
432                         a = 0;
433                 }
434         }
435
436         /* remove characters which would interfere with the network */
437         for (a = 0; a < strlen(string); ++a) {
438                 if (string[a] == '!')
439                         strcpy(&string[a], &string[a + 1]);
440                 if (string[a] == '@')
441                         strcpy(&string[a], &string[a + 1]);
442                 if (string[a] == '_')
443                         strcpy(&string[a], &string[a + 1]);
444                 if (string[a] == ',')
445                         strcpy(&string[a], &string[a + 1]);
446                 if (string[a] == '%')
447                         strcpy(&string[a], &string[a + 1]);
448                 if (string[a] == '|')
449                         strcpy(&string[a], &string[a + 1]);
450         }
451
452 }
453
454
455 void progress(CtdlIPC * ipc, unsigned long curr, unsigned long cmax) {
456         static char dots[] = "**************************************************";
457         char dots_printed[51];
458         char fmt[42];
459         unsigned long a;
460
461         if (curr >= cmax) {
462                 scr_printf("\r%79s\r", "");
463         }
464         else {
465                 /* a will be range 0-50 rather than 0-100 */
466                 a = (curr * 50) / cmax;
467                 sprintf(fmt, "[%%s%%%lds] %%3ld%%%% %%10ld/%%10ld\r", 50 - a);
468                 strncpy(dots_printed, dots, a);
469                 dots_printed[a] = 0;
470                 scr_printf(fmt, dots_printed, "", curr * 100 / cmax, curr, cmax);
471                 scr_flush();
472         }
473 }
474
475
476 /*
477  * NOT the same locate_host() in locate_host.c.  This one just does a
478  * 'who am i' to try to discover where the user is...
479  */
480 void locate_host(CtdlIPC * ipc, char *hbuf) {
481         FILE *who = (FILE *) popen("who am i", "r");
482         if (who == NULL) {
483                 strcpy(hbuf, ipc->ServInfo.fqdn);
484                 return;
485         }
486         fgets(hbuf, SIZ, who);
487         if (hbuf[strlen(hbuf) - 1] == '\n') {
488                 hbuf[strlen(hbuf) - 1] = 0;
489         }
490         pclose(who);
491         stripallbut(hbuf, '(', ')');
492 }
493
494 /*
495  * miscellaneous server commands (testing, etc.)
496  */
497 void misc_server_cmd(CtdlIPC * ipc, char *cmd) {
498         char buf[SIZ];
499
500         CtdlIPC_chat_send(ipc, cmd);
501         CtdlIPC_chat_recv(ipc, buf);
502         scr_printf("%s\n", buf);
503         if (buf[0] == '1') {
504                 set_keepalives(KA_HALF);
505                 while (CtdlIPC_chat_recv(ipc, buf), strcmp(buf, "000")) {
506                         scr_printf("%s\n", buf);
507                 }
508                 set_keepalives(KA_YES);
509                 return;
510         }
511         if (buf[0] == '4') {
512                 do {
513                         newprompt("> ", buf, 255);
514                         CtdlIPC_chat_send(ipc, buf);
515                 } while (strcmp(buf, "000"));
516                 return;
517         }
518 }
519
520
521 /*
522  * compute the checksum of a file
523  */
524 int file_checksum(char *filename) {
525         int cksum = 0;
526         int ch;
527         FILE *fp;
528
529         fp = fopen(filename, "r");
530         if (fp == NULL)
531                 return (0);
532
533         /* yes, this algorithm may allow cksum to overflow, but that's ok
534          * as long as it overflows consistently, which it will.
535          */
536         while (ch = getc(fp), ch >= 0) {
537                 cksum = (cksum + ch);
538         }
539
540         fclose(fp);
541         return (cksum);
542 }
543
544 /*
545  * nuke a directory and its contents
546  */
547 int nukedir(char *dirname) {
548         DIR *dp;
549         struct dirent *d;
550         char filename[SIZ];
551
552         dp = opendir(dirname);
553         if (dp == NULL) {
554                 return (errno);
555         }
556
557         while (d = readdir(dp), d != NULL) {
558                 snprintf(filename, sizeof filename, "%s/%s", dirname, d->d_name);
559                 unlink(filename);
560         }
561
562         closedir(dp);
563         return (rmdir(dirname));
564 }