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