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