5b34772fcfc3a7be9a843e79a4aeb41060034815
[citadel.git] / textclient / citadel_ipc.c
1 /*
2  * Copyright (c) 1987-2019 by the citadel.org team
3  *
4  *  This program is open source software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License version 3.
6  *
7  *  This program is distributed in the hope that it will be useful,
8  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
9  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  *  GNU General Public License for more details.
11  */
12
13 #include "textclient.h"
14
15 #ifdef HAVE_OPENSSL
16 static SSL_CTX *ssl_ctx;
17 char arg_encrypt;
18 char rc_encrypt;
19
20 #endif                          /* HAVE_OPENSSL */
21
22 #ifndef INADDR_NONE
23 #define INADDR_NONE 0xffffffff
24 #endif
25
26 static void (*status_hook) (char *s) = NULL;
27 char ctdl_autoetc_dir[PATH_MAX] = "";
28 char file_citadel_rc[PATH_MAX] = "";
29 char ctdl_run_dir[PATH_MAX] = "";
30 char ctdl_etc_dir[PATH_MAX] = "";
31 char ctdl_home_directory[PATH_MAX] = "";
32 char file_citadel_socket[PATH_MAX] = "";
33
34 char *viewdefs[] = {
35         "Messages",
36         "Summary",
37         "Address book",
38         "Calendar",
39         "Tasks"
40 };
41
42 char *axdefs[] = {
43         "Deleted",
44         "New User",
45         "Problem User",
46         "Local User",
47         "Network User",
48         "Preferred User",
49         "Admin",
50         "Admin"
51 };
52
53
54 void CtdlIPC_lock(CtdlIPC * ipc)
55 {
56         if (ipc->network_status_cb)
57                 ipc->network_status_cb(1);
58 }
59
60
61 void CtdlIPC_unlock(CtdlIPC * ipc)
62 {
63         if (ipc->network_status_cb)
64                 ipc->network_status_cb(0);
65 }
66
67
68 char *libcitadelclient_version_string(void)
69 {
70         return "libcitadelclient(unnumbered)";
71 }
72
73
74
75
76 #define COMPUTE_DIRECTORY(SUBDIR) memcpy(dirbuffer,SUBDIR, sizeof dirbuffer);\
77         snprintf(SUBDIR,sizeof SUBDIR,  "%s%s%s%s%s%s%s", \
78                          (home&!relh)?ctdl_home_directory:basedir, \
79              ((basedir!=ctdldir)&(home&!relh))?basedir:"/", \
80              ((basedir!=ctdldir)&(home&!relh))?"/":"", \
81                          relhome, \
82              (relhome[0]!='\0')?"/":"",\
83                          dirbuffer,\
84                          (dirbuffer[0]!='\0')?"/":"");
85
86 #define DBG_PRINT(A) if (dbg==1) fprintf (stderr,"%s : %s \n", #A, A)
87
88
89 void calc_dirs_n_files(int relh, int home, const char *relhome, char *ctdldir, int dbg)
90 {
91         const char *basedir = "";
92         char dirbuffer[PATH_MAX] = "";
93
94         StripSlashes(ctdldir, 1);
95
96 #ifndef HAVE_RUN_DIR
97         basedir = ctdldir;
98 #else
99         basedir = RUN_DIR;
100 #endif
101         COMPUTE_DIRECTORY(ctdl_run_dir);
102         StripSlashes(ctdl_run_dir, 1);
103
104
105 #ifndef HAVE_AUTO_ETC_DIR
106         basedir = ctdldir;
107 #else
108         basedir = AUTO_ETC_DIR;
109 #endif
110         COMPUTE_DIRECTORY(ctdl_autoetc_dir);
111         StripSlashes(ctdl_autoetc_dir, 1);
112
113
114 #ifndef HAVE_ETC_DIR
115         basedir = ctdldir;
116 #else
117         basedir = ETC_DIR;
118 #endif
119         COMPUTE_DIRECTORY(ctdl_etc_dir);
120         StripSlashes(ctdl_etc_dir, 1);
121
122
123
124         snprintf(file_citadel_rc, sizeof file_citadel_rc, "%scitadel.rc", ctdl_etc_dir);
125         StripSlashes(file_citadel_rc, 0);
126
127         snprintf(file_citadel_socket, sizeof file_citadel_socket, "%scitadel.socket", ctdl_run_dir);
128         StripSlashes(file_citadel_socket, 0);
129
130         DBG_PRINT(ctdl_run_dir);
131         DBG_PRINT(file_citadel_socket);
132         DBG_PRINT(ctdl_etc_dir);
133         DBG_PRINT(file_citadel_rc);
134 }
135
136 void setCryptoStatusHook(void (*hook) (char *s))
137 {
138         status_hook = hook;
139 }
140
141 void CtdlIPC_SetNetworkStatusCallback(CtdlIPC * ipc, void (*hook) (int state))
142 {
143         ipc->network_status_cb = hook;
144 }
145
146
147 char instant_msgs = 0;
148
149
150 static void serv_read(CtdlIPC * ipc, char *buf, unsigned int bytes);
151 static void serv_write(CtdlIPC * ipc, const char *buf, unsigned int nbytes);
152 #ifdef HAVE_OPENSSL
153 static void serv_read_ssl(CtdlIPC * ipc, char *buf, unsigned int bytes);
154 static void serv_write_ssl(CtdlIPC * ipc, const char *buf, unsigned int nbytes);
155 static void endtls(SSL * ssl);
156 #endif                          /* HAVE_OPENSSL */
157 static void CtdlIPC_getline(CtdlIPC * ipc, char *buf);
158 static void CtdlIPC_putline(CtdlIPC * ipc, const char *buf);
159
160
161
162 const char *svn_revision(void);
163
164 /*
165  * Does nothing.  The server should always return 200.
166  */
167 int CtdlIPCNoop(CtdlIPC * ipc)
168 {
169         char aaa[128];
170
171         return CtdlIPCGenericCommand(ipc, "NOOP", NULL, 0, NULL, NULL, aaa);
172 }
173
174
175 /*
176  * Does nothing interesting.  The server should always return 200
177  * along with your string.
178  */
179 int CtdlIPCEcho(CtdlIPC * ipc, const char *arg, char *cret)
180 {
181         int ret;
182         char *aaa;
183
184         if (!arg)
185                 return -2;
186         if (!cret)
187                 return -2;
188
189         aaa = (char *) malloc((size_t) (strlen(arg) + 6));
190         if (!aaa)
191                 return -1;
192
193         sprintf(aaa, "ECHO %s", arg);
194         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
195         free(aaa);
196         return ret;
197 }
198
199
200 /*
201  * Asks the server to close the connecction.
202  * Should always return 200.
203  */
204 int CtdlIPCQuit(CtdlIPC * ipc)
205 {
206         int ret = 221;          /* Default to successful quit */
207         char aaa[SIZ];
208
209         CtdlIPC_lock(ipc);
210         if (ipc->sock > -1) {
211                 CtdlIPC_putline(ipc, "QUIT");
212                 CtdlIPC_getline(ipc, aaa);
213                 ret = atoi(aaa);
214         }
215 #ifdef HAVE_OPENSSL
216         if (ipc->ssl)
217                 SSL_shutdown(ipc->ssl);
218         ipc->ssl = NULL;
219 #endif
220         if (ipc->sock)
221                 shutdown(ipc->sock, 2); /* Close connection; we're dead */
222         ipc->sock = -1;
223         CtdlIPC_unlock(ipc);
224         return ret;
225 }
226
227
228 /*
229  * Asks the server to log out.  Should always return 200, even if no user
230  * was logged in.  The user will not be logged in after this!
231  */
232 int CtdlIPCLogout(CtdlIPC * ipc)
233 {
234         int ret;
235         char aaa[SIZ];
236
237         CtdlIPC_lock(ipc);
238         CtdlIPC_putline(ipc, "LOUT");
239         CtdlIPC_getline(ipc, aaa);
240         ret = atoi(aaa);
241         CtdlIPC_unlock(ipc);
242         return ret;
243 }
244
245
246 /*
247  * First stage of authentication - pass the username.  Returns 300 if the
248  * username is able to log in, with the username correctly spelled in cret.
249  * Returns various 500 error codes if the user doesn't exist, etc.
250  */
251 int CtdlIPCTryLogin(CtdlIPC * ipc, const char *username, char *cret)
252 {
253         int ret;
254         char *aaa;
255
256         if (!username)
257                 return -2;
258         if (!cret)
259                 return -2;
260
261         aaa = (char *) malloc((size_t) (strlen(username) + 6));
262         if (!aaa)
263                 return -1;
264
265         sprintf(aaa, "USER %s", username);
266         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
267         free(aaa);
268         return ret;
269 }
270
271
272 /*
273  * Second stage of authentication - provide password.  The server returns
274  * 200 and several arguments in cret relating to the user's account.
275  */
276 int CtdlIPCTryPassword(CtdlIPC * ipc, const char *passwd, char *cret)
277 {
278         int ret;
279         char *aaa;
280
281         if (!passwd)
282                 return -2;
283         if (!cret)
284                 return -2;
285
286         aaa = (char *) malloc((size_t) (strlen(passwd) + 6));
287         if (!aaa)
288                 return -1;
289
290         sprintf(aaa, "PASS %s", passwd);
291         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
292         free(aaa);
293         return ret;
294 }
295
296
297 /*
298  * Second stage of authentication - provide password.  The server returns
299  * 200 and several arguments in cret relating to the user's account.
300  */
301 int CtdlIPCTryApopPassword(CtdlIPC * ipc, const char *response, char *cret)
302 {
303         int ret;
304         char *aaa;
305
306         if (!response)
307                 return -2;
308         if (!cret)
309                 return -2;
310
311         aaa = (char *) malloc((size_t) (strlen(response) + 6));
312         if (!aaa)
313                 return -1;
314
315         sprintf(aaa, "PAS2 %s", response);
316         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
317         free(aaa);
318         return ret;
319 }
320
321
322 /*
323  * Create a new user.  This returns 200 plus the same arguments as TryPassword
324  * if selfservice is nonzero, unless there was a problem creating the account.
325  * If selfservice is zero, creates a new user but does not log out the existing
326  * user - intended for use by system administrators to create accounts on
327  * behalf of other users.
328  */
329 int CtdlIPCCreateUser(CtdlIPC * ipc, const char *username, int selfservice, char *cret)
330 {
331         int ret;
332         char *aaa;
333
334         if (!username)
335                 return -2;
336         if (!cret)
337                 return -2;
338
339         aaa = (char *) malloc((size_t) (strlen(username) + 6));
340         if (!aaa)
341                 return -1;
342
343         sprintf(aaa, "%s %s", selfservice ? "NEWU" : "CREU", username);
344         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
345         free(aaa);
346         return ret;
347 }
348
349
350 /*
351  * Changes the user's password.  Returns 200 if changed, errors otherwise.
352  */
353 int CtdlIPCChangePassword(CtdlIPC * ipc, const char *passwd, char *cret)
354 {
355         int ret;
356         char *aaa;
357
358         if (!passwd)
359                 return -2;
360         if (!cret)
361                 return -2;
362
363         aaa = (char *) malloc((size_t) (strlen(passwd) + 6));
364         if (!aaa)
365                 return -1;
366
367         sprintf(aaa, "SETP %s", passwd);
368         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
369         free(aaa);
370         return ret;
371 }
372
373
374 /* LKRN */
375 /* Caller must free the march list */
376 /* Room types are defined in enum RoomList; keep these in sync! */
377 /* floor is -1 for all, or floornum */
378 int CtdlIPCKnownRooms(CtdlIPC * ipc, enum RoomList which, int floor, struct march **listing, char *cret)
379 {
380         int ret;
381         struct march *march = NULL;
382         static char *proto[] = { "LKRA", "LKRN", "LKRO", "LZRM", "LRMS", "LPRM" };
383         char aaa[SIZ];
384         char *bbb = NULL;
385         size_t bbb_len;
386
387         if (!listing)
388                 return -2;
389         if (*listing)
390                 return -2;      /* Free the listing first */
391         if (!cret)
392                 return -2;
393         /* if (which < 0 || which > 4) return -2; */
394         if (floor < -1)
395                 return -2;      /* Can't validate upper bound, sorry */
396
397         sprintf(aaa, "%s %d", proto[which], floor);
398         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, &bbb, &bbb_len, cret);
399         if (ret / 100 == 1) {
400                 struct march *mptr;
401
402                 while (bbb && strlen(bbb)) {
403                         int a;
404
405                         extract_token(aaa, bbb, 0, '\n', sizeof aaa);
406                         a = strlen(aaa);
407                         memmove(bbb, bbb + a + 1, strlen(bbb) - a);
408                         mptr = (struct march *) malloc(sizeof(struct march));
409                         if (mptr) {
410                                 mptr->next = NULL;
411                                 extract_token(mptr->march_name, aaa, 0, '|', sizeof mptr->march_name);
412                                 mptr->march_flags = (unsigned int) extract_int(aaa, 1);
413                                 mptr->march_floor = (char) extract_int(aaa, 2);
414                                 mptr->march_order = (char) extract_int(aaa, 3);
415                                 mptr->march_flags2 = (unsigned int) extract_int(aaa, 4);
416                                 mptr->march_access = (char) extract_int(aaa, 5);
417                                 if (march == NULL)
418                                         march = mptr;
419                                 else {
420                                         struct march *mptr2;
421
422                                         mptr2 = march;
423                                         while (mptr2->next != NULL)
424                                                 mptr2 = mptr2->next;
425                                         mptr2->next = mptr;
426                                 }
427                         }
428                 }
429         }
430         *listing = march;
431         if (bbb)
432                 free(bbb);
433         return ret;
434 }
435
436
437 /* GETU */
438 /* Caller must free the struct ctdluser; caller may pass an existing one */
439 int CtdlIPCGetConfig(CtdlIPC * ipc, struct ctdluser **uret, char *cret)
440 {
441         int ret;
442
443         if (!cret)
444                 return -2;
445         if (!uret)
446                 return -2;
447         if (!*uret)
448                 *uret = (struct ctdluser *) calloc(1, sizeof(struct ctdluser));
449         if (!*uret)
450                 return -1;
451
452         ret = CtdlIPCGenericCommand(ipc, "GETU", NULL, 0, NULL, NULL, cret);
453         if (ret / 100 == 2) {
454                 uret[0]->flags = extract_int(cret, 2);
455         }
456         return ret;
457 }
458
459
460 /* SETU */
461 int CtdlIPCSetConfig(CtdlIPC * ipc, struct ctdluser *uret, char *cret)
462 {
463         char aaa[48];
464
465         if (!uret)
466                 return -2;
467         if (!cret)
468                 return -2;
469
470         sprintf(aaa, "SETU 80|24|%d", uret->flags);
471         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
472 }
473
474
475 /* RENU */
476 int CtdlIPCRenameUser(CtdlIPC * ipc, char *oldname, char *newname, char *cret)
477 {
478         int ret;
479         char cmd[256];
480
481         if (!oldname)
482                 return -2;
483         if (!newname)
484                 return -2;
485         if (!cret)
486                 return -2;
487
488         snprintf(cmd, sizeof cmd, "RENU %s|%s", oldname, newname);
489         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
490         return ret;
491 }
492
493
494 /* GOTO */
495 int CtdlIPCGotoRoom(CtdlIPC * ipc, const char *room, const char *passwd, struct ctdlipcroom **rret, char *cret)
496 {
497         int ret;
498         char *aaa;
499
500         if (!cret)
501                 return -2;
502         if (!rret)
503                 return -2;
504         if (!*rret)
505                 *rret = (struct ctdlipcroom *) calloc(1, sizeof(struct ctdlipcroom));
506         if (!*rret)
507                 return -1;
508
509         if (passwd) {
510                 aaa = (char *) malloc(strlen(room) + strlen(passwd) + 7);
511                 if (!aaa) {
512                         free(*rret);
513                         return -1;
514                 }
515                 sprintf(aaa, "GOTO %s|%s", room, passwd);
516         } else {
517                 aaa = (char *) malloc(strlen(room) + 6);
518                 if (!aaa) {
519                         free(*rret);
520                         return -1;
521                 }
522                 sprintf(aaa, "GOTO %s", room);
523         }
524         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
525         if (ret / 100 == 2) {
526                 extract_token(rret[0]->RRname, cret, 0, '|', sizeof rret[0]->RRname);
527                 rret[0]->RRunread = extract_long(cret, 1);
528                 rret[0]->RRtotal = extract_long(cret, 2);
529                 rret[0]->RRinfoupdated = extract_int(cret, 3);
530                 rret[0]->RRflags = extract_int(cret, 4);
531                 rret[0]->RRhighest = extract_long(cret, 5);
532                 rret[0]->RRlastread = extract_long(cret, 6);
533                 rret[0]->RRismailbox = extract_int(cret, 7);
534                 rret[0]->RRaide = extract_int(cret, 8);
535                 rret[0]->RRnewmail = extract_long(cret, 9);
536                 rret[0]->RRfloor = extract_int(cret, 10);
537                 rret[0]->RRcurrentview = extract_int(cret, 11);
538                 rret[0]->RRdefaultview = extract_int(cret, 12);
539                 /* position 13 is a trash folder flag ... irrelevant in this client */
540                 rret[0]->RRflags2 = extract_int(cret, 14);
541         } else {
542                 free(*rret);
543                 *rret = NULL;
544         }
545         free(aaa);
546         return ret;
547 }
548
549
550 /* MSGS */
551 /* which is 0 = all, 1 = old, 2 = new, 3 = last, 4 = first, 5 = gt, 6 = lt */
552 /* whicharg is number of messages, applies to last, first, gt, lt */
553 int CtdlIPCGetMessages(CtdlIPC * ipc, enum MessageList which, int whicharg, const char *mtemplate, unsigned long **mret, char *cret)
554 {
555         int ret;
556         unsigned long count = 0;
557         static char *proto[] = { "ALL", "OLD", "NEW", "LAST", "FIRST", "GT", "LT" };
558         char aaa[33];
559         char *bbb = NULL;
560         size_t bbb_len;
561
562         if (!cret)
563                 return -2;
564         if (!mret)
565                 return -2;
566         if (*mret)
567                 return -2;
568         if (which < 0 || which > 6)
569                 return -2;
570
571         if (which <= 2)
572                 sprintf(aaa, "MSGS %s||%d", proto[which], (mtemplate) ? 1 : 0);
573         else
574                 sprintf(aaa, "MSGS %s|%d|%d", proto[which], whicharg, (mtemplate) ? 1 : 0);
575         if (mtemplate)
576                 count = strlen(mtemplate);
577         ret = CtdlIPCGenericCommand(ipc, aaa, mtemplate, count, &bbb, &bbb_len, cret);
578         if (ret / 100 != 1)
579                 return ret;
580         count = 0;
581         *mret = (unsigned long *) calloc(1, sizeof(unsigned long));
582         if (!*mret)
583                 return -1;
584         while (bbb && strlen(bbb)) {
585                 extract_token(aaa, bbb, 0, '\n', sizeof aaa);
586                 remove_token(bbb, 0, '\n');
587                 *mret = (unsigned long *) realloc(*mret, (size_t) ((count + 2) * sizeof(unsigned long)));
588                 if (*mret) {
589                         (*mret)[count++] = atol(aaa);
590                         (*mret)[count] = 0L;
591                 } else {
592                         break;
593                 }
594         }
595         if (bbb)
596                 free(bbb);
597         return ret;
598 }
599
600
601 /* MSG0, MSG2 */
602 int CtdlIPCGetSingleMessage(CtdlIPC * ipc, long msgnum, int headers, int as_mime, struct ctdlipcmessage **mret, char *cret)
603 {
604         int ret;
605         char aaa[SIZ];
606         char *bbb = NULL;
607         size_t bbb_len;
608         int multipart_hunting = 0;
609         char multipart_prefix[128];
610         char encoding[256];
611
612         if (!cret)
613                 return -1;
614         if (!mret)
615                 return -1;
616         if (!*mret)
617                 *mret = (struct ctdlipcmessage *) calloc(1, sizeof(struct ctdlipcmessage));
618         if (!*mret)
619                 return -1;
620         if (!msgnum)
621                 return -1;
622
623         strcpy(encoding, "");
624         strcpy(mret[0]->content_type, "");
625         sprintf(aaa, "MSG%d %ld|%d", as_mime, msgnum, headers);
626         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, &bbb, &bbb_len, cret);
627         if (ret / 100 == 1) {
628                 if (as_mime != 2) {
629                         strcpy(mret[0]->mime_chosen, "1");      /* Default chosen-part is "1" */
630                         while (strlen(bbb) > 4 && bbb[4] == '=') {
631                                 extract_token(aaa, bbb, 0, '\n', sizeof aaa);
632                                 remove_token(bbb, 0, '\n');
633
634                                 if (!strncasecmp(aaa, "nhdr=yes", 8))
635                                         mret[0]->nhdr = 1;
636                                 else if (!strncasecmp(aaa, "from=", 5))
637                                         safestrncpy(mret[0]->author, &aaa[5], SIZ);
638                                 else if (!strncasecmp(aaa, "type=", 5))
639                                         mret[0]->type = atoi(&aaa[5]);
640                                 else if (!strncasecmp(aaa, "msgn=", 5))
641                                         safestrncpy(mret[0]->msgid, &aaa[5], SIZ);
642                                 else if (!strncasecmp(aaa, "subj=", 5))
643                                         safestrncpy(mret[0]->subject, &aaa[5], SIZ);
644                                 else if (!strncasecmp(aaa, "rfca=", 5))
645                                         safestrncpy(mret[0]->email, &aaa[5], SIZ);
646                                 else if (!strncasecmp(aaa, "room=", 5))
647                                         safestrncpy(mret[0]->room, &aaa[5], SIZ);
648                                 else if (!strncasecmp(aaa, "rcpt=", 5))
649                                         safestrncpy(mret[0]->recipient, &aaa[5], SIZ);
650                                 else if (!strncasecmp(aaa, "wefw=", 5))
651                                         safestrncpy(mret[0]->references, &aaa[5], SIZ);
652                                 else if (!strncasecmp(aaa, "time=", 5))
653                                         mret[0]->time = atol(&aaa[5]);
654
655                                 /* Multipart/alternative prefix & suffix strings help
656                                  * us to determine which part we want to download.
657                                  */
658                                 else if (!strncasecmp(aaa, "pref=", 5)) {
659                                         extract_token(multipart_prefix, &aaa[5], 1, '|', sizeof multipart_prefix);
660                                         if (!strcasecmp(multipart_prefix, "multipart/alternative")) {
661                                                 ++multipart_hunting;
662                                         }
663                                 } else if (!strncasecmp(aaa, "suff=", 5)) {
664                                         extract_token(multipart_prefix, &aaa[5], 1, '|', sizeof multipart_prefix);
665                                         if (!strcasecmp(multipart_prefix, "multipart/alternative")) {
666                                                 ++multipart_hunting;
667                                         }
668                                 }
669
670                                 else if (!strncasecmp(aaa, "part=", 5)) {
671                                         struct parts *ptr, *chain;
672
673                                         ptr = (struct parts *) calloc(1, sizeof(struct parts));
674                                         if (ptr) {
675
676                                                 /* Fill the buffers for the caller */
677                                                 extract_token(ptr->name, &aaa[5], 0, '|', sizeof ptr->name);
678                                                 extract_token(ptr->filename, &aaa[5], 1, '|', sizeof ptr->filename);
679                                                 extract_token(ptr->number, &aaa[5], 2, '|', sizeof ptr->number);
680                                                 extract_token(ptr->disposition, &aaa[5], 3, '|', sizeof ptr->disposition);
681                                                 extract_token(ptr->mimetype, &aaa[5], 4, '|', sizeof ptr->mimetype);
682                                                 ptr->length = extract_long(&aaa[5], 5);
683                                                 if (!mret[0]->attachments)
684                                                         mret[0]->attachments = ptr;
685                                                 else {
686                                                         chain = mret[0]->attachments;
687                                                         while (chain->next)
688                                                                 chain = chain->next;
689                                                         chain->next = ptr;
690                                                 }
691
692                                                 /* Now handle multipart/alternative */
693                                                 if (multipart_hunting > 0) {
694                                                         if ((!strcasecmp(ptr->mimetype, "text/plain"))
695                                                             || (!strcasecmp(ptr->mimetype, "text/html"))) {
696                                                                 strcpy(mret[0]->mime_chosen, ptr->number);
697                                                         }
698                                                 }
699
700                                         }
701                                 }
702                         }
703                         /* Eliminate "text\n" */
704                         remove_token(bbb, 0, '\n');
705
706                         /* If doing a MIME thing, pull out the extra headers */
707                         if (as_mime == 4) {
708                                 do {
709                                         if (!strncasecmp(bbb, "Content-type:", 13)) {
710                                                 extract_token(mret[0]->content_type, bbb, 0, '\n', sizeof mret[0]->content_type);
711                                                 strcpy(mret[0]->content_type, &mret[0]->content_type[13]);
712                                                 striplt(mret[0]->content_type);
713
714                                                 /* strip out ";charset=" portion.  FIXME do something with
715                                                  * the charset (like... convert it) instead of just throwing
716                                                  * it away
717                                                  */
718                                                 if (strstr(mret[0]->content_type, ";") != NULL) {
719                                                         strcpy(strstr(mret[0]->content_type, ";"), "");
720                                                 }
721
722                                         }
723                                         if (!strncasecmp(bbb, "X-Citadel-MSG4-Partnum:", 23)) {
724                                                 extract_token(mret[0]->mime_chosen, bbb, 0, '\n', sizeof mret[0]->mime_chosen);
725                                                 strcpy(mret[0]->mime_chosen, &mret[0]->mime_chosen[23]);
726                                                 striplt(mret[0]->mime_chosen);
727                                         }
728                                         if (!strncasecmp(bbb, "Content-transfer-encoding:", 26)) {
729                                                 extract_token(encoding, bbb, 0, '\n', sizeof encoding);
730                                                 strcpy(encoding, &encoding[26]);
731                                                 striplt(encoding);
732                                         }
733                                         remove_token(bbb, 0, '\n');
734                                 } while ((bbb[0] != 0) && (bbb[0] != '\n'));
735                                 remove_token(bbb, 0, '\n');
736                         }
737
738
739                 }
740                 if (strlen(bbb)) {
741
742                         if ((!strcasecmp(encoding, "base64")) || (!strcasecmp(encoding, "quoted-printable"))) {
743                                 char *ccc = NULL;
744                                 int bytes_decoded = 0;
745                                 ccc = malloc(strlen(bbb) + 32768);
746                                 if (!strcasecmp(encoding, "base64")) {
747                                         bytes_decoded = CtdlDecodeBase64(ccc, bbb, strlen(bbb));
748                                 } else if (!strcasecmp(encoding, "quoted-printable")) {
749                                         bytes_decoded = CtdlDecodeQuotedPrintable(ccc, bbb, strlen(bbb));
750                                 }
751                                 ccc[bytes_decoded] = 0;
752                                 free(bbb);
753                                 bbb = ccc;
754                         }
755
756                         /* FIXME: Strip trailing whitespace */
757                         bbb = (char *) realloc(bbb, (size_t) (strlen(bbb) + 1));
758
759                 } else {
760                         bbb = (char *) realloc(bbb, 1);
761                         *bbb = '\0';
762                 }
763                 mret[0]->text = bbb;
764         }
765         return ret;
766 }
767
768
769 /* WHOK */
770 int CtdlIPCWhoKnowsRoom(CtdlIPC * ipc, char **listing, char *cret)
771 {
772         int ret;
773         size_t bytes;
774
775         if (!cret)
776                 return -2;
777         if (!listing)
778                 return -2;
779         if (*listing)
780                 return -2;
781
782         ret = CtdlIPCGenericCommand(ipc, "WHOK", NULL, 0, listing, &bytes, cret);
783         return ret;
784 }
785
786
787 /* INFO */
788 int CtdlIPCServerInfo(CtdlIPC * ipc, char *cret)
789 {
790         int ret;
791         size_t bytes;
792         char *listing = NULL;
793         char buf[SIZ];
794
795         if (!cret)
796                 return -2;
797
798         ret = CtdlIPCGenericCommand(ipc, "INFO", NULL, 0, &listing, &bytes, cret);
799         if (ret / 100 == 1) {
800                 int line = 0;
801
802                 while (*listing && strlen(listing)) {
803                         extract_token(buf, listing, 0, '\n', sizeof buf);
804                         remove_token(listing, 0, '\n');
805                         switch (line++) {
806                         case 0:
807                                 ipc->ServInfo.pid = atoi(buf);
808                                 break;
809                         case 1:
810                                 strcpy(ipc->ServInfo.nodename, buf);
811                                 break;
812                         case 2:
813                                 strcpy(ipc->ServInfo.humannode, buf);
814                                 break;
815                         case 3:
816                                 strcpy(ipc->ServInfo.fqdn, buf);
817                                 break;
818                         case 4:
819                                 strcpy(ipc->ServInfo.software, buf);
820                                 break;
821                         case 5:
822                                 ipc->ServInfo.rev_level = atoi(buf);
823                                 break;
824                         case 6:
825                                 strcpy(ipc->ServInfo.site_location, buf);
826                                 break;
827                         case 7:
828                                 strcpy(ipc->ServInfo.sysadm, buf);
829                                 break;
830                         case 9:
831                                 strcpy(ipc->ServInfo.moreprompt, buf);
832                                 break;
833                         case 10:
834                                 ipc->ServInfo.ok_floors = atoi(buf);
835                                 break;
836                         case 11:
837                                 ipc->ServInfo.paging_level = atoi(buf);
838                                 break;
839                         case 13:
840                                 ipc->ServInfo.supports_qnop = atoi(buf);
841                                 break;
842                         case 14:
843                                 ipc->ServInfo.supports_ldap = atoi(buf);
844                                 break;
845                         case 15:
846                                 ipc->ServInfo.newuser_disabled = atoi(buf);
847                                 break;
848                         case 16:
849                                 strcpy(ipc->ServInfo.default_cal_zone, buf);
850                                 break;
851                         case 17:
852                                 ipc->ServInfo.load_avg = atof(buf);
853                                 break;
854                         case 18:
855                                 ipc->ServInfo.worker_avg = atof(buf);
856                                 break;
857                         case 19:
858                                 ipc->ServInfo.thread_count = atoi(buf);
859                                 break;
860                         case 20:
861                                 ipc->ServInfo.has_sieve = atoi(buf);
862                                 break;
863                         case 21:
864                                 ipc->ServInfo.fulltext_enabled = atoi(buf);
865                                 break;
866                         case 22:
867                                 strcpy(ipc->ServInfo.svn_revision, buf);
868                                 break;
869                         case 24:
870                                 ipc->ServInfo.guest_logins = atoi(buf);
871                                 break;
872                         }
873                 }
874
875         }
876         if (listing)
877                 free(listing);
878         return ret;
879 }
880
881
882 /* RDIR */
883 int CtdlIPCReadDirectory(CtdlIPC * ipc, char **listing, char *cret)
884 {
885         int ret;
886         size_t bytes;
887
888         if (!cret)
889                 return -2;
890         if (!listing)
891                 return -2;
892         if (*listing)
893                 return -2;
894
895         ret = CtdlIPCGenericCommand(ipc, "RDIR", NULL, 0, listing, &bytes, cret);
896         return ret;
897 }
898
899
900 /*
901  * Set last-read pointer in this room to msgnum, or 0 for HIGHEST.
902  */
903 int CtdlIPCSetLastRead(CtdlIPC * ipc, long msgnum, char *cret)
904 {
905         int ret;
906         char aaa[64];
907
908         if (!cret)
909                 return -2;
910
911         if (msgnum) {
912                 sprintf(aaa, "SLRP %ld", msgnum);
913         } else {
914                 sprintf(aaa, "SLRP HIGHEST");
915         }
916         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
917         return ret;
918 }
919
920
921 /* INVT */
922 int CtdlIPCInviteUserToRoom(CtdlIPC * ipc, const char *username, char *cret)
923 {
924         int ret;
925         char *aaa;
926
927         if (!cret)
928                 return -2;
929         if (!username)
930                 return -2;
931
932         aaa = (char *) malloc(strlen(username) + 6);
933         if (!aaa)
934                 return -1;
935
936         sprintf(aaa, "INVT %s", username);
937         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
938         free(aaa);
939         return ret;
940 }
941
942
943 /* KICK */
944 int CtdlIPCKickoutUserFromRoom(CtdlIPC * ipc, const char *username, char *cret)
945 {
946         int ret;
947         char *aaa;
948
949         if (!cret)
950                 return -1;
951         if (!username)
952                 return -1;
953
954         aaa = (char *) malloc(strlen(username) + 6);
955
956         sprintf(aaa, "KICK %s", username);
957         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
958         free(aaa);
959         return ret;
960 }
961
962
963 /* GETR */
964 int CtdlIPCGetRoomAttributes(CtdlIPC * ipc, struct ctdlroom **qret, char *cret)
965 {
966         int ret;
967
968         if (!cret)
969                 return -2;
970         if (!qret)
971                 return -2;
972         if (!*qret)
973                 *qret = (struct ctdlroom *) calloc(1, sizeof(struct ctdlroom));
974         if (!*qret)
975                 return -1;
976
977         ret = CtdlIPCGenericCommand(ipc, "GETR", NULL, 0, NULL, NULL, cret);
978         if (ret / 100 == 2) {
979                 extract_token(qret[0]->QRname, cret, 0, '|', sizeof qret[0]->QRname);
980                 extract_token(qret[0]->QRpasswd, cret, 1, '|', sizeof qret[0]->QRpasswd);
981                 extract_token(qret[0]->QRdirname, cret, 2, '|', sizeof qret[0]->QRdirname);
982                 qret[0]->QRflags = extract_int(cret, 3);
983                 qret[0]->QRfloor = extract_int(cret, 4);
984                 qret[0]->QRorder = extract_int(cret, 5);
985                 qret[0]->QRdefaultview = extract_int(cret, 6);
986                 qret[0]->QRflags2 = extract_int(cret, 7);
987         }
988         return ret;
989 }
990
991
992 /* SETR */
993 /* set forget to kick all users out of room */
994 int CtdlIPCSetRoomAttributes(CtdlIPC * ipc, int forget, struct ctdlroom *qret, char *cret)
995 {
996         int ret;
997         char *aaa;
998
999         if (!cret)
1000                 return -2;
1001         if (!qret)
1002                 return -2;
1003
1004         aaa = (char *) malloc(strlen(qret->QRname) + strlen(qret->QRpasswd) + strlen(qret->QRdirname) + 64);
1005         if (!aaa)
1006                 return -1;
1007
1008         sprintf(aaa, "SETR %s|%s|%s|%d|%d|%d|%d|%d|%d",
1009                 qret->QRname, qret->QRpasswd, qret->QRdirname,
1010                 qret->QRflags, forget, qret->QRfloor, qret->QRorder, qret->QRdefaultview, qret->QRflags2);
1011         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1012         free(aaa);
1013         return ret;
1014 }
1015
1016
1017 /* GETA */
1018 int CtdlIPCGetRoomAide(CtdlIPC * ipc, char *cret)
1019 {
1020         if (!cret)
1021                 return -1;
1022
1023         return CtdlIPCGenericCommand(ipc, "GETA", NULL, 0, NULL, NULL, cret);
1024 }
1025
1026
1027 /* SETA */
1028 int CtdlIPCSetRoomAide(CtdlIPC * ipc, const char *username, char *cret)
1029 {
1030         int ret;
1031         char *aaa;
1032
1033         if (!cret)
1034                 return -2;
1035         if (!username)
1036                 return -2;
1037
1038         aaa = (char *) malloc(strlen(username) + 6);
1039         if (!aaa)
1040                 return -1;
1041
1042         sprintf(aaa, "SETA %s", username);
1043         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1044         free(aaa);
1045         return ret;
1046 }
1047
1048
1049 /* ENT0 */
1050 int CtdlIPCPostMessage(CtdlIPC * ipc, int flag, int *subject_required, struct ctdlipcmessage *mr, char *cret)
1051 {
1052         int ret;
1053         char cmd[SIZ];
1054         char *ptr;
1055
1056         if (!cret)
1057                 return -2;
1058         if (!mr)
1059                 return -2;
1060
1061         if (mr->references) {
1062                 for (ptr = mr->references; *ptr != 0; ++ptr) {
1063                         if (*ptr == '|')
1064                                 *ptr = '!';
1065                 }
1066         }
1067
1068         snprintf(cmd, sizeof cmd,
1069                  "ENT0 %d|%s|%d|%d|%s|%s||||||%s|", flag, mr->recipient,
1070                  mr->anonymous, mr->type, mr->subject, mr->author, mr->references);
1071         ret = CtdlIPCGenericCommand(ipc, cmd, mr->text, strlen(mr->text), NULL, NULL, cret);
1072         if ((flag == 0) && (subject_required != NULL)) {
1073                 /* Is the server strongly recommending that the user enter a message subject? */
1074                 if ((cret[3] != '\0') && (cret[4] != '\0')) {
1075                         *subject_required = extract_int(&cret[4], 1);
1076                 }
1077
1078
1079         }
1080         return ret;
1081 }
1082
1083
1084 /* RINF */
1085 int CtdlIPCRoomInfo(CtdlIPC * ipc, char **iret, char *cret)
1086 {
1087         size_t bytes;
1088
1089         if (!cret)
1090                 return -2;
1091         if (!iret)
1092                 return -2;
1093         if (*iret)
1094                 return -2;
1095
1096         return CtdlIPCGenericCommand(ipc, "RINF", NULL, 0, iret, &bytes, cret);
1097 }
1098
1099
1100 /* DELE */
1101 int CtdlIPCDeleteMessage(CtdlIPC * ipc, long msgnum, char *cret)
1102 {
1103         char aaa[64];
1104
1105         if (!cret)
1106                 return -2;
1107         if (!msgnum)
1108                 return -2;
1109
1110         sprintf(aaa, "DELE %ld", msgnum);
1111         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1112 }
1113
1114
1115 /* MOVE */
1116 int CtdlIPCMoveMessage(CtdlIPC * ipc, int copy, long msgnum, const char *destroom, char *cret)
1117 {
1118         int ret;
1119         char *aaa;
1120
1121         if (!cret)
1122                 return -2;
1123         if (!destroom)
1124                 return -2;
1125         if (!msgnum)
1126                 return -2;
1127
1128         aaa = (char *) malloc(strlen(destroom) + 28);
1129         if (!aaa)
1130                 return -1;
1131
1132         sprintf(aaa, "MOVE %ld|%s|%d", msgnum, destroom, copy);
1133         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1134         free(aaa);
1135         return ret;
1136 }
1137
1138
1139 /* KILL */
1140 int CtdlIPCDeleteRoom(CtdlIPC * ipc, int for_real, char *cret)
1141 {
1142         char aaa[64];
1143
1144         if (!cret)
1145                 return -2;
1146
1147         sprintf(aaa, "KILL %d", for_real);
1148         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1149 }
1150
1151
1152 /* CRE8 */
1153 int CtdlIPCCreateRoom(CtdlIPC * ipc, int for_real, const char *roomname, int type, const char *password, int floor, char *cret)
1154 {
1155         int ret;
1156         char *aaa;
1157
1158         if (!cret)
1159                 return -2;
1160         if (!roomname)
1161                 return -2;
1162
1163         if (password) {
1164                 aaa = (char *) malloc(strlen(roomname) + strlen(password) + 40);
1165                 if (!aaa)
1166                         return -1;
1167                 sprintf(aaa, "CRE8 %d|%s|%d|%s|%d", for_real, roomname, type, password, floor);
1168         } else {
1169                 aaa = (char *) malloc(strlen(roomname) + 40);
1170                 if (!aaa)
1171                         return -1;
1172                 sprintf(aaa, "CRE8 %d|%s|%d||%d", for_real, roomname, type, floor);
1173         }
1174         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1175         free(aaa);
1176         return ret;
1177 }
1178
1179
1180 /* FORG */
1181 int CtdlIPCForgetRoom(CtdlIPC * ipc, char *cret)
1182 {
1183         if (!cret)
1184                 return -2;
1185
1186         return CtdlIPCGenericCommand(ipc, "FORG", NULL, 0, NULL, NULL, cret);
1187 }
1188
1189
1190 /* MESG */
1191 int CtdlIPCSystemMessage(CtdlIPC * ipc, const char *message, char **mret, char *cret)
1192 {
1193         int ret;
1194         char *aaa;
1195         size_t bytes;
1196
1197         if (!cret)
1198                 return -2;
1199         if (!mret)
1200                 return -2;
1201         if (*mret)
1202                 return -2;
1203         if (!message)
1204                 return -2;
1205
1206         aaa = (char *) malloc(strlen(message) + 6);
1207         if (!aaa)
1208                 return -1;
1209
1210         sprintf(aaa, "MESG %s", message);
1211         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, mret, &bytes, cret);
1212         free(aaa);
1213         return ret;
1214 }
1215
1216
1217 /* GNUR */
1218 int CtdlIPCNextUnvalidatedUser(CtdlIPC * ipc, char *cret)
1219 {
1220         if (!cret)
1221                 return -2;
1222
1223         return CtdlIPCGenericCommand(ipc, "GNUR", NULL, 0, NULL, NULL, cret);
1224 }
1225
1226
1227 /* GREG */
1228 int CtdlIPCGetUserRegistration(CtdlIPC * ipc, const char *username, char **rret, char *cret)
1229 {
1230         int ret;
1231         char *aaa;
1232         size_t bytes;
1233
1234         if (!cret)
1235                 return -2;
1236         if (!rret)
1237                 return -2;
1238         if (*rret)
1239                 return -2;
1240
1241         if (username)
1242                 aaa = (char *) malloc(strlen(username) + 6);
1243         else
1244                 aaa = (char *) malloc(12);
1245         if (!aaa)
1246                 return -1;
1247
1248         if (username)
1249                 sprintf(aaa, "GREG %s", username);
1250         else
1251                 sprintf(aaa, "GREG _SELF_");
1252         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, rret, &bytes, cret);
1253         free(aaa);
1254         return ret;
1255 }
1256
1257
1258 /* VALI */
1259 int CtdlIPCValidateUser(CtdlIPC * ipc, const char *username, int axlevel, char *cret)
1260 {
1261         int ret;
1262         char *aaa;
1263
1264         if (!cret)
1265                 return -2;
1266         if (!username)
1267                 return -2;
1268         if (axlevel < AxDeleted || axlevel > AxAideU)
1269                 return -2;
1270
1271         aaa = (char *) malloc(strlen(username) + 17);
1272         if (!aaa)
1273                 return -1;
1274
1275         sprintf(aaa, "VALI %s|%d", username, axlevel);
1276         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1277         free(aaa);
1278         return ret;
1279 }
1280
1281
1282 /* EINF */
1283 int CtdlIPCSetRoomInfo(CtdlIPC * ipc, int for_real, const char *info, char *cret)
1284 {
1285         char aaa[64];
1286
1287         if (!cret)
1288                 return -1;
1289         if (!info)
1290                 return -1;
1291
1292         sprintf(aaa, "EINF %d", for_real);
1293         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1294 }
1295
1296
1297 /* LIST */
1298 int CtdlIPCUserListing(CtdlIPC * ipc, char *searchstring, char **listing, char *cret)
1299 {
1300         size_t bytes;
1301         char *cmd;
1302         int ret;
1303
1304         if (!cret)
1305                 return -1;
1306         if (!listing)
1307                 return -1;
1308         if (*listing)
1309                 return -1;
1310         if (!searchstring)
1311                 return -1;
1312
1313         cmd = malloc(strlen(searchstring) + 10);
1314         sprintf(cmd, "LIST %s", searchstring);
1315
1316         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, listing, &bytes, cret);
1317         free(cmd);
1318         return (ret);
1319 }
1320
1321
1322 /* REGI */
1323 int CtdlIPCSetRegistration(CtdlIPC * ipc, const char *info, char *cret)
1324 {
1325         if (!cret)
1326                 return -1;
1327         if (!info)
1328                 return -1;
1329
1330         return CtdlIPCGenericCommand(ipc, "REGI", info, strlen(info), NULL, NULL, cret);
1331 }
1332
1333
1334 /* CHEK */
1335 int CtdlIPCMiscCheck(CtdlIPC * ipc, struct ctdlipcmisc *chek, char *cret)
1336 {
1337         int ret;
1338
1339         if (!cret)
1340                 return -1;
1341         if (!chek)
1342                 return -1;
1343
1344         ret = CtdlIPCGenericCommand(ipc, "CHEK", NULL, 0, NULL, NULL, cret);
1345         if (ret / 100 == 2) {
1346                 chek->newmail = extract_long(cret, 0);
1347                 chek->needregis = extract_int(cret, 1);
1348                 chek->needvalid = extract_int(cret, 2);
1349         }
1350         return ret;
1351 }
1352
1353
1354 /* DELF */
1355 int CtdlIPCDeleteFile(CtdlIPC * ipc, const char *filename, char *cret)
1356 {
1357         int ret;
1358         char *aaa;
1359
1360         if (!cret)
1361                 return -2;
1362         if (!filename)
1363                 return -2;
1364
1365         aaa = (char *) malloc(strlen(filename) + 6);
1366         if (!aaa)
1367                 return -1;
1368
1369         sprintf(aaa, "DELF %s", filename);
1370         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1371         free(aaa);
1372         return ret;
1373 }
1374
1375
1376 /* MOVF */
1377 int CtdlIPCMoveFile(CtdlIPC * ipc, const char *filename, const char *destroom, char *cret)
1378 {
1379         int ret;
1380         char *aaa;
1381
1382         if (!cret)
1383                 return -2;
1384         if (!filename)
1385                 return -2;
1386         if (!destroom)
1387                 return -2;
1388
1389         aaa = (char *) malloc(strlen(filename) + strlen(destroom) + 7);
1390         if (!aaa)
1391                 return -1;
1392
1393         sprintf(aaa, "MOVF %s|%s", filename, destroom);
1394         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1395         free(aaa);
1396         return ret;
1397 }
1398
1399
1400 /* RWHO */
1401 int CtdlIPCOnlineUsers(CtdlIPC * ipc, char **listing, time_t * stamp, char *cret)
1402 {
1403         int ret;
1404         size_t bytes;
1405
1406         if (!cret)
1407                 return -1;
1408         if (!listing)
1409                 return -1;
1410         if (*listing)
1411                 return -1;
1412
1413         *stamp = CtdlIPCServerTime(ipc, cret);
1414         if (!*stamp)
1415                 *stamp = time(NULL);
1416         ret = CtdlIPCGenericCommand(ipc, "RWHO", NULL, 0, listing, &bytes, cret);
1417         return ret;
1418 }
1419
1420
1421 /* OPEN */
1422 int CtdlIPCFileDownload(CtdlIPC * ipc, const char *filename, void **buf, size_t resume, void (*progress_gauge_callback)
1423                          (CtdlIPC *, unsigned long, unsigned long), char *cret)
1424 {
1425         int ret;
1426         size_t bytes;
1427         time_t last_mod;
1428         char mimetype[SIZ];
1429         char *aaa;
1430
1431         if (!cret)
1432                 return -2;
1433         if (!filename)
1434                 return -2;
1435         if (!buf)
1436                 return -2;
1437         if (*buf)
1438                 return -2;
1439         if (ipc->downloading)
1440                 return -2;
1441
1442         aaa = (char *) malloc(strlen(filename) + 6);
1443         if (!aaa)
1444                 return -1;
1445
1446         sprintf(aaa, "OPEN %s", filename);
1447         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1448         free(aaa);
1449         if (ret / 100 == 2) {
1450                 ipc->downloading = 1;
1451                 bytes = extract_long(cret, 0);
1452                 last_mod = extract_int(cret, 1);
1453                 extract_token(mimetype, cret, 2, '|', sizeof mimetype);
1454
1455                 ret = CtdlIPCReadDownload(ipc, buf, bytes, resume, progress_gauge_callback, cret);
1456                 /*
1457                    ret = CtdlIPCHighSpeedReadDownload(ipc, buf, bytes, resume,
1458                    progress_gauge_callback, cret);
1459                  */
1460
1461                 ret = CtdlIPCEndDownload(ipc, cret);
1462                 if (ret / 100 == 2)
1463                         sprintf(cret, "%d|%ld|%s|%s", (int) bytes, last_mod, filename, mimetype);
1464         }
1465         return ret;
1466 }
1467
1468
1469 /* OPNA */
1470 int CtdlIPCAttachmentDownload(CtdlIPC * ipc, long msgnum, const char *part, void **buf, void (*progress_gauge_callback)
1471                                (CtdlIPC *, unsigned long, unsigned long), char *cret)
1472 {
1473         int ret;
1474         size_t bytes;
1475         time_t last_mod;
1476         char filename[SIZ];
1477         char mimetype[SIZ];
1478         char aaa[SIZ];
1479
1480         if (!cret)
1481                 return -2;
1482         if (!buf)
1483                 return -2;
1484         if (*buf)
1485                 return -2;
1486         if (!part)
1487                 return -2;
1488         if (!msgnum)
1489                 return -2;
1490         if (ipc->downloading)
1491                 return -2;
1492
1493         sprintf(aaa, "OPNA %ld|%s", msgnum, part);
1494         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1495         if (ret / 100 == 2) {
1496                 ipc->downloading = 1;
1497                 bytes = extract_long(cret, 0);
1498                 last_mod = extract_int(cret, 1);
1499                 extract_token(filename, cret, 2, '|', sizeof filename);
1500                 extract_token(mimetype, cret, 3, '|', sizeof mimetype);
1501                 /* ret = CtdlIPCReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret); */
1502                 ret = CtdlIPCHighSpeedReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret);
1503                 ret = CtdlIPCEndDownload(ipc, cret);
1504                 if (ret / 100 == 2)
1505                         sprintf(cret, "%d|%ld|%s|%s", (int) bytes, last_mod, filename, mimetype);
1506         }
1507         return ret;
1508 }
1509
1510
1511 /* OIMG */
1512 int CtdlIPCImageDownload(CtdlIPC * ipc, const char *filename, void **buf, void (*progress_gauge_callback)
1513                           (CtdlIPC *, unsigned long, unsigned long), char *cret)
1514 {
1515         int ret;
1516         size_t bytes;
1517         time_t last_mod;
1518         char mimetype[SIZ];
1519         char *aaa;
1520
1521         if (!cret)
1522                 return -1;
1523         if (!buf)
1524                 return -1;
1525         if (*buf)
1526                 return -1;
1527         if (!filename)
1528                 return -1;
1529         if (ipc->downloading)
1530                 return -1;
1531
1532         aaa = (char *) malloc(strlen(filename) + 6);
1533         if (!aaa)
1534                 return -1;
1535
1536         sprintf(aaa, "OIMG %s", filename);
1537         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1538         free(aaa);
1539         if (ret / 100 == 2) {
1540                 ipc->downloading = 1;
1541                 bytes = extract_long(cret, 0);
1542                 last_mod = extract_int(cret, 1);
1543                 extract_token(mimetype, cret, 2, '|', sizeof mimetype);
1544 /*              ret = CtdlIPCReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret); */
1545                 ret = CtdlIPCHighSpeedReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret);
1546                 ret = CtdlIPCEndDownload(ipc, cret);
1547                 if (ret / 100 == 2)
1548                         sprintf(cret, "%d|%ld|%s|%s", (int) bytes, last_mod, filename, mimetype);
1549         }
1550         return ret;
1551 }
1552
1553
1554 /* UOPN */
1555 int CtdlIPCFileUpload(CtdlIPC * ipc, const char *save_as, const char *comment, const char *path, void (*progress_gauge_callback)
1556                        (CtdlIPC *, unsigned long, unsigned long), char *cret)
1557 {
1558         int ret;
1559         char *aaa;
1560         FILE *uploadFP;
1561         char MimeTestBuf[64];
1562         const char *MimeType;
1563         long len;
1564
1565         if (!cret)
1566                 return -1;
1567         if (!save_as)
1568                 return -1;
1569         if (!comment)
1570                 return -1;
1571         if (!path)
1572                 return -1;
1573         if (!*path)
1574                 return -1;
1575         if (ipc->uploading)
1576                 return -1;
1577
1578         uploadFP = fopen(path, "r");
1579         if (!uploadFP)
1580                 return -2;
1581
1582         len = fread(&MimeTestBuf[0], 1, 64, uploadFP);
1583         rewind(uploadFP);
1584         if (len < 0)
1585                 return -3;
1586
1587         MimeType = GuessMimeType(&MimeTestBuf[0], len);
1588         aaa = (char *) malloc(strlen(save_as) + strlen(MimeType) + strlen(comment) + 7);
1589         if (!aaa)
1590                 return -1;
1591
1592         sprintf(aaa, "UOPN %s|%s|%s", save_as, MimeType, comment);
1593         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1594         free(aaa);
1595         if (ret / 100 == 2) {
1596                 ipc->uploading = 1;
1597                 ret = CtdlIPCWriteUpload(ipc, uploadFP, progress_gauge_callback, cret);
1598                 ret = CtdlIPCEndUpload(ipc, (ret == -2 ? 1 : 0), cret);
1599                 ipc->uploading = 0;
1600         }
1601         return ret;
1602 }
1603
1604
1605 /* UIMG */
1606 int CtdlIPCImageUpload(CtdlIPC * ipc, int for_real, const char *path, const char *save_as, void (*progress_gauge_callback)
1607                         (CtdlIPC *, unsigned long, unsigned long), char *cret)
1608 {
1609         int ret;
1610         FILE *uploadFP;
1611         char *aaa;
1612         char MimeTestBuf[64];
1613         const char *MimeType;
1614         long len;
1615
1616         if (!cret)
1617                 return -1;
1618         if (!save_as)
1619                 return -1;
1620         if (!path && for_real)
1621                 return -1;
1622         if (!*path && for_real)
1623                 return -1;
1624         if (ipc->uploading)
1625                 return -1;
1626
1627         aaa = (char *) malloc(strlen(save_as) + 17);
1628         if (!aaa)
1629                 return -1;
1630
1631         uploadFP = fopen(path, "r");
1632         if (!uploadFP)
1633                 return -2;
1634
1635         len = fread(&MimeTestBuf[0], 1, 64, uploadFP);
1636         rewind(uploadFP);
1637         if (len < 0)
1638                 return -3;
1639         MimeType = GuessMimeType(&MimeTestBuf[0], 64);
1640
1641         sprintf(aaa, "UIMG %d|%s|%s", for_real, MimeType, save_as);
1642         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1643         free(aaa);
1644         if (ret / 100 == 2 && for_real) {
1645                 ipc->uploading = 1;
1646                 ret = CtdlIPCWriteUpload(ipc, uploadFP, progress_gauge_callback, cret);
1647                 ret = CtdlIPCEndUpload(ipc, (ret == -2 ? 1 : 0), cret);
1648                 ipc->uploading = 0;
1649         }
1650         return ret;
1651 }
1652
1653
1654 /* QUSR */
1655 int CtdlIPCQueryUsername(CtdlIPC * ipc, const char *username, char *cret)
1656 {
1657         int ret;
1658         char *aaa;
1659
1660         if (!cret)
1661                 return -2;
1662         if (!username)
1663                 return -2;
1664
1665         aaa = (char *) malloc(strlen(username) + 6);
1666         if (!aaa)
1667                 return -1;
1668
1669         sprintf(aaa, "QUSR %s", username);
1670         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1671         free(aaa);
1672         return ret;
1673 }
1674
1675
1676 /* LFLR */
1677 int CtdlIPCFloorListing(CtdlIPC * ipc, char **listing, char *cret)
1678 {
1679         size_t bytes;
1680
1681         if (!cret)
1682                 return -2;
1683         if (!listing)
1684                 return -2;
1685         if (*listing)
1686                 return -2;
1687
1688         return CtdlIPCGenericCommand(ipc, "LFLR", NULL, 0, listing, &bytes, cret);
1689 }
1690
1691
1692 /* CFLR */
1693 int CtdlIPCCreateFloor(CtdlIPC * ipc, int for_real, const char *name, char *cret)
1694 {
1695         int ret;
1696         char aaa[SIZ];
1697
1698         if (!cret)
1699                 return -2;
1700         if (!name)
1701                 return -2;
1702
1703         sprintf(aaa, "CFLR %s|%d", name, for_real);
1704         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1705         return ret;
1706 }
1707
1708
1709 /* KFLR */
1710 int CtdlIPCDeleteFloor(CtdlIPC * ipc, int for_real, int floornum, char *cret)
1711 {
1712         char aaa[SIZ];
1713
1714         if (!cret)
1715                 return -1;
1716         if (floornum < 0)
1717                 return -1;
1718
1719         sprintf(aaa, "KFLR %d|%d", floornum, for_real);
1720         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1721 }
1722
1723
1724 /* EFLR */
1725 int CtdlIPCEditFloor(CtdlIPC * ipc, int floornum, const char *floorname, char *cret)
1726 {
1727         int ret;
1728         char aaa[SIZ];
1729
1730         if (!cret)
1731                 return -2;
1732         if (!floorname)
1733                 return -2;
1734         if (floornum < 0)
1735                 return -2;
1736
1737         sprintf(aaa, "EFLR %d|%s", floornum, floorname);
1738         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1739         return ret;
1740 }
1741
1742
1743 /*
1744  * IDEN 
1745  *
1746  * You only need to fill out hostname, the defaults will be used if any of the
1747  * other fields are not set properly.
1748  */
1749 int CtdlIPCIdentifySoftware(CtdlIPC * ipc, int developerid, int clientid,
1750                             int revision, const char *software_name, const char *hostname, char *cret)
1751 {
1752         int ret;
1753         char *aaa;
1754
1755         if (developerid < 0 || clientid < 0 || revision < 0 || !software_name) {
1756                 developerid = 8;
1757                 clientid = 0;
1758                 revision = CLIENT_VERSION - 600;
1759                 software_name = "Citadel (libcitadel)";
1760         }
1761         if (!hostname)
1762                 return -2;
1763
1764         aaa = (char *) malloc(strlen(software_name) + strlen(hostname) + 29);
1765         if (!aaa)
1766                 return -1;
1767
1768         sprintf(aaa, "IDEN %d|%d|%d|%s|%s", developerid, clientid, revision, software_name, hostname);
1769         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1770         free(aaa);
1771         return ret;
1772 }
1773
1774
1775 /* SEXP */
1776 int CtdlIPCSendInstantMessage(CtdlIPC * ipc, const char *username, const char *text, char *cret)
1777 {
1778         int ret;
1779         char *aaa;
1780
1781         if (!cret)
1782                 return -2;
1783         if (!username)
1784                 return -2;
1785
1786         aaa = (char *) malloc(strlen(username) + 8);
1787         if (!aaa)
1788                 return -1;
1789
1790         if (text) {
1791                 sprintf(aaa, "SEXP %s|-", username);
1792                 ret = CtdlIPCGenericCommand(ipc, aaa, text, strlen(text), NULL, NULL, cret);
1793         } else {
1794                 sprintf(aaa, "SEXP %s||", username);
1795                 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1796         }
1797         free(aaa);
1798         return ret;
1799 }
1800
1801
1802 /* GEXP */
1803 int CtdlIPCGetInstantMessage(CtdlIPC * ipc, char **listing, char *cret)
1804 {
1805         size_t bytes;
1806
1807         if (!cret)
1808                 return -2;
1809         if (!listing)
1810                 return -2;
1811         if (*listing)
1812                 return -2;
1813
1814         return CtdlIPCGenericCommand(ipc, "GEXP", NULL, 0, listing, &bytes, cret);
1815 }
1816
1817
1818 /* DEXP */
1819 /* mode is 0 = enable, 1 = disable, 2 = status */
1820 int CtdlIPCEnableInstantMessageReceipt(CtdlIPC * ipc, int mode, char *cret)
1821 {
1822         char aaa[64];
1823
1824         if (!cret)
1825                 return -2;
1826
1827         sprintf(aaa, "DEXP %d", mode);
1828         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1829 }
1830
1831
1832 /* EBIO */
1833 int CtdlIPCSetBio(CtdlIPC * ipc, char *bio, char *cret)
1834 {
1835         if (!cret)
1836                 return -2;
1837         if (!bio)
1838                 return -2;
1839
1840         return CtdlIPCGenericCommand(ipc, "EBIO", bio, strlen(bio), NULL, NULL, cret);
1841 }
1842
1843
1844 /* RBIO */
1845 int CtdlIPCGetBio(CtdlIPC * ipc, const char *username, char **listing, char *cret)
1846 {
1847         int ret;
1848         size_t bytes;
1849         char *aaa;
1850
1851         if (!cret)
1852                 return -2;
1853         if (!username)
1854                 return -2;
1855         if (!listing)
1856                 return -2;
1857         if (*listing)
1858                 return -2;
1859
1860         aaa = (char *) malloc(strlen(username) + 6);
1861         if (!aaa)
1862                 return -1;
1863
1864         sprintf(aaa, "RBIO %s", username);
1865         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, listing, &bytes, cret);
1866         free(aaa);
1867         return ret;
1868 }
1869
1870
1871 /* LBIO */
1872 int CtdlIPCListUsersWithBios(CtdlIPC * ipc, char **listing, char *cret)
1873 {
1874         size_t bytes;
1875
1876         if (!cret)
1877                 return -2;
1878         if (!listing)
1879                 return -2;
1880         if (*listing)
1881                 return -2;
1882
1883         return CtdlIPCGenericCommand(ipc, "LBIO", NULL, 0, listing, &bytes, cret);
1884 }
1885
1886
1887 /* STEL */
1888 int CtdlIPCStealthMode(CtdlIPC * ipc, int mode, char *cret)
1889 {
1890         char aaa[64];
1891
1892         if (!cret)
1893                 return -1;
1894
1895         sprintf(aaa, "STEL %d", mode ? 1 : 0);
1896         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1897 }
1898
1899
1900 /* TERM */
1901 int CtdlIPCTerminateSession(CtdlIPC * ipc, int sid, char *cret)
1902 {
1903         char aaa[64];
1904
1905         if (!cret)
1906                 return -1;
1907
1908         sprintf(aaa, "TERM %d", sid);
1909         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1910 }
1911
1912
1913 /* DOWN */
1914 int CtdlIPCTerminateServerNow(CtdlIPC * ipc, char *cret)
1915 {
1916         if (!cret)
1917                 return -1;
1918
1919         return CtdlIPCGenericCommand(ipc, "DOWN", NULL, 0, NULL, NULL, cret);
1920 }
1921
1922
1923 /* SCDN */
1924 int CtdlIPCTerminateServerScheduled(CtdlIPC * ipc, int mode, char *cret)
1925 {
1926         char aaa[16];
1927
1928         if (!cret)
1929                 return -1;
1930
1931         sprintf(aaa, "SCDN %d", mode ? 1 : 0);
1932         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1933 }
1934
1935
1936 /* EMSG */
1937 int CtdlIPCEnterSystemMessage(CtdlIPC * ipc, const char *filename, const char *text, char *cret)
1938 {
1939         int ret;
1940         char *aaa;
1941
1942         if (!cret)
1943                 return -2;
1944         if (!text)
1945                 return -2;
1946         if (!filename)
1947                 return -2;
1948
1949         aaa = (char *) malloc(strlen(filename) + 6);
1950         if (!aaa)
1951                 return -1;
1952
1953         sprintf(aaa, "EMSG %s", filename);
1954         ret = CtdlIPCGenericCommand(ipc, aaa, text, strlen(text), NULL, NULL, cret);
1955         free(aaa);
1956         return ret;
1957 }
1958
1959
1960 /* UCHG */
1961 int CtdlIPCChangeUsername(CtdlIPC * ipc, const char *username, char *cret)
1962 {
1963         int ret;
1964         char *aaa;
1965
1966         if (!cret)
1967                 return -2;
1968         if (!username)
1969                 return -2;
1970
1971         aaa = (char *) malloc(strlen(username) + 6);
1972         if (!aaa)
1973                 return -1;
1974
1975         sprintf(aaa, "UCHG %s", username);
1976         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1977         free(aaa);
1978         return ret;
1979 }
1980
1981
1982 /* TIME */
1983 /* This function returns the actual server time reported, or 0 if error */
1984 time_t CtdlIPCServerTime(CtdlIPC * ipc, char *cret)
1985 {
1986         time_t tret;
1987         int ret;
1988
1989         ret = CtdlIPCGenericCommand(ipc, "TIME", NULL, 0, NULL, NULL, cret);
1990         if (ret / 100 == 2) {
1991                 tret = extract_long(cret, 0);
1992         } else {
1993                 tret = 0L;
1994         }
1995         return tret;
1996 }
1997
1998
1999 /* AGUP */
2000 int CtdlIPCAideGetUserParameters(CtdlIPC * ipc, const char *who, struct ctdluser **uret, char *cret)
2001 {
2002         int ret;
2003         char aaa[SIZ];
2004
2005         if (!cret)
2006                 return -2;
2007         if (!uret)
2008                 return -2;
2009         if (!*uret)
2010                 *uret = (struct ctdluser *) calloc(1, sizeof(struct ctdluser));
2011         if (!*uret)
2012                 return -1;
2013
2014         sprintf(aaa, "AGUP %s", who);
2015         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2016
2017         if (ret / 100 == 2) {
2018                 extract_token(uret[0]->fullname, cret, 0, '|', sizeof uret[0]->fullname);
2019                 extract_token(uret[0]->password, cret, 1, '|', sizeof uret[0]->password);
2020                 uret[0]->flags = extract_int(cret, 2);
2021                 uret[0]->timescalled = extract_long(cret, 3);
2022                 uret[0]->posted = extract_long(cret, 4);
2023                 uret[0]->axlevel = extract_int(cret, 5);
2024                 uret[0]->usernum = extract_long(cret, 6);
2025                 uret[0]->lastcall = extract_long(cret, 7);
2026                 uret[0]->USuserpurge = extract_int(cret, 8);
2027         }
2028         return ret;
2029 }
2030
2031
2032 /* ASUP */
2033 int CtdlIPCAideSetUserParameters(CtdlIPC * ipc, const struct ctdluser *uret, char *cret)
2034 {
2035         int ret;
2036         char *aaa;
2037
2038         if (!cret)
2039                 return -2;
2040         if (!uret)
2041                 return -2;
2042
2043         aaa = (char *) malloc(strlen(uret->fullname) + strlen(uret->password) + 84);
2044         if (!aaa)
2045                 return -1;
2046
2047         sprintf(aaa, "ASUP %s|%s|%d|%ld|%ld|%d|%ld|%ld|%d",
2048                 uret->fullname, uret->password, uret->flags, uret->timescalled,
2049                 uret->posted, uret->axlevel, uret->usernum, uret->lastcall, uret->USuserpurge);
2050         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2051         free(aaa);
2052         return ret;
2053 }
2054
2055
2056 /* AGEA */
2057 int CtdlIPCAideGetEmailAddresses(CtdlIPC * ipc, const char *who, char *target_buf, char *cret)
2058 {
2059         int ret;
2060         char aaa[SIZ];
2061         char *emailaddrs = NULL;
2062         size_t emailaddrs_len = 0;
2063
2064         sprintf(aaa, "AGEA %s", who);
2065         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, &emailaddrs, &emailaddrs_len, cret);
2066
2067         if (ret / 100 == 1) {
2068                 strcpy(target_buf, emailaddrs);
2069         }
2070
2071         if (emailaddrs != NULL) {
2072                 free(emailaddrs);
2073         }
2074
2075         return ret;
2076 }
2077
2078
2079 /* ASEA */
2080 int CtdlIPCAideSetEmailAddresses(CtdlIPC * ipc, const char *who, char *emailaddrs, char *cret)
2081 {
2082         char aaa[SIZ];
2083         int ret;
2084
2085         if (!who)
2086                 return -2;
2087         if (!emailaddrs)
2088                 return -2;
2089         if (!cret)
2090                 return -2;
2091
2092         sprintf(aaa, "ASEA %s", who);
2093         ret = CtdlIPCGenericCommand(ipc, aaa, emailaddrs, 0, NULL, NULL, cret);
2094         return ret;
2095 }
2096
2097
2098 /* GPEX */
2099 /* which is 0 = room, 1 = floor, 2 = site, 3 = default for mailboxes */
2100 /* caller must free the struct ExpirePolicy */
2101 int CtdlIPCGetMessageExpirationPolicy(CtdlIPC * ipc, GPEXWhichPolicy which, struct ExpirePolicy **policy, char *cret)
2102 {
2103         static char *proto[] = {
2104                 strof(roompolicy),
2105                 strof(floorpolicy),
2106                 strof(sitepolicy),
2107                 strof(mailboxespolicy)
2108         };
2109         char cmd[256];
2110         int ret;
2111
2112         if (!cret)
2113                 return -2;
2114         if (!policy)
2115                 return -2;
2116         if (!*policy)
2117                 *policy = (struct ExpirePolicy *) calloc(1, sizeof(struct ExpirePolicy));
2118         if (!*policy)
2119                 return -1;
2120         if (which < 0 || which > 3)
2121                 return -2;
2122
2123         sprintf(cmd, "GPEX %s", proto[which]);
2124         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2125         if (ret / 100 == 2) {
2126                 policy[0]->expire_mode = extract_int(cret, 0);
2127                 policy[0]->expire_value = extract_int(cret, 1);
2128         }
2129         return ret;
2130 }
2131
2132
2133 /* SPEX */
2134 /* which is 0 = room, 1 = floor, 2 = site, 3 = default for mailboxes */
2135 /* policy is 0 = inherit, 1 = no purge, 2 = by count, 3 = by age (days) */
2136 int CtdlIPCSetMessageExpirationPolicy(CtdlIPC * ipc, int which, struct ExpirePolicy *policy, char *cret)
2137 {
2138         char aaa[38];
2139         char *whichvals[] = { "room", "floor", "site", "mailboxes" };
2140
2141         if (!cret)
2142                 return -2;
2143         if (which < 0 || which > 3)
2144                 return -2;
2145         if (!policy)
2146                 return -2;
2147         if (policy->expire_mode < 0 || policy->expire_mode > 3)
2148                 return -2;
2149         if (policy->expire_mode >= 2 && policy->expire_value < 1)
2150                 return -2;
2151
2152         sprintf(aaa, "SPEX %s|%d|%d", whichvals[which], policy->expire_mode, policy->expire_value);
2153         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2154 }
2155
2156
2157 /* CONF GET */
2158 int CtdlIPCGetSystemConfig(CtdlIPC * ipc, char **listing, char *cret)
2159 {
2160         size_t bytes;
2161
2162         if (!cret)
2163                 return -2;
2164         if (!listing)
2165                 return -2;
2166         if (*listing)
2167                 return -2;
2168
2169         return CtdlIPCGenericCommand(ipc, "CONF GET", NULL, 0, listing, &bytes, cret);
2170 }
2171
2172
2173 /* CONF SET */
2174 int CtdlIPCSetSystemConfig(CtdlIPC * ipc, const char *listing, char *cret)
2175 {
2176         if (!cret)
2177                 return -2;
2178         if (!listing)
2179                 return -2;
2180
2181         return CtdlIPCGenericCommand(ipc, "CONF SET", listing, strlen(listing), NULL, NULL, cret);
2182 }
2183
2184
2185 /* CONF GETSYS */
2186 int CtdlIPCGetSystemConfigByType(CtdlIPC * ipc, const char *mimetype, char **listing, char *cret)
2187 {
2188         int ret;
2189         char *aaa;
2190         size_t bytes;
2191
2192         if (!cret)
2193                 return -2;
2194         if (!mimetype)
2195                 return -2;
2196         if (!listing)
2197                 return -2;
2198         if (*listing)
2199                 return -2;
2200
2201         aaa = malloc(strlen(mimetype) + 13);
2202         if (!aaa)
2203                 return -1;
2204         sprintf(aaa, "CONF GETSYS|%s", mimetype);
2205         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, listing, &bytes, cret);
2206         free(aaa);
2207         return ret;
2208 }
2209
2210
2211 /* CONF PUTSYS */
2212 int CtdlIPCSetSystemConfigByType(CtdlIPC * ipc, const char *mimetype, const char *listing, char *cret)
2213 {
2214         int ret;
2215         char *aaa;
2216
2217         if (!cret)
2218                 return -2;
2219         if (!mimetype)
2220                 return -2;
2221         if (!listing)
2222                 return -2;
2223
2224         aaa = malloc(strlen(mimetype) + 13);
2225         if (!aaa)
2226                 return -1;
2227         sprintf(aaa, "CONF PUTSYS|%s", mimetype);
2228         ret = CtdlIPCGenericCommand(ipc, aaa, listing, strlen(listing), NULL, NULL, cret);
2229         free(aaa);
2230         return ret;
2231 }
2232
2233
2234 /* GNET */
2235 int CtdlIPCGetRoomNetworkConfig(CtdlIPC * ipc, char **listing, char *cret)
2236 {
2237         size_t bytes;
2238
2239         if (!cret)
2240                 return -2;
2241         if (!listing)
2242                 return -2;
2243         if (*listing)
2244                 return -2;
2245
2246         return CtdlIPCGenericCommand(ipc, "GNET", NULL, 0, listing, &bytes, cret);
2247 }
2248
2249
2250 /* SNET */
2251 int CtdlIPCSetRoomNetworkConfig(CtdlIPC * ipc, const char *listing, char *cret)
2252 {
2253         if (!cret)
2254                 return -2;
2255         if (!listing)
2256                 return -2;
2257
2258         return CtdlIPCGenericCommand(ipc, "SNET", listing, strlen(listing), NULL, NULL, cret);
2259 }
2260
2261
2262 /* REQT */
2263 int CtdlIPCRequestClientLogout(CtdlIPC * ipc, int session, char *cret)
2264 {
2265         char aaa[64];
2266
2267         if (!cret)
2268                 return -2;
2269         if (session < 0)
2270                 return -2;
2271
2272         sprintf(aaa, "REQT %d", session);
2273         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2274 }
2275
2276
2277 /* SEEN */
2278 int CtdlIPCSetMessageSeen(CtdlIPC * ipc, long msgnum, int seen, char *cret)
2279 {
2280         char aaa[27];
2281
2282         if (!cret)
2283                 return -2;
2284         if (msgnum < 0)
2285                 return -2;
2286
2287         sprintf(aaa, "SEEN %ld|%d", msgnum, seen ? 1 : 0);
2288         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2289 }
2290
2291
2292 /* STLS */
2293 int CtdlIPCStartEncryption(CtdlIPC * ipc, char *cret)
2294 {
2295         int a;
2296         int r;
2297         char buf[SIZ];
2298
2299 #ifdef HAVE_OPENSSL
2300         SSL *temp_ssl;
2301
2302         /* New SSL object */
2303         temp_ssl = SSL_new(ssl_ctx);
2304         if (!temp_ssl) {
2305                 error_printf("SSL_new failed: %s\n", ERR_reason_error_string(ERR_get_error()));
2306                 return -2;
2307         }
2308         /* Pointless flag waving */
2309 #if SSLEAY_VERSION_NUMBER >= 0x0922
2310         SSL_set_session_id_context(temp_ssl, (const unsigned char *) "Citadel SID", 14);
2311 #endif
2312
2313         /* Associate network connection with SSL object */
2314         if (SSL_set_fd(temp_ssl, ipc->sock) < 1) {
2315                 error_printf("SSL_set_fd failed: %s\n", ERR_reason_error_string(ERR_get_error()));
2316                 return -2;
2317         }
2318
2319         if (status_hook != NULL) {
2320                 status_hook("Requesting encryption...\r");
2321         }
2322
2323         /* Ready to start SSL/TLS */
2324         r = CtdlIPCGenericCommand(ipc, "STLS", NULL, 0, NULL, NULL, cret);
2325         if (r / 100 != 2) {
2326                 error_printf("Server can't start TLS: %s\n", buf);
2327                 endtls(temp_ssl);
2328                 return r;
2329         }
2330
2331         /* Do SSL/TLS handshake */
2332         if ((a = SSL_connect(temp_ssl)) < 1) {
2333                 error_printf("SSL_connect failed: %s\n", ERR_reason_error_string(ERR_get_error()));
2334                 endtls(temp_ssl);
2335                 return -2;
2336         }
2337         ipc->ssl = temp_ssl;
2338
2339         error_printf("Encrypting with %s cipher %s\n",
2340                      SSL_CIPHER_get_version(SSL_get_current_cipher(ipc->ssl)), SSL_CIPHER_get_name(SSL_get_current_cipher(ipc->ssl))
2341             );
2342         return r;
2343 #else
2344         return 0;
2345 #endif                          /* HAVE_OPENSSL */
2346 }
2347
2348
2349 #ifdef HAVE_OPENSSL
2350 static void endtls(SSL * ssl)
2351 {
2352         if (ssl) {
2353                 SSL_shutdown(ssl);
2354                 SSL_free(ssl);
2355         }
2356 }
2357 #endif
2358
2359
2360 /* QDIR */
2361 int CtdlIPCDirectoryLookup(CtdlIPC * ipc, const char *address, char *cret)
2362 {
2363         int ret;
2364         char *aaa;
2365
2366         if (!address)
2367                 return -2;
2368         if (!cret)
2369                 return -2;
2370
2371         aaa = (char *) malloc(strlen(address) + 6);
2372         if (!aaa)
2373                 return -1;
2374
2375         sprintf(aaa, "QDIR %s", address);
2376         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2377         free(aaa);
2378         return ret;
2379 }
2380
2381
2382 /* IPGM */
2383 int CtdlIPCInternalProgram(CtdlIPC * ipc, int secret, char *cret)
2384 {
2385         char aaa[30];
2386
2387         if (!cret)
2388                 return -2;
2389         sprintf(aaa, "IPGM %d", secret);
2390         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2391 }
2392
2393
2394
2395
2396 /* ************************************************************************** */
2397 /*           Stuff below this line is not for public consumption            */
2398 /* ************************************************************************** */
2399
2400
2401 /* Read a listing from the server up to 000.  Append to dest if it exists */
2402 char *CtdlIPCReadListing(CtdlIPC * ipc, char *dest)
2403 {
2404         size_t length = 0;
2405         size_t linelength;
2406         char *ret = NULL;
2407         char aaa[SIZ];
2408
2409         ret = dest;
2410         if (ret != NULL) {
2411                 length = strlen(ret);
2412         } else {
2413                 length = 0;
2414         }
2415
2416         while (CtdlIPC_getline(ipc, aaa), strcmp(aaa, "000")) {
2417                 linelength = strlen(aaa);
2418                 ret = (char *) realloc(ret, (size_t) (length + linelength + 2));
2419                 if (ret) {
2420                         strcpy(&ret[length], aaa);
2421                         length += linelength;
2422                         strcpy(&ret[length++], "\n");
2423                 }
2424         }
2425
2426         return (ret);
2427 }
2428
2429
2430 /* Send a listing to the server; generate the ending 000. */
2431 int CtdlIPCSendListing(CtdlIPC * ipc, const char *listing)
2432 {
2433         char *text;
2434
2435         text = (char *) malloc(strlen(listing) + 6);
2436         if (text) {
2437                 strcpy(text, listing);
2438                 while (text[strlen(text) - 1] == '\n')
2439                         text[strlen(text) - 1] = '\0';
2440                 strcat(text, "\n000");
2441                 CtdlIPC_putline(ipc, text);
2442                 free(text);
2443                 text = NULL;
2444         } else {
2445                 /* Malloc failed but we are committed to send */
2446                 /* This may result in extra blanks at the bottom */
2447                 CtdlIPC_putline(ipc, text);
2448                 CtdlIPC_putline(ipc, "000");
2449         }
2450         return 0;
2451 }
2452
2453
2454 /* Partial read of file from server */
2455 size_t CtdlIPCPartialRead(CtdlIPC * ipc, void **buf, size_t offset, size_t bytes, char *cret)
2456 {
2457         size_t len = 0;
2458         char aaa[SIZ];
2459
2460         if (!buf)
2461                 return 0;
2462         if (!cret)
2463                 return 0;
2464         if (bytes < 1)
2465                 return 0;
2466
2467         CtdlIPC_lock(ipc);
2468         sprintf(aaa, "READ %d|%d", (int) offset, (int) bytes);
2469         CtdlIPC_putline(ipc, aaa);
2470         CtdlIPC_getline(ipc, aaa);
2471         if (aaa[0] != '6')
2472                 strcpy(cret, &aaa[4]);
2473         else {
2474                 len = extract_long(&aaa[4], 0);
2475                 *buf = (void *) realloc(*buf, (size_t) (offset + len));
2476                 if (*buf) {
2477                         /* I know what I'm doing */
2478                         serv_read(ipc, ((char *) (*buf) + offset), len);
2479                 } else {
2480                         /* We have to read regardless */
2481                         serv_read(ipc, aaa, len);
2482                         len = 0;
2483                 }
2484         }
2485         CtdlIPC_unlock(ipc);
2486         return len;
2487 }
2488
2489
2490 /* CLOS */
2491 int CtdlIPCEndDownload(CtdlIPC * ipc, char *cret)
2492 {
2493         int ret;
2494
2495         if (!cret)
2496                 return -2;
2497         if (!ipc->downloading)
2498                 return -2;
2499
2500         ret = CtdlIPCGenericCommand(ipc, "CLOS", NULL, 0, NULL, NULL, cret);
2501         if (ret / 100 == 2)
2502                 ipc->downloading = 0;
2503         return ret;
2504 }
2505
2506
2507 /* MSGP */
2508 int CtdlIPCSpecifyPreferredFormats(CtdlIPC * ipc, char *cret, char *formats)
2509 {
2510         int ret;
2511         char cmd[SIZ];
2512
2513         snprintf(cmd, sizeof cmd, "MSGP %s", formats);
2514         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2515         return ret;
2516 }
2517
2518
2519
2520 /* READ */
2521 int CtdlIPCReadDownload(CtdlIPC * ipc, void **buf, size_t bytes, size_t resume, void (*progress_gauge_callback)
2522                          (CtdlIPC *, unsigned long, unsigned long), char *cret)
2523 {
2524         size_t len;
2525
2526         if (!cret)
2527                 return -1;
2528         if (!buf)
2529                 return -1;
2530         if (*buf)
2531                 return -1;
2532         if (!ipc->downloading)
2533                 return -1;
2534
2535         len = resume;
2536         if (progress_gauge_callback)
2537                 progress_gauge_callback(ipc, len, bytes);
2538         while (len < bytes) {
2539                 size_t block;
2540
2541                 block = CtdlIPCPartialRead(ipc, buf, len, 4096, cret);
2542                 if (block == 0) {
2543                         free(*buf);
2544                         return 0;
2545                 }
2546                 len += block;
2547                 if (progress_gauge_callback)
2548                         progress_gauge_callback(ipc, len, bytes);
2549         }
2550         return len;
2551 }
2552
2553 /* READ - pipelined */
2554 int CtdlIPCHighSpeedReadDownload(CtdlIPC * ipc, void **buf, size_t bytes, size_t resume, void (*progress_gauge_callback)
2555                                   (CtdlIPC *, unsigned long, unsigned long), char *cret)
2556 {
2557         size_t len;
2558         int calls;              /* How many calls in the pipeline */
2559         int i;                  /* iterator */
2560         char aaa[4096];
2561
2562         if (!cret)
2563                 return -1;
2564         if (!buf)
2565                 return -1;
2566         if (*buf)
2567                 return -1;
2568         if (!ipc->downloading)
2569                 return -1;
2570
2571         *buf = (void *) realloc(*buf, bytes - resume);
2572         if (!*buf)
2573                 return -1;
2574
2575         len = 0;
2576         CtdlIPC_lock(ipc);
2577         if (progress_gauge_callback)
2578                 progress_gauge_callback(ipc, len, bytes);
2579
2580         /* How many calls will be in the pipeline? */
2581         calls = (bytes - resume) / 4096;
2582         if ((bytes - resume) % 4096)
2583                 calls++;
2584
2585         /* Send all requests at once */
2586         for (i = 0; i < calls; i++) {
2587                 sprintf(aaa, "READ %d|4096", (int) (i * 4096 + resume));
2588                 CtdlIPC_putline(ipc, aaa);
2589         }
2590
2591         /* Receive all responses at once */
2592         for (i = 0; i < calls; i++) {
2593                 CtdlIPC_getline(ipc, aaa);
2594                 if (aaa[0] != '6')
2595                         strcpy(cret, &aaa[4]);
2596                 else {
2597                         len = extract_long(&aaa[4], 0);
2598                         /* I know what I'm doing */
2599                         serv_read(ipc, ((char *) (*buf) + (i * 4096)), len);
2600                 }
2601                 if (progress_gauge_callback)
2602                         progress_gauge_callback(ipc, i * 4096 + len, bytes);
2603         }
2604         CtdlIPC_unlock(ipc);
2605         return len;
2606 }
2607
2608
2609 /* UCLS */
2610 int CtdlIPCEndUpload(CtdlIPC * ipc, int discard, char *cret)
2611 {
2612         int ret;
2613         char cmd[8];
2614
2615         if (!cret)
2616                 return -1;
2617         if (!ipc->uploading)
2618                 return -1;
2619
2620         sprintf(cmd, "UCLS %d", discard ? 0 : 1);
2621         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2622         ipc->uploading = 0;
2623         return ret;
2624 }
2625
2626
2627 /* WRIT */
2628 int CtdlIPCWriteUpload(CtdlIPC * ipc, FILE * uploadFP, void (*progress_gauge_callback)
2629                         (CtdlIPC *, unsigned long, unsigned long), char *cret)
2630 {
2631         int ret = -1;
2632         size_t offset = 0;
2633         size_t bytes;
2634         char aaa[SIZ];
2635         char buf[4096];
2636         FILE *fd = uploadFP;
2637         int ferr;
2638
2639         if (!cret)
2640                 return -1;
2641
2642         fseek(fd, 0L, SEEK_END);
2643         bytes = ftell(fd);
2644         rewind(fd);
2645
2646         if (progress_gauge_callback)
2647                 progress_gauge_callback(ipc, 0, bytes);
2648
2649         while (offset < bytes) {
2650                 size_t to_write;
2651
2652                 /* Read some data in */
2653                 to_write = fread(buf, 1, 4096, fd);
2654                 if (!to_write) {
2655                         if (feof(fd) || ferror(fd))
2656                                 break;
2657                 }
2658                 sprintf(aaa, "WRIT %d", (int) to_write);
2659                 CtdlIPC_putline(ipc, aaa);
2660                 CtdlIPC_getline(ipc, aaa);
2661                 strcpy(cret, &aaa[4]);
2662                 ret = atoi(aaa);
2663                 if (aaa[0] == '7') {
2664                         to_write = extract_long(&aaa[4], 0);
2665
2666                         serv_write(ipc, buf, to_write);
2667                         offset += to_write;
2668                         if (progress_gauge_callback)
2669                                 progress_gauge_callback(ipc, offset, bytes);
2670                         /* Detect short reads and back up if needed */
2671                         /* offset will never be negative anyway */
2672                         fseek(fd, (signed) offset, SEEK_SET);
2673                 } else {
2674                         break;
2675                 }
2676         }
2677         if (progress_gauge_callback)
2678                 progress_gauge_callback(ipc, 1, 1);
2679         ferr = ferror(fd);
2680         fclose(fd);
2681         return (!ferr ? ret : -2);
2682 }
2683
2684
2685 /*
2686  * Generic command method.  This method should handle any server command
2687  * except for CHAT.  It takes the following arguments:
2688  *
2689  * ipc                  The server to speak with
2690  * command              Preformatted command to send to server
2691  * to_send              A text or binary file to send to server
2692  *                      (only sent if server requests it)
2693  * bytes_to_send        The number of bytes in to_send (required if
2694  *                      sending binary, optional if sending listing)
2695  * to_receive           Pointer to a NULL pointer, if the server
2696  *                      sends text or binary we will allocate memory
2697  *                      for the file and stuff it here
2698  * bytes_to_receive     If a file is received, we will store its
2699  *                      byte count here
2700  * proto_response       The protocol response.  Caller must provide
2701  *                      this buffer and ensure that it is at least
2702  *                      128 bytes in length.
2703  *
2704  * This function returns a number equal to the protocol response number,
2705  * -1 if an internal error occurred, -2 if caller provided bad values,
2706  * or 0 - the protocol response number if bad values were found during
2707  * the protocol exchange.
2708  * It stores the protocol response string (minus the number) in 
2709  * protocol_response as described above.  Some commands send additional
2710  * data in this string.
2711  */
2712 int CtdlIPCGenericCommand(CtdlIPC * ipc,
2713                           const char *command, const char *to_send,
2714                           size_t bytes_to_send, char **to_receive, size_t * bytes_to_receive, char *proto_response)
2715 {
2716         char buf[SIZ];
2717         int ret;
2718
2719         if (!command)
2720                 return -2;
2721         if (!proto_response)
2722                 return -2;
2723
2724         CtdlIPC_lock(ipc);
2725         CtdlIPC_putline(ipc, command);
2726         while (1) {
2727                 CtdlIPC_getline(ipc, proto_response);
2728                 if (proto_response[3] == '*')
2729                         instant_msgs = 1;
2730                 ret = atoi(proto_response);
2731                 strcpy(proto_response, &proto_response[4]);
2732                 switch (ret / 100) {
2733                 default:        /* Unknown, punt */
2734                 case 2: /* OK */
2735                 case 3: /* MORE_DATA */
2736                 case 5: /* ERROR */
2737                         /* Don't need to do anything */
2738                         break;
2739                 case 1: /* LISTING_FOLLOWS */
2740                         if (to_receive && !*to_receive && bytes_to_receive) {
2741                                 *to_receive = CtdlIPCReadListing(ipc, NULL);
2742                         } else {        /* Drain */
2743                                 while (CtdlIPC_getline(ipc, buf), strcmp(buf, "000"));
2744                                 ret = -ret;
2745                         }
2746                         break;
2747                 case 4: /* SEND_LISTING */
2748                         if (to_send) {
2749                                 CtdlIPCSendListing(ipc, to_send);
2750                         } else {
2751                                 /* No listing given, fake it */
2752                                 CtdlIPC_putline(ipc, "000");
2753                                 ret = -ret;
2754                         }
2755                         break;
2756                 case 6: /* BINARY_FOLLOWS */
2757                         if (to_receive && !*to_receive && bytes_to_receive) {
2758                                 *bytes_to_receive = extract_long(proto_response, 0);
2759                                 *to_receive = (char *)
2760                                     malloc((size_t) * bytes_to_receive);
2761                                 if (!*to_receive) {
2762                                         ret = -1;
2763                                 } else {
2764                                         serv_read(ipc, *to_receive, *bytes_to_receive);
2765                                 }
2766                         } else {
2767                                 /* Drain */
2768                                 size_t drain;
2769
2770                                 drain = extract_long(proto_response, 0);
2771                                 while (drain > SIZ) {
2772                                         serv_read(ipc, buf, SIZ);
2773                                         drain -= SIZ;
2774                                 }
2775                                 serv_read(ipc, buf, drain);
2776                                 ret = -ret;
2777                         }
2778                         break;
2779                 case 7: /* SEND_BINARY */
2780                         if (to_send && bytes_to_send) {
2781                                 serv_write(ipc, to_send, bytes_to_send);
2782                         } else if (bytes_to_send) {
2783                                 /* Fake it, send nulls */
2784                                 size_t fake;
2785
2786                                 fake = bytes_to_send;
2787                                 memset(buf, '\0', SIZ);
2788                                 while (fake > SIZ) {
2789                                         serv_write(ipc, buf, SIZ);
2790                                         fake -= SIZ;
2791                                 }
2792                                 serv_write(ipc, buf, fake);
2793                                 ret = -ret;
2794                         }       /* else who knows?  DANGER WILL ROBINSON */
2795                         break;
2796                 case 8: /* START_CHAT_MODE */
2797                         if (!strncasecmp(command, "CHAT", 4)) {
2798                                 /* Don't call chatmode with generic! */
2799                                 CtdlIPC_putline(ipc, "/quit");
2800                                 ret = -ret;
2801                         } else {
2802                                 /* In this mode we send then receive listing */
2803                                 if (to_send) {
2804                                         CtdlIPCSendListing(ipc, to_send);
2805                                 } else {
2806                                         /* No listing given, fake it */
2807                                         CtdlIPC_putline(ipc, "000");
2808                                         ret = -ret;
2809                                 }
2810                                 if (to_receive && !*to_receive && bytes_to_receive) {
2811                                         *to_receive = CtdlIPCReadListing(ipc, NULL);
2812                                 } else {        /* Drain */
2813                                         while (CtdlIPC_getline(ipc, buf), strcmp(buf, "000"));
2814                                         ret = -ret;
2815                                 }
2816                         }
2817                         break;
2818                 case 9: /* ASYNC_MSG */
2819                         /* CtdlIPCDoAsync(ret, proto_response); */
2820                         free(CtdlIPCReadListing(ipc, NULL));    /* STUB FIXME */
2821                         break;
2822                 }
2823                 if (ret / 100 != 9)
2824                         break;
2825         }
2826         CtdlIPC_unlock(ipc);
2827         return ret;
2828 }
2829
2830
2831 /*
2832  * Connect to a Citadel on a remote host using a TCP/IP socket
2833  */
2834 static int tcp_connectsock(char *host, char *service)
2835 {
2836         struct in6_addr serveraddr;
2837         struct addrinfo hints;
2838         struct addrinfo *res = NULL;
2839         struct addrinfo *ai = NULL;
2840         int rc = (-1);
2841         int sock = (-1);
2842
2843         if ((host == NULL) || IsEmptyStr(host)) {
2844                 service = DEFAULT_HOST;
2845         }
2846         if ((service == NULL) || IsEmptyStr(service)) {
2847                 service = DEFAULT_PORT;
2848         }
2849
2850         memset(&hints, 0x00, sizeof(hints));
2851         hints.ai_flags = AI_NUMERICSERV;
2852         hints.ai_family = AF_UNSPEC;
2853         hints.ai_socktype = SOCK_STREAM;
2854
2855         /*
2856          * Handle numeric IPv4 and IPv6 addresses
2857          */
2858         rc = inet_pton(AF_INET, host, &serveraddr);
2859         if (rc == 1) {          /* dotted quad */
2860                 hints.ai_family = AF_INET;
2861                 hints.ai_flags |= AI_NUMERICHOST;
2862         } else {
2863                 rc = inet_pton(AF_INET6, host, &serveraddr);
2864                 if (rc == 1) {  /* IPv6 address */
2865                         hints.ai_family = AF_INET6;
2866                         hints.ai_flags |= AI_NUMERICHOST;
2867                 }
2868         }
2869
2870         /* Begin the connection process */
2871
2872         rc = getaddrinfo(host, service, &hints, &res);
2873         if (rc != 0) {
2874                 return (-1);
2875         }
2876
2877         /*
2878          * Try all available addresses until we connect to one or until we run out.
2879          */
2880         for (ai = res; ai != NULL; ai = ai->ai_next) {
2881                 sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
2882                 if (sock < 0)
2883                         return (-1);
2884
2885                 rc = connect(sock, ai->ai_addr, ai->ai_addrlen);
2886                 if (rc >= 0) {
2887                         return (sock);  /* Connected! */
2888                 } else {
2889                         close(sock);    /* Failed.  Close the socket to avoid fd leak! */
2890                 }
2891         }
2892
2893         return (-1);
2894 }
2895
2896
2897
2898
2899
2900 /*
2901  * Connect to a Citadel on the local host using a unix domain socket
2902  */
2903 static int uds_connectsock(int *isLocal, char *sockpath)
2904 {
2905         struct sockaddr_un addr;
2906         int s;
2907
2908         memset(&addr, 0, sizeof(addr));
2909         addr.sun_family = AF_UNIX;
2910         safestrncpy(addr.sun_path, sockpath, sizeof addr.sun_path);
2911
2912         s = socket(AF_UNIX, SOCK_STREAM, 0);
2913         if (s < 0) {
2914                 return -1;
2915         }
2916
2917         if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
2918                 close(s);
2919                 return -1;
2920         }
2921
2922         *isLocal = 1;
2923         return s;
2924 }
2925
2926
2927 /*
2928  * input binary data from socket
2929  */
2930 static void serv_read(CtdlIPC * ipc, char *buf, unsigned int bytes)
2931 {
2932         unsigned int len, rlen;
2933
2934 #if defined(HAVE_OPENSSL)
2935         if (ipc->ssl) {
2936                 serv_read_ssl(ipc, buf, bytes);
2937                 return;
2938         }
2939 #endif
2940         len = 0;
2941         while (len < bytes) {
2942                 rlen = read(ipc->sock, &buf[len], bytes - len);
2943                 if (rlen < 1) {
2944                         connection_died(ipc, 0);
2945                         return;
2946                 }
2947                 len += rlen;
2948         }
2949 }
2950
2951
2952 /*
2953  * send binary to server
2954  */
2955 void serv_write(CtdlIPC * ipc, const char *buf, unsigned int nbytes)
2956 {
2957         unsigned int bytes_written = 0;
2958         int retval;
2959
2960 #if defined(HAVE_OPENSSL)
2961         if (ipc->ssl) {
2962                 serv_write_ssl(ipc, buf, nbytes);
2963                 return;
2964         }
2965 #endif
2966         while (bytes_written < nbytes) {
2967                 retval = write(ipc->sock, &buf[bytes_written], nbytes - bytes_written);
2968                 if (retval < 1) {
2969                         connection_died(ipc, 0);
2970                         return;
2971                 }
2972                 bytes_written += retval;
2973         }
2974 }
2975
2976
2977 #ifdef HAVE_OPENSSL
2978 /*
2979  * input binary data from encrypted connection
2980  */
2981 static void serv_read_ssl(CtdlIPC * ipc, char *buf, unsigned int bytes)
2982 {
2983         int len, rlen;
2984         char junk[1];
2985
2986         len = 0;
2987         while (len < bytes) {
2988                 if (SSL_want_read(ipc->ssl)) {
2989                         if ((SSL_write(ipc->ssl, junk, 0)) < 1) {
2990                                 error_printf("SSL_write in serv_read:\n");
2991                                 ERR_print_errors_fp(stderr);
2992                         }
2993                 }
2994                 rlen = SSL_read(ipc->ssl, &buf[len], bytes - len);
2995                 if (rlen < 1) {
2996                         long errval;
2997
2998                         errval = SSL_get_error(ipc->ssl, rlen);
2999                         if (errval == SSL_ERROR_WANT_READ || errval == SSL_ERROR_WANT_WRITE) {
3000                                 sleep(1);
3001                                 continue;
3002                         }
3003 /***
3004  Not sure why we'd want to handle these error codes any differently,
3005  but this definitely isn't the way to handle them.  Someone must have
3006  naively assumed that we could fall back to unencrypted communications,
3007  but all it does is just recursively blow the stack.
3008                         if (errval == SSL_ERROR_ZERO_RETURN ||
3009                                         errval == SSL_ERROR_SSL) {
3010                                 serv_read(ipc, &buf[len], bytes - len);
3011                                 return;
3012                         }
3013  ***/
3014                         error_printf("SSL_read in serv_read: %s\n", ERR_reason_error_string(ERR_peek_error()));
3015                         connection_died(ipc, 1);
3016                         return;
3017                 }
3018                 len += rlen;
3019         }
3020 }
3021
3022
3023 /*
3024  * send binary to server encrypted
3025  */
3026 static void serv_write_ssl(CtdlIPC * ipc, const char *buf, unsigned int nbytes)
3027 {
3028         unsigned int bytes_written = 0;
3029         int retval;
3030         char junk[1];
3031
3032         while (bytes_written < nbytes) {
3033                 if (SSL_want_write(ipc->ssl)) {
3034                         if ((SSL_read(ipc->ssl, junk, 0)) < 1) {
3035                                 error_printf("SSL_read in serv_write:\n");
3036                                 ERR_print_errors_fp(stderr);
3037                         }
3038                 }
3039                 retval = SSL_write(ipc->ssl, &buf[bytes_written], nbytes - bytes_written);
3040                 if (retval < 1) {
3041                         long errval;
3042
3043                         errval = SSL_get_error(ipc->ssl, retval);
3044                         if (errval == SSL_ERROR_WANT_READ || errval == SSL_ERROR_WANT_WRITE) {
3045                                 sleep(1);
3046                                 continue;
3047                         }
3048                         if (errval == SSL_ERROR_ZERO_RETURN || errval == SSL_ERROR_SSL) {
3049                                 serv_write(ipc, &buf[bytes_written], nbytes - bytes_written);
3050                                 return;
3051                         }
3052                         error_printf("SSL_write in serv_write: %s\n", ERR_reason_error_string(ERR_peek_error()));
3053                         connection_died(ipc, 1);
3054                         return;
3055                 }
3056                 bytes_written += retval;
3057         }
3058 }
3059
3060
3061
3062
3063 static void CtdlIPC_init_OpenSSL(void)
3064 {
3065         int a;
3066         const SSL_METHOD *ssl_method;
3067         DH *dh;
3068
3069         /* already done init */
3070         if (ssl_ctx) {
3071                 return;
3072         }
3073
3074         /* Get started */
3075         a = 0;
3076         ssl_ctx = NULL;
3077         dh = NULL;
3078         SSL_load_error_strings();
3079         SSLeay_add_ssl_algorithms();
3080
3081         /* Set up the SSL context in which we will oeprate */
3082         ssl_method = SSLv23_client_method();
3083         ssl_ctx = SSL_CTX_new(ssl_method);
3084         if (!ssl_ctx) {
3085                 error_printf("SSL_CTX_new failed: %s\n", ERR_reason_error_string(ERR_get_error()));
3086                 return;
3087         }
3088         /* Any reasonable cipher we can get */
3089         if (!(SSL_CTX_set_cipher_list(ssl_ctx, CIT_CIPHERS))) {
3090                 error_printf("No ciphers available for encryption\n");
3091                 return;
3092         }
3093         SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_BOTH);
3094
3095         /* Load DH parameters into the context */
3096         dh = DH_new();
3097         if (!dh) {
3098                 error_printf("Can't allocate a DH object: %s\n", ERR_reason_error_string(ERR_get_error()));
3099                 return;
3100         }
3101
3102         if (!(DH_generate_parameters_ex(dh, 128, DH_GENERATOR_2, 0))) {
3103                 error_printf("Can't generate DH parameters: %s\n", ERR_reason_error_string(ERR_get_error()));
3104                 DH_free(dh);
3105                 return;
3106         }
3107
3108         SSL_CTX_set_tmp_dh(ssl_ctx, dh);
3109         DH_free(dh);
3110 }
3111
3112 #endif                          /* HAVE_OPENSSL */
3113
3114
3115 int ReadNetworkChunk(CtdlIPC * ipc)
3116 {
3117         fd_set read_fd;
3118         int ret = 0;
3119         int err = 0;
3120         struct timeval tv;
3121         size_t n;
3122
3123         tv.tv_sec = 1;
3124         tv.tv_usec = 1000;
3125         /*tries = 0; */
3126         n = 0;
3127         while (1) {
3128                 errno = 0;
3129                 FD_ZERO(&read_fd);
3130                 FD_SET(ipc->sock, &read_fd);
3131                 ret = select(ipc->sock + 1, &read_fd, NULL, NULL, &tv);
3132
3133                 if (ret > 0) {
3134
3135                         *(ipc->BufPtr) = '\0';
3136                         n = recv(ipc->sock, ipc->BufPtr, ipc->BufSize - (ipc->BufPtr - ipc->Buf) - 1, 0);
3137                         if (n > 0) {
3138                                 ipc->BufPtr[n] = '\0';
3139                                 ipc->BufUsed += n;
3140                                 return n;
3141                         } else
3142                                 return n;
3143                 } else if (ret < 0) {
3144                         if (!(errno == EINTR || errno == EAGAIN))
3145                                 error_printf("\nselect failed: %d %s\n", err, strerror(err));
3146                         return -1;
3147                 }               /*
3148                                    else {
3149                                    tries ++;
3150                                    if (tries >= 10)
3151                                    n = read(ipc->sock, ipc->BufPtr, ipc->BufSize  - (ipc->BufPtr - ipc->Buf) - 1);
3152                                    if (n > 0) {
3153                                    ipc->BufPtr[n]='\0';
3154                                    ipc->BufUsed += n;
3155                                    return n;
3156                                    }
3157                                    else {
3158                                    connection_died(ipc, 0);
3159                                    return -1;
3160                                    }
3161                                    } */
3162         }
3163 }
3164
3165 /*
3166  * input string from socket - implemented in terms of serv_read()
3167  */
3168 #ifdef CHUNKED_READ
3169
3170 static void CtdlIPC_getline(CtdlIPC * ipc, char *buf)
3171 {
3172         int i, ntries;
3173         char *aptr, *bptr, *aeptr, *beptr;
3174
3175 //      error_printf("---\n");
3176
3177         beptr = buf + SIZ;
3178 #if defined(HAVE_OPENSSL)
3179         if (ipc->ssl) {
3180
3181                 /* Read one character at a time. */
3182                 for (i = 0;; i++) {
3183                         serv_read(ipc, &buf[i], 1);
3184                         if (buf[i] == '\n' || i == (SIZ - 1))
3185                                 break;
3186                 }
3187
3188                 /* If we got a long line, discard characters until the newline. */
3189                 if (i == (SIZ - 1))
3190                         while (buf[i] != '\n')
3191                                 serv_read(ipc, &buf[i], 1);
3192
3193                 /* Strip the trailing newline (and carriage return, if present) */
3194                 if (i >= 0 && buf[i] == 10)
3195                         buf[i--] = 0;
3196                 if (i >= 0 && buf[i] == 13)
3197                         buf[i--] = 0;
3198         } else
3199 #endif
3200         {
3201                 if (ipc->Buf == NULL) {
3202                         ipc->BufSize = SIZ;
3203                         ipc->Buf = (char *) malloc(ipc->BufSize + 10);
3204                         *(ipc->Buf) = '\0';
3205                         ipc->BufPtr = ipc->Buf;
3206                 }
3207
3208                 ntries = 0;
3209 //              while ((ipc->BufUsed == 0)||(ntries++ > 10))
3210                 if (ipc->BufUsed == 0)
3211                         ReadNetworkChunk(ipc);
3212
3213 ////            if (ipc->BufUsed != 0) while (1)
3214                 bptr = buf;
3215
3216                 while (1) {
3217                         aptr = ipc->BufPtr;
3218                         aeptr = ipc->Buf + ipc->BufSize;
3219                         while ((aptr < aeptr) && (bptr < beptr) && (*aptr != '\0') && (*aptr != '\n'))
3220                                 *(bptr++) = *(aptr++);
3221                         if ((*aptr == '\n') && (aptr < aeptr)) {
3222                                 /* Terminate it right, remove the line breaks */
3223                                 while ((aptr < aeptr) && ((*aptr == '\n') || (*aptr == '\r')))
3224                                         aptr++;
3225                                 while ((aptr < aeptr) && (*(aptr + 1) == '\0'))
3226                                         aptr++;
3227                                 *(bptr++) = '\0';
3228 //                              fprintf(stderr, "parsing %d %d %d - %d %d %d %s\n", ipc->BufPtr - ipc->Buf, aptr - ipc->BufPtr, ipc->BufUsed , *aptr, *(aptr-1), *(aptr+1), buf);
3229                                 if ((bptr > buf + 1) && (*(bptr - 1) == '\r'))
3230                                         *(--bptr) = '\0';
3231
3232                                 /* is there more in the buffer we need to read later? */
3233                                 if (ipc->Buf + ipc->BufUsed > aptr) {
3234                                         ipc->BufPtr = aptr;
3235                                 } else {
3236                                         ipc->BufUsed = 0;
3237                                         ipc->BufPtr = ipc->Buf;
3238                                 }
3239 //                              error_printf("----bla6\n");
3240                                 return;
3241
3242                         } /* should we move our read stuf to the bufferstart so we have more space at the end? */
3243                         else if ((ipc->BufPtr != ipc->Buf) && (ipc->BufUsed > (ipc->BufSize - (ipc->BufSize / 4)))) {
3244                                 size_t NewBufSize = ipc->BufSize * 2;
3245                                 int delta = (ipc->BufPtr - ipc->Buf);
3246                                 char *NewBuf;
3247
3248                                 /* if the line would end after our buffer, we should use a bigger buffer. */
3249                                 NewBuf = (char *) malloc(NewBufSize + 10);
3250                                 memcpy(NewBuf, ipc->BufPtr, ipc->BufUsed - delta);
3251                                 free(ipc->Buf);
3252                                 ipc->Buf = ipc->BufPtr = NewBuf;
3253                                 ipc->BufUsed -= delta;
3254                                 ipc->BufSize = NewBufSize;
3255                         }
3256                         if (ReadNetworkChunk(ipc) < 0) {
3257 //                              error_printf("----bla\n");
3258                                 return;
3259                         }
3260                 }
3261 ///             error_printf("----bl45761%s\nipc->BufUsed");
3262         }
3263 //      error_printf("----bla1\n");
3264 }
3265
3266 #else                           /* CHUNKED_READ */
3267
3268 static void CtdlIPC_getline(CtdlIPC * ipc, char *buf)
3269 {
3270         int i;
3271
3272         /* Read one character at a time. */
3273         for (i = 0;; i++) {
3274                 serv_read(ipc, &buf[i], 1);
3275                 if (buf[i] == '\n' || i == (SIZ - 1))
3276                         break;
3277         }
3278
3279         /* If we got a long line, discard characters until the newline. */
3280         if (i == (SIZ - 1))
3281                 while (buf[i] != '\n')
3282                         serv_read(ipc, &buf[i], 1);
3283
3284         /* Strip the trailing newline (and carriage return, if present) */
3285         if (i >= 0 && buf[i] == 10)
3286                 buf[i--] = 0;
3287         if (i >= 0 && buf[i] == 13)
3288                 buf[i--] = 0;
3289 }
3290
3291
3292 #endif                          /* CHUNKED_READ */
3293
3294
3295 void CtdlIPC_chat_recv(CtdlIPC * ipc, char *buf)
3296 {
3297         CtdlIPC_getline(ipc, buf);
3298 }
3299
3300 /*
3301  * send line to server - implemented in terms of serv_write()
3302  */
3303 static void CtdlIPC_putline(CtdlIPC * ipc, const char *buf)
3304 {
3305         char *cmd = NULL;
3306         int len;
3307
3308         len = strlen(buf);
3309         cmd = malloc(len + 2);
3310         if (!cmd) {
3311                 /* This requires no extra memory */
3312                 serv_write(ipc, buf, len);
3313                 serv_write(ipc, "\n", 1);
3314         } else {
3315                 /* This is network-optimized */
3316                 strncpy(cmd, buf, len);
3317                 strcpy(cmd + len, "\n");
3318                 serv_write(ipc, cmd, len + 1);
3319                 free(cmd);
3320         }
3321
3322         ipc->last_command_sent = time(NULL);
3323 }
3324
3325 void CtdlIPC_chat_send(CtdlIPC * ipc, const char *buf)
3326 {
3327         CtdlIPC_putline(ipc, buf);
3328 }
3329
3330
3331 /*
3332  * attach to server
3333  */
3334 CtdlIPC *CtdlIPC_new(int argc, char **argv, char *hostbuf, char *portbuf)
3335 {
3336         int a;
3337         char cithost[SIZ];
3338         char citport[SIZ];
3339         char sockpath[SIZ];
3340         CtdlIPC *ipc;
3341
3342         ipc = malloc(sizeof(struct _CtdlIPC));
3343         if (!ipc) {
3344                 return 0;
3345         }
3346 #if defined(HAVE_OPENSSL)
3347         ipc->ssl = NULL;
3348         CtdlIPC_init_OpenSSL();
3349 #endif
3350         ipc->sock = -1;         /* Not connected */
3351         ipc->isLocal = 0;       /* Not local, of course! */
3352         ipc->downloading = 0;
3353         ipc->uploading = 0;
3354         ipc->last_command_sent = 0L;
3355         ipc->network_status_cb = NULL;
3356         ipc->Buf = NULL;
3357         ipc->BufUsed = 0;
3358         ipc->BufPtr = NULL;
3359
3360         strcpy(cithost, DEFAULT_HOST);  /* default host */
3361         strcpy(citport, DEFAULT_PORT);  /* default port */
3362
3363         /* Allow caller to supply our values */
3364         if (hostbuf && strlen(hostbuf) > 0) {
3365                 strcpy(cithost, hostbuf);
3366         }
3367         if (portbuf && strlen(portbuf) > 0) {
3368                 strcpy(citport, portbuf);
3369         }
3370
3371         /* Read host/port from command line if present */
3372         for (a = 0; a < argc; ++a) {
3373                 if (a == 0) {
3374                         /* do nothing */
3375                 } else if (a == 1) {
3376                         strcpy(cithost, argv[a]);
3377                 } else if (a == 2) {
3378                         strcpy(citport, argv[a]);
3379                 } else {
3380                         error_printf("%s: usage: ", argv[0]);
3381                         error_printf("%s [host] [port] ", argv[0]);
3382                         free(ipc);
3383                         errno = EINVAL;
3384                         return 0;
3385                 }
3386         }
3387
3388         if ((!strcmp(cithost, "localhost")) || (!strcmp(cithost, "127.0.0.1"))) {
3389                 ipc->isLocal = 1;
3390         }
3391
3392         /* If we're using a unix domain socket we can do a bunch of stuff */
3393         if (!strcmp(cithost, UDS)) {
3394                 if (!strcasecmp(citport, DEFAULT_PORT)) {
3395                         snprintf(sockpath, sizeof sockpath, "%s", file_citadel_socket);
3396                 } else {
3397                         snprintf(sockpath, sizeof sockpath, "%s/%s", citport, "citadel.socket");
3398                 }
3399                 printf("[%s]\n", sockpath);
3400                 ipc->sock = uds_connectsock(&(ipc->isLocal), sockpath);
3401                 if (ipc->sock == -1) {
3402                         free(ipc);
3403                         return 0;
3404                 }
3405                 if (hostbuf != NULL)
3406                         strcpy(hostbuf, cithost);
3407                 if (portbuf != NULL)
3408                         strcpy(portbuf, sockpath);
3409                 strcpy(ipc->ip_hostname, "");
3410                 strcpy(ipc->ip_address, "");
3411                 return ipc;
3412         }
3413
3414         printf("[%s:%s]\n", cithost, citport);
3415         ipc->sock = tcp_connectsock(cithost, citport);
3416         if (ipc->sock == -1) {
3417                 free(ipc);
3418                 return 0;
3419         }
3420
3421
3422         /* Learn the actual network identity of the host to which we are connected */
3423
3424         struct sockaddr_in6 clientaddr;
3425         unsigned int addrlen = sizeof(clientaddr);
3426
3427         ipc->ip_hostname[0] = 0;
3428         ipc->ip_address[0] = 0;
3429
3430         getpeername(ipc->sock, (struct sockaddr *) &clientaddr, &addrlen);
3431         getnameinfo((struct sockaddr *) &clientaddr, addrlen, ipc->ip_hostname, sizeof ipc->ip_hostname, NULL, 0, 0);
3432         getnameinfo((struct sockaddr *) &clientaddr, addrlen, ipc->ip_address, sizeof ipc->ip_address, NULL, 0, NI_NUMERICHOST);
3433
3434         /* stuff other things elsewhere */
3435
3436         if (hostbuf != NULL)
3437                 strcpy(hostbuf, cithost);
3438         if (portbuf != NULL)
3439                 strcpy(portbuf, citport);
3440         return ipc;
3441 }
3442
3443
3444 /*
3445  * Disconnect and delete the IPC class (destructor)
3446  */
3447 void CtdlIPC_delete(CtdlIPC * ipc)
3448 {
3449 #ifdef HAVE_OPENSSL
3450         if (ipc->ssl) {
3451                 SSL_shutdown(ipc->ssl);
3452                 SSL_free(ipc->ssl);
3453                 ipc->ssl = NULL;
3454         }
3455 #endif
3456         if (ipc->sock > -1) {
3457                 shutdown(ipc->sock, 2); /* Close it up */
3458                 ipc->sock = -1;
3459         }
3460         if (ipc->Buf != NULL)
3461                 free(ipc->Buf);
3462         ipc->Buf = NULL;
3463         ipc->BufPtr = NULL;
3464         free(ipc);
3465 }
3466
3467
3468 /*
3469  * Disconnect and delete the IPC class (destructor)
3470  * Also NULLs out the pointer
3471  */
3472 void CtdlIPC_delete_ptr(CtdlIPC ** pipc)
3473 {
3474         CtdlIPC_delete(*pipc);
3475         *pipc = NULL;
3476 }
3477
3478
3479 /*
3480  * return the file descriptor of the server socket so we can select() on it.
3481  *
3482  * FIXME: This is only used in chat mode; eliminate it when chat mode gets
3483  * rewritten...
3484  */
3485 int CtdlIPC_getsockfd(CtdlIPC * ipc)
3486 {
3487         return ipc->sock;
3488 }
3489
3490
3491 /*
3492  * return one character
3493  *
3494  * FIXME: This is only used in chat mode; eliminate it when chat mode gets
3495  * rewritten...
3496  */
3497 char CtdlIPC_get(CtdlIPC * ipc)
3498 {
3499         char buf[2];
3500         char ch;
3501
3502         serv_read(ipc, buf, 1);
3503         ch = (int) buf[0];
3504
3505         return (ch);
3506 }